From: Brendan Hansen Date: Fri, 25 Feb 2022 21:13:08 +0000 (-0600) Subject: started working on changing to a component-based architecture X-Git-Url: https://git.brendanfh.com/?a=commitdiff_plain;h=d06fe50dcb52b99af163c703f0f1a6db4426c2c3;p=bar-game.git started working on changing to a component-based architecture --- diff --git a/src/entity/editor.onyx b/src/entity/editor.onyx index a94bdce..88226b4 100644 --- a/src/entity/editor.onyx +++ b/src/entity/editor.onyx @@ -55,11 +55,13 @@ editor_update :: (dt: f32) { mouse_pos := mouse_get_position_vector(); if is_button_just_down(GLFW_MOUSE_BUTTON_LEFT) && mouse_pos.x < ~~window_width - sidebar_width && mouse_pos.y > menubar_height { + /* entity_type := ^scene.entity_types.entries[active_index].value; entity := entity_type.create_default(^scene); scene->add(entity); entity.pos = mouse_pos; + */ } } @@ -72,11 +74,9 @@ editor_update :: (dt: f32) { selected_entity_id = Entity_Nothing; for scene.entities { - if get_rect := ENT_INFO(it).get_rect; get_rect != null_proc { - if get_rect(it) |> Rect.contains(mouse_pos) { - selected_entity_id = it.id; - break; - } + if Entity.get_rect(it) |> Rect.contains(mouse_pos) { + selected_entity_id = it.id; + break; } } @@ -102,7 +102,7 @@ editor_update :: (dt: f32) { new_top_left.x = editor_grid_size * math.floor(new_top_left.x / editor_grid_size); new_top_left.y = editor_grid_size * math.floor(new_top_left.y / editor_grid_size); - rect := ENT_INFO(selected_entity).get_rect(selected_entity); + rect := Entity.get_rect(selected_entity); new_rect := Rect.{ new_top_left.x, new_top_left.y, rect.w, rect.h }; selected_entity.pos = Rect.center(new_rect); @@ -119,7 +119,7 @@ editor_update :: (dt: f32) { if resizing { if editor_grid_shown { - rect := ENT_INFO(selected_entity).get_rect(selected_entity); + rect := Entity.get_rect(selected_entity); new_size := mouse_get_position_vector() - Rect.top_left(rect); new_size.x = editor_grid_size * math.floor(new_size.x / editor_grid_size); new_size.y = editor_grid_size * math.floor(new_size.y / editor_grid_size); @@ -163,9 +163,8 @@ editor_draw :: () { if selected_entity_id != Entity_Nothing { selected_entity := scene->get(selected_entity_id); - get_rect := ENT_INFO(selected_entity).get_rect; - r := get_rect(selected_entity); + r := Entity.get_rect(selected_entity); immediate_set_color(.{1,1,0,0.5}); immediate_rectangle(r.x-2, r.y-2, r.w+4, r.h+4); immediate_rectangle(r.x, r.y, r.w, r.h); @@ -225,6 +224,7 @@ editor_draw :: () { #local render_create_sidebar :: (x, y, w, h: f32) { i := 0; + /* for scene.entity_types.entries { defer i += 1; @@ -237,6 +237,7 @@ editor_draw :: () { active_index = i; } } + */ #persist test : [..] u8; if test.data == null { @@ -255,7 +256,7 @@ editor_draw :: () { #local render_entity_fields :: (entity: ^Entity, x, y, w, h: f32) { assert(entity != null, "entity is null"); - assert(type_info.struct_inherits(entity.type, Entity), "entity is not an entity"); + /* info := cast(^type_info.Type_Info_Struct) type_info.get_type_info(entity.type); font_print(editor_big_font, x + 2, y + 24, info.name); @@ -267,6 +268,7 @@ editor_draw :: () { if active_index >= 0 do sidebar_width += w; render_struct_fields(any.{~~entity, entity.type}, 0, x, y + 4, w, h); + */ } #local render_struct_fields :: (v: any, i: i32, x, y, w, h: f32, depth := 0) -> (new_y: f32, new_i: i32) { @@ -322,9 +324,9 @@ editor_draw :: () { } elseif it.type == Entity_ID { value_buf: [1024] u8; - entity_type := (scene->get(*cast(^Entity_ID) member_any.data)).type; // Dereferencing null here - value_str := conv.format_va(value_buf, "{} ({})", .[member_any, .{^entity_type, type_expr}]); - font_print(editor_font, x + w - font_get_width(editor_font, value_str) - 2, y + Field_Height, value_str); + // entity_type := (scene->get(*cast(^Entity_ID) member_any.data)).type; // Dereferencing null here + // value_str := conv.format_va(value_buf, "{} ({})", .[member_any, .{^entity_type, type_expr}]); + // font_print(editor_font, x + w - font_get_width(editor_font, value_str) - 2, y + Field_Height, value_str); } else { value_buf: [1024] u8; diff --git a/src/entity/manager.onyx b/src/entity/manager.onyx index 89406bd..e54cfda 100644 --- a/src/entity/manager.onyx +++ b/src/entity/manager.onyx @@ -15,16 +15,41 @@ Entity_ID :: #distinct u32 }); } +Component :: struct { + type: type_expr; +} + +IsComponent :: interface (c: $C) { + { c } -> ^Component; +} + +UpdateComponent :: struct { + use base: Component; + func : (e: ^Entity, dt: f32) -> void; +} + +RenderComponent :: struct { + use base: Component; + func : (e: ^Entity) -> void; +} + Entity :: struct { id: Entity_ID; - [Entity_Store.Skip] - type: type_expr; flags: Entity_Flags; pos: Vector2; - size: Vector2; + // Does every entity need to have a size? + size: Vector2; get_rect :: (use e: ^Entity) => Rect.{ pos.x - size.x / 2, pos.y - size.y / 2, size.x, size.y }; + + components: Map(type_expr, ^Component); + has :: (use this: ^Entity, component_type: type_expr) => components->has(component_type); + get :: (use this: ^Entity, $component_type: type_expr) => cast(^component_type) components[component_type]; + add :: (use this: ^Entity, component: ^Component) => { + if components->has(component.type) do return; + components[component.type] = component; + } } Entity_Flags :: enum #flags { @@ -34,22 +59,6 @@ Entity_Flags :: enum #flags { Carryable :: 0x04; } -IsEntity :: interface (e: $E) { - { e } -> ^Entity; - e.init_data; - e.init(e, e.init_data.{}); // This constraint ensures that everything in the initialization data is given a default value. -} - -Entity_Info :: struct { - create_default : (scene: ^Entity_Manager) -> ^Entity = null_proc; - - destroy : (entity: ^Entity) -> void = null_proc; - 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 { entity_allocator: Allocator; @@ -57,11 +66,10 @@ Entity_Manager :: struct { entities: [..] ^Entity; entity_map: Map(Entity_ID, ^Entity); - entity_types: Map(type_expr, Entity_Info); + // entity_types: Map(type_expr, Entity_Info); next_entity_id: Entity_ID; - register :: entity_manager_register; add :: entity_manager_add; make :: entity_manager_make; update :: entity_manager_update; @@ -69,28 +77,21 @@ Entity_Manager :: struct { get :: entity_manager_get; delete :: entity_manager_delete; query :: entity_manager_query; - query_by_type :: entity_manager_query_by_type; query_by_flags :: entity_manager_query_by_flags; + query_by_component :: entity_manager_query_by_component; + create_component :: entity_manager_create_component; + create_and_add :: entity_manager_create_and_add; + load_from_file :: entity_manager_load_from_file; save_to_file :: entity_manager_save_to_file; } -// This assumes that the main entity manager is called "scene". -ENT_INFO :: macro (e: ^Entity) => { - @CompilerBug // Why does the following line break the program? - // It's like its declaring a new null scene...? - // scene :: scene; - - return ^scene.entity_types[e.type]; -} - 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); // Entity ID 0 is reserved as a "empty / null" entity em.next_entity_id = 1; @@ -98,45 +99,6 @@ entity_manager_create :: () -> Entity_Manager { return em; } -entity_manager_register :: (use this: ^Entity_Manager, $entity_type: type_expr) where IsEntity(^entity_type) { - if !entity_types->has(entity_type) { - - #if DEBUG { // Validate that the entity_type does not have any pointers. - use type_info; - - info := cast (^Type_Info_Struct) get_type_info(entity_type); - for info.members { - member_info := get_type_info(it.type); - is_pointer := member_info.kind == .Pointer; - if member_info.kind == .Basic { - if (cast(^Type_Info_Basic) member_info).basic_kind == .Rawptr { - is_pointer = is_pointer; - } - } - - message_buf: [1024] u8; - message := conv.format(message_buf, "Cannot have a pointer member on an Entity! '{}' is a pointer!", it.name); - assert(!is_pointer, message); - } - } - - - info := Entity_Info.{}; - info.create_default = #solidify entity_manager_create_default { entity_type=entity_type }; - - @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.destroy) { info.destroy = entity_type.destroy; if DEBUG do debug_log(.Debug, "{} has '{}'.", entity_type, "destroy"); } - #if #defined(entity_type.update) { info.update = entity_type.update; if DEBUG do debug_log(.Debug, "{} has '{}'.", entity_type, "update"); } - #if #defined(entity_type.draw) { info.draw = entity_type.draw; if DEBUG do debug_log(.Debug, "{} has '{}'.", entity_type, "draw"); } - #if #defined(entity_type.get_rect) { info.get_rect = entity_type.get_rect; if DEBUG do debug_log(.Debug, "{} has '{}'.", entity_type, "get_rect"); } - #if #defined(entity_type.interact) { info.interact = entity_type.interact; if DEBUG do debug_log(.Debug, "{} has '{}'.", entity_type, "interact"); } - - entity_types[entity_type] = info; - } -} - entity_manager_add :: (use this: ^Entity_Manager, entity: ^Entity) -> Entity_ID { // Automatically assign an id if the entity does not have one. // This will not be used when loading the entity from a file. @@ -147,44 +109,47 @@ entity_manager_add :: (use this: ^Entity_Manager, entity: ^Entity) -> Entity_ID next_entity_id = ~~(math.max(cast(u32) next_entity_id, cast(u32) entity.id) + 1); } - assert(cast(u32) entity.type != 0, "Adding an entity without a type!"); - entities << entity; entity_map[entity.id] = entity; return entity.id; } -#local entity_manager_create_default :: (use this: ^Entity_Manager, $entity_type: type_expr) -> ^entity_type where IsEntity(^entity_type) { - entity := new(entity_type, allocator=entity_allocator); - entity.type = entity_type; - entity_type.init(entity, entity_type.init_data.{}); +entity_manager_make :: (use this: ^Entity_Manager) -> ^Entity { + entity := new(Entity, allocator=entity_allocator); + map.init(^entity.components); return entity; } -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 = entity_type; - entity_type.init(entity, data); - this->add(entity); - return entity; +entity_manager_create_component :: (use this: ^Entity_Manager, $component_type: type_expr) -> ^component_type where IsComponent(^component_type) { + comp := new(component_type, allocator=entity_allocator); + comp.type = component_type; + return comp; +} + +entity_manager_create_and_add :: macro (this: ^Entity_Manager, entity: ^Entity, $component_type: type_expr, init_block: Code) where IsComponent(^component_type) { + comp := new(component_type, allocator=this.entity_allocator); + comp.type = component_type; + + #insert init_block; + entity->add(comp); } entity_manager_update :: (use this: ^Entity_Manager, dt: f32) { for entities { - update := entity_types[it.type].update; - if update == null_proc do continue; - - update(it, dt); + update_comp := it->get(UpdateComponent); + if update_comp != null { + update_comp.func(it, dt); + } } } entity_manager_draw :: (use this: ^Entity_Manager) { // Entities should be sorted by z-order. for entities { - draw := entity_types[it.type].draw; - if draw == null_proc do continue; - - draw(it); + render_comp := it->get(RenderComponent); + if render_comp != null { + render_comp.func(it); + } } } @@ -193,38 +158,35 @@ entity_manager_get :: (use this: ^Entity_Manager, id: Entity_ID) => entity_map[i entity_manager_delete :: (use this: ^Entity_Manager, ent: ^Entity) { map.delete(^entity_map, ent.id); array.remove(^entities, ent); // This preserves the order of the entities, but does that matter? + + // This assuems that components cannot and will not be shared between entities. + for^ ent.components.entries { + if it.value != null { + raw_free(entity_allocator, it.value); + } + } + + map.free(^ent.components); raw_free(entity_allocator, ent); } 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; - - if Rect.intersects(get_rect(it), area) { + if Rect.intersects(Entity.get_rect(it), area) { ents << it; } } return ents; - } -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!"); - } - +entity_manager_query_by_component :: (use this: ^Entity_Manager, area: Rect, comp_type: type_expr) -> [] ^Entity { ents: [..] ^Entity; for entities { - if it.type != type do continue; - - get_rect := entity_types[it.type].get_rect; - if get_rect == null_proc do continue; + if !it.components->has(comp_type) do continue; - if Rect.intersects(get_rect(it), area) { + if Rect.intersects(Entity.get_rect(it), area) { ents << it; } } @@ -241,10 +203,7 @@ entity_manager_query_by_flags :: (use this: ^Entity_Manager, area: Rect, 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) { + if Rect.intersects(Entity.get_rect(it), area) { ents << it; } } @@ -252,20 +211,3 @@ entity_manager_query_by_flags :: (use this: ^Entity_Manager, area: Rect, flags: return ents; } -if_entity_is :: macro (entity: ^Entity, type: type_expr, body: Code) { - if entity.type == type { - it := cast(^type) entity; - #insert body; - } -} - -switch_entity :: macro (entity: ^Entity, body: Code) { - switch entity.type do #insert body; -} - -entity_case :: macro (type: type_expr, body: Code) { - case type { - object := cast(^type) entity; - #insert body; - } -} diff --git a/src/entity/player.onyx b/src/entity/player.onyx index ea4df01..19e71b2 100644 --- a/src/entity/player.onyx +++ b/src/entity/player.onyx @@ -45,32 +45,130 @@ facing_to_direction_vector :: macro (f: Facing) -> Vector2 { } } -Player :: struct { - use entity: Entity; +player_create :: (scene: ^Entity_Manager, pos := Vector2.{400, 200}, controls := player_1_controls) -> ^Entity { + this := scene->make(); + this.pos = pos; + this.size = .{32, 32}; + this.flags |= .Solid; + + scene->create_and_add(this, PlayerComponent) { + comp.holding = Entity_Nothing; + comp.controls = controls; + comp.color = .{1,1,1}; + } - [Editor_Disabled] - holding: Entity_ID; - controls: Player_Controls; - color := Color.{1,1,1}; - facing := Facing.Up; + scene->create_and_add(this, UpdateComponent) { + comp.func = PlayerComponent.update; + } - init_data :: struct { - pos := Vector2.{400, 200}; - controls: Player_Controls = player_1_controls; - color := Color.{1,1,1}; + scene->create_and_add(this, RenderComponent) { + comp.func = PlayerComponent.render; } - init :: (use this: ^Player, data: init_data) { - this.pos = data.pos; - this.size = .{32, 32}; - this.holding = Entity_Nothing; + return this; +} + +PlayerComponent :: struct { + use base: Component; - this.controls = data.controls; - this.color = data.color; + color := Color.{1,1,1}; + holding : Entity_ID; + controls : Player_Controls; + facing := Facing.Up; + create :: (scene: ^Entity_Manager, pos := Vector2.{400, 200}, controls := player_1_controls) -> ^Entity { + this := scene->make(); + this.pos = pos; + this.size = .{32, 32}; this.flags |= .Solid; + + player_comp := scene->create_component(PlayerComponent); + player_comp.holding = Entity_Nothing; + player_comp.controls = controls; + player_comp.color = .{1,1,1}; + this->add(player_comp); + + update_comp := scene->create_component(UpdateComponent); + update_comp.func = update; + this->add(update_comp); + + render_comp := scene->create_component(RenderComponent); + render_comp.func = render; + this->add(render_comp); + + return this; + } + + update :: (use this: ^Entity, dt: f32) { + speed :: 128.0f; + + player := this->get(PlayerComponent); + + delta: Vector2; + if is_key_down(player.controls.left) { delta.x -= speed * dt; player.facing = .Left; } + if is_key_down(player.controls.right) { delta.x += speed * dt; player.facing = .Right; } + if is_key_down(player.controls.up) { delta.y -= speed * dt; player.facing = .Up; } + if is_key_down(player.controls.down) { delta.y += speed * dt; player.facing = .Down; } + + dist := math.max(size.x, size.y) * 2; + walls := scene->query_by_flags(.{pos.x - dist, pos.y - dist, dist * 2, dist * 2}, .Solid); + defer memory.free_slice(^walls); + + try_move(this, .{delta.x, 0}, walls); + try_move(this, .{0, delta.y}, walls); + } + + try_move :: (use this: ^Entity, delta: Vector2, obsticles: [] ^Entity) { + pos += delta; + ent_rect := Entity.get_rect(this); + + holding: Entity_ID; + if player := this->get(PlayerComponent); player != null { + holding = player.holding; + } + + for obsticles { + // Don't check collision on the object that you are carrying or yourself because it probably won't make sense. + if it == this do continue; + if it.id == holding do continue; + + if Rect.intersects(ent_rect, Entity.get_rect(it)) { + pos -= delta; + break; + } + } + } + + render :: (use this: ^Entity) { + player := this->get(PlayerComponent); + immediate_set_color(player.color); + + rect := Entity.get_rect(this); + immediate_image(^player_assets.texture, rect.x, rect.y, rect.w, rect.h); + } +} + +wall_create :: (scene: ^Entity_Manager, pos, size: Vector2) -> ^Entity { + this := scene->make(); + this.pos = pos; + this.size = size; + this.flags |= .Solid; + + scene->create_and_add(this, RenderComponent) { + comp.func = wall_draw; } + return this; +} + +wall_draw :: (use this: ^Entity) { + immediate_set_color(.{1,1,1}); + + r := Entity.get_rect(this); + immediate_rectangle(r.x, r.y, r.w, r.h); +} + +/* get_rect :: Entity.get_rect update :: (use this: ^Player, dt: f32) { @@ -98,6 +196,7 @@ Player :: struct { if holding != Entity_Nothing { holding_object := scene->get(holding); holding_object.flags |= .Carryable; + /* :ENT_INFO obj_get_rect := ENT_INFO(holding_object).get_rect; if obj_get_rect != null_proc { r := get_rect(holding_object); @@ -105,6 +204,7 @@ Player :: struct { d := Vector2.{(size.x + r.w) / 2 + 4, (size.y + r.h) / 2 + 4}; holding_object.pos = pos + facing_to_direction_vector(facing) * d; } + */ holding = Entity_Nothing; } else { @@ -136,9 +236,11 @@ Player :: struct { defer memory.free_slice(^objects); for objects { + /* :ENT_INFO if interact := ENT_INFO(it).interact; interact != null_proc { interact(it, this); } + */ } } @@ -148,11 +250,13 @@ Player :: struct { // if holding != Entity_Nothing { holding_object := scene->get(holding); + /* :ENT_INFO obj_get_rect := ENT_INFO(holding_object).get_rect; if obj_get_rect != null_proc { r := obj_get_rect(holding_object); holding_object.pos = pos - .{0, (size.y + r.h) / 2}; } + */ } } @@ -166,52 +270,23 @@ Player :: struct { if it == this do continue; if it.id == this.holding do continue; + /* :ENT_INFO vtable := ENT_INFO(it); if Rect.intersects(ent_rect, vtable.get_rect(it)) { pos -= delta; break; } + */ } } - draw :: (use this: ^Player) { - immediate_set_color(color); - - rect := this->get_rect(); - immediate_image(^player_assets.texture, rect.x, rect.y, rect.w, rect.h); - } } +*/ player_assets: struct { ["assets/images/player.png"] texture: Texture; } - -Wall :: struct { - use entity: Entity; - - init_data :: struct { - pos := Vector2.{0, 0}; - size := Vector2.{10, 10}; - } - - init :: (use this: ^Wall, data: init_data) { - this.pos = data.pos; - this.size = data.size; - - flags |= .Solid; - } - - get_rect :: Entity.get_rect - - draw :: (use this: ^Wall) { - immediate_set_color(.{1,1,1}); - - r := Wall.get_rect(this); - immediate_rectangle(r.x, r.y, r.w, r.h); - } -} - Door :: struct { use entity: Entity; @@ -244,9 +319,9 @@ Door :: struct { interact :: (use this: ^Door, interactor: ^Entity) { // Doors only open if interacted with by a player - if_entity_is(interactor, Player) { + //if_entity_is(interactor, Player) { target_openness = 0.8f - target_openness; - } + //} } draw :: (use this: ^Door) { diff --git a/src/entity/store.onyx b/src/entity/store.onyx index 85c0ee7..f744d1f 100644 --- a/src/entity/store.onyx +++ b/src/entity/store.onyx @@ -8,6 +8,7 @@ Entity_Store :: enum { } entity_manager_save_to_file :: (use this: ^Entity_Manager, filename: str) { + /* err, output_file := os.open(filename, .Write); defer os.close(^output_file); writer_ := io.writer_make(^output_file); @@ -70,9 +71,11 @@ entity_manager_save_to_file :: (use this: ^Entity_Manager, filename: str) { } } } + */ } entity_manager_load_from_file :: (use this: ^Entity_Manager, filename: str) { + /* err, input_file := os.open(filename, .Read); if err != .None { debug_log(.Error, "Failed to open file: {}", filename); @@ -142,6 +145,7 @@ entity_manager_load_from_file :: (use this: ^Entity_Manager, filename: str) { } if current_entity != null do this->add(current_entity); + */ } diff --git a/src/game.onyx b/src/game.onyx index b48cb02..de36b34 100644 --- a/src/game.onyx +++ b/src/game.onyx @@ -17,22 +17,15 @@ game_init :: () { load_assets(); scene = entity_manager_create(); - scene->register(Player); - scene->register(Wall); - scene->register(Door); - scene->register(Item_Entity); - scene->load_from_file("scenes/quick_save.scene"); + // scene->load_from_file("scenes/quick_save.scene"); + player := player_create(^scene); + scene->add(player); + + wall := wall_create(^scene, .{0, 100}, .{300, 40}); + scene->add(wall); item_store = item_store_make(); item_store->load_items_from_file("scenes/default.items"); - - #if DEBUG { - debug_log(.Debug, "Registered Entity types:"); - for scene.entity_types.entries { - info := cast(^type_info.Type_Info_Struct) type_info.get_type_info(it.key); - debug_log(.Debug, " {}", info.name); - } - } } #local quick_save_file := "scenes/quick_save.scene";