fantasy 0.1.3 → 0.1.7

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: fe5958bf098b2f7a0190eecbd5a9914c5a232e38863b4e9a19280b44653d771a
4
- data.tar.gz: fa8899b7f6f6c04cda1e3ee6d470e940e377395b403d6dd3cddcdbf39583cc30
3
+ metadata.gz: 5b920c99d05b572330432388e4cb4bae6ef5b814d8834bb3b05da16375f49bf6
4
+ data.tar.gz: 2a5d7c01b7f3e98b5c8928f70981f5bf3591badc844f33a1209f49f6883a426d
5
5
  SHA512:
6
- metadata.gz: 7c7830aca33b0a07482782f644301c854f9017ed10bc4602435945a70901e60fdd01adae8ed757f4ecb3a07a51e0b4279a273dbe149c665472e75e7c32e80f28
7
- data.tar.gz: 5fee0000ec447edd1849592ab3f5145214d4a84dcf983bf1fff9c05a5ac3655d99bf519d4760ebaba2fadba6194dbf697468cd2483d0b3a77e8ab1611210eb65
6
+ metadata.gz: b791f6790eaa41e1c0cd49ef9faf514ef1fdacf9310f4dcf6cdfe6aeae2ebde28952d2f748c1e4f155dea6145dbbee7755599bf6a2555479b36a0d0390752bc0
7
+ data.tar.gz: e6be40d13c6b7a8c19e0e3604a1554eaa2f2efbc08ea7a9769e9b3db94407a1d8e3fd18c7ea0e08089ff6643e69e9c251d3de746720f464d6313b29baa288a50
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,225 @@ 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
+ ### Tilemap (TODO)
223
+
224
+ For easy creation of:
225
+
226
+ - Top-down map levels
227
+ - Platformer map levels (TODO)
228
+ - Allow specific map keys like a=>actor_1, b=>actor_2 (TODO)
229
+
230
+ ### Tweens (TODO)
231
+
232
+ Multiple movement animation effects like in [DoTween](http://dotween.demigiant.com/documentation.php) (TODO)
146
233
 
147
234
  ## API
148
235
 
236
+ ### Game Scene transitions
237
+
238
+ Configure your game elements on each Scene:
239
+
240
+ ```ruby
241
+ SCREEN_WIDTH = 640
242
+ SCREEN_HEIGHT = 360
243
+
244
+ # (Optional)
245
+ on_presentation do
246
+ # Game elements running when the game loads
247
+ end
248
+
249
+ on_game do
250
+ # Game elements running when in game Scene
251
+ end
252
+
253
+ # (Optional)
254
+ on_end do
255
+ # Game elements running when game is ended
256
+ end
257
+ ```
258
+
259
+ How to go from Scene to Scene:
260
+
261
+ ```ruby
262
+ Global.go_to_presentation
263
+ Global.go_to_game
264
+ Global.go_to_end
265
+ ```
266
+
267
+ #### Example
268
+
269
+ ```ruby
270
+ on_presentation do
271
+ HudText.new(position: Coordinates.new(10, 100), text: "Press space to start")
272
+
273
+ on_space_bar do
274
+ Global.go_to_game
275
+ end
276
+ end
277
+
278
+ on_game do
279
+ # [...]
280
+ if player.dead
281
+ Global.go_to_end
282
+ end
283
+ end
284
+
285
+ on_end do
286
+ HudText.new(position: Coordinates.new(10, 100), text: "You are dead. Press space to re-tart")
287
+
288
+ on_space_bar do
289
+ Global.go_to_presentation
290
+ end
291
+ end
292
+ ```
293
+
294
+ ### Actor
295
+
296
+ Actor can be used directly:
297
+
298
+ ```ruby
299
+ player = Actor.new("warrior") # ./images/warrior.png
300
+ player.position = Coordinates.new(100, 100)
301
+ player.solid = true
302
+ player.speed = 200
303
+ player.layer = 1
304
+ player.move_with_cursors
305
+
306
+ player.on_collision do |other|
307
+ if other.name == "enemy"
308
+ player.destroy
309
+ end
310
+ end
311
+
312
+ player.on_after_move do
313
+ if player.position.x > SCREEN_WIDTH
314
+ player.position.x = SCREEN_WIDTH
315
+ end
316
+
317
+ if player.position.x < 0
318
+ player.position.x = 0
319
+ end
320
+ end
321
+ ```
322
+
323
+ Or in a subclass:
324
+
325
+ ```ruby
326
+ class Player < Actor
327
+ def initialize
328
+ super("warrior") # ./images/warrior.png
329
+ @position = Coordinates.new(100, 100)
330
+ @solid = true
331
+ @speed = 200
332
+ @layer = 1
333
+ @direction = Coordinates.zero
334
+ move_with_cursors
335
+ end
336
+
337
+ on_collision do |other|
338
+ if other.name == "enemy"
339
+ destroy
340
+ end
341
+ end
342
+
343
+ on_after_move do
344
+ if @position.x > SCREEN_WIDTH
345
+ @position.x = SCREEN_WIDTH
346
+ end
347
+
348
+ if @position.x < 0
349
+ @position.x = 0
350
+ end
351
+ end
352
+ end
353
+ ```
354
+
355
+ ### Clock
356
+
357
+ ```ruby
358
+ clock =
359
+ Clock.new do
360
+ enemy.attack
361
+ sleep(1)
362
+ enemy.defend
363
+ end
364
+
365
+ clock.run_now
366
+ clock.run_on(seconds: 2)
367
+ clock.repeat(seconds: 2, times: 10)
368
+ clock.stop
369
+ ```
370
+
371
+ ### Background
372
+
373
+ ```ruby
374
+ # Simple color
375
+ on_presentation do
376
+ Global.background = Color.new(r: 34, g: 35, b: 35)
377
+ end
378
+
379
+ # Replicable (by default) Image
380
+ # position is relative to Global.camera
381
+ on_game do
382
+ background = Background.new(image_name: "beach")
383
+ # background.replicable = false # if you don't want the image to replicate
384
+ background.scale = 6
385
+ end
386
+ ```
387
+
388
+ ### Camera
389
+
390
+ ```ruby
391
+ on_game do
392
+ on_loop do
393
+ # Camera follows player
394
+ Global.camera.position.y = player.position.y - (SCREEN_HEIGHT / 2)
395
+ end
396
+ end
397
+ ```
398
+
399
+ ### Sound
400
+
401
+ ```ruby
402
+ Sound.play("shoot") # ./sounds/shoot.wav
403
+ ```
404
+
405
+
406
+ ### UI
407
+
408
+ #### HUD Text
409
+
410
+ ```ruby
411
+ timer = HudText.new(position: Coordinates.new(20, 10))
412
+ timer.text = 0
413
+ timer.size = "big"
414
+
415
+ Clock.new { timer.text += 1 }.repeat(seconds: 1)
416
+ ```
417
+
418
+ #### HUD Image
419
+
420
+ ```ruby
421
+ icon = HudImage.new(position: Coordinates.new(SCREEN_WIDTH - 220, 8), image_name: "ring")
422
+ icon.scale = 4
423
+ icon.visible = true
424
+
425
+ Clock.new { icon.visible = !icon.visible }.repeat(seconds: 1)
426
+ ```
149
427
 
150
428
 
151
429
  ## Credits for assets
152
430
 
153
- - Sprites: www.kenney.nl
154
431
  - Font: VT323 Project Authors (peter.hull@oikoi.com)
155
432
 
156
433
  ## Bugs
157
434
 
158
435
  - When dragging in debug mode new elements are being added to the drag (TODO)
159
436
  - Rubocop is not passing
437
+ - Tests are missing
438
+ - Allow Ruby 2.5+
160
439
 
161
440
  ## Development
162
441
 
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)
@@ -51,7 +54,7 @@ class Actor
51
54
 
