From: Brendan Hansen Date: Sat, 29 Jan 2022 04:03:40 +0000 (-0600) Subject: entity system improvements X-Git-Url: https://git.brendanfh.com/?a=commitdiff_plain;h=7ff4c40ff4002cdf6b3711d9f9a097f449ebe940;p=bar-game.git entity system improvements --- diff --git a/docs/plan.md b/docs/plan.md index e69de29..6dc5302 100644 --- a/docs/plan.md +++ b/docs/plan.md @@ -0,0 +1,15 @@ +MadLabs Brewery Bar Arcade Game +------------------------------- + +Think Overcooked meets Stardew Valley. + +Chaotic order-meeting tasks, with small town people with consistent orders. +When they've been here a couple of times, they should have to ask for their usual. + +As this is an arcade game, the controls will be rather limited: + - At most 2 players + - 1 joystick and 2 buttons each + - 1 button for interacting + - 1 button for canelling + + diff --git a/src/build.onyx b/src/build.onyx index e5765df..9ef2e81 100644 --- a/src/build.onyx +++ b/src/build.onyx @@ -1,3 +1,11 @@ +package runtime.vars + + +MAJOR_VERSION :: 0 +MINOR_VERSION :: 1 + +DEBUG :: true + #load_path "/home/brendan/dev/onyx" #load_path "./../lib" diff --git a/src/entity/manager.onyx b/src/entity/manager.onyx index 46c293a..8d43f9c 100644 --- a/src/entity/manager.onyx +++ b/src/entity/manager.onyx @@ -2,27 +2,34 @@ use package core use package glfw3 -Entity_Handles :: struct { - update : (entity: ^Entity, dt: f32) -> void = null_proc; - draw : (entity: ^Entity) -> void = null_proc; - get_rect : (entity: ^Entity) -> Rect = null_proc; -} - Entity :: struct { id: u32; type: type_expr; + flags: Entity_Flags; pos: Vector2; } +Entity_Flags :: enum #flags { + Interactable; + Solid; +} + IsEntity :: interface (e: $E) { { e } -> ^Entity; - { e.handles } -> Entity_Handles; + e.init_data; + e.init(e, e.init_data.{}); // This constraint ensures that everything in the initialization data is given a default value. +} + +Entity_Handles :: struct { + update : (entity: ^Entity, dt: f32) -> void = null_proc; + draw : (entity: ^Entity) -> void = null_proc; + get_rect : (entity: ^Entity) -> Rect = null_proc; + interact : (entity: ^Entity, interactor: ^Entity) -> void = null_proc; } Entity_Manager :: struct { - // The allocator for these entity pointers is assumed to be the - // context allocator. + entity_allocator: Allocator; entities: [..] ^Entity; entity_types: Map(type_expr, Entity_Handles); @@ -30,13 +37,17 @@ Entity_Manager :: struct { register :: entity_manager_register; add :: entity_manager_add; + make :: entity_manager_make; update :: entity_manager_update; draw :: entity_manager_draw; - query_entities :: entity_manager_query_entities; + query :: entity_manager_query; + query_by_type :: entity_manager_query_by_type; + query_by_flags :: entity_manager_query_by_flags; } -entity_manager_make :: () -> Entity_Manager { +entity_manager_create :: () -> Entity_Manager { em: Entity_Manager; + em.entity_allocator = context.allocator; @TODO // Replace the allocator here. array.init(^em.entities, 4); map.init(^em.entity_types); @@ -49,7 +60,17 @@ entity_manager_make :: () -> Entity_Manager { entity_manager_register :: (use this: ^Entity_Manager, $entity_type: type_expr) where IsEntity(^entity_type) { if !entity_types->has(entity_type) { - entity_types[entity_type] = entity_type.handles; + handles := Entity_Handles.{}; + + @CompilerFeatures // Maybe there should be data stored in the Type_Info_Struct about + // the functions/methods that are defined in the structs scope. I don't know if that + // would be worthwhile or if it would just be bulking up the type info data. + #if #defined(entity_type.update) { handles.update = entity_type.update; if DEBUG do printf("{} has '{}'.\n", entity_type, "update"); } + #if #defined(entity_type.draw) { handles.draw = entity_type.draw; if DEBUG do printf("{} has '{}'.\n", entity_type, "draw"); } + #if #defined(entity_type.get_rect) { handles.get_rect = entity_type.get_rect; if DEBUG do printf("{} has '{}'.\n", entity_type, "get_rect"); } + #if #defined(entity_type.interact) { handles.interact = entity_type.interact; if DEBUG do printf("{} has '{}'.\n", entity_type, "interact"); } + + entity_types[entity_type] = handles; } } @@ -64,40 +85,91 @@ entity_manager_add :: (use this: ^Entity_Manager, entity: ^$T) -> u32 where IsEn return entity.id; } +entity_manager_make :: (use this: ^Entity_Manager, $entity_type: type_expr, data: entity_type.init_data = .{}) -> ^entity_type where IsEntity(^entity_type) { + entity := new(entity_type, allocator=entity_allocator); + entity_type.init(entity, data); + this->add(entity); + return entity; +} + entity_manager_update :: (use this: ^Entity_Manager, dt: f32) { for entities { - vtable := ^entity_types[it.type]; - if vtable == null do continue; - if vtable.update == null_proc do continue; + update := entity_types[it.type].update; + if update == null_proc do continue; - vtable.update(it, dt); + update(it, dt); } } entity_manager_draw :: (use this: ^Entity_Manager) { // Entities should be sorted by z-order. for entities { - vtable := ^entity_types[it.type]; - if vtable == null do continue; - if vtable.draw == null_proc do continue; + draw := entity_types[it.type].draw; + if draw == null_proc do continue; + + draw(it); + } +} + +entity_manager_query :: (use this: ^Entity_Manager, area: Rect) -> [] ^Entity { + ents: [..] ^Entity; + for entities { + get_rect := entity_types[it.type].get_rect; + if get_rect == null_proc do continue; - vtable.draw(it); + if Rect.intersects(get_rect(it), area) { + ents << it; + } } + + return ents; + } -entity_manager_query_entities :: (use this: ^Entity_Manager, area: Rect, type: type_expr = void) -> [] ^Entity { +entity_manager_query_by_type :: (use this: ^Entity_Manager, area: Rect, type: type_expr) -> [] ^Entity { + #if DEBUG { + use type_info; + assert(struct_inherits(type, Entity), "Expected a type that inherits from Entity!"); + } + ents: [..] ^Entity; for entities { if type != void && it.type != type do continue; - vtable := ^entity_types[it.type]; - if vtable == null do continue; - if vtable.get_rect == null_proc do continue; + get_rect := entity_types[it.type].get_rect; + if get_rect == null_proc do continue; - if Rect.intersects(vtable.get_rect(it), area) { + if Rect.intersects(get_rect(it), area) { ents << it; } } return ents; -} \ No newline at end of file +} + +entity_manager_query_by_flags :: (use this: ^Entity_Manager, area: Rect, flags: Entity_Flags) -> [] ^Entity { + ents: [..] ^Entity; + + // Currently, enum #flags are not the greatest and & doesn't do what you want it to here. + expected_flags := cast(u32) flags; + + for entities { + if (~~ it.flags & expected_flags) != expected_flags do continue; + + get_rect := entity_types[it.type].get_rect; + if get_rect == null_proc do continue; + + if Rect.intersects(get_rect(it), area) { + ents << it; + } + } + + return ents; +} + +entity_is :: macro (entity: ^Entity, type: type_expr, body: Code) { + if entity.type == type { + it := cast(^type) entity; + #insert body; + } +} diff --git a/src/entity/player.onyx b/src/entity/player.onyx index fed7e8e..e3c119a 100644 --- a/src/entity/player.onyx +++ b/src/entity/player.onyx @@ -3,30 +3,48 @@ use package core use package glfw3 Player :: struct { - handles :: Entity_Handles.{ update, draw, get_rect } - use entity: Entity; - make :: () -> ^Player { - player := new(Player); - player.pos = .{0,0}; - return player; + health := 69.0f; + + Size :: 16.0f + + init_data :: struct { + pos := Vector2.{400, 200}; } - get_rect :: (use this: ^Player) => Rect.{ pos.x, pos.y, 64, 64 }; + init :: (use this: ^Player, data: init_data) { + this.pos = data.pos; + } + + get_rect :: (use this: ^Player) => Rect.{ pos.x - Size, pos.y - Size, Size * 2, Size * 2 }; update :: (use this: ^Player, dt: f32) { + speed :: Size * 8; + delta: Vector2; - if is_key_down(GLFW_KEY_W) do delta.y -= 200 * dt; - if is_key_down(GLFW_KEY_S) do delta.y += 200 * dt; - if is_key_down(GLFW_KEY_A) do delta.x -= 200 * dt; - if is_key_down(GLFW_KEY_D) do delta.x += 200 * dt; + if is_key_down(GLFW_KEY_W) do delta.y -= speed * dt; + if is_key_down(GLFW_KEY_S) do delta.y += speed * dt; + if is_key_down(GLFW_KEY_A) do delta.x -= speed * dt; + if is_key_down(GLFW_KEY_D) do delta.x += speed * dt; - walls := entity_manager->query_entities(.{pos.x - 100, pos.y - 100, 200, 200}, Wall); + walls := entity_manager->query_by_flags(.{pos.x - 100, pos.y - 100, 200, 200}, .Solid); defer memory.free_slice(^walls); try_move(this, .{delta.x, 0}, walls); try_move(this, .{0, delta.y}, walls); + + if is_key_just_up(GLFW_KEY_SPACE) { + area := Rect.{pos.x - Size * 2, pos.y - Size * 2, Size * 4, Size * 4}; + objects := entity_manager->query_by_flags(area, Entity_Flags.Solid | .Interactable); + defer memory.free_slice(^objects); + + for objects { + vtable := ^entity_manager.entity_types[it.type]; + if vtable.interact == null_proc do continue; + vtable.interact(it, this); + } + } } try_move :: (use this: ^Player, delta: Vector2, obsticles: [] ^Entity) { @@ -34,7 +52,8 @@ Player :: struct { ent_rect := get_rect(this); for obsticles { - if Rect.intersects(ent_rect, Wall.get_rect(~~ it)) { + vtable := ^entity_manager.entity_types[it.type]; + if Rect.intersects(ent_rect, vtable.get_rect(~~ it)) { pos -= delta; break; } @@ -43,7 +62,9 @@ Player :: struct { draw :: (use this: ^Player) { immediate_set_color(.{1,1,1}); - immediate_image(^player_texture, pos.x, pos.y, 64, 64); + + rect := this->get_rect(); + immediate_image(^player_texture, rect.x, rect.y, rect.w, rect.h); } } @@ -52,16 +73,19 @@ player_texture: Texture; Wall :: struct { - handles :: Entity_Handles.{ get_rect = get_rect, draw = draw } - use entity: Entity; size: Vector2; - make :: (pos, size: Vector2) -> ^Wall { - wall := new(Wall); - wall.pos = pos; - wall.size = size; - return wall; + init_data :: struct { + pos := Vector2.{0, 0}; + size := Vector2.{0, 0}; + } + + init :: (use this: ^Wall, data: init_data) { + this.pos = data.pos; + this.size = data.size; + + flags |= .Solid; } get_rect :: (use this: ^Wall) => Rect.{ pos.x, pos.y, size.x, size.y }; @@ -70,4 +94,65 @@ Wall :: struct { immediate_set_color(.{1,1,1}); immediate_rectangle(pos.x, pos.y, size.x, size.y); } -} \ No newline at end of file +} + +Door :: struct { + use entity: Entity; + size: Vector2; + + openness := 0.0f; + target_openness := 0.0f; + + init_data :: struct { + pos := Vector2.{0, 0}; + size := Vector2.{0, 0}; + } + + init :: (use this: ^Door, data: init_data) { + this.pos = data.pos; + this.size = data.size; + + this.flags |= .Interactable; + this.flags |= .Solid; + } + + get_rect :: (use this: ^Door) => Rect.{ pos.x, pos.y, size.x * (1 - openness), size.y }; + + update :: (use this: ^Door, dt: f32) { + if openness != target_openness { + move_towards(^openness, target_openness, dt * 2); + } + } + + interact :: (use this: ^Door, interactor: ^Entity) { + printf("The door at {} was interacted with by a {}!\n", pos, interactor.type); + + // Doors only open if interacted with by a player + entity_is(interactor, Player) { + if target_openness > 0 do target_openness = 0; + else do target_openness = 0.8f; + + dist := Vector2.mag(this.pos - interactor.pos); + printf("The player is {} units away and has {.2} health.\n", dist, it.health); + } + } + + draw :: (use this: ^Door) { + immediate_set_color(.{0.7, 0.7, 0.1}); + + r := this->get_rect(); + immediate_rectangle(r.x, r.y, r.w, r.h); + } +} + +@Relocate +move_towards :: (v: ^$T, target: T, diff: T) { + if math.abs(target - *v) <= diff { + *v = target; + return; + } + + if *v < target do *v += diff; + if *v > target do *v -= diff; +} + diff --git a/src/utils/vecmath.onyx b/src/utils/vecmath.onyx index 10217ba..70700ed 100644 --- a/src/utils/vecmath.onyx +++ b/src/utils/vecmath.onyx @@ -1,5 +1,7 @@ Vector2 :: struct [conv.Custom_Format.{format_vector2}] { x, y: f32; + + mag :: macro (v: Vector2) => math.sqrt(v.x * v.x + v.y * v.y); } Vector3i :: struct [conv.Custom_Format.{format_vector3i}] { @@ -40,8 +42,8 @@ Vector3 :: struct [conv.Custom_Format.{format_vector3}] { } #operator + macro (v1, v2: Vector2) => (typeof v1).{ v1.x + v2.x, v1.y + v2.y }; -#operator - macro (v1, v2: Vector2) => Vector2.{ v1.x - v2.x, v1.y - v2.y }; -#operator * macro (v: Vector2, s: f32) => Vector2.{ v.x * s, v.y * s }; +#operator - macro (v1, v2: Vector2) => (typeof v1).{ v1.x - v2.x, v1.y - v2.y }; +#operator * macro (v: Vector2, s: f32) => (typeof v1).{ v.x * s, v.y * s }; #operator == macro (v1, v2: Vector2) => v1.x == v2.x && v1.y == v2.y; #operator + macro (v1, v2: Vector3) => Vector3.{ v1.x + v2.x, v1.y + v2.y, v1.z + v2.z }; @@ -81,4 +83,4 @@ Rect :: struct { && r1.y <= r2.y + r2.h && r1.y + r1.h >= r2.y; } -} \ No newline at end of file +}