add particles
This commit is contained in:
parent
ae088c5a0e
commit
9320b46f68
180
src/app.zig
180
src/app.zig
@ -6,6 +6,7 @@ const assert = std.debug.assert;
|
|||||||
|
|
||||||
const Math = @import("math");
|
const Math = @import("math");
|
||||||
const Vec2 = Math.Vec2;
|
const Vec2 = Math.Vec2;
|
||||||
|
const Range = Math.Range;
|
||||||
|
|
||||||
const STBImage = @import("stb_image");
|
const STBImage = @import("stb_image");
|
||||||
const STBTrueType = @import("stb_truetype");
|
const STBTrueType = @import("stb_truetype");
|
||||||
@ -26,6 +27,8 @@ const Prng = std.Random.DefaultPrng;
|
|||||||
|
|
||||||
const max_entities = 2048;
|
const max_entities = 2048;
|
||||||
|
|
||||||
|
const max_particles = 4096;
|
||||||
|
|
||||||
gpa: Allocator,
|
gpa: Allocator,
|
||||||
camera_pos: Vec2,
|
camera_pos: Vec2,
|
||||||
prng: Prng,
|
prng: Prng,
|
||||||
@ -39,6 +42,9 @@ eternal_flame_id: EntityId,
|
|||||||
minions_buffer: [max_entities]EntityId,
|
minions_buffer: [max_entities]EntityId,
|
||||||
minions: std.ArrayList(EntityId),
|
minions: std.ArrayList(EntityId),
|
||||||
|
|
||||||
|
particles_buffer: [max_particles]Particle,
|
||||||
|
particles: std.ArrayList(Particle),
|
||||||
|
|
||||||
upgrades: Upgrades,
|
upgrades: Upgrades,
|
||||||
shop: Shop,
|
shop: Shop,
|
||||||
|
|
||||||
@ -118,6 +124,18 @@ const Tilesheet = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const Particle = struct {
|
||||||
|
size: Vec2,
|
||||||
|
pos: Vec2,
|
||||||
|
rotation: f32,
|
||||||
|
vel: Vec2,
|
||||||
|
angular_vel: f32,
|
||||||
|
|
||||||
|
color: Color,
|
||||||
|
created_at: Nanoseconds,
|
||||||
|
dissapear_at: Nanoseconds,
|
||||||
|
};
|
||||||
|
|
||||||
const Animation = struct {
|
const Animation = struct {
|
||||||
frames: []Gfx.Sprite.Id,
|
frames: []Gfx.Sprite.Id,
|
||||||
frame_duration: Platform.Nanoseconds,
|
frame_duration: Platform.Nanoseconds,
|
||||||
@ -199,6 +217,7 @@ const Entity = struct {
|
|||||||
tree_health: u32 = 0,
|
tree_health: u32 = 0,
|
||||||
tree_visual_health: f32 = 0,
|
tree_visual_health: f32 = 0,
|
||||||
tree_max_health: u32 = 0,
|
tree_max_health: u32 = 0,
|
||||||
|
tree_trunk_height: u32 = 0,
|
||||||
|
|
||||||
station_spawn_tree_cooldown: Nanoseconds = 0,
|
station_spawn_tree_cooldown: Nanoseconds = 0,
|
||||||
station_owned_tree: ?EntityId = null,
|
station_owned_tree: ?EntityId = null,
|
||||||
@ -668,6 +687,9 @@ pub fn init(self: *App, plt: Platform.Init) !?u8 {
|
|||||||
.minions_buffer = undefined,
|
.minions_buffer = undefined,
|
||||||
.minions = .initBuffer(&self.minions_buffer),
|
.minions = .initBuffer(&self.minions_buffer),
|
||||||
|
|
||||||
|
.particles_buffer = undefined,
|
||||||
|
.particles = .initBuffer(&self.particles_buffer),
|
||||||
|
|
||||||
.terrain_sheet = terrain_sheet,
|
.terrain_sheet = terrain_sheet,
|
||||||
.terrain_tiles = terrain_tiles,
|
.terrain_tiles = terrain_tiles,
|
||||||
};
|
};
|
||||||
@ -949,7 +971,8 @@ fn spawnTree(self: *App, station_id: EntityId) void {
|
|||||||
.tree_max_health = health,
|
.tree_max_health = health,
|
||||||
.seed = rng.int(u64),
|
.seed = rng.int(u64),
|
||||||
|
|
||||||
.pos = station.pos.add(.init((rng.float(f32)-0.5) * terrain_tile_size.x, 0))
|
.pos = station.pos.add(.init((rng.float(f32)-0.5) * terrain_tile_size.x, 0)),
|
||||||
|
.tree_trunk_height = rng.uintAtMost(u32, 4) + 2
|
||||||
})) |id| {
|
})) |id| {
|
||||||
station.station_owned_tree = id;
|
station.station_owned_tree = id;
|
||||||
}
|
}
|
||||||
@ -1670,6 +1693,7 @@ fn showDebug(self: *App, plt: Platform.Frame) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_ = ImGUI.checkbox("Minion state", &self.show_minion_state);
|
_ = ImGUI.checkbox("Minion state", &self.show_minion_state);
|
||||||
|
_ = ImGUI.text("Particle count {}", .{self.particles.items.len});
|
||||||
|
|
||||||
ImGUI.separator();
|
ImGUI.separator();
|
||||||
_ = ImGUI.text("Minion upgrades", .{});
|
_ = ImGUI.text("Minion upgrades", .{});
|
||||||
@ -1767,7 +1791,7 @@ fn showDebug(self: *App, plt: Platform.Frame) !void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn drawTree(self: *App, seed: u64, pos: Vec2, health_percent: f32) void {
|
fn drawTree(self: *App, plt: Platform.Frame, seed: u64, pos: Vec2, trunk_height: u32, health_percent: f32) void {
|
||||||
var prng = Prng.init(seed);
|
var prng = Prng.init(seed);
|
||||||
const rng = prng.random();
|
const rng = prng.random();
|
||||||
|
|
||||||
@ -1789,8 +1813,6 @@ fn drawTree(self: *App, seed: u64, pos: Vec2, health_percent: f32) void {
|
|||||||
self.terrain_sheet.get(125, 39),
|
self.terrain_sheet.get(125, 39),
|
||||||
};
|
};
|
||||||
|
|
||||||
const trunk_height = rng.uintAtMost(u32, 4) + 2;
|
|
||||||
|
|
||||||
Gfx.drawSprite(self.terrain_sheet.get(125, 42), .init(0, 0), .init(1, 1), .white);
|
Gfx.drawSprite(self.terrain_sheet.get(125, 42), .init(0, 0), .init(1, 1), .white);
|
||||||
for (0..trunk_height) |i| {
|
for (0..trunk_height) |i| {
|
||||||
const sprite = tree_middle[rng.uintLessThan(u32, tree_middle.len)];
|
const sprite = tree_middle[rng.uintLessThan(u32, tree_middle.len)];
|
||||||
@ -1831,6 +1853,19 @@ fn drawTree(self: *App, seed: u64, pos: Vec2, health_percent: f32) void {
|
|||||||
Gfx.drawSprite(corner_leaves, .init(2, -1), .init(-1, -1), .white);
|
Gfx.drawSprite(corner_leaves, .init(2, -1), .init(-1, -1), .white);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.spawnParticles(plt, .{
|
||||||
|
.pos = pos.add(.init(0, -terrain_tile_size.y*(@as(f32, @floatFromInt(trunk_height))+1.5))),
|
||||||
|
.size = .init(4, 4),
|
||||||
|
.radius = .init(10, 40),
|
||||||
|
.count = .init(0, 0.03),
|
||||||
|
.duration_s = .init(0.5, 2.5),
|
||||||
|
.angle = .init(0, std.math.pi),
|
||||||
|
.color_variance = 0.1,
|
||||||
|
.color = .rgb(40, 150, 40),
|
||||||
|
.vel_y = .init(10, 60),
|
||||||
|
.angular_vel = .init(-4, 4)
|
||||||
|
});
|
||||||
|
|
||||||
if (health_percent < 1) {
|
if (health_percent < 1) {
|
||||||
drawProgressBar(1, .init(0, 20), .init(terrain_tile_size.x, 10), .black);
|
drawProgressBar(1, .init(0, 20), .init(terrain_tile_size.x, 10), .black);
|
||||||
drawProgressBar(health_percent, .init(0, 20), .init(terrain_tile_size.x, 10), .white);
|
drawProgressBar(health_percent, .init(0, 20), .init(terrain_tile_size.x, 10), .white);
|
||||||
@ -1859,6 +1894,61 @@ fn drawEntityAnimation(self: *App, e: *Entity, dt: Nanoseconds) void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ParticleOptions = struct {
|
||||||
|
pos: Vec2,
|
||||||
|
size: Vec2,
|
||||||
|
count: Range,
|
||||||
|
radius: Range,
|
||||||
|
angle: Range = .init(0, std.math.pi*2),
|
||||||
|
duration_s: Range = .init(0.5, 1),
|
||||||
|
vel_x: Range = .init(0, 0),
|
||||||
|
vel_y: Range = .init(0, 0),
|
||||||
|
angular_vel: Range = .init(0, 0),
|
||||||
|
|
||||||
|
color: Color = .white,
|
||||||
|
color_variance: f32 = 0
|
||||||
|
};
|
||||||
|
|
||||||
|
fn spawnParticles(self: *App, plt: Platform.Frame, opts: ParticleOptions) void {
|
||||||
|
const rng = self.prng.random();
|
||||||
|
|
||||||
|
var count_f32: f32 = 0;
|
||||||
|
if (opts.count.to < 1) {
|
||||||
|
if (opts.count.to > rng.float(f32)) {
|
||||||
|
count_f32 = 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
count_f32 = opts.count.random(rng);
|
||||||
|
}
|
||||||
|
for (0..@intFromFloat(count_f32)) |_| {
|
||||||
|
const radius = opts.radius.random(rng);
|
||||||
|
const angle = opts.angle.random(rng);
|
||||||
|
const duration_s = opts.duration_s.random(rng);
|
||||||
|
|
||||||
|
const color = Color{
|
||||||
|
.r = opts.color.r + ((rng.float(f32)-0.5)*2) * opts.color_variance,
|
||||||
|
.g = opts.color.g + ((rng.float(f32)-0.5)*2) * opts.color_variance,
|
||||||
|
.b = opts.color.b + ((rng.float(f32)-0.5)*2) * opts.color_variance,
|
||||||
|
.a = opts.color.a + ((rng.float(f32)-0.5)*2) * opts.color_variance,
|
||||||
|
};
|
||||||
|
|
||||||
|
const offset = Vec2.init(radius, 0).rotate(angle);
|
||||||
|
self.particles.appendBounded(Particle{
|
||||||
|
.color = color,
|
||||||
|
.pos = opts.pos.add(offset),
|
||||||
|
.size = opts.size,
|
||||||
|
.rotation = 0,
|
||||||
|
.created_at = plt.t,
|
||||||
|
.dissapear_at = plt.t + @as(Nanoseconds,@intFromFloat(duration_s * std.time.ns_per_s)),
|
||||||
|
.vel = .init(opts.vel_x.random(rng), opts.vel_y.random(rng)),
|
||||||
|
.angular_vel = opts.angular_vel.random(rng)
|
||||||
|
}) catch {
|
||||||
|
log.warn("Particle limit reached", .{});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn frame(self: *App, plt: Platform.Frame) !void {
|
pub fn frame(self: *App, plt: Platform.Frame) !void {
|
||||||
const input = plt.input;
|
const input = plt.input;
|
||||||
_ = input; // autofix
|
_ = input; // autofix
|
||||||
@ -2168,6 +2258,19 @@ pub fn frame(self: *App, plt: Platform.Frame) !void {
|
|||||||
break :blk animations.right;
|
break :blk animations.right;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (minion.minion_animation == .walk) {
|
||||||
|
self.spawnParticles(plt, .{
|
||||||
|
.pos = minion.pos,
|
||||||
|
.size = .init(8, 8),
|
||||||
|
.radius = .init(5, 30),
|
||||||
|
.count = .init(0, 0.03),
|
||||||
|
.duration_s = .init(0.5, 1.5),
|
||||||
|
.angle = .init(0, std.math.pi),
|
||||||
|
.color_variance = 0.1,
|
||||||
|
.color = .rgb(40, 150, 40),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2249,17 +2352,59 @@ pub fn frame(self: *App, plt: Platform.Frame) !void {
|
|||||||
|
|
||||||
const health_percent = e.tree_visual_health / @as(f32, @floatFromInt(e.tree_max_health));
|
const health_percent = e.tree_visual_health / @as(f32, @floatFromInt(e.tree_max_health));
|
||||||
self.drawTree(
|
self.drawTree(
|
||||||
|
plt,
|
||||||
e.seed,
|
e.seed,
|
||||||
e.pos,
|
e.pos,
|
||||||
|
e.tree_trunk_height,
|
||||||
health_percent
|
health_percent
|
||||||
);
|
);
|
||||||
|
|
||||||
if (e.tree_visual_health < 0.1) {
|
if (e.tree_visual_health < 0.1) {
|
||||||
self.destroyTree(id);
|
self.destroyTree(id);
|
||||||
|
|
||||||
|
for (0..e.tree_trunk_height) |i| {
|
||||||
|
self.spawnParticles(plt, .{
|
||||||
|
.pos = e.pos.add(.init(0, -terrain_tile_size.y*@as(f32,@floatFromInt(i+1)))),
|
||||||
|
.size = .init(4, 6),
|
||||||
|
.radius = .init(1, 16),
|
||||||
|
.count = .init(20, 70),
|
||||||
|
.duration_s = .init(0.2, 1.0),
|
||||||
|
.angle = .init(0, std.math.pi*2),
|
||||||
|
.color_variance = 0.1,
|
||||||
|
.color = .rgb(200, 120, 40),
|
||||||
|
.angular_vel = .init(-8, 8)
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{ // Draw particles
|
||||||
|
var i: usize = 0;
|
||||||
|
while (i < self.particles.items.len) {
|
||||||
|
const particle = &self.particles.items[i];
|
||||||
|
if (plt.t >= particle.dissapear_at) {
|
||||||
|
_ = self.particles.swapRemove(i);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const duration = @as(f64, @floatFromInt(particle.dissapear_at - particle.created_at));
|
||||||
|
const progress = @as(f64, @floatFromInt(plt.t - particle.created_at)) / duration;
|
||||||
|
|
||||||
|
particle.pos = particle.pos.add(particle.vel.multiplyScalar(dt));
|
||||||
|
particle.rotation += particle.angular_vel*dt;
|
||||||
|
|
||||||
|
var color = particle.color;
|
||||||
|
color.a *= (1-@as(f32, @floatCast(progress)));
|
||||||
|
|
||||||
|
var quad = Gfx.Quad.initRect(particle.size.divideScalar(-2), particle.size);
|
||||||
|
quad.applyRotate(particle.rotation, .init(0, 0));
|
||||||
|
quad.applyOffset(particle.pos);
|
||||||
|
Gfx.drawQuad(quad, color);
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
{ // Draw eternal flame
|
{ // Draw eternal flame
|
||||||
var eternal_flame_iter = self.initEntityIteratorByType(.eternal_flame);
|
var eternal_flame_iter = self.initEntityIteratorByType(.eternal_flame);
|
||||||
while (eternal_flame_iter.next()) |id| {
|
while (eternal_flame_iter.next()) |id| {
|
||||||
@ -2300,25 +2445,52 @@ pub fn frame(self: *App, plt: Platform.Frame) !void {
|
|||||||
eternal_flame.eternal_flame_size = .large;
|
eternal_flame.eternal_flame_size = .large;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var particle_radius: f32 = 0;
|
||||||
|
var particle_count: f32 = 0;
|
||||||
|
var particle_vel: f32 = 0;
|
||||||
|
|
||||||
switch (eternal_flame.eternal_flame_size) {
|
switch (eternal_flame.eternal_flame_size) {
|
||||||
.small => {
|
.small => {
|
||||||
const flame_size = Vec2.init(16, 32).multiplyScalar(4);
|
const flame_size = Vec2.init(16, 32).multiplyScalar(4);
|
||||||
eternal_flame.animation = self.fire1_animation;
|
eternal_flame.animation = self.fire1_animation;
|
||||||
eternal_flame.animation_rect = .init(-flame_size.x/2, -flame_size.y, flame_size.x, flame_size.y);
|
eternal_flame.animation_rect = .init(-flame_size.x/2, -flame_size.y, flame_size.x, flame_size.y);
|
||||||
|
|
||||||
|
particle_radius = 40;
|
||||||
|
particle_count = 0.05;
|
||||||
|
particle_vel = 70;
|
||||||
},
|
},
|
||||||
.medium => {
|
.medium => {
|
||||||
const flame_size = Vec2.init(32, 48).multiplyScalar(4);
|
const flame_size = Vec2.init(32, 48).multiplyScalar(4);
|
||||||
eternal_flame.animation = self.fire2_animation;
|
eternal_flame.animation = self.fire2_animation;
|
||||||
eternal_flame.animation_rect = .init(-flame_size.x/2, -flame_size.y, flame_size.x, flame_size.y);
|
eternal_flame.animation_rect = .init(-flame_size.x/2, -flame_size.y, flame_size.x, flame_size.y);
|
||||||
|
|
||||||
|
particle_radius = 60;
|
||||||
|
particle_count = 0.1;
|
||||||
|
particle_vel = 100;
|
||||||
},
|
},
|
||||||
.large => {
|
.large => {
|
||||||
const flame_size = Vec2.init(32, 48).multiplyScalar(4);
|
const flame_size = Vec2.init(32, 48).multiplyScalar(4);
|
||||||
eternal_flame.animation = self.fire5_animation;
|
eternal_flame.animation = self.fire5_animation;
|
||||||
eternal_flame.animation_rect = .init(-flame_size.x/2, -flame_size.y, flame_size.x, flame_size.y);
|
eternal_flame.animation_rect = .init(-flame_size.x/2, -flame_size.y, flame_size.x, flame_size.y);
|
||||||
|
|
||||||
|
particle_radius = 80;
|
||||||
|
particle_count = 0.3;
|
||||||
|
particle_vel = 120;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.drawEntityAnimation(eternal_flame, plt.dt);
|
self.drawEntityAnimation(eternal_flame, plt.dt);
|
||||||
|
self.spawnParticles(plt, .{
|
||||||
|
.pos = eternal_flame.pos.sub(.init(0, 70)),
|
||||||
|
.size = .init(4, 12),
|
||||||
|
.radius = .init(0, particle_radius),
|
||||||
|
.count = .init(0, particle_count),
|
||||||
|
.duration_s = .init(0.5, 3),
|
||||||
|
.color_variance = 0.15,
|
||||||
|
.color = .rgb(200, 40, 40),
|
||||||
|
.vel_y = .init(-1, -particle_vel),
|
||||||
|
.angular_vel = .init(-4, 4)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -41,3 +41,12 @@ pub fn rgb_hex(text: []const u8) ?Color {
|
|||||||
const b = std.fmt.parseInt(u8, text[5..7], 16) catch return null;
|
const b = std.fmt.parseInt(u8, text[5..7], 16) catch return null;
|
||||||
return rgb(r, g, b);
|
return rgb(r, g, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn lerp(self: Color, other: Color, t: f32) Color {
|
||||||
|
return Color{
|
||||||
|
.r = std.math.lerp(self.r, other.r, t),
|
||||||
|
.g = std.math.lerp(self.g, other.g, t),
|
||||||
|
.b = std.math.lerp(self.b, other.b, t),
|
||||||
|
.a = std.math.lerp(self.a, other.a, t),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@ -39,7 +39,7 @@ const State = struct {
|
|||||||
nearest_sampler: sg.Sampler,
|
nearest_sampler: sg.Sampler,
|
||||||
default_sampler: Sampler,
|
default_sampler: Sampler,
|
||||||
|
|
||||||
quads_buffer: [2048]VertexQuad,
|
quads_buffer: [8192]VertexQuad,
|
||||||
quads: std.ArrayList(VertexQuad),
|
quads: std.ArrayList(VertexQuad),
|
||||||
|
|
||||||
clear_color: Color,
|
clear_color: Color,
|
||||||
@ -530,9 +530,13 @@ pub fn endFrame() void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn drawRectangle(pos: Vec2, size: Vec2, color: Color) void {
|
pub fn drawRectangle(pos: Vec2, size: Vec2, color: Color) void {
|
||||||
|
drawQuad(Quad.initRect(pos, size), color);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn drawQuad(quad: Quad, color: Color) void {
|
||||||
const self = &g_state;
|
const self = &g_state;
|
||||||
|
|
||||||
drawSprite(self.default_sprite, pos, size, color);
|
drawSpriteQuad(self.default_sprite, quad, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn drawRectangleOutline(pos: Vec2, size: Vec2, color: Color, width: f32, alignment: f32) void {
|
pub fn drawRectangleOutline(pos: Vec2, size: Vec2, color: Color, width: f32, alignment: f32) void {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user