ballistic projectiles for cannons

This commit is contained in:
2026-06-26 17:11:25 -07:00
parent 4d267c7f5d
commit 4124f39632
3 changed files with 88 additions and 14 deletions
+1
View File
@@ -21,6 +21,7 @@ TOWER_COST :: 50
MAX_PROJECTILES :: 64
PROJECTILE_SPEED :: 280 // pixels per second
PROJECTILE_HIT_RADIUS :: 10
CANNON_PROJECTILES_SPEED :: 320.0
OVERLAY_BAR_H :: 28
OVERLAY_PADDING :: 6
+79 -13
View File
@@ -8,6 +8,13 @@ Projectile :: struct {
target_slot: int,
damage: int,
speed: f32,
mode: Projectile_Mode,
direction: Vec2,
}
Projectile_Mode :: enum {
Homing, // chases target
Ballistic, // flies in a fixed direction
}
acquire_projectile :: proc(world: ^World) -> ^Projectile {
@@ -33,6 +40,7 @@ spawn_projectile :: proc(world: ^World, from: Vec2, target_slot: int, damage: in
p := acquire_projectile(world)
if p == nil do return
p.mode = .Homing
p^ = Projectile {
active = true,
position = from,
@@ -42,36 +50,94 @@ spawn_projectile :: proc(world: ^World, from: Vec2, target_slot: int, damage: in
}
}
spawn_ballistic :: proc(world: ^World, from, dir: Vec2, damage: int) {
p := acquire_projectile(world)
if p == nil do return
p^ = Projectile {
active = true,
position = from,
direction = dir,
mode = .Ballistic,
damage = damage,
speed = CANNON_PROJECTILES_SPEED,
}
}
hit_enemy_at :: proc(world: ^World, pos: Vec2, damage: int) -> bool {
for i in 0 ..< MAX_ENEMIES {
e := &world.enemies[i]
if !e.active do continue
if distance(pos, e.position) <= PROJECTILE_HIT_RADIUS {
apply_tower_hit(world, e, damage)
return true
}
}
return false
}
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
switch p.mode {
case .Homing:
update_homing_projectile(world, p, dt)
case .Ballistic:
update_ballistic_projectile(world, p, dt)
}
}
}
move_toward(&p.position, target.position, p.speed, dt)
update_homing_projectile :: proc(world: ^World, p: ^Projectile, dt: f32) {
target, ok := enemy_still_on_map(world, p.target_slot)
if !ok {
p.active = false
return
}
if distance(p.position, target.position) <= PROJECTILE_HIT_RADIUS {
target.health -= p.damage
p.active = false
move_toward(&p.position, target.position, p.speed, dt)
if target.health <= 0 {
target.active = false
push_event(world, .Enemy_Killed, gold_reward = 5)
}
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)
}
}
}
update_ballistic_projectile :: proc(world: ^World, p: ^Projectile, dt: f32) {
step := p.speed * dt
p.position.x += p.direction.x * step
p.position.y += p.direction.y * step
if hit_enemy_at(world, p.position, p.damage) {
p.active = false
return
}
if p.position.x < 0 ||
p.position.y < 0 ||
p.position.x > MAP_W * TILE_SIZE ||
p.position.y > MAP_H * TILE_SIZE {
p.active = false
}
}
render_projectiles :: proc(world: ^World) {
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)
color := rl.GOLD
radius: f32 = 4
if p.mode == .Ballistic {
color = rl.GRAY
radius = 6
}
rl.DrawCircle(i32(p.position.x), i32(p.position.y), radius, color)
}
}
+8 -1
View File
@@ -232,7 +232,14 @@ update_towers :: proc(world: ^World, dt: f32) {
spawn_projectile(world, t.position, slot, damage)
case .Cannon:
if target := enemy_at_slot(world, slot); target != nil {
apply_tower_hit(world, target, damage)
len := distance(t.position, target.position)
if (len > 0.001) {
dir := Vec2 {
(target.position.x - t.position.x) / len,
(target.position.y - t.position.y) / len,
}
spawn_ballistic(world, t.position, dir, damage)
}
}
case .Ice:
if target := enemy_at_slot(world, slot); target != nil {