fantasy 0.1.1 → 0.1.5.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 98083689a972e68c27f07ae5c9c8ef9818c1b1215fbf2baccc541233efca6827
4
- data.tar.gz: db32ef03fd54c4711b9b38b9f4e3f4ad587839fc4d78523982c6c69f85cb0ab4
3
+ metadata.gz: 58bcb1e825d5cdf5185a26d5bd1dd972fb585ddeefa8fab964984e4ddd975584
4
+ data.tar.gz: eea7d4e258d2a3f949b97b187fd540e9e7702515fcac381c5ae53b244f06c546
5
5
  SHA512:
6
- metadata.gz: 5a6d2c503880c672d147909cd5ab5a88639ab4e3e019ea5b81601c4aa0cc967e03e5c8b48f1994309ab379e83012f5a103043ba6b64989ba8009d2c7e3950174
7
- data.tar.gz: 93f37ba3a6fde17d55ba7aa203a1ade6645e69af8d670d37b5a7d78442c5c1dc4bb115784520cbd5b6a9ef661b18e6dbc2b094dab4c2a54feca8d37e4c527303
6
+ metadata.gz: 9a4ec479708e117036d068ddcf3c5f33f0338a85e324ca7708bd1922c41c632804e90733fea5c3d2dc258f2d00e2b8fbf37e30d4cf4e0afec70e663af2125319
7
+ data.tar.gz: 2beeb1fdb476866e25db35ee12559c7571656e07a0ab9c112d832ec1a0fdffa520f471fc4054bc67a4e14154f445aa9e9b8d5e889d988f64920f04931c53bb01
data/README.md CHANGED
@@ -1,11 +1,83 @@
1
1
  # Ruby in Fantasy
2
2
 
3
+ Simple toolbox library and lean API to build great mini games in Ruby.
4
+
3
5
  An upper layer over Gosu library to offer a more friendly API for an easy and lean game development.
4
6
 
5
7
  Specially intended to use Ruby as a learning language to introduce children into programming.
6
8
 
7
9
  **Attention**: This project is in early development phase, right now it is just an experiment
8
10
 
11
+ ## Use
12
+
13
+ ### Hello World
14
+
15
+ ```ruby
16
+ require "fantasy"
17
+
18
+ SCREEN_WIDTH = 320
19
+ SCREEN_HEIGHT = 240
20
+
21
+ on_game do
22
+ message = HudText.new(position: Coordinates.new(10, 10))
23
+ message.text = "Hello, World!"
24
+ message.size = "big"
25
+ end
26
+
27
+ start!
28
+ ```
29
+
30
+ ### Action game
31
+
32
+ ```ruby
33
+ SCREEN_WIDTH = 640
34
+ SCREEN_HEIGHT = 360
35
+
36
+ on_game do
37
+ player = Actor.new("warrior")
38
+ player.position = Coordinates.new(100, 100)
39
+ points = HudText.new(position: Coordinates.new(10, 20))
40
+ points.text = 0
41
+
42
+ on_space_bar do
43
+ Sound.play("shoot")
44
+ bullet = Actor.new("bullet")
45
+ bullet.position = player.position
46
+ bullet.speed = 100
47
+ bullet.direction = Coordinates.up
48
+ bullet.on_collision do |other|
49
+ if other.name == "enemy"
50
+ Sound.play("impact")
51
+ other.destroy
52
+ bullet.destroy
53
+ points.text += 1
54
+ end
55
+ end
56
+ end
57
+
58
+ player.on_collision do |other|
59
+ if other.name == "enemy"
60
+ Sound.play("game_over")
61
+ Global.go_to_end
62
+ end
63
+ end
64
+ end
65
+
66
+ on_end do
67
+ HudText.new(position: Coordinates.new(10, 100), text: "You are dead. Press space to re-tart")
68
+
69
+ on_space_bar do
70
+ Global.go_to_game
71
+ end
72
+ end
73
+
74
+ start!
75
+ ```
76
+
77
+ ## Examples
78
+
79
+ See the [Ruby in Fantasy Games Collection](https://github.com/fguillen/RubyInFantasyGames).
80
+
9
81
  ## Installation
10
82
 
11
83
  Add this line to your application's Gemfile:
@@ -24,6 +96,19 @@ Or install it yourself as:
24
96
 
25
97
  ## Features
26
98
 
99
+ ### Game Scene transitions
100
+
101
+ Easy to configure 3 basic game states:
102
+
103
+ - Game presentation scene
104
+ - Game game scene
105
+ - Game end scene
106
+ - Other scenes, like levels or such (TODO)
107
+
108
+ Each state should be independent and unique Actors and other elements can be created and configured for each state
109
+
110
+ Built-in mechanism to move from one state to another.
111
+
27
112
  ### Actor
28
113
 
29
114
  Managing game elements which have (optionally) image, movement and collision
@@ -36,6 +121,7 @@ Managing game elements which have (optionally) image, movement and collision
36
121
  - Gravity (TODO)
37
122
  - Animations (TODO)
38
123
  - Possibility to extend Actor class or instantiate it directly for simple characters
124
+ - Allowing magic instance properties (Like in OpenStruct). So programmer can do `actor.stuff = 1` and it is valid (TODO)
39
125
 
40
126
  ### Clock
41
127
 
@@ -79,7 +165,7 @@ For easy creation of head-up display components.
79
165
 
80
166
  ### Debug Mode
81
167
 
82
- When active different attributes from all the Actors and HUD elements will be visible in the screen.
168
+ Press `d` to activate it. When active debug visuals are shown:
83
169
 
84
170
  - Position
85
171
  - Collider
@@ -99,18 +185,6 @@ Actors in the game will be rendered in the relative position to this camera.
99
185
 
100
186
  - Not deal with RGB or anything, just a list of colors (TODO)
101
187
 
102
- ### Game Scene transitions
103
-
104
- Easy to configure 3 basic game states:
105
-
106
- - Game presentation scene
107
- - Game game scene
108
- - Game end scene
109
- - Other scenes, like levels or such (TODO)
110
-
111
- Each state should be independent and unique Actors and other elements can be created and configured for each state
112
-
113
- Built-in mechanism to move from one state to another.
114
188
 
115
189
  ### Pause Game (TODO)
116
190
 
@@ -129,8 +203,8 @@ Direct and easy way to play a sound
129
203
  Simple way to set up:
130
204
 
131
205
  - Background color
132
- - Image background (TODO)
133
- - Repeatable image background (TODO)
206
+ - Image background
207
+ - Repeatable image background
134
208
 
135
209
  ### Data Persistance (TODO)
136
210
 
@@ -143,20 +217,224 @@ Easy access to keyboard and mouse inputs on any part of the code. Specially in t
143
217
  - Allow "on_space_bar" and each Actor (TODO)
144
218
  - Allow multiple "on_space_bar" (TODO)
145
219
  - Remove "on_space_bar" when changing scene (TODO)
220
+ - Detect when key/mouse button is pressed in the actual frame in any part of the code (TODO)
221
+
222
+ ### Tile Map (TODO)
223
+
224
+ For easy creation of:
225
+
226
+ - Top-down map levels (TODO)
227
+ - Platformer map levels (TODO)
228
+
229
+ ### Tweens (TODO)
230
+
231
+ Multiple movement animation effects like in [DoTween](http://dotween.demigiant.com/documentation.php) (TODO)
146
232
 
147
233
  ## API
148
234
 
235
+ ### Game Scene transitions
236
+
237
+ Configure your game elements on each Scene:
238
+
239
+ ```ruby
240
+ SCREEN_WIDTH = 640
241
+ SCREEN_HEIGHT = 360
242
+
243
+ # (Optional)
244
+ on_presentation do
245
+ # Game elements running when the game loads
246
+ end
247
+
248
+ on_game do
249
+ # Game elements running when in game Scene
250
+ end
251
+
252
+ # (Optional)
253
+ on_end do
254
+ # Game elements running when game is ended
255
+ end
256
+ ```
257
+
258
+ How to go from Scene to Scene:
259
+
260
+ ```ruby
261
+ Global.go_to_presentation
262
+ Global.go_to_game
263
+ Global.go_to_end
264
+ ```
265
+
266
+ #### Example
267
+
268
+ ```ruby
269
+ on_presentation do
270
+ HudText.new(position: Coordinates.new(10, 100), text: "Press space to start")
271
+
272
+ on_space_bar do
273
+ Global.go_to_game
274
+ end
275
+ end
276
+
277
+ on_game do
278
+ # [...]
279
+ if player.dead
280
+ Global.go_to_end
281
+ end
282
+ end
283
+
284
+ on_end do
285
+ HudText.new(position: Coordinates.new(10, 100), text: "You are dead. Press space to re-tart")
286
+
287
+ on_space_bar do
288
+ Global.go_to_presentation
289
+ end
290
+ end
291
+ ```
292
+
293
+ ### Actor
294
+
295
+ Actor can be used directly:
296
+
297
+ ```ruby
298
+ player = Actor.new("warrior") # ./images/warrior.png
299
+ player.position = Coordinates.new(100, 100)
300
+ player.solid = true
301
+ player.speed = 200
302
+ player.layer = 1
303
+ player.move_with_cursors
304
+
305
+ player.on_collision do |other|
306
+ if other.name == "enemy"
307
+ player.destroy
308
+ end
309
+ end
310
+
311
+ player.on_after_move do
312
+ if player.position.x > SCREEN_WIDTH
313
+ player.position.x = SCREEN_WIDTH
314
+ end
315
+
316
+ if player.position.x < 0
317
+ player.position.x = 0
318
+ end
319
+ end
320
+ ```
321
+
322
+ Or in a subclass:
323
+
324
+ ```ruby
325
+ class Player < Actor
326
+ def initialize
327
+ super("warrior") # ./images/warrior.png
328
+ @position = Coordinates.new(100, 100)
329
+ @solid = true
330
+ @speed = 200
331
+ @layer = 1
332
+ @direction = Coordinates.zero
333
+ move_with_cursors
334
+ end
335
+
336
+ on_collision do |other|
337
+ if other.name == "enemy"
338
+ destroy
339
+ end
340
+ end
341
+
342
+ on_after_move do
343
+ if @position.x > SCREEN_WIDTH
344
+ @position.x = SCREEN_WIDTH
345
+ end
346
+
347
+ if @position.x < 0
348
+ @position.x = 0
349
+ end
350
+ end
351
+ end
352
+ ```
353
+
354
+ ### Clock
355
+
356
+ ```ruby
357
+ clock =
358
+ Clock.new do
359
+ enemy.attack
360
+ sleep(1)
361
+ enemy.defend
362
+ end
363
+
364
+ clock.run_now
365
+ clock.run_on(seconds: 2)
366
+ clock.repeat(seconds: 2, times: 10)
367
+ clock.stop
368
+ ```
369
+
370
+ ### Background
371
+
372
+ ```ruby
373
+ # Simple color
374
+ on_presentation do
375
+ Global.background = Color.new(r: 34, g: 35, b: 35)
376
+ end
377
+
378
+ # Replicable (by default) Image
379
+ # position is relative to Global.camera
380
+ on_game do
381
+ background = Background.new(image_name: "beach")
382
+ # background.replicable = false # if you don't want the image to replicate
383
+ background.scale = 6
384
+ end
385
+ ```
386
+
387
+ ### Camera
388
+
389
+ ```ruby
390
+ on_game do
391
+ on_loop do
392
+ # Camera follows player
393
+ Global.camera.position.y = player.position.y - (SCREEN_HEIGHT / 2)
394
+ end
395
+ end
396
+ ```
397
+
398
+ ### Sound
399
+
400
+ ```ruby
401
+ Sound.play("shoot") # ./sounds/shoot.wav
402
+ ```
403
+
404
+
405
+ ### UI
406
+
407
+ #### HUD Text
408
+
409
+ ```ruby
410
+ timer = HudText.new(position: Coordinates.new(20, 10))
411
+ timer.text = 0
412
+ timer.size = "big"
413
+
414
+ Clock.new { timer.text += 1 }.repeat(seconds: 1)
415
+ ```
416
+
417
+ #### HUD Image
418
+
419
+ ```ruby
420
+ icon = HudImage.new(position: Coordinates.new(SCREEN_WIDTH - 220, 8), image_name: "ring")
421
+ icon.scale = 4
422
+ icon.visible = true
423
+
424
+ Clock.new { icon.visible = !icon.visible }.repeat(seconds: 1)
425
+ ```
149
426
 
150
427
 
151
428
  ## Credits for assets
152
429
 
153
- - Sprites: www.kenney.nl
154
430
  - Font: VT323 Project Authors (peter.hull@oikoi.com)
155
431
 
156
432
  ## Bugs
157
433
 
158
434
  - When dragging in debug mode new elements are being added to the drag (TODO)
159
435
  - Rubocop is not passing
436
+ - Tests are missing
437
+ - Allow Ruby 2.5+
160
438
 
161
439
  ## Development
162
440
 
data/fantasy.gemspec CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |spec|
8
8
  spec.authors = ["Fernando Guillen"]
9
9
  spec.email = ["fguillen.mail@gmail.com"]
10
10
 
11
- spec.summary = "Simple toolbox library and lean API to build great mini games"
12
- spec.description = "Simple toolbox library and lean API to build great mini games"
11
+ spec.summary = "Simple toolbox library and lean API to build great mini games in Ruby"
12
+ spec.description = "Simple toolbox library and lean API to build great mini games in Ruby"
13
13
  spec.homepage = "https://github.com/fguillen/fantasy"
14
14
  spec.license = "MIT"
15
15
  spec.required_ruby_version = ">= 3.0.0"
data/lib/fantasy/actor.rb CHANGED
@@ -1,8 +1,11 @@
1
1
  class Actor
2
+ include MoveByCursor
3
+
2
4
  attr_reader :image, :moving_with_cursors
3
- attr_accessor :position, :direction, :speed, :solid, :scale, :name, :layer
5
+ attr_accessor :image_name, :position, :direction, :speed, :solid, :scale, :name, :layer
4
6
 
5
7
  def initialize(image_name)
8
+ @image_name = image_name
6
9
  @image = Image.new(image_name)
7
10
  @name = image_name
8
11
  @position = Coordinates.new(0, 0)
@@ -80,13 +83,12 @@ class Actor
80
83
  @position = @position + (@direction * @speed * Global.frame_time)
81
84
 
82
85
  if solid?
83
- actual_collisions = collisions
84
- actual_collisions.each do |actor|
86
+ collisions.each do |actor|
85
87
  collision_with(actor)
86
88
  actor.collision_with(self)
87
89
  end
88
90
 
89
- @position = @last_position if actual_collisions.any?
91
+ @position = @last_position if collisions.any? # we don't cache collisions because position may be changed on collision callback
90
92
  end
91
93
  end
92
94
  end
@@ -114,20 +116,6 @@ class Actor
114
116
  @on_collision_callback.call(actor) unless @on_collision_callback.nil?
115
117
  end
116
118
 
117
- def calculate_direction_by_cursors
118
- if Gosu.button_down?(Gosu::KB_DOWN)
119
- @direction = Coordinates.down
120
- elsif Gosu.button_down?(Gosu::KB_UP)
121
- @direction = Coordinates.up
122
- elsif Gosu.button_down?(Gosu::KB_RIGHT)
123
- @direction = Coordinates.right
124
- elsif Gosu.button_down?(Gosu::KB_LEFT)
125
- @direction = Coordinates.left
126
- else
127
- @direction = Coordinates.zero
128
- end
129
- end
130
-
131
119
  def collisions
132
120
  Global.actors.reject { |e| e == self }.select { |e| e.solid? }.select do |actor|
133
121
  Utils.collision? self, actor
@@ -138,4 +126,37 @@ class Actor
138
126
  @on_destroy_callback.call unless @on_destroy_callback.nil?
139
127
  Global.actors.delete(self)
140
128
  end
129
+
130
+ def clone
131
+ actor = self.class.new(@image_name)
132
+ actor.image_name = @image_name
133
+ actor.name = @name
134
+ actor.position = @position.clone
135
+ actor.direction = @direction.clone
136
+ actor.speed = @speed
137
+ actor.scale = @scale
138
+ actor.moving_with_cursors if @moving_with_cursors
139
+ actor.solid = @solid
140
+ actor.layer = @layer
141
+
142
+ actor.on_after_move_callback = @on_after_move_callback
143
+ actor.on_collision_callback = @on_collision_callback
144
+ actor.on_destroy_callback = @on_destroy_callback
145
+
146
+ actor
147
+ end
148
+
149
+ protected
150
+
151
+ def on_after_move_callback=(block)
152
+ @on_after_move_callback = block
153
+ end
154
+
155
+ def on_collision_callback=(block)
156
+ @on_collision_callback = block
157
+ end
158
+
159
+ def on_destroy_callback=(block)
160
+ @on_destroy_callback = block
161
+ end
141
162
  end
@@ -0,0 +1,62 @@
1
+ class Background
2
+ attr_accessor :scale, :color, :visible, :position, :layer
3
+
4
+ def initialize(image_name:)
5
+ @image = Image.new(image_name)
6
+ @name = image_name
7
+ @position = Coordinates.zero
8
+ @scale = 1
9
+ @visible = true
10
+ @draggable_on_debug = true
11
+ @dragging = false
12
+ @layer = -100
13
+ @replicable = true
14
+
15
+ Global.backgrounds.push(self)
16
+ end
17
+
18
+ def width
19
+ @image.width() * @scale
20
+ end
21
+
22
+ def height
23
+ @image.height() * @scale
24
+ end
25
+
26
+ def position_in_camera
27
+ @position - Global.camera.position
28
+ end
29
+
30
+ def draw
31
+ if @visible
32
+ if @replicable
33
+ draw_replicable
34
+ else
35
+ draw_normal
36
+ end
37
+ end
38
+ end
39
+
40
+ def draw_normal
41
+ @image.draw(x: position_in_camera.x, y: position_in_camera.y, scale: @scale)
42
+ end
43
+
44
+ # Camera relative Tiles
45
+ def draw_replicable
46
+ tiles_delta_x = (position_in_camera.x % width) - width
47
+ tiles_delta_y = (position_in_camera.y % height) - height
48
+
49
+ tiles_needed_horizontal = ((SCREEN_WIDTH - (tiles_delta_x + width)) / width.to_f).ceil + 1
50
+ tiles_needed_vertical = ((SCREEN_HEIGHT - (tiles_delta_y + height)) / height.to_f).ceil + 1
51
+
52
+ tiles_needed_horizontal.times do |index_horizontal|
53
+ tiles_needed_vertical.times do |index_vertical|
54
+ @image.draw(x: tiles_delta_x + (width * index_horizontal), y: tiles_delta_y + (height * index_vertical), scale: @scale)
55
+ end
56
+ end
57
+ end
58
+
59
+ def destroy
60
+ Global.backgrounds.delete(self)
61
+ end
62
+ end
data/lib/fantasy/base.rb CHANGED
@@ -54,6 +54,7 @@ def on_mouse_button_right(&block)
54
54
  end
55
55
 
56
56
  def start!
57
+ Global.setup
57
58
  Global.game = Game.new
58
59
  Global.game.show
59
60
  end
@@ -1,7 +1,31 @@
1
1
  class Camera
2
- attr_accessor :position
2
+ include MoveByCursor
3
+
4
+ attr_accessor :position, :direction, :speed
3
5
 
4
6
  def initialize(position: Coordinates.zero)
5
7
  @position = position
8
+ @direction = Coordinates.zero
9
+ @speed = 0
10
+ @moving_with_cursors = false
11
+ @on_after_move_callback = nil
12
+ end
13
+
14
+ def move_with_cursors
15
+ @moving_with_cursors = true
16
+ end
17
+
18
+ def move
19
+ calculate_direction_by_cursors if @moving_with_cursors
20
+
21
+ if @direction != Coordinates.zero && !@speed.zero?
22
+ @position = @position + (@direction * @speed * Global.frame_time)
23
+ end
24
+
25
+ @on_after_move_callback.call unless @on_after_move_callback.nil?
26
+ end
27
+
28
+ def on_after_move(&block)
29
+ @on_after_move_callback = block
6
30
  end
7
31
  end
data/lib/fantasy/clock.rb CHANGED
@@ -1,7 +1,10 @@
1
1
  class Clock
2
+ attr_accessor :persistent
3
+
2
4
  def initialize(&block)
3
5
  @block = block
4
6
  @thread = nil
7
+ @persistent = false # if persistent, clock is not stopped when loading new scene
5
8
 
6
9
  Global.clocks << self
7
10
  end
@@ -28,12 +31,22 @@ class Clock
28
31
  while(times_executed < times)
29
32
  @block.call
30
33
  times_executed += 1;
31
- sleep(seconds)
34
+
35
+ seconds_to_sleep = seconds.is_a?(Range) ? rand(seconds) : seconds
36
+ sleep(seconds_to_sleep)
32
37
  end
33
38
  end
34
39
  end
35
40
 
36
41
  def stop
37
- Thread.kill(@thread)
42
+ Thread.kill(@thread) unless @thread.nil?
43
+ end
44
+
45
+ def started?
46
+ !@thread.nil?
47
+ end
48
+
49
+ def persistent?
50
+ @persistent
38
51
  end
39
52
  end
@@ -28,4 +28,8 @@ class Coordinates < Vector2d
28
28
  def y=(value)
29
29
  @y = value
30
30
  end
31
+
32
+ def clone
33
+ Coordinates.new(@x, @y)
34
+ end
31
35
  end
@@ -1,26 +1,17 @@
1
1
  module Draggable
2
2
  def drag
3
- puts "XXX: dragging 1 #{name}"
4
3
  mouse_position = Global.mouse_position
5
4
 
6
- puts "@draggable_on_debug: #{@draggable_on_debug}"
7
- puts "@dragging: #{@dragging}"
8
- puts "Gosu.button_down?(Gosu::MS_LEFT): #{Gosu.button_down?(Gosu::MS_LEFT)}"
9
- puts "collision: #{Utils.collision_at?(self, mouse_position.x, mouse_position.y)}"
10
-
11
5
  if @draggable_on_debug && !@dragging && Gosu.button_down?(Gosu::MS_LEFT) && Utils.collision_at?(self, mouse_position.x, mouse_position.y)
12
- puts "XXX: dragging start #{name}"
13
6
  @dragging = true
14
7
  @dragging_offset = mouse_position - @position
15
8
  end
16
9
 
17
10
  if @dragging && !Gosu.button_down?(Gosu::MS_LEFT)
18
- puts "XXX: dragging end #{name}"
19
11
  @dragging = false
20
12
  end
21
13
 
22
14
  if @dragging
23
- puts "XXX: dragging 3 #{name}"
24
15
  @position = mouse_position - @dragging_offset
25
16
  end
26
17
  end
@@ -2,7 +2,7 @@ require "ostruct"
2
2
 
3
3
  module Global
4
4
  class << self
5
- attr_accessor :actors, :hud_texts, :hud_images, :clocks
5
+ attr_accessor :actors, :hud_texts, :hud_images, :backgrounds, :tile_maps, :clocks
6
6
  attr_accessor :debug
7
7
  attr_accessor :setup_proc, :loop_proc, :button_proc
8
8
  attr_accessor :presentation_proc, :game_proc, :end_proc
@@ -25,6 +25,8 @@ module Global
25
25
  @actors = []
26
26
  @hud_texts = []
27
27
  @hud_images = []
28
+ @backgrounds = []
29
+ @tile_maps = []
28
30
  @clocks = []
29
31
  @last_frame_at = Time.now
30
32
  @debug = false
@@ -33,11 +35,19 @@ module Global
33
35
  @references = OpenStruct.new
34
36
  @camera = Camera.new(position: Coordinates.zero)
35
37
  @game_state = Global.presentation_proc.nil? ? "game" : "presentation"
38
+ @scene_started_at = Time.now
36
39
 
37
- @presentation_proc = Global.default_on_presentation if @presentation_proc.nil?
38
- @game_proc = Global.default_on_game if @game_proc.nil?
39
- @end_proc = Global.default_on_end if @end_proc.nil?
40
- @paused = false
40
+ if @presentation_proc.nil?
41
+ on_presentation { Global.default_on_presentation }
42
+ end
43
+
44
+ if @game_proc.nil?
45
+ on_game { Global.default_on_game }
46
+ end
47
+
48
+ if @end_proc.nil?
49
+ on_end { Global.default_on_end }
50
+ end
41
51
  end
42
52
 
43
53
  def update
@@ -74,7 +84,7 @@ module Global
74
84
  puts "Game stage 'presentation'"
75
85
 
76
86
  clear_state_elements
77
- presentation_proc.call
87
+ @presentation_proc.call
78
88
  end
79
89
 
80
90
  def go_to_game
@@ -92,15 +102,27 @@ module Global
92
102
  end
93
103
 
94
104
  def clear_state_elements
105
+ @scene_started_at = Time.now
95
106
  @actors.clear
96
107
  @hud_texts.clear
97
108
  @hud_images.clear
98
- @clocks.each(&:stop)
109
+ @backgrounds.clear
110
+ @tile_maps.clear
111
+ @clocks.reject(&:persistent?).each(&:stop)
99
112
  @background = Color.new(r: 0, g: 0, b: 0)
100
113
  end
101
114
 
102
115
  def mouse_position
103
116
  Coordinates.new(Global.game.mouse_x, Global.game.mouse_y)
104
117
  end
118
+
119
+ def setup
120
+ Sound.preload_sounds
121
+ Image.preload_images
122
+ end
123
+
124
+ def seconds_in_scene
125
+ Time.now - @scene_started_at
126
+ end
105
127
  end
106
128
  end
data/lib/fantasy/image.rb CHANGED
@@ -22,6 +22,12 @@ class Image
22
22
  locate_image(image_name)
23
23
  end
24
24
 
25
+ def preload_images
26
+ Dir.each_child(base_path) do |file_name|
27
+ locate_image(file_name) unless file_name.start_with?(".")
28
+ end
29
+ end
30
+
25
31
  private
26
32
 
27
33
  def locate_image(image_name)
@@ -29,14 +35,17 @@ class Image
29
35
 
30
36
  puts "Initialize image: '#{image_name}'"
31
37
 
32
- base_path = "#{Dir.pwd}/images"
33
- file_name = Dir.entries(base_path).find { |e| e.start_with?("#{image_name}.") }
38
+ file_name = Dir.entries(base_path).find { |e| e =~ /^#{image_name}($|\.)/ }
34
39
 
35
- raise "Image file not found with name '#{image_name}'" if file_name.nil?
40
+ raise "Image file not found with name '#{image_name}' in #{base_path}" if file_name.nil?
36
41
 
37
42
  @@images[image_name] = Gosu::Image.new("#{base_path}/#{file_name}", { retro: true })
38
43
 
39
44
  return @@images[image_name]
40
45
  end
46
+
47
+ def base_path
48
+ "#{Dir.pwd}/images"
49
+ end
41
50
  end
42
51
  end
@@ -0,0 +1,15 @@
1
+ module MoveByCursor
2
+ def calculate_direction_by_cursors
3
+ if Gosu.button_down?(Gosu::KB_DOWN)
4
+ @direction = Coordinates.down
5
+ elsif Gosu.button_down?(Gosu::KB_UP)
6
+ @direction = Coordinates.up
7
+ elsif Gosu.button_down?(Gosu::KB_RIGHT)
8
+ @direction = Coordinates.right
9
+ elsif Gosu.button_down?(Gosu::KB_LEFT)
10
+ @direction = Coordinates.left
11
+ else
12
+ @direction = Coordinates.zero
13
+ end
14
+ end
15
+ end
data/lib/fantasy/loop.rb CHANGED
@@ -38,6 +38,8 @@ class Game < Gosu::Window
38
38
  e.move
39
39
  end
40
40
 
41
+ Global.camera.move
42
+
41
43
  Global.loop_proc.call() unless Global.loop_proc.nil?
42
44
  end
43
45
 
@@ -45,6 +47,8 @@ class Game < Gosu::Window
45
47
  Gosu.draw_rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, Global.background)
46
48
 
47
49
  (
50
+ Global.backgrounds +
51
+ Global.tile_maps +
48
52
  Global.actors +
49
53
  Global.hud_texts +
50
54
  Global.hud_images
data/lib/fantasy/sound.rb CHANGED
@@ -11,8 +11,7 @@ module Sound
11
11
 
12
12
  puts "Initialize Sound: '#{sound_name}'"
13
13
 
14
- base_path = "#{Dir.pwd}/sounds"
15
- file_name = Dir.entries(base_path).find { |e| e.start_with?("#{sound_name}.") }
14
+ file_name = Dir.entries(base_path).find { |e| e =~ /^#{sound_name}($|\.)/ }
16
15
 
17
16
  raise "Sound file not found with name '#{sound_name}'" if file_name.nil?
18
17
 
@@ -20,5 +19,15 @@ module Sound
20
19
 
21
20
  return @@sounds[sound_name]
22
21
  end
22
+
23
+ def preload_sounds
24
+ Dir.each_child(base_path) do |file_name|
25
+ locate_sound(file_name) unless file_name.start_with?(".")
26
+ end
27
+ end
28
+
29
+ def base_path
30
+ "#{Dir.pwd}/sounds"
31
+ end
23
32
  end
24
33
  end
@@ -0,0 +1,78 @@
1
+ class TileMap
2
+ attr_accessor :position
3
+
4
+ def initialize(map_name:, tiles:, tile_size: nil, tile_width: nil, tile_height: nil)
5
+ @tile_width = tile_width || tile_size
6
+ @tile_height = tile_height || tile_size
7
+
8
+ if(@tile_height.nil? or @tile_width.nil?)
9
+ raise("Tile size is not properly defined. Either you set a `tile_size` or a `tile_width` and `tile_height`")
10
+ end
11
+
12
+ @map_name = map_name
13
+ @tiles = tiles
14
+ @position = Coordinates.zero
15
+
16
+ @grid = TileMap.load_grid(@map_name)
17
+ end
18
+
19
+ def width
20
+ @grid.max(&:length) * @tile_width
21
+ end
22
+
23
+ def height
24
+ @grid.length * @tile_height
25
+ end
26
+
27
+ def spawn
28
+ tile_position = Coordinates.zero
29
+
30
+ @grid.each do |line|
31
+ tile_position.x = 0
32
+
33
+ line.each do |tile_index|
34
+ if !tile_index.nil?
35
+ actor = @tiles[tile_index].clone
36
+ actor.position.x = @position.x + (tile_position.x * @tile_width)
37
+ actor.position.y = @position.y + (tile_position.y * @tile_height)
38
+ end
39
+
40
+ tile_position.x += 1
41
+ end
42
+
43
+ tile_position.y += 1
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ class << self
50
+ @@maps = {}
51
+
52
+ def load_grid(map_name)
53
+ File.readlines(TileMap.locate_map(map_name), chomp: true).map do |line|
54
+ line.each_char.map do |char|
55
+ char == " " ? nil : char.to_i
56
+ end
57
+ end
58
+ end
59
+
60
+ def locate_map(map_name)
61
+ return @@maps[map_name] if @@maps[map_name]
62
+
63
+ puts "Initialize map: '#{map_name}'"
64
+
65
+ file_name = Dir.entries(base_path).find { |e| e =~ /^#{map_name}($|\.)/ }
66
+
67
+ raise "Map file not found with name '#{map_name}' in #{base_path}" if file_name.nil?
68
+
69
+ @@maps[map_name] = "#{base_path}/#{file_name}"
70
+
71
+ return @@maps[map_name]
72
+ end
73
+
74
+ def base_path
75
+ "#{Dir.pwd}/maps"
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,11 @@
1
+ module Tween
2
+ def self.move_towards(from:, to:, speed:)
3
+ direction = to - from
4
+ step = [direction.length, speed * Global.frame_time].min
5
+
6
+ return to if step.zero?
7
+
8
+ direction = direction.normalize
9
+ return from + (direction * step)
10
+ end
11
+ end
data/lib/fantasy/utils.rb CHANGED
@@ -1,12 +1,15 @@
1
1
  module Utils
2
2
  # https://developer.mozilla.org/en-US/docs/Games/Techniques/2D_collision_detection
3
3
  def self.collision?(actor_1, actor_2)
4
- (
5
- actor_1.position.x < (actor_2.position.x + actor_2.width) &&
6
- (actor_1.position.x + actor_1.width) > actor_2.position.x &&
7
- actor_1.position.y < (actor_2.position.y + actor_2.height) &&
8
- actor_1.position.y + actor_1.height > actor_2.position.y
9
- )
4
+ result =
5
+ (
6
+ actor_1.position.x < (actor_2.position.x + actor_2.width) &&
7
+ (actor_1.position.x + actor_1.width) > actor_2.position.x &&
8
+ actor_1.position.y < (actor_2.position.y + actor_2.height) &&
9
+ actor_1.position.y + actor_1.height > actor_2.position.y
10
+ )
11
+
12
+ result
10
13
  end
11
14
 
12
15
  def self.collision_at?(actor, x, y)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Fantasy
4
- VERSION = "0.1.1"
4
+ VERSION = "0.1.5.1"
5
5
  end
data/lib/fantasy.rb CHANGED
@@ -2,6 +2,8 @@
2
2
  require "gosu"
3
3
 
4
4
  require_relative "fantasy/version"
5
+ require_relative "fantasy/includes/move_by_cursors"
6
+ require_relative "fantasy/tween"
5
7
  require_relative "fantasy/draggable"
6
8
  require_relative "fantasy/color"
7
9
  require_relative "fantasy/actor"
@@ -12,7 +14,9 @@ require_relative "fantasy/clock"
12
14
  require_relative "fantasy/loop"
13
15
  require_relative "fantasy/hud_text"
14
16
  require_relative "fantasy/hud_image"
17
+ require_relative "fantasy/background"
15
18
  require_relative "fantasy/sound"
16
19
  require_relative "fantasy/camera"
17
20
  require_relative "fantasy/image"
21
+ require_relative "fantasy/tile_map"
18
22
  require_relative "fantasy/base"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fantasy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Fernando Guillen
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-03-11 00:00:00.000000000 Z
11
+ date: 2022-03-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: gosu
@@ -38,7 +38,7 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: 2.2.3
41
- description: Simple toolbox library and lean API to build great mini games
41
+ description: Simple toolbox library and lean API to build great mini games in Ruby
42
42
  email:
43
43
  - fguillen.mail@gmail.com
44
44
  executables: []
@@ -58,6 +58,7 @@ files:
58
58
  - fonts/VT323-Regular.ttf
59
59
  - lib/fantasy.rb
60
60
  - lib/fantasy/actor.rb
61
+ - lib/fantasy/background.rb
61
62
  - lib/fantasy/base.rb
62
63
  - lib/fantasy/camera.rb
63
64
  - lib/fantasy/clock.rb
@@ -68,8 +69,11 @@ files:
68
69
  - lib/fantasy/hud_image.rb
69
70
  - lib/fantasy/hud_text.rb
70
71
  - lib/fantasy/image.rb
72
+ - lib/fantasy/includes/move_by_cursors.rb
71
73
  - lib/fantasy/loop.rb
72
74
  - lib/fantasy/sound.rb
75
+ - lib/fantasy/tile_map.rb
76
+ - lib/fantasy/tween.rb
73
77
  - lib/fantasy/utils.rb
74
78
  - lib/fantasy/version.rb
75
79
  homepage: https://github.com/fguillen/fantasy
@@ -98,5 +102,5 @@ requirements: []
98
102
  rubygems_version: 3.2.22
99
103
  signing_key:
100
104
  specification_version: 4
101
- summary: Simple toolbox library and lean API to build great mini games
105
+ summary: Simple toolbox library and lean API to build great mini games in Ruby
102
106
  test_files: []