dxopal 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/.ignore +5 -0
  4. data/.nojekyll +4 -0
  5. data/CHANGELOG.md +90 -0
  6. data/DEVELOPMENT.md +57 -0
  7. data/Gemfile +2 -0
  8. data/Gemfile.lock +39 -0
  9. data/README.md +22 -0
  10. data/Rakefile +66 -0
  11. data/TODO.md +278 -0
  12. data/build/dxopal.js +46590 -0
  13. data/build/dxopal.min.js +1 -0
  14. data/config.ru +39 -0
  15. data/doc/api/DXOpal.html +129 -0
  16. data/doc/api/DXOpal/Font.html +485 -0
  17. data/doc/api/DXOpal/Image.html +2533 -0
  18. data/doc/api/DXOpal/Input.html +1086 -0
  19. data/doc/api/DXOpal/Input/MouseCodes.html +146 -0
  20. data/doc/api/DXOpal/RemoteResource.html +641 -0
  21. data/doc/api/DXOpal/Sound.html +568 -0
  22. data/doc/api/DXOpal/SoundEffect.html +444 -0
  23. data/doc/api/DXOpal/SoundEffect/WaveTypes.html +130 -0
  24. data/doc/api/DXOpal/Sprite.html +1419 -0
  25. data/doc/api/DXOpal/Window.html +1915 -0
  26. data/doc/api/_index.html +228 -0
  27. data/doc/api/class_list.html +51 -0
  28. data/doc/api/css/common.css +1 -0
  29. data/doc/api/css/full_list.css +58 -0
  30. data/doc/api/css/style.css +492 -0
  31. data/doc/api/file.CHANGELOG.html +162 -0
  32. data/doc/api/file.README.html +124 -0
  33. data/doc/api/file_list.html +61 -0
  34. data/doc/api/frames.html +17 -0
  35. data/doc/api/index.html +124 -0
  36. data/doc/api/js/app.js +248 -0
  37. data/doc/api/js/full_list.js +216 -0
  38. data/doc/api/js/jquery.js +4 -0
  39. data/doc/api/method_list.html +939 -0
  40. data/doc/api/top-level-namespace.html +110 -0
  41. data/doc/en/index.html +93 -0
  42. data/doc/ja/index.html +92 -0
  43. data/dxopal.gemspec +29 -0
  44. data/exe/dxopal +44 -0
  45. data/index.html +56 -0
  46. data/opal/dxopal.rb +54 -0
  47. data/opal/dxopal/constants/colors.rb +16 -0
  48. data/opal/dxopal/font.rb +20 -0
  49. data/opal/dxopal/image.rb +301 -0
  50. data/opal/dxopal/input.rb +170 -0
  51. data/opal/dxopal/input/key_codes.rb +168 -0
  52. data/opal/dxopal/remote_resource.rb +65 -0
  53. data/opal/dxopal/sound.rb +53 -0
  54. data/opal/dxopal/sound_effect.rb +84 -0
  55. data/opal/dxopal/sprite.rb +94 -0
  56. data/opal/dxopal/sprite/collision_area.rb +288 -0
  57. data/opal/dxopal/sprite/collision_check.rb +106 -0
  58. data/opal/dxopal/sprite/collision_checker.rb +169 -0
  59. data/opal/dxopal/sprite/physics.rb +82 -0
  60. data/opal/dxopal/version.rb +3 -0
  61. data/opal/dxopal/window.rb +173 -0
  62. data/template/index.html +13 -0
  63. data/template/main.rb +9 -0
  64. metadata +191 -0
