diff --git a/assets/textures/enemy/player_idle.png b/assets/textures/enemy/player_idle.png new file mode 100644 index 0000000..d9be6af Binary files /dev/null and b/assets/textures/enemy/player_idle.png differ diff --git a/assets/textures/enemy/player_walk.png b/assets/textures/enemy/player_walk.png new file mode 100644 index 0000000..105f2fa Binary files /dev/null and b/assets/textures/enemy/player_walk.png differ diff --git a/src/asset_manager.odin b/src/asset_manager.odin index df95f5c..4cfddaf 100644 --- a/src/asset_manager.odin +++ b/src/asset_manager.odin @@ -12,13 +12,19 @@ Player_Assets :: struct { clips: [Player_State]AnimationClip, } +Enemy_Assets :: struct { + clips: [Enemy_State]AnimationClip, +} + Asset_Manager :: struct { player: Player_Assets, + enemy: Enemy_Assets, } asset_manager_init :: proc() -> 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 } @@ -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) { for clip in assets.player.clips { rl.UnloadTexture(clip.texture) diff --git a/src/debug.odin b/src/debug.odin index f01a9db..b20d809 100644 --- a/src/debug.odin +++ b/src/debug.odin @@ -22,7 +22,15 @@ debug_init :: proc(game: ^Game) { debug_fps :: proc() { when DEBUG_MODE { 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 @@ -82,6 +90,13 @@ debug_bounds :: proc(game: ^Game) { 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.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) + } } } } diff --git a/src/enemy.odin b/src/enemy.odin index 17e1ba0..b4b26ec 100644 --- a/src/enemy.odin +++ b/src/enemy.odin @@ -1,24 +1,115 @@ package game +import "core:math/rand" import rl "vendor:raylib" -Enemy :: struct { - position: rl.Vector2, +Enemy_State :: enum { IDLE, WALK, ATTACK } +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, - 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 } -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) { - rl.DrawCircle(i32(enemy.position.x), i32(enemy.position.y), 10, rl.RED) +get_spawn_rect :: proc(max_x, max_y: f32) -> rl.Rectangle { + // 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) + } } diff --git a/src/main.odin b/src/main.odin index 4c4d372..aac5f17 100644 --- a/src/main.odin +++ b/src/main.odin @@ -7,14 +7,15 @@ import "core:mem" // ----------------------------------------------------------------------------- DEBUG_MODE :: true +WINDOW_TITLE :: "Graveyard" WINDOW_WIDTH :: 1920 WINDOW_HEIGHT :: 1080 GAME_WIDTH :: 480 GAME_HEIGHT :: 270 - -WINDOW_TITLE :: "Graveyard" -PLAYER_SPEED :: 180 +MAX_ENEMIES :: 140 +MAX_PROPS :: 64 TILE_SIZE :: 32 + // ----------------------------------------------------------------------------- main :: proc() { diff --git a/src/map.odin b/src/map.odin index 50f41c5..c9964b6 100644 --- a/src/map.odin +++ b/src/map.odin @@ -171,6 +171,21 @@ check_map_collision :: proc(rect: rl.Rectangle, game_map: Game_Map) -> bool { 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..