|
|
@@ -1,6 +1,7 @@
|
|
|
package main
|
|
|
|
|
|
import "base:runtime"
|
|
|
+import "core:encoding/uuid"
|
|
|
import "core:fmt"
|
|
|
import "core:image/png"
|
|
|
import "core:math"
|
|
|
@@ -86,25 +87,6 @@ Item_Info :: struct {
|
|
|
// Global Item Database (mutable to set sprite_ids at runtime)
|
|
|
item_db: [Item_Kind]Item_Info
|
|
|
|
|
|
-Box_Id :: distinct u32
|
|
|
-
|
|
|
-Box_Slot :: struct {
|
|
|
- kind: Item_Kind,
|
|
|
- count: u32,
|
|
|
-}
|
|
|
-
|
|
|
-Box_System :: struct {
|
|
|
- is_open: bool,
|
|
|
- slots: #soa[dynamic]Box_Slot,
|
|
|
-}
|
|
|
-
|
|
|
-Drag_State :: struct {
|
|
|
- active: bool,
|
|
|
- item: Box_Slot,
|
|
|
- source_box: Box_Id,
|
|
|
- source_slot: int,
|
|
|
-}
|
|
|
-
|
|
|
// --- UI System Types ---
|
|
|
|
|
|
Font_Atlas :: struct {
|
|
|
@@ -132,16 +114,12 @@ UI_Context :: struct {
|
|
|
line_height: f32,
|
|
|
}
|
|
|
|
|
|
-Entity_Kind :: enum u8 {
|
|
|
- Player,
|
|
|
- Enemy,
|
|
|
- Cube,
|
|
|
+ComponentID :: enum u8 {
|
|
|
+ Transform,
|
|
|
+ Physics,
|
|
|
+ Render,
|
|
|
Garden,
|
|
|
-}
|
|
|
-
|
|
|
-AABB :: struct {
|
|
|
- min: Vec3,
|
|
|
- max: Vec3,
|
|
|
+ ColliderAABB,
|
|
|
}
|
|
|
|
|
|
Garden_State :: enum u8 {
|
|
|
@@ -152,16 +130,31 @@ Garden_State :: enum u8 {
|
|
|
|
|
|
Entity_Id :: distinct u32
|
|
|
|
|
|
+ColliderAABB_Component :: struct {
|
|
|
+ min: Vec3,
|
|
|
+ max: Vec3,
|
|
|
+}
|
|
|
+
|
|
|
+Garden_Component :: struct {
|
|
|
+ state: Garden_State,
|
|
|
+}
|
|
|
+
|
|
|
+Transform_Component :: struct {
|
|
|
+ position: Vec3,
|
|
|
+ rotation: Vec3,
|
|
|
+ scale: Vec3,
|
|
|
+}
|
|
|
+transform_nil := Transform_Component{{}, {}, {1.0, 1.0, 1.0}}
|
|
|
+
|
|
|
+Physics_Component :: struct {
|
|
|
+ velocity: Vec3,
|
|
|
+}
|
|
|
+
|
|
|
+Render_Component :: struct {}
|
|
|
+
|
|
|
Entity :: struct {
|
|
|
- pos: Vec3,
|
|
|
- vel: Vec3,
|
|
|
- color: Vec3,
|
|
|
- kind: Entity_Kind,
|
|
|
- scale: Vec3,
|
|
|
- box_id: Box_Id,
|
|
|
- is_physics: bool,
|
|
|
- collider: AABB,
|
|
|
- garden_state: Garden_State,
|
|
|
+ id: Entity_Id,
|
|
|
+ components: u64,
|
|
|
}
|
|
|
|
|
|
Camera :: struct {
|
|
|
@@ -182,26 +175,31 @@ Input_State :: struct {
|
|
|
}
|
|
|
|
|
|
App_State :: struct {
|
|
|
- arena: virtual.Arena,
|
|
|
- allocator: mem.Allocator,
|
|
|
- pass_action: sg.Pass_Action,
|
|
|
- ui: UI_Context,
|
|
|
- ui_pip: sgl.Pipeline,
|
|
|
- scene_pip: sgl.Pipeline,
|
|
|
- mesh_cube: Mesh,
|
|
|
- box_sys: #soa[dynamic]Box_System,
|
|
|
- entities: #soa[dynamic]Entity,
|
|
|
- player_id: Entity_Id,
|
|
|
- dpi_scale: f32,
|
|
|
- camera: Camera,
|
|
|
- last_input: Input_State,
|
|
|
- input: Input_State,
|
|
|
- selected_color: [3]f32,
|
|
|
- active_hotbar_slot: int,
|
|
|
- drag: Drag_State,
|
|
|
- sprites: [dynamic]Sprite,
|
|
|
- debug_cube_mesh: Mesh,
|
|
|
- debug_line_mesh: Mesh,
|
|
|
+ arena: virtual.Arena,
|
|
|
+ allocator: mem.Allocator,
|
|
|
+ pass_action: sg.Pass_Action,
|
|
|
+ ui: UI_Context,
|
|
|
+ ui_pip: sgl.Pipeline,
|
|
|
+ scene_pip: sgl.Pipeline,
|
|
|
+ mesh_cube: Mesh,
|
|
|
+ entities: [dynamic]Entity,
|
|
|
+ player_ent: ^Entity,
|
|
|
+ // Componets
|
|
|
+ garde_components: [dynamic]Garden_Component,
|
|
|
+ transform_components: [dynamic]Transform_Component,
|
|
|
+ physics_components: [dynamic]Physics_Component,
|
|
|
+ render_components: [dynamic]Render_Component,
|
|
|
+ collider_aabb_components: [dynamic]ColliderAABB_Component,
|
|
|
+ //
|
|
|
+ dpi_scale: f32,
|
|
|
+ camera: Camera,
|
|
|
+ last_input: Input_State,
|
|
|
+ input: Input_State,
|
|
|
+ selected_color: [3]f32,
|
|
|
+ active_hotbar_slot: int,
|
|
|
+ sprites: [dynamic]Sprite,
|
|
|
+ debug_cube_mesh: Mesh,
|
|
|
+ debug_line_mesh: Mesh,
|
|
|
}
|
|
|
|
|
|
state: App_State
|
|
|
@@ -479,43 +477,12 @@ ui_slider :: proc(label: string, val: ^f32, min_v, max_v: f32) {
|
|
|
|
|
|
// --- Game Logic ---
|
|
|
|
|
|
-push_box :: proc(size: u32) -> Box_Id {
|
|
|
- box := Box_System {
|
|
|
- slots = make(#soa[dynamic]Box_Slot, 0, size),
|
|
|
- }
|
|
|
- for i in 0 ..< size do append_soa(&box.slots, Box_Slot{.None, 0})
|
|
|
- id := len(state.box_sys)
|
|
|
- append_soa(&state.box_sys, box)
|
|
|
- return Box_Id(id)
|
|
|
-}
|
|
|
-
|
|
|
-push_entity :: proc(
|
|
|
- kind: Entity_Kind,
|
|
|
- pos: Vec3 = {0.0, 0.0, 0.0},
|
|
|
- scale: Vec3 = {1.0, 1.0, 1.0},
|
|
|
-) -> Entity_Id {
|
|
|
- e := Entity {
|
|
|
- pos = pos,
|
|
|
- kind = kind,
|
|
|
- scale = scale,
|
|
|
- collider = AABB{min = {-0.5, -0.5, -0.5}, max = {0.5, 0.5, 0.5}},
|
|
|
+push_entity :: proc() -> ^Entity {
|
|
|
+ ent := Entity {
|
|
|
+ id = Entity_Id(len(state.entities)),
|
|
|
}
|
|
|
- switch kind {
|
|
|
- case .Player:
|
|
|
- e.color = {0.2, 0.8, 0.2}
|
|
|
- e.is_physics = true
|
|
|
- case .Enemy:
|
|
|
- e.color = {0.8, 0.3, 0.3}
|
|
|
- e.is_physics = true
|
|
|
- case .Cube:
|
|
|
- e.color = state.selected_color
|
|
|
- e.is_physics = true
|
|
|
- case .Garden:
|
|
|
- e.color = {130.0 / 255.0, 108.0 / 255.0, 65.0 / 255.0}
|
|
|
- }
|
|
|
- id := len(state.entities)
|
|
|
- append_soa(&state.entities, e)
|
|
|
- return Entity_Id(id)
|
|
|
+ append(&state.entities, ent)
|
|
|
+ return &state.entities[ent.id]
|
|
|
}
|
|
|
|
|
|
// Проверка пересечения луча и AABB (Axis Aligned Bounding Box)
|
|
|
@@ -565,8 +532,7 @@ init :: proc "c" () {
|
|
|
|
|
|
// 1. Сначала инициализируем массивы (включая sprites)
|
|
|
state.mesh_cube = make_cube_mesh(state.allocator)
|
|
|
- state.entities = make(#soa[dynamic]Entity, 0, MAX_ENTITIES, state.allocator)
|
|
|
- state.box_sys = make(#soa[dynamic]Box_System, 0, MAX_BOX, state.allocator)
|
|
|
+ state.entities = make([dynamic]Entity, 0, MAX_ENTITIES, state.allocator)
|
|
|
state.sprites = make([dynamic]Sprite, 0, 16, state.allocator)
|
|
|
|
|
|
// 2. Затем загружаем ресурсы (шрифты и спрайты предметов)
|
|
|
@@ -582,41 +548,15 @@ init :: proc "c" () {
|
|
|
scene_pip_desc: sg.Pipeline_Desc
|
|
|
scene_pip_desc.depth.write_enabled = true
|
|
|
scene_pip_desc.depth.compare = .LESS_EQUAL
|
|
|
- scene_pip_desc.cull_mode = .FRONT
|
|
|
+ scene_pip_desc.cull_mode = .BACK
|
|
|
state.scene_pip = sgl.make_pipeline(scene_pip_desc)
|
|
|
|
|
|
state.pass_action = {
|
|
|
colors = {0 = {load_action = .CLEAR, clear_value = {0.9, 0.9, 0.95, 1}}},
|
|
|
}
|
|
|
|
|
|
- state.player_id = push_entity(.Player)
|
|
|
- player_ref := &state.entities[state.player_id]
|
|
|
- player_ref.box_id = push_box(INVENTORY_SIZE)
|
|
|
-
|
|
|
- state.box_sys[player_ref.box_id].slots[0] = Box_Slot {
|
|
|
- kind = .Shovel,
|
|
|
- count = 1,
|
|
|
- }
|
|
|
- state.box_sys[player_ref.box_id].slots[1] = Box_Slot {
|
|
|
- kind = .Sword,
|
|
|
- count = 1,
|
|
|
- }
|
|
|
- state.box_sys[player_ref.box_id].slots[2] = Box_Slot {
|
|
|
- kind = .Potion,
|
|
|
- count = 5,
|
|
|
- }
|
|
|
-
|
|
|
- // TODO:
|
|
|
- for x in 0 ..< 10 {
|
|
|
- for z in 0 ..< 10 {
|
|
|
- offset := Vec2{0.1 * f32(x), 0.1 * f32(z)}
|
|
|
- push_entity(
|
|
|
- .Garden,
|
|
|
- {0.0 + (1.0 * f32(x)) + offset.x, 0.0, 0.0 + (1.0 * f32(z)) + offset.y},
|
|
|
- {1.0, 0.1, 1.0},
|
|
|
- )
|
|
|
- }
|
|
|
- }
|
|
|
+ state.player_ent = push_entity()
|
|
|
+ add_transofrm_component(state.player_ent)
|
|
|
|
|
|
state.camera.dist = 10.0; state.camera.pitch = 0.5; state.selected_color = {0.2, 0.5, 1.0}
|
|
|
}
|
|
|
@@ -630,11 +570,7 @@ frame :: proc "c" () {
|
|
|
dt := f32(sapp.frame_duration())
|
|
|
|
|
|
system_input_player(dt)
|
|
|
- system_physics(dt)
|
|
|
view, proj := get_camera_matrices()
|
|
|
- system_interaction(view, proj)
|
|
|
- system_builder(view, proj)
|
|
|
-
|
|
|
system_render(view, proj)
|
|
|
|
|
|
sgl.defaults(); sgl.push_pipeline(); sgl.load_pipeline(state.ui_pip)
|
|
|
@@ -654,34 +590,6 @@ frame :: proc "c" () {
|
|
|
ui_window_begin("Stats", 20, 20, 220, 150)
|
|
|
ui_label(fmt.tprintf("FPS: %.0f", 1.0 / dt))
|
|
|
ui_label(fmt.tprintf("Entities: %d", len(state.entities)))
|
|
|
- if ui_button("Spawn Enemy") do push_entity(.Enemy, {0, 10, 0})
|
|
|
-
|
|
|
- ui_window_begin("Builder", 20, 200, 250, 180)
|
|
|
- ui_slider("Red", &state.selected_color.x, 0, 1)
|
|
|
- ui_slider("Green", &state.selected_color.y, 0, 1)
|
|
|
- ui_slider("Blue", &state.selected_color.z, 0, 1)
|
|
|
- ui_draw_rect(
|
|
|
- state.ui.curr_x,
|
|
|
- state.ui.curr_y,
|
|
|
- state.ui.curr_w,
|
|
|
- 30,
|
|
|
- {state.selected_color.x, state.selected_color.y, state.selected_color.z, 1},
|
|
|
- )
|
|
|
-
|
|
|
- player_box_id := state.entities[state.player_id].box_id
|
|
|
- draw_hud(w, h)
|
|
|
- if state.box_sys[player_box_id].is_open do draw_inventory(player_box_id, w, h)
|
|
|
-
|
|
|
- if state.drag.active {
|
|
|
- info := item_db[state.drag.item.kind]
|
|
|
- mx, my := state.ui.mouse_pos.x, state.ui.mouse_pos.y
|
|
|
- if info.sprite_id >= 0 {
|
|
|
- ui_draw_sprite(info.sprite_id, mx - 20, my - 20, 40, 40)
|
|
|
- } else {
|
|
|
- ui_draw_rect(mx - 20, my - 20, 40, 40, info.color)
|
|
|
- }
|
|
|
- ui_draw_text(fmt.tprintf("%d", state.drag.item.count), mx, my, UI_COLOR_TEXT)
|
|
|
- }
|
|
|
|
|
|
sgl.pop_matrix(); sgl.matrix_mode_projection(); sgl.pop_matrix(); sgl.pop_pipeline()
|
|
|
sg.begin_pass({action = state.pass_action, swapchain = sglue.swapchain()})
|
|
|
@@ -693,378 +601,130 @@ frame :: proc "c" () {
|
|
|
state.input.mouse_dx = 0; state.input.mouse_dy = 0; state.input.left_clicked = false
|
|
|
}
|
|
|
|
|
|
-draw_inventory :: proc(box_id: Box_Id, screen_w, screen_h: f32) {
|
|
|
- slots := &state.box_sys[box_id].slots
|
|
|
- row_width := f32(INVENTORY_COLS) * (SLOT_SIZE + SLOT_SPACING) - SLOT_SPACING
|
|
|
- main_h := f32(INVENTORY_MAIN_ROWS) * (SLOT_SIZE + SLOT_SPACING) - SLOT_SPACING
|
|
|
- win_padding := f32(10.0)
|
|
|
- win_w := row_width + win_padding * 2
|
|
|
- win_h := main_h + win_padding * 2 + 20
|
|
|
- start_x := (screen_w - win_w) / 2
|
|
|
- start_y := (screen_h - win_h) / 2 - 50.0
|
|
|
-
|
|
|
- ui_draw_rect(start_x, start_y, win_w, win_h, UI_COLOR_WINDOW_BG)
|
|
|
- ui_draw_outline(start_x, start_y, win_w, win_h, 1.0, UI_COLOR_BORDER)
|
|
|
- ui_draw_text("Backpack", start_x + win_padding, start_y + 5, UI_COLOR_TEXT)
|
|
|
-
|
|
|
- ctx := &state.ui
|
|
|
- grid_start_x := start_x + win_padding
|
|
|
- grid_start_y := start_y + 30
|
|
|
- slots_count := INVENTORY_MAIN_ROWS * INVENTORY_COLS
|
|
|
-
|
|
|
- for i in 0 ..< slots_count {
|
|
|
- if i >= len(slots) do break
|
|
|
- col := i % INVENTORY_COLS; row := i / INVENTORY_COLS
|
|
|
- sx := grid_start_x + f32(col) * (SLOT_SIZE + SLOT_SPACING)
|
|
|
- sy := grid_start_y + f32(row) * (SLOT_SIZE + SLOT_SPACING)
|
|
|
- slot := &slots[i]
|
|
|
- hovered :=
|
|
|
- ctx.mouse_pos.x >= sx &&
|
|
|
- ctx.mouse_pos.x < sx + SLOT_SIZE &&
|
|
|
- ctx.mouse_pos.y >= sy &&
|
|
|
- ctx.mouse_pos.y < sy + SLOT_SIZE
|
|
|
-
|
|
|
- bg_color := UI_COLOR_SLOT_BG
|
|
|
- if hovered do bg_color = UI_COLOR_SLOT_HOVER
|
|
|
- ui_draw_rect(sx, sy, SLOT_SIZE, SLOT_SIZE, bg_color)
|
|
|
- ui_draw_outline(sx, sy, SLOT_SIZE, SLOT_SIZE, 1.0, {0.7, 0.7, 0.7, 1.0})
|
|
|
-
|
|
|
- if slot.kind != .None {
|
|
|
- info := item_db[slot.kind]
|
|
|
- pad := SLOT_PADDING
|
|
|
- if info.sprite_id >= 0 {
|
|
|
- ui_draw_sprite(
|
|
|
- info.sprite_id,
|
|
|
- sx + pad,
|
|
|
- sy + pad,
|
|
|
- SLOT_SIZE - pad * 2,
|
|
|
- SLOT_SIZE - pad * 2,
|
|
|
- )
|
|
|
- } else {
|
|
|
- ui_draw_rect(
|
|
|
- sx + pad,
|
|
|
- sy + pad,
|
|
|
- SLOT_SIZE - pad * 2,
|
|
|
- SLOT_SIZE - pad * 2,
|
|
|
- info.color,
|
|
|
- )
|
|
|
- }
|
|
|
- if slot.count > 1 {
|
|
|
- count_str := fmt.tprintf("%d", slot.count)
|
|
|
- tw := ui_measure_text(count_str)
|
|
|
- ui_draw_text(
|
|
|
- count_str,
|
|
|
- sx + SLOT_SIZE - tw - 2,
|
|
|
- sy + SLOT_SIZE - 12,
|
|
|
- UI_COLOR_TEXT,
|
|
|
- )
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if hovered {
|
|
|
- if ctx.mouse_pressed && !state.drag.active && slot.kind != .None {
|
|
|
- state.drag.active =
|
|
|
- true; state.drag.item = slot^; state.drag.source_box = box_id; state.drag.source_slot = i
|
|
|
- slot.kind = .None; slot.count = 0
|
|
|
- } else if !ctx.mouse_down && state.last_input.left_down && state.drag.active {
|
|
|
- if slot.kind ==
|
|
|
- .None {slot^ = state.drag.item; state.drag.active = false} else if slot.kind == state.drag.item.kind {slot.count += state.drag.item.count; state.drag.active = false} else {temp := slot^; slot^ = state.drag.item; state.drag.item = temp}
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-draw_hud :: proc(screen_w, screen_h: f32) {
|
|
|
- player_box_id := state.entities[state.player_id].box_id
|
|
|
- slots := &state.box_sys[player_box_id].slots
|
|
|
- start_slot_idx := INVENTORY_SIZE - INVENTORY_COLS
|
|
|
- total_w := f32(INVENTORY_COLS) * (SLOT_SIZE + SLOT_SPACING) - SLOT_SPACING
|
|
|
- start_x := (screen_w - total_w) / 2.0
|
|
|
- start_y := screen_h - SLOT_SIZE - 10.0
|
|
|
- ctx := &state.ui
|
|
|
-
|
|
|
- for i in 0 ..< INVENTORY_COLS {
|
|
|
- idx := start_slot_idx + i
|
|
|
- slot := &slots[idx]
|
|
|
- sx := start_x + f32(i) * (SLOT_SIZE + SLOT_SPACING)
|
|
|
- sy := start_y
|
|
|
- hovered :=
|
|
|
- ctx.mouse_pos.x >= sx &&
|
|
|
- ctx.mouse_pos.x < sx + SLOT_SIZE &&
|
|
|
- ctx.mouse_pos.y >= sy &&
|
|
|
- ctx.mouse_pos.y < sy + SLOT_SIZE
|
|
|
-
|
|
|
- ui_draw_rect(sx, sy, SLOT_SIZE, SLOT_SIZE, {0.9, 0.9, 0.9, 0.8})
|
|
|
- ui_draw_outline(sx, sy, SLOT_SIZE, SLOT_SIZE, 1.0, UI_COLOR_BORDER)
|
|
|
-
|
|
|
- if i == state.active_hotbar_slot {
|
|
|
- ui_draw_outline(sx - 2, sy - 2, SLOT_SIZE + 4, SLOT_SIZE + 4, 3.0, UI_COLOR_ACCENT)
|
|
|
- }
|
|
|
-
|
|
|
- if slot.kind != .None {
|
|
|
- info := item_db[slot.kind]
|
|
|
- pad := SLOT_PADDING
|
|
|
- if info.sprite_id >= 0 {
|
|
|
- ui_draw_sprite(
|
|
|
- info.sprite_id,
|
|
|
- sx + pad,
|
|
|
- sy + pad,
|
|
|
- SLOT_SIZE - pad * 2,
|
|
|
- SLOT_SIZE - pad * 2,
|
|
|
- )
|
|
|
- } else {
|
|
|
- ui_draw_rect(
|
|
|
- sx + pad,
|
|
|
- sy + pad,
|
|
|
- SLOT_SIZE - pad * 2,
|
|
|
- SLOT_SIZE - pad * 2,
|
|
|
- info.color,
|
|
|
- )
|
|
|
- }
|
|
|
- if slot.count > 1 {
|
|
|
- count_str := fmt.tprintf("%d", slot.count)
|
|
|
- tw := ui_measure_text(count_str)
|
|
|
- ui_draw_text(
|
|
|
- count_str,
|
|
|
- sx + SLOT_SIZE - tw - 2,
|
|
|
- sy + SLOT_SIZE - 12,
|
|
|
- UI_COLOR_TEXT,
|
|
|
- )
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if state.box_sys[player_box_id].is_open && hovered {
|
|
|
- if ctx.mouse_pressed && !state.drag.active && slot.kind != .None {
|
|
|
- state.drag.active =
|
|
|
- true; state.drag.item = slot^; state.drag.source_box = player_box_id; state.drag.source_slot = idx
|
|
|
- slot.kind = .None; slot.count = 0
|
|
|
- } else if !ctx.mouse_down && state.last_input.left_down && state.drag.active {
|
|
|
- if slot.kind ==
|
|
|
- .None {slot^ = state.drag.item; state.drag.active = false} else if slot.kind == state.drag.item.kind {slot.count += state.drag.item.count; state.drag.active = false} else {temp := slot^; slot^ = state.drag.item; state.drag.item = temp}
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-system_interaction :: proc(view, proj: linalg.Matrix4f32) {
|
|
|
- // Если курсор заблокирован или открыт инвентарь - не взаимодействуем
|
|
|
- if state.input.locked do return
|
|
|
- if len(state.entities) > 0 && state.box_sys[state.entities[state.player_id].box_id].is_open do return
|
|
|
-
|
|
|
- // Получаем луч из камеры
|
|
|
- ray_origin, ray_dir := get_mouse_ray(state.input.mouse_x, state.input.mouse_y, view, proj)
|
|
|
-
|
|
|
- closest_dist := f32(10000.0)
|
|
|
- hit_entity_idx := -1
|
|
|
-
|
|
|
- // 1. Ищем ближайшую сущность, с которой пересекся луч
|
|
|
- for i in 0 ..< len(state.entities) {
|
|
|
- // Не взаимодействуем с самим игроком
|
|
|
- if Entity_Id(i) == state.player_id do continue
|
|
|
+system_input_player :: proc(dt: f32) {
|
|
|
+ if len(state.entities) == 0 do return
|
|
|
|
|
|
- e := &state.entities[i]
|
|
|
+ SPEED: f32 : 10.0 // units per second
|
|
|
+ SENSITIVITY: f32 : 0.002 // radians per pixel
|
|
|
|
|
|
- // Переводим локальный AABB в мировые координаты с учетом позиции и масштаба
|
|
|
- world_min := e.pos + (e.collider.min * e.scale)
|
|
|
- world_max := e.pos + (e.collider.max * e.scale)
|
|
|
+ player := state.player_ent
|
|
|
+ transform := get_transform_component(player)
|
|
|
|
|
|
- if hit, dist := ray_aabb_intersect(ray_origin, ray_dir, world_min, world_max); hit {
|
|
|
- if dist < closest_dist {
|
|
|
- closest_dist = dist
|
|
|
- hit_entity_idx = i
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
+ if state.input.locked {
|
|
|
+ dx := f32(state.input.mouse_dx)
|
|
|
+ dy := f32(state.input.mouse_dy)
|
|
|
|
|
|
- // 2. Если нашли сущность
|
|
|
- if hit_entity_idx != -1 {
|
|
|
- target := &state.entities[hit_entity_idx]
|
|
|
-
|
|
|
- proj_copy := proj
|
|
|
- view_copy := view
|
|
|
-
|
|
|
- // --- HOVER (Наведение) ---
|
|
|
- // Рисуем желтую обводку вокруг объекта
|
|
|
- sgl.defaults()
|
|
|
- sgl.push_pipeline()
|
|
|
- sgl.load_pipeline(state.scene_pip)
|
|
|
- sgl.matrix_mode_projection(); sgl.load_matrix(cast(^f32)&proj_copy)
|
|
|
- sgl.matrix_mode_modelview(); sgl.load_matrix(cast(^f32)&view_copy)
|
|
|
-
|
|
|
- // Рисуем линии чуть шире и желтым цветом
|
|
|
- sgl.push_matrix()
|
|
|
- sgl.translate(target.pos.x, target.pos.y, target.pos.z)
|
|
|
- // Чуть увеличиваем масштаб обводки, чтобы она была видна поверх объекта
|
|
|
- scale_hover := target.scale * 1.05
|
|
|
- sgl.scale(scale_hover.x, scale_hover.y, scale_hover.z)
|
|
|
-
|
|
|
- sgl.begin_lines()
|
|
|
- sgl.c3f(1.0, 1.0, 0.0) // Желтый цвет
|
|
|
- // Используем существующий меш линий, но игнорируем его черный цвет, задавая свой через c3f
|
|
|
- // (Примечание: в debug_draw_mesh_lines используется цвет вершин, поэтому напишем упрощенный код здесь)
|
|
|
- m := &state.debug_line_mesh
|
|
|
- for idx in m.indices {
|
|
|
- v := m.vertices[idx]
|
|
|
- sgl.v3f(v.pos.x, v.pos.y, v.pos.z)
|
|
|
- }
|
|
|
- sgl.end()
|
|
|
- sgl.pop_matrix()
|
|
|
- sgl.pop_pipeline()
|
|
|
-
|
|
|
- // --- CLICK (Клик) ---
|
|
|
- if state.input.left_clicked {
|
|
|
- fmt.println("Clicked on entity:", target.kind, "ID:", hit_entity_idx)
|
|
|
-
|
|
|
- // Пример взаимодействия:
|
|
|
- // 1. Если это грядка - меняем состояние
|
|
|
- if target.kind == .Garden {
|
|
|
- if target.garden_state == .Dry {
|
|
|
- target.garden_state = .Water
|
|
|
- target.color = {0.2, 0.2, 0.8} // Синий
|
|
|
- } else if target.garden_state == .Water {
|
|
|
- target.garden_state = .Plant
|
|
|
- target.color = {0.2, 0.8, 0.2} // Зеленый
|
|
|
- } else {
|
|
|
- target.garden_state = .Dry
|
|
|
- target.color = {130.0 / 255.0, 108.0 / 255.0, 65.0 / 255.0} // Коричневый
|
|
|
- }
|
|
|
- } else {
|
|
|
- // 2. Если это куб или враг - меняем цвет на случайный или подбрасываем
|
|
|
-
|
|
|
- target.color = {rand.float32(), rand.float32(), rand.float32()}
|
|
|
- target.vel.y += 5.0 // Подбросить
|
|
|
- }
|
|
|
-
|
|
|
- // Сбрасываем клик, чтобы не сработал builder (строительство) в этом же кадре
|
|
|
- state.input.left_clicked = false
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
+ state.camera.yaw -= dx * SENSITIVITY
|
|
|
+ state.camera.pitch += dy * SENSITIVITY
|
|
|
+ if state.camera.yaw > math.PI * 2 do state.camera.yaw -= math.PI * 2
|
|
|
+ if state.camera.yaw < 0 do state.camera.yaw += math.PI * 2
|
|
|
|
|
|
-system_input_player :: proc(dt: f32) {
|
|
|
- if len(state.entities) == 0 do return
|
|
|
- player := &state.entities[state.player_id]
|
|
|
- SPEED :: 10.0
|
|
|
- SENSITIVITY :: 0.15 * 0.02
|
|
|
|
|
|
- if state.input.locked {
|
|
|
- state.camera.yaw -= state.input.mouse_dx * SENSITIVITY
|
|
|
- state.camera.pitch += state.input.mouse_dy * SENSITIVITY
|
|
|
- state.camera.pitch = math.clamp(state.camera.pitch, -1.5, 1.5)
|
|
|
+ state.camera.pitch = math.clamp(state.camera.pitch, -math.PI * 0.49, math.PI * 0.49)
|
|
|
}
|
|
|
|
|
|
sin_yaw, cos_yaw := math.sincos(state.camera.yaw)
|
|
|
forward := Vec3{sin_yaw, 0, cos_yaw}
|
|
|
right := Vec3{cos_yaw, 0, -sin_yaw}
|
|
|
|
|
|
- move_dir := Vec3{0, 0, 0}
|
|
|
+ move_dir := Vec3{}
|
|
|
if state.input.keys[.W] do move_dir -= forward
|
|
|
if state.input.keys[.S] do move_dir += forward
|
|
|
if state.input.keys[.A] do move_dir -= right
|
|
|
if state.input.keys[.D] do move_dir += right
|
|
|
|
|
|
- if linalg.length2(move_dir) > 0.01 {
|
|
|
+ if linalg.length2(move_dir) > 0.0001 {
|
|
|
move_dir = linalg.normalize(move_dir)
|
|
|
- player.vel.x = move_dir.x * SPEED
|
|
|
- player.vel.z = move_dir.z * SPEED
|
|
|
- } else {
|
|
|
- player.vel.x = 0
|
|
|
- player.vel.z = 0
|
|
|
+ transform.position += move_dir * SPEED * dt
|
|
|
}
|
|
|
+}
|
|
|
|
|
|
- if state.input.keys[.SPACE] && player.pos.y <= 0.51 {
|
|
|
- player.vel.y = 10.0
|
|
|
- }
|
|
|
+system_render :: proc(view, proj: linalg.Matrix4f32) {
|
|
|
+ v := view; p := proj
|
|
|
+ sgl.defaults()
|
|
|
+ sgl.push_pipeline()
|
|
|
+ sgl.load_pipeline(state.scene_pip)
|
|
|
+ sgl.matrix_mode_projection(); sgl.load_matrix(cast(^f32)&p)
|
|
|
+ sgl.matrix_mode_modelview(); sgl.load_matrix(cast(^f32)&v)
|
|
|
|
|
|
- if state.input.keys[._1] do state.active_hotbar_slot = 0
|
|
|
- if state.input.keys[._2] do state.active_hotbar_slot = 1
|
|
|
- if state.input.keys[._3] do state.active_hotbar_slot = 2
|
|
|
- if state.input.keys[._4] do state.active_hotbar_slot = 3
|
|
|
- if state.input.keys[._5] do state.active_hotbar_slot = 4
|
|
|
- if state.input.keys[._6] do state.active_hotbar_slot = 5
|
|
|
- if state.input.keys[._7] do state.active_hotbar_slot = 6
|
|
|
- if state.input.keys[._8] do state.active_hotbar_slot = 7
|
|
|
- if state.input.keys[._9] do state.active_hotbar_slot = 8
|
|
|
-
|
|
|
- if state.last_input.keys[.TAB] && !state.input.keys[.TAB] {
|
|
|
- state.box_sys[player.box_id].is_open = !state.box_sys[player.box_id].is_open
|
|
|
- state.input.locked = !state.box_sys[player.box_id].is_open
|
|
|
- sapp.lock_mouse(state.input.locked)
|
|
|
+ draw_grid_thick(100, 1.0, 0.05)
|
|
|
|
|
|
- if !state.box_sys[player.box_id].is_open && state.drag.active {
|
|
|
- state.box_sys[state.drag.source_box].slots[state.drag.source_slot] = state.drag.item
|
|
|
- state.drag.active = false
|
|
|
- }
|
|
|
+ for i in 0 ..< len(state.entities) {
|
|
|
+ ent := &state.entities[i]
|
|
|
+ ent_transform := get_transform_component(ent)
|
|
|
+ draw_mesh_instance(
|
|
|
+ &state.mesh_cube,
|
|
|
+ ent_transform.position,
|
|
|
+ ent_transform.scale,
|
|
|
+ {1.0, 0.0, 1.0},
|
|
|
+ )
|
|
|
}
|
|
|
-}
|
|
|
|
|
|
-system_builder :: proc(view, proj: linalg.Matrix4f32) {
|
|
|
- if state.input.locked do return
|
|
|
- if state.input.mouse_x < 300 do return
|
|
|
- if len(state.entities) > 0 && state.box_sys[state.entities[state.player_id].box_id].is_open do return
|
|
|
+ sgl.pop_pipeline()
|
|
|
+}
|
|
|
|
|
|
- if state.input.left_clicked {
|
|
|
- origin, dir := get_mouse_ray(state.input.mouse_x, state.input.mouse_y, view, proj)
|
|
|
- if hit_pos, hit := ray_plane_intersect(origin, dir, 0.5); hit {
|
|
|
- push_entity(.Cube, hit_pos)
|
|
|
- }
|
|
|
- }
|
|
|
+has_component :: proc(ent: ^Entity, comp_id: ComponentID) -> bool {
|
|
|
+ return (ent.components & (1 << comp_id)) != 0
|
|
|
}
|
|
|
|
|
|
-system_physics :: proc(dt: f32) {
|
|
|
- GRAVITY :: 30.0
|
|
|
- FLOOR_Y :: 0.5
|
|
|
- for i in 0 ..< len(state.entities) {
|
|
|
- e := &state.entities[i]
|
|
|
- if !e.is_physics do continue
|
|
|
+remove_component :: proc(ent: ^Entity, comp_id: ComponentID) {
|
|
|
+ ent.components &= ~(1 << comp_id)
|
|
|
+}
|
|
|
|
|
|
- e.vel.y -= GRAVITY * dt
|
|
|
- e.pos += e.vel * dt
|
|
|
+add_transofrm_component :: proc(
|
|
|
+ ent: ^Entity,
|
|
|
+ comp: Transform_Component = {{}, {}, {1.0, 1.0, 1.0}},
|
|
|
+) {
|
|
|
+ ent.components |= (1 << ComponentID.Transform)
|
|
|
+ inject_at(&state.transform_components, ent.id, comp)
|
|
|
+}
|
|
|
|
|
|
- if e.pos.y < FLOOR_Y {
|
|
|
- e.pos.y = FLOOR_Y
|
|
|
- if e.kind ==
|
|
|
- .Player {e.vel.y = 0} else {e.vel.y *= -0.8; e.vel.x *= 0.95; e.vel.z *= 0.95}
|
|
|
- }
|
|
|
- if e.pos.x > 50 do e.vel.x = -abs(e.vel.x)
|
|
|
- if e.pos.x < -50 do e.vel.x = abs(e.vel.x)
|
|
|
- if e.pos.z > 50 do e.vel.z = -abs(e.vel.z)
|
|
|
- if e.pos.z < -50 do e.vel.z = abs(e.vel.z)
|
|
|
+get_transform_component :: proc(ent: ^Entity) -> ^Transform_Component {
|
|
|
+ if !has_component(ent, ComponentID.Transform) {
|
|
|
+ return &transform_nil
|
|
|
}
|
|
|
+ return &state.transform_components[ent.id]
|
|
|
}
|
|
|
|
|
|
-system_render :: proc(view, proj: linalg.Matrix4f32) {
|
|
|
- v := view; p := proj
|
|
|
- sgl.defaults()
|
|
|
- sgl.push_pipeline()
|
|
|
- sgl.load_pipeline(state.scene_pip)
|
|
|
- sgl.matrix_mode_projection(); sgl.load_matrix(cast(^f32)&p)
|
|
|
- sgl.matrix_mode_modelview(); sgl.load_matrix(cast(^f32)&v)
|
|
|
+add_physics_component :: proc(ent: ^Entity, comp: Physics_Component = {}) {
|
|
|
+ ent.components |= (1 << ComponentID.Physics)
|
|
|
+ // inject_at_soa(&state.physics_components, ent.id, comp)
|
|
|
+}
|
|
|
+
|
|
|
+get_physics_component :: proc(ent: ^Entity) -> ^Physics_Component {
|
|
|
+ if !has_component(ent, ComponentID.Physics) {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ return &state.physics_components[ent.id]
|
|
|
+}
|
|
|
|
|
|
- draw_grid(100, 1.0)
|
|
|
+add_garden_component :: proc(ent: ^Entity, comp: Garden_Component) {
|
|
|
+ ent.components |= (1 << ComponentID.Garden)
|
|
|
+ // inject_at_soa(&state.garde_components, ent.id, comp)
|
|
|
+}
|
|
|
|
|
|
- for i in 0 ..< len(state.entities) {
|
|
|
- e := &state.entities[i]
|
|
|
-
|
|
|
- // 1. Рисуем сам цветной куб
|
|
|
- draw_mesh_instance(&state.mesh_cube, e.pos, e.scale, e.color)
|
|
|
-
|
|
|
- // 2. Если это Garden, рисуем черную обводку поверх
|
|
|
- if e.kind == .Garden {
|
|
|
- // offset сдвигает глубину линий чуть ближе к камере,
|
|
|
- // чтобы они не проваливались внутрь куба (Z-fighting)
|
|
|
- // sgl.offset(1.0, 1.0)
|
|
|
- debug_draw_mesh_lines(&state.debug_line_mesh, e.pos, e.scale)
|
|
|
- // sgl.offset(0.0, 0.0) // Обязательно сбрасываем обратно
|
|
|
- }
|
|
|
+get_garden_component :: proc(ent: ^Entity) -> ^Garden_Component {
|
|
|
+ if !has_component(ent, ComponentID.Garden) {
|
|
|
+ return nil
|
|
|
}
|
|
|
+ return &state.garde_components[ent.id]
|
|
|
+}
|
|
|
|
|
|
- sgl.pop_pipeline()
|
|
|
+add_collider_aabb_component :: proc(ent: ^Entity, comp: ColliderAABB_Component) {
|
|
|
+ ent.components |= (1 << ComponentID.ColliderAABB)
|
|
|
+ // inject_at_soa(&state.collider_aabb_components, ent.id, comp)
|
|
|
+}
|
|
|
+
|
|
|
+get_collider_aabb_component :: proc(ent: ^Entity) -> ^ColliderAABB_Component {
|
|
|
+ if !has_component(ent, ComponentID.ColliderAABB) {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ return &state.collider_aabb_components[ent.id]
|
|
|
}
|
|
|
|
|
|
get_camera_matrices :: proc() -> (view, proj: linalg.Matrix4f32) {
|
|
|
if len(state.entities) == 0 do return linalg.MATRIX4F32_IDENTITY, linalg.MATRIX4F32_IDENTITY
|
|
|
- player_pos := state.entities[state.player_id].pos
|
|
|
+ transform := get_transform_component(state.player_ent)
|
|
|
cam_offset :=
|
|
|
Vec3 {
|
|
|
math.cos(state.camera.pitch) * math.sin(state.camera.yaw),
|
|
|
@@ -1072,7 +732,11 @@ get_camera_matrices :: proc() -> (view, proj: linalg.Matrix4f32) {
|
|
|
math.cos(state.camera.pitch) * math.cos(state.camera.yaw),
|
|
|
} *
|
|
|
state.camera.dist
|
|
|
- view = linalg.matrix4_look_at_f32(player_pos + cam_offset, player_pos, {0, 1, 0})
|
|
|
+ view = linalg.matrix4_look_at_f32(
|
|
|
+ transform.position + cam_offset,
|
|
|
+ transform.position,
|
|
|
+ {0, 1, 0},
|
|
|
+ )
|
|
|
proj = linalg.matrix4_perspective_f32(
|
|
|
60.0 * (math.PI / 180.0),
|
|
|
sapp.widthf() / sapp.heightf(),
|
|
|
@@ -1208,18 +872,57 @@ draw_mesh_instance :: proc(m: ^Mesh, pos: Vec3, scale: Vec3, tint: Vec3) {
|
|
|
sgl.pop_matrix()
|
|
|
}
|
|
|
|
|
|
-draw_grid :: proc(slices: int, spacing: f32) {
|
|
|
- sgl.begin_lines()
|
|
|
- sgl.c3f(0.3, 0.3, 0.3)
|
|
|
+draw_grid_thick :: proc(slices: int, spacing: f32, thickness: f32) {
|
|
|
half := f32(slices) * spacing * 0.5
|
|
|
+
|
|
|
for i in 0 ..= slices {
|
|
|
p := -half + f32(i) * spacing
|
|
|
- sgl.v3f(-half, 0, p); sgl.v3f(half, 0, p)
|
|
|
- sgl.v3f(p, 0, -half); sgl.v3f(p, 0, half)
|
|
|
+
|
|
|
+ // Центральные оси
|
|
|
+ if math.abs(p) < 0.0001 {
|
|
|
+ // X axis (red)
|
|
|
+ draw_thick_line(
|
|
|
+ Vec3{-half, 0, 0},
|
|
|
+ Vec3{half, 0, 0},
|
|
|
+ thickness * 2,
|
|
|
+ Vec3{0.8, 0.2, 0.2},
|
|
|
+ )
|
|
|
+ // Z axis (blue)
|
|
|
+ draw_thick_line(
|
|
|
+ Vec3{0, 0, -half},
|
|
|
+ Vec3{0, 0, half},
|
|
|
+ thickness * 2,
|
|
|
+ Vec3{0.2, 0.2, 0.8},
|
|
|
+ )
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ // Обычные линии сетки (серые)
|
|
|
+ draw_thick_line(Vec3{-half, 0, p}, Vec3{half, 0, p}, thickness, Vec3{0.35, 0.35, 0.35})
|
|
|
+ draw_thick_line(Vec3{p, 0, -half}, Vec3{p, 0, half}, thickness, Vec3{0.35, 0.35, 0.35})
|
|
|
}
|
|
|
+}
|
|
|
+
|
|
|
+draw_thick_line :: proc(p0: Vec3, p1: Vec3, thickness: f32, color: Vec3) {
|
|
|
+ dir := linalg.normalize(p1 - p0)
|
|
|
+ right := Vec3{-dir.z, 0, dir.x} * (thickness * 0.5) // перпендикуляр в XZ
|
|
|
+
|
|
|
+ sgl.c3f(color.x, color.y, color.z)
|
|
|
+
|
|
|
+ sgl.begin_triangles()
|
|
|
+ // Первая тройка вершин
|
|
|
+ sgl.v3f(p0.x + right.x, p0.y + right.y, p0.z + right.z)
|
|
|
+ sgl.v3f(p1.x + right.x, p1.y + right.y, p1.z + right.z)
|
|
|
+ sgl.v3f(p1.x - right.x, p1.y - right.y, p1.z - right.z)
|
|
|
+
|
|
|
+ // Вторая тройка вершин
|
|
|
+ sgl.v3f(p1.x - right.x, p1.y - right.y, p1.z - right.z)
|
|
|
+ sgl.v3f(p0.x - right.x, p0.y - right.y, p0.z - right.z)
|
|
|
+ sgl.v3f(p0.x + right.x, p0.y + right.y, p0.z + right.z)
|
|
|
sgl.end()
|
|
|
}
|
|
|
|
|
|
+
|
|
|
get_mouse_ray :: proc(mx, my: f32, view, proj: linalg.Matrix4f32) -> (origin, dir: Vec3) {
|
|
|
x := (2.0 * mx) / sapp.widthf() - 1.0
|
|
|
y := 1.0 - (2.0 * my) / sapp.heightf()
|
|
|
@@ -1249,8 +952,8 @@ event :: proc "c" (ev: ^sapp.Event) {
|
|
|
state.input.mouse_x = ev.mouse_x
|
|
|
state.input.mouse_y = ev.mouse_y
|
|
|
if state.input.locked {
|
|
|
- state.input.mouse_dx = ev.mouse_dx
|
|
|
- state.input.mouse_dy = ev.mouse_dy
|
|
|
+ state.input.mouse_dx += ev.mouse_dx
|
|
|
+ state.input.mouse_dy += ev.mouse_dy
|
|
|
}
|
|
|
case .MOUSE_DOWN:
|
|
|
if ev.mouse_button == .LEFT {
|