52
55
  def draw_debug
53
56
  Utils.draw_frame(position_in_camera.x, position_in_camera.y, width, height, 1, Gosu::Color::RED) if solid
54
- Global.pixel_font.draw_text("#{@position.x.floor},#{@position.y.floor}", position_in_camera.x, position_in_camera.y - 20, 1)
57
+ Global.pixel_fonts["medium"].draw_text("#{@position.x.floor},#{@position.y.floor}", position_in_camera.x, position_in_camera.y - 20, 1)
55
58
  end
56
59
 
57
60
  def position_in_camera
@@ -113,20 +116,6 @@ class Actor
113
116
  @on_collision_callback.call(actor) unless @on_collision_callback.nil?
114
117
  end
115
118
 
116
- def calculate_direction_by_cursors
117
- if Gosu.button_down?(Gosu::KB_DOWN)
118
- @direction = Coordinates.down
119
- elsif Gosu.button_down?(Gosu::KB_UP)
120
- @direction = Coordinates.up
121
- elsif Gosu.button_down?(Gosu::KB_RIGHT)
122
- @direction = Coordinates.right
123
- elsif Gosu.button_down?(Gosu::KB_LEFT)
124
- @direction = Coordinates.left
125
- else
126
- @direction = Coordinates.zero
127
- end
128
- end
129
-
130
119
  def collisions
131
120
  Global.actors.reject { |e| e == self }.select { |e| e.solid? }.select do |actor|
132
121
  Utils.collision? self, actor
@@ -137,4 +126,37 @@ class Actor
137
126
  @on_destroy_callback.call unless @on_destroy_callback.nil?
138
127
  Global.actors.delete(self)
139
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
140
162
  end
