author_engine 0.5.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -17,66 +17,88 @@ class AuthorEngine
17
17
  @save_file = AuthorEngine::SaveFile.new(nil)
18
18
  @save_file.load(false, project_string)
19
19
 
20
- size = 16
21
-
22
- @levels = @save_file.levels
23
- @levels.each {|level| level.each {|sprite| sprite.x = sprite.x * size; sprite.y = sprite.y * size}}
24
-
25
20
  @sprites = []
26
21
  @spritesheet = nil
27
22
  @spritesheet_width = @save_file.sprites.columns
28
23
  @spritesheet_height = @save_file.sprites.rows
29
24
  @sprite_size = 16
30
25
 
31
- @fps = 0
32
- @counted_frames = 0
33
- @frame_count_stated_at = 0
34
-
35
26
  @game = Game.new(code: @save_file.code)
36
- build_spritesheet_and_sprites_list
37
27
  resize_canvas
38
28
 
39
- @collision_detection = AuthorEngine::CollisionDetection.new(@sprites, @levels)
40
- @game.collision_detection = @collision_detection
41
-
42
- @levels.each {|level| @collision_detection.add_level(level) }
43
-
44
- @game.init
29
+ @fps = 0
30
+ @counted_frames = 0
31
+ @frame_count_stated_at = 0
45
32
 
46
33
  @show_touch_controls = false
47
- @touch_buttons = []
48
- buttons = AuthorEngine::Part::OpalInput::BUTTONS
49
- key_states = AuthorEngine::Part::OpalInput::KEY_STATES
50
- @touch_buttons.push(
51
- TouchButton.new(
52
- label: "Up", color: @game.dark_gray, x: 137, y: `window.innerHeight/2 - #{125}`, width: 50, height: 50, side: :left,
53
- contact_proc: proc { key_states[buttons["up"]] = true}, no_contact_proc: proc { key_states[buttons["up"]] = false}
54
- ),
55
- TouchButton.new(
56
- label: "Down", color: @game.dark_gray, x: 137, y: `window.innerHeight/2 + 25`, width: 50, height: 50, side: :left,
57
- contact_proc: proc { key_states[buttons["down"]] = true}, no_contact_proc: proc { key_states[buttons["down"]] = false}
58
- ),
59
-
60
- TouchButton.new(
61
- label: "Left", color: @game.black, x: 175, width: 50, height: 50, side: :left,
62
- contact_proc: proc { key_states[buttons["left"]] = true}, no_contact_proc: proc { key_states[buttons["left"]] = false}
63
- ),
64
- TouchButton.new(
65
- label: "Right", color: @game.black, x: 100, width: 50, height: 50, side: :left,
66
- contact_proc: proc { key_states[buttons["right"]] = true}, no_contact_proc: proc { key_states[buttons["right"]] = false}
67
- ),
68
-
69
-
70
- TouchButton.new(
71
- label: "X", color: @game.red, x: 50, width: 50, height: 50, side: :right,
72
- contact_proc: proc { key_states[buttons["x"]] = true}, no_contact_proc: proc { key_states[buttons["x"]] = false}
73
- ),
74
- TouchButton.new(
75
- label: "Y", color: @game.yellow, x: 125, width: 50, height: 50, side: :right,
76
- contact_proc: proc { key_states[buttons["y"]] = true}, no_contact_proc: proc { key_states[buttons["y"]] = false}
77
- )
78
- )
79
- touch_handler_setup
34
+
35
+ @game_loaded = false
36
+
37
+ @loader_tasks = [
38
+ [
39
+ "Loading levels",
40
+ proc {
41
+ @levels = @save_file.levels
42
+ @levels.each {|level| level.each {|sprite| sprite.x = sprite.x * @sprite_size; sprite.y = sprite.y * @sprite_size}}
43
+ }
44
+ ],
45
+
46
+ [
47
+ "Evaluating game",
48
+ proc {
49
+ @game.authorengine_eval_code
50
+ },
51
+ ],
52
+
53
+ [
54
+ "Loading sprites",
55
+ proc {
56
+ build_spritesheet_and_sprites_list
57
+ },
58
+ ],
59
+
60
+ [
61
+ "Setting up collision detection",
62
+ proc {
63
+ @collision_detection = AuthorEngine::CollisionDetection.new(@sprites, @levels, @save_file.sprites)
64
+ @game.authorengine_collision_detection = @collision_detection
65
+ },
66
+ ],
67
+
68
+ [
69
+ "Initializing game",
70
+ proc {
71
+ @game.init
72
+ },
73
+ ],
74
+
75
+ [
76
+ "Setting up touch controls",
77
+ proc {
78
+ @touch_joystick = TouchJoystick.new(radius: 50)
79
+ @touch_buttons = []
80
+ @touch_buttons.push(
81
+ TouchButton.new(
82
+ label: "X", color: @game.red, width: 50, height: 50, for_key: "x"
83
+ ),
84
+ TouchButton.new(
85
+ label: "Y", color: @game.yellow, width: 50, height: 50, for_key: "y"
86
+ )
87
+ )
88
+
89
+ @fullscreen_button = TouchButton.new(label: "Fullscreen", color: @game.black, width: 100, height: 50)
90
+ touch_handler_setup
91
+ reposition_touch_controls
92
+ },
93
+ ],
94
+
95
+ [
96
+ "Loading done",
97
+ proc {
98
+ @game_loaded = true
99
+ },
100
+ ],
101
+ ]
80
102
 
