commit 05a4504d913601357972796f35e886b3cfd12453
parent a9824dfff8d77abe9ae2fb41571093d09c1e15ba
Author: brookjeynes <me@brookjeynes.dev>
Date: Fri, 31 Jul 2026 11:40:58 +1000
refactor: extract enemy type
Signed-off-by: brookjeynes <me@brookjeynes.dev>
Diffstat:
4 files changed, 133 insertions(+), 112 deletions(-)
diff --git a/src/enemy.zig b/src/enemy.zig
@@ -1,61 +0,0 @@
-const raylib = @import("raylib");
-const raymath = @import("raymath");
-
-const bullet = @import("./bullet.zig");
-
-pub const bullet_damage: f32 = 50.0;
-
-pub const Enemy = @This();
-
-movement_speed: f32 = 40.0,
-bullet_speed: f32 = 100.0,
-size: raylib.Vector2 = .{ .x = 40, .y = 40 },
-direction: raylib.Vector2 = .{},
-position: raylib.Vector2 = .{},
-
-bullet_pool: bullet.Pool,
-
-pub fn handleMovement(self: *Enemy, target: raylib.Vector2) void {
- const delta: f32 = raylib.GetFrameTime();
-
- const direction: raymath.Vector2 = raymath.Vector2Subtract(@bitCast(target), @bitCast(self.position));
- self.direction = @bitCast(raymath.Vector2Normalize(@bitCast(direction)));
- self.position.x += self.direction.x * self.movement_speed * delta;
- self.position.y += self.direction.y * self.movement_speed * delta;
-
- const max_x: f32 = @as(f32, @floatFromInt(raylib.GetScreenWidth())) - self.size.x;
- const max_y: f32 = @as(f32, @floatFromInt(raylib.GetScreenHeight())) - self.size.y;
- self.position.x = @max(0, @min(max_x, self.position.x));
- self.position.y = @max(0, @min(max_y, self.position.y));
-
- self.shoot();
-}
-
-pub fn shoot(self: *Enemy) void {
- if (self.bullet_pool.tick()) {
- self.bullet_pool.spawn(
- .{
- .x = self.position.x + (self.size.x / 2),
- .y = self.position.y + (self.size.y / 2),
- },
- .{
- .x = self.direction.x * self.bullet_speed,
- .y = self.direction.y * self.bullet_speed,
- },
- raylib.RED,
- );
- }
-}
-
-pub fn render(self: *Enemy) void {
- raylib.DrawRectangleV(
- self.position,
- self.size,
- raylib.RED,
- );
-
- self.bullet_pool.render(.{
- .x = @floatFromInt(raylib.GetScreenWidth()),
- .y = @floatFromInt(raylib.GetScreenHeight()),
- });
-}
diff --git a/src/enemy_types/grunt.zig b/src/enemy_types/grunt.zig
@@ -0,0 +1,59 @@
+const raylib = @import("raylib");
+const raymath = @import("raymath");
+
+const bullet = @import("../bullet.zig");
+
+pub const bullet_damage: f32 = 50.0;
+pub const size: raylib.Vector2 = .{ .x = 40, .y = 40 };
+
+pub const Grunt = @This();
+
+movement_speed: f32 = 40.0,
+bullet_speed: f32 = 100.0,
+direction: raylib.Vector2 = .{},
+position: raylib.Vector2 = .{},
+
+bullet_pool: bullet.Pool,
+
+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,
+ );
+ }
+}
+
+pub fn handleMovement(self: *Grunt, target: raylib.Vector2) void {
+ const delta: f32 = raylib.GetFrameTime();
+
+ const direction: raymath.Vector2 = raymath.Vector2Subtract(@bitCast(target), @bitCast(self.position));
+ self.direction = @bitCast(raymath.Vector2Normalize(@bitCast(direction)));
+ self.position.x += self.direction.x * self.movement_speed * delta;
+ self.position.y += self.direction.y * self.movement_speed * delta;
+
+ const max_x: f32 = @as(f32, @floatFromInt(raylib.GetScreenWidth())) - size.x;
+ const max_y: f32 = @as(f32, @floatFromInt(raylib.GetScreenHeight())) - size.y;
+ self.position.x = @max(0, @min(max_x, self.position.x));
+ self.position.y = @max(0, @min(max_y, self.position.y));
+}
+
+pub fn render(self: *Grunt) void {
+ raylib.DrawRectangleV(
+ self.position,
+ size,
+ raylib.RED,
+ );
+
+ self.bullet_pool.render(.{
+ .x = @floatFromInt(raylib.GetScreenWidth()),
+ .y = @floatFromInt(raylib.GetScreenHeight()),
+ });
+}
diff --git a/src/entity.zig b/src/entity.zig
@@ -0,0 +1,7 @@
+const Grunt = @import("./enemy_types/grunt.zig");
+
+pub const EntityType = enum { grunt };
+
+pub const Entity = union(EntityType) {
+ grunt: Grunt,
+};
diff --git a/src/main.zig b/src/main.zig
@@ -2,16 +2,14 @@ const raylib = @import("raylib");
const raymath = @import("raymath");
const Bullet = @import("./bullet.zig");
-const Enemy = @import("./enemy.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");
const max_enemies: u8 = 255;
-const Entity = union(enum) {
- enemy: Enemy,
-};
-
const Screen = enum(u3) {
title,
gameplay,
@@ -144,20 +142,26 @@ pub fn main() !void {
for (0..game_state.enemy_count) |i| {
const entity = &game_state.enemies[i];
+
switch (entity.*) {
- .enemy => |*e| e.handleMovement(player.position),
+ .grunt => |*e| {
+ e.handleMovement(player.position);
+ e.shoot();
+ },
}
}
if (enemy_spawn_time.tick(delta)) {
- game_state.spawn_enemy(.{ .enemy = spawnEnemy(
+ game_state.spawn_enemy(spawnEnemy(
.{
.x = game_state.screen.width,
.y = game_state.screen.height,
},
player,
difficulty,
- ) });
+ .grunt,
+ Grunt.size,
+ ));
}
raylib.BeginDrawing();
@@ -172,9 +176,9 @@ pub fn main() !void {
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];
+ .grunt => |*grunt| {
+ for (0..grunt.bullet_pool.count) |bullet_idx| {
+ const bullet = grunt.bullet_pool.bullets[bullet_idx];
const is_collision = raylib.CheckCollisionCircleRec(
.{
@@ -191,8 +195,8 @@ pub fn main() !void {
);
if (is_collision) {
- enemy.bullet_pool.despawn(bullet_idx);
- player.health -= Enemy.bullet_damage;
+ grunt.bullet_pool.despawn(bullet_idx);
+ player.health -= Grunt.bullet_damage;
if (player.health < 0) {
game_state.screen.current = .title;
@@ -210,10 +214,10 @@ pub fn main() !void {
},
Bullet.radius,
.{
- .x = enemy.position.x,
- .y = enemy.position.y,
- .width = enemy.size.x,
- .height = enemy.size.y,
+ .x = grunt.position.x,
+ .y = grunt.position.y,
+ .width = Grunt.size.x,
+ .height = Grunt.size.y,
},
);
@@ -233,7 +237,7 @@ pub fn main() !void {
for (0..game_state.enemy_count) |idx| {
const entity = &game_state.enemies[idx];
switch (entity.*) {
- .enemy => |*e| e.render(),
+ .grunt => |*grunt| grunt.render(),
}
}
@@ -251,43 +255,55 @@ pub fn main() !void {
}
}
-fn spawnEnemy(screen: raylib.Vector2, player: Player, difficulty: f32) Enemy {
+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;
- 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)));
- 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) {
- enemy.position = .{ .x = spawn_x, .y = spawn_y };
- break;
- }
- }
+ 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);
- enemy.movement_speed = @min(160.0, 40.0 * difficulty);
- enemy.bullet_speed = @min(300.0, 100.0 * difficulty);
- enemy.bullet_pool.fire_timer.duration = @max(0.3, 2.0 / difficulty);
+ const center_x = spawn_x + enemy_size.x / 2;
+ const center_y = spawn_y + enemy_size.y / 2;
- return enemy;
+ 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 };
+ },
+ };
}