@@ -0,0 +1,16 @@
1
+ module DXOpal
2
+ module Constants
3
+ # Pre-defined ARGB colors
4
+ module Colors
5
+ C_BLACK = [255, 0, 0, 0]
6
+ C_RED = [255, 255, 0, 0]
7
+ C_GREEN = [255, 0, 255, 0]
8
+ C_BLUE = [255, 0, 0, 255]
9
+ C_YELLOW = [255, 255, 255, 0]
10
+ C_CYAN = [255, 0, 255, 255]
11
+ C_MAGENTA = [255, 255, 0, 255]
12
+ C_WHITE = [255, 255, 255, 255]
13
+ C_DEFAULT = [0, 0, 0, 0]
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,20 @@
1
+ module DXOpal
2
+ class Font
3
+ def self.default; @@default ||= Font.new(24); end
4
+ def self.default=(f); @@default = f; end
5
+
6
+ def initialize(size, fontname=nil, option={})
7
+ @size = size
8
+ @orig_fontname = fontname
9
+ @fontname = fontname || "sans-serif"
10
+ end
11
+
12
+ def size; @size; end
13
+ def fontname; @orig_fontname; end
14
+
15
+ # Return a string like "48px serif"
16
+ def _spec_str
17
+ "#{@size}px #{@fontname}"
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,301 @@
1
+ require 'dxopal/remote_resource'
2
+
3
+ module DXOpal
4
+ class Image < RemoteResource
5
+ RemoteResource.add_class(Image)
6
+
7
+ # Load remote image (called via Window.load_resources)
8
+ def self._load(path_or_url)
9
+ raw_img = `new Image()`
10
+ img_promise = %x{
11
+ new Promise(function(resolve, reject) {
12
+ raw_img.onload = function() {
13
+ resolve(raw_img);
14
+ };
15
+ raw_img.src = path_or_url;
16
+ });
17
+ }
18
+
19
+ img = new(0, 0)
20
+ %x{
21
+ #{img_promise}.then(function(raw_img){
22
+ img.$_resize(raw_img.width, raw_img.height);
23
+ img.$_draw_raw_image(0, 0, raw_img);
24
+ });
25
+ }
26
+ return img, img_promise
27
+ end
28
+
29
+ # Create an instance of Image
30
+ def initialize(width, height, color=C_DEFAULT, canvas: nil)
31
+ @width, @height = width, height
32
+ @canvas = canvas || `document.createElement("canvas")`
33
+ @ctx = `#{@canvas}.getContext('2d')`
34
+ _resize(@width, @height)
35
+ box_fill(0, 0, @width, @height, color)
36
+ end
37
+ attr_reader :ctx, :canvas, :width, :height
38
+
39
+ # Set size of this image
40
+ def _resize(w, h)
41
+ @width, @height = w, h
42
+ %x{
43
+ #{@canvas}.width = w;
44
+ #{@canvas}.height = h;
45
+ }
46
+ end
47
+
48
+ # Draw an Image on this image
49
+ def draw(x, y, image)
50
+ %x{
51
+ #{@ctx}.drawImage(#{image.canvas}, x, y);
52
+ }
53
+ return self
54
+ end
55
+
56
+ # Draw an Image on this image with rotation
57
+ # - angle: Rotation angle (radian)
58
+ # - center_x, center_y: Rotation center in the `image` (default: center of the `image`)
59
+ def draw_rot(x, y, image, angle, center_x=nil, center_y=nil)
60
+ draw_ex(x, y, image, angle: angle, center_x: center_x, center_y: center_y)
61
+ end
62
+
63
+ def draw_ex(x, y, image, options={})
64
+ scale_x = options[:scale_x] || 1
65
+ scale_y = options[:scale_y] || 1
66
+ center_x = options[:center_x] || image.width/2
67
+ center_y = options[:center_y] || image.height/2
68
+ # TODO: alpha
69
+ # TODO: blend
70
+ angle = options[:angle] || 0
71
+
72
+ cx = x + center_x
73
+ cy = y + center_y
74
+ %x{
75
+ #{@ctx}.translate(cx, cy);
76
+ #{@ctx}.rotate(angle * Math.PI / 180.0);
77
+ #{@ctx}.scale(scale_x, scale_y);
78
+ #{@ctx}.drawImage(#{image.canvas}, x-cx, y-cy);
79
+ #{@ctx}.setTransform(1, 0, 0, 1, 0, 0); // reset
80
+ }
81
+ return self
82
+ end
83
+
84
+ # Draw some text on this image
85
+ def draw_font(x, y, string, font, color=[255,255,255])
86
+ ctx = @ctx
87
+ %x{
88
+ ctx.font = #{font._spec_str};
89
+ ctx.textBaseline = 'top';
90
+ ctx.fillStyle = #{_rgba(color)};
91
+ ctx.fillText(string, x, y);
92
+ }
93
+ return self
94
+ end
95
+
96
+ # Get a pixel as ARGB array
97
+ def [](x, y)
98
+ ctx = @ctx
99
+ ret = nil
100
+ %x{
101
+ var pixel = ctx.getImageData(x, y, 1, 1);
102
+ var rgba = pixel.data;
103
+ ret = [rgba[3], rgba[0], rgba[1], rgba[2]];
104
+ }
105
+ return ret
106
+ end
107
+
108
+ # Put a pixel on this image
109
+ def []=(x, y, color)
110
+ box_fill(x, y, x, y, color)
111
+ end
112
+
113
+ # Return true if the pixel at `(x, y)` has the `color`
114
+ def compare(x, y, color)
115
+ ctx = @ctx
116
+ rgba1 = _rgba_ary(color)
117
+ rgba2 = nil
118
+ ret = nil
119
+ %x{
120
+ var pixel = ctx.getImageData(x, y, 1, 1);
121
+ rgba2 = pixel.data;
122
+ // TODO: what is the right way to compare an Array and an Uint8ClampedArray?
123
+ ret = rgba1[0] == rgba2[0] &&
124
+ rgba1[1] == rgba2[1] &&
125
+ rgba1[2] == rgba2[2] &&
126
+ rgba1[3] == rgba2[3]
127
+ }
128
+ return ret
129
+ end
130
+
131
+ # Draw a line on this image
132
+ def line(x1, y1, x2, y2, color)
133
+ ctx = @ctx
134
+ %x{
135
+ ctx.beginPath();
136
+ ctx.strokeStyle = #{_rgba(color)};
137
+ ctx.moveTo(x1, y1);
138
+ ctx.lineTo(x2, y2);
139
+ ctx.stroke();
140
+ }
141
+ return self
142
+ end
143
+
144
+ # Draw a rectangle on this image
145
+ def box(x1, y1, x2, y2, color)
146
+ ctx = @ctx
147
+ %x{
148
+ ctx.beginPath();
149
+ ctx.strokeStyle = #{_rgba(color)};
150
+ ctx.rect(x1, y1, x2-x1+1, y2-y1+1);
151
+ ctx.stroke();
152
+ }
153
+ return self
154
+ end
155
+
156
+ # Draw a filled box on this image
157
+ def box_fill(x1, y1, x2, y2, color)
158
+ ctx = @ctx
159
+ %x{
160
+ ctx.beginPath();
161
+ ctx.fillStyle = #{_rgba(color)};
162
+ ctx.fillRect(x1, y1, x2-x1+1, y2-y1+1);
163
+ }
164
+ return self
165
+ end
166
+
167
+ # Draw a circle on this image
168
+ def circle(x, y, r, color)
169
+ ctx = @ctx
170
+ %x{
171
+ ctx.beginPath();
172
+ ctx.strokeStyle = #{_rgba(color)};
173
+ ctx.arc(x, y, r, 0, Math.PI*2, false)
174
+ ctx.stroke();
175
+ }
176
+ return self
177
+ end
178
+
179
+ # Draw a filled circle on this image
180
+ def circle_fill(x, y, r, color)
181
+ ctx = @ctx
182
+ %x{
183
+ ctx.beginPath();
184
+ ctx.fillStyle = #{_rgba(color)};
185
+ ctx.arc(x, y, r, 0, Math.PI*2, false)
186
+ ctx.fill();
187
+ }
188
+ return self
189
+ end
190
+
191
+ # Draw a triangle on this image
192
+ def triangle(x1, y1, x2, y2, x3, y3, color)
193
+ ctx = @ctx
194
+ %x{
195
+ ctx.beginPath();
196
+ ctx.strokeStyle = #{_rgba(color)};
197
+ ctx.moveTo(x1, y1);
198
+ ctx.lineTo(x2, y2);
199
+ ctx.lineTo(x3, y3);
200
+ ctx.lineTo(x1, y1);
201
+ ctx.stroke();
202
+ }
203
+ return self
204
+ end
205
+
206
+ # Draw a filled triangle on this image
207
+ def triangle_fill(x1, y1, x2, y2, x3, y3, color)
208
+ ctx = @ctx
209
+ %x{
210
+ ctx.beginPath();
211
+ ctx.fillStyle = #{_rgba(color)};
212
+ ctx.moveTo(x1, y1);
213
+ ctx.lineTo(x2, y2);
214
+ ctx.lineTo(x3, y3);
215
+ ctx.fill();
216
+ }
217
+ return self
218
+ end
219
+
220
+ # Fill this image with `color`
221
+ def fill(color)
222
+ box_fill(0, 0, @width-1, @height-1, color)
223
+ end
224
+
225
+ # Clear this image (i.e. fill with `[0,0,0,0]`)
226
+ def clear
227
+ fill([0, 0, 0, 0])
228
+ end
229
+
230
+ # Return an Image which is a copy of the specified area
231
+ def slice(x, y, width, height)
232
+ newimg = Image.new(width, height)
233
+ data = _image_data(x, y, width, height)
234
+ newimg._put_image_data(data, 0, 0)
235
+ return newimg
236
+ end
237
+
238
+ # Slice this image into xcount*ycount tiles
239
+ def slice_tiles(xcount, ycount)
240
+ tile_w = @width / xcount
241
+ tile_h = @height / ycount
242
+ return (0...ycount).flat_map{|v|
243
+ (0...xcount).map{|u|
244
+ slice(tile_w * u, tile_h * v, tile_w, tile_h)
245
+ }
246
+ }
247
+ end
248
+
249
+ # Copy an <img> onto this image
250
+ def _draw_raw_image(x, y, raw_img)
251
+ %x{
252
+ #{@ctx}.drawImage(#{raw_img}, x, y)
253
+ }
254
+ end
255
+
256
+ # Return .getImageData
257
+ def _image_data(x=0, y=0, w=@width, h=@height)
258
+ return `#{@ctx}.getImageData(x, y, w, h)`
259
+ end
260
+
261
+ # Call .putImageData
262
+ def _put_image_data(image_data, x, y)
263
+ `#{@ctx}.putImageData(image_data, x, y)`
264
+ end
265
+
266
+ # Return a string like 'rgb(255, 255, 255)'
267
+ # `color` is 3 or 4 numbers
268
+ def _rgb(color)
269
+ case color.length
270
+ when 4
271
+ # Just ignore alpha
272
+ rgb = color[1, 3]
273
+ when 3
274
+ rgb = color
275
+ else
276
+ raise "invalid color: #{color.inspect}"
277
+ end
278
+ return "rgb(" + rgb.join(', ') + ")";
279
+ end
280
+
281
+ # Return a string like 'rgba(255, 255, 255, 128)'
282
+ # `color` is 3 or 4 numbers
283
+ def _rgba(color)
284
+ return "rgba(" + _rgba_ary(color).join(', ') + ")"
285
+ end
286
+
287
+ # Return an array like `[255, 255, 255, 128]`
288
+ def _rgba_ary(color)
289
+ case color.length
290
+ when 4
291
+ # color is ARGB in DXRuby, so move A to the last
292
+ color[1, 3] + [color[0]/255.0]
293
+ when 3
294
+ # Complement 255 as alpha
295
+ color + [1.0]
296
+ else
297
+ raise "invalid color: #{color.inspect}"
298
+ end
299
+ end
300
+ end
301
+ end
@@ -0,0 +1,170 @@
1
+ module DXOpal
2
+ module Input
3
+ module MouseCodes
4
+ M_LBUTTON = 1
5
+ M_RBUTTON = 2
6
+ M_MBUTTON = 4
7
+ # DXOpal extention
8
+ M_4TH_BUTTON = 8
9
+ M_5TH_BUTTON = 16
10
+ end
11
+
12
+ def self._pressing_keys; @@pressing_keys; end
13
+
14
+ # Internal setup for Input class
15
+ def self._init(canvas)
16
+ @@tick = 0
17
+ @@pressing_keys = `new Object()`
18
+ @@mouse_info = `{x: 0, y: 0}`
19
+ @@pressing_mouse_buttons = `new Object()`
20
+
21
+ rect = `canvas.getBoundingClientRect()`
22
+ @@canvas_x = `rect.left + window.pageXOffset`
23
+ @@canvas_y = `rect.top + window.pageYOffset`
24
+
25
+ self._init_mouse_events
26
+ self.keyevent_target = `window` unless Input.keyevent_target
27
+ end
28
+
29
+ # Called on every frame from Window
30
+ def self._on_tick
31
+ @@tick += 1
32
+ end
33
+
34
+ # Return 1 if 'right', -1 if 'left'
35
+ def self.x(pad_number=0)
36
+ ret = 0
37
+ ret += 1 if key_down?(K_RIGHT)
38
+ ret -= 1 if key_down?(K_LEFT)
39
+ ret
40
+ end
41
+
42
+ # Return 1 if 'down', -1 if 'up'
43
+ def self.y(pad_number=0)
44
+ ret = 0
45
+ ret += 1 if key_down?(K_DOWN)
46
+ ret -= 1 if key_down?(K_UP)
47
+ ret
48
+ end
49
+
50
+ #
51
+ # Keyboard
52
+ #
53
+
54
+ # Return true if the key is being pressed
55
+ def self.key_down?(code)
56
+ return `#{@@pressing_keys}[code] > 0`
57
+ end
58
+
59
+ # Return true if the key is just pressed
60
+ def self.key_push?(code)
61
+ return `#{@@pressing_keys}[code] == #{@@tick}-1`
62
+ end
63
+
64
+ # Return true if the key is just released
65
+ def self.key_release?(code)
66
+ return `#{@@pressing_keys}[code] == -(#{@@tick}-1)`
67
+ end
68
+
69
+ # (private) JS keydown event handler
70
+ ON_KEYDOWN_ = %x{
71
+ function(ev){
72
+ #{Input._pressing_keys}[ev.keyCode] = #{@@tick};
73
+ ev.preventDefault();
74
+ ev.stopPropagation();
75
+ }
76
+ }
77
+ # (private) JS keyup event handler
78
+ ON_KEYUP_ = %x{
79
+ function(ev){
80
+ #{Input._pressing_keys}[ev.keyCode] = -#{@@tick};
81
+ ev.preventDefault();
82
+ ev.stopPropagation();
83
+ }
84
+ }
85
+ # Set DOM element to receive keydown/keyup event
86
+ #
87
+ # By default, `window` is set to this (i.e. all key events are
88
+ # stolen by DXOpal.) If canvas element is set to this, only key events
89
+ # happend on canvas are processed by DXOpal.
90
+ def self.keyevent_target=(target)
91
+ if @@keyevent_target
92
+ %x{
93
+ #{@@keyevent_target}.removeEventListener('keydown', #{ON_KEYDOWN_});
94
+ #{@@keyevent_target}.removeEventListener('keyup', #{ON_KEYUP_});
95
+ }
96
+ end
97
+ @@keyevent_target = target
98
+ %x{
99
+ if (#{@@keyevent_target}.tagName == "CANVAS") {
100
+ #{@@keyevent_target}.setAttribute('tabindex', 0);
101
+ }
102
+ #{@@keyevent_target}.addEventListener('keydown', #{ON_KEYDOWN_});
103
+ #{@@keyevent_target}.addEventListener('keyup', #{ON_KEYUP_});
104
+ }
105
+ end
106
+
107
+ # Return DOM element set by `keyevent_target=`
108
+ def self.keyevent_target; @@keyevent_target; end
109
+
110
+ #
111
+ # Mouse
112
+ #
113
+
114
+ # (internal) initialize mouse events
115
+ def self._init_mouse_events
116
+ %x{
117
+ document.addEventListener('mousemove', function(ev){
118
+ #{@@mouse_info}.x = ev.pageX - #{@@canvas_x};
119
+ #{@@mouse_info}.y = ev.pageY - #{@@canvas_y};
120
+ });
121
+ document.addEventListener('mousedown', function(ev){
122
+ #{@@mouse_info}.x = ev.pageX - #{@@canvas_x};
123
+ #{@@mouse_info}.y = ev.pageY - #{@@canvas_y};
124
+ for (var k=1; k<=16; k<<=1) {
125
+ if (ev.buttons & k) {
126
+ #{@@pressing_mouse_buttons}[k] = #{@@tick};
127
+ }
128
+ }
129
+ });
130
+ document.addEventListener('mouseup', function(ev){
131
+ #{@@mouse_info}.x = ev.pageX - #{@@canvas_x};
132
+ #{@@mouse_info}.y = ev.pageY - #{@@canvas_y};
133
+ for (var k=1; k<=16; k<<=1) {
134
+ if ((ev.buttons & k) == 0 && #{@@pressing_mouse_buttons}[k]) {
135
+ #{@@pressing_mouse_buttons}[k] = -#{@@tick};
136
+ }
137
+ }
138
+ });
139
+ }
140
+ end
141
+
142
+ # Return position of mouse cursor
143
+ # (0, 0) is the top-left corner of the canvas
144
+ def self.mouse_x
145
+ return `#{@@mouse_info}.x`
146
+ end
147
+ def self.mouse_y
148
+ return `#{@@mouse_info}.y`
149
+ end
150
+ class << self
151
+ alias mouse_pos_x mouse_x
152
+ alias mouse_pos_y mouse_y
153
+ end
154
+
155
+ # Return true if the mouse button is being pressed
156
+ def self.mouse_down?(mouse_code)
157
+ return `#{@@pressing_mouse_buttons}[mouse_code] > 0`
158
+ end
159
+
160
+ # Return true if the mouse button is pressed in the last tick
161
+ def self.mouse_push?(mouse_code)
162
+ return `#{@@pressing_mouse_buttons}[mouse_code] == -(#{@@tick}-1)`
163
+ end
164
+
165
+ # Return true if the mouse button is released in the last tick
166
+ def self.mouse_release?(mouse_code)
167
+ return `#{@@pressing_mouse_buttons}[mouse_code] == -(#{@@tick}-1)`
168
+ end
169
+ end
170
+ end