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 +4 -4
- data/README.md +295 -16
- data/fantasy.gemspec +2 -2
- data/lib/fantasy/actor.rb +38 -16
- data/lib/fantasy/background.rb +30 -2
- data/lib/fantasy/base.rb +1 -0
- data/lib/fantasy/camera.rb +25 -1
- data/lib/fantasy/clock.rb +1 -0
- data/lib/fantasy/coordinates.rb +4 -0
- data/lib/fantasy/global.rb +29 -4
- data/lib/fantasy/hud_text.rb +13 -5
- data/lib/fantasy/image.rb +12 -3
- data/lib/fantasy/includes/move_by_cursors.rb +15 -0
- data/lib/fantasy/loop.rb +3 -0
- data/lib/fantasy/sound.rb +11 -2
- data/lib/fantasy/tilemap.rb +84 -0
- data/lib/fantasy/tween.rb +11 -0
- data/lib/fantasy/utils.rb +5 -0
- data/lib/fantasy/version.rb +1 -1
- data/lib/fantasy.rb +3 -0
- metadata +7 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5b920c99d05b572330432388e4cb4bae6ef5b814d8834bb3b05da16375f49bf6
|
4
|
+
data.tar.gz: 2a5d7c01b7f3e98b5c8928f70981f5bf3591badc844f33a1209f49f6883a426d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
133
|
-
- Repeatable image background
|
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.
|
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
|
data/lib/fantasy/background.rb
CHANGED
@@ -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
|
-
|
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
data/lib/fantasy/camera.rb
CHANGED
@@ -1,7 +1,31 @@
|
|
1
1
|
class Camera
|
2
|
-
|
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
data/lib/fantasy/coordinates.rb
CHANGED
data/lib/fantasy/global.rb
CHANGED
@@ -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 :
|
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
|
-
|
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
|
-
@
|
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
|
data/lib/fantasy/hud_text.rb
CHANGED
@@ -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
|
-
|
21
|
-
|
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
|
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.
|
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
|
-
|
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
|
-
|
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
|
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
|
data/lib/fantasy/version.rb
CHANGED
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.
|
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-
|
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: []
|