fantasy 0.1.3 → 0.1.7

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: 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: []