From b9235246c7cd81664595d32da38c3f71417e5edf Mon Sep 17 00:00:00 2001 From: codegirl-007 Date: Sat, 13 Jun 2026 23:33:03 -0700 Subject: [PATCH] shoot projectiles --- game/constants.odin | 4 +++ game/projectile.odin | 67 ++++++++++++++++++++++++++++++++++++++++++++ game/render.odin | 37 ++++++++++++++++-------- game/tower.odin | 23 ++++++++------- game/wave.odin | 12 ++++++++ game/world.odin | 6 ++-- 6 files changed, 123 insertions(+), 26 deletions(-) create mode 100644 game/projectile.odin diff --git a/game/constants.odin b/game/constants.odin index 73dfcf8..176fd21 100644 --- a/game/constants.odin +++ b/game/constants.odin @@ -13,3 +13,7 @@ MAX_TOWERS :: 32 TOWER_COST :: 50 +MAX_PROJECTILES :: 64 +PROJECTILE_SPEED :: 280 // pixels per second +PROJECTILE_HIT_RADIUS :: 10 + diff --git a/game/projectile.odin b/game/projectile.odin new file mode 100644 index 0000000..345cee6 --- /dev/null +++ b/game/projectile.odin @@ -0,0 +1,67 @@ +package game + +Projectile :: struct { + active: bool, + position: Vec2, + target_slot: int, + damage: int, + speed: f32, +} + +acquire_projectile :: proc(world: ^World) -> ^Projectile { + for i in 0 ..< MAX_PROJECTILES { + if !world.projectiles[i].active { + return &world.projectiles[i] + } + } + + return nil +} + +enemy_still_on_map :: proc(world: ^World, slot: int) -> (^Enemy, bool) { + if slot < 0 || slot >= MAX_ENEMIES do return nil, false + e := &world.enemies[slot] + if !e.active do return nil, false + if len(world.path) == 0 do return nil, false + if e.path_index >= len(world.path) do return nil, false + return e, true +} + +spawn_projectile :: proc(world: ^World, from: Vec2, target_slot: int, damage: int) { + p := acquire_projectile(world) + if p == nil do return + + p^ = Projectile { + active = true, + position = from, + target_slot = target_slot, + damage = damage, + speed = PROJECTILE_SPEED, + } +} + +update_projectiles :: proc(world: ^World, dt: f32) { + for i in 0 ..< MAX_PROJECTILES { + p := &world.projectiles[i] + if !p.active do continue + + target, ok := enemy_still_on_map(world, p.target_slot) + if !ok { + p.active = false + continue + } + + move_toward(&p.position, target.position, p.speed, dt) + + if distance(p.position, target.position) <= PROJECTILE_HIT_RADIUS { + target.health -= p.damage + p.active = false + + if target.health <= 0 { + target.active = false + push_event(world, .Enemy_Killed, gold_reward = 5) + } + } + } +} + diff --git a/game/render.odin b/game/render.odin index f203e22..0de875c 100644 --- a/game/render.odin +++ b/game/render.odin @@ -11,6 +11,22 @@ render_world :: proc(world: ^World) { } } + for t in world.towers { + rl.DrawRectangle( + i32(t.position.x - 14), + i32(t.position.y - 14), + 28, + 28, + rl.Color{255, 203, 0, 255}, + ) + } + + for i in 0 ..< MAX_PROJECTILES { + p := world.projectiles[i] + if !p.active do continue + rl.DrawCircle(i32(p.position.x), i32(p.position.y), 4, rl.GOLD) + } + for i in 0 ..< MAX_ENEMIES { e := world.enemies[i] if !e.active do continue @@ -24,18 +40,11 @@ render_world :: proc(world: ^World) { ) } - for t in world.towers { - rl.DrawRectangle( - i32(t.position.x - 14), - i32(t.position.y - 14), - 28, - 28, - rl.Color{255, 203, 0, 255}, - ) - } - if (world.base_health == 0) { - rl.DrawText("**GAME OVER**", 20, 20, 20, rl.RED) + for i in 0 ..< MAX_PROJECTILES { + p := world.projectiles[i] + if !p.active do continue + rl.DrawCircle(i32(p.position.x), i32(p.position.y), 4, rl.GOLD) } rl.DrawText(fmt.ctprintf("Gold: %d", world.gold), 10, 10, 22, rl.BLACK) // Gold HUD @@ -47,7 +56,11 @@ render_world :: proc(world: ^World) { 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) + rl.DrawText("Combat!", 10, 98, 22, rl.MAROON) + case .Game_Over: + rl.DrawText("GAME_OVER", 260, 280, 40, rl.RED) + case .Victory: + rl.DrawText("Victory!", 260, 280, 40, rl.GREEN) } } diff --git a/game/tower.odin b/game/tower.odin index 701ae47..7b0e40d 100644 --- a/game/tower.odin +++ b/game/tower.odin @@ -42,23 +42,23 @@ try_place_tower :: proc(world: ^World, gx, gy: int, kind: Tower_Kind) -> bool { return true } -find_target :: proc(tower: Tower, world: ^World) -> ^Enemy { +find_target_slot :: proc(tower: Tower, world: ^World) -> int { arch := get_archetype(tower.kind) - best: ^Enemy = nil + best_slot: int = -1 best_dist: f32 = 999999 for i in 0 ..< MAX_ENEMIES { e := &world.enemies[i] - if !e.active do continue + if e.path_index >= len(world.path) do continue d := distance(tower.position, e.position) if d <= arch.range && d < best_dist { - best = e + best_slot = i best_dist = d } } - return best + return best_slot } update_towers :: proc(world: ^World, dt: f32) { @@ -68,16 +68,15 @@ update_towers :: proc(world: ^World, dt: f32) { if t.cooldown > 0 do continue - target := find_target(t^, world) - if target == nil do continue + slot := find_target_slot(t^, world) + if slot < 0 do continue arch := get_archetype(t.kind) - target.health -= arch.damage - t.cooldown = arch.fire_rate - if target.health <= 0 { - target.active = false - push_event(world, .Enemy_Killed, gold_reward = 5) + switch t.kind { + case .Archer: + spawn_projectile(world, t.position, slot, arch.damage) + t.cooldown = arch.fire_rate } } } diff --git a/game/wave.odin b/game/wave.odin index a929bc3..816a22a 100644 --- a/game/wave.odin +++ b/game/wave.odin @@ -3,6 +3,8 @@ package game Game_Phase :: enum { Build, Combat, + Game_Over, + Victory, } Wave_Spawner :: struct { @@ -49,3 +51,13 @@ update_phase :: proc(world: ^World) { } } +check_end :: proc(world: ^World) { + if world.base_health <= 0 { + world.phase = .Game_Over + } + + if world.wave >= MAX_WAVES && world.phase == .Build && wave_clear(world) { + world.phase = .Victory + } +} + diff --git a/game/world.odin b/game/world.odin index 6339cac..1151e55 100644 --- a/game/world.odin +++ b/game/world.odin @@ -1,7 +1,5 @@ package game -import "core:fmt" -import rl "vendor:raylib" World :: struct { gold: int, base_health: int, @@ -13,6 +11,7 @@ World :: struct { phase: Game_Phase, wave: int, spawner: Wave_Spawner, + projectiles: [MAX_PROJECTILES]Projectile, } init_world :: proc() -> World { @@ -28,13 +27,16 @@ init_world :: proc() -> World { // update world, never draw update_world :: proc(world: ^World, dt: f32) { + if world.phase == .Game_Over || world.phase == .Victory do return cmd := gather_commands(world) execute_command(world, cmd) update_wave(world, dt) update_enemies(world, dt) + update_projectiles(world, dt) update_towers(world, dt) update_phase(world) process_events(world) + check_end(world) }