handlemap rewrite init
This commit is contained in:
+19
-30
@@ -3,36 +3,25 @@ package main
|
|||||||
import hm "core:container/handle_map"
|
import hm "core:container/handle_map"
|
||||||
import rl "vendor:raylib"
|
import rl "vendor:raylib"
|
||||||
|
|
||||||
Enemy :: struct {
|
// ИИ врага — летит к игроку. Вызывается из диспатчера sys_update
|
||||||
using actor: Actor,
|
enemy_update :: proc(state: ^GameState, e: ^Enemy, h: Handle, dt: f32) {
|
||||||
touch_dmg: i32,
|
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,
|
|
||||||
// },
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|||||||
+40
-19
@@ -1,5 +1,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
|
import hm "core:container/handle_map"
|
||||||
import rl "vendor:raylib"
|
import rl "vendor:raylib"
|
||||||
|
|
||||||
Graphics :: union {
|
Graphics :: union {
|
||||||
@@ -31,6 +32,7 @@ GfxRing :: struct {
|
|||||||
outer_radius: f32,
|
outer_radius: f32,
|
||||||
color: rl.Color,
|
color: rl.Color,
|
||||||
}
|
}
|
||||||
|
|
||||||
GfxCircleLines :: struct {
|
GfxCircleLines :: struct {
|
||||||
pos: rl.Vector2,
|
pos: rl.Vector2,
|
||||||
radius: f32,
|
radius: f32,
|
||||||
@@ -38,16 +40,7 @@ GfxCircleLines :: struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
GfxTexture :: struct {
|
GfxTexture :: struct {
|
||||||
// TODO: Implement texure rendering
|
// TODO: Implement texture rendering
|
||||||
}
|
|
||||||
|
|
||||||
FX :: struct {
|
|
||||||
parentId: EntityID,
|
|
||||||
lifetime: f32,
|
|
||||||
permanent: bool,
|
|
||||||
pos: rl.Vector2,
|
|
||||||
gfx: [MAX_GFX]Graphics,
|
|
||||||
marked: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
gfx_draw :: proc(pos: rl.Vector2, g: Graphics) {
|
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.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.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) {
|
sys_fx_update :: proc(state: ^GameState, dt: f32) {
|
||||||
append(&state.fxs, fx)
|
it := hm.iterator_make(&state.fxs)
|
||||||
|
for fx, _ in hm.iterate(&it) {
|
||||||
|
fx_update(state, fx, dt)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: Создание и удаление FX с лайфтаймами
|
|
||||||
|
|||||||
+19
-12
@@ -1,17 +1,24 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
GroundItem3 :: struct {
|
// Пикап лута игроком — по коллизионным парам (как урон)
|
||||||
using ent: Entity2,
|
sys_pickup :: proc(state: ^GameState) {
|
||||||
heal: i32,
|
_, p := get_player(state)
|
||||||
}
|
if p == nil do return
|
||||||
|
|
||||||
sys_pickup :: proc(state: ^GameState3) {
|
for c in state.collisions {
|
||||||
for &item in &state.items {
|
other_h: Handle
|
||||||
if item.marked do continue
|
if c.a == state.player {
|
||||||
_, ok := check_col(cast(^Entity2)&item, cast(^Entity2)&state.player)
|
other_h = c.b
|
||||||
if ok {
|
} else if c.b == state.player {
|
||||||
state.player.hp = min(state.player.hp + item.heal, state.player.max_hp)
|
other_h = c.a
|
||||||
item.marked = true
|
} 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-88
@@ -1,7 +1,5 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import hm "core:container/handle_map"
|
|
||||||
import "core:fmt"
|
|
||||||
import rl "vendor:raylib"
|
import rl "vendor:raylib"
|
||||||
|
|
||||||
main :: proc() {
|
main :: proc() {
|
||||||
@@ -9,100 +7,32 @@ main :: proc() {
|
|||||||
rl.SetTargetFPS(TARGET_FPS)
|
rl.SetTargetFPS(TARGET_FPS)
|
||||||
defer rl.CloseWindow()
|
defer rl.CloseWindow()
|
||||||
|
|
||||||
state := GameState {
|
state := GameState{
|
||||||
camera = {zoom = 1.5, offset = {SCREEN_W / 2, SCREEN_H / 2}},
|
camera = {zoom = 1.5, offset = {SCREEN_W / 2, SCREEN_H / 2}},
|
||||||
}
|
}
|
||||||
|
state.collisions = make([dynamic]CollisionPair)
|
||||||
|
defer delete(state.collisions)
|
||||||
|
|
||||||
p := hm.add(
|
state.player = spawn(&state, make_player())
|
||||||
&state.entities,
|
spawn(&state, make_enemy({600, 500}))
|
||||||
Entity {
|
spawn(&state, make_enemy({200, 500}))
|
||||||
pos = {400, 300},
|
spawn(&state, make_enemy({600, 100}))
|
||||||
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}}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
for !rl.WindowShouldClose() {
|
for !rl.WindowShouldClose() {
|
||||||
dt := rl.GetFrameTime()
|
dt := rl.GetFrameTime()
|
||||||
|
|
||||||
sys_input(&state)
|
sys_update(&state, dt) // вход игрока + ИИ врагов (диспатчер)
|
||||||
sys_enemy_ai(&state)
|
sys_move(&state, dt) // движение всех
|
||||||
sys_move(&state, dt)
|
sys_collisions(&state) // пары + резолв
|
||||||
sys_collisions(&state)
|
sys_damage(&state) // дамаг игроку от врагов
|
||||||
sys_damage(&state)
|
sys_death(&state) // враги умерли → лут
|
||||||
sys_death(&state)
|
sys_pickup(&state) // игрок подбирает лут
|
||||||
sys_pickup(&state)
|
sys_fx_update(&state, dt) // FX: таймеры + следование
|
||||||
sys_update(&state, dt)
|
sys_sweep(&state) // удалить помеченное
|
||||||
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)
|
sys_render(&state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-30
@@ -1,48 +1,41 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
|
import hm "core:container/handle_map"
|
||||||
import rl "vendor:raylib"
|
import rl "vendor:raylib"
|
||||||
|
|
||||||
Player2 :: struct {
|
// Вход и атака игрока — вызывается из диспатчера sys_update
|
||||||
using actor: Actor,
|
player_update :: proc(state: ^GameState, p: ^Player, h: Handle, dt: f32) {
|
||||||
attack_range: f32,
|
// движение
|
||||||
attack_dmg: i32,
|
|
||||||
attack_cd: f32,
|
|
||||||
attack_timer: f32,
|
|
||||||
invuln_timer: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
sys_input :: proc(state: ^GameState3) {
|
|
||||||
p := &state.player
|
|
||||||
|
|
||||||
dir: rl.Vector2
|
dir: rl.Vector2
|
||||||
if rl.IsKeyDown(.W) || rl.IsKeyDown(.UP) do dir.y -= 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(.S) || rl.IsKeyDown(.DOWN) do dir.y += 1
|
||||||
if rl.IsKeyDown(.A) || rl.IsKeyDown(.LEFT) do dir.x -= 1
|
if rl.IsKeyDown(.A) || rl.IsKeyDown(.LEFT) do dir.x -= 1
|
||||||
if rl.IsKeyDown(.D) || rl.IsKeyDown(.RIGHT) 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 {
|
if rl.IsKeyPressed(.SPACE) && p.attack_timer <= 0 {
|
||||||
p.attack_timer = p.attack_cd
|
p.attack_timer = p.attack_cd
|
||||||
init_fx(
|
|
||||||
state,
|
// визуал атаки — кольцо
|
||||||
FX {
|
init_fx(state, FX{
|
||||||
parentId = p.id,
|
parent = h,
|
||||||
lifetime = p.attack_cd,
|
|
||||||
permanent = false,
|
|
||||||
gfx = {
|
gfx = {
|
||||||
0 = GfxRing {
|
0 = GfxRing{inner_radius = p.attack_range * 0.7, outer_radius = p.attack_range, color = {255, 255, 255, 40}},
|
||||||
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}},
|
1 = GfxCircleLines{radius = p.attack_range, color = {255, 255, 255, 80}},
|
||||||
},
|
},
|
||||||
},
|
lifetime = p.attack_cd,
|
||||||
)
|
})
|
||||||
for &enemy in &state.enemies {
|
|
||||||
if dist(Entity2(p), Entity2(enemy)) <= p.attack_range {
|
// дамаг врагам в радиусе
|
||||||
enemy.hp -= p.attack_dmg
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+239
-149
@@ -7,47 +7,34 @@ SCREEN_W :: 1280
|
|||||||
SCREEN_H :: 720
|
SCREEN_H :: 720
|
||||||
TARGET_FPS :: 60
|
TARGET_FPS :: 60
|
||||||
SQRT2_INV :: 0.7071067811865476
|
SQRT2_INV :: 0.7071067811865476
|
||||||
|
|
||||||
// ─── Базовые типы ───
|
|
||||||
|
|
||||||
MAX_GFX :: 2
|
MAX_GFX :: 2
|
||||||
MAX_ENTITIES :: 1024
|
|
||||||
|
|
||||||
Handle :: hm.Handle32
|
Handle :: hm.Handle32
|
||||||
|
|
||||||
// ─── Коллизии ───
|
MAX_ENTITIES :: 1024
|
||||||
|
MAX_FX :: 256
|
||||||
|
|
||||||
// EntityKind :: enum {
|
// ─── Общее — всё что есть у любой сущности ───
|
||||||
// Player,
|
|
||||||
// Enemy,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// CollisionEntry :: struct {
|
BaseEntity :: struct {
|
||||||
// a_kind: EntityKind,
|
|
||||||
// a_idx: int,
|
|
||||||
// b_kind: EntityKind,
|
|
||||||
// b_idx: int,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// ─── Игровое состояние ───
|
|
||||||
|
|
||||||
|
|
||||||
Entity :: struct {
|
|
||||||
handle: Handle,
|
|
||||||
pos: rl.Vector2,
|
pos: rl.Vector2,
|
||||||
|
vel: rl.Vector2,
|
||||||
col_size: f32,
|
col_size: f32,
|
||||||
marked: bool,
|
marked: bool,
|
||||||
v: EntityVariant,
|
gfx: [MAX_GFX]Graphics,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── Акторское — то что есть у живых ───
|
||||||
|
|
||||||
Actor :: struct {
|
Actor :: struct {
|
||||||
vel: rl.Vector2,
|
using base: BaseEntity,
|
||||||
hp: i32,
|
hp: i32,
|
||||||
max_hp: i32,
|
max_hp: i32,
|
||||||
speed: f32,
|
speed: f32,
|
||||||
gfx: [MAX_GFX]Graphics,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── Конкретные типы ───
|
||||||
|
|
||||||
Player :: struct {
|
Player :: struct {
|
||||||
using actor: Actor,
|
using actor: Actor,
|
||||||
attack_range: f32,
|
attack_range: f32,
|
||||||
@@ -57,156 +44,272 @@ Player :: struct {
|
|||||||
invuln_timer: f32,
|
invuln_timer: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Enemy :: struct {
|
||||||
|
using actor: Actor,
|
||||||
|
touch_dmg: i32,
|
||||||
|
}
|
||||||
|
|
||||||
GroundItem :: struct {
|
GroundItem :: struct {
|
||||||
|
using base: BaseEntity,
|
||||||
heal: i32,
|
heal: i32,
|
||||||
gfx: [MAX_GFX]Graphics,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
EntityVariant :: union {
|
EntityVariant :: union { Player, Enemy, GroundItem }
|
||||||
Player,
|
|
||||||
Enemy,
|
// ─── Контейнер для handle_map ───
|
||||||
GroundItem,
|
|
||||||
|
Entity :: struct {
|
||||||
|
handle: Handle,
|
||||||
|
v: EntityVariant,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── Состояние игры ───
|
||||||
|
|
||||||
GameState :: struct {
|
GameState :: struct {
|
||||||
player: Handle,
|
player: Handle,
|
||||||
camera: rl.Camera2D,
|
camera: rl.Camera2D,
|
||||||
entities: hm.Static_Handle_Map(MAX_ENTITIES, Entity, Handle),
|
entities: hm.Static_Handle_Map(MAX_ENTITIES, Entity, Handle),
|
||||||
// particles: hm.Static_Handle_Map(MAX_PARTICLE_FX, Fx2, 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) {
|
// Общее из любой сущности (BaseEntity первым полем во всех вариантах)
|
||||||
if rl.Vector2Length(dir) > 0 {
|
get_base :: proc(e: ^Entity) -> ^BaseEntity {
|
||||||
e.v.vel = rl.Vector2Normalize(dir) * speed
|
#partial switch &v in e.v {
|
||||||
} else {
|
case Player:
|
||||||
e.v.vel = {}
|
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) {
|
make_enemy :: proc(pos: rl.Vector2) -> Entity {
|
||||||
e.v.pos += e.v.vel * dt
|
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 {
|
make_ground_item :: proc(pos: rl.Vector2) -> Entity {
|
||||||
return rl.Vector2Distance(a.v.pos, b.v.pos)
|
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 {
|
spawn :: proc(state: ^GameState, e: Entity) -> Handle {
|
||||||
// return rl.Rectangle{e.pos.x - e.col_size / 2, e.pos.y - e.col_size / 2, e.col_size, e.col_size}
|
return hm.add(&state.entities, e)
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// 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
|
|
||||||
// }
|
|
||||||
|
|
||||||
// ─── Общие системы ───
|
// ─── Хелперы ───
|
||||||
|
|
||||||
|
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, h in hm.iterate(&it) {
|
||||||
|
#partial switch &v in e.v {
|
||||||
|
case Player:
|
||||||
|
player_update(state, &v, h, dt)
|
||||||
|
case Enemy:
|
||||||
|
enemy_update(state, &v, h, dt)
|
||||||
|
case GroundItem:
|
||||||
|
// пока статичный
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Общее движение ───
|
||||||
|
|
||||||
sys_move :: proc(state: ^GameState, dt: f32) {
|
sys_move :: proc(state: ^GameState, dt: f32) {
|
||||||
it := hm.iterator_make(&state.entities)
|
it := hm.iterator_make(&state.entities)
|
||||||
for e, _ in hm.iterate(&it) {
|
for e, _ in hm.iterate(&it) {
|
||||||
#partial switch &v in e.v {
|
b := get_base(e)
|
||||||
case Player:
|
b.pos += b.vel * dt
|
||||||
e.pos += v.vel * dt
|
}
|
||||||
case Enemy:
|
}
|
||||||
e.pos += v.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_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_damage :: proc(state: ^GameState) {
|
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 {
|
if p.invuln_timer > 0 {
|
||||||
p.invuln_timer -= rl.GetFrameTime()
|
p.invuln_timer -= rl.GetFrameTime()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// for c in state.collisions {
|
for c in state.collisions {
|
||||||
// if c.a_kind == .Player && c.b_kind == .Enemy {
|
other_h: Handle
|
||||||
// state.player.hp -= state.enemies[c.b_idx].touch_dmg
|
if c.a == state.player {
|
||||||
// state.player.invuln_timer = 0.5
|
other_h = c.b
|
||||||
// if state.player.hp <= 0 {
|
} else if c.b == state.player {
|
||||||
// state.player.hp = 0
|
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)
|
it := hm.iterator_make(&state.entities)
|
||||||
for e, i in hm.iterate(&it) {
|
for e, h in hm.iterate(&it) {
|
||||||
if e.marked do hm.remove(&state.entities, i)
|
if get_base(e).marked {
|
||||||
|
hm.remove(&state.entities, h)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// i = 0
|
it2 := hm.iterator_make(&state.fxs)
|
||||||
// for i < len(state.fxs) {
|
for fx, h in hm.iterate(&it2) {
|
||||||
// if state.fxs[i].marked {
|
if fx.marked {
|
||||||
// ordered_remove(&state.fxs, i)
|
hm.remove(&state.fxs, h)
|
||||||
// } else {
|
}
|
||||||
// i += 1
|
}
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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)
|
entity_draw :: proc(e: ^Entity) {
|
||||||
for e, _ in hm.iterate(&it) {
|
b := get_base(e)
|
||||||
#partial switch &v in e.v {
|
for g in b.gfx {
|
||||||
case Enemy:
|
gfx_draw(b.pos, g)
|
||||||
enemy_update(state, p, e)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,33 +320,20 @@ sys_render :: proc(state: ^GameState) {
|
|||||||
{
|
{
|
||||||
it := hm.iterator_make(&state.entities)
|
it := hm.iterator_make(&state.entities)
|
||||||
for e, _ in hm.iterate(&it) {
|
for e, _ in hm.iterate(&it) {
|
||||||
#partial switch &v in e.v {
|
entity_draw(e)
|
||||||
case Player:
|
if a := get_actor(e); a != nil {
|
||||||
for g in &v.gfx do gfx_draw(e.pos, g)
|
ui_draw_world_hp_bar(get_base(e), a)
|
||||||
case Enemy:
|
|
||||||
for g in &v.gfx do gfx_draw(e.pos, g)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// for &e in state.enemies {
|
it2 := hm.iterator_make(&state.fxs)
|
||||||
// entity_draw(&e)
|
for fx, _ in hm.iterate(&it2) {
|
||||||
// ui_draw_world_hp_bar(cast(^Actor)&e)
|
fx_draw(fx)
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// 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)
|
|
||||||
}
|
}
|
||||||
rl.EndMode2D()
|
rl.EndMode2D()
|
||||||
|
|
||||||
// ui_draw(state)
|
ui_draw(state)
|
||||||
|
|
||||||
rl.EndDrawing()
|
rl.EndDrawing()
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-13
@@ -1,24 +1,26 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
|
import hm "core:container/handle_map"
|
||||||
import rl "vendor:raylib"
|
import rl "vendor:raylib"
|
||||||
|
|
||||||
|
ui_draw_world_hp_bar :: proc(b: ^BaseEntity, actor: ^Actor) {
|
||||||
ui_draw_world_hp_bar :: proc(actor: ^Actor) {
|
|
||||||
hp_ratio := f32(actor.hp) / f32(actor.max_hp)
|
hp_ratio := f32(actor.hp) / f32(actor.max_hp)
|
||||||
bar_w := actor.col_size * 3
|
bar_w := b.col_size * 3
|
||||||
bar_pos := actor.pos + rl.Vector2{-bar_w / 2, -actor.col_size - 10}
|
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, 4}, {60, 60, 60, 255})
|
||||||
rl.DrawRectangleV(bar_pos, {bar_w * hp_ratio, 4}, {220, 40, 40, 255})
|
rl.DrawRectangleV(bar_pos, {bar_w * hp_ratio, 4}, {220, 40, 40, 255})
|
||||||
}
|
}
|
||||||
|
|
||||||
ui_draw :: proc(state: ^GameState3) {
|
ui_draw :: proc(state: ^GameState) {
|
||||||
rl.DrawText(
|
_, p := get_player(state)
|
||||||
rl.TextFormat("HP: %d/%d", state.player.hp, state.player.max_hp),
|
hp: i32 = 0
|
||||||
12,
|
max_hp: i32 = 1
|
||||||
12,
|
if p != nil {
|
||||||
20,
|
hp = p.hp
|
||||||
rl.RAYWHITE,
|
max_hp = p.max_hp
|
||||||
)
|
}
|
||||||
rl.DrawText(rl.TextFormat("Enemies: %d", len(state.enemies)), 12, 36, 16, {180, 180, 180, 255})
|
|
||||||
|
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})
|
rl.DrawText("[SPACE] attack [WASD] move", 12, SCREEN_H - 28, 14, {120, 120, 120, 255})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user