Initial enemy setup, with collisions and basic spawning.

Modified-by: Jason Hilder <jhilder95@gmail.com>
This commit is contained in:
2026-07-23 08:34:46 +02:00
parent 2282947c82
commit 5da084391e
7 changed files with 153 additions and 13 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

+19 -1
View File
@@ -12,13 +12,19 @@ Player_Assets :: struct {
clips: [Player_State]AnimationClip, clips: [Player_State]AnimationClip,
} }
Enemy_Assets :: struct {
clips: [Enemy_State]AnimationClip,
}
Asset_Manager :: struct { Asset_Manager :: struct {
player: Player_Assets, player: Player_Assets,
enemy: Enemy_Assets,
} }
asset_manager_init :: proc() -> Asset_Manager { asset_manager_init :: proc() -> Asset_Manager {
assets := Asset_Manager{} assets := Asset_Manager{}
init_player_assets(&assets.player.clips); init_player_assets(&assets.player.clips)
init_enemy_assets(&assets.enemy.clips)
return assets return assets
} }
@@ -35,6 +41,18 @@ init_player_assets :: proc(clip_array: ^[Player_State]AnimationClip) {
} }
} }
init_enemy_assets :: proc(clip_array: ^[Enemy_State]AnimationClip) {
idle_tex := rl.LoadTexture("assets/textures/enemy/player_idle.png")
walk_tex := rl.LoadTexture("assets/textures/enemy/player_walk.png")
clip_array[.IDLE] = AnimationClip {
texture = idle_tex, frame_count = 6, frame_width = 32, frame_height = 32, frame_time = 0.14, looping = true,
}
clip_array[.WALK] = AnimationClip {
texture = walk_tex, frame_count = 8, frame_width = 32, frame_height = 32, frame_time = 0.14, looping = true,
}
}
asset_manager_destroy :: proc(assets: ^Asset_Manager) { asset_manager_destroy :: proc(assets: ^Asset_Manager) {
for clip in assets.player.clips { for clip in assets.player.clips {
rl.UnloadTexture(clip.texture) rl.UnloadTexture(clip.texture)
+15
View File
@@ -22,7 +22,15 @@ debug_init :: proc(game: ^Game) {
debug_fps :: proc() { debug_fps :: proc() {
when DEBUG_MODE { when DEBUG_MODE {
rl.DrawFPS(20, 20) rl.DrawFPS(20, 20)
for i in i32(0)..<4 {
if rl.IsGamepadAvailable(i) {
rl.DrawText(rl.TextFormat("GP%d: %s", i, rl.GetGamepadName(i)), 20, 45 + i*20, 20, rl.BLACK)
} else {
rl.DrawText(rl.TextFormat("GP%d: NOT DETECTED", i), 20, 45 + i*20, 20, rl.GRAY)
} }
}
}
} }
// DEBUG CAMERA // DEBUG CAMERA
@@ -82,6 +90,13 @@ debug_bounds :: proc(game: ^Game) {
player_collider := get_player_collider(&game.player, game.player.position) player_collider := get_player_collider(&game.player, game.player.position)
rl.DrawRectangleLines( i32(player.position.x), i32(player.position.y), TILE_SIZE, TILE_SIZE, rl.GREEN) rl.DrawRectangleLines( i32(player.position.x), i32(player.position.y), TILE_SIZE, TILE_SIZE, rl.GREEN)
rl.DrawRectangleLinesEx(player_collider, 1, rl.BLUE) rl.DrawRectangleLinesEx(player_collider, 1, rl.BLUE)
// enemies
for &e in game.enemies {
enemy_collider := get_enemy_collider(e, e.position)
rl.DrawRectangleLinesEx(enemy_collider, 1, rl.BLACK)
rl.DrawRectangleLines( i32(e.position.x), i32(e.position.y), TILE_SIZE, TILE_SIZE, rl.MAROON)
}
} }
} }
} }
+100 -9
View File
@@ -1,24 +1,115 @@
package game package game
import "core:math/rand"
import rl "vendor:raylib" import rl "vendor:raylib"
Enemy :: struct { Enemy_State :: enum { IDLE, WALK, ATTACK }
position: rl.Vector2, Enemy_Type :: enum { SKELETON, ZOMBIE, GHOST }
Enemy :: struct {
// info
type: Enemy_Type,
position: rl.Vector2,
state: Enemy_State,
animation: AnimationState,
flipped: bool,
// movement
velocity: rl.Vector2, velocity: rl.Vector2,
speed: f32, // max move speed speed: f32,
// collision
collider_offset: rl.Vector2,
collider_w: f32,
collider_h: f32
} }
enemy_init :: proc() -> Enemy {
enemy := Enemy{}
enemy.position = {300, 300} enemy_init :: proc(assets: ^Enemy_Assets, pos: [2]f32) -> Enemy {
enemy := Enemy{}
enemy.position = {pos.x, pos.y}
enemy.state = .IDLE
enemy.collider_offset = rl.Vector2{8, 15}
enemy.collider_w = 16
enemy.collider_h = 10
animation_play(&enemy.animation, &assets.clips[.IDLE])
return enemy return enemy
} }
update_enemy :: proc() { } get_enemy_collider :: proc(enemy: Enemy, pos: rl.Vector2) -> rl.Rectangle {
return rl.Rectangle{
(pos.x + enemy.collider_offset.x),
pos.y + enemy.collider_offset.y,
enemy.collider_w,
enemy.collider_h,
}
}
draw_enemy :: proc(enemy: Enemy) { get_spawn_rect :: proc(max_x, max_y: f32) -> rl.Rectangle {
rl.DrawCircle(i32(enemy.position.x), i32(enemy.position.y), 10, rl.RED) // Get random x and y pos
x_pos := rand.float32_range(TILE_SIZE, (max_x - TILE_SIZE))
y_pos := rand.float32_range(TILE_SIZE, (max_y - TILE_SIZE))
return rl.Rectangle{x_pos, y_pos, TILE_SIZE, TILE_SIZE}
}
// @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) {
max_x := game_map.width_px
max_y := game_map.height_px
cnt := enemy_count^
// Can only have Max amount of enemies
if cnt < MAX_ENEMIES {
MAX_ATTEMPTS :: 30
enemy_rect := rl.Rectangle{}
valid_pos := false
for attempt := 0; !valid_pos && attempt < MAX_ATTEMPTS; attempt += 1 {
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)
}
if !valid_pos {
return // couldn't find a spot this call, skip spawning
}
// init the enemy there in .idle state
enemy_array[cnt] = enemy_init(assets, {enemy_rect.x, enemy_rect.y})
cnt += 1
enemy_count^ = cnt
}
}
update_enemies :: proc(enemy_array: ^[MAX_ENEMIES]Enemy, assets: ^Enemy_Assets) {
for &enemy in enemy_array {
moving := false
dt := rl.GetFrameTime()
enemy.state = moving ? .WALK : .IDLE
animation_play(&enemy.animation, &assets.clips[enemy.state])
animation_update(&enemy.animation, dt)
}
}
draw_enemies :: proc(enemy_array: ^[MAX_ENEMIES]Enemy) {
for &enemy in enemy_array {
clip := enemy.animation.clip
src_rect := animation_frame_rect(&enemy.animation)
dst_rect := rl.Rectangle{
x = enemy.position.x,
y = enemy.position.y,
width = f32(clip.frame_width),
height = f32(clip.frame_height),
}
rl.DrawTexturePro(clip.texture, src_rect, dst_rect, rl.Vector2{}, 0, rl.WHITE)
}
} }
+4 -3
View File
@@ -7,14 +7,15 @@ import "core:mem"
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
DEBUG_MODE :: true DEBUG_MODE :: true
WINDOW_TITLE :: "Graveyard"
WINDOW_WIDTH :: 1920 WINDOW_WIDTH :: 1920
WINDOW_HEIGHT :: 1080 WINDOW_HEIGHT :: 1080
GAME_WIDTH :: 480 GAME_WIDTH :: 480
GAME_HEIGHT :: 270 GAME_HEIGHT :: 270
MAX_ENEMIES :: 140
WINDOW_TITLE :: "Graveyard" MAX_PROPS :: 64
PLAYER_SPEED :: 180
TILE_SIZE :: 32 TILE_SIZE :: 32
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
main :: proc() { main :: proc() {
+15
View File
@@ -171,6 +171,21 @@ check_map_collision :: proc(rect: rl.Rectangle, game_map: Game_Map) -> bool {
return false return false
} }
// true when collision found
check_enemy_collision :: proc(rect_a: rl.Rectangle, active_enemies: int, enemies: ^[MAX_ENEMIES]Enemy) -> bool {
for idx in 0..<active_enemies {
e := enemies[idx]
enemy_rect := get_enemy_collider(e, e.position)
if rl.CheckCollisionRecs(rect_a, enemy_rect) {
return true
}
}
return false
}
draw_map :: proc(current_map: Game_Map) { draw_map :: proc(current_map: Game_Map) {
for value, index in current_map.data { for value, index in current_map.data {
row := index / current_map.width row := index / current_map.width