minigl 2.4.3 → 2.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 +8 -3
- data/lib/minigl/particles.rb +313 -0
- data/lib/minigl.rb +1 -0
- data/test/particles_game.rb +70 -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: 6078cf1c0aafd790460ec6b7723dff865c0a2e78a8359fd8734cd8a6a07aec60
|
4
|
+
data.tar.gz: 61be9170370e9b912a7c3a7087ca145de121f36b005abf62bfe193ca1dc29ec3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 68d1060caaf63cfe944a924b692c38680833a15fa49412060295904e630d8b73e5b6342abf4febf1ff6d9324e88f61e0a34fa5c9014e70b376cafa10402e0df5
|
7
|
+
data.tar.gz: ff9db71adca879e4ec8b7044c1864477befb45eff207cdd0dc2b1ff3d97d4f6f3f0255e1d61e6705293d7ab3d490c484543f40cd861545cf1936b5b09d2da590
|
data/README.md
CHANGED
@@ -10,6 +10,7 @@ It provides the following features:
|
|
10
10
|
* UI (text, buttons, text fields, drop-down lists, progress bars)
|
11
11
|
* Basic physics and collision checking
|
12
12
|
* Animated objects
|
13
|
+
* Particle systems
|
13
14
|
|
14
15
|
More functionalities are coming. Feel free to contribute! You can send feedback
|
15
16
|
to victordavidsantos@gmail.com.
|
@@ -38,13 +39,17 @@ After installing the Gosu dependencies, you can just `gem install minigl`.
|
|
38
39
|
|
39
40
|
## Documentation
|
40
41
|
|
41
|
-
* The library is 100% RDoc-documented [here](
|
42
|
+
* The library is 100% RDoc-documented [here](https://www.rubydoc.info/gems/minigl).
|
42
43
|
* The [wiki](https://github.com/victords/minigl/wiki) is a work in progress with tutorials and examples.
|
43
44
|
* Test package and examples aren't complete!
|
44
45
|
|
45
|
-
## Version 2.
|
46
|
+
## Version 2.5.0
|
46
47
|
|
47
|
-
*
|
48
|
+
* Added `Particles` (particle system) class. Run `test/particles_game.rb` to check its capabilities!
|
49
|
+
|
50
|
+
### Version 2.5.1
|
51
|
+
|
52
|
+
Patch fixing the required Ruby version (>= 3.1).
|
48
53
|
|
49
54
|
## Contributing
|
50
55
|
|
@@ -0,0 +1,313 @@
|
|
1
|
+
module MiniGL
|
2
|
+
# A particle system.
|
3
|
+
class Particles
|
4
|
+
# Create a new particle system.
|
5
|
+
# Options:
|
6
|
+
# - x: x-coordinate of the origin of the particle system. If +source+ is
|
7
|
+
# set, it has precedence.
|
8
|
+
# - y: y-coordinate of the origin of the particle system. If +source+ is
|
9
|
+
# set, it has precedence.
|
10
|
+
# - source: if set, must be an object that responds to +x+ and +y+. The
|
11
|
+
# position of the particle system will be updated to this object's
|
12
|
+
# position on initialization and every time +update+ is called.
|
13
|
+
# - emission_interval (Integer|Range): interval in frames between each
|
14
|
+
# particle emission. It can be a fixed value or a range, in which case
|
15
|
+
# the interval will be a random value within that range (a new value
|
16
|
+
# before each emission). Default: 10.
|
17
|
+
# - emission_rate (Integer|Range): how many particles will be emitted at a
|
18
|
+
# time. It can be a fixed value or a range, in which case a random number
|
19
|
+
# of particles in that range will be emitted each time. Default: 1.
|
20
|
+
# - duration (Integer): how many frames each particle will live. Default: 30.
|
21
|
+
# - shape (Symbol|nil): one of +:square+, +:triangle_up+, or
|
22
|
+
# +:triangle_down+, to emit basic shapes (if the +img+ option is set, it
|
23
|
+
# has precedence). Shape particles don't support rotation. Either this or
|
24
|
+
# +img+ must be set.
|
25
|
+
# - img (Gosu::Image|nil): image of the particle, has precedence over
|
26
|
+
# +shape+. Either this or +shape+ must be set.
|
27
|
+
# - scale (Numeric): fixed scale of each particle, ignored if +scale_change+
|
28
|
+
# is set to a valid value. Default: 1.
|
29
|
+
# - scale_change (Symbol|nil): one of +:grow+, +:shrink+, or +:alternate+,
|
30
|
+
# indicates how the scale of the particle will change over time. +:grow+
|
31
|
+
# will cause the scale to change from +scale_min+ to +scale_max+;
|
32
|
+
# +:shrink+ will cause the scale to change from +scale_max+ to
|
33
|
+
# +scale_min+; +:alternate+ will cause the scale to first go from
|
34
|
+
# +scale_min+ to +scale_max+, in <code>scale_inflection * duration</code>
|
35
|
+
# frames, and then back to +scale_min+ in the remaining frames. All
|
36
|
+
# changes are linear over time.
|
37
|
+
# - scale_min (Numeric): minimum scale, to be used together with
|
38
|
+
# +scale_change+. Default: 0.
|
39
|
+
# - scale_max (Numeric): maximum scale, to be used together with
|
40
|
+
# +scale_change+. Default: 1.
|
41
|
+
# - scale_inflection (Numeric): should be a number between 0 and 1, to be
|
42
|
+
# used with +scale_change+ set to +:alternate+. Default: 0.5.
|
43
|
+
# - alpha (Numeric): fixed alpha of each particle, ignored if +alpha_change+
|
44
|
+
# is set to a valid value. Default: 255.
|
45
|
+
# - alpha_change, alpha_min, alpha_max, alpha_inflection: behave the same
|
46
|
+
# way as the corresponding properties for +scale+. Default +alpha_max+ is
|
47
|
+
# 255.
|
48
|
+
# - angle (Numeric|Range|nil): initial rotation angle of each particle in
|
49
|
+
# degrees. Can be a fixed value or a range, in which case the initial
|
50
|
+
# rotation will be a random value within that range. Default: nil (no
|
51
|
+
# rotation).
|
52
|
+
# - rotation(Numeric|nil): how much each particle will rotate each frame,
|
53
|
+
# in degrees. Default: nil (no rotation).
|
54
|
+
# - speed (Vector|Hash|nil): specifies how the particle will move each
|
55
|
+
# frame. It can be a +Vector+, in which case the particle will move a
|
56
|
+
# fixed amount (corresponding to the +x+ and +y+ values of the vector)
|
57
|
+
# or a +Hash+ with +:x+ and +:y+ keys, in this case the value can be
|
58
|
+
# fixed or a range, for random movement. Default: nil (no movement).
|
59
|
+
# - color (Integer): color to tint the particles, in the 0xRRGGBB format.
|
60
|
+
# Default: 0xffffff (white, no tinting).
|
61
|
+
# - round_position (Boolean): only draw particles in integer positions.
|
62
|
+
# Default: true.
|
63
|
+
def initialize(**options)
|
64
|
+
raise "Particles must have either a shape or an image!" if options[:shape].nil? && options[:img].nil?
|
65
|
+
|
66
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
67
|
+
@x = @options[:source]&.x || @options[:x] || 0
|
68
|
+
@y = @options[:source]&.y || @options[:y] || 0
|
69
|
+
|
70
|
+
@particles = []
|
71
|
+
@emitting = false
|
72
|
+
end
|
73
|
+
|
74
|
+
# Starts emitting particles. This returns +self+, so you can create, start,
|
75
|
+
# and assign a particle system to a variable like this:
|
76
|
+
# <code>@p_system = Particles.new(...).start</code>
|
77
|
+
def start
|
78
|
+
set_emission_time
|
79
|
+
@timer = @emission_time
|
80
|
+
@emitting = true
|
81
|
+
self
|
82
|
+
end
|
83
|
+
|
84
|
+
# Stops emitting new particles. The existing particles will still be kept
|
85
|
+
# alive until they hit +duration+ frames.
|
86
|
+
def stop
|
87
|
+
@emitting = false
|
88
|
+
end
|
89
|
+
|
90
|
+
# Returns a boolean indicating whether this particle system is currently
|
91
|
+
# emitting particles.
|
92
|
+
def emitting?
|
93
|
+
@emitting
|
94
|
+
end
|
95
|
+
|
96
|
+
# Returns the current particle count.
|
97
|
+
def count
|
98
|
+
@particles.size
|
99
|
+
end
|
100
|
+
|
101
|
+
# Updates the particle system. This should be called in the +update+ loop
|
102
|
+
# of the game.
|
103
|
+
def update
|
104
|
+
@particles.each do |particle|
|
105
|
+
particle.update
|
106
|
+
@particles.delete(particle) if particle.dead?
|
107
|
+
end
|
108
|
+
return unless @emitting
|
109
|
+
|
110
|
+
if @options[:source]
|
111
|
+
@x = @options[:source].x
|
112
|
+
@y = @options[:source].y
|
113
|
+
end
|
114
|
+
|
115
|
+
@timer += 1
|
116
|
+
if @timer >= @emission_time
|
117
|
+
count = @options[:emission_rate].is_a?(Range) ? rand(@options[:emission_rate]) : @options[:emission_rate]
|
118
|
+
count.times do
|
119
|
+
x = @options[:area] ? @x + rand * @options[:area].x : @x + @options[:spread] * (rand - 0.5)
|
120
|
+
y = @options[:area] ? @y + rand * @options[:area].y : @y + @options[:spread] * (rand - 0.5)
|
121
|
+
@particles << Particle.new(x:,
|
122
|
+
y:,
|
123
|
+
duration: @options[:duration],
|
124
|
+
shape: @options[:shape],
|
125
|
+
img: @options[:img],
|
126
|
+
**@options.slice(*PARTICLE_OPTIONS))
|
127
|
+
end
|
128
|
+
set_emission_time
|
129
|
+
@timer = 0
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# Draws the particles.
|
134
|
+
# Parameters:
|
135
|
+
# - map (Map|nil): a map whose camera will be used to determine the
|
136
|
+
# position of particles in the screen.
|
137
|
+
# - z_index (Integer): z-index to draw the particles. Default: 0.
|
138
|
+
def draw(map = nil, z_index = 0)
|
139
|
+
@particles.each do |particle|
|
140
|
+
particle.draw(map, z_index)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
# :nodoc:
|
147
|
+
DEFAULT_OPTIONS = {
|
148
|
+
x: 0,
|
149
|
+
y: 0,
|
150
|
+
source: nil,
|
151
|
+
emission_interval: 10,
|
152
|
+
emission_rate: 1,
|
153
|
+
duration: 30,
|
154
|
+
shape: nil,
|
155
|
+
img: nil,
|
156
|
+
spread: 0,
|
157
|
+
scale: 1,
|
158
|
+
scale_change: nil,
|
159
|
+
scale_min: 0,
|
160
|
+
scale_max: 1,
|
161
|
+
scale_inflection: 0.5,
|
162
|
+
alpha: 255,
|
163
|
+
alpha_change: nil,
|
164
|
+
alpha_min: 0,
|
165
|
+
alpha_max: 255,
|
166
|
+
alpha_inflection: 0.5,
|
167
|
+
angle: nil,
|
168
|
+
rotation: nil,
|
169
|
+
speed: nil,
|
170
|
+
color: 0xffffff,
|
171
|
+
round_position: true,
|
172
|
+
}.freeze
|
173
|
+
|
174
|
+
# :nodoc:
|
175
|
+
PARTICLE_OPTIONS = %i[
|
176
|
+
scale
|
177
|
+
scale_change
|
178
|
+
scale_min
|
179
|
+
scale_max
|
180
|
+
scale_inflection
|
181
|
+
alpha
|
182
|
+
alpha_change
|
183
|
+
alpha_min
|
184
|
+
alpha_max
|
185
|
+
alpha_inflection
|
186
|
+
angle
|
187
|
+
rotation
|
188
|
+
speed
|
189
|
+
color
|
190
|
+
round_position
|
191
|
+
].freeze
|
192
|
+
|
193
|
+
def set_emission_time # :nodoc:
|
194
|
+
interval = @options[:emission_interval]
|
195
|
+
@emission_time = interval.is_a?(Range) ? rand(interval) : interval
|
196
|
+
end
|
197
|
+
|
198
|
+
class Particle # :nodoc:
|
199
|
+
def initialize(x:, y:, duration:, shape: nil, img: nil, **options)
|
200
|
+
@x = x
|
201
|
+
@y = y
|
202
|
+
@duration = duration
|
203
|
+
@shape = shape
|
204
|
+
@img = img
|
205
|
+
@options = DEFAULT_OPTIONS.slice(*PARTICLE_OPTIONS).merge(options)
|
206
|
+
@elapsed_time = 0
|
207
|
+
|
208
|
+
if @options[:angle].is_a?(Range)
|
209
|
+
@angle = rand(@options[:angle])
|
210
|
+
elsif @options[:angle].is_a?(Numeric)
|
211
|
+
@angle = @options[:angle]
|
212
|
+
end
|
213
|
+
|
214
|
+
if @options[:speed].is_a?(Hash)
|
215
|
+
speed_x = @options[:speed][:x].is_a?(Range) ? rand(@options[:speed][:x]) : (@options[:speed][:x] || 0)
|
216
|
+
speed_y = @options[:speed][:y].is_a?(Range) ? rand(@options[:speed][:y]) : (@options[:speed][:y] || 0)
|
217
|
+
@speed = Vector.new(speed_x, speed_y)
|
218
|
+
elsif @options[:speed].is_a?(Vector)
|
219
|
+
@speed = @options[:speed]
|
220
|
+
end
|
221
|
+
|
222
|
+
init_variable_property(:scale)
|
223
|
+
init_variable_property(:alpha)
|
224
|
+
end
|
225
|
+
|
226
|
+
def init_variable_property(name)
|
227
|
+
ivar_name = "@#{name}".to_sym
|
228
|
+
case @options["#{name}_change".to_sym]
|
229
|
+
when :grow, :alternate
|
230
|
+
instance_variable_set(ivar_name, @options["#{name}_min".to_sym])
|
231
|
+
when :shrink
|
232
|
+
instance_variable_set(ivar_name, @options["#{name}_max".to_sym])
|
233
|
+
else
|
234
|
+
instance_variable_set(ivar_name, @options[name.to_sym])
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def update_variable_property(name)
|
239
|
+
ivar_name = "@#{name}".to_sym
|
240
|
+
min = @options["#{name}_min".to_sym]
|
241
|
+
max = @options["#{name}_max".to_sym]
|
242
|
+
case @options["#{name}_change".to_sym]
|
243
|
+
when :grow
|
244
|
+
instance_variable_set(ivar_name, min + (@elapsed_time.to_f / @duration) * (max - min))
|
245
|
+
when :shrink
|
246
|
+
instance_variable_set(ivar_name, max - (@elapsed_time.to_f / @duration) * (max - min))
|
247
|
+
when :alternate
|
248
|
+
inflection_point = (@options["#{name}_inflection".to_sym] * @duration).round
|
249
|
+
if @elapsed_time >= inflection_point
|
250
|
+
instance_variable_set(ivar_name, min + (@duration - @elapsed_time).to_f / (@duration - inflection_point) * (max - min))
|
251
|
+
else
|
252
|
+
instance_variable_set(ivar_name, min + (@elapsed_time.to_f / inflection_point) * (max - min))
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
def dead?
|
258
|
+
@elapsed_time >= @duration
|
259
|
+
end
|
260
|
+
|
261
|
+
def update
|
262
|
+
if @options[:rotation] && !@img.nil?
|
263
|
+
@angle = 0 if @angle.nil?
|
264
|
+
@angle += @options[:rotation]
|
265
|
+
@angle -= 360 if @angle >= 360
|
266
|
+
end
|
267
|
+
|
268
|
+
if @speed
|
269
|
+
@x += @speed.x
|
270
|
+
@y += @speed.y
|
271
|
+
end
|
272
|
+
|
273
|
+
update_variable_property(:scale) if @options[:scale_change]
|
274
|
+
if @options[:alpha_change]
|
275
|
+
update_variable_property(:alpha)
|
276
|
+
@alpha = @alpha.round
|
277
|
+
end
|
278
|
+
|
279
|
+
@elapsed_time += 1
|
280
|
+
end
|
281
|
+
|
282
|
+
def draw(map, z_index)
|
283
|
+
x = @x - (map&.cam&.x || 0)
|
284
|
+
y = @y - (map&.cam&.y || 0)
|
285
|
+
if @options[:round_position]
|
286
|
+
x = x.round
|
287
|
+
y = y.round
|
288
|
+
end
|
289
|
+
color = (@alpha << 24) | @options[:color]
|
290
|
+
if @img
|
291
|
+
if @angle
|
292
|
+
@img.draw_rot(x, y, z_index, @angle, 0.5, 0.5, @scale, @scale, color)
|
293
|
+
else
|
294
|
+
@img.draw(x - @img.width * @scale * 0.5, y - @img.height * @scale * 0.5, z_index, @scale, @scale, color)
|
295
|
+
end
|
296
|
+
else
|
297
|
+
case @shape
|
298
|
+
when :square
|
299
|
+
G.window.draw_rect(@x - @scale * 0.5, @y - @scale * 0.5, @scale, @scale, color, z_index)
|
300
|
+
when :triangle_up
|
301
|
+
G.window.draw_triangle(@x - @scale * 0.5, @y + @scale * 0.433, color,
|
302
|
+
@x + @scale * 0.5, @y + @scale * 0.433, color,
|
303
|
+
@x, @y - @scale * 0.433, color, z_index)
|
304
|
+
when :triangle_down
|
305
|
+
G.window.draw_triangle(@x - @scale * 0.5, @y - @scale * 0.433, color,
|
306
|
+
@x + @scale * 0.5, @y - @scale * 0.433, color,
|
307
|
+
@x, @y + @scale * 0.433, color, z_index)
|
308
|
+
end
|
309
|
+
end
|
310
|
+
end
|
311
|
+
end
|
312
|
+
end
|
313
|
+
end
|
data/lib/minigl.rb
CHANGED
@@ -0,0 +1,70 @@
|
|
1
|
+
require_relative '../lib/minigl'
|
2
|
+
|
3
|
+
include MiniGL
|
4
|
+
|
5
|
+
class MyGame < GameWindow
|
6
|
+
class Source
|
7
|
+
attr_accessor :x, :y
|
8
|
+
|
9
|
+
def initialize(x, y)
|
10
|
+
@x = x
|
11
|
+
@y = y
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
super(800, 600, false)
|
17
|
+
@source = Source.new(100, 100)
|
18
|
+
@particles_systems = [
|
19
|
+
Particles.new(
|
20
|
+
source: @source,
|
21
|
+
img: Res.img(:square),
|
22
|
+
duration: 30,
|
23
|
+
spread: 50,
|
24
|
+
emission_rate: 5,
|
25
|
+
color: 0x00ffff,
|
26
|
+
scale_change: :grow,
|
27
|
+
alpha_change: :shrink,
|
28
|
+
speed: { x: -1..1, y: -2..2 },
|
29
|
+
rotation: 1,
|
30
|
+
angle: 0..89
|
31
|
+
),
|
32
|
+
Particles.new(
|
33
|
+
x: 400,
|
34
|
+
y: 100,
|
35
|
+
shape: :triangle_down,
|
36
|
+
spread: 50,
|
37
|
+
emission_interval: 8,
|
38
|
+
emission_rate: 1..3,
|
39
|
+
scale: 40,
|
40
|
+
color: 0xffff00,
|
41
|
+
alpha_change: :alternate,
|
42
|
+
),
|
43
|
+
]
|
44
|
+
@particles_systems.each(&:start)
|
45
|
+
end
|
46
|
+
|
47
|
+
def update
|
48
|
+
KB.update
|
49
|
+
@source.x -= 3 if KB.key_down?(Gosu::KB_LEFT)
|
50
|
+
@source.x += 3 if KB.key_down?(Gosu::KB_RIGHT)
|
51
|
+
@source.y -= 3 if KB.key_down?(Gosu::KB_UP)
|
52
|
+
@source.y += 3 if KB.key_down?(Gosu::KB_DOWN)
|
53
|
+
|
54
|
+
if KB.key_pressed?(Gosu::KB_SPACE)
|
55
|
+
if @particles_systems[1].emitting?
|
56
|
+
@particles_systems[1].stop
|
57
|
+
else
|
58
|
+
@particles_systems[1].start
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
@particles_systems.each(&:update)
|
63
|
+
end
|
64
|
+
|
65
|
+
def draw
|
66
|
+
@particles_systems.each(&:draw)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
MyGame.new.show
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: minigl
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.5.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Victor David Santos
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-10-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: gosu
|
@@ -46,6 +46,7 @@ files:
|
|
46
46
|
- lib/minigl/localization.rb
|
47
47
|
- lib/minigl/map.rb
|
48
48
|
- lib/minigl/movement.rb
|
49
|
+
- lib/minigl/particles.rb
|
49
50
|
- lib/minigl/text.rb
|
50
51
|
- test/collision.rb
|
51
52
|
- test/data/font/font1.ttf
|
@@ -82,6 +83,7 @@ files:
|
|
82
83
|
- test/map_tests.rb
|
83
84
|
- test/mov_game.rb
|
84
85
|
- test/movement_tests.rb
|
86
|
+
- test/particles_game.rb
|
85
87
|
- test/res_tests.rb
|
86
88
|
- test/test.png
|
87
89
|
- test/vector_tests.rb
|
@@ -97,14 +99,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
97
99
|
requirements:
|
98
100
|
- - ">="
|
99
101
|
- !ruby/object:Gem::Version
|
100
|
-
version: '
|
102
|
+
version: '3.1'
|
101
103
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
104
|
requirements:
|
103
105
|
- - ">="
|
104
106
|
- !ruby/object:Gem::Version
|
105
107
|
version: '0'
|
106
108
|
requirements: []
|
107
|
-
rubygems_version: 3.
|
109
|
+
rubygems_version: 3.4.10
|
108
110
|
signing_key:
|
109
111
|
specification_version: 4
|
110
112
|
summary: MiniGL
|
@@ -117,6 +119,7 @@ test_files:
|
|
117
119
|
- test/map_tests.rb
|
118
120
|
- test/mov_game.rb
|
119
121
|
- test/movement_tests.rb
|
122
|
+
- test/particles_game.rb
|
120
123
|
- test/res_tests.rb
|
121
124
|
- test/vector_tests.rb
|
122
125
|
- test/test.png
|