diff --git a/.nvim.lua b/.nvim.lua new file mode 100644 index 0000000..3077a27 --- /dev/null +++ b/.nvim.lua @@ -0,0 +1,9 @@ +-- .nvim.lua — локальные настройки проекта +-- Создан :LocalInit. Автоматически грузится nvim-config-local (первый раз — через :ConfigLocalTrust). + +-- Раннер: команда, выполняемая в нижнем терминале edgy +-- по :Run или dr. Можно строкой или функцией. +require("core.runner").setup({ + cmd = "make run", + focus = true, -- фокус на терминал при запуске +}) diff --git a/Makefile b/Makefile index 3f58322..e0ee7c6 100644 --- a/Makefile +++ b/Makefile @@ -1,2 +1,2 @@ run: - odin run ./src/ + odin run ./src3/ diff --git a/info.md b/info.md index ca6ae79..d7263ac 100644 --- a/info.md +++ b/info.md @@ -4,3 +4,4 @@ t.git 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-swordsman-1-3-level-pixel-top-down-sprite-character-pack/ + diff --git a/src/enemy.odin b/src/enemy.odin deleted file mode 100644 index 736856b..0000000 --- a/src/enemy.odin +++ /dev/null @@ -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)) - } -} diff --git a/src/entity.odin b/src/entity.odin deleted file mode 100644 index 7cbb09d..0000000 --- a/src/entity.odin +++ /dev/null @@ -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 -} diff --git a/src/fx.odin b/src/fx.odin deleted file mode 100644 index 6eb8414..0000000 --- a/src/fx.odin +++ /dev/null @@ -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 - } - } -} diff --git a/src/gfx.odin b/src/gfx.odin deleted file mode 100644 index e5af18c..0000000 --- a/src/gfx.odin +++ /dev/null @@ -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) - } -} diff --git a/src/item.odin b/src/item.odin deleted file mode 100644 index b58895f..0000000 --- a/src/item.odin +++ /dev/null @@ -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) { -} diff --git a/src/main.odin b/src/main.odin deleted file mode 100644 index e612be1..0000000 --- a/src/main.odin +++ /dev/null @@ -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) - } -} diff --git a/src/map.odin b/src/map.odin deleted file mode 100644 index 50acd56..0000000 --- a/src/map.odin +++ /dev/null @@ -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) - } - } -} diff --git a/src/player.odin b/src/player.odin deleted file mode 100644 index 42ebb0e..0000000 --- a/src/player.odin +++ /dev/null @@ -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 -} diff --git a/src/scene.odin b/src/scene.odin deleted file mode 100644 index 3af4b92..0000000 --- a/src/scene.odin +++ /dev/null @@ -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 - } - } - } -} diff --git a/src/ui.odin b/src/ui.odin deleted file mode 100644 index 4645041..0000000 --- a/src/ui.odin +++ /dev/null @@ -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}) -} diff --git a/src2/attack.odin b/src2/attack.odin deleted file mode 100644 index bb5255b..0000000 --- a/src2/attack.odin +++ /dev/null @@ -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 -} diff --git a/src2/enemy.odin b/src2/enemy.odin deleted file mode 100644 index 736856b..0000000 --- a/src2/enemy.odin +++ /dev/null @@ -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)) - } -} diff --git a/src2/entity.odin b/src2/entity.odin deleted file mode 100644 index 3e9b1f6..0000000 --- a/src2/entity.odin +++ /dev/null @@ -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 -} diff --git a/src2/fx.odin b/src2/fx.odin deleted file mode 100644 index 6eb8414..0000000 --- a/src2/fx.odin +++ /dev/null @@ -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 - } - } -} diff --git a/src2/gfx.odin b/src2/gfx.odin deleted file mode 100644 index e5af18c..0000000 --- a/src2/gfx.odin +++ /dev/null @@ -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) - } -} diff --git a/src2/item.odin b/src2/item.odin deleted file mode 100644 index b58895f..0000000 --- a/src2/item.odin +++ /dev/null @@ -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) { -} diff --git a/src2/main.odin b/src2/main.odin deleted file mode 100644 index ef76296..0000000 --- a/src2/main.odin +++ /dev/null @@ -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) - } -} diff --git a/src2/map.odin b/src2/map.odin deleted file mode 100644 index d1fe328..0000000 --- a/src2/map.odin +++ /dev/null @@ -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) - } - } -} diff --git a/src2/player.odin b/src2/player.odin deleted file mode 100644 index b0b8100..0000000 --- a/src2/player.odin +++ /dev/null @@ -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 -} diff --git a/src2/scene.odin b/src2/scene.odin deleted file mode 100644 index ee5d23d..0000000 --- a/src2/scene.odin +++ /dev/null @@ -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 - } - } - } -} diff --git a/src2/ui.odin b/src2/ui.odin deleted file mode 100644 index 4645041..0000000 --- a/src2/ui.odin +++ /dev/null @@ -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}) -} diff --git a/src3/gfx.odin b/src3/gfx.odin index 59e1b1e..0f22f92 100644 --- a/src3/gfx.odin +++ b/src3/gfx.odin @@ -1,6 +1,5 @@ package main -import "core:fmt" import rl "vendor:raylib" Graphics :: union { @@ -44,11 +43,11 @@ GfxTexture :: struct {} GfxAnimatedSprite :: struct { pos: rl.Vector2, - rect: rl.Rectangle, - tex: ^rl.Texture2D, - frame: u32, + atlas: ^Atlas, + frame: i32, speed: f32, current_time: f32, + update: proc(g: ^GfxAnimatedSprite, dt: f32), } PlayerTexFiles :: enum { @@ -60,33 +59,65 @@ PlayerTexFiles :: enum { ATTACK, } - -GraphicsAssetStore :: struct { - playerTex: map[PlayerTexFiles]rl.Texture2D, +Atlas :: struct { + tex: 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] } gfx_init :: proc() -> GraphicsAssetStore { st := GraphicsAssetStore{} - st.playerTex = make(map[PlayerTexFiles]rl.Texture2D) - - st.playerTex[.IDLE] = rl.LoadTexture("assets/3rdparty/player_idle.png") + st.playerTex = make(map[PlayerTexFiles]Atlas) + st.playerTex[.IDLE] = init_atlas( + texture_path = "assets/3rdparty/player_idle.png", + size = rl.Vector2{64, 64}, + ) 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) { for _, tex in g.playerTex { - rl.UnloadTexture(tex) + rl.UnloadTexture(tex.tex) } delete(g.playerTex) } gfx_draw :: proc(pos: rl.Vector2, g: ^Graphics) { - #partial switch &v in g { + #partial switch v in g { case GfxCircle: rl.DrawCircleV(pos + v.pos, v.radius, v.color) case GfxRect: @@ -96,29 +127,31 @@ gfx_draw :: proc(pos: rl.Vector2, g: ^Graphics) { case GfxCircleLines: rl.DrawCircleLinesV(pos + v.pos, v.radius, v.color) case GfxAnimatedSprite: - fmt.println(pos.x, pos.y) //TODO: Count from L to R row-wize - v.rect.x = v.rect.width * f32(v.frame) - v.rect.y = v.rect.height * f32(v.frame) + frame_offset := v.atlas->get_offset(v.frame) rl.DrawTexturePro( - v.tex^, - v.rect, + v.atlas^.tex, rl.Rectangle { - pos.x - v.rect.width * 3 / 2, - pos.y - v.rect.height * 3 / 2, - v.rect.width * 3, - v.rect.height * 3, + frame_offset.x, + frame_offset.y, + v.atlas.frame_size.x, + 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}, 0, rl.WHITE, ) - v.current_time += rl.GetFrameTime() - if v.current_time > v.speed { - v.current_time = v.current_time - v.speed - v.frame += 1 - } + // v.current_time += rl.GetFrameTime() + // if v.current_time > v.speed { + // v.current_time = v.current_time - v.speed + // v.frame += 1 + // } - // rl.DrawTextureRec(v.tex, v.rect, rl.Vector2{pos.x, pos.y}, rl.Color{}) } } diff --git a/src3/main.odin b/src3/main.odin index 45bc8b4..d11fb52 100644 --- a/src3/main.odin +++ b/src3/main.odin @@ -4,7 +4,7 @@ import rl "vendor:raylib" SCREEN_W :: 1280 SCREEN_H :: 720 -TARGET_FPS :: 60 +TARGET_FPS :: 120 // test edit main :: proc() { rl.InitWindow(SCREEN_W, SCREEN_H, "ARPG Demo") diff --git a/src3/player.odin b/src3/player.odin index e99f61e..f88cf54 100644 --- a/src3/player.odin +++ b/src3/player.odin @@ -66,10 +66,10 @@ make_player :: proc(gfx: ^GraphicsAssetStore) -> Entity { 0 = GfxCircle{pos = {}, radius = 14, color = {80, 220, 100, 255}}, 1 = GfxAnimatedSprite { pos = {}, - rect = {0, 0, 64, 64}, - tex = gfx_get(gfx, .IDLE), + atlas = player_atlas_get(gfx, .IDLE), frame = 0, - speed = 1, + speed = 0.5, + update = update_player_sprite, }, } p.hp = p.base_max_hp @@ -78,6 +78,15 @@ make_player :: proc(gfx: ^GraphicsAssetStore) -> Entity { 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 { @@ -233,4 +242,11 @@ player_update :: proc(state: ^GameState, p: ^Player, h: Handle, dt: f32) { for &s in p.skills { if s.timer > 0 do s.timer -= dt } + + for &gfx in p.gfx { + #partial switch &v in gfx { + case GfxAnimatedSprite: + v->update(dt) + } + } }