fantasy 0.1.1 → 0.1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +294 -16
- data/fantasy.gemspec +2 -2
- data/lib/fantasy/actor.rb +39 -18
- data/lib/fantasy/background.rb +62 -0
- data/lib/fantasy/base.rb +1 -0
- data/lib/fantasy/camera.rb +25 -1
- data/lib/fantasy/clock.rb +15 -2
- data/lib/fantasy/coordinates.rb +4 -0
- data/lib/fantasy/draggable.rb +0 -9
- data/lib/fantasy/global.rb +29 -7
- data/lib/fantasy/image.rb +12 -3
- data/lib/fantasy/includes/move_by_cursors.rb +15 -0
- data/lib/fantasy/loop.rb +4 -0
- data/lib/fantasy/sound.rb +11 -2
- data/lib/fantasy/tile_map.rb +78 -0
- data/lib/fantasy/tween.rb +11 -0
- data/lib/fantasy/utils.rb +9 -6
- data/lib/fantasy/version.rb +1 -1
- data/lib/fantasy.rb +4 -0
- metadata +8 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 58bcb1e825d5cdf5185a26d5bd1dd972fb585ddeefa8fab964984e4ddd975584
|
4
|
+
data.tar.gz: eea7d4e258d2a3f949b97b187fd540e9e7702515fcac381c5ae53b244f06c546
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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,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
|
-
|
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
|
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
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
@@ -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
|
-
|
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
|
data/lib/fantasy/coordinates.rb
CHANGED
data/lib/fantasy/draggable.rb
CHANGED
@@ -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
|
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, :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
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
-
@
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
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)
|
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"
|
@@ -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
|
+
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: []
|