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 b6efce7dc80d44096c058ba013768601f57b5953
parent 5ec394929f5acb6a022d18a61d772b6f8723cb99
Author: brookjeynes <me@brookjeynes.dev>
Date:   Fri, 31 Jul 2026 15:21:01 +1000

feat: upgrades

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

Diffstat:
Msrc/bullet.zig | 13+++++++++----
Msrc/constants.zig | 1+
Msrc/entity.zig | 3++-
Msrc/main.zig | 5++---
Msrc/player.zig | 16++++++++--------
Msrc/state.zig | 15++++++++++-----
Asrc/upgrade.zig | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
7 files changed, 94 insertions(+), 21 deletions(-)

diff --git a/src/bullet.zig b/src/bullet.zig @@ -1,7 +1,10 @@ +const std = @import("std"); + const raylib = @import("raylib"); + +const constants = @import("./constants.zig"); const Timer = @import("./timer.zig"); -pub const max_bullets: u8 = 255; pub const radius: f32 = 10.0; pub const Bullet = struct { @@ -11,7 +14,7 @@ pub const Bullet = struct { }; pub const Pool = struct { - bullets: [255]Bullet, + bullets: [constants.max_bullets]Bullet, count: usize = 0, fire_timer: Timer, @@ -25,6 +28,7 @@ pub const Pool = struct { velocity: raylib.Vector2, colour: raylib.Color, ) void { + if (self.count == self.bullets.len) return; self.bullets[self.count] = .{ .position = position, .velocity = velocity, @@ -34,11 +38,11 @@ pub const Pool = struct { } pub fn despawn(self: *Pool, idx: usize) void { - for (idx..self.count) |i| { + for (idx..self.count - 1) |i| { self.bullets[i] = self.bullets[i + 1]; } - self.count -= 1; + if (self.count > 0) self.count -= 1; } pub fn render(self: *Pool, screen_size: raylib.Vector2) void { @@ -60,6 +64,7 @@ pub const Pool = struct { } raylib.DrawCircleV(bullet.position, radius, bullet.color); + i += 1; } } diff --git a/src/constants.zig b/src/constants.zig @@ -1,3 +1,4 @@ +pub const max_bullets: u8 = 255; pub const max_enemies: u8 = 255; pub const screen_width: u16 = 800; pub const screen_height: u16 = 1000; diff --git a/src/entity.zig b/src/entity.zig @@ -1,6 +1,7 @@ -const Grunt = @import("./enemy_types/grunt.zig"); const raylib = @import("raylib"); +const Grunt = @import("./enemy_types/grunt.zig"); + pub const EntityType = enum { grunt }; pub const Entity = union(EntityType) { diff --git a/src/main.zig b/src/main.zig @@ -2,12 +2,12 @@ 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 Player = @import("./player.zig"); const GameState = @import("./state.zig").GameState; const Timer = @import("./timer.zig"); const Button = @import("./ui.zig").Button; -const constants = @import("./constants.zig"); const start_button = Button.new( .{ .x = 100, .y = 70 }, @@ -56,7 +56,6 @@ pub fn main() !void { .upgrade => {}, .gameplay => { // -- tick - for (0..game_state.enemy_count) |idx| { game_state.enemies[idx].move(game_state.player.position); } @@ -78,7 +77,7 @@ pub fn main() !void { // Health overlay const health_percent: f32 = @max( 0, - @min(1, game_state.player.health / Player.max_health), + @min(1, game_state.player.health / game_state.player.max_health), ); const damage_percent = 1 - health_percent; raylib.DrawRectangle( diff --git a/src/player.zig b/src/player.zig @@ -1,25 +1,25 @@ const raylib = @import("raylib"); const raymath = @import("raymath"); -const std = @import("std"); const bullet = @import("./bullet.zig"); const Timer = @import("./timer.zig"); pub const Player = @This(); -const bullet_speed: f32 = 300.0; 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; -pub const max_health: f32 = 255; -const health_regen_amount: f32 = 50; +const starting_max_health: u8 = 255; movement_speed: f32 = 140.0, +bullet_speed: f32 = 300.0, +health_regen_amount: f32 = 50, size: raylib.Vector2 = .{ .x = 50, .y = 50 }, position: raylib.Vector2 = .{}, velocity: raylib.Vector2 = .{}, -health: f32 = max_health, +max_health: f32 = starting_max_health, +health: f32 = starting_max_health, health_regen_timer: Timer = .{ .duration = 5.0, }, @@ -41,7 +41,7 @@ pub fn render(self: *Player) void { pub fn tick(self: *Player) void { if (self.health_regen_timer.tick(raylib.GetFrameTime())) { - self.health = @min(self.health + health_regen_amount, max_health); + self.health = @min(self.health + self.health_regen_amount, self.max_health); } } @@ -105,8 +105,8 @@ pub fn shoot(self: *Player) void { .y = mouse_pos.y - center.y, }); const bullet_vel: raylib.Vector2 = .{ - .x = dir.x * bullet_speed, - .y = dir.y * bullet_speed, + .x = dir.x * self.bullet_speed, + .y = dir.y * self.bullet_speed, }; self.bullet_pool.spawn(center, bullet_vel, raylib.BLUE); } diff --git a/src/state.zig b/src/state.zig @@ -8,6 +8,8 @@ const Entity = @import("./entity.zig").Entity; const EntityType = @import("./entity.zig").EntityType; const Player = @import("./player.zig"); const Timer = @import("./timer.zig"); +const UpgradeId = @import("./upgrade.zig").UpgradeId; +const Upgrades = @import("./upgrade.zig").Upgrades; pub const Screen = enum(u3) { title, @@ -20,6 +22,7 @@ pub const GameState = struct { time: f32, player: Player, + upgrades: Upgrades, enemies: [constants.max_enemies]Entity, enemy_count: u8, @@ -42,8 +45,10 @@ pub const GameState = struct { .y = constants.screen_height / 2, }, }, + .upgrades = .{ .levels = [_]u8{0} ** UpgradeId.count }, .enemies = undefined, + .enemies_killed = 0, .enemy_count = 0, }; } @@ -56,7 +61,7 @@ pub const GameState = struct { } pub fn despawn_enemy(self: *GameState, enemy_idx: u8) void { - for (enemy_idx..self.enemy_count) |i| { + for (enemy_idx..self.enemy_count - 1) |i| { self.enemies[i] = self.enemies[i + 1]; } @@ -70,7 +75,8 @@ pub const GameState = struct { self.enemy_count = 0; self.enemy_spawn_time = .{ .duration = 0.7 }; - self.player.health = Player.max_health; + self.upgrades.apply(&self.player); + self.player.health = self.player.max_health; self.player.position = .{ .x = constants.screen_width / 2, .y = constants.screen_height / 2, @@ -110,7 +116,7 @@ pub const GameState = struct { } var enemy_idx: u8 = 0; - while (enemy_idx < self.enemy_count) { + lbl: while (enemy_idx < self.enemy_count) { const entity = &self.enemies[enemy_idx]; switch (entity.*) { @@ -158,9 +164,8 @@ pub const GameState = struct { if (is_collision) { self.player.bullet_pool.despawn(player_bullet_idx); - self.despawn_enemy(enemy_idx); - enemy_idx -= 1; + continue :lbl; } } }, diff --git a/src/upgrade.zig b/src/upgrade.zig @@ -0,0 +1,62 @@ +const Player = @import("./player.zig"); + +pub const UpgradeId = enum(u8) { + max_health, + movement_speed, + bullet_speed, + fire_rate, + regen, + + pub const count = @typeInfo(UpgradeId).@"enum".fields.len; +}; + +const Meta = struct { + name: []const u8, + base: f32, + per_level: f32, + max_level: u8, + cost: u8, +}; + +pub const meta: [UpgradeId.count]Meta = .{ + .{ .name = "health", .base = 255, .per_level = 50, .max_level = 10, .cost = 5 }, + .{ .name = "speed", .base = 140, .per_level = 10, .max_level = 10, .cost = 5 }, + .{ .name = "bullet spd", .base = 300, .per_level = 20, .max_level = 10, .cost = 5 }, + .{ .name = "fire rate", .base = 0.4, .per_level = -0.02, .max_level = 10, .cost = 5 }, + .{ .name = "regen", .base = 50, .per_level = 10, .max_level = 10, .cost = 5 }, +}; + +pub const Upgrades = struct { + levels: [UpgradeId.count]u8, + + pub fn level(self: Upgrades, comptime id: UpgradeId) u8 { + return self.levels[@intFromEnum(id)]; + } + + pub fn maxed(self: Upgrades, comptime id: UpgradeId) bool { + return self.level(id) >= meta[@intFromEnum(id)].max_level; + } + + pub fn stat(self: Upgrades, comptime id: UpgradeId) f32 { + const m = meta[@intFromEnum(id)]; + return m.base + m.per_level * @as(f32, @floatFromInt(self.level(id))); + } + + pub fn cost(self: Upgrades, comptime id: UpgradeId) u8 { + return meta[@intFromEnum(id)].cost * (self.level(id) + 1); + } + + pub fn upgrade(self: *Upgrades, comptime id: UpgradeId) bool { + if (self.maxed(id)) return false; + self.levels[@intFromEnum(id)] += 1; + return true; + } + + pub fn apply(self: Upgrades, player: *Player) void { + player.max_health = self.stat(.max_health); + player.movement_speed = self.stat(.movement_speed); + player.bullet_speed = self.stat(.bullet_speed); + player.health_regen_amount = self.stat(.regen); + player.bullet_pool.fire_timer = .{ .duration = self.stat(.fire_rate) }; + } +};