wave spawner, game phases, making game window larger

This commit is contained in:
2026-06-12 00:11:07 -07:00
parent 285b434499
commit c13a7979b0
8 changed files with 94 additions and 18 deletions
+12 -6
View File
@@ -5,7 +5,7 @@ import rl "vendor:raylib"
Command_Kind :: enum {
None,
Place_Tower,
Spawn_Test,
Start_Wave,
}
Command :: struct {
@@ -15,14 +15,20 @@ Command :: struct {
}
gather_commands :: proc(world: ^World) -> Command {
if rl.IsMouseButtonPressed(.LEFT) {
if world.phase == .Build && rl.IsKeyPressed(.N) {
return Command{kind = .Start_Wave}
}
if world.phase == .Build && rl.IsMouseButtonPressed(.LEFT) {
mouse := rl.GetMousePosition()
gx, gy := screen_to_grid(mouse.x, mouse.y)
return Command{kind = .Place_Tower, gx = gx, gy = gy}
}
if rl.IsKeyPressed(.SPACE) {
return Command{kind = .Spawn_Test}
if rl.IsMouseButtonPressed(.LEFT) {
mouse := rl.GetMousePosition()
gx, gy := screen_to_grid(mouse.x, mouse.y)
return Command{kind = .Place_Tower, gx = gx, gy = gy}
}
return Command{kind = .None}
@@ -31,10 +37,10 @@ gather_commands :: proc(world: ^World) -> Command {
execute_command :: proc(world: ^World, cmd: Command) {
switch cmd.kind {
case .None:
case .Spawn_Test:
spawn_group(world, 3)
case .Place_Tower:
try_place_tower(world, cmd.gx, cmd.gy, .Archer)
case .Start_Wave:
start_wave(world)
}
}
+2 -2
View File
@@ -1,7 +1,7 @@
package game
MAP_W :: 16 // map width in tiles
MAP_H :: 12 // map height in tiles
MAP_W :: 30 // map width in tiles
MAP_H :: 20 // map height in tiles
TILE_SIZE :: 32 // size of tiles in pixels
SCREEN_W :: MAP_W * TILE_SIZE
+3
View File
@@ -2,6 +2,7 @@ package game
Event_Kind :: enum {
Enemy_Killed,
Wave_Survived,
}
Event :: struct {
@@ -18,6 +19,8 @@ process_events :: proc(world: ^World) {
switch ev.kind {
case .Enemy_Killed:
world.gold += ev.gold_reward
case .Wave_Survived:
world.gold += ev.gold_reward
}
}
clear(&world.events)
+6 -6
View File
@@ -31,16 +31,16 @@ init_map :: proc(world_map: ^Map) {
}
}
for x in 1 ..< 13 {
world_map.tiles[6][x] = .Path
for x in 9 ..< 21 {
world_map.tiles[10][x] = .Path
}
for y in 7 ..< 8 {
world_map.tiles[y][12] = .Path
for y in 11 ..< 14 {
world_map.tiles[y][20] = .Path
}
for y in 4 ..< 9 {
for x in 3 ..< 12 {
for y in 7 ..< 15 {
for x in 6 ..< 24 {
if world_map.tiles[y][x] != .Path {
world_map.tiles[y][x] = .Build
}
+12 -3
View File
@@ -35,8 +35,17 @@ render_world :: proc(world: ^World) {
if (world.base_health == 0) {
rl.DrawText("**GAME OVER**", 20, 20, 20, rl.RED)
}
rl.DrawText(fmt.ctprintf("Gold: %d", world.gold), 10, 10, 20, rl.BLACK) // Gold HUD
rl.DrawText(fmt.ctprintf("Base HP: %d", world.base_health), 10, 32, 20, rl.BLACK) // HP HUD
rl.DrawText(fmt.ctprintf("Enemies: %d", active_enemy_count(world)), 10, 54, 18, rl.DARKGRAY)
rl.DrawText(fmt.ctprintf("Gold: %d", world.gold), 10, 10, 22, rl.BLACK) // Gold HUD
rl.DrawText(fmt.ctprintf("Base HP: %d", world.base_health), 10, 32, 22, rl.BLACK) // HP HUD
rl.DrawText(fmt.ctprintf("Enemies: %d", active_enemy_count(world)), 10, 54, 22, rl.BLACK)
rl.DrawText(fmt.ctprintf("Wave: %d / %d", world.wave, MAX_WAVES), 10, 76, 22, rl.BLACK)
switch world.phase {
case .Build:
rl.DrawText("Build - click towers, N = start wave", 10, 98, 22, rl.BLACK)
case .Combat:
rl.DrawText("Combat!", 10, 74, 22, rl.MAROON)
}
}
+2 -1
View File
@@ -19,7 +19,7 @@ Tower_Archetype :: struct {
}
TOWER_ARCHETYPES: [Tower_Kind]Tower_Archetype = {
.Archer = {kind = .Archer, range = 95, damage = 12, fire_rate = 0.45, cost = TOWER_COST},
.Archer = {kind = .Archer, range = 20, damage = 12, fire_rate = 0.45, cost = TOWER_COST},
}
get_archetype :: proc(kind: Tower_Kind) -> Tower_Archetype {
@@ -27,6 +27,7 @@ get_archetype :: proc(kind: Tower_Kind) -> Tower_Archetype {
}
try_place_tower :: proc(world: ^World, gx, gy: int, kind: Tower_Kind) -> bool {
if world.phase != .Build do return false
arch := get_archetype(kind)
if !can_build_at(&world.world_map, gx, gy) do return false
if world.gold < TOWER_COST do return false
+51
View File
@@ -0,0 +1,51 @@
package game
Game_Phase :: enum {
Build,
Combat,
}
Wave_Spawner :: struct {
enemies_to_spawn: int,
spawn_timer: f32,
spawn_interval: f32,
}
start_wave :: proc(world: ^World) {
if world.phase != .Build do return
world.wave += 1
world.phase = .Combat
world.spawner = Wave_Spawner {
enemies_to_spawn = 5 + world.wave * 2,
spawn_timer = 0,
spawn_interval = 0.55,
}
}
update_wave :: proc(world: ^World, dt: f32) {
if world.phase != .Combat do return
if world.spawner.enemies_to_spawn <= 0 do return
if len(world.path) < 2 do return
world.spawner.spawn_timer -= dt
if world.spawner.spawn_timer <= 0 {
health := 15 + world.wave * 5
speed := 55 + f32(world.wave) * 5
spawn_enemy(world, health, speed)
world.spawner.enemies_to_spawn -= 1
world.spawner.spawn_timer = world.spawner.spawn_interval
}
}
wave_clear :: proc(world: ^World) -> bool {
if world.spawner.enemies_to_spawn > 0 do return false
return active_enemy_count(world) == 0
}
update_phase :: proc(world: ^World) {
if world.phase == .Combat && wave_clear(world) {
world.phase = .Build
push_event(world, .Wave_Survived, gold_reward = 25)
}
}
+6
View File
@@ -10,12 +10,16 @@ World :: struct {
enemies: [MAX_ENEMIES]Enemy,
towers: [dynamic]Tower,
events: [dynamic]Event,
phase: Game_Phase,
wave: int,
spawner: Wave_Spawner,
}
init_world :: proc() -> World {
world := World {
gold = 100,
base_health = 20,
phase = .Build,
}
init_map(&world.world_map)
build_path(&world.world_map, &world.path)
@@ -27,8 +31,10 @@ update_world :: proc(world: ^World, dt: f32) {
cmd := gather_commands(world)
execute_command(world, cmd)
update_wave(world, dt)
update_enemies(world, dt)
update_towers(world, dt)
update_phase(world)
process_events(world)
}