diff --git a/src/engine/math.zig b/src/engine/math.zig index 7933229..412c75d 100644 --- a/src/engine/math.zig +++ b/src/engine/math.zig @@ -380,6 +380,10 @@ pub const Rect = struct { }; } + pub fn initCentered(x: f32, y: f32, width: f32, height: f32) Rect { + return init(x - width/2, y - height/2, width, height); + } + pub fn clip(self: Rect, other: Rect) Rect { const left_edge = @max(self.left(), other.left()); const right_edge = @min(self.right(), other.right()); @@ -437,7 +441,7 @@ pub const Rect = struct { return (lhs.left() < rhs.right() and lhs.right() > rhs.left()) and (lhs.top() < rhs.bottom() and lhs.bottom() > rhs.top()); } - + pub fn growX(self: Rect, amount: f32) Rect { return Rect{ .pos = .init(self.pos.x - amount, self.pos.y), @@ -453,7 +457,7 @@ pub const Rect = struct { } pub fn grow(self: Rect, amount: f32) Rect { - return self.growX(self.growY(amount), amount); + return self.growX(amount).growY(amount); } }; diff --git a/src/game.zig b/src/game.zig index 13c3779..069f355 100644 --- a/src/game.zig +++ b/src/game.zig @@ -55,7 +55,12 @@ const Kinetic = struct { const Player = struct { kinetic: Kinetic = .{}, - last_shot_at: ?Nanoseconds = null + money: u32 = 0, + last_shot_at: ?Nanoseconds = null, + + pub fn getRect(self: Player) Rect { + return getCenteredRect(self.kinetic.pos, 16); + } }; const Bullet = struct { @@ -88,10 +93,20 @@ const Wave = struct { }; }; +const Pickup = struct { + const Kind = enum { + money + }; + + pos: Vec2, + kind: Kind, +}; + +const pickup_spawn_duration_s = Range.init(1, 5); const wave_infos = [_]Wave.Info{ .{ .enemies = 10, - .duration_s = 1, + .duration_s = 10, .starts_at_s = 0 } }; @@ -105,6 +120,9 @@ player: Player = .{}, bullets: std.ArrayList(Bullet) = .empty, enemies: std.ArrayList(Enemy) = .empty, +pickups: std.ArrayList(Pickup) = .empty, +next_pickup_spawn_at: Nanoseconds, + wave_timer: Nanoseconds = 0, waves: std.ArrayList(Wave) = .empty, @@ -114,16 +132,22 @@ pub fn init(gpa: Allocator, seed: u64, assets: *Assets) !Game { var arena = std.heap.ArenaAllocator.init(gpa); errdefer arena.deinit(); + var rng = RNGState.init(seed); + + const next_pickup_spawn_at_s = pickup_spawn_duration_s.random(rng.random()); + const next_pickup_spawn_at: Nanoseconds = @intFromFloat(next_pickup_spawn_at_s * std.time.ns_per_s); + return Game{ .arena = arena, .gpa = gpa, .assets = assets, + .next_pickup_spawn_at = next_pickup_spawn_at, .player = .{ .kinetic = .{ .pos = .init(50, 50), } }, - .rng = RNGState.init(seed), + .rng = rng }; } @@ -132,6 +156,7 @@ pub fn deinit(self: *Game) void { self.bullets.deinit(self.gpa); self.enemies.deinit(self.gpa); self.waves.deinit(self.gpa); + self.pickups.deinit(self.gpa); self.spawned_waves.deinit(self.gpa); } @@ -184,6 +209,17 @@ fn spawnEnemy(self: *Game) !void { }); } +fn spawnPickup(self: *Game) !void { + const margin = 10; + const spawn_area = (Rect{ .pos = .zero, .size = world_size }).grow(-margin); + const pos = Vec2.initRandomRect(self.rng.random(), spawn_area); + + try self.pickups.append(self.gpa, Pickup{ + .pos = pos, + .kind = .money + }); +} + pub fn tick(self: *Game, frame: *Engine.Frame) !void { const dt = frame.deltaTime(); @@ -202,7 +238,7 @@ pub fn tick(self: *Game, frame: *Engine.Frame) !void { try self.spawned_waves.append(self.gpa, i); try self.waves.append(self.gpa, .{ .started_at = self.wave_timer, - .duration = wave_info.duration_s * std.time.ns_per_s, + .duration = @as(u64, wave_info.duration_s) * std.time.ns_per_s, .enemies_spawned = 0, .total_enemies = wave_info.enemies }); @@ -237,6 +273,13 @@ pub fn tick(self: *Game, frame: *Engine.Frame) !void { } } + if (self.wave_timer >= self.next_pickup_spawn_at) { + const next_pickup_spawn_at_s = pickup_spawn_duration_s.random(self.rng.random()); + const next_pickup_spawn_at: Nanoseconds = @intFromFloat(next_pickup_spawn_at_s * std.time.ns_per_s); + self.next_pickup_spawn_at = self.wave_timer + next_pickup_spawn_at; + try self.spawnPickup(); + } + frame.graphics.canvas_size = world_size; if (frame.isKeyPressed(.F3)) { @@ -306,7 +349,7 @@ pub fn tick(self: *Game, frame: *Engine.Frame) !void { } frame.drawRectangle(.{ - .rect = getCenteredRect(self.player.kinetic.pos, 16), + .rect = self.player.getRect(), .color = rgb(255, 255, 255) }); @@ -342,6 +385,32 @@ pub fn tick(self: *Game, frame: *Engine.Frame) !void { }); } + { + var index: usize = 0; + while (index < self.pickups.items.len) { + var destroy: bool = false; + const pickup = self.pickups.items[index]; + + const pickup_rect = Rect.initCentered(pickup.pos.x, pickup.pos.y, 10, 10); + + if (pickup_rect.hasOverlap(self.player.getRect())) { + self.player.money += 1; + destroy = true; + } + + frame.drawRectangle(.{ + .rect = pickup_rect, + .color = rgb(20, 20, 200) + }); + + if (destroy) { + _ = self.pickups.swapRemove(index); + } else { + index += 1; + } + } + } + { var i: usize = 0; while (i < self.bullets.items.len) { @@ -371,6 +440,8 @@ pub fn tick(self: *Game, frame: *Engine.Frame) !void { @divFloor(wave_timer_s, 60), @mod(wave_timer_s, 60) }); + + frame.drawTextFormat(.init(10, 30), text_opts, "{d}", .{ self.player.money }); } pub fn debug(self: *Game) !void { @@ -387,6 +458,9 @@ pub fn debug(self: *Game) !void { try self.spawnEnemy(); } + const time_left_til_pickup = self.next_pickup_spawn_at - self.wave_timer; + imgui.textFmt("Waves: {}\n", .{self.waves.items.len}); imgui.textFmt("Enemies: {}\n", .{self.enemies.items.len}); + imgui.textFmt("Time until next pickup: {d:.2}s\n", .{@as(f32, @floatFromInt(time_left_til_pickup)) / std.time.ns_per_s}); }