Setup awareness for walls and other entities.

This commit is contained in:
2026-08-05 08:10:14 +02:00
parent 657e09ecf2
commit a109e27ff4
3 changed files with 92 additions and 12 deletions
+89 -10
View File
@@ -76,6 +76,10 @@ get_spawn_rect :: proc(max_x, max_y: f32) -> rl.Rectangle {
return rand_pos return rand_pos
} }
// true when the enemy's candidate rect overlaps the player
check_player_collision :: proc(rect: rl.Rectangle, player: ^Player) -> bool {
return rl.CheckCollisionRecs(rect, get_player_collider(player, player.position))
}
// @TODO consider a self check, ie pass the enemy in question index and make sure to skip that // @TODO consider a self check, ie pass the enemy in question index and make sure to skip that
spawn_enemy :: proc(enemy_array: ^[MAX_ENEMIES]Enemy, assets: ^Enemy_Assets, enemy_count: ^int, game_map: Game_Map) { spawn_enemy :: proc(enemy_array: ^[MAX_ENEMIES]Enemy, assets: ^Enemy_Assets, enemy_count: ^int, game_map: Game_Map) {
@@ -92,7 +96,7 @@ spawn_enemy :: proc(enemy_array: ^[MAX_ENEMIES]Enemy, assets: ^Enemy_Assets, ene
for attempt := 0; !valid_pos && attempt < MAX_ATTEMPTS; attempt += 1 { for attempt := 0; !valid_pos && attempt < MAX_ATTEMPTS; attempt += 1 {
enemy_rect = get_spawn_rect(max_x, max_y) enemy_rect = get_spawn_rect(max_x, max_y)
valid_pos = !check_map_collision(enemy_rect, game_map) && !check_enemy_collision(enemy_rect, cnt, enemy_array) valid_pos = !check_map_collision(enemy_rect, game_map) && !check_enemy_collision(enemy_rect, -1, cnt, enemy_array)
} }
if !valid_pos { if !valid_pos {
return // couldn't find a spot this call, skip spawning return // couldn't find a spot this call, skip spawning
@@ -105,7 +109,7 @@ spawn_enemy :: proc(enemy_array: ^[MAX_ENEMIES]Enemy, assets: ^Enemy_Assets, ene
} }
} }
update_enemies :: proc(enemy_array: ^[MAX_ENEMIES]Enemy, assets: ^Enemy_Assets, enemy_cnt: int, game_map: Game_Map, dt: f32) { update_enemies :: proc(player: ^Player, enemy_array: ^[MAX_ENEMIES]Enemy, assets: ^Enemy_Assets, enemy_cnt: int, game_map: Game_Map, dt: f32) {
for idx in 0..<enemy_cnt { for idx in 0..<enemy_cnt {
enemy := &enemy_array[idx] enemy := &enemy_array[idx]
moving := false moving := false
@@ -115,19 +119,25 @@ update_enemies :: proc(enemy_array: ^[MAX_ENEMIES]Enemy, assets: ^Enemy_Assets,
enemy_grid_pos := world_to_grid(enemy_center) enemy_grid_pos := world_to_grid(enemy_center)
enemy_grid_index := grid_to_index(enemy_grid_pos.x, enemy_grid_pos.y, game_map.width) enemy_grid_index := grid_to_index(enemy_grid_pos.x, enemy_grid_pos.y, game_map.width)
// already normalized // get dir from flow field already normalized
flow_dir := game_map.flow_field[enemy_grid_index] flow_dir := game_map.flow_field[enemy_grid_index]
// wall avoidance steering // check around enemies for separation of neighbors
enemy_push := get_enemy_separation(idx, enemy_cnt, enemy_array)
wall_push := get_wall_separation(enemy, game_map)
steer_dir := rl.Vector2Normalize(flow_dir + enemy_push + wall_push)
target_velocity := steer_dir * enemy.speed
target_velocity := flow_dir * enemy.speed
enemy.velocity = rl.Vector2MoveTowards(enemy.velocity, target_velocity, enemy.accel * dt)
// Check for collisions // Check for collisions
// target_velocity := flow_dir * enemy.speed
enemy.velocity = rl.Vector2MoveTowards(enemy.velocity, target_velocity, enemy.accel * dt)
new_position := enemy.velocity * dt new_position := enemy.velocity * dt
new_x_pos := rl.Vector2{enemy.position.x + new_position.x, enemy.position.y} new_x_pos := rl.Vector2{enemy.position.x + new_position.x, enemy.position.y}
if if
!check_map_collision(get_enemy_collider(enemy^, new_x_pos), game_map) !check_map_collision(get_enemy_collider(enemy^, new_x_pos), game_map) &&
!check_enemy_collision(get_enemy_collider(enemy^, new_x_pos), idx, enemy_cnt, enemy_array) &&
!check_player_collision(get_enemy_collider(enemy^, new_x_pos), player)
{ {
enemy.position.x = new_x_pos.x enemy.position.x = new_x_pos.x
moving = true moving = true
@@ -136,9 +146,12 @@ update_enemies :: proc(enemy_array: ^[MAX_ENEMIES]Enemy, assets: ^Enemy_Assets,
moving = false moving = false
} }
new_y_pos := rl.Vector2{enemy.position.x, enemy.position.y + new_position.y} new_y_pos := rl.Vector2{enemy.position.x, enemy.position.y + new_position.y}
if if
!check_map_collision(get_enemy_collider(enemy^, new_y_pos), game_map) !check_map_collision(get_enemy_collider(enemy^, new_y_pos), game_map) &&
!check_enemy_collision(get_enemy_collider(enemy^, new_y_pos), idx, enemy_cnt, enemy_array) &&
!check_player_collision(get_enemy_collider(enemy^, new_y_pos), player)
{ {
enemy.position.y = new_y_pos.y enemy.position.y = new_y_pos.y
moving = true moving = true
@@ -150,10 +163,76 @@ update_enemies :: proc(enemy_array: ^[MAX_ENEMIES]Enemy, assets: ^Enemy_Assets,
enemy.state = moving ? .WALK : .IDLE enemy.state = moving ? .WALK : .IDLE
animation_play(&enemy.animation, &assets.clips[enemy.state]) animation_play(&enemy.animation, &assets.clips[enemy.state])
animation_update(&enemy.animation, dt) animation_update(&enemy.animation, dt)
} }
} }
get_wall_separation:: proc(enemy: ^Enemy, game_map: Game_Map) -> rl.Vector2 {
// how close another enemy needs to be before we push away from them
distance_check: f32 = TILE_SIZE
total_push := rl.Vector2{}
current_enemy_center := get_enemy_center(enemy^, enemy.position)
enemy_grid := world_to_grid(current_enemy_center)
min_grid := Grid_Pos(rl.Vector2{f32(enemy_grid.x - 1), f32(enemy_grid.y - 1)})
max_grid := Grid_Pos(rl.Vector2{f32(enemy_grid.x + 1), f32(enemy_grid.y + 1)})
for gy := min_grid[1]; gy <= max_grid[1]; gy += 1 {
for gx := min_grid[0]; gx <= max_grid[0]; gx += 1 {
// bounds check
if gx < 0 || gx >= game_map.width || gy < 0 || gy >= game_map.height do continue
idx := (gy * game_map.width) + gx
tile := Tile_Type(game_map.data[idx])
def := tile_defs[tile]
if def.solid {
tile_grid := grid_to_world(gx, gy)
tile_grid.x += TILE_SIZE / 2
tile_grid.y += TILE_SIZE / 2
distance_between := rl.Vector2Distance(tile_grid, current_enemy_center)
if distance_between < distance_check {
// vector pointing away from the other enemy, stronger the closer they are
// (not normalized — closer enemies naturally produce a bigger vector here)
total_push += (current_enemy_center - tile_grid)
}
}
}
}
return total_push
}
get_enemy_separation :: proc(self_idx: int, active_enemies: int, enemies: ^[MAX_ENEMIES]Enemy) -> rl.Vector2 {
// how close another enemy needs to be before we push away from them
distance_check: f32 = TILE_SIZE / 2
current_enemy := enemies[self_idx]
current_enemy_center := get_enemy_center(current_enemy, current_enemy.position)
// adds the combined push-away value of every nearby enemy
total_push := rl.Vector2{}
for idx in 0..<active_enemies {
if idx == self_idx do continue
other_enemy := enemies[idx]
other_enemy_center := get_enemy_center(other_enemy, other_enemy.position)
distance_between := rl.Vector2Distance(current_enemy_center, other_enemy_center)
if distance_between < distance_check {
// vector pointing away from the other enemy, stronger the closer they are
// (not normalized — closer enemies naturally produce a bigger vector here)
total_push += (current_enemy_center - other_enemy_center)
}
}
return total_push
}
draw_enemies :: proc(enemy_array: ^[MAX_ENEMIES]Enemy, enemy_cnt: int) { draw_enemies :: proc(enemy_array: ^[MAX_ENEMIES]Enemy, enemy_cnt: int) {
for idx in 0..<enemy_cnt { for idx in 0..<enemy_cnt {
+1 -1
View File
@@ -92,7 +92,7 @@ game_update :: proc(game: ^Game) {
update_camera(&game.player, &game.camera, game.current_map.width_px, game.current_map.height_px) update_camera(&game.player, &game.camera, game.current_map.width_px, game.current_map.height_px)
if update_timer(&game.enenmy_spawn_timer, dt) do spawn_enemy(&game.enemies, &game.assets.enemy, &game.enemy_count, game.current_map) if update_timer(&game.enenmy_spawn_timer, dt) do spawn_enemy(&game.enemies, &game.assets.enemy, &game.enemy_count, game.current_map)
update_enemies(&game.enemies, &game.assets.enemy, game.enemy_count, game.current_map, dt) update_enemies(&game.player, &game.enemies, &game.assets.enemy, game.enemy_count, game.current_map, dt)
} }
// @ DEBUG // @ DEBUG
+2 -1
View File
@@ -184,8 +184,9 @@ check_map_collision :: proc(rect: rl.Rectangle, game_map: Game_Map) -> bool {
} }
// true when collision found // true when collision found
check_enemy_collision :: proc(rect_a: rl.Rectangle, active_enemies: int, enemies: ^[MAX_ENEMIES]Enemy) -> bool { check_enemy_collision :: proc(rect_a: rl.Rectangle, self_idx: int, active_enemies: int, enemies: ^[MAX_ENEMIES]Enemy) -> bool {
for idx in 0..<active_enemies { for idx in 0..<active_enemies {
if idx == self_idx do continue
e := enemies[idx] e := enemies[idx]
enemy_rect := get_enemy_collider(e, e.position) enemy_rect := get_enemy_collider(e, e.position)