81
103
  return self
82
104
  end
@@ -94,7 +116,14 @@ class AuthorEngine
94
116
 
95
117
  def run_game
96
118
  `window.requestAnimationFrame(function() {#{run_game}})` # placed here to ensure next frame is called even if draw or update throw an error
97
- `#{@game.canvas_context}.clearRect(0,0, window.innerWidth, window.innerHeight)`
119
+ width = `window.innerWidth`
120
+ height = `window.innerHeight`
121
+ game_width = 128 * @game.authorengine_scale
122
+ game_height = 128 * @game.authorengine_scale
123
+
124
+ area_width = (`window.innerWidth` - game_width) / 2
125
+
126
+ `#{@game.authorengine_canvas_context}.clearRect(#{area_width},0, #{game_width}, #{game_height})`
98
127
 
99
128
  @counted_frames+=1
100
129
 
@@ -104,25 +133,22 @@ class AuthorEngine
104
133
  @counted_frames = 0
105
134
  end
106
135
 
136
+ `#{@game.authorengine_canvas_context}.save()`
137
+ `#{@game.authorengine_canvas_context}.translate(window.innerWidth/2 - #{game_height/2}, 0)`
138
+ `#{@game.authorengine_canvas_context}.scale(#{@game.authorengine_scale}, #{@game.authorengine_scale})`
139
+ `#{@game.authorengine_canvas_context}.save()`
107
140
 
108
- if @sprites.size == (@spritesheet_width/@sprite_size)*(@spritesheet_height/@sprite_size)
109
- width = 128 * @game.scale
141
+ region = `new Path2D()`
142
+ `#{region}.rect(0, 0, 128, 128)`
143
+ `#{@game.authorengine_canvas_context}.clip(#{region})`
144
+ `#{@game.authorengine_canvas_context}.save()`
110
145
 
111
- # `#{@canvas_context}.setTransform(1, 0, 0, 1, 0, 0)`
112
- `#{@game.canvas_context}.save()`
113
- `#{@game.canvas_context}.translate(window.innerWidth/2 - #{width/2}, 0)`
114
- `#{@game.canvas_context}.scale(#{@game.scale}, #{@game.scale})`
115
- `#{@game.canvas_context}.save()`
116
146
 
117
- region = `new Path2D()`
118
- `#{region}.rect(0, 0, 128, 128)`
119
- `#{@game.canvas_context}.clip(#{region})`
120
- `#{@game.canvas_context}.save()`
147
+ if @game_loaded or @loader_tasks.empty?
121
148
  draw
122
-
123
- `#{@game.canvas_context}.restore()`
124
- `#{@game.canvas_context}.restore()`
125
- `#{@game.canvas_context}.restore()`
149
+ `#{@game.authorengine_canvas_context}.restore()`
150
+ `#{@game.authorengine_canvas_context}.restore()`
151
+ `#{@game.authorengine_canvas_context}.restore()`
126
152
 
