diff --git a/src/Build_raylib.zig b/src/Build_raylib.zig index 92df317..5fccf34 100644 --- a/src/Build_raylib.zig +++ b/src/Build_raylib.zig @@ -260,6 +260,8 @@ pub fn linkWithEmscripten( "-sMODULARIZE", "-sEXPORT_NAME=emscriptenModuleFactory", "-sEXPORT_ES6", + "-sINITIAL_MEMORY=4mb", + "-sSTACK_SIZE=1mb", }); return emcc_command; } diff --git a/src/Models/Explosion.zig b/src/Models/Explosion.zig new file mode 100644 index 0000000..d9ceb73 --- /dev/null +++ b/src/Models/Explosion.zig @@ -0,0 +1,75 @@ +const std = @import("std"); +const raylib = @import("raylib"); +const Shared = @import("../Shared.zig").Shared; +const Color = @import("../Colors.zig").Color; +const Particle = @import("Particle.zig").Particle; + +pub const Explosion = struct { + active: bool, + position: raylib.Vector2, + lifeSpawn: f32, + particle: [PARTICLE_COUNT]Particle, + blastRadius: f32, + + const ANIMATION_SPEED_MOD = 15; + const PARTICLE_COUNT = 50; + + pub fn init(position: raylib.Vector2, color: Color, blastRadius: f32) Explosion { + var particles: [PARTICLE_COUNT]Particle = undefined; + for (0..PARTICLE_COUNT) |i| { + particles[i] = Particle.init(position, GetRandomColor(color), Shared.Random.Get().float(f32) * 4); + } + + return Explosion{ + .active = true, + .position = position, + .lifeSpawn = 0, + .particle = particles, + .blastRadius = blastRadius, + }; + } + + pub inline fn GetRandomColor(color: Color) raylib.Color { + switch (Shared.Random.Get().intRangeAtMost(u8, 0, 2)) { + 0 => { + return color.Base; + }, + 1 => { + return color.Light; + }, + else => { + return color.Dark; + }, + } + } + + pub inline fn Update(self: *@This(), screenSize: raylib.Vector2) void { + if (self.active) { + self.lifeSpawn += raylib.getFrameTime() * ANIMATION_SPEED_MOD; + + var nonActiveCount: u32 = 0; + for (0..PARTICLE_COUNT) |i| { + var particle = self.particle[i]; + particle.Update(screenSize, self.blastRadius * 5); + if (self.lifeSpawn < 5) { + if (!particle.active) { + particle.Reset(self.position); + } + } else { + if (!particle.active) nonActiveCount += 1; + } + + self.particle[i] = particle; + } + self.active = nonActiveCount < PARTICLE_COUNT; + } + } + + pub inline fn Draw(self: @This()) void { + if (self.active) { + for (0..PARTICLE_COUNT) |i| { + self.particle[i].Draw(); + } + } + } +}; diff --git a/src/Models/Meteor.zig b/src/Models/Meteor.zig index 324fde9..ef32abf 100644 --- a/src/Models/Meteor.zig +++ b/src/Models/Meteor.zig @@ -5,6 +5,7 @@ const Shared = @import("../Shared.zig").Shared; const Player = @import("./Player.zig").Player; const Shoot = @import("./Shoot.zig").Shoot; const Alien = @import("./Alien.zig").Alien; +const Explosion = @import("./Explosion.zig").Explosion; pub const SpriteFrames = 5; const MeteorSprite1 = Shared.Sprite.init(SpriteFrames, .Meteor1); @@ -27,6 +28,7 @@ pub const Meteor = struct { color: raylib.Color, frame: f32, meteorSprite: Shared.Sprite, + explosion: Explosion, const ANIMATION_SPEED_MOD = 15; pub const METEORS_SPEED = 3; @@ -65,6 +67,7 @@ pub const Meteor = struct { .color = Shared.Color.White, .frame = 0, .meteorSprite = GetSprite(), + .explosion = Explosion{ .active = false, .particle = undefined, .lifeSpawn = 0, .position = undefined, .blastRadius = radius }, }; return meteor; @@ -166,6 +169,10 @@ pub const Meteor = struct { } pub inline fn Update(self: *@This(), player: Player, comptime shoots: []Shoot, comptime aliens: []Alien, comptime alien_shoots: []Shoot, screenSize: raylib.Vector2, shipHeight: f32, base_size: f32) MeteorStatus { + if (self.explosion.active) { + self.explosion.Update(screenSize); + } + // If Active if (self.active) { // Reset Frame @@ -184,6 +191,7 @@ pub const Meteor = struct { // Phase 2, check per pixel collision with player if (PerPixelCollisionDetection(self.*, player, shipHeight, base_size)) { self.active = false; + self.explosion = Explosion.init(self.position, Shared.Color.Green, self.radius); Shared.Sound.Play(.Explosion); return MeteorStatus{ .collide = true }; @@ -253,6 +261,7 @@ pub const Meteor = struct { shoots[i].active = false; shoots[i].lifeSpawn = 0; self.active = false; + self.explosion = Explosion.init(self.position, Shared.Color.Green, self.radius); return MeteorStatus{ .shot = shoots[i] }; } @@ -269,6 +278,7 @@ pub const Meteor = struct { alien_shoots[i].active = false; alien_shoots[i].lifeSpawn = 0; self.active = false; + self.explosion = Explosion.init(self.position, Shared.Color.Green, self.radius); return MeteorStatus{ .shot = alien_shoots[i] }; } @@ -281,6 +291,10 @@ pub const Meteor = struct { } self.frame = SpriteFrames - 1; + if (self.explosion.active) { + return MeteorStatus{ .animating = true }; + } + return MeteorStatus{ .default = true }; } @@ -388,6 +402,10 @@ pub const Meteor = struct { const activeRadiusY = 375; pub inline fn Draw(self: @This(), shipPosition: raylib.Vector2) void { + if (self.explosion.active) { + self.explosion.Draw(); + } + if (self.position.x == inactivitePoint and self.position.y == inactivitePoint) return; if (self.frame == SpriteFrames - 1) return; diff --git a/src/Models/Particle.zig b/src/Models/Particle.zig new file mode 100644 index 0000000..a7a81dd --- /dev/null +++ b/src/Models/Particle.zig @@ -0,0 +1,92 @@ +const std = @import("std"); +const raylib = @import("raylib"); +const Shared = @import("../Shared.zig").Shared; + +pub const Particle = struct { + position: raylib.Vector2, + speed: raylib.Vector2, + radius: f32, + lifeSpawn: f32, + active: bool, + color: raylib.Color, + + const ANIMATION_SPEED_MOD = 100; + + const PARTICLE_MAX_SPEED: f32 = 100; + const PARTICLE_MIN_SPEED: f32 = 20; + + const LifeSpanLength = 50; + + pub inline fn init(position: raylib.Vector2, color: raylib.Color, radius: f32) Particle { + const rotation = Shared.Random.Get().float(f32) * 360; + return Particle{ + .position = position, + .speed = raylib.Vector2.init( + @cos((std.math.degreesToRadians(f32, rotation) * (PARTICLE_MAX_SPEED - PARTICLE_MIN_SPEED)) + PARTICLE_MIN_SPEED), + @sin((std.math.degreesToRadians(f32, rotation) * (PARTICLE_MAX_SPEED - PARTICLE_MIN_SPEED)) + PARTICLE_MIN_SPEED), + ), + .radius = radius, + .active = true, + .lifeSpawn = Shared.Random.Get().float(f32) * LifeSpanLength, + .color = color.alpha((Shared.Random.Get().float(f32) * 0.2) + 0.55), + }; + } + + pub inline fn Reset(self: *@This(), position: raylib.Vector2) void { + self.position = position; + const rotation = Shared.Random.Get().float(f32) * 360; + self.speed = raylib.Vector2.init( + @cos((std.math.degreesToRadians(f32, rotation) * (PARTICLE_MAX_SPEED - PARTICLE_MIN_SPEED)) + PARTICLE_MIN_SPEED), + @sin((std.math.degreesToRadians(f32, rotation) * (PARTICLE_MAX_SPEED - PARTICLE_MIN_SPEED)) + PARTICLE_MIN_SPEED), + ); + self.active = true; + self.lifeSpawn = Shared.Random.Get().float(f32) * (LifeSpanLength / 2); + } + + pub inline fn Update(self: *@This(), screenSize: raylib.Vector2, endLifeSpan: f32) void { + if (self.active) { + self.lifeSpawn += raylib.getFrameTime() * ANIMATION_SPEED_MOD; + + // Movement + self.position.x += self.speed.x; + self.position.y -= self.speed.y; + + // Collision logic: particle vs walls + if (self.position.x > screenSize.x + self.radius) { + self.active = false; + self.lifeSpawn = 0; + } else if (self.position.x < 0 - self.radius) { + self.active = false; + self.lifeSpawn = 0; + } + if (self.position.y > screenSize.y + self.radius) { + self.active = false; + self.lifeSpawn = 0; + } else if (self.position.y < 0 - self.radius) { + self.active = false; + self.lifeSpawn = 0; + } + + // Life of particle + if (self.lifeSpawn >= endLifeSpan) { + self.position.x = 0; + self.position.y = 0; + self.speed.x = 0; + self.speed.y = 0; + self.lifeSpawn = 0; + self.active = false; + } + } + } + + pub inline fn Draw(self: @This()) void { + if (self.active) { + const color = if (self.lifeSpawn > 15) self.color else self.color.alpha((30 - self.lifeSpawn) / 30); + raylib.drawCircleV( + self.position, + self.radius, + color, + ); + } + } +};