--- /dev/null
+Items
+-----
+
+Items in this game will be defined in a file, or a set of files.
+They will have the following properties:
+ - unique id
+ - display name
+ - texture (or some way of drawing the item)
+
+The file that the items will be stored in will have a similar format
+to the entity scene format:
+
+item_id:
+name="Test"
+texture="./assets/..."
--- /dev/null
+:beer
+name="Beer"
+weight=12.34
+
+
entity.size.y = 24.0000
target_openness = 0.8000
-:Item
+:Item_Entity
entity.id = 4
entity.flags = 0
entity.pos.x = 891.5230
color.b = 1.0000
color.a = 1.0000
-:Item
+:Item_Entity
entity.id = 5
entity.flags = 4
entity.pos.x = 167.6909
#load "gfx/texture"
#load "gfx/ui"
+#load "utils/any_utils"
+#load "utils/asset_loader"
#load "utils/input"
#load "utils/vecmath"
//
// - [x] Create new entity
// - [ ] Edit entity properties in UI
-// - [ ] Serialize / Deserialize a scene
+// - [x] Serialize / Deserialize a scene
//
use package core
use package opengles
use package glfw3
-Editor_Range :: struct {min, max: f32;}
-Editor_Disabled :: struct {}
+Editor_Range :: struct {min, max: f32;}
+Editor_Disabled :: struct {}
+Editor_Custom_Field :: struct { func: () -> void; }
editor_init :: () {
editor_font = font_lookup(.{"./assets/fonts/calibri.ttf", 18});
if resizing {
selected_entity.size += mouse_get_delta_vector();
}
+
+ if is_key_just_down(GLFW_KEY_DELETE) {
+ scene->delete(selected_entity);
+ selected_entity_id = Entity_Nothing;
+ }
}
}
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);
+ if draw_button(.{ x + w - 150, y, 150, 24 }, "Delete") {
+ scene->delete(entity);
+ selected_entity_id = Entity_Nothing;
+ return;
+ }
if active_index >= 0 do sidebar_width += w;
render_struct_fields(any.{~~entity, entity.type}, 0, x, y + 4, w, h);
use package core
+
Item :: struct {
+ name: str;
+ weight := 10.0f;
+
+ color := Color.{ 1, 1, 0 };
+}
+
+Item_Store :: struct {
+ item_allocator: Allocator;
+ items: Map(str, ^Item);
+
+ load_items_from_file :: item_store_load_items_from_file;
+}
+
+item_store_make :: () -> Item_Store {
+ is: Item_Store;
+ is.item_allocator = context.allocator;
+ map.init(^is.items);
+
+ return is;
+}
+
+item_store_load_items_from_file :: (use this: ^Item_Store, path: str) {
+ @CopyNPaste // from entity/store.onyx
+
+ err, input_file := os.open(path, .Read);
+ if err != .None {
+ printf("Failed to open file: {}\n", path);
+ return;
+ }
+ defer os.close(^input_file);
+ reader := io.reader_make(^input_file);
+
+ use type_info;
+
+ current_item: ^Item;
+
+ line_number := 1;
+ while !io.reader_empty(^reader) {
+ defer line_number += 1;
+
+ line := io.read_line(^reader, inplace=true);
+ if line.count <= 1 do continue;
+
+ if line[0] == #char ":" {
+ item_id := string.advance(line)
+ |> string.strip_whitespace();
+
+ if items->has(item_id) {
+ printf("Duplicate definition for item with id '{}', on line: {}.\n", item_id);
+ return;
+ }
+
+ item_id = string.alloc_copy(item_id, allocator=item_allocator);
+
+ current_item = new(Item, allocator=item_allocator);
+ items[item_id] = current_item;
+ continue;
+ }
+
+ if current_item == null do continue;
+
+ var_name := string.read_until(^line, #char "=")
+ |> string.strip_whitespace();
+ string.advance(^line, 1);
+ string.strip_whitespace(^line);
+
+ member := get_any_for_member(ptr_to_any(current_item), var_name);
+ if member.data == null {
+ printf("'{}' is not a valid member of Item on line {}.\n", var_name, line_number);
+ continue;
+ }
+
+ if !conv.parse_any(member.data, member.type, line) {
+ printf("Unable to parse '{}' for type '{}' on line {}.\n", line, member.type, line_number);
+ continue;
+ }
+ }
+}
+
+
+Item_Entity :: struct {
use entity: Entity;
+ [Editor_Custom_Field.{render_item_picker}]
+ item: str;
color: Color;
init_data :: struct {
color := Color.{1, 0, 1, 1};
}
- init :: (use this: ^Item, data: init_data) {
+ init :: (use this: ^Item_Entity, data: init_data) {
this.pos = data.pos;
this.size = .{16, 16};
this.color = data.color;
get_rect :: Entity.get_rect
- draw :: (use this: ^Item) {
+ draw :: (use this: ^Item_Entity) {
immediate_set_color(this.color);
r := this->get_rect();
immediate_rectangle(r.x, r.y, r.w, r.h);
}
}
+
+
+#local {
+ render_item_picker :: () {
+
+ }
+}
\ No newline at end of file
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;
update :: entity_manager_update;
draw :: entity_manager_draw;
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;
@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 printf("{} has '{}'.\n", entity_type, "destroy"); }
#if #defined(entity_type.update) { info.update = entity_type.update; if DEBUG do printf("{} has '{}'.\n", entity_type, "update"); }
#if #defined(entity_type.draw) { info.draw = entity_type.draw; if DEBUG do printf("{} has '{}'.\n", entity_type, "draw"); }
#if #defined(entity_type.get_rect) { info.get_rect = entity_type.get_rect; if DEBUG do printf("{} has '{}'.\n", entity_type, "get_rect"); }
entity_manager_get :: (use this: ^Entity_Manager, id: Entity_ID) => entity_map[id];
+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?
+ raw_free(entity_allocator, ent);
+}
+
entity_manager_query :: (use this: ^Entity_Manager, area: Rect) -> [] ^Entity {
ents: [..] ^Entity;
for entities {
immediate_set_color(color);
rect := this->get_rect();
- immediate_image(^player_texture, rect.x, rect.y, rect.w, rect.h);
+ immediate_image(^player_assets.texture, rect.x, rect.y, rect.w, rect.h);
}
}
-player_texture: Texture;
-
+player_assets: struct {
+ ["assets/images/player.png"] texture: Texture;
+}
Wall :: struct {
}
if current_entity != null do this->add(current_entity);
-
- get_any_for_member :: (base: any, member_name: str) -> any {
- use type_info;
-
- name := member_name;
- info := cast(^Type_Info_Struct) get_type_info(base.type);
- assert(info.kind == .Struct, "bad type");
-
- part_name := string.read_until(^name, #char ".");
- string.advance(^name, 1);
-
- for info.members {
- if it.name == part_name {
- member_info := get_type_info(it.type);
- if member_info.kind == .Struct {
- return get_any_for_member(any.{cast(^u8) base.data + it.offset, it.type}, name);
- } else {
- return any.{cast(^u8) base.data + it.offset, it.type};
- }
- }
- }
-
- return .{null, void};
- }
}
//
scene: Entity_Manager;
+item_store: Item_Store;
game_init :: () {
- player_texture = texture_make(#cstr "./assets/images/player.png");
+ // player_texture = texture_make(#cstr "./assets/images/player.png");
+
+ // This process of queueing the asset bucket should
+ // be made automatic somehow...
+ queue_assets(^player_assets);
+
+
+ load_assets();
scene = entity_manager_create();
scene->register(Player);
scene->register(Wall);
scene->register(Door);
- scene->register(Item);
+ scene->register(Item_Entity);
scene->load_from_file("scenes/quick_save.scene");
+ item_store = item_store_make();
+ item_store->load_items_from_file("scenes/default.items");
+
#if DEBUG {
println("Registered Entity types:");
for scene.entity_types.entries {
use package core
+use package core.intrinsics.onyx { __zero_value as Zero }
use package opengles
use package stb_image
+#local texture_cache: Map(str, Texture);
+
Texture :: struct {
texture: GLint;
width, height, channels: i32;
filename: str;
}
-texture_make :: (path: cstr) -> Texture {
+texture_make :: #match {}
+#match texture_make (filename: str) -> (Texture, bool) {
+ buffer: [512] u8;
+ memory.copy(~~ buffer, filename.data, math.min(filename.count, 511));
+ return texture_make(cast(cstr) buffer);
+}
+
+#match texture_make (path: cstr) -> (Texture, bool) {
+ filename := string.from_cstr(path);
+ if texture_cache->has(filename) {
+ return texture_cache[filename], true;
+ }
+
tex: Texture;
- tex.filename = path |> string.from_cstr();
+ tex.filename = filename;
pixels := stbi_load(path, ^tex.width, ^tex.height, ^tex.channels, 4);
- assert(pixels != null, "Failed to load texture.");
+ if pixels == null {
+ return Zero(Texture), false;
+ }
defer stbi_image_free(pixels);
glGenTextures(1, ^tex.texture);
glBindTexture(GL_TEXTURE_2D, 0);
- return tex;
+ // This assumes that the filename data is going to be persistant forever.
+ // Not a great assumption to make but is it really worth copying it?
+ texture_cache[filename] = tex;
+ return tex, true;
+}
+
+texture_free :: (use tex: ^Texture) {
+ glDeleteTextures(1, ^texture);
}
texture_use :: (use tex: ^Texture, texture_binding := 0) {
glActiveTexture(GL_TEXTURE0 + texture_binding);
glBindTexture(GL_TEXTURE_2D, texture);
}
-
--- /dev/null
+//
+// The contents of this file will probably be merged with the
+// core libraries in Onyx at some point. I just haven't flushed
+// out the details yet on what everything should exactly do and
+// be responsible for.
+//
+
+use package core
+
+ptr_to_any :: (x: ^$T) -> any {
+ return .{x, T};
+}
+
+//
+// This function looks up a member's offset and type (effectively
+// an 'any'), given the base pointer and type in the form of an
+// 'any' and the member_name, which is a '.' separate list of
+// symbols.
+//
+get_any_for_member :: (base: any, member_name: str) -> any {
+ use type_info;
+
+ name := member_name;
+ info := cast(^Type_Info_Struct) get_type_info(base.type);
+ assert(info.kind == .Struct, "bad type");
+
+ part_name := string.read_until(^name, #char ".");
+ string.advance(^name, 1);
+
+ for info.members {
+ if it.name == part_name {
+ member_info := get_type_info(it.type);
+ if member_info.kind == .Struct {
+ return get_any_for_member(any.{cast(^u8) base.data + it.offset, it.type}, name);
+ } else {
+ return any.{cast(^u8) base.data + it.offset, it.type};
+ }
+ }
+ }
+
+ return .{null, void};
+}
--- /dev/null
+
+use package core
+
+queue_assets :: (store: ^$T, callsite := #callsite) {
+ assets_to_load << .{ T, store, callsite };
+}
+
+load_assets :: () {
+ for^ assets_to_load {
+ load_asset_bucket(it);
+ }
+
+ array.clear(^assets_to_load);
+}
+
+
+#local {
+ assets_to_load: [..] Asset_Bucket_To_Load;
+
+ Asset_Bucket_To_Load :: struct {
+ type: type_expr;
+ dest: rawptr;
+
+ location: CallSite;
+ }
+
+ load_asset_bucket :: (bucket: ^Asset_Bucket_To_Load) {
+ use type_info;
+
+ struct_info := cast(^Type_Info_Struct) get_type_info(bucket.type);
+ if struct_info.kind != .Struct do return;
+
+ for struct_info.members {
+ load_asset(cast(^u8) bucket.dest + it.offset, it.type, bucket.location, it.tags);
+ }
+ }
+
+ load_asset :: (dest: rawptr, type: type_expr, location: CallSite, tags: [] any) {
+ switch type {
+ case Texture {
+ out_texture := cast(^Texture) dest;
+ path_tag := array.first(tags, (x) => x.type == str);
+
+ if path_tag != null {
+ path := *cast(^str) path_tag.data;
+ if texture, success := texture_make(path); success {
+ *out_texture = texture;
+ } else {
+ printf("[ERROR] Failed to load texture '{}' for asset queued here: {}:{},{}.\n", path, location.file, location.line, location.column);
+ }
+ } else {
+ printf("[ERROR] Texture path not found for texture asset load here: {}:{},{}.\n", location.file, location.line, location.column);
+ }
+ }
+
+ case #default {
+ printf("[WARN] Asset loader does not know how to load a '{}'.\n", type);
+ }
+ }
+ }
+}