@@ -10,6 +10,7 @@ class Background
10
10
  @draggable_on_debug = true
11
11
  @dragging = false
12
12
  @layer = -100
13
+ @replicable = true
13
14
 
14
15
  Global.backgrounds.push(self)
15
16
  end
@@ -22,9 +23,36 @@ class Background
22
23
  @image.height() * @scale
23
24
  end
24
25
 
26
+ def position_in_camera
27
+ @position - Global.camera.position
28
+ end
29
+
25
30
  def draw
26
- if visible
27
- @image.draw(x: @position.x, y: @position.y, scale: @scale)
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
28
56
  end
29
57
  end
30
58
 
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,5 +1,6 @@
1
1
  class Clock
2
2
  attr_accessor :persistent
3
+ attr_reader :thread
3
4
 
4
5
  def initialize(&block)
5
6
  @block = block
@@ -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
@@ -2,7 +2,7 @@ require "ostruct"
2
2
 
3
3
  module Global
4
4
  class << self
5
- attr_accessor :actors, :hud_texts, :hud_images, :backgrounds, :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
@@ -15,7 +15,7 @@ module Global
15
15
 
16
16
  attr_accessor :game
17
17
  attr_reader :frame_time # delta_time
18
- attr_reader :pixel_font
18
+ attr_reader :pixel_fonts
19
19
  attr_reader :references
20
20
  attr_reader :camera
21
21
  attr_reader :game_state
@@ -26,14 +26,24 @@ module Global
26
26
  @hud_texts = []
27
27
  @hud_images = []
28
28
  @backgrounds = []
29
+ @tile_maps = []
29
30
  @clocks = []
30
31
  @last_frame_at = Time.now
31
32
  @debug = false
32
- @pixel_font = Gosu::Font.new(20, { name: "#{__dir__}/../../fonts/VT323-Regular.ttf" } )
33
+
34
+ @pixel_fonts = {}
35
+ @pixel_fonts["small"] = Gosu::Font.new(20, { name: "#{__dir__}/../../fonts/VT323-Regular.ttf" } )
36
+ @pixel_fonts["medium"] = Gosu::Font.new(40, { name: "#{__dir__}/../../fonts/VT323-Regular.ttf" } )
37
+ @pixel_fonts["big"] = Gosu::Font.new(60, { name: "#{__dir__}/../../fonts/VT323-Regular.ttf" } )
38
+ @pixel_fonts["huge"] = Gosu::Font.new(100, { name: "#{__dir__}/../../fonts/VT323-Regular.ttf" } )
39
+
33
40
  @d_key_pressed = false
34
41
  @references = OpenStruct.new
35
42
  @camera = Camera.new(position: Coordinates.zero)
36
43
  @game_state = Global.presentation_proc.nil? ? "game" : "presentation"
44
+ @scene_started_at = Time.now
45
+
46
+ @background = Color.new(r: 0, g: 0, b: 0)
37
47
 
38
48
  if @presentation_proc.nil?
39
49
  on_presentation { Global.default_on_presentation }
@@ -100,16 +110,31 @@ module Global
100
110
  end
101
111
 
102
112
  def clear_state_elements
113
+ @scene_started_at = Time.now
103
114
  @actors.clear
104
115
  @hud_texts.clear
105
116
  @hud_images.clear
106
117
  @backgrounds.clear
107
- @clocks.reject(&:persistent?).each(&:stop)
118
+ @tile_maps.clear
119
+
120
+ @clocks.reject(&:persistent?).each do |clock|
121
+ clock.stop unless clock.thread == Thread.current # no stop current Thread
122
+ end
123
+
108
124
  @background = Color.new(r: 0, g: 0, b: 0)
109
125
  end
110
126
 
111
127
  def mouse_position
112
128
  Coordinates.new(Global.game.mouse_x, Global.game.mouse_y)
113
129
  end
130
+
131
+ def setup
132
+ Sound.preload_sounds
133
+ Image.preload_images
134
+ end
135
+
136
+ def seconds_in_scene
137
+ Time.now - @scene_started_at
138
+ end
114
139
  end
115
140
  end
@@ -1,5 +1,5 @@
1
1
  class HudText
2
- attr_accessor :text, :size, :color, :visible, :layer, :in_world
2
+ attr_accessor :text, :size, :color, :visible, :layer, :in_world, :position
3
3
 
4
4
  def initialize(position:, text: "")
5
5
  @position = position
@@ -17,14 +17,22 @@ class HudText
17
17
 
18
18
  def draw
19
19
  if visible
20
- Global.pixel_font.draw_markup(text, screen_position.x + scale, screen_position.y - 20 + scale, 1, scale, scale, Gosu::Color::BLACK)
21
- Global.pixel_font.draw_markup(text, screen_position.x, screen_position.y - 20, 1, scale, scale, color)
20
+ font.draw_markup(text, screen_position.x + shadow_offset, screen_position.y - 20 + shadow_offset, 1, 1, 1, Gosu::Color::BLACK)
21
+ font.draw_markup(text, screen_position.x, screen_position.y - 20, 1, 1, 1, color)
22
22
  end
