fantasy 0.1.0 → 0.1.5
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 +302 -14
- data/fantasy.gemspec +2 -2
- data/lib/fantasy/actor.rb +42 -21
- 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 -8
- data/lib/fantasy/hud_image.rb +2 -2
- data/lib/fantasy/image.rb +51 -0
- data/lib/fantasy/includes/move_by_cursors.rb +15 -0
- data/lib/fantasy/loop.rb +4 -0
- data/lib/fantasy/sound.rb +14 -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 +5 -0
- metadata +9 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: df4ba5be3b9ac4fbe5530172717fc75bda3687980d3b495c025fa7165e582254
|
4
|
+
data.tar.gz: a57ad439621fa4b7e531047350e5703812bad73338829cbab29e49e857c5dde8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 32cd9949324305bfbca173ef312797cd0a6ad7ca71c1bdf508220f10b02430501441acc537d02b5409d6c91f2865fb715315da65416932930069c87510369795
|
7
|
+
data.tar.gz: 797984f3d327e193e30665d633373afb45e9f34c90e84ad801379378c742f2d19eec69dc6aba779aba2be156f030bf322bb8863105bc7526564118fd647f2cd0
|
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,17 +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
|
-
- Initial screen
|
107
|
-
- Game
|
108
|
-
- Game over screen
|
109
|
-
|
110
|
-
Each state should be independent and unique Actors and other elements can be created and configured for each state
|
111
|
-
|
112
|
-
Built-in mechanism to move from one state to another.
|
113
188
|
|
114
189
|
### Pause Game (TODO)
|
115
190
|
|
@@ -123,9 +198,17 @@ Move the core functions to the top level hierarchy so I don't need to create a `
|
|
123
198
|
|
124
199
|
Direct and easy way to play a sound
|
125
200
|
|
201
|
+
### Background
|
202
|
+
|
203
|
+
Simple way to set up:
|
204
|
+
|
205
|
+
- Background color
|
206
|
+
- Image background
|
207
|
+
- Repeatable image background
|
208
|
+
|
126
209
|
### Data Persistance (TODO)
|
127
210
|
|
128
|
-
Simple
|
211
|
+
Simple mechanism to save data in disk. For user preferences, game progress, high scores and others
|
129
212
|
|
130
213
|
### User Inputs
|
131
214
|
|
@@ -134,19 +217,224 @@ Easy access to keyboard and mouse inputs on any part of the code. Specially in t
|
|
134
217
|
- Allow "on_space_bar" and each Actor (TODO)
|
135
218
|
- Allow multiple "on_space_bar" (TODO)
|
136
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)
|
137
232
|
|
138
233
|
## API
|
139
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
|
+
```
|
140
426
|
|
141
427
|
|
142
428
|
## Credits for assets
|
143
429
|
|
144
|
-
- Sprites: www.kenney.nl
|
145
430
|
- Font: VT323 Project Authors (peter.hull@oikoi.com)
|
146
431
|
|
147
432
|
## Bugs
|
148
433
|
|
149
434
|
- When dragging in debug mode new elements are being added to the drag (TODO)
|
435
|
+
- Rubocop is not passing
|
436
|
+
- Tests are missing
|
437
|
+
- Allow Ruby 2.5+
|
150
438
|
|
151
439
|
## Development
|
152
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,9 +1,12 @@
|
|
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)
|
6
|
-
@
|
8
|
+
@image_name = image_name
|
9
|
+
@image = Image.new(image_name)
|
7
10
|
@name = image_name
|
8
11
|
@position = Coordinates.new(0, 0)
|
9
12
|
@direction = Coordinates.new(0, 0)
|
@@ -23,7 +26,7 @@ class Actor
|
|
23
26
|
end
|
24
27
|
|
25
28
|
def image=(image_name)
|
26
|
-
@image =
|
29
|
+
@image = Image.new(image_name)
|
27
30
|
end
|
28
31
|
|
29
32
|
def width
|
@@ -44,7 +47,7 @@ class Actor
|
|
44
47
|
end
|
45
48
|
|
46
49
|
def draw
|
47
|
-
@image.draw(position_in_camera.x, position_in_camera.y,
|
50
|
+
@image.draw(x: position_in_camera.x, y: position_in_camera.y, scale: @scale)
|
48
51
|
|
49
52
|
draw_debug if Global.debug
|
50
53
|
end
|
@@ -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,19 +25,29 @@ 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
|
31
|
-
@pixel_font = Gosu::Font.new(20, { name: "#{__dir__}
|
33
|
+
@pixel_font = Gosu::Font.new(20, { name: "#{__dir__}/../../fonts/VT323-Regular.ttf" } )
|
32
34
|
@d_key_pressed = false
|
33
35
|
@references = OpenStruct.new
|
34
36
|
@camera = Camera.new(position: Coordinates.zero)
|
35
37
|
@game_state = Global.presentation_proc.nil? ? "game" : "presentation"
|
38
|
+
@game_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
|
@@ -95,12 +105,23 @@ module Global
|
|
95
105
|
@actors.clear
|
96
106
|
@hud_texts.clear
|
97
107
|
@hud_images.clear
|
98
|
-
@
|
108
|
+
@backgrounds.clear
|
109
|
+
@tile_maps.clear
|
110
|
+
@clocks.reject(&:persistent?).each(&:stop)
|
99
111
|
@background = Color.new(r: 0, g: 0, b: 0)
|
100
112
|
end
|
101
113
|
|
102
114
|
def mouse_position
|
103
115
|
Coordinates.new(Global.game.mouse_x, Global.game.mouse_y)
|
104
116
|
end
|
117
|
+
|
118
|
+
def setup
|
119
|
+
Sound.preload_sounds
|
120
|
+
Image.preload_images
|
121
|
+
end
|
122
|
+
|
123
|
+
def seconds_in_game
|
124
|
+
Time.now - @game_started_at
|
125
|
+
end
|
105
126
|
end
|
106
127
|
end
|
data/lib/fantasy/hud_image.rb
CHANGED
@@ -4,7 +4,7 @@ class HudImage
|
|
4
4
|
attr_accessor :name, :scale, :color, :visible, :position, :layer
|
5
5
|
|
6
6
|
def initialize(position:, image_name: )
|
7
|
-
@image =
|
7
|
+
@image = Image.new(image_name)
|
8
8
|
@name = image_name
|
9
9
|
@position = position
|
10
10
|
@scale = 1
|
@@ -30,7 +30,7 @@ class HudImage
|
|
30
30
|
|
31
31
|
def draw
|
32
32
|
if visible
|
33
|
-
@image.draw(@position.x, @position.y,
|
33
|
+
@image.draw(x: @position.x, y: @position.y, scale: @scale)
|
34
34
|
end
|
35
35
|
|
36
36
|
draw_debug if Global.debug
|
@@ -0,0 +1,51 @@
|
|
1
|
+
class Image
|
2
|
+
def initialize(image_name)
|
3
|
+
@image = Image.load(image_name)
|
4
|
+
end
|
5
|
+
|
6
|
+
def draw(x:, y:, scale: 1)
|
7
|
+
@image.draw(x, y, 0, scale, scale)
|
8
|
+
end
|
9
|
+
|
10
|
+
def width
|
11
|
+
@image.width
|
12
|
+
end
|
13
|
+
|
14
|
+
def height
|
15
|
+
@image.height
|
16
|
+
end
|
17
|
+
|
18
|
+
class << self
|
19
|
+
@@images = {}
|
20
|
+
|
21
|
+
def load(image_name)
|
22
|
+
locate_image(image_name)
|
23
|
+
end
|
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
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def locate_image(image_name)
|
34
|
+
return @@images[image_name] if @@images[image_name]
|
35
|
+
|
36
|
+
puts "Initialize image: '#{image_name}'"
|
37
|
+
|
38
|
+
file_name = Dir.entries(base_path).find { |e| e =~ /^#{image_name}($|\.)/ }
|
39
|
+
|
40
|
+
raise "Image file not found with name '#{image_name}' in #{base_path}" if file_name.nil?
|
41
|
+
|
42
|
+
@@images[image_name] = Gosu::Image.new("#{base_path}/#{file_name}", { retro: true })
|
43
|
+
|
44
|
+
return @@images[image_name]
|
45
|
+
end
|
46
|
+
|
47
|
+
def base_path
|
48
|
+
"#{Dir.pwd}/images"
|
49
|
+
end
|
50
|
+
end
|
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,11 +11,23 @@ module Sound
|
|
11
11
|
|
12
12
|
puts "Initialize Sound: '#{sound_name}'"
|
13
13
|
|
14
|
-
|
15
|
-
|
14
|
+
file_name = Dir.entries(base_path).find { |e| e =~ /^#{sound_name}($|\.)/ }
|
15
|
+
|
16
|
+
raise "Sound file not found with name '#{sound_name}'" if file_name.nil?
|
17
|
+
|
16
18
|
@@sounds[sound_name] = Gosu::Sample.new("#{base_path}/#{file_name}")
|
17
19
|
|
18
20
|
return @@sounds[sound_name]
|
19
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
|
20
32
|
end
|
21
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,6 +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"
|
20
|
+
require_relative "fantasy/image"
|
21
|
+
require_relative "fantasy/tile_map"
|
17
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.5
|
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
|
@@ -67,8 +68,12 @@ files:
|
|
67
68
|
- lib/fantasy/global.rb
|
68
69
|
- lib/fantasy/hud_image.rb
|
69
70
|
- lib/fantasy/hud_text.rb
|
71
|
+
- lib/fantasy/image.rb
|
72
|
+
- lib/fantasy/includes/move_by_cursors.rb
|
70
73
|
- lib/fantasy/loop.rb
|
71
74
|
- lib/fantasy/sound.rb
|
75
|
+
- lib/fantasy/tile_map.rb
|
76
|
+
- lib/fantasy/tween.rb
|
72
77
|
- lib/fantasy/utils.rb
|
73
78
|
- lib/fantasy/version.rb
|
74
79
|
homepage: https://github.com/fguillen/fantasy
|
@@ -97,5 +102,5 @@ requirements: []
|
|
97
102
|
rubygems_version: 3.2.22
|
98
103
|
signing_key:
|
99
104
|
specification_version: 4
|
100
|
-
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
|
101
106
|
test_files: []
|