Skip to content

Commit 8b19038

Browse files
committed
particles kinda workin
1 parent 60b4623 commit 8b19038

7 files changed

Lines changed: 317 additions & 55 deletions

File tree

Fire_Rescue/data/game_data.lsd

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,7 @@ assets: {
143143
"building back": {
144144
z_index: -1,
145145
position: [ 0, 0 ],
146-
render_scale_basis: 9,
147-
render_scale_axis: .Y,
146+
sizing: [ 9, .Y ],
148147
texture_id: .BUILDING_BACK,
149148

150149
animations: {
@@ -156,12 +155,44 @@ assets: {
156155
// READY: $"IN_GAME",
157156
// GAME_OVER: $"IN_GAME",
158157
}
158+
159+
particle_emitters: [
160+
{
161+
desired_particle_count: 256,
162+
texture_id: .FIRE,
163+
164+
velocity: [ [ 0, -1 ], [ 0, -1 ] ],
165+
acceleration: [ [ 0, -0.1], [ 0, -0.1 ] ],
166+
167+
emit_box: [ 0, 0, 16, 9 ], // for testing
168+
emit_frequency: [ 0.1, 0.5 ],
169+
fade_in_time: [ 0.1, 0.1 ],
170+
fade_out_time: [ 0.1, 0.1 ],
171+
172+
rotation: [ 0, 6.28 ],
173+
rotation_velocity: [ -3.14, 3.14 ],
174+
rotation_acceleration: [ -7, 7 ],
175+
176+
size: [ [ 0.25, 0.25 ], [ 0.5, 0.5 ] ],
177+
size_velocity: [ [ -0.001, -0.001 ], [ 0.001, 0.001 ] ],
178+
size_acceleration: [ [ -0.001, -0.001 ], [ 0.001, 0.001 ] ],
179+
180+
alpha: [ 1, 1 ],
181+
182+
lifetime: [ 1, 3 ],
183+
184+
animation: {
185+
frames: [
186+
{ clip: [ 0, 0, 16, 16 ] }
187+
]
188+
}
189+
}
190+
]
159191
}
160192
"building front": {
161193
z_index: 1,
162194
position: [ 0, 0 ],
163-
render_scale_basis: 9,
164-
render_scale_axis: .Y,
195+
sizing: [ 9, .Y ],
165196
texture_id: .BUILDING_FRONT,
166197

167198
animations: {

Fire_Rescue/source/game.jai

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ Game: struct {
4646
decorations: [] Decoration;
4747
}
4848

49-
controller: Enumerated_Array(Controller_Keys, Input_Key);
50-
49+
// misc
50+
controller: Enumerated_Array(Controller_Keys, Input_Key);
5151
tile_render_unit: float;
5252
viewport: Rectf;
5353
pending_reload: bool;
@@ -65,17 +65,8 @@ Game: struct {
6565
next_jump_time: float;
6666
time_of_last_mode_change: float;
6767

68-
69-
// visual
70-
// foreground_particles: Slot_Array(Particle, 256);
71-
// background_particles: Slot_Array(Particle, 256);
72-
// Particle_Emitter smoke_emitter;
73-
// Particle_Emitter fire_emitter;
74-
75-
7668
// frame timing info
7769
last_update_time: Apollo_Time;
78-
7970
last_render_time: Apollo_Time;
8071
render_delta_time: float; // delta_time used for variable-step update
8172

@@ -131,7 +122,7 @@ init_game :: () {
131122
});
132123
if !ok exit(1);
133124

134-
// post-processing on physics valuesk
125+
// post-processing on physics values
135126
for *guy_templates {
136127
using it;
137128

@@ -143,6 +134,12 @@ init_game :: () {
143134

144135
for *textures Simp.texture_load_from_file(it, texture_paths[it_index]);
145136

137+
for *decorations {
138+
for *it.particle_emitters {
139+
prep_particle_emitter(it);
140+
}
141+
}
142+
146143
// window resize needs to be handled after loading tile_unit_basis
147144
handle_window_resize_for_game();
148145
}
@@ -273,8 +270,7 @@ Decoration :: struct {
273270
z_index: int; // negative is rendered behind player and guys, positive is in front
274271

275272
position: Vec2f;
276-
render_scale_basis: float;
277-
render_scale_axis: Axis;
273+
sizing: Sizing_Policy;
278274

279275
texture_id: Texture_ID;
280276
animator: Simple_Animator(Game.state.Mode);
@@ -287,6 +283,9 @@ free_decoration :: (decor: *Decoration) {
287283
for *decor.animations {
288284
free_animation(it);
289285
}
286+
for *decor.particle_emitters {
287+
free_particle_emitter(it);
288+
}
290289
}
291290

292291
handle_window_resize_for_game :: () {
@@ -641,7 +640,7 @@ render_decoration :: (decor: *Decoration) {
641640
clip := to_Rectf(current_frame.clip);
642641
texture := *textures[decor.texture_id];
643642

644-
render_scale := decor.render_scale_basis / get_axis(clip.size, decor.render_scale_axis);
643+
render_scale := get_scalar(clip.size, decor.sizing);
645644
render_offset := current_frame.offset * render_scale;
646645

647646
render_rect := Rectf.{
@@ -655,6 +654,10 @@ render_decoration :: (decor: *Decoration) {
655654
size = render_rect.size,
656655
texture = texture,
657656
);
657+
658+
for *decor.particle_emitters {
659+
do_particle_emitter(it, .{});
660+
}
658661
}
659662

660663
render_ambulance :: () {
@@ -801,6 +804,13 @@ start_game :: () {
801804
player.position = constants.stretcher_init_position;
802805
score = 0;
803806
lives = 3;
807+
808+
for *decorations {
809+
for *it.particle_emitters {
810+
memset(it.particles.data, 0, it.particles.count * size_of(Particle));
811+
it.next_emit_time = 0;
812+
}
813+
}
804814
}
805815

806816
spawn_new_guy :: () {
@@ -837,4 +847,15 @@ get_sorted_decorations :: () -> (bg: [] *Decoration, fg: [] *Decoration) {
837847
bg := array_view(sorted, 0, first_fg_index);
838848
fg := array_view(sorted, first_fg_index);
839849
return bg, fg;
840-
}
850+
}
851+
852+
853+
Sizing_Policy :: struct {
854+
// units: enum { TILE; VIEWPORT; WINDOW; }; // TODO
855+
target_size: float;
856+
scale_axis: Axis;
857+
}
858+
859+
get_scalar :: inline (size: Vec2f, policy: Sizing_Policy) -> float {
860+
return policy.target_size / get_axis(size, policy.scale_axis);
861+
}

Fire_Rescue/source/main.jai

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,3 +174,4 @@ init_gon_io_data :: () {
174174
// // IMPORTANT NOTE: right now this also serves as the owner for 'source strings' that are referenced by the script!
175175
// prev_commands: [..] struct { statement: string; result: string; };
176176
// prev_commands_index: int;
177+

Fire_Rescue/source/particles.jai

Lines changed: 87 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
/*
33
Particles and Emitters are updated on a variable-timestep basis.
44
Becuase of this, we don't really separate the update and render routines.
5+
6+
TODO: definitely need to set proper sizing modes based on a single axis,
7+
rather than having absolute sizing where each axis is scaled independently
8+
59
*/
610

711

@@ -34,50 +38,52 @@ Particle :: struct {
3438
// maybe later add animation states so we can transition to another state on death or contact with certain elements in level
3539
}
3640

37-
do_particle :: (emitter: Particle_Emitter, using particle: *Particle, delta_time: float) -> bool {
38-
particle.velocity += particle.acceleration * delta_time;
39-
particle.position += particle.velocity * delta_time;
41+
do_particle :: (using particle: *Particle, emitter: Particle_Emitter, delta_time: float) -> bool {
42+
velocity += acceleration * delta_time;
43+
position += velocity * delta_time;
44+
45+
rotation_velocity += rotation_acceleration * delta_time;
46+
rotation += rotation_velocity * delta_time;
4047

41-
particle.rotation_velocity += particle.rotation_acceleration * delta_time;
42-
particle.rotation += particle.rotation_velocity * delta_time;
48+
alpha_velocity += alpha_acceleration * delta_time;
49+
alpha += alpha_velocity * delta_time;
4350

44-
particle.alpha_velocity += particle.alpha_acceleration * delta_time;
45-
particle.alpha += particle.alpha_velocity * delta_time;
51+
step_animator(*animator, emitter.animation, Game.render_delta_time);
4652

47-
step_animator(*animator, animation, Game.render_delta_time);
53+
offset := Vec2f.{ 0, 0 }; // TODO: make parameter
4854

49-
clip := rect_to_frect(animation.frames[animator.current].clip);
50-
render_size := size * render_unit;
51-
render_position := ((position + offset) * tile_render_unit) - (render_size / 2);
55+
current_frame := get_current_frame(animator, emitter.animation);
56+
clip := rect_to_frect(current_frame.clip);
57+
render_size := size * Game.tile_render_unit;
58+
render_position := ((position + offset) * Game.tile_render_unit) - (render_size / 2);
5259

53-
lifetime_lerp := clamp((Game.current_time - spawn_time) / lifetime, 0, 1);
60+
lifetime_lerp := clamp((Game.time_since_start - spawn_time) / lifetime, 0, 1);
5461

5562
// fade-in and fade-out particle
56-
render_alpha = alpha;
63+
render_alpha := alpha;
5764
if lifetime_lerp < fade_in_time {
5865
render_alpha *= lifetime_lerp / fade_in_time;
5966
} else if lifetime_lerp > 1 - fade_out_time {
6067
render_alpha *= ((1 - lifetime_lerp) / fade_out_time);
6168
}
6269

70+
texture := *Game.textures[emitter.texture_id];
71+
6372
render_draw_quad(
6473
texture = texture,
6574
position = render_position,
6675
size = render_size,
6776
clip = *clip,
68-
flip = animation.frames[animator.current].flip,
69-
palette = palette,
77+
flip = current_frame.flip,
78+
// palette = palette,
7079
color = .{ 1, 1, 1, render_alpha },
7180
rotate = rotation
7281
);
7382

74-
return spawn_time + lifetime > Game.current_time;
83+
return spawn_time + lifetime > Game.time_since_start;
7584
}
7685

7786
Particle_Emitter :: struct {
78-
my_update_particle: type_of(update_particle);
79-
my_render_particle: type_of(render_particle);
80-
8187
// user can set desired_particle_count in LSD,
8288
// then we use that to init the particles array
8389
desired_particle_count: int;
@@ -97,7 +103,7 @@ Particle_Emitter :: struct {
97103
rotation_velocity: Range(float);
98104
rotation_acceleration: Range(float);
99105

100-
alpha: Range(float);
106+
alpha: Range(float) = .{ 1, 1 };
101107
alpha_velocity: Range(float);
102108
alpha_acceleration: Range(float);
103109

@@ -117,19 +123,32 @@ Particle_Emitter :: struct {
117123
}
118124
}
119125

120-
init_particle_emitter :: (emitter: Particle_Emitter) {
121-
emitter.particles = NewArray(particle_count, desired_particle_count);
126+
free_particle_emitter :: (using emitter: *Particle_Emitter) {
127+
array_free(emitter.particles);
128+
}
129+
130+
prep_particle_emitter :: (using emitter: *Particle_Emitter) {
131+
particles = NewArray(desired_particle_count, Particle);
122132

123-
if emit_frequency[0] <= 0 {
124-
log("Warning: emit_frquency values cannot be zero! Defaulting to 1.");
125-
emit_frequency[0] = 1;
133+
check_emit_frequency :: inline (value: *float, name: string) {
134+
if value.* <= 0 {
135+
log("Warning: % must be greater than zero! Defaulting to 1.", name);
136+
value.* = 1;
137+
}
126138
}
127-
if emit_frequency[1] <= 0 {
128-
log("Warning: emit_frquency values cannot be zero! Defaulting to 1.");
129-
emit_frequency[1] = 1;
139+
check_emit_frequency(*emit_frequency.min, "emit_frequency.min");
140+
check_emit_frequency(*emit_frequency.max, "emit_frequency.max");
141+
142+
check_fade_time :: inline (value: *float, name: string) {
143+
if value.* < 0 {
144+
log("Warning: % must be between 0 and 1! Clamping value.", name);
145+
value.* = clamp(value.*, 0, 1);
146+
}
130147
}
131-
132-
// check fade times
148+
check_fade_time(*fade_in_time.min, "fade_in_time.min");
149+
check_fade_time(*fade_in_time.max, "fade_in_time.max");
150+
check_fade_time(*fade_out_time.min, "fade_out_time.min");
151+
check_fade_time(*fade_out_time.max, "fade_out_time.max");
133152
}
134153

135154
get_first_empty_particle_slot :: (emitter: *Particle_Emitter) -> *Particle {
@@ -138,10 +157,10 @@ get_first_empty_particle_slot :: (emitter: *Particle_Emitter) -> *Particle {
138157
}
139158

140159
do_particle_emitter :: (using emitter: *Particle_Emitter, offset: Vector2) {
141-
while Game.current_time > next_emit_time {
160+
while Game.time_since_start > next_emit_time {
142161
p := get_first_empty_particle_slot(emitter);
143162
if !p {
144-
next_emit_time = Game.current_time + random_get_within_range(emit_frequency);
163+
next_emit_time = Game.time_since_start + random_get_within_range(emit_frequency);
145164
break;
146165
}
147166

@@ -160,6 +179,9 @@ do_particle_emitter :: (using emitter: *Particle_Emitter, offset: Vector2) {
160179
alpha_acceleration = random_get_within_range(alpha_acceleration),
161180
lifetime = random_get_within_range(lifetime),
162181

182+
fade_in\_time = random_get_within_range(fade_in\_time),
183+
fade_out_time = random_get_within_range(fade_out_time),
184+
163185
spawn_time = next_emit_time,
164186
active = true,
165187
};
@@ -168,13 +190,44 @@ do_particle_emitter :: (using emitter: *Particle_Emitter, offset: Vector2) {
168190
}
169191

170192
for *emitter.particles {
171-
if !do_particle(emitter, it) {
193+
if !it.active continue;
194+
if !do_particle(it, emitter, Game.render_delta_time) {
172195
it.active = false;
173196
}
174197
}
175198
}
176199

177200

201+
// manual particle emitting procedures
202+
203+
// emit particles using the particle emitter's properties to initialize
204+
// TODO: create a version that allows user to manually override particular initializer properties
205+
// this will require either making some separate Particle_Initializer struct or having a lot of optional parameters
206+
emit_particles :: (using emitter: *Particle_Emitter, count: int, offset: Vector2) {
207+
for 0..count-1 {
208+
p := get_first_empty_particle_slot(emitter);
209+
if !p return;
210+
211+
p.* = Particle.{
212+
position = offset + emit_box.position + random_get_within_range(Vector2.{}, emit_box.size),
213+
velocity = random_get_within_range(velocity),
214+
acceleration = random_get_within_range(acceleration),
215+
size = random_get_within_range(size),
216+
size_velocity = random_get_within_range(size_velocity),
217+
size_acceleration = random_get_within_range(size_acceleration),
218+
rotation = random_get_within_range(rotation),
219+
rotation_velocity = random_get_within_range(rotation_velocity),
220+
rotation_acceleration = random_get_within_range(rotation_acceleration),
221+
alpha = random_get_within_range(alpha),
222+
alpha_velocity = random_get_within_range(alpha_velocity),
223+
alpha_acceleration = random_get_within_range(alpha_acceleration),
224+
lifetime = random_get_within_range(lifetime),
225+
226+
spawn_time = Game.time_since_start,
227+
active = true,
228+
};
229+
}
230+
}
178231

179232

180233

@@ -183,7 +236,7 @@ do_particle_emitter :: (using emitter: *Particle_Emitter, offset: Vector2) {
183236
// TODO: move to another file?
184237
Range :: struct(T: Type) { min, max: T; }
185238

186-
random_get_within_range :: (using range: Range(T)) -> T {
239+
random_get_within_range :: (using range: Range($T)) -> T {
187240
return random_get_within_range(range.min, range.max);
188241
}
189242

0 commit comments

Comments
 (0)