23
23
 
24
24
  draw_debug if Global.debug
25
25
  end
26
26
 
27
- def scale
27
+ def font
28
+ found_font = Global.pixel_fonts[@size]
29
+ if found_font.nil?
30
+ raise "HudText.size not valid '#{@size}'. Valid sizes: 'small, medium, big, huge'"
31
+ end
32
+ found_font
33
+ end
34
+
35
+ def shadow_offset
28
36
  case @size
29
37
  when "small"
30
38
  1
@@ -52,6 +60,6 @@ class HudText
52
60
  end
53
61
 
54
62
  def draw_debug
55
- Global.pixel_font.draw_text("#{@position.x.floor},#{@position.y.floor}", @position.x, @position.y, 1)
63
+ Global.pixel_fonts["medium"].draw_text("#{@position.x.floor},#{@position.y.floor}", @position.x, @position.y, 1)
56
64
  end
57
65
  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
 
@@ -46,6 +48,7 @@ class Game < Gosu::Window
46
48
 
47
49
  (
48
50
  Global.backgrounds +
51
+ Global.tile_maps +
49
52
  Global.actors +
50
53
  Global.hud_texts +
51
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,84 @@
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_template = @tiles[tile_index]
36
+
37
+ if actor_template.nil?
38
+ raise "Tilemap config error. Not found Tile for index '#{tile_index}'"
39
+ end
40
+
41
+ actor = actor_template.clone
42
+ actor.position.x = @position.x + (tile_position.x * @tile_width)
43
+ actor.position.y = @position.y + (tile_position.y * @tile_height)
44
+ end
45
+
46
+ tile_position.x += 1
47
+ end
48
+
49
+ tile_position.y += 1
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ class << self
56
+ @@maps = {}
57
+
58
+ def load_grid(map_name)
59
+ File.readlines(Tilemap.locate_map(map_name), chomp: true).map do |line|
60
+ line.each_char.map do |char|
61
+ char == " " ? nil : char.to_i
62
+ end
63
+ end
64
+ end
65
+
66
+ def locate_map(map_name)
67
+ return @@maps[map_name] if @@maps[map_name]
68
+
69
+ puts "Initialize map: '#{map_name}'"
70
+
71
+ file_name = Dir.entries(base_path).find { |e| e =~ /^#{map_name}($|\.)/ }
72
+
73
+ raise "Map file not found with name '#{map_name}' in #{base_path}" if file_name.nil?
74
+
75
+ @@maps[map_name] = "#{base_path}/#{file_name}"
76
+
77
+ return @@maps[map_name]
78
+ end
79
+
80
+ def base_path
81
+ "#{Dir.pwd}/maps"
82
+ end
83
+ end
84
+ 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
@@ -27,4 +27,9 @@ module Utils
27
27
  Gosu.draw_rect(x, y + (height - stroke), width, stroke, color)
28
28
  Gosu.draw_rect(x, y, stroke, height, color)
29
29
  end
30
+
31
+ def self.remap(value:, from_ini:, from_end:, to_ini:, to_end:)
32
+ result = to_ini + (value - from_ini) * (to_end - to_ini) / (from_end - from_ini);
33
+ result
34
+ end
30
35
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Fantasy
4
- VERSION = "0.1.3"
4
+ VERSION = "0.1.7"
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"
@@ -16,4 +18,5 @@ require_relative "fantasy/background"
16
18
  require_relative "fantasy/sound"
17
19
  require_relative "fantasy/camera"
18
20
  require_relative "fantasy/image"
21
+ require_relative "fantasy/tilemap"
19
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.3
4
+ version: 0.1.7
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-12 00:00:00.000000000 Z
11
+ date: 2022-03-17 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: []
@@ -69,8 +69,11 @@ files:
69
69
  - lib/fantasy/hud_image.rb
70
70
  - lib/fantasy/hud_text.rb
71
71
  - lib/fantasy/image.rb
72
+ - lib/fantasy/includes/move_by_cursors.rb
72
73
  - lib/fantasy/loop.rb
73
74
  - lib/fantasy/sound.rb
75
+ - lib/fantasy/tilemap.rb
76
+ - lib/fantasy/tween.rb
74
77
  - lib/fantasy/utils.rb
75
78
  - lib/fantasy/version.rb
76
79
  homepage: https://github.com/fguillen/fantasy
@@ -99,5 +102,5 @@ requirements: []
99
102
  rubygems_version: 3.2.22
100
103
  signing_key:
101
104
  specification_version: 4
102
- 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
103
106
  test_files: []