entity system improvements
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Sat, 29 Jan 2022 04:03:40 +0000 (22:03 -0600)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Sat, 29 Jan 2022 04:03:40 +0000 (22:03 -0600)
docs/plan.md
src/build.onyx
src/entity/manager.onyx
src/entity/player.onyx
src/utils/vecmath.onyx

index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..6dc53028e16ccaa4a5e294d7b807afeaadb644a1 100644 (file)
@@ -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
+
+
index e5765dffcfc60b767aee10c9eedc328eb9849f0e..9ef2e81f62e60824dac155eba158a0c74e533401 100644 (file)
@@ -1,3 +1,11 @@
+package runtime.vars
+
+
+MAJOR_VERSION :: 0
+MINOR_VERSION :: 1
+
+DEBUG :: true
+
 
 #load_path "/home/brendan/dev/onyx"
 #load_path "./../lib"
index 46c293aad173c750ed9fbd9aa9f7fd6107eed453..8d43f9c3f8c299b67ccee572aad2552071249c9e 100644 (file)
@@ -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;
+    }
+}
index fed7e8e804f003f439a22ca1d89ca332d7b87a7e..e3c119a0b1edecbdeb042d20175c9f4783c40839 100644 (file)
@@ -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;
+}
+
index 10217ba5dec5f0b4794982c2e14c205a48327783..70700edd659d71f489de68b120a951db9cb73c0f 100644 (file)
@@ -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
+}