commit 5ec394929f5acb6a022d18a61d772b6f8723cb99
parent b99458bcc2e81750dc5242157ed953aeda2d8280
Author: brookjeynes <me@brookjeynes.dev>
Date: Fri, 31 Jul 2026 14:42:47 +1000
refactor: move things around
Signed-off-by: brookjeynes <me@brookjeynes.dev>
Diffstat:
7 files changed, 343 insertions(+), 267 deletions(-)
diff --git a/src/constants.zig b/src/constants.zig
@@ -0,0 +1,5 @@
+pub const max_enemies: u8 = 255;
+pub const screen_width: u16 = 800;
+pub const screen_height: u16 = 1000;
+pub const screen_title: [*]const u8 = "1.44mb game jam";
+pub const fps: u8 = 60;
diff --git a/src/enemy_types/grunt.zig b/src/enemy_types/grunt.zig
@@ -15,20 +15,22 @@ position: raylib.Vector2 = .{},
bullet_pool: bullet.Pool,
+pub fn tick(self: *Grunt) void {
+ if (self.bullet_pool.tick()) self.shoot();
+}
+
pub fn shoot(self: *Grunt) void {
- if (self.bullet_pool.tick()) {
- self.bullet_pool.spawn(
- .{
- .x = self.position.x + (size.x / 2),
- .y = self.position.y + (size.y / 2),
- },
- .{
- .x = self.direction.x * self.bullet_speed,
- .y = self.direction.y * self.bullet_speed,
- },
- raylib.RED,
- );
- }
+ self.bullet_pool.spawn(
+ .{
+ .x = self.position.x + (size.x / 2),
+ .y = self.position.y + (size.y / 2),
+ },
+ .{
+ .x = self.direction.x * self.bullet_speed,
+ .y = self.direction.y * self.bullet_speed,
+ },
+ raylib.RED,
+ );
}
pub fn handleMovement(self: *Grunt, target: raylib.Vector2) void {
diff --git a/src/entity.zig b/src/entity.zig
@@ -1,7 +1,26 @@
const Grunt = @import("./enemy_types/grunt.zig");
+const raylib = @import("raylib");
pub const EntityType = enum { grunt };
pub const Entity = union(EntityType) {
grunt: Grunt,
+
+ pub fn render(entity: *Entity) void {
+ switch (entity.*) {
+ .grunt => |*grunt| grunt.render(),
+ }
+ }
+
+ pub fn move(entity: *Entity, target_position: raylib.Vector2) void {
+ switch (entity.*) {
+ .grunt => |*grunt| grunt.handleMovement(target_position),
+ }
+ }
+
+ pub fn tick(entity: *Entity) void {
+ switch (entity.*) {
+ .grunt => |*grunt| grunt.tick(),
+ }
+ }
};
diff --git a/src/main.zig b/src/main.zig
@@ -3,308 +3,101 @@ const raymath = @import("raymath");
const Bullet = @import("./bullet.zig");
const Grunt = @import("./enemy_types/grunt.zig");
-const Entity = @import("./entity.zig").Entity;
-const EntityType = @import("./entity.zig").EntityType;
const Player = @import("./player.zig");
+const GameState = @import("./state.zig").GameState;
const Timer = @import("./timer.zig");
-
-const max_enemies: u8 = 255;
-
-const Screen = enum(u3) {
- title,
- gameplay,
- upgrade,
-};
-
-const GameState = struct {
- screen: struct {
- width: u16 = 800,
- height: u16 = 1000,
- current: Screen = .title,
- title: [*]const u8 = "1.44mb game jam",
+const Button = @import("./ui.zig").Button;
+const constants = @import("./constants.zig");
+
+const start_button = Button.new(
+ .{ .x = 100, .y = 70 },
+ .{
+ .x = (constants.screen_width / 2) - 50,
+ .y = (constants.screen_height / 2) - 35,
},
- 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);
- }
-};
+);
pub fn main() !void {
- var game_state: GameState = .{
- .screen = .{},
- .enemies = undefined,
- };
+ var game_state: GameState = .init();
raylib.InitWindow(
- game_state.screen.width,
- game_state.screen.height,
- game_state.screen.title,
+ constants.screen_width,
+ constants.screen_height,
+ constants.screen_title,
);
defer raylib.CloseWindow();
- raylib.SetTargetFPS(game_state.fps);
-
- var player: Player = .{
- .bullet_pool = .{
- .bullets = undefined,
- .fire_timer = .{
- .duration = Player.bullet_spawn_cooldown,
- },
- },
- .position = .{
- .x = game_state.screen.width / 2,
- .y = game_state.screen.height / 2,
- },
- };
-
- var enemy_spawn_time: Timer = .{ .duration = 0.7 };
-
- 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 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,
- };
-
+ raylib.SetTargetFPS(constants.fps);
while (!raylib.WindowShouldClose()) {
- switch (game_state.screen.current) {
+ switch (game_state.screen) {
.title => {
- const mouse_point = raylib.GetMousePosition();
-
raylib.BeginDrawing();
defer raylib.EndDrawing();
raylib.ClearBackground(raylib.RAYWHITE);
raylib.DrawRectangleV(
- start_button_position,
- start_button_size,
+ start_button.position,
+ start_button.size,
raylib.BLACK,
);
raylib.DrawText(
"start",
- @intFromFloat(start_button_position.x + (start_button_size.x / 2)),
- @intFromFloat(start_button_position.y + (start_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, start_button_bounds)) {
- if (raylib.IsMouseButtonReleased(raylib.MOUSE_BUTTON_LEFT)) {
- game_state.reset();
- enemy_spawn_time = .{ .duration = 0.7 };
- player.health = Player.max_health;
- }
+ if (start_button.clicked(raylib.GetMousePosition())) {
+ game_state.reset();
}
},
.upgrade => {},
.gameplay => {
- const delta = raylib.GetFrameTime();
- const difficulty = game_state.difficulty();
+ // -- tick
- game_state.time += delta;
-
- enemy_spawn_time.duration = @max(0.15, 0.7 / difficulty);
-
- player.tick();
- player.handleMovement();
-
- for (0..game_state.enemy_count) |i| {
- const entity = &game_state.enemies[i];
-
- switch (entity.*) {
- .grunt => |*e| {
- e.handleMovement(player.position);
- e.shoot();
- },
- }
- }
-
- if (enemy_spawn_time.tick(delta)) {
- game_state.spawn_enemy(spawnEnemy(
- .{
- .x = game_state.screen.width,
- .y = game_state.screen.height,
- },
- player,
- difficulty,
- .grunt,
- Grunt.size,
- ));
+ for (0..game_state.enemy_count) |idx| {
+ game_state.enemies[idx].move(game_state.player.position);
}
+ game_state.player.handleMovement();
+ game_state.tick();
+ // -- render
raylib.BeginDrawing();
defer raylib.EndDrawing();
raylib.ClearBackground(raylib.RAYWHITE);
- player.render();
-
- var enemy_idx: u8 = 0;
- while (enemy_idx < game_state.enemy_count) {
- const entity = &game_state.enemies[enemy_idx];
-
- switch (entity.*) {
- .grunt => |*grunt| {
- var enemy_bullet_idx = grunt.bullet_pool.count;
- while (enemy_bullet_idx > 0) {
- enemy_bullet_idx -= 1;
-
- const bullet = grunt.bullet_pool.bullets[enemy_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) {
- grunt.bullet_pool.despawn(enemy_bullet_idx);
- player.health -= Grunt.bullet_damage;
- if (player.health <= 0) {
- game_state.screen.current = .title;
- }
- }
- }
-
- var player_bullet_idx = player.bullet_pool.count;
- while (player_bullet_idx > 0) {
- player_bullet_idx -= 1;
-
- const bullet = player.bullet_pool.bullets[player_bullet_idx];
- const is_collision = raylib.CheckCollisionCircleRec(
- .{
- .x = bullet.position.x,
- .y = bullet.position.y,
- },
- Bullet.radius,
- .{
- .x = grunt.position.x,
- .y = grunt.position.y,
- .width = Grunt.size.x,
- .height = Grunt.size.y,
- },
- );
-
- if (is_collision) {
- player.bullet_pool.despawn(player_bullet_idx);
-
- game_state.despawn_enemy(enemy_idx);
- break;
- }
- }
- },
- }
-
- enemy_idx += 1;
- }
-
+ // Render entities
+ game_state.player.render();
for (0..game_state.enemy_count) |idx| {
- const entity = &game_state.enemies[idx];
- switch (entity.*) {
- .grunt => |*grunt| grunt.render(),
- }
+ game_state.enemies[idx].render();
}
- const health_percent: f32 = @max(0, @min(1, player.health / Player.max_health));
+ // Health overlay
+ const health_percent: f32 = @max(
+ 0,
+ @min(1, game_state.player.health / Player.max_health),
+ );
const damage_percent = 1 - health_percent;
raylib.DrawRectangle(
0,
0,
- game_state.screen.width,
- game_state.screen.height,
+ constants.screen_width,
+ constants.screen_height,
raylib.ColorAlpha(raylib.RED, damage_percent),
);
- raylib.DrawText(raylib.TextFormat("score: %0.0f", game_state.time), 10, 10, 18, raylib.BLACK);
+
+ // Score
+ raylib.DrawText(
+ raylib.TextFormat("score: %0.0f", game_state.time),
+ 10,
+ 10,
+ 18,
+ raylib.BLACK,
+ );
},
}
}
}
-
-fn spawnEnemy(
- screen: raylib.Vector2,
- player: Player,
- difficulty: f32,
- enemy_type: EntityType,
- enemy_size: raylib.Vector2,
-) Entity {
- const min_dist: f32 = 200.0;
- const player_cx = player.position.x + player.size.x / 2;
- const player_cy = player.position.y + player.size.y / 2;
-
- const spawn_position: raylib.Vector2 = lbl: {
- while (true) {
- const rnd_x = @as(f32, @floatFromInt(raylib.GetRandomValue(0, 100)));
- const rnd_y = @as(f32, @floatFromInt(raylib.GetRandomValue(0, 100)));
- const spawn_x = (rnd_x / 100.0) * (@as(f32, @floatCast(screen.x)) - enemy_size.x);
- const spawn_y = (rnd_y / 100.0) * (@as(f32, @floatCast(screen.y)) - enemy_size.y);
-
- const center_x = spawn_x + enemy_size.x / 2;
- const center_y = spawn_y + enemy_size.y / 2;
-
- const distance_between_player = raymath.Vector2Distance(
- .{ .x = center_x, .y = center_y },
- .{ .x = player_cx, .y = player_cy },
- );
-
- if (distance_between_player >= min_dist) {
- break :lbl .{ .x = spawn_x, .y = spawn_y };
- }
- }
- };
-
- return switch (enemy_type) {
- .grunt => lbl: {
- var grunt = Grunt{
- .bullet_pool = .{
- .bullets = undefined,
- .fire_timer = .{
- .duration = 2.0,
- },
- },
- .position = spawn_position,
- };
-
- grunt.movement_speed = @min(160.0, 40.0 * difficulty);
- grunt.bullet_speed = @min(300.0, 100.0 * difficulty);
- grunt.bullet_pool.fire_timer.duration = @max(0.3, 2.0 / difficulty);
-
- break :lbl .{ .grunt = grunt };
- },
- };
-}
diff --git a/src/player.zig b/src/player.zig
@@ -45,6 +45,10 @@ pub fn tick(self: *Player) void {
}
}
+pub fn handleDamage(self: *Player, damage: f32) void {
+ self.health -= damage;
+}
+
pub fn handleMovement(self: *Player) void {
const delta: f32 = @min(raylib.GetFrameTime(), max_delta);
diff --git a/src/state.zig b/src/state.zig
@@ -0,0 +1,225 @@
+const raylib = @import("raylib");
+const raymath = @import("raymath");
+
+const Bullet = @import("./bullet.zig");
+const constants = @import("./constants.zig");
+const Grunt = @import("./enemy_types/grunt.zig");
+const Entity = @import("./entity.zig").Entity;
+const EntityType = @import("./entity.zig").EntityType;
+const Player = @import("./player.zig");
+const Timer = @import("./timer.zig");
+
+pub const Screen = enum(u3) {
+ title,
+ gameplay,
+ upgrade,
+};
+
+pub const GameState = struct {
+ screen: Screen,
+ time: f32,
+
+ player: Player,
+
+ enemies: [constants.max_enemies]Entity,
+ enemy_count: u8,
+ enemy_spawn_time: Timer = .{ .duration = 0.7 },
+
+ pub fn init() GameState {
+ return .{
+ .screen = .title,
+ .time = 0,
+
+ .player = .{
+ .bullet_pool = .{
+ .bullets = undefined,
+ .fire_timer = .{
+ .duration = Player.bullet_spawn_cooldown,
+ },
+ },
+ .position = .{
+ .x = constants.screen_width / 2,
+ .y = constants.screen_height / 2,
+ },
+ },
+
+ .enemies = undefined,
+ .enemy_count = 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 = .gameplay;
+ self.time = 0;
+
+ self.enemy_count = 0;
+ self.enemy_spawn_time = .{ .duration = 0.7 };
+
+ self.player.health = Player.max_health;
+ self.player.position = .{
+ .x = constants.screen_width / 2,
+ .y = constants.screen_height / 2,
+ };
+ }
+
+ pub fn game_difficulty(self: GameState) f32 {
+ return @min(5.0, 1.0 + self.time * 0.02);
+ }
+
+ pub fn tick(self: *GameState) void {
+ const difficulty = self.game_difficulty();
+ const delta = raylib.GetFrameTime();
+ self.time += delta;
+
+ self.enemy_spawn_time.duration = @max(0.15, 0.7 / difficulty);
+
+ self.player.tick();
+ if (self.player.health <= 0) {
+ self.screen = .title;
+ }
+
+ for (0..self.enemy_count) |i| {
+ self.enemies[i].tick();
+ }
+
+ if (self.enemy_spawn_time.tick(delta)) {
+ const grunt = createEnemy(
+ .{ .x = constants.screen_width, .y = constants.screen_height },
+ self.player,
+ difficulty,
+ .grunt,
+ Grunt.size,
+ );
+
+ self.spawn_enemy(grunt);
+ }
+
+ var enemy_idx: u8 = 0;
+ while (enemy_idx < self.enemy_count) {
+ const entity = &self.enemies[enemy_idx];
+
+ switch (entity.*) {
+ .grunt => |*grunt| {
+ var enemy_bullet_idx = grunt.bullet_pool.count;
+ while (enemy_bullet_idx > 0) {
+ enemy_bullet_idx -= 1;
+
+ const bullet = grunt.bullet_pool.bullets[enemy_bullet_idx];
+ const is_collision = raylib.CheckCollisionCircleRec(
+ .{ .x = bullet.position.x, .y = bullet.position.y },
+ Bullet.radius,
+ .{
+ .x = self.player.position.x,
+ .y = self.player.position.y,
+ .width = self.player.size.x,
+ .height = self.player.size.y,
+ },
+ );
+
+ if (is_collision) {
+ grunt.bullet_pool.despawn(enemy_bullet_idx);
+ self.player.handleDamage(Grunt.bullet_damage);
+ }
+ }
+
+ var player_bullet_idx = self.player.bullet_pool.count;
+ while (player_bullet_idx > 0) {
+ player_bullet_idx -= 1;
+
+ const bullet = self.player.bullet_pool.bullets[player_bullet_idx];
+ const is_collision = raylib.CheckCollisionCircleRec(
+ .{
+ .x = bullet.position.x,
+ .y = bullet.position.y,
+ },
+ Bullet.radius,
+ .{
+ .x = grunt.position.x,
+ .y = grunt.position.y,
+ .width = Grunt.size.x,
+ .height = Grunt.size.y,
+ },
+ );
+
+ if (is_collision) {
+ self.player.bullet_pool.despawn(player_bullet_idx);
+
+ self.despawn_enemy(enemy_idx);
+ enemy_idx -= 1;
+ }
+ }
+ },
+ }
+
+ enemy_idx += 1;
+ }
+ }
+};
+
+fn createEnemy(
+ screen: raylib.Vector2,
+ player: Player,
+ difficulty: f32,
+ enemy_type: EntityType,
+ enemy_size: raylib.Vector2,
+) Entity {
+ const min_dist: f32 = 200.0;
+ const player_cx = player.position.x + player.size.x / 2;
+ const player_cy = player.position.y + player.size.y / 2;
+
+ const spawn_position: raylib.Vector2 = lbl: {
+ while (true) {
+ const rnd_x = @as(f32, @floatFromInt(raylib.GetRandomValue(0, 100)));
+ const rnd_y = @as(f32, @floatFromInt(raylib.GetRandomValue(0, 100)));
+ const spawn_x = (rnd_x / 100.0) * (@as(f32, @floatCast(screen.x)) - enemy_size.x);
+ const spawn_y = (rnd_y / 100.0) * (@as(f32, @floatCast(screen.y)) - enemy_size.y);
+
+ const center_x = spawn_x + enemy_size.x / 2;
+ const center_y = spawn_y + enemy_size.y / 2;
+
+ const distance_between_player = raymath.Vector2Distance(
+ .{ .x = center_x, .y = center_y },
+ .{ .x = player_cx, .y = player_cy },
+ );
+
+ if (distance_between_player >= min_dist) {
+ break :lbl .{ .x = spawn_x, .y = spawn_y };
+ }
+ }
+ };
+
+ return switch (enemy_type) {
+ .grunt => lbl: {
+ var grunt = Grunt{
+ .bullet_pool = .{
+ .bullets = undefined,
+ .fire_timer = .{
+ .duration = 2.0,
+ },
+ },
+ .position = spawn_position,
+ };
+
+ grunt.movement_speed = @min(160.0, 40.0 * difficulty);
+ grunt.bullet_speed = @min(300.0, 100.0 * difficulty);
+ grunt.bullet_pool.fire_timer.duration = @max(0.3, 2.0 / difficulty);
+
+ break :lbl .{ .grunt = grunt };
+ },
+ };
+}
diff --git a/src/ui.zig b/src/ui.zig
@@ -0,0 +1,28 @@
+const raylib = @import("raylib");
+
+pub const Button = struct {
+ size: raylib.Vector2,
+ position: raylib.Vector2,
+ bounds: raylib.Rectangle,
+
+ pub fn new(size: raylib.Vector2, position: raylib.Vector2) Button {
+ return .{
+ .size = size,
+ .position = position,
+ .bounds = .{
+ .x = position.x,
+ .y = position.y,
+ .width = size.x,
+ .height = size.y,
+ },
+ };
+ }
+
+ pub fn clicked(self: Button, target: raylib.Vector2) bool {
+ if (raylib.CheckCollisionPointRec(target, self.bounds)) {
+ return raylib.IsMouseButtonReleased(raylib.MOUSE_BUTTON_LEFT);
+ }
+
+ return false;
+ }
+};