127
153
  update
128
154
 
@@ -130,31 +156,89 @@ class AuthorEngine
130
156
  draw_touch_controls
131
157
  update_touch_controls
132
158
  end
159
+
133
160
  else
134
- @game.draw_background
135
- @game.text("Loading sprite #{@sprites.size}/#{(@spritesheet_width/@sprite_size)*(@spritesheet_height/@sprite_size)}.", 0, @game.height/2, 8)
161
+ task = @loader_tasks.shift
162
+ @game.rect(0, 0, @game.width, @game.height, @game.dark_purple)
163
+ @game.text("AuthorEngine v#{AuthorEngine::VERSION}", 2, @game.height / 2 - 20, 10)
164
+ @game.text("#{task[0]}...", 6, @game.height / 2 - 4, 8, 0, @game.light_gray)
165
+ @game.text("Empowered by Opal v#{Opal::VERSION}, a Ruby interpeter.", 4, @game.height - 6, 4, 0, @game.indigo)
166
+
167
+ `#{@game.authorengine_canvas_context}.restore()`
168
+ `#{@game.authorengine_canvas_context}.restore()`
169
+ `#{@game.authorengine_canvas_context}.restore()`
170
+
171
+ task[1].call
136
172
  end
137
173
 
138
174
  return nil
139
175
  end
140
176
 
141
177
  def draw_touch_controls
178
+ @fullscreen_button.draw
142
179
  @touch_buttons.each(&:draw)
180
+ @touch_joystick.draw
143
181
  end
144
182
 
145
183
  def update_touch_controls
146
- active_buttons = []
147
-
148
- @touch_buttons.each do |button|
149
- @current_touches.each do |id, touch|
150
- if touch.x.between?(button.x, button.x+button.width) && touch.y.between?(button.y, button.y+button.height)
151
- active_buttons << button
152
- button.active
153
- end
184
+ @touch_buttons.each { |button| button.trigger?(@current_touches) }
185
+ @touch_joystick.update(@current_touches)
186
+ end
187
+
188
+ def reposition_touch_controls
189
+ return nil unless @touch_joystick
190
+
191
+ width = `window.innerWidth`
192
+ height = `window.innerHeight`
193
+ game_width = 128 * @game.authorengine_scale
194
+ game_height = 128 * @game.authorengine_scale
195
+
196
+ # place controls under game
197
+ if width < height
198
+ area_width = width
199
+ area_height = height - game_height
200
+
201
+ puts "space: width #{area_width} x height #{area_height}"
202
+
203
+ @touch_joystick.x = @touch_joystick.radius + @touch_joystick.radius
204
+ @touch_joystick.y = game_height + area_height / 2
205
+
206
+ padding = 10
207
+ last_x = 20
208
+ @touch_buttons.reverse.each do |button|
209
+ button.x = width - (last_x + button.width)
210
+ button.y = (height - area_height) + area_height / 2 - button.height / 2
211
+
212
+ last_x += button.width + padding
154
213
  end
214
+
215
+ @fullscreen_button.x = width - (width / 2 + @fullscreen_button.width / 2)
216
+ @fullscreen_button.y = height - @fullscreen_button.height
217
+
218
+ # place controls beside game
219
+ else
220
+ area_width = (`window.innerWidth` - game_width) / 2
221
+ area_height = game_height
222
+
223
+ puts "space: width #{area_width} x height #{area_height}"
224
+
225
+ @touch_joystick.x = @touch_joystick.radius + @touch_joystick.radius
226
+ @touch_joystick.y = game_height / 2
227
+
228
+ padding = 10
229
+ last_x = 50
230
+ @touch_buttons.reverse.each do |button|
231
+ button.x = width - (last_x + button.width)
232
+ button.y = game_height / 2 - button.height / 2
233
+
234
+ last_x += button.width + padding
235
+ end
236
+
237
+ @fullscreen_button.x = width - @fullscreen_button.width
238
+ @fullscreen_button.y = 0
155
239
  end
156
240
 
157
- (@touch_buttons - active_buttons).each(&:inactive)
241
+ return nil
158
242
  end
159
243
 
160
244
  def resize_canvas
@@ -162,17 +246,20 @@ class AuthorEngine
162
246
  height = `window.innerHeight`
163
247
 
164
248
  if width < height
165
- @game.scale = `window.innerWidth / 128.0`
249
+ @game.authorengine_scale = `#{width} / 128.0`
166
250
  else
167
- @game.scale = `window.innerHeight / 128.0`
251
+ @game.authorengine_scale = `#{height} / 128.0`
168
252
  end
169
253
 
170
- `#{@game.canvas}.width = #{width}`
171
- `#{@game.canvas}.height = #{height}`
172
- `#{@game.canvas}.style.width = #{width}`
173
- `#{@game.canvas}.style.height = #{height}`
254
+ `#{@game.authorengine_canvas}.width = #{width}`
255
+ `#{@game.authorengine_canvas}.height = #{height}`
256
+ `#{@game.authorengine_canvas}.style.width = #{width}`
257
+ `#{@game.authorengine_canvas}.style.height = #{height}`
258
+
259
+ `#{@game.authorengine_canvas_context}.imageSmoothingEnabled = false`
174
260
 
175
- `#{@game.canvas_context}.imageSmoothingEnabled = false`
261
+ reposition_touch_controls
262
+ `#{@game.authorengine_canvas_context}.clearRect(0, 0, window.innerWidth, window.innerHeight)`
176
263
  return nil
177
264
  end
178
265
 
@@ -212,7 +299,6 @@ class AuthorEngine
212
299
  (width/size).times do |x|
213
300
  `#{temp_canvas_context}.clearRect(0,0, #{size}, #{size})`
214
301
  `#{temp_canvas_context}.drawImage(#{@spritesheet}, #{x * size}, #{y * size}, #{size}, #{size}, 0, 0, #{size}, #{size})`
215
- @collision_detection.add_sprite(`#{temp_canvas_context}.getImageData(0,0, #{size}, #{size}).data`)
216
302
 
217
303
  `createImageBitmap(#{@spritesheet}, #{x * size}, #{y * size}, #{size}, #{size}).then(sprite => { #{@sprites.push(`sprite`)} })`
218
304
  end
@@ -221,19 +307,26 @@ class AuthorEngine
221
307
  return nil
222
308
  end
223
309
 
310
+ def fullscreen_changed
311
+ resize_canvas
312
+ end
313
+
224
314
  def show(update_interval = (1000.0 / 60))
225
315
  return unless RUBY_ENGINE == "opal"
226
316
 
227
317
  `window.addEventListener('resize', () => { #{resize_canvas} })`
228
- `document.addEventListener('keydown', (event) => { #{AuthorEngine::Part::OpalInput::KEY_STATES[`event.key`] = true} })`
318
+ `document.addEventListener('keydown', (event) => { #{@show_touch_controls = false; AuthorEngine::Part::OpalInput::KEY_STATES[`event.key`] = true} })`
229
319
  `document.addEventListener('keyup', (event) => { #{AuthorEngine::Part::OpalInput::KEY_STATES[`event.key`] = false} })`
230
320
 
231
- `#{@game.canvas}.addEventListener('touchstart', (event) => { #{@show_touch_controls = true; handle_touch_start(`event`); puts "Touch started..."} })`
232
- `#{@game.canvas}.addEventListener('touchmove', (event) => { #{handle_touch_move(`event`); puts "Touch moved..."} })`
233
- `#{@game.canvas}.addEventListener('touchcancel', (event) => { #{handle_touch_cancel(`event`); puts "Touch canceled."} })`
234
- `#{@game.canvas}.addEventListener('touchend', (event) => { #{handle_touch_end(`event`); puts "Touch Ended."} })`
321
+ `#{@game.authorengine_canvas}.addEventListener('touchstart', (event) => { #{@show_touch_controls = true; handle_touch_start(`event`)} })`
322
+ `#{@game.authorengine_canvas}.addEventListener('touchmove', (event) => { #{handle_touch_move(`event`)} })`
323
+ `#{@game.authorengine_canvas}.addEventListener('touchcancel', (event) => { #{handle_touch_cancel(`event`)} })`
324
+ `#{@game.authorengine_canvas}.addEventListener('touchend', (event) => { #{handle_touch_end(`event`)} })`
325
+
326
+ `#{@game.authorengine_canvas}.addEventListener('fullscreenchange', () => { #{fullscreen_changed} })`
235
327
 
236
328
  `document.getElementById('loading').style.display = "none"`
329
+
237
330
  `window.requestAnimationFrame(function() {#{run_game}})`
238
331
  return nil
239
332
  end
@@ -2,8 +2,8 @@ class AuthorEngine
2
2
  class Part
3
3
  module OpalGraphics
4
4
  def rect(x = 0, y = 0, width = 1, height = 1, color = "white", z = 0)
5
- `#{@canvas_context}.fillStyle = #{color}`
6
- `#{@canvas_context}.fillRect(#{x}, #{y}, #{width}, #{height})`
5
+ `#{@authorengine_canvas_context}.fillStyle = #{color}`
6
+ `#{@authorengine_canvas_context}.fillRect(#{x}, #{y}, #{width}, #{height})`
7
7
  return nil
8
8
  end
9
9
 
@@ -11,25 +11,25 @@ class AuthorEngine
11
11
  size = 16 # sprite size
12
12
  sprites = AuthorEngine::GameRunner.instance.sprites
13
13
 
14
- `#{@canvas_context}.save()`
14
+ `#{@authorengine_canvas_context}.save()`
15
15
  if alpha <= 0
16
16
  alpha = 0
17
17
  else
18
18
  alpha = (alpha / 255.0)
19
19
  end
20
- `#{@canvas_context}.globalAlpha = #{alpha}`
20
+ `#{@authorengine_canvas_context}.globalAlpha = #{alpha}`
21
21
 
22
- `#{@canvas_context}.drawImage(#{sprites[sprite_sheet_index]}, #{x}, #{y})`
22
+ `#{@authorengine_canvas_context}.drawImage(#{sprites[sprite_sheet_index]}, #{x}, #{y})`
23
23
 
24
- `#{@canvas_context}.restore()`
24
+ `#{@authorengine_canvas_context}.restore()`
25
25
  end
26
26
 
27
27
  def text(string, x = 0, y = 0, size = 4, z = 0, color = "white")
28
28
  font = "#{size}px Connection, Consolas"
29
- `#{@canvas_context}.font = #{font}`
30
- `#{@canvas_context}.fillStyle = #{color}`
31
- `#{@canvas_context}.textBaseline = "top"`
32
- `#{@canvas_context}.fillText(#{string}, #{x}, #{y})`
29
+ `#{@authorengine_canvas_context}.font = #{font}`
30
+ `#{@authorengine_canvas_context}.fillStyle = #{color}`
31
+ `#{@authorengine_canvas_context}.textBaseline = "top"`
32
+ `#{@authorengine_canvas_context}.fillText(#{string}, #{x}, #{y})`
33
33
  end
34
34
 
35
35
  def level(index, z = 0)
@@ -45,24 +45,24 @@ class AuthorEngine
45
45
  _level = AuthorEngine::GameRunner.instance.levels[level]
46
46
  raise "No level at '#{index}'!" unless _level
47
47
  raise "No sprite at '#{current_sprite}'!" unless AuthorEngine::GameRunner.instance.sprites[current_sprite]
48
- raise "No sprite at '#{current_sprite}'!" unless AuthorEngine::GameRunner.instance.sprites[replacement_sprite]
48
+ raise "No sprite at '#{replacement_sprite}'!" unless AuthorEngine::GameRunner.instance.sprites[replacement_sprite]
49
49
 
50
50
  _level.each {|sprite| sprite.sprite = replacement_sprite if sprite.sprite == current_sprite}
51
51
  end
52
52
 
53
53
  def translate(x, y, &block)
54
- `#{@canvas_context}.save()`
55
- `#{@canvas_context}.translate(#{x}, #{y})`
54
+ `#{@authorengine_canvas_context}.save()`
55
+ `#{@authorengine_canvas_context}.translate(#{x}, #{y})`
56
56
  block.call if block
57
- `#{@canvas_context}.restore()`
57
+ `#{@authorengine_canvas_context}.restore()`
58
58
  end
59
59
 
60
60
  def rotate(angle, x = 0, y = 0, &block)
61
- `#{@canvas_context}.save()`
62
- `#{@canvas_context}.translate(#{x}, #{y})`
63
- `#{@canvas_context}.rotate(#{angle})`
61
+ `#{@authorengine_canvas_context}.save()`
62
+ `#{@authorengine_canvas_context}.translate(#{x}, #{y})`
63
+ `#{@authorengine_canvas_context}.rotate(#{angle})`
64
64
  block.call if block
65
- `#{@canvas_context}.restore()`
65
+ `#{@authorengine_canvas_context}.restore()`
66
66
  end
67
67
  end
68
68
  end
@@ -1,36 +1,57 @@
1
1
  class AuthorEngine
2
2
  class TouchButton
3
- attr_reader :x, :y, :width, :height
4
- def initialize(label:, color:, x:, y: nil, width:, height:, side:, contact_proc:, no_contact_proc:)
3
+ attr_accessor :x, :y
4
+ attr_reader :width, :height
5
+ def initialize(label:, color:, x: 0, y: 0, width:, height:, for_key: nil, &block)
5
6
  @label, @color, @x, @y, @width, @height = label, color, x, y, width, height
6
- @side, @contact_proc, @no_contact_proc = side, contact_proc, no_contact_proc
7
+ @for_key = for_key
8
+ @block = block
9
+
10
+ @buttons = AuthorEngine::Part::OpalInput::BUTTONS
11
+ @key_states = AuthorEngine::Part::OpalInput::KEY_STATES
7
12
 
8
13
  @game = AuthorEngine::GameRunner.instance.game
9
- @game_width = 128 * @game.scale
14
+ @game_width = 128 * @game.authorengine_scale
10
15
  @game_x = `window.innerWidth/2 - #{@game_width/2}`
16
+ end
11
17
 
12
- if @side == :left
13
- @x = @game_x-@x
14
- elsif @side == :right
15
- @x = @game_x+@game_width+@x
16
- else
17
- raise "side must be :left or :right"
18
- end
18
+ def draw
19
+ `#{@game.authorengine_canvas_context}.fillStyle = #{@color}`
20
+ `#{@game.authorengine_canvas_context}.fillRect(#{@x}, #{@y}, #{@width}, #{@height})`
19
21
 
20
- @y = `window.innerHeight/2 - #{height}` unless @y.is_a?(Numeric)
22
+ font = "#{@height}px Connection, Consolas"
23
+ `#{@game.authorengine_canvas_context}.font = #{font}`
24
+ `#{@game.authorengine_canvas_context}.fillStyle = "white"`
25
+ `#{@game.authorengine_canvas_context}.textBaseline = "top"`
26
+ `#{@game.authorengine_canvas_context}.fillText(#{@label}, #{@x}, #{@y}, #{@width})`
21
27
  end
22
28
 
23
- def draw
24
- `#{@game.canvas_context}.fillStyle = #{@color}`
25
- `#{@game.canvas_context}.fillRect(#{@x}, #{@y}, #{width}, #{width})`
29
+ def trigger?(touches)
30
+ triggered = false
31
+
32
+ touches.detect do |id, touch|
33
+ if touch.x.between?(@x, @x+@width) && touch.y.between?(@y, @y+@height)
34
+ triggered = true
35
+ end
36
+ end
37
+
38
+
39
+ if @for_key
40
+ active if triggered
41
+ inactive unless triggered
42
+ else
43
+ @block.call if @block && triggered
44
+ end
45
+
46
+ return triggered
26
47
  end
27
48
 
28
49
  def active
29
- @contact_proc.call
50
+ @key_states[@buttons[@for_key]] = true
30
51
  end
31
52
 
32
53
  def inactive
33
- @no_contact_proc.call
54
+ @key_states[@buttons[@for_key]] = false
34
55
  end
35
56
  end
36
57
  end