shoot projectiles
This commit is contained in:
@@ -13,3 +13,7 @@ MAX_TOWERS :: 32
|
||||
|
||||
TOWER_COST :: 50
|
||||
|
||||
MAX_PROJECTILES :: 64
|
||||
PROJECTILE_SPEED :: 280 // pixels per second
|
||||
PROJECTILE_HIT_RADIUS :: 10
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+25
-12
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+11
-12
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-2
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user