commit 4f60de7c89963bb3c965c63ade7c4f1eae1ee3a9
parent a7a39a80e476dac702281c6e4adb5e36d208ed0c
Author: brookjeynes <me@brookjeynes.dev>
Date: Thu, 30 Jul 2026 06:05:39 +1000
feat: add enemies
Signed-off-by: brookjeynes <me@brookjeynes.dev>
Diffstat:
4 files changed, 148 insertions(+), 17 deletions(-)
diff --git a/src/bullet.zig b/src/bullet.zig
@@ -1,9 +1,7 @@
const raylib = @import("raylib");
const Timer = @import("./timer.zig");
-const speed: f16 = 300.0;
const radius: f16 = 10.0;
-const spawn_cooldown: f16 = 0.4;
pub const Bullet = struct {
position: raylib.Vector2,
@@ -17,7 +15,8 @@ pub fn Pool(comptime upper: usize) type {
bullets: [upper]Bullet = undefined,
count: usize = 0,
- fire_timer: Timer = .{ .duration = spawn_cooldown },
+ speed: f16,
+ fire_timer: Timer,
pub fn tick(self: *Self) bool {
return self.fire_timer.tick(raylib.GetFrameTime());
@@ -27,11 +26,12 @@ pub fn Pool(comptime upper: usize) type {
self: *Self,
position: raylib.Vector2,
direction: raylib.Vector2,
+ colour: raylib.Color,
) void {
self.bullets[self.count] = .{
.position = position,
.direction = direction,
- .color = raylib.MAROON,
+ .color = colour,
};
self.count += 1;
}
@@ -42,8 +42,8 @@ pub fn Pool(comptime upper: usize) type {
var i: usize = 0;
while (i < self.count) {
var bullet = &self.bullets[i];
- bullet.position.x += bullet.direction.x * speed * delta;
- bullet.position.y += bullet.direction.y * speed * delta;
+ bullet.position.x += bullet.direction.x * self.speed * delta;
+ bullet.position.y += bullet.direction.y * self.speed * delta;
if (bullet.position.x < -radius * 2 or
bullet.position.x > screen_size.x + radius * 2 or
diff --git a/src/enemy.zig b/src/enemy.zig
@@ -0,0 +1,64 @@
+const raylib = @import("raylib");
+const raymath = @import("raymath");
+
+const bullet = @import("./bullet.zig");
+
+const max_spawned_bullets = 2000;
+const bullet_speed: f16 = 100.0;
+const bullet_spawn_cooldown: f16 = 2.0;
+
+pub const Enemy = @This();
+
+movement_speed: f16 = 40.0,
+size: raylib.Vector2 = .{ .x = 40, .y = 40 },
+direction: raylib.Vector2 = .{},
+position: raylib.Vector2 = .{},
+
+bullet_pool: bullet.Pool(max_spawned_bullets) = .{
+ .speed = bullet_speed,
+ .fire_timer = .{
+ .duration = bullet_spawn_cooldown,
+ },
+},
+
+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),
+ },
+ self.direction,
+ 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/main.zig b/src/main.zig
@@ -1,8 +1,10 @@
const std = @import("std");
const raylib = @import("raylib");
+const raymath = @import("raymath");
const bullet = @import("./bullet.zig");
+const Enemy = @import("./enemy.zig");
const Player = @import("./player.zig");
const Timer = @import("./timer.zig");
@@ -23,8 +25,28 @@ pub fn main() !void {
},
};
+ var enemy_spawn_time: Timer = .{ .duration = 0.2 };
+ var enemies: [255]Enemy = undefined;
+ var enemy_count: u8 = 0;
+
while (!raylib.WindowShouldClose()) {
+ const delta = raylib.GetFrameTime();
+
player.handleMovement();
+ for (0..enemy_count) |i| {
+ enemies[i].handleMovement(player.position);
+ }
+
+ if (enemy_spawn_time.tick(delta)) {
+ enemies[enemy_count] = spawnEnemy(
+ .{
+ .x = screen_width,
+ .y = screen_height,
+ },
+ player,
+ );
+ enemy_count += 1;
+ }
raylib.BeginDrawing();
defer raylib.EndDrawing();
@@ -32,5 +54,37 @@ pub fn main() !void {
raylib.ClearBackground(raylib.RAYWHITE);
player.render();
+ for (0..enemy_count) |i| {
+ enemies[i].render();
+ }
}
}
+
+fn spawnEnemy(screen: raylib.Vector2, player: Player) Enemy {
+ 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{ .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;
+ }
+ }
+
+ return enemy;
+}
diff --git a/src/player.zig b/src/player.zig
@@ -3,27 +3,35 @@ const raymath = @import("raymath");
const bullet = @import("./bullet.zig");
-const max_bullets = 2000;
+const max_spawned_bullets = 2000;
+const bullet_speed: f16 = 300.0;
+const bullet_spawn_cooldown: f16 = 0.4;
pub const Player = @This();
movement_speed: f16 = 140.0,
size: raylib.Vector2 = .{ .x = 50, .y = 50 },
direction: raylib.Vector2 = .{},
+last_known_direction: raylib.Vector2 = .{},
position: raylib.Vector2 = .{},
-bullet_pool: bullet.Pool(max_bullets) = .{},
+bullet_pool: bullet.Pool(max_spawned_bullets) = .{
+ .speed = bullet_speed,
+ .fire_timer = .{
+ .duration = bullet_spawn_cooldown,
+ },
+},
pub fn handleMovement(self: *Player) void {
const delta: f32 = raylib.GetFrameTime();
- self.direction = .{};
- if (raylib.IsKeyDown(raylib.KEY_RIGHT)) self.direction.x += 1;
- if (raylib.IsKeyDown(raylib.KEY_LEFT)) self.direction.x -= 1;
- if (raylib.IsKeyDown(raylib.KEY_UP)) self.direction.y -= 1;
- if (raylib.IsKeyDown(raylib.KEY_DOWN)) self.direction.y += 1;
+ var direction: raymath.Vector2 = .{};
+ if (raylib.IsKeyDown(raylib.KEY_RIGHT)) direction.x += 1;
+ if (raylib.IsKeyDown(raylib.KEY_LEFT)) direction.x -= 1;
+ if (raylib.IsKeyDown(raylib.KEY_UP)) direction.y -= 1;
+ if (raylib.IsKeyDown(raylib.KEY_DOWN)) direction.y += 1;
- self.direction = @bitCast(raymath.Vector2Normalize(@bitCast(self.direction)));
+ self.direction = @bitCast(raymath.Vector2Normalize(direction));
self.position.x += self.direction.x * self.movement_speed * delta;
self.position.y += self.direction.y * self.movement_speed * delta;
@@ -32,7 +40,11 @@ pub fn handleMovement(self: *Player) void {
self.position.x = @max(0, @min(max_x, self.position.x));
self.position.y = @max(0, @min(max_y, self.position.y));
- if (self.direction.x != 0 or self.direction.y != 0) {
+ if (direction.x != 0 or direction.y != 0) {
+ self.last_known_direction = @bitCast(direction);
+ }
+
+ if (self.last_known_direction.x != 0 or self.last_known_direction.y != 0) {
self.shoot();
}
}
@@ -44,7 +56,8 @@ pub fn shoot(self: *Player) void {
.x = self.position.x + (self.size.x / 2),
.y = self.position.y + (self.size.y / 2),
},
- self.direction,
+ self.last_known_direction,
+ raylib.BLUE,
);
}
}
@@ -53,7 +66,7 @@ pub fn render(self: *Player) void {
raylib.DrawRectangleV(
self.position,
self.size,
- raylib.MAROON,
+ raylib.BLUE,
);
self.bullet_pool.render(.{