commit 248f0a5e5bd5d84234854f7aff8183236b37d90f
parent 263def7734ce19f24df9469a82c7362a6c394ef3
Author: brookjeynes <me@brookjeynes.dev>
Date: Thu, 30 Jul 2026 15:03:37 +1000
refactor: game state
Signed-off-by: brookjeynes <me@brookjeynes.dev>
Diffstat:
4 files changed, 173 insertions(+), 116 deletions(-)
diff --git a/src/bullet.zig b/src/bullet.zig
@@ -13,7 +13,7 @@ pub fn Pool(comptime upper: usize) type {
return struct {
const Self = @This();
- bullets: [upper]Bullet = undefined,
+ bullets: [upper]Bullet,
count: usize = 0,
fire_timer: Timer,
diff --git a/src/enemy.zig b/src/enemy.zig
@@ -14,11 +14,7 @@ size: raylib.Vector2 = .{ .x = 40, .y = 40 },
direction: raylib.Vector2 = .{},
position: raylib.Vector2 = .{},
-bullet_pool: bullet.Pool(max_spawned_bullets) = .{
- .fire_timer = .{
- .duration = 2.0,
- },
-},
+bullet_pool: bullet.Pool(max_spawned_bullets),
pub fn handleMovement(self: *Enemy, target: raylib.Vector2) void {
const delta: f32 = raylib.GetFrameTime();
diff --git a/src/main.zig b/src/main.zig
@@ -1,5 +1,3 @@
-const std = @import("std");
-
const raylib = @import("raylib");
const raymath = @import("raymath");
@@ -8,50 +6,101 @@ const Enemy = @import("./enemy.zig");
const Player = @import("./player.zig");
const Timer = @import("./timer.zig");
-const Screen = enum(u8) {
+const max_enemies: u8 = 255;
+
+const Entity = union(enum) {
+ enemy: Enemy,
+};
+
+const Screen = enum(u3) {
title,
gameplay,
upgrade,
};
-pub fn main() !void {
- const screen_width: f16 = 800.0;
- const screen_height: f16 = 1000.0;
- const fps: u8 = 60;
+const GameState = struct {
+ screen: struct {
+ width: u16 = 800,
+ height: u16 = 1000,
+ current: Screen = .title,
+ title: [*]const u8 = "1.44mb game jam",
+ },
+ fps: u8 = 60,
+ time: f32 = 0,
+
+ enemies: [max_enemies]Entity,
+ enemy_count: u8 = 0,
+
+ pub fn spawn_enemy(self: *GameState, enemy: Entity) void {
+ if (self.enemy_count == self.enemies.len) return;
+
+ self.enemies[self.enemy_count] = enemy;
+ self.enemy_count += 1;
+ }
+
+ pub fn despawn_enemy(self: *GameState, enemy_idx: u8) void {
+ for (enemy_idx..self.enemy_count) |i| {
+ self.enemies[i] = self.enemies[i + 1];
+ }
+
+ if (self.enemy_count > 0) self.enemy_count -= 1;
+ }
+
+ pub fn reset(self: *GameState) void {
+ self.screen.current = .gameplay;
+ self.time = 0;
+ self.enemy_count = 0;
+ }
+
+ pub fn difficulty(self: GameState) f32 {
+ return @min(5.0, 1.0 + self.time * 0.02);
+ }
+};
- var current_screen: Screen = .title;
+pub fn main() !void {
+ var game_state: GameState = .{
+ .screen = .{},
+ .enemies = undefined,
+ };
- raylib.InitWindow(screen_width, screen_height, "1.44mb game jam");
+ raylib.InitWindow(
+ game_state.screen.width,
+ game_state.screen.height,
+ game_state.screen.title,
+ );
defer raylib.CloseWindow();
- raylib.SetTargetFPS(fps);
+ raylib.SetTargetFPS(game_state.fps);
var player: Player = .{
+ .bullet_pool = .{
+ .bullets = undefined,
+ .fire_timer = .{
+ .duration = Player.bullet_spawn_cooldown,
+ },
+ },
.position = .{
- .x = screen_width / 2,
- .y = screen_height / 2,
+ .x = game_state.screen.width / 2,
+ .y = game_state.screen.height / 2,
},
};
var enemy_spawn_time: Timer = .{ .duration = 0.7 };
- var enemies: [255]Enemy = undefined;
- var enemy_count: u8 = 0;
- var game_time: f32 = 0;
-
- const button_size: raylib.Vector2 = .{ .x = 100, .y = 70 };
- const button_position: raylib.Vector2 = .{
- .x = (screen_width / 2) - (button_size.x / 2),
- .y = (screen_height / 2) - (button_size.y / 2),
+
+ const start_button_size: raylib.Vector2 = .{ .x = 100, .y = 70 };
+ const start_button_position: raylib.Vector2 = .{
+ .x = (game_state.screen.width / 2) - (start_button_size.x / 2),
+ .y = (game_state.screen.height / 2) - (start_button_size.y / 2),
};
- const button_bounds: raylib.Rectangle = .{
- .x = button_position.x,
- .y = button_position.y,
- .width = button_size.x,
- .height = button_size.y,
+ const start_button_bounds: raylib.Rectangle = .{
+ .x = start_button_position.x,
+ .y = start_button_position.y,
+ .width = start_button_size.x,
+ .height = start_button_size.y,
};
while (!raylib.WindowShouldClose()) {
- switch (current_screen) {
+ switch (game_state.screen.current) {
.title => {
const mouse_point = raylib.GetMousePosition();
@@ -61,23 +110,21 @@ pub fn main() !void {
raylib.ClearBackground(raylib.RAYWHITE);
raylib.DrawRectangleV(
- button_position,
- button_size,
+ start_button_position,
+ start_button_size,
raylib.BLACK,
);
raylib.DrawText(
"start",
- @intFromFloat(button_position.x + (button_size.x / 2)),
- @intFromFloat(button_position.y + (button_size.y / 2)),
+ @intFromFloat(start_button_position.x + (start_button_size.x / 2)),
+ @intFromFloat(start_button_position.y + (start_button_size.y / 2)),
18,
raylib.WHITE,
);
- if (raylib.CheckCollisionPointRec(mouse_point, button_bounds)) {
+ if (raylib.CheckCollisionPointRec(mouse_point, start_button_bounds)) {
if (raylib.IsMouseButtonReleased(raylib.MOUSE_BUTTON_LEFT)) {
- current_screen = .gameplay;
- game_time = 0;
- enemy_count = 0;
+ game_state.reset();
enemy_spawn_time = .{ .duration = 0.7 };
player.health = Player.max_health;
}
@@ -86,29 +133,31 @@ pub fn main() !void {
.upgrade => {},
.gameplay => {
const delta = raylib.GetFrameTime();
+ const difficulty = game_state.difficulty();
- game_time += delta;
+ game_state.time += delta;
- const difficulty: f32 = @min(5.0, 1.0 + game_time * 0.02);
enemy_spawn_time.duration = @max(0.15, 0.7 / difficulty);
player.tick();
player.handleMovement();
- for (0..enemy_count) |i| {
- enemies[i].handleMovement(player.position);
+ for (0..game_state.enemy_count) |i| {
+ const entity = &game_state.enemies[i];
+ switch (entity.*) {
+ .enemy => |*e| e.handleMovement(player.position),
+ }
}
- if (enemy_spawn_time.tick(delta) and enemy_count < enemies.len) {
- enemies[enemy_count] = spawnEnemy(
+ if (enemy_spawn_time.tick(delta)) {
+ game_state.spawn_enemy(.{ .enemy = spawnEnemy(
.{
- .x = screen_width,
- .y = screen_height,
+ .x = game_state.screen.width,
+ .y = game_state.screen.height,
},
player,
difficulty,
- );
- enemy_count += 1;
+ ) });
}
raylib.BeginDrawing();
@@ -118,77 +167,85 @@ pub fn main() !void {
player.render();
- var enemy_idx: usize = 0;
- while (enemy_idx < enemy_count) {
- const enemy = &enemies[enemy_idx];
-
- for (0..enemy.bullet_pool.count) |bullet_idx| {
- const bullet = enemy.bullet_pool.bullets[bullet_idx];
-
- const is_collision = raylib.CheckCollisionCircleRec(
- .{
- .x = bullet.position.x,
- .y = bullet.position.y,
- },
- Bullet.radius,
- .{
- .x = player.position.x,
- .y = player.position.y,
- .width = player.size.x,
- .height = player.size.y,
- },
- );
-
- if (is_collision) {
- enemy.bullet_pool.despawn(bullet_idx);
- player.health -= Enemy.bullet_damage;
-
- if (player.health < 0) {
- current_screen = .title;
+ var enemy_idx: u8 = 0;
+ while (enemy_idx < game_state.enemy_count) {
+ const entity = &game_state.enemies[enemy_idx];
+
+ switch (entity.*) {
+ .enemy => |*enemy| {
+ for (0..enemy.bullet_pool.count) |bullet_idx| {
+ const bullet = enemy.bullet_pool.bullets[bullet_idx];
+
+ const is_collision = raylib.CheckCollisionCircleRec(
+ .{
+ .x = bullet.position.x,
+ .y = bullet.position.y,
+ },
+ Bullet.radius,
+ .{
+ .x = player.position.x,
+ .y = player.position.y,
+ .width = player.size.x,
+ .height = player.size.y,
+ },
+ );
+
+ if (is_collision) {
+ enemy.bullet_pool.despawn(bullet_idx);
+ player.health -= Enemy.bullet_damage;
+
+ if (player.health < 0) {
+ game_state.screen.current = .title;
+ }
+ }
}
- }
- }
- for (0..player.bullet_pool.count) |bullet_idx| {
- const bullet = player.bullet_pool.bullets[bullet_idx];
-
- const is_collision = raylib.CheckCollisionCircleRec(
- .{
- .x = bullet.position.x,
- .y = bullet.position.y,
- },
- Bullet.radius,
- .{
- .x = enemy.position.x,
- .y = enemy.position.y,
- .width = enemy.size.x,
- .height = enemy.size.y,
- },
- );
-
- if (is_collision) {
- player.bullet_pool.despawn(bullet_idx);
-
- for (enemy_idx..enemy_count) |i| {
- enemies[i] = enemies[i + 1];
- }
- if (enemy_count != 0) {
- enemy_count -= 1;
+ for (0..player.bullet_pool.count) |bullet_idx| {
+ const bullet = player.bullet_pool.bullets[bullet_idx];
+
+ const is_collision = raylib.CheckCollisionCircleRec(
+ .{
+ .x = bullet.position.x,
+ .y = bullet.position.y,
+ },
+ Bullet.radius,
+ .{
+ .x = enemy.position.x,
+ .y = enemy.position.y,
+ .width = enemy.size.x,
+ .height = enemy.size.y,
+ },
+ );
+
+ if (is_collision) {
+ player.bullet_pool.despawn(bullet_idx);
+
+ game_state.despawn_enemy(enemy_idx);
+ continue;
+ }
}
- continue;
- }
+ },
}
enemy_idx += 1;
}
- for (0..enemy_count) |idx| {
- enemies[idx].render();
+ for (0..game_state.enemy_count) |idx| {
+ const entity = &game_state.enemies[idx];
+ switch (entity.*) {
+ .enemy => |*e| e.render(),
+ }
}
const health_percent: f16 = @max(0, @min(1, player.health / Player.max_health));
const damage_percent = 1 - health_percent;
- raylib.DrawRectangle(0, 0, screen_width, screen_height, raylib.ColorAlpha(raylib.RED, damage_percent));
+ raylib.DrawRectangle(
+ 0,
+ 0,
+ game_state.screen.width,
+ game_state.screen.height,
+ raylib.ColorAlpha(raylib.RED, damage_percent),
+ );
},
}
}
@@ -199,7 +256,15 @@ fn spawnEnemy(screen: raylib.Vector2, player: Player, difficulty: f32) Enemy {
const player_cx = player.position.x + player.size.x / 2;
const player_cy = player.position.y + player.size.y / 2;
- var enemy = Enemy{ .position = .{ .x = 0, .y = 0 } };
+ var enemy = Enemy{
+ .bullet_pool = .{
+ .bullets = undefined,
+ .fire_timer = .{
+ .duration = 2.0,
+ },
+ },
+ .position = .{ .x = 0, .y = 0 },
+ };
while (true) {
const rnd_x = @as(f32, @floatFromInt(raylib.GetRandomValue(0, 100)));
const rnd_y = @as(f32, @floatFromInt(raylib.GetRandomValue(0, 100)));
diff --git a/src/player.zig b/src/player.zig
@@ -9,7 +9,7 @@ pub const Player = @This();
const max_spawned_bullets = 2000;
const bullet_speed: f32 = 300.0;
-const bullet_spawn_cooldown: f32 = 0.4;
+pub const bullet_spawn_cooldown: f32 = 0.4;
const acceleration: f32 = 10000.0;
const friction: f32 = 10000.0;
const max_delta: f32 = 1.0 / 30.0;
@@ -25,11 +25,7 @@ health_regen_timer: Timer = .{
.duration = 5.0,
},
-bullet_pool: bullet.Pool(max_spawned_bullets) = .{
- .fire_timer = .{
- .duration = bullet_spawn_cooldown,
- },
-},
+bullet_pool: bullet.Pool(max_spawned_bullets),
pub fn render(self: *Player) void {
raylib.DrawRectangleV(