3D only
This commit is contained in:
@@ -0,0 +1,9 @@
|
|||||||
|
-- .nvim.lua — локальные настройки проекта
|
||||||
|
-- Создан :LocalInit. Автоматически грузится nvim-config-local (первый раз — через :ConfigLocalTrust).
|
||||||
|
|
||||||
|
-- Раннер: команда, выполняемая в нижнем терминале edgy
|
||||||
|
-- по :Run или <leader>dr. Можно строкой или функцией.
|
||||||
|
require("core.runner").setup({
|
||||||
|
cmd = "make run",
|
||||||
|
focus = true, -- фокус на терминал при запуске
|
||||||
|
})
|
||||||
@@ -4,3 +4,4 @@ t.git
|
|||||||
https://craftpix.net/freebies/free-basic-pixel-art-fantasy-icons-16x16-for-ui/
|
https://craftpix.net/freebies/free-basic-pixel-art-fantasy-icons-16x16-for-ui/
|
||||||
https://craftpix.net/freebies/free-water-and-fire-magic-sprite-vector-pack/
|
https://craftpix.net/freebies/free-water-and-fire-magic-sprite-vector-pack/
|
||||||
https://craftpix.net/freebies/free-swordsman-1-3-level-pixel-top-down-sprite-character-pack/
|
https://craftpix.net/freebies/free-swordsman-1-3-level-pixel-top-down-sprite-character-pack/
|
||||||
|
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
package main
|
|
||||||
import rl "vendor:raylib"
|
|
||||||
|
|
||||||
Enemy :: struct {
|
|
||||||
using actor: Actor,
|
|
||||||
touch_dmg: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enemy_update :: proc(state: ^GameState, e: ^Enemy, h: Handle, dt: f32) {
|
|
||||||
pe := get_entity(state, state.player)
|
|
||||||
if pe != nil {
|
|
||||||
dir := get_base(pe).pos - e.pos
|
|
||||||
e.vel = rl.Vector2Normalize(dir) * e.speed
|
|
||||||
}
|
|
||||||
|
|
||||||
if e.hp <= 0 && !e.marked {
|
|
||||||
e.marked = true
|
|
||||||
spawn(state, make_ground_item(e.pos))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import hm "core:container/handle_map"
|
|
||||||
import rl "vendor:raylib"
|
|
||||||
|
|
||||||
MAX_GFX :: 2
|
|
||||||
|
|
||||||
Handle :: hm.Handle32
|
|
||||||
|
|
||||||
MAX_ENTITIES :: 1024
|
|
||||||
|
|
||||||
BaseEntity :: struct {
|
|
||||||
pos: rl.Vector2,
|
|
||||||
vel: rl.Vector2,
|
|
||||||
col_size: f32,
|
|
||||||
marked: bool,
|
|
||||||
gfx: [MAX_GFX]Graphics,
|
|
||||||
}
|
|
||||||
|
|
||||||
Actor :: struct {
|
|
||||||
using base: BaseEntity,
|
|
||||||
hp: i32,
|
|
||||||
max_hp: i32,
|
|
||||||
speed: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
EntityVariant :: union {
|
|
||||||
Player,
|
|
||||||
Enemy,
|
|
||||||
GroundItem,
|
|
||||||
FX,
|
|
||||||
}
|
|
||||||
|
|
||||||
Entity :: struct {
|
|
||||||
handle: Handle,
|
|
||||||
v: EntityVariant,
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
case FX:
|
|
||||||
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
|
|
||||||
}
|
|
||||||
-32
@@ -1,32 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
|
|
||||||
FX :: struct {
|
|
||||||
using base: BaseEntity,
|
|
||||||
parent: Handle,
|
|
||||||
lifetime: f32,
|
|
||||||
permanent: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
init_fx :: proc(state: ^GameState, fx: FX) -> Handle {
|
|
||||||
return spawn(state, Entity{v = fx})
|
|
||||||
}
|
|
||||||
|
|
||||||
fx_update :: proc(state: ^GameState, fx: ^FX, h: Handle, dt: f32) {
|
|
||||||
if fx.parent != {} {
|
|
||||||
if pe := get_entity(state, fx.parent); pe != nil {
|
|
||||||
fx.pos = get_base(pe).pos
|
|
||||||
} else {
|
|
||||||
fx.marked = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !fx.permanent {
|
|
||||||
if fx.lifetime > 0 {
|
|
||||||
fx.lifetime -= dt
|
|
||||||
} else {
|
|
||||||
fx.marked = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import rl "vendor:raylib"
|
|
||||||
|
|
||||||
Graphics :: union {
|
|
||||||
GfxNone,
|
|
||||||
GfxCircle,
|
|
||||||
GfxRect,
|
|
||||||
GfxRing,
|
|
||||||
GfxCircleLines,
|
|
||||||
GfxTexture,
|
|
||||||
}
|
|
||||||
|
|
||||||
GfxNone :: struct {}
|
|
||||||
|
|
||||||
GfxCircle :: struct {
|
|
||||||
pos: rl.Vector2,
|
|
||||||
radius: f32,
|
|
||||||
color: rl.Color,
|
|
||||||
}
|
|
||||||
|
|
||||||
GfxRect :: struct {
|
|
||||||
pos: rl.Vector2,
|
|
||||||
size: rl.Vector2,
|
|
||||||
color: rl.Color,
|
|
||||||
}
|
|
||||||
|
|
||||||
GfxRing :: struct {
|
|
||||||
pos: rl.Vector2,
|
|
||||||
inner_radius: f32,
|
|
||||||
outer_radius: f32,
|
|
||||||
color: rl.Color,
|
|
||||||
}
|
|
||||||
|
|
||||||
GfxCircleLines :: struct {
|
|
||||||
pos: rl.Vector2,
|
|
||||||
radius: f32,
|
|
||||||
color: rl.Color,
|
|
||||||
}
|
|
||||||
|
|
||||||
GfxTexture :: struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
gfx_draw :: proc(pos: rl.Vector2, g: Graphics) {
|
|
||||||
#partial switch v in g {
|
|
||||||
case GfxCircle:
|
|
||||||
rl.DrawCircleV(pos + v.pos, v.radius, v.color)
|
|
||||||
case GfxRect:
|
|
||||||
rl.DrawRectangleV(pos - v.size / 2 + v.pos, v.size, v.color)
|
|
||||||
case GfxRing:
|
|
||||||
rl.DrawRing(pos + v.pos, v.inner_radius, v.outer_radius, 0, 360, 0, v.color)
|
|
||||||
case GfxCircleLines:
|
|
||||||
rl.DrawCircleLinesV(pos + v.pos, v.radius, v.color)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
package main
|
|
||||||
import rl "vendor:raylib"
|
|
||||||
|
|
||||||
GroundItem :: struct {
|
|
||||||
using base: BaseEntity,
|
|
||||||
heal: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
item_update :: proc(state: ^GameState, i: ^GroundItem, h: Handle, dt: f32) {
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import rl "vendor:raylib"
|
|
||||||
|
|
||||||
SCREEN_W :: 1280
|
|
||||||
SCREEN_H :: 720
|
|
||||||
TARGET_FPS :: 60
|
|
||||||
|
|
||||||
|
|
||||||
main :: proc() {
|
|
||||||
rl.InitWindow(SCREEN_W, SCREEN_H, "Iso Arena")
|
|
||||||
rl.SetTargetFPS(TARGET_FPS)
|
|
||||||
defer rl.CloseWindow()
|
|
||||||
|
|
||||||
state := GameState {
|
|
||||||
camera = {zoom = 1.5, offset = {SCREEN_W / 2, SCREEN_H / 2}},
|
|
||||||
}
|
|
||||||
state.collisions = make([dynamic]CollisionPair)
|
|
||||||
defer delete(state.collisions)
|
|
||||||
|
|
||||||
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, dt)
|
|
||||||
sys_update(&state, dt)
|
|
||||||
sys_sweep(&state)
|
|
||||||
|
|
||||||
if pe := get_entity(&state, state.player); pe != nil {
|
|
||||||
state.camera.target = get_base(pe).pos
|
|
||||||
}
|
|
||||||
sys_draw(&state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import "core:fmt"
|
|
||||||
import rl "vendor:raylib"
|
|
||||||
|
|
||||||
TILE_W :: 64
|
|
||||||
TILE_H :: 32
|
|
||||||
MAP_SIZE :: 60
|
|
||||||
|
|
||||||
Vector2I :: distinct [2]i32
|
|
||||||
|
|
||||||
world_to_tile_V2I :: #force_inline proc(pos: rl.Vector2) -> Vector2I {
|
|
||||||
return Vector2I{i32((pos.x - pos.y) * TILE_W * 0.5), i32((pos.x + pos.y) * TILE_H * 0.5)}
|
|
||||||
}
|
|
||||||
|
|
||||||
world_to_tile :: #force_inline proc(pos: rl.Vector2) -> rl.Vector2 {
|
|
||||||
return rl.Vector2{(pos.x - pos.y) * TILE_W * 0.5, (pos.x + pos.y) * TILE_H * 0.5}
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO: return i32?
|
|
||||||
depth_key :: #force_inline proc(pos: rl.Vector2) -> f32 {
|
|
||||||
return pos.x + pos.y
|
|
||||||
}
|
|
||||||
|
|
||||||
draw_tile :: #force_inline proc(wprldPos: rl.Vector2, color: rl.Color) {
|
|
||||||
tilePos := world_to_tile(wprldPos)
|
|
||||||
top := tilePos + rl.Vector2{0, -TILE_H * 0.5}
|
|
||||||
right := tilePos + rl.Vector2{TILE_W * 0.5, 0}
|
|
||||||
down := tilePos + rl.Vector2{0, TILE_H * 0.5}
|
|
||||||
left := tilePos + rl.Vector2{-TILE_W * 0.5, 0}
|
|
||||||
|
|
||||||
rl.DrawLineV(top, right, color)
|
|
||||||
rl.DrawLineV(right, down, color)
|
|
||||||
rl.DrawLineV(down, left, color)
|
|
||||||
rl.DrawLineV(left, top, color)
|
|
||||||
}
|
|
||||||
|
|
||||||
draw_grid :: #force_inline proc() {
|
|
||||||
middle := MAP_SIZE / 2
|
|
||||||
for x in 0 ..< MAP_SIZE {
|
|
||||||
for y in 0 ..< MAP_SIZE {
|
|
||||||
wx := f32(x - middle)
|
|
||||||
wy := f32(y - middle)
|
|
||||||
color := ((x + y) % 2 == 0) ? rl.Color{64, 64, 76, 255} : rl.Color{51, 51, 63, 255}
|
|
||||||
draw_tile(rl.Vector2{wx, wy}, color)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import hm "core:container/handle_map"
|
|
||||||
import rl "vendor:raylib"
|
|
||||||
|
|
||||||
Player :: struct {
|
|
||||||
using actor: Actor,
|
|
||||||
attack_range: f32,
|
|
||||||
attack_dmg: i32,
|
|
||||||
attack_cd: f32,
|
|
||||||
attack_timer: f32,
|
|
||||||
invuln_timer: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
PlayerInput :: struct {
|
|
||||||
move: rl.Vector2,
|
|
||||||
attack: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sys_input :: proc(state: ^GameState, dt: f32) {
|
|
||||||
input: PlayerInput
|
|
||||||
|
|
||||||
if rl.IsKeyDown(.W) || rl.IsKeyDown(.UP) do input.move.y -= 1
|
|
||||||
if rl.IsKeyDown(.S) || rl.IsKeyDown(.DOWN) do input.move.y += 1
|
|
||||||
if rl.IsKeyDown(.A) || rl.IsKeyDown(.LEFT) do input.move.x -= 1
|
|
||||||
if rl.IsKeyDown(.D) || rl.IsKeyDown(.RIGHT) do input.move.x += 1
|
|
||||||
input.attack = rl.IsKeyPressed(.SPACE)
|
|
||||||
|
|
||||||
player_input(state, input, dt)
|
|
||||||
}
|
|
||||||
|
|
||||||
player_input :: proc(state: ^GameState, input: PlayerInput, dt: f32) {
|
|
||||||
_, p := get_player(state)
|
|
||||||
if p == nil do return
|
|
||||||
|
|
||||||
p.vel = rl.Vector2Normalize(input.move) * p.speed
|
|
||||||
|
|
||||||
if p.attack_timer > 0 do p.attack_timer -= dt
|
|
||||||
if input.attack && p.attack_timer <= 0 {
|
|
||||||
p.attack_timer = p.attack_cd
|
|
||||||
|
|
||||||
init_fx(
|
|
||||||
state,
|
|
||||||
FX {
|
|
||||||
base = BaseEntity {
|
|
||||||
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}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
parent = state.player,
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
player_update :: proc(state: ^GameState, p: ^Player, h: Handle, dt: f32) {
|
|
||||||
if p.invuln_timer > 0 do p.invuln_timer -= dt
|
|
||||||
}
|
|
||||||
-174
@@ -1,174 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import hm "core:container/handle_map"
|
|
||||||
import rl "vendor:raylib"
|
|
||||||
|
|
||||||
SQRT2_INV :: 0.7071067811865476
|
|
||||||
|
|
||||||
CollisionPair :: struct {
|
|
||||||
a: Handle,
|
|
||||||
b: Handle,
|
|
||||||
}
|
|
||||||
|
|
||||||
GameState :: struct {
|
|
||||||
player: Handle,
|
|
||||||
camera: rl.Camera2D,
|
|
||||||
entities: hm.Static_Handle_Map(MAX_ENTITIES, Entity, 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
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
spawn :: proc(state: ^GameState, e: Entity) -> Handle {
|
|
||||||
return hm.add(&state.entities, e)
|
|
||||||
}
|
|
||||||
|
|
||||||
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:
|
|
||||||
item_update(state, &v, h, dt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
it2 := hm.iterator_make(&state.entities)
|
|
||||||
for e, _ in hm.iterate(&it2) {
|
|
||||||
b := get_base(e)
|
|
||||||
b.pos += b.vel * dt
|
|
||||||
}
|
|
||||||
|
|
||||||
it3 := hm.iterator_make(&state.entities)
|
|
||||||
for e, h in hm.iterate(&it3) {
|
|
||||||
#partial switch &v in e.v {
|
|
||||||
case FX:
|
|
||||||
fx_update(state, &v, h, dt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve_collisions(state)
|
|
||||||
|
|
||||||
apply_collision_effects(state)
|
|
||||||
}
|
|
||||||
|
|
||||||
sys_sweep :: proc(state: ^GameState) {
|
|
||||||
it := hm.iterator_make(&state.entities)
|
|
||||||
for e, h in hm.iterate(&it) {
|
|
||||||
if get_base(e).marked {
|
|
||||||
hm.remove(&state.entities, h)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
entity_draw :: proc(e: ^Entity) {
|
|
||||||
b := get_base(e)
|
|
||||||
for g in b.gfx {
|
|
||||||
gfx_draw(b.pos, g)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sys_draw :: proc(state: ^GameState) {
|
|
||||||
rl.BeginDrawing()
|
|
||||||
rl.ClearBackground({20, 20, 30, 255})
|
|
||||||
rl.BeginMode2D(state.camera)
|
|
||||||
{
|
|
||||||
draw_grid()
|
|
||||||
it := hm.iterator_make(&state.entities)
|
|
||||||
for e, _ in hm.iterate(&it) {
|
|
||||||
entity_draw(e)
|
|
||||||
if a := get_actor(e); a != nil {
|
|
||||||
ui_draw_world_hp_bar(get_base(e), a)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rl.EndMode2D()
|
|
||||||
|
|
||||||
ui_draw(state)
|
|
||||||
rl.EndDrawing()
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve_collisions :: proc(state: ^GameState) {
|
|
||||||
clear(&state.collisions)
|
|
||||||
|
|
||||||
hs: [MAX_ENTITIES]Handle
|
|
||||||
n := 0
|
|
||||||
it := hm.iterator_make(&state.entities)
|
|
||||||
for _, h in hm.iterate(&it) {
|
|
||||||
hs[n] = h
|
|
||||||
n += 1
|
|
||||||
}
|
|
||||||
|
|
||||||
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]})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
apply_collision_effects :: proc(state: ^GameState) {
|
|
||||||
_, p := get_player(state)
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
#partial switch &v in e.v {
|
|
||||||
case Enemy:
|
|
||||||
if p != nil && p.invuln_timer <= 0 {
|
|
||||||
p.hp -= v.touch_dmg
|
|
||||||
p.invuln_timer = 0.5
|
|
||||||
if p.hp <= 0 do p.hp = 0
|
|
||||||
}
|
|
||||||
case GroundItem:
|
|
||||||
if p != nil && !v.marked {
|
|
||||||
p.hp = min(p.hp + v.heal, p.max_hp)
|
|
||||||
v.marked = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-26
@@ -1,26 +0,0 @@
|
|||||||
package main
|
|
||||||
import hm "core:container/handle_map"
|
|
||||||
|
|
||||||
import rl "vendor:raylib"
|
|
||||||
|
|
||||||
ui_draw_world_hp_bar :: proc(b: ^BaseEntity, actor: ^Actor) {
|
|
||||||
hp_ratio := f32(actor.hp) / f32(actor.max_hp)
|
|
||||||
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: ^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})
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import hm "core:container/handle_map"
|
|
||||||
import rl "vendor:raylib"
|
|
||||||
|
|
||||||
MAX_ATTACK_HITS :: 32
|
|
||||||
|
|
||||||
Attack :: struct {
|
|
||||||
using base: BaseEntity,
|
|
||||||
parent: Handle,
|
|
||||||
dmg: i32,
|
|
||||||
radius: f32,
|
|
||||||
lifetime: f32,
|
|
||||||
hit: [MAX_ATTACK_HITS]Handle,
|
|
||||||
hit_count: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
init_attack :: proc(state: ^GameState, a: Attack) -> Handle {
|
|
||||||
return spawn(state, Entity{v = a})
|
|
||||||
}
|
|
||||||
|
|
||||||
attack_update :: proc(state: ^GameState, a: ^Attack, h: Handle, dt: f32) {
|
|
||||||
if a.parent != {} {
|
|
||||||
if pe := get_entity(state, a.parent); pe != nil {
|
|
||||||
a.pos = get_base(pe).pos
|
|
||||||
} else {
|
|
||||||
a.marked = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
it := hm.iterator_make(&state.entities)
|
|
||||||
for e, eh in hm.iterate(&it) {
|
|
||||||
#partial switch &v in e.v {
|
|
||||||
case Enemy:
|
|
||||||
if v.hp <= 0 || v.marked do continue
|
|
||||||
if rl.Vector2Distance(a.pos, v.pos) <= a.radius && !attack_has_hit(a, eh) {
|
|
||||||
v.hp -= a.dmg
|
|
||||||
attack_add_hit(a, eh)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.lifetime > 0 {
|
|
||||||
a.lifetime -= dt
|
|
||||||
} else {
|
|
||||||
a.marked = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
attack_has_hit :: proc(a: ^Attack, h: Handle) -> bool {
|
|
||||||
for i in 0 ..< a.hit_count {
|
|
||||||
if a.hit[i] == h do return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
attack_add_hit :: proc(a: ^Attack, h: Handle) {
|
|
||||||
if a.hit_count >= MAX_ATTACK_HITS do return
|
|
||||||
a.hit[a.hit_count] = h
|
|
||||||
a.hit_count += 1
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
package main
|
|
||||||
import rl "vendor:raylib"
|
|
||||||
|
|
||||||
Enemy :: struct {
|
|
||||||
using actor: Actor,
|
|
||||||
touch_dmg: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enemy_update :: proc(state: ^GameState, e: ^Enemy, h: Handle, dt: f32) {
|
|
||||||
pe := get_entity(state, state.player)
|
|
||||||
if pe != nil {
|
|
||||||
dir := get_base(pe).pos - e.pos
|
|
||||||
e.vel = rl.Vector2Normalize(dir) * e.speed
|
|
||||||
}
|
|
||||||
|
|
||||||
if e.hp <= 0 && !e.marked {
|
|
||||||
e.marked = true
|
|
||||||
spawn(state, make_ground_item(e.pos))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import hm "core:container/handle_map"
|
|
||||||
import rl "vendor:raylib"
|
|
||||||
|
|
||||||
MAX_GFX :: 2
|
|
||||||
|
|
||||||
Handle :: hm.Handle32
|
|
||||||
|
|
||||||
MAX_ENTITIES :: 1024
|
|
||||||
|
|
||||||
BaseEntity :: struct {
|
|
||||||
pos: rl.Vector2,
|
|
||||||
vel: rl.Vector2,
|
|
||||||
col_size: f32,
|
|
||||||
marked: bool,
|
|
||||||
gfx: [MAX_GFX]Graphics,
|
|
||||||
}
|
|
||||||
|
|
||||||
Actor :: struct {
|
|
||||||
using base: BaseEntity,
|
|
||||||
hp: i32,
|
|
||||||
max_hp: i32,
|
|
||||||
speed: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
EntityVariant :: union {
|
|
||||||
Player,
|
|
||||||
Enemy,
|
|
||||||
GroundItem,
|
|
||||||
FX,
|
|
||||||
Attack,
|
|
||||||
}
|
|
||||||
|
|
||||||
Entity :: struct {
|
|
||||||
handle: Handle,
|
|
||||||
v: EntityVariant,
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
case FX:
|
|
||||||
return cast(^BaseEntity)&v
|
|
||||||
case Attack:
|
|
||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
|
|
||||||
FX :: struct {
|
|
||||||
using base: BaseEntity,
|
|
||||||
parent: Handle,
|
|
||||||
lifetime: f32,
|
|
||||||
permanent: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
init_fx :: proc(state: ^GameState, fx: FX) -> Handle {
|
|
||||||
return spawn(state, Entity{v = fx})
|
|
||||||
}
|
|
||||||
|
|
||||||
fx_update :: proc(state: ^GameState, fx: ^FX, h: Handle, dt: f32) {
|
|
||||||
if fx.parent != {} {
|
|
||||||
if pe := get_entity(state, fx.parent); pe != nil {
|
|
||||||
fx.pos = get_base(pe).pos
|
|
||||||
} else {
|
|
||||||
fx.marked = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !fx.permanent {
|
|
||||||
if fx.lifetime > 0 {
|
|
||||||
fx.lifetime -= dt
|
|
||||||
} else {
|
|
||||||
fx.marked = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import rl "vendor:raylib"
|
|
||||||
|
|
||||||
Graphics :: union {
|
|
||||||
GfxNone,
|
|
||||||
GfxCircle,
|
|
||||||
GfxRect,
|
|
||||||
GfxRing,
|
|
||||||
GfxCircleLines,
|
|
||||||
GfxTexture,
|
|
||||||
}
|
|
||||||
|
|
||||||
GfxNone :: struct {}
|
|
||||||
|
|
||||||
GfxCircle :: struct {
|
|
||||||
pos: rl.Vector2,
|
|
||||||
radius: f32,
|
|
||||||
color: rl.Color,
|
|
||||||
}
|
|
||||||
|
|
||||||
GfxRect :: struct {
|
|
||||||
pos: rl.Vector2,
|
|
||||||
size: rl.Vector2,
|
|
||||||
color: rl.Color,
|
|
||||||
}
|
|
||||||
|
|
||||||
GfxRing :: struct {
|
|
||||||
pos: rl.Vector2,
|
|
||||||
inner_radius: f32,
|
|
||||||
outer_radius: f32,
|
|
||||||
color: rl.Color,
|
|
||||||
}
|
|
||||||
|
|
||||||
GfxCircleLines :: struct {
|
|
||||||
pos: rl.Vector2,
|
|
||||||
radius: f32,
|
|
||||||
color: rl.Color,
|
|
||||||
}
|
|
||||||
|
|
||||||
GfxTexture :: struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
gfx_draw :: proc(pos: rl.Vector2, g: Graphics) {
|
|
||||||
#partial switch v in g {
|
|
||||||
case GfxCircle:
|
|
||||||
rl.DrawCircleV(pos + v.pos, v.radius, v.color)
|
|
||||||
case GfxRect:
|
|
||||||
rl.DrawRectangleV(pos - v.size / 2 + v.pos, v.size, v.color)
|
|
||||||
case GfxRing:
|
|
||||||
rl.DrawRing(pos + v.pos, v.inner_radius, v.outer_radius, 0, 360, 0, v.color)
|
|
||||||
case GfxCircleLines:
|
|
||||||
rl.DrawCircleLinesV(pos + v.pos, v.radius, v.color)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
package main
|
|
||||||
import rl "vendor:raylib"
|
|
||||||
|
|
||||||
GroundItem :: struct {
|
|
||||||
using base: BaseEntity,
|
|
||||||
heal: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
item_update :: proc(state: ^GameState, i: ^GroundItem, h: Handle, dt: f32) {
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import rl "vendor:raylib"
|
|
||||||
|
|
||||||
SCREEN_W :: 1280
|
|
||||||
SCREEN_H :: 720
|
|
||||||
TARGET_FPS :: 60
|
|
||||||
|
|
||||||
|
|
||||||
main :: proc() {
|
|
||||||
rl.InitWindow(SCREEN_W, SCREEN_H, "Iso Arena 3D")
|
|
||||||
rl.SetTargetFPS(TARGET_FPS)
|
|
||||||
defer rl.CloseWindow()
|
|
||||||
|
|
||||||
camera := rl.Camera3D {
|
|
||||||
position = {0, 80, 80},
|
|
||||||
target = {0, 0, 0},
|
|
||||||
up = {0, 1, 0},
|
|
||||||
fovy = 400,
|
|
||||||
projection = .ORTHOGRAPHIC,
|
|
||||||
}
|
|
||||||
|
|
||||||
state := GameState {
|
|
||||||
camera = camera,
|
|
||||||
}
|
|
||||||
state.collisions = make([dynamic]CollisionPair)
|
|
||||||
defer delete(state.collisions)
|
|
||||||
|
|
||||||
state.player = spawn(&state, make_player())
|
|
||||||
spawn(&state, make_enemy({200, 200}))
|
|
||||||
spawn(&state, make_enemy({-200, 200}))
|
|
||||||
spawn(&state, make_enemy({200, -200}))
|
|
||||||
|
|
||||||
for !rl.WindowShouldClose() {
|
|
||||||
dt := rl.GetFrameTime()
|
|
||||||
|
|
||||||
sys_input(&state, dt)
|
|
||||||
sys_update(&state, dt)
|
|
||||||
sys_sweep(&state)
|
|
||||||
|
|
||||||
if pe := get_entity(&state, state.player); pe != nil {
|
|
||||||
p := get_base(pe).pos
|
|
||||||
state.camera.target = {p.x, 0, p.y}
|
|
||||||
state.camera.position = state.camera.target + {600, 600, 600}
|
|
||||||
}
|
|
||||||
sys_draw(&state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import rl "vendor:raylib"
|
|
||||||
|
|
||||||
TILE_W :: 64
|
|
||||||
MAP_SIZE :: 20
|
|
||||||
|
|
||||||
depth_key :: #force_inline proc(pos: rl.Vector2) -> f32 {
|
|
||||||
return pos.x + pos.y
|
|
||||||
}
|
|
||||||
|
|
||||||
draw_grid_3d :: proc() {
|
|
||||||
middle := MAP_SIZE / 2
|
|
||||||
for x in 0 ..< MAP_SIZE {
|
|
||||||
for y in 0 ..< MAP_SIZE {
|
|
||||||
wx := f32(x - middle) * TILE_W
|
|
||||||
wy := f32(y - middle) * TILE_W
|
|
||||||
color := ((x + y) % 2 == 0) ? rl.Color{64, 64, 76, 255} : rl.Color{51, 51, 63, 255}
|
|
||||||
rl.DrawPlane({wx, 0, wy}, {TILE_W, TILE_W}, color)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import hm "core:container/handle_map"
|
|
||||||
import rl "vendor:raylib"
|
|
||||||
|
|
||||||
Player :: struct {
|
|
||||||
using actor: Actor,
|
|
||||||
attack_range: f32,
|
|
||||||
attack_dmg: i32,
|
|
||||||
attack_cd: f32,
|
|
||||||
attack_timer: f32,
|
|
||||||
invuln_timer: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
PlayerInput :: struct {
|
|
||||||
move: rl.Vector2,
|
|
||||||
attack: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
make_player :: proc() -> Entity {
|
|
||||||
return Entity {
|
|
||||||
v = Player {
|
|
||||||
actor = Actor {
|
|
||||||
base = BaseEntity {
|
|
||||||
pos = {0, 0},
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sys_input :: proc(state: ^GameState, dt: f32) {
|
|
||||||
input: PlayerInput
|
|
||||||
|
|
||||||
if rl.IsKeyDown(.W) || rl.IsKeyDown(.UP) do input.move += {-1, -1}
|
|
||||||
if rl.IsKeyDown(.S) || rl.IsKeyDown(.DOWN) do input.move += {1, 1}
|
|
||||||
if rl.IsKeyDown(.A) || rl.IsKeyDown(.LEFT) do input.move += {-1, 1}
|
|
||||||
if rl.IsKeyDown(.D) || rl.IsKeyDown(.RIGHT) do input.move += {1, -1}
|
|
||||||
input.attack = rl.IsKeyPressed(.SPACE)
|
|
||||||
|
|
||||||
player_input(state, input, dt)
|
|
||||||
}
|
|
||||||
|
|
||||||
player_input :: proc(state: ^GameState, input: PlayerInput, dt: f32) {
|
|
||||||
_, p := get_player(state)
|
|
||||||
if p == nil do return
|
|
||||||
|
|
||||||
p.vel = rl.Vector2Normalize(input.move) * p.speed
|
|
||||||
|
|
||||||
if p.attack_timer > 0 do p.attack_timer -= dt
|
|
||||||
if input.attack && p.attack_timer <= 0 {
|
|
||||||
p.attack_timer = p.attack_cd
|
|
||||||
|
|
||||||
init_attack(
|
|
||||||
state,
|
|
||||||
Attack {
|
|
||||||
base = BaseEntity {
|
|
||||||
gfx = {
|
|
||||||
0 = GfxRing {
|
|
||||||
inner_radius = p.attack_range * 0.7,
|
|
||||||
outer_radius = p.attack_range,
|
|
||||||
color = {255, 255, 255, 40},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
parent = state.player,
|
|
||||||
dmg = p.attack_dmg,
|
|
||||||
radius = p.attack_range,
|
|
||||||
lifetime = p.attack_cd,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
player_update :: proc(state: ^GameState, p: ^Player, h: Handle, dt: f32) {
|
|
||||||
if p.invuln_timer > 0 do p.invuln_timer -= dt
|
|
||||||
}
|
|
||||||
-202
@@ -1,202 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import hm "core:container/handle_map"
|
|
||||||
import rl "vendor:raylib"
|
|
||||||
|
|
||||||
SQRT2_INV :: 0.7071067811865476
|
|
||||||
|
|
||||||
CollisionPair :: struct {
|
|
||||||
a: Handle,
|
|
||||||
b: Handle,
|
|
||||||
}
|
|
||||||
|
|
||||||
GameState :: struct {
|
|
||||||
player: Handle,
|
|
||||||
camera: rl.Camera3D,
|
|
||||||
entities: hm.Static_Handle_Map(MAX_ENTITIES, Entity, 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
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
spawn :: proc(state: ^GameState, e: Entity) -> Handle {
|
|
||||||
return hm.add(&state.entities, e)
|
|
||||||
}
|
|
||||||
|
|
||||||
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:
|
|
||||||
item_update(state, &v, h, dt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
it2 := hm.iterator_make(&state.entities)
|
|
||||||
for e, _ in hm.iterate(&it2) {
|
|
||||||
b := get_base(e)
|
|
||||||
b.pos += b.vel * dt
|
|
||||||
}
|
|
||||||
|
|
||||||
it3 := hm.iterator_make(&state.entities)
|
|
||||||
for e, h in hm.iterate(&it3) {
|
|
||||||
#partial switch &v in e.v {
|
|
||||||
case FX:
|
|
||||||
fx_update(state, &v, h, dt)
|
|
||||||
case Attack:
|
|
||||||
attack_update(state, &v, h, dt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve_collisions(state)
|
|
||||||
|
|
||||||
apply_collision_effects(state)
|
|
||||||
}
|
|
||||||
|
|
||||||
sys_sweep :: proc(state: ^GameState) {
|
|
||||||
it := hm.iterator_make(&state.entities)
|
|
||||||
for e, h in hm.iterate(&it) {
|
|
||||||
if get_base(e).marked {
|
|
||||||
hm.remove(&state.entities, h)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
entity_draw :: proc(state: ^GameState, e: ^Entity) {
|
|
||||||
b := get_base(e)
|
|
||||||
sp := rl.GetWorldToScreen({b.pos.x, 0, b.pos.y}, state.camera)
|
|
||||||
scale := SCREEN_H / state.camera.fovy
|
|
||||||
|
|
||||||
#partial switch v in b.gfx[0] {
|
|
||||||
case GfxCircle:
|
|
||||||
rl.DrawCircleV(sp, v.radius * scale, v.color)
|
|
||||||
case:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ground_effect_draw :: proc(state: ^GameState, e: ^Entity) {
|
|
||||||
b := get_base(e)
|
|
||||||
|
|
||||||
#partial switch v in b.gfx[0] {
|
|
||||||
case GfxRing:
|
|
||||||
rl.DrawCylinder({b.pos.x, 0.02, b.pos.y}, v.outer_radius, v.outer_radius, 0.04, 48, v.color)
|
|
||||||
case:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sys_draw :: proc(state: ^GameState) {
|
|
||||||
rl.BeginDrawing()
|
|
||||||
rl.ClearBackground({20, 20, 30, 255})
|
|
||||||
|
|
||||||
rl.BeginMode3D(state.camera)
|
|
||||||
draw_grid_3d()
|
|
||||||
it := hm.iterator_make(&state.entities)
|
|
||||||
for e, _ in hm.iterate(&it) {
|
|
||||||
ground_effect_draw(state, e)
|
|
||||||
}
|
|
||||||
rl.EndMode3D()
|
|
||||||
|
|
||||||
scale := SCREEN_H / state.camera.fovy
|
|
||||||
it2 := hm.iterator_make(&state.entities)
|
|
||||||
for e, _ in hm.iterate(&it2) {
|
|
||||||
entity_draw(state, e)
|
|
||||||
if a := get_actor(e); a != nil {
|
|
||||||
b := get_base(e)
|
|
||||||
sp := rl.GetWorldToScreen({b.pos.x, 0, b.pos.y}, state.camera)
|
|
||||||
hp_ratio := f32(a.hp) / f32(a.max_hp)
|
|
||||||
bar_w := b.col_size * 3 * scale
|
|
||||||
bar_pos := sp - rl.Vector2{bar_w / 2, b.col_size * 2 * scale + 12}
|
|
||||||
rl.DrawRectangleV(bar_pos, {bar_w, 5}, {60, 60, 60, 255})
|
|
||||||
rl.DrawRectangleV(bar_pos, {bar_w * hp_ratio, 5}, {220, 40, 40, 255})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ui_draw(state)
|
|
||||||
rl.EndDrawing()
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve_collisions :: proc(state: ^GameState) {
|
|
||||||
clear(&state.collisions)
|
|
||||||
|
|
||||||
hs: [MAX_ENTITIES]Handle
|
|
||||||
n := 0
|
|
||||||
it := hm.iterator_make(&state.entities)
|
|
||||||
for _, h in hm.iterate(&it) {
|
|
||||||
hs[n] = h
|
|
||||||
n += 1
|
|
||||||
}
|
|
||||||
|
|
||||||
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]})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
apply_collision_effects :: proc(state: ^GameState) {
|
|
||||||
_, p := get_player(state)
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
#partial switch &v in e.v {
|
|
||||||
case Enemy:
|
|
||||||
if p != nil && p.invuln_timer <= 0 {
|
|
||||||
p.hp -= v.touch_dmg
|
|
||||||
p.invuln_timer = 0.5
|
|
||||||
if p.hp <= 0 do p.hp = 0
|
|
||||||
}
|
|
||||||
case GroundItem:
|
|
||||||
if p != nil && !v.marked {
|
|
||||||
p.hp = min(p.hp + v.heal, p.max_hp)
|
|
||||||
v.marked = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
package main
|
|
||||||
import hm "core:container/handle_map"
|
|
||||||
|
|
||||||
import rl "vendor:raylib"
|
|
||||||
|
|
||||||
ui_draw_world_hp_bar :: proc(b: ^BaseEntity, actor: ^Actor) {
|
|
||||||
hp_ratio := f32(actor.hp) / f32(actor.max_hp)
|
|
||||||
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: ^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})
|
|
||||||
}
|
|
||||||
+61
-28
@@ -1,6 +1,5 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import "core:fmt"
|
|
||||||
import rl "vendor:raylib"
|
import rl "vendor:raylib"
|
||||||
|
|
||||||
Graphics :: union {
|
Graphics :: union {
|
||||||
@@ -44,11 +43,11 @@ GfxTexture :: struct {}
|
|||||||
|
|
||||||
GfxAnimatedSprite :: struct {
|
GfxAnimatedSprite :: struct {
|
||||||
pos: rl.Vector2,
|
pos: rl.Vector2,
|
||||||
rect: rl.Rectangle,
|
atlas: ^Atlas,
|
||||||
tex: ^rl.Texture2D,
|
frame: i32,
|
||||||
frame: u32,
|
|
||||||
speed: f32,
|
speed: f32,
|
||||||
current_time: f32,
|
current_time: f32,
|
||||||
|
update: proc(g: ^GfxAnimatedSprite, dt: f32),
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayerTexFiles :: enum {
|
PlayerTexFiles :: enum {
|
||||||
@@ -60,33 +59,65 @@ PlayerTexFiles :: enum {
|
|||||||
ATTACK,
|
ATTACK,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Atlas :: struct {
|
||||||
GraphicsAssetStore :: struct {
|
tex: rl.Texture2D,
|
||||||
playerTex: map[PlayerTexFiles]rl.Texture2D,
|
frame_size: rl.Vector2,
|
||||||
|
cols: i32,
|
||||||
|
rows: i32,
|
||||||
|
frames: i32,
|
||||||
|
get_offset: proc(a: ^Atlas, f: i32) -> rl.Vector2,
|
||||||
}
|
}
|
||||||
|
|
||||||
gfx_get :: proc(g: ^GraphicsAssetStore, f: PlayerTexFiles) -> ^rl.Texture2D {
|
|
||||||
|
GraphicsAssetStore :: struct {
|
||||||
|
playerTex: map[PlayerTexFiles]Atlas,
|
||||||
|
}
|
||||||
|
|
||||||
|
player_atlas_get :: proc(g: ^GraphicsAssetStore, f: PlayerTexFiles) -> ^Atlas {
|
||||||
return &g.playerTex[f]
|
return &g.playerTex[f]
|
||||||
}
|
}
|
||||||
|
|
||||||
gfx_init :: proc() -> GraphicsAssetStore {
|
gfx_init :: proc() -> GraphicsAssetStore {
|
||||||
st := GraphicsAssetStore{}
|
st := GraphicsAssetStore{}
|
||||||
st.playerTex = make(map[PlayerTexFiles]rl.Texture2D)
|
st.playerTex = make(map[PlayerTexFiles]Atlas)
|
||||||
|
|
||||||
st.playerTex[.IDLE] = rl.LoadTexture("assets/3rdparty/player_idle.png")
|
|
||||||
|
|
||||||
|
st.playerTex[.IDLE] = init_atlas(
|
||||||
|
texture_path = "assets/3rdparty/player_idle.png",
|
||||||
|
size = rl.Vector2{64, 64},
|
||||||
|
)
|
||||||
return st
|
return st
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init_atlas :: proc(texture_path: cstring, size: rl.Vector2) -> Atlas {
|
||||||
|
atlas := Atlas {
|
||||||
|
tex = rl.LoadTexture(texture_path),
|
||||||
|
frame_size = size,
|
||||||
|
get_offset = get_offset,
|
||||||
|
}
|
||||||
|
|
||||||
|
atlas.rows = atlas.tex.height / (i32(size.y))
|
||||||
|
atlas.cols = atlas.tex.width / i32(size.x)
|
||||||
|
atlas.frames = atlas.cols * atlas.rows
|
||||||
|
return atlas
|
||||||
|
}
|
||||||
|
|
||||||
|
get_offset :: proc(a: ^Atlas, f: i32) -> rl.Vector2 {
|
||||||
|
|
||||||
|
col := f % a.cols
|
||||||
|
row := f / a.cols
|
||||||
|
|
||||||
|
return rl.Vector2{f32(col) * a.frame_size.x, f32(row) * a.frame_size.y}
|
||||||
|
}
|
||||||
|
|
||||||
gfx_free :: proc(g: GraphicsAssetStore) {
|
gfx_free :: proc(g: GraphicsAssetStore) {
|
||||||
for _, tex in g.playerTex {
|
for _, tex in g.playerTex {
|
||||||
rl.UnloadTexture(tex)
|
rl.UnloadTexture(tex.tex)
|
||||||
}
|
}
|
||||||
delete(g.playerTex)
|
delete(g.playerTex)
|
||||||
}
|
}
|
||||||
|
|
||||||
gfx_draw :: proc(pos: rl.Vector2, g: ^Graphics) {
|
gfx_draw :: proc(pos: rl.Vector2, g: ^Graphics) {
|
||||||
#partial switch &v in g {
|
#partial switch v in g {
|
||||||
case GfxCircle:
|
case GfxCircle:
|
||||||
rl.DrawCircleV(pos + v.pos, v.radius, v.color)
|
rl.DrawCircleV(pos + v.pos, v.radius, v.color)
|
||||||
case GfxRect:
|
case GfxRect:
|
||||||
@@ -96,29 +127,31 @@ gfx_draw :: proc(pos: rl.Vector2, g: ^Graphics) {
|
|||||||
case GfxCircleLines:
|
case GfxCircleLines:
|
||||||
rl.DrawCircleLinesV(pos + v.pos, v.radius, v.color)
|
rl.DrawCircleLinesV(pos + v.pos, v.radius, v.color)
|
||||||
case GfxAnimatedSprite:
|
case GfxAnimatedSprite:
|
||||||
fmt.println(pos.x, pos.y)
|
|
||||||
//TODO: Count from L to R row-wize
|
//TODO: Count from L to R row-wize
|
||||||
v.rect.x = v.rect.width * f32(v.frame)
|
frame_offset := v.atlas->get_offset(v.frame)
|
||||||
v.rect.y = v.rect.height * f32(v.frame)
|
|
||||||
rl.DrawTexturePro(
|
rl.DrawTexturePro(
|
||||||
v.tex^,
|
v.atlas^.tex,
|
||||||
v.rect,
|
|
||||||
rl.Rectangle {
|
rl.Rectangle {
|
||||||
pos.x - v.rect.width * 3 / 2,
|
frame_offset.x,
|
||||||
pos.y - v.rect.height * 3 / 2,
|
frame_offset.y,
|
||||||
v.rect.width * 3,
|
v.atlas.frame_size.x,
|
||||||
v.rect.height * 3,
|
v.atlas.frame_size.y,
|
||||||
|
},
|
||||||
|
rl.Rectangle {
|
||||||
|
pos.x - v.atlas.frame_size.x * 3 / 2,
|
||||||
|
pos.y - v.atlas.frame_size.y * 3 / 2,
|
||||||
|
v.atlas.frame_size.x * 3,
|
||||||
|
v.atlas.frame_size.y * 3,
|
||||||
},
|
},
|
||||||
rl.Vector2{0, 0},
|
rl.Vector2{0, 0},
|
||||||
0,
|
0,
|
||||||
rl.WHITE,
|
rl.WHITE,
|
||||||
)
|
)
|
||||||
v.current_time += rl.GetFrameTime()
|
// v.current_time += rl.GetFrameTime()
|
||||||
if v.current_time > v.speed {
|
// if v.current_time > v.speed {
|
||||||
v.current_time = v.current_time - v.speed
|
// v.current_time = v.current_time - v.speed
|
||||||
v.frame += 1
|
// v.frame += 1
|
||||||
}
|
// }
|
||||||
|
|
||||||
// rl.DrawTextureRec(v.tex, v.rect, rl.Vector2{pos.x, pos.y}, rl.Color{})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ import rl "vendor:raylib"
|
|||||||
|
|
||||||
SCREEN_W :: 1280
|
SCREEN_W :: 1280
|
||||||
SCREEN_H :: 720
|
SCREEN_H :: 720
|
||||||
TARGET_FPS :: 60
|
TARGET_FPS :: 120 // test edit
|
||||||
|
|
||||||
main :: proc() {
|
main :: proc() {
|
||||||
rl.InitWindow(SCREEN_W, SCREEN_H, "ARPG Demo")
|
rl.InitWindow(SCREEN_W, SCREEN_H, "ARPG Demo")
|
||||||
|
|||||||
+19
-3
@@ -66,10 +66,10 @@ make_player :: proc(gfx: ^GraphicsAssetStore) -> Entity {
|
|||||||
0 = GfxCircle{pos = {}, radius = 14, color = {80, 220, 100, 255}},
|
0 = GfxCircle{pos = {}, radius = 14, color = {80, 220, 100, 255}},
|
||||||
1 = GfxAnimatedSprite {
|
1 = GfxAnimatedSprite {
|
||||||
pos = {},
|
pos = {},
|
||||||
rect = {0, 0, 64, 64},
|
atlas = player_atlas_get(gfx, .IDLE),
|
||||||
tex = gfx_get(gfx, .IDLE),
|
|
||||||
frame = 0,
|
frame = 0,
|
||||||
speed = 1,
|
speed = 0.5,
|
||||||
|
update = update_player_sprite,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
p.hp = p.base_max_hp
|
p.hp = p.base_max_hp
|
||||||
@@ -78,6 +78,15 @@ make_player :: proc(gfx: ^GraphicsAssetStore) -> Entity {
|
|||||||
return Entity{v = p}
|
return Entity{v = p}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
update_player_sprite :: proc(g: ^GfxAnimatedSprite, dt: f32) {
|
||||||
|
g.current_time += dt
|
||||||
|
if g.current_time > g.speed {
|
||||||
|
g.current_time = g.current_time - g.speed
|
||||||
|
g.frame += 1
|
||||||
|
if g.frame > 3 do g.frame = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ─── Итоговые статы из экипировки ───
|
// ─── Итоговые статы из экипировки ───
|
||||||
|
|
||||||
player_total_dmg :: proc(p: ^Player) -> i32 {
|
player_total_dmg :: proc(p: ^Player) -> i32 {
|
||||||
@@ -233,4 +242,11 @@ player_update :: proc(state: ^GameState, p: ^Player, h: Handle, dt: f32) {
|
|||||||
for &s in p.skills {
|
for &s in p.skills {
|
||||||
if s.timer > 0 do s.timer -= dt
|
if s.timer > 0 do s.timer -= dt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for &gfx in p.gfx {
|
||||||
|
#partial switch &v in gfx {
|
||||||
|
case GfxAnimatedSprite:
|
||||||
|
v->update(dt)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user