fantasy 0.1.1 → 0.1.5.1

Sign up to get free protection for your applications and to get access to all the features.
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: []