From 8f596735e1a530870e3d161bed21fa66fa73078e Mon Sep 17 00:00:00 2001 From: Alexey Bagno Date: Sun, 2 Aug 2026 17:21:07 +0300 Subject: [PATCH] handlemap rewrite init --- src/enemy.odin | 49 +++--- src/gfx.odin | 59 ++++--- src/item.odin | 31 ++-- src/main.odin | 106 +++---------- src/player.odin | 63 ++++---- src/scene.odin | 398 +++++++++++++++++++++++++++++------------------- src/ui.odin | 28 ++-- 7 files changed, 383 insertions(+), 351 deletions(-) diff --git a/src/enemy.odin b/src/enemy.odin index 6e1386e..b3a1303 100644 --- a/src/enemy.odin +++ b/src/enemy.odin @@ -3,36 +3,25 @@ package main import hm "core:container/handle_map" import rl "vendor:raylib" -Enemy :: struct { - using actor: Actor, - touch_dmg: i32, +// ИИ врага — летит к игроку. Вызывается из диспатчера sys_update +enemy_update :: proc(state: ^GameState, e: ^Enemy, h: Handle, dt: f32) { + pe := get_entity(state, state.player) + if pe == nil do return + + dir := get_base(pe).pos - e.pos + set_vel(&e.base, dir, e.speed) } -enemy_update :: proc(state: ^GameState, p, e: ^Entity) { - p := hm.get(&state.entities, state.player) +// Мёртвые враги → лут +sys_death :: proc(state: ^GameState) { + it := hm.iterator_make(&state.entities) + for e, _ in hm.iterate(&it) { + #partial switch &v in e.v { + case Enemy: + if v.hp <= 0 && !v.marked { + v.marked = true + spawn(state, make_ground_item(v.pos)) + } + } + } } - -// sys_enemy_ai :: proc(state: ^GameState) { -// for &enemy in &state.enemies { -// dir := state.player.pos - enemy.pos -// set_vel(cast(^Entity2)&enemy, dir, enemy.speed) -// } -// } -// -// sys_death :: proc(state: ^GameState3) { -// for &enemy in &state.enemies { -// if enemy.hp > 0 || enemy.marked do continue -// enemy.marked = true -// init_entity( -// state, -// GroundItem3 { -// ent = Entity2 { -// GroundItem3 = enemy.pos, -// GroundItem3 = {GroundItem3 = GfxCircle{GroundItem3 = {0, 0}, GroundItem3 = 6, GroundItem3 = {255, 220, 50, 255}}}, -// GroundItem3 = 10, -// }, -// heal = 25, -// }, -// ) -// } -// } diff --git a/src/gfx.odin b/src/gfx.odin index 477536a..31c90ce 100644 --- a/src/gfx.odin +++ b/src/gfx.odin @@ -1,5 +1,6 @@ package main +import hm "core:container/handle_map" import rl "vendor:raylib" Graphics :: union { @@ -31,6 +32,7 @@ GfxRing :: struct { outer_radius: f32, color: rl.Color, } + GfxCircleLines :: struct { pos: rl.Vector2, radius: f32, @@ -38,16 +40,7 @@ GfxCircleLines :: struct { } GfxTexture :: struct { - // TODO: Implement texure rendering -} - -FX :: struct { - parentId: EntityID, - lifetime: f32, - permanent: bool, - pos: rl.Vector2, - gfx: [MAX_GFX]Graphics, - marked: bool, + // TODO: Implement texture rendering } gfx_draw :: proc(pos: rl.Vector2, g: Graphics) { @@ -63,14 +56,41 @@ gfx_draw :: proc(pos: rl.Vector2, g: Graphics) { } } -fx_update :: proc(state: ^GameState3, fx: ^FX, dt: f32) { +// ─── FX ─── + +FX :: struct { + handle: Handle, + parent: Handle, // 0 = свободно плавает + pos: rl.Vector2, + gfx: [MAX_GFX]Graphics, + lifetime: f32, + permanent: bool, + marked: bool, +} + +init_fx :: proc(state: ^GameState, fx: FX) -> Handle { + return hm.add(&state.fxs, fx) +} + +fx_update :: proc(state: ^GameState, fx: ^FX, dt: f32) { if fx.marked do return - if fx.parentId != 0 { - parent, ok := get_ent_by_id(state, fx.parentId) - if ok do fx.pos = parent.pos + + // следуем за родителем, пока жив + if fx.parent != {} { + if e := get_entity(state, fx.parent); e != nil { + fx.pos = get_base(e).pos + } else { + fx.marked = true // родитель умер + return + } } + if !fx.permanent { - if fx.lifetime > 0 {fx.lifetime -= dt} else {fx.marked = true} + if fx.lifetime > 0 { + fx.lifetime -= dt + } else { + fx.marked = true + } } } @@ -80,8 +100,9 @@ fx_draw :: proc(fx: ^FX) { } } -init_fx :: proc(state: ^GameState3, fx: FX) { - append(&state.fxs, fx) +sys_fx_update :: proc(state: ^GameState, dt: f32) { + it := hm.iterator_make(&state.fxs) + for fx, _ in hm.iterate(&it) { + fx_update(state, fx, dt) + } } - -//TODO: Создание и удаление FX с лайфтаймами diff --git a/src/item.odin b/src/item.odin index be09781..6fb9966 100644 --- a/src/item.odin +++ b/src/item.odin @@ -1,17 +1,24 @@ package main -GroundItem3 :: struct { - using ent: Entity2, - heal: i32, -} +// Пикап лута игроком — по коллизионным парам (как урон) +sys_pickup :: proc(state: ^GameState) { + _, p := get_player(state) + if p == nil do return -sys_pickup :: proc(state: ^GameState3) { - for &item in &state.items { - if item.marked do continue - _, ok := check_col(cast(^Entity2)&item, cast(^Entity2)&state.player) - if ok { - state.player.hp = min(state.player.hp + item.heal, state.player.max_hp) - item.marked = true - } + for c in state.collisions { + other_h: Handle + if c.a == state.player { + other_h = c.b + } else if c.b == state.player { + other_h = c.a + } else do continue + + e := get_entity(state, other_h) + if e == nil do continue + gi, ok := &e.v.(GroundItem) + if !ok || gi.marked do continue + + p.hp = min(p.hp + gi.heal, p.max_hp) + gi.marked = true } } diff --git a/src/main.odin b/src/main.odin index 3e5fc33..919f6e0 100644 --- a/src/main.odin +++ b/src/main.odin @@ -1,7 +1,5 @@ package main -import hm "core:container/handle_map" -import "core:fmt" import rl "vendor:raylib" main :: proc() { @@ -9,100 +7,32 @@ main :: proc() { rl.SetTargetFPS(TARGET_FPS) defer rl.CloseWindow() - state := GameState { + state := GameState{ camera = {zoom = 1.5, offset = {SCREEN_W / 2, SCREEN_H / 2}}, } + state.collisions = make([dynamic]CollisionPair) + defer delete(state.collisions) - p := hm.add( - &state.entities, - Entity { - pos = {400, 300}, - col_size = 16, - variant = Player { - actor = Actor { - hp = 100, - max_hp = 100, - speed = 220, - gfx = {0 = GfxCircle{pos = {0, 0}, radius = 14, color = {80, 220, 100, 255}}}, - }, - attack_range = 75, - attack_dmg = 35, - attack_cd = 0.3, - invuln_timer = 0, - }, - }, - ) - - state.player = p - - //TODO: Коллизия - - // state.collisions = make([dynamic]CollisionEntry) - // defer delete(state.collisions) - - hm.add( - &state.entities, - Entity { - pos = {600, 500}, - col_size = 16, - variant = Enemy { - actor = Actor { - hp = 40, - max_hp = 40, - speed = 55, - gfx = {0 = GfxCircle{pos = {0, 0}, radius = 11, color = {220, 70, 70, 255}}}, - }, - }, - }, - ) - - hm.add( - &state.entities, - Entity { - pos = {200, 500}, - col_size = 16, - variant = Enemy { - actor = Actor { - hp = 40, - max_hp = 40, - speed = 55, - gfx = {0 = GfxCircle{pos = {0, 0}, radius = 11, color = {220, 70, 70, 255}}}, - }, - }, - }, - ) - - hm.add( - &state.entities, - Entity { - pos = {600, 500}, - col_size = 16, - variant = Enemy { - actor = Actor { - hp = 40, - max_hp = 40, - speed = 55, - gfx = {0 = GfxCircle{pos = {0, 0}, radius = 11, color = {220, 70, 70, 255}}}, - }, - }, - }, - ) - + state.player = spawn(&state, make_player()) + spawn(&state, make_enemy({600, 500})) + spawn(&state, make_enemy({200, 500})) + spawn(&state, make_enemy({600, 100})) for !rl.WindowShouldClose() { dt := rl.GetFrameTime() - sys_input(&state) - sys_enemy_ai(&state) - sys_move(&state, dt) - sys_collisions(&state) - sys_damage(&state) - sys_death(&state) - sys_pickup(&state) - sys_update(&state, dt) - sys_sweep(&state) + sys_update(&state, dt) // вход игрока + ИИ врагов (диспатчер) + sys_move(&state, dt) // движение всех + sys_collisions(&state) // пары + резолв + sys_damage(&state) // дамаг игроку от врагов + sys_death(&state) // враги умерли → лут + sys_pickup(&state) // игрок подбирает лут + sys_fx_update(&state, dt) // FX: таймеры + следование + sys_sweep(&state) // удалить помеченное - state.camera.target = state.player.pos + if pe := get_entity(&state, state.player); pe != nil { + state.camera.target = get_base(pe).pos + } sys_render(&state) } } diff --git a/src/player.odin b/src/player.odin index e453457..d3b2562 100644 --- a/src/player.odin +++ b/src/player.odin @@ -1,48 +1,41 @@ package main +import hm "core:container/handle_map" import rl "vendor:raylib" -Player2 :: struct { - using actor: Actor, - attack_range: f32, - attack_dmg: i32, - attack_cd: f32, - attack_timer: f32, - invuln_timer: f32, -} - -sys_input :: proc(state: ^GameState3) { - p := &state.player - +// Вход и атака игрока — вызывается из диспатчера sys_update +player_update :: proc(state: ^GameState, p: ^Player, h: Handle, dt: f32) { + // движение dir: rl.Vector2 - if rl.IsKeyDown(.W) || rl.IsKeyDown(.UP) do dir.y -= 1 - if rl.IsKeyDown(.S) || rl.IsKeyDown(.DOWN) do dir.y += 1 - if rl.IsKeyDown(.A) || rl.IsKeyDown(.LEFT) do dir.x -= 1 + if rl.IsKeyDown(.W) || rl.IsKeyDown(.UP) do dir.y -= 1 + if rl.IsKeyDown(.S) || rl.IsKeyDown(.DOWN) do dir.y += 1 + if rl.IsKeyDown(.A) || rl.IsKeyDown(.LEFT) do dir.x -= 1 if rl.IsKeyDown(.D) || rl.IsKeyDown(.RIGHT) do dir.x += 1 - set_vel(cast(^Entity2)p, dir, p.speed) + set_vel(&p.base, dir, p.speed) - if p.attack_timer > 0 do p.attack_timer -= rl.GetFrameTime() + // атака + if p.attack_timer > 0 do p.attack_timer -= dt if rl.IsKeyPressed(.SPACE) && p.attack_timer <= 0 { p.attack_timer = p.attack_cd - init_fx( - state, - FX { - parentId = p.id, - lifetime = p.attack_cd, - permanent = false, - gfx = { - 0 = GfxRing { - inner_radius = p.attack_range * 0.7, - outer_radius = p.attack_range, - color = {255, 255, 255, 40}, - }, - 1 = GfxCircleLines{radius = p.attack_range, color = {255, 255, 255, 80}}, - }, + + // визуал атаки — кольцо + init_fx(state, FX{ + parent = h, + gfx = { + 0 = GfxRing{inner_radius = p.attack_range * 0.7, outer_radius = p.attack_range, color = {255, 255, 255, 40}}, + 1 = GfxCircleLines{radius = p.attack_range, color = {255, 255, 255, 80}}, }, - ) - for &enemy in &state.enemies { - if dist(Entity2(p), Entity2(enemy)) <= p.attack_range { - enemy.hp -= p.attack_dmg + lifetime = p.attack_cd, + }) + + // дамаг врагам в радиусе + it := hm.iterator_make(&state.entities) + for e, _ in hm.iterate(&it) { + #partial switch &v in e.v { + case Enemy: + if rl.Vector2Distance(p.pos, v.pos) <= p.attack_range { + v.hp -= p.attack_dmg + } } } } diff --git a/src/scene.odin b/src/scene.odin index 1cd1548..0592c07 100644 --- a/src/scene.odin +++ b/src/scene.odin @@ -7,47 +7,34 @@ SCREEN_W :: 1280 SCREEN_H :: 720 TARGET_FPS :: 60 SQRT2_INV :: 0.7071067811865476 - -// ─── Базовые типы ─── - MAX_GFX :: 2 -MAX_ENTITIES :: 1024 Handle :: hm.Handle32 -// ─── Коллизии ─── +MAX_ENTITIES :: 1024 +MAX_FX :: 256 -// EntityKind :: enum { -// Player, -// Enemy, -// } +// ─── Общее — всё что есть у любой сущности ─── -// CollisionEntry :: struct { -// a_kind: EntityKind, -// a_idx: int, -// b_kind: EntityKind, -// b_idx: int, -// } - -// ─── Игровое состояние ─── - - -Entity :: struct { - handle: Handle, +BaseEntity :: struct { pos: rl.Vector2, + vel: rl.Vector2, col_size: f32, marked: bool, - v: EntityVariant, + gfx: [MAX_GFX]Graphics, } +// ─── Акторское — то что есть у живых ─── + Actor :: struct { - vel: rl.Vector2, - hp: i32, - max_hp: i32, - speed: f32, - gfx: [MAX_GFX]Graphics, + using base: BaseEntity, + hp: i32, + max_hp: i32, + speed: f32, } +// ─── Конкретные типы ─── + Player :: struct { using actor: Actor, attack_range: f32, @@ -57,156 +44,272 @@ Player :: struct { invuln_timer: f32, } +Enemy :: struct { + using actor: Actor, + touch_dmg: i32, +} GroundItem :: struct { - heal: i32, - gfx: [MAX_GFX]Graphics, + using base: BaseEntity, + heal: i32, } -EntityVariant :: union { - Player, - Enemy, - GroundItem, +EntityVariant :: union { Player, Enemy, GroundItem } + +// ─── Контейнер для handle_map ─── + +Entity :: struct { + handle: Handle, + v: EntityVariant, } +// ─── Состояние игры ─── + GameState :: struct { - player: Handle, - camera: rl.Camera2D, - entities: hm.Static_Handle_Map(MAX_ENTITIES, Entity, Handle), - // particles: hm.Static_Handle_Map(MAX_PARTICLE_FX, Fx2, Handle), + player: Handle, + camera: rl.Camera2D, + entities: hm.Static_Handle_Map(MAX_ENTITIES, Entity, Handle), + fxs: hm.Static_Handle_Map(MAX_FX, FX, Handle), + collisions: [dynamic]CollisionPair, } +// ─── Доступ ─── -// ─── Хелперы ─── +get_entity :: proc(state: ^GameState, h: Handle) -> ^Entity { + e, ok := hm.get(&state.entities, h) + return e if ok else nil +} -set_vel :: #force_inline proc(e: ^Entity, dir: rl.Vector2, speed: f32) { - if rl.Vector2Length(dir) > 0 { - e.v.vel = rl.Vector2Normalize(dir) * speed - } else { - e.v.vel = {} +// Общее из любой сущности (BaseEntity первым полем во всех вариантах) +get_base :: proc(e: ^Entity) -> ^BaseEntity { + #partial switch &v in e.v { + case Player: + return cast(^BaseEntity)&v + case Enemy: + return cast(^BaseEntity)&v + case GroundItem: + return cast(^BaseEntity)&v + } + return nil +} + +// Акторское (есть только у живых) +get_actor :: proc(e: ^Entity) -> ^Actor { + #partial switch &v in e.v { + case Player: + return &v.actor + case Enemy: + return &v.actor + } + return nil +} + +get_player :: proc(state: ^GameState) -> (^Entity, ^Player) { + e := get_entity(state, state.player) + if e == nil do return nil, nil + p, ok := &e.v.(Player) + if !ok do return nil, nil + return e, p +} + +// ─── Фабрики ─── + +make_player :: proc() -> Entity { + return Entity{ + v = Player{ + actor = Actor{ + base = BaseEntity{ + pos = {400, 300}, col_size = 16, + gfx = {0 = GfxCircle{pos = {}, radius = 14, color = {80, 220, 100, 255}}}, + }, + hp = 100, max_hp = 100, speed = 220, + }, + attack_range = 75, + attack_dmg = 35, + attack_cd = 0.3, + }, } } -move_ent :: #force_inline proc(e: ^Entity, dt: f32) { - e.v.pos += e.v.vel * dt +make_enemy :: proc(pos: rl.Vector2) -> Entity { + return Entity{ + v = Enemy{ + actor = Actor{ + base = BaseEntity{ + pos = pos, col_size = 16, + gfx = {0 = GfxCircle{pos = {}, radius = 11, color = {220, 70, 70, 255}}}, + }, + hp = 40, max_hp = 40, speed = 55, + }, + touch_dmg = 10, + }, + } } -dist :: #force_inline proc(a, b: Entity) -> f32 { - return rl.Vector2Distance(a.v.pos, b.v.pos) +make_ground_item :: proc(pos: rl.Vector2) -> Entity { + return Entity{ + v = GroundItem{ + base = BaseEntity{ + pos = pos, col_size = 10, + gfx = {0 = GfxCircle{pos = {}, radius = 6, color = {255, 220, 50, 255}}}, + }, + heal = 25, + }, + } } -// entity_rect :: #force_inline proc(e: ^Entity2) -> rl.Rectangle { -// return rl.Rectangle{e.pos.x - e.col_size / 2, e.pos.y - e.col_size / 2, e.col_size, e.col_size} -// } -// -// check_col :: proc(a, b: ^Entity2) -> (overlap: rl.Rectangle, colliding: bool) { -// c := rl.GetCollisionRec(entity_rect(a), entity_rect(b)) -// if c.width <= 0 || c.height <= 0 do return rl.Rectangle{}, false -// return c, true -// } -// -// resolve_aabb :: proc(a, b: ^Entity2) -> bool { -// c, ok := check_col(a, b) -// if !ok do return false -// -// if c.width < c.height { -// sign: f32 = 1 if a.pos.x < b.pos.x else -1 -// a.pos.x -= sign * c.width * 0.5 -// b.pos.x += sign * c.width * 0.5 -// } else { -// sign: f32 = 1 if a.pos.y < b.pos.y else -1 -// a.pos.y -= sign * c.height * 0.5 -// b.pos.y += sign * c.height * 0.5 -// } -// return true -// } +spawn :: proc(state: ^GameState, e: Entity) -> Handle { + return hm.add(&state.entities, e) +} -// ─── Общие системы ─── +// ─── Хелперы ─── -sys_move :: proc(state: ^GameState, dt: f32) { +set_vel :: proc(b: ^BaseEntity, dir: rl.Vector2, speed: f32) { + if rl.Vector2Length(dir) > 0 { + b.vel = rl.Vector2Normalize(dir) * speed + } else { + b.vel = {} + } +} + +// ─── Коллизия ─── + +entity_rect :: #force_inline proc(b: ^BaseEntity) -> rl.Rectangle { + return rl.Rectangle{b.pos.x - b.col_size / 2, b.pos.y - b.col_size / 2, b.col_size, b.col_size} +} + +check_col :: proc(a, b: ^BaseEntity) -> (overlap: rl.Rectangle, colliding: bool) { + c := rl.GetCollisionRec(entity_rect(a), entity_rect(b)) + if c.width <= 0 || c.height <= 0 do return rl.Rectangle{}, false + return c, true +} + +resolve_aabb :: proc(a, b: ^BaseEntity) -> bool { + c, ok := check_col(a, b) + if !ok do return false + + if c.width < c.height { + sign: f32 = 1 if a.pos.x < b.pos.x else -1 + a.pos.x -= sign * c.width * 0.5 + b.pos.x += sign * c.width * 0.5 + } else { + sign: f32 = 1 if a.pos.y < b.pos.y else -1 + a.pos.y -= sign * c.height * 0.5 + b.pos.y += sign * c.height * 0.5 + } + return true +} + +// ─── Диспатчер: один свитч, типизированные функции ─── + +sys_update :: proc(state: ^GameState, dt: f32) { it := hm.iterator_make(&state.entities) - for e, _ in hm.iterate(&it) { + for e, h in hm.iterate(&it) { #partial switch &v in e.v { case Player: - e.pos += v.vel * dt + player_update(state, &v, h, dt) case Enemy: - e.pos += v.vel * dt + enemy_update(state, &v, h, dt) + case GroundItem: + // пока статичный } } } -// sys_collisions :: proc(state: ^GameState3) { -// clear(&state.collisions) -// -// p_ent := cast(^Entity2)&state.player -// -// for &enemy, i in &state.enemies { -// if resolve_aabb(p_ent, cast(^Entity2)&enemy) { -// append( -// &state.collisions, -// CollisionEntry{a_kind = .Player, a_idx = 0, b_kind = .Enemy, b_idx = i}, -// ) -// } -// } -// -// for i in 0 ..< len(state.enemies) { -// for j in i + 1 ..< len(state.enemies) { -// resolve_aabb(cast(^Entity2)&state.enemies[i], cast(^Entity2)&state.enemies[j]) -// } -// } -// } +// ─── Общее движение ─── +sys_move :: proc(state: ^GameState, dt: f32) { + it := hm.iterator_make(&state.entities) + for e, _ in hm.iterate(&it) { + b := get_base(e) + b.pos += b.vel * dt + } +} + +// ─── Коллизии ─── + +CollisionPair :: struct { + a: Handle, + b: Handle, +} + +sys_collisions :: proc(state: ^GameState) { + clear(&state.collisions) + + // собрать все живые handle'ы + hs: [MAX_ENTITIES]Handle + n := 0 + it := hm.iterator_make(&state.entities) + for _, h in hm.iterate(&it) { + hs[n] = h + n += 1 + } + + // O(n²): резолвим пересечения и собираем пары + for i in 0 ..< n { + for j in i + 1 ..< n { + a := get_entity(state, hs[i]) + b := get_entity(state, hs[j]) + if resolve_aabb(get_base(a), get_base(b)) { + append(&state.collisions, CollisionPair{a = hs[i], b = hs[j]}) + } + } + } +} + +// Дамаг игроку от врагов по коллизионным парам sys_damage :: proc(state: ^GameState) { - p := hm.get(&state.entities, state.player) + _, p := get_player(state) + if p == nil do return if p.invuln_timer > 0 { p.invuln_timer -= rl.GetFrameTime() return } - // for c in state.collisions { - // if c.a_kind == .Player && c.b_kind == .Enemy { - // state.player.hp -= state.enemies[c.b_idx].touch_dmg - // state.player.invuln_timer = 0.5 - // if state.player.hp <= 0 { - // state.player.hp = 0 - // } - // } - // } + for c in state.collisions { + other_h: Handle + if c.a == state.player { + other_h = c.b + } else if c.b == state.player { + other_h = c.a + } else do continue + + e := get_entity(state, other_h) + if e == nil do continue + ev, ok := &e.v.(Enemy) + if !ok do continue + + p.hp -= ev.touch_dmg + p.invuln_timer = 0.5 + if p.hp <= 0 do p.hp = 0 + break // один урон за кадр, дальше неуязвим + } } -//TODO: УБрать вообще? мапа фиксит инвалидацию поинтеров -sys_sweep :: proc(state: ^GameState) { +// ─── Очистка помеченного ─── +sys_sweep :: proc(state: ^GameState) { it := hm.iterator_make(&state.entities) - for e, i in hm.iterate(&it) { - if e.marked do hm.remove(&state.entities, i) + for e, h in hm.iterate(&it) { + if get_base(e).marked { + hm.remove(&state.entities, h) + } } - // i = 0 - // for i < len(state.fxs) { - // if state.fxs[i].marked { - // ordered_remove(&state.fxs, i) - // } else { - // i += 1 - // } - // } + it2 := hm.iterator_make(&state.fxs) + for fx, h in hm.iterate(&it2) { + if fx.marked { + hm.remove(&state.fxs, h) + } + } } -// entity_draw :: proc(e: ^Entity2) { -// for g in e.gfx { -// gfx_draw(e.pos, g) -// } -// } -// -sys_update :: proc(state: ^GameState, dt: f32) { - p := hm.get(&state.entities, state.player) +// ─── Рендер ─── - it := hm.iterator_make(&state.entities) - for e, _ in hm.iterate(&it) { - #partial switch &v in e.v { - case Enemy: - enemy_update(state, p, e) - } +entity_draw :: proc(e: ^Entity) { + b := get_base(e) + for g in b.gfx { + gfx_draw(b.pos, g) } } @@ -217,33 +320,20 @@ sys_render :: proc(state: ^GameState) { { it := hm.iterator_make(&state.entities) for e, _ in hm.iterate(&it) { - #partial switch &v in e.v { - case Player: - for g in &v.gfx do gfx_draw(e.pos, g) - case Enemy: - for g in &v.gfx do gfx_draw(e.pos, g) + entity_draw(e) + if a := get_actor(e); a != nil { + ui_draw_world_hp_bar(get_base(e), a) } } - // for &e in state.enemies { - // entity_draw(&e) - // ui_draw_world_hp_bar(cast(^Actor)&e) - // } - // - // for &it in state.items { - // entity_draw(&it) - // } - // - // for &fx in state.fxs { - // fx_draw(&fx) - // } - // - // entity_draw(&state.player) - // ui_draw_world_hp_bar(cast(^Actor)&state.player) + it2 := hm.iterator_make(&state.fxs) + for fx, _ in hm.iterate(&it2) { + fx_draw(fx) + } } rl.EndMode2D() - // ui_draw(state) + ui_draw(state) rl.EndDrawing() } diff --git a/src/ui.odin b/src/ui.odin index c229fee..14a2237 100644 --- a/src/ui.odin +++ b/src/ui.odin @@ -1,24 +1,26 @@ package main +import hm "core:container/handle_map" import rl "vendor:raylib" - -ui_draw_world_hp_bar :: proc(actor: ^Actor) { +ui_draw_world_hp_bar :: proc(b: ^BaseEntity, actor: ^Actor) { hp_ratio := f32(actor.hp) / f32(actor.max_hp) - bar_w := actor.col_size * 3 - bar_pos := actor.pos + rl.Vector2{-bar_w / 2, -actor.col_size - 10} + bar_w := b.col_size * 3 + bar_pos := b.pos + rl.Vector2{-bar_w / 2, -b.col_size - 10} rl.DrawRectangleV(bar_pos, {bar_w, 4}, {60, 60, 60, 255}) rl.DrawRectangleV(bar_pos, {bar_w * hp_ratio, 4}, {220, 40, 40, 255}) } -ui_draw :: proc(state: ^GameState3) { - rl.DrawText( - rl.TextFormat("HP: %d/%d", state.player.hp, state.player.max_hp), - 12, - 12, - 20, - rl.RAYWHITE, - ) - rl.DrawText(rl.TextFormat("Enemies: %d", len(state.enemies)), 12, 36, 16, {180, 180, 180, 255}) +ui_draw :: proc(state: ^GameState) { + _, p := get_player(state) + hp: i32 = 0 + max_hp: i32 = 1 + if p != nil { + hp = p.hp + max_hp = p.max_hp + } + + rl.DrawText(rl.TextFormat("HP: %d/%d", hp, max_hp), 12, 12, 20, rl.RAYWHITE) + rl.DrawText(rl.TextFormat("Entities: %d", hm.len(state.entities)), 12, 36, 16, {180, 180, 180, 255}) rl.DrawText("[SPACE] attack [WASD] move", 12, SCREEN_H - 28, 14, {120, 120, 120, 255}) }