1.44mb-gamejam

submission for the 1.44mb game jam - https://2pgarcade.com/contest-144mb.html
git clone git://brookjeynes.dev/bjeynes/1.44mb-gamejam.git
Log | Files | Refs

commit 2fe84e94cecc71ae5e67988e8226eb772718d4c0
parent 410b751ad9501886166e35a73a400a597286fce2
Author: brookjeynes <me@brookjeynes.dev>
Date:   Fri,  7 Aug 2026 08:58:36 +1000

feat: new enemy type

Signed-off-by: brookjeynes <me@brookjeynes.dev>

Diffstat:
Asrc/enemy_types/blorb.zig | 88+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/entity.zig | 7++++++-
Msrc/state.zig | 93+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
3 files changed, 183 insertions(+), 5 deletions(-)

diff --git a/src/enemy_types/blorb.zig b/src/enemy_types/blorb.zig @@ -0,0 +1,88 @@ +const std = @import("std"); + +const raylib = @import("raylib"); + +const bullet = @import("../bullet.zig"); + +pub const bullet_damage: f32 = 80.0; +pub const size: raylib.Vector2 = .{ .x = 60, .y = 60 }; + +pub const Blorb = @This(); + +movement_speed: f32 = 20.0, +bullet_speed: f32 = 50.0, +direction: raylib.Vector2 = .{}, +position: raylib.Vector2 = .{}, +shoot_offset: u2 = 0, + +bullet_pool: bullet.Pool, + +pub fn tick(self: *Blorb) void { + if (self.bullet_pool.tick()) self.shoot(); +} + +pub fn shoot(self: *Blorb) void { + const center: raylib.Vector2 = .{ + .x = self.position.x + (size.x / 2), + .y = self.position.y + (size.y / 2), + }; + + for ([_]u2{ 0, 1, 2, 3 }) |delta| { + const side = self.shoot_offset +% delta; + const spawn_position: raylib.Vector2 = switch (side) { + 0 => .{ .x = center.x, .y = self.position.y }, + 1 => .{ .x = self.position.x + size.x, .y = center.y }, + 2 => .{ .x = center.x, .y = self.position.y + size.y }, + else => .{ .x = self.position.x, .y = center.y }, + }; + const bullet_velocity: raylib.Vector2 = switch (side) { + 0 => .{ .x = 0, .y = -self.bullet_speed }, + 1 => .{ .x = self.bullet_speed, .y = 0 }, + 2 => .{ .x = 0, .y = self.bullet_speed }, + else => .{ .x = -self.bullet_speed, .y = 0 }, + }; + self.bullet_pool.spawn(spawn_position, bullet_velocity, raylib.PURPLE); + } + + self.shoot_offset +%= 1; +} + +pub fn setRandomDirection(self: *Blorb) void { + const angle: f32 = @as(f32, @floatFromInt(raylib.GetRandomValue(0, 360))); + self.direction = .{ + .x = @cos(angle * std.math.pi / 180.0), + .y = @sin(angle * std.math.pi / 180.0), + }; +} + +pub fn handleMovement(self: *Blorb) void { + const delta: f32 = raylib.GetFrameTime(); + + 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; + + if (self.position.x <= 0 or self.position.x >= max_x) { + self.direction.x = -self.direction.x; + self.position.x = @max(0, @min(max_x, self.position.x)); + } + if (self.position.y <= 0 or self.position.y >= max_y) { + self.direction.y = -self.direction.y; + self.position.y = @max(0, @min(max_y, self.position.y)); + } +} + +pub fn render(self: *Blorb) void { + raylib.DrawRectangleV( + self.position, + size, + raylib.PURPLE, + ); + + self.bullet_pool.render(.{ + .x = @floatFromInt(raylib.GetScreenWidth()), + .y = @floatFromInt(raylib.GetScreenHeight()), + }); +} diff --git a/src/entity.zig b/src/entity.zig @@ -1,27 +1,32 @@ const raylib = @import("raylib"); const Grunt = @import("./enemy_types/grunt.zig"); +const Blorb = @import("./enemy_types/blorb.zig"); -pub const EntityType = enum { grunt }; +pub const EntityType = enum { grunt, blorb }; pub const Entity = union(EntityType) { grunt: Grunt, + blorb: Blorb, pub fn render(entity: *Entity) void { switch (entity.*) { .grunt => |*grunt| grunt.render(), + .blorb => |*blorb| blorb.render(), } } pub fn move(entity: *Entity, target_position: raylib.Vector2) void { switch (entity.*) { .grunt => |*grunt| grunt.handleMovement(target_position), + .blorb => |*blorb| blorb.handleMovement(), } } pub fn tick(entity: *Entity) void { switch (entity.*) { .grunt => |*grunt| grunt.tick(), + .blorb => |*blorb| blorb.tick(), } } }; diff --git a/src/state.zig b/src/state.zig @@ -4,6 +4,7 @@ const raymath = @import("raymath"); const Bullet = @import("./bullet.zig"); const constants = @import("./constants.zig"); const Grunt = @import("./enemy_types/grunt.zig"); +const Blorb = @import("./enemy_types/blorb.zig"); const Entity = @import("./entity.zig").Entity; const EntityType = @import("./entity.zig").EntityType; const Player = @import("./player.zig"); @@ -27,7 +28,8 @@ pub const GameState = struct { enemies: [constants.max_enemies]Entity, enemy_count: u8, - enemy_spawn_time: Timer = .{ .duration = 0.7 }, + grunt_spawn_time: Timer = .{ .duration = 0.7 }, + blorb_spawn_time: Timer = .{ .duration = 5.0 }, pub fn init() GameState { return .{ @@ -74,7 +76,9 @@ pub const GameState = struct { self.time = 0; self.enemy_count = 0; - self.enemy_spawn_time = .{ .duration = 0.7 }; + self.enemies_killed = 0; + self.grunt_spawn_time = .{ .duration = 0.7 }; + self.blorb_spawn_time = .{ .duration = 5.0 }; self.upgrades.apply(&self.player); self.player.health = self.player.max_health; @@ -93,7 +97,8 @@ pub const GameState = struct { const delta = raylib.GetFrameTime(); self.time += delta; - self.enemy_spawn_time.duration = @max(0.15, 0.7 / difficulty); + self.grunt_spawn_time.duration = @max(0.15, 0.7 / difficulty); + self.blorb_spawn_time.duration = @max(0.15, 5.0 / difficulty); self.player.tick(); if (self.player.health <= 0) { @@ -104,7 +109,7 @@ pub const GameState = struct { self.enemies[i].tick(); } - if (self.enemy_spawn_time.tick(delta)) { + if (self.grunt_spawn_time.tick(delta)) { const grunt = createEnemy( .{ .x = constants.screen_width, .y = constants.screen_height }, self.player, @@ -116,6 +121,18 @@ pub const GameState = struct { self.spawn_enemy(grunt); } + if (self.blorb_spawn_time.tick(delta)) { + const blorb = createEnemy( + .{ .x = constants.screen_width, .y = constants.screen_height }, + self.player, + difficulty, + .blorb, + Blorb.size, + ); + + self.spawn_enemy(blorb); + } + var enemy_idx: u8 = 0; lbl: while (enemy_idx < self.enemy_count) { const entity = &self.enemies[enemy_idx]; @@ -171,6 +188,56 @@ pub const GameState = struct { } } }, + .blorb => |*blorb| { + var enemy_bullet_idx = blorb.bullet_pool.count; + while (enemy_bullet_idx > 0) { + enemy_bullet_idx -= 1; + + const bullet = blorb.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) { + blorb.bullet_pool.despawn(enemy_bullet_idx); + self.player.handleDamage(Blorb.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 = blorb.position.x, + .y = blorb.position.y, + .width = Blorb.size.x, + .height = Blorb.size.y, + }, + ); + + if (is_collision) { + self.player.bullet_pool.despawn(player_bullet_idx); + self.despawn_enemy(enemy_idx); + self.enemies_killed += 1; + continue :lbl; + } + } + }, } enemy_idx += 1; @@ -228,5 +295,23 @@ fn createEnemy( break :lbl .{ .grunt = grunt }; }, + .blorb => lbl: { + var blorb = Blorb{ + .bullet_pool = .{ + .bullets = undefined, + .fire_timer = .{ + .duration = 4.0, + }, + }, + .position = spawn_position, + }; + + blorb.movement_speed = @min(160.0, 40.0 * difficulty); + blorb.bullet_speed = @min(300.0, 100.0 * difficulty); + blorb.bullet_pool.fire_timer.duration = @max(0.3, 2.0 / difficulty); + blorb.setRandomDirection(); + + break :lbl .{ .blorb = blorb }; + }, }; }