diff --git a/src/combat_screen.zig b/src/combat_screen.zig index c725907..e3abdd9 100644 --- a/src/combat_screen.zig +++ b/src/combat_screen.zig @@ -76,6 +76,13 @@ const Laser = struct { duration: Nanoseconds }; +const Bomb = struct { + kinetic: Kinetic, + size: f32, + explode_at: Nanoseconds, + explosion_radius: f32 +}; + const Enemy = struct { kinetic: Kinetic, speed: f32, @@ -126,6 +133,7 @@ enemies: std.ArrayList(Enemy) = .empty, player: Player = .{}, bullets: std.ArrayList(Bullet) = .empty, lasers: std.ArrayList(Laser) = .empty, +bombs: std.ArrayList(Bomb) = .empty, pickups: std.ArrayList(Pickup) = .empty, next_pickup_spawn_at: Nanoseconds, @@ -164,6 +172,7 @@ pub fn deinit(self: *CombatScreen) void { self.pickups.deinit(self.gpa); self.spawned_waves.deinit(self.gpa); self.lasers.deinit(self.gpa); + self.bombs.deinit(self.gpa); } pub fn spawnEnemy(self: *CombatScreen) !void { @@ -360,6 +369,15 @@ pub fn tick(self: *CombatScreen, state: *State, frame: *Engine.Frame) !TickResul }); }, .bomb => { + try self.bombs.append(self.gpa, .{ + .kinetic = .{ + .pos = self.player.kinetic.pos, + .vel = bullet_dir.multiplyScalar(800) + }, + .size = 10, + .explode_at = frame.time_ns + std.time.ns_per_s, + .explosion_radius = 50 + }); }, .laser => { try self.lasers.append(self.gpa, .{ @@ -400,27 +418,79 @@ pub fn tick(self: *CombatScreen, state: *State, frame: *Engine.Frame) !TickResul }); } - for (self.lasers.items) |*laser| { - const laser_length = 1000; - const laser_line = Line{ - .p0 = laser.origin, - .p1 = laser.origin.add(laser.dir.multiplyScalar(laser_length)) - }; - const laser_quad = laser_line.getQuad(laser.size); + { + var index: usize = 0; + while (index < self.lasers.items.len) { + var destroy = false; + var laser = &self.lasers.items[index]; - for (self.enemies.items) |*enemy| { - const enemy_rect = getCenteredRect(enemy.kinetic.pos, enemy.size); - if (laser_quad.isRectOverlap(enemy_rect)) { - enemy.dead = true; + const laser_length = 1000; + const laser_line = Line{ + .p0 = laser.origin, + .p1 = laser.origin.add(laser.dir.multiplyScalar(laser_length)) + }; + const laser_quad = laser_line.getQuad(laser.size); + + for (self.enemies.items) |*enemy| { + const enemy_rect = getCenteredRect(enemy.kinetic.pos, enemy.size); + if (laser_quad.isRectOverlap(enemy_rect)) { + enemy.dead = true; + } + } + + if (frame.time_ns > (laser.created_at + laser.duration)) { + destroy = true; + } + + frame.drawLine( + laser_line.p0, + laser_line.p1, + rgb(200, 20, 255), + laser.size + ); + + if (destroy) { + _ = self.lasers.swapRemove(index); + } else { + index += 1; } } + } - frame.drawLine( - laser_line.p0, - laser_line.p1, - rgb(200, 20, 255), - laser.size - ); + { + var index: usize = 0; + while (index < self.bombs.items.len) { + var destroy = false; + var bomb = &self.bombs.items[index]; + bomb.kinetic.update(dt, .{ + .friction = 0.998 + }); + + if (frame.time_ns >= bomb.explode_at) { + destroy = true; + + for (self.enemies.items) |*enemy| { + const enemy_rect = getCenteredRect(enemy.kinetic.pos, enemy.size); + if (enemy_rect.checkCircleOverlap(bomb.kinetic.pos, bomb.explosion_radius)) { + enemy.dead = true; + frame.drawCircle(bomb.kinetic.pos, bomb.explosion_radius, rgb(200, 20, 20)); + } + } + } + frame.drawCircle(bomb.kinetic.pos, bomb.explosion_radius, rgba(200, 20, 20, 0.2)); + + const bomb_rect = getCenteredRect(bomb.kinetic.pos, bomb.size); + frame.drawRectangle(.{ + .rect = bomb_rect, + .color = rgb(200, 20, 255) + }); + + if (destroy) { + _ = self.bombs.swapRemove(index); + } else { + index += 1; + } + } } for (self.enemies.items) |*enemy| { diff --git a/src/engine/frame.zig b/src/engine/frame.zig index 42b057d..da184d9 100644 --- a/src/engine/frame.zig +++ b/src/engine/frame.zig @@ -230,6 +230,16 @@ pub fn popScissor(self: *Frame) void { }); } +pub fn drawCircle(self: *Frame, center: Vec2, radius: f32, color: Vec4) void { + self.pushGraphicsCommand(.{ + .draw_circle = .{ + .center = center, + .radius = radius, + .color = color, + } + }); +} + pub fn drawRectangle(self: *Frame, opts: GraphicsCommand.DrawRectangle) void { self.pushGraphicsCommand(.{ .draw_rectangle = opts }); } diff --git a/src/engine/graphics.zig b/src/engine/graphics.zig index 05adc99..7b20316 100644 --- a/src/engine/graphics.zig +++ b/src/engine/graphics.zig @@ -48,6 +48,12 @@ pub const Command = union(enum) { origin: Vec2 = .init(0, 0), }; + pub const DrawCircle = struct { + center: Vec2, + radius: f32, + color: Vec4, + }; + set_scissor: Rect, draw_rectangle: DrawRectangle, draw_line: struct { pos1: Vec2, pos2: Vec2, color: Vec4, width: f32 }, @@ -60,6 +66,7 @@ pub const Command = union(enum) { }, push_transformation: struct { translation: Vec2, scale: Vec2 }, pop_transformation: void, + draw_circle: DrawCircle }; const Texture = struct { @@ -206,6 +213,25 @@ pub fn drawCommand(command: Command) void { font_context.setColor(color); font_context.drawText(opts.pos.x * font_resolution_scale.x, opts.pos.y * font_resolution_scale.y, opts.text); }, + .draw_circle => |opts| { + sgl.beginTriangleStrip(); + defer sgl.end(); + + const angle_step: f32 = std.math.pi * 2.0 / 32.0; + + sgl.c4f(opts.color.x, opts.color.y, opts.color.z, opts.color.w); + + sgl.v2f(opts.center.x, opts.center.y); + var angle: f32 = 0; + while (angle < std.math.pi * 2) { + const point_on_circle = Vec2.initAngle(angle).multiplyScalar(opts.radius).add(opts.center); + sgl.v2f(point_on_circle.x, point_on_circle.y); + sgl.v2f(opts.center.x, opts.center.y); + + angle += angle_step; + } + sgl.v2f(opts.center.x + opts.radius, opts.center.y); + } } } diff --git a/src/engine/math.zig b/src/engine/math.zig index 9ac8636..885907c 100644 --- a/src/engine/math.zig +++ b/src/engine/math.zig @@ -472,6 +472,43 @@ pub const Rect = struct { return left_overlap or right_overlap or top_overlap or bottom_overlap; } + pub fn checkCircleOverlap(self: Rect, circle_center: Vec2, circle_radius: f32) bool { + const cx = circle_center.x; + const cy = circle_center.y; + const rx = self.pos.x; + const ry = self.pos.y; + const rw = self.size.x; + const rh = self.size.y; + + // temporary variables to set edges for testing + var testX = cx; + var testY = cy; + + // which edge is closest? + if (cx < rx) { + testX = rx; // test left edge + } else if (cx > rx+rw) { + testX = rx+rw; // right edge + } + + if (cy < ry) { + testY = ry; // top edge + } else if (cy > ry+rh) { + testY = ry+rh; // bottom edge + } + + // get distance from closest edges + const distX = cx-testX; + const distY = cy-testY; + const distance = @sqrt( (distX*distX) + (distY*distY) ); + + // if the distance is less than the radius, collision! + if (distance <= circle_radius) { + return true; + } + return false; + } + pub fn hasOverlap(lhs: Rect, rhs: Rect) bool { return (lhs.left() < rhs.right() and lhs.right() > rhs.left()) and (lhs.top() < rhs.bottom() and lhs.bottom() > rhs.top());