diff --git a/Makefile b/Makefile index 051924f..2d9a244 100755 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ MAKEFLAGS = --jobs=4 UNAME != uname -nMAKEDIR != pwd +MAKEDIR != pwd # Options # DBG --- build with debugging symbols and logging @@ -71,7 +71,7 @@ ARCH = x64 ifeq ($(OS), Windows_NT) LDFLAGS += -mwin32 -static CFLAGS += -mwin32 - LDLIBS += mingw32 kernel32 opengl32 user32 shell32 dxgi gdi32 ws2_32 ole32 winmm setupapi m + LDLIBS += mingw32 kernel32 d3d11 user32 shell32 dxgi gdi32 ws2_32 ole32 winmm setupapi m EXT = .exe PLAT = w64 PKGCMD = cd $(BIN); zip -q -r $(MAKEDIR)/$(DISTDIR)/$(DIST) . -x \*.a ./obj/\* @@ -142,11 +142,12 @@ SHADERS = $(shell ls source/shaders/*.sglsl) SHADERS := $(patsubst %.sglsl, %.sglsl.h, $(SHADERS)) install: $(BIN)/$(NAME) - cp $(BIN)/$(NAME) $(DESTDIR) + cp -f $(BIN)/$(NAME) $(DESTDIR) $(BIN)/$(NAME): $(BIN)/libengine.a $(BIN)/libquickjs.a $(BIN)/libcdb.a @echo Linking $(NAME) $(LD) $^ $(LDFLAGS) -L$(BIN) $(LDLIBS) -o $@ + cp $(BIN)/$(NAME) . @echo Finished build $(DISTDIR)/$(DIST): $(BIN)/$(NAME) @@ -207,6 +208,11 @@ jso: tools/jso.c $(BIN)/libquickjs.a @echo Making $@ from $< ./jso $< > $@ +WINCC = x86_64-w64-mingw32-gcc +.PHONY: crosswin +crosswin: + make CC=$(WINCC) OS=Windows_NT + clean: @echo Cleaning project @rm -rf bin dist diff --git a/docs/game.md b/docs/game.md index c33a865..a0dc273 100644 --- a/docs/game.md +++ b/docs/game.md @@ -42,7 +42,7 @@ In edit mode, there are no running scripts; only editing them. There are two distinct items in the Primum Machina: the Entity, and the Component. Components give qualities to Entities. An Entity is any real, tangible thing in the universe, and so every entity has a position. Components do not necessarily have a position; they can be things like the image that draws where the entity is located, and colliders that allow the entity to respond with the world. ### Components -The most "bare metal" are the components. These are essentially hooks into the engine that tell it how to do particular things. For example, to render a sprite, Javascript does no rendering, but rather tells the engine to create an image and render it in a particular spot. +The most "bare metal" are the components. These are essentially hooks into the engine that tell it how to do particular things. For example, to render a sprite, Javascript does no rendering, but rather tells the engine to create an image and render it in a particular spot. Javascript does the accounting to make or destroy the sprite as needed - but besides that initial startup, no scripting is done. Components are rendered in an "ECS" style. To work, components must be installed on an entity. They have no meaning outside of a physical object in the world. @@ -62,7 +62,7 @@ All objects follow the prototyping model of inheritence. This makes it trivial t Components cannot be prototyped. They are fundamentally tied to the entity they are bound to. -Entities can be prototyped out. What this means is that, when you select an object in the game, you can either make a "subtype" of it, where changes to the object trickle down to the created one, or a "sidetype" of it, which is a total duplicate of the object. +Entities can be prototyped out. What this means is that, when you select an object in the game, you can either make a "subtype" of it, where changes to the object trickle down to the created one, or a "sidetype" of it, which is a total duplicate of the object. Javascript handled creating entites with components that have your saved values. entity.clone(parent) -> create a subtyped version of the entity entity.dup(parent) -> create a copy of the entity. @@ -79,6 +79,17 @@ Only first Ur-types can have components. Every inherited thing after it can only Ur-types also remember the list of entities that compose the given Ur. +Visually it looks like this: + +Ur-ur, the thing all Ur-types derive from + - Ur-type 1, defined in script, with components + - Variant 1, same component combination but different values + - Variant 2, other different values + - Variant 2A, overwritten values from Variant 2 + - Ur-type 2 + +All ur-types and variants can be created in the world, where they become a true blue ENTITY. Entities can be under entities infinitely. + ### Loading traits Traits are defined by code and a data file. When an Ur-type is extended with a trait, the code is run, and then the data file contains modifications and diff --git a/scripts/base.js b/scripts/base.js index d46aa1c..0c28ac4 100644 --- a/scripts/base.js +++ b/scripts/base.js @@ -24,6 +24,11 @@ Object.defineProperty(Object.prototype, 'getOwnPropertyDescriptors', { } }); +Object.defHidden = function(obj, prop) +{ + Object.defineProperty(obj, prop, {enumerable:false, writable:true}); +} + Object.defineProperty(Object.prototype, 'obscure', { value: function(name) { Object.defineProperty(this, name, { enumerable: false }); diff --git a/scripts/editor.js b/scripts/editor.js index 4861473..6a654e1 100644 --- a/scripts/editor.js +++ b/scripts/editor.js @@ -762,7 +762,15 @@ editor.inputs['C-d'] = function() { }; editor.inputs['C-d'].doc = "Duplicate all selected objects."; -editor.inputs.f3 = function() { editor.selectlist.forEach(x => Log.say(JSON.stringify(x,null,2))); }; +editor.inputs.f3 = function() { + Log.say("Selected JSON ..."); + editor.selectlist.forEach(x => Log.say(JSON.stringify(x,null,2))); + Log.say("UR JSON ..."); + for (var key of Object.keys(editor.selectlist[0].ur.type)) + Log.say(key); + + editor.selectlist.forEach(x => Log.say(JSON.stringify(x.ur.type,null,2))); +}; editor.inputs['C-m'] = function() { if (editor.sel_comp) { @@ -1922,3 +1930,4 @@ if (IO.exists("editor.config")) editor.clear_level(); editor.camera = Game.camera; Game.stop(); +Game.editor_mode(false); diff --git a/scripts/engine.js b/scripts/engine.js index 3407344..9a145cb 100644 --- a/scripts/engine.js +++ b/scripts/engine.js @@ -510,7 +510,7 @@ var Game = { sys_cmd(4); }, - render() { sys_cmd(10); }, + editor_mode(m) { sys_cmd(10, m); }, playing() { return sys_cmd(5); }, paused() { return sys_cmd(6); }, @@ -554,6 +554,13 @@ gameobject.make_parentable(Primum); Primum.tag = "PRIMUM"; Primum.selectable = false; Primum.ur = { tag: "Primum" }; +Primum.spawn = function(ur) { + if (typeof ur === 'string') + ur = prototypes.get_ur(ur); + + return ur.type.make(this); + }; + /* Reparent this object to a new one */ World.reparent = function(parent) { Log.warn("Cannot reparent the Primum."); } World.unparent = function() { Log.warn("The Primum has no parent, always."); } diff --git a/scripts/entity.js b/scripts/entity.js index ad966a4..8df3f43 100644 --- a/scripts/entity.js +++ b/scripts/entity.js @@ -15,13 +15,7 @@ var gameobject = { save: true, selectable: true, - spawn(ur) { - if (typeof ur === 'string') - ur = prototypes.get_ur(ur); - - return ur.type.make(this); - }, - + /* Make a duplicate of this exact object */ clone(name, ext) { var obj = Object.create(this); complete_assign(obj, ext); @@ -113,37 +107,12 @@ var gameobject = { gizmo: "", /* Path to an image to draw for this gameobject */ - /* Bounding box of the object in world dimensions */ + /* Bounding box of the ur, if it were to be spawned */ boundingbox() { - var boxes = []; - boxes.push({t:0, r:0,b:0,l:0}); - for (var key in this.components) { - if ('boundingbox' in this.components[key]) - boxes.push(this.components[key].boundingbox()); - } - - if (boxes.empty) return cwh2bb([0,0], [0,0]); - - var bb = boxes[0]; - - boxes.forEach(function(x) { - bb = bb_expand(bb, x); - }); - - var cwh = bb2cwh(bb); - - if (!bb) return; - - if (this.flipx) cwh.c.x *= -1; - if (this.flipy) cwh.c.y *= -1; - - cwh.c = cwh.c.add(this.pos); - bb = cwh2bb(cwh.c, cwh.wh); - - return bb ? bb : cwh2bb([0,0], [0,0]); }, + width() { var bb = this.boundingbox(); return bb.r - bb.l; @@ -204,7 +173,8 @@ var gameobject = { make(level) { level ??= Primum; var obj = Object.create(this); - this.instances.push(obj); +// this.instances.push(obj); +// obj.ur = this; obj.toString = function() { if (obj.ur) return obj.ur.tag; @@ -272,6 +242,45 @@ var gameobject = { sync() { }, dirty() { return false; }, + spawn(ur) { + if (typeof ur === 'string') + ur = prototypes.get_ur(ur); + + return ur.type.make(this); + }, + + + /* Bounding box of the object in world dimensions */ + boundingbox() { + var boxes = []; + boxes.push({t:0, r:0,b:0,l:0}); + + for (var key in this.components) { + if ('boundingbox' in this.components[key]) + boxes.push(this.components[key].boundingbox()); + } + + if (boxes.empty) return cwh2bb([0,0], [0,0]); + + var bb = boxes[0]; + + boxes.forEach(function(x) { + bb = bb_expand(bb, x); + }); + + var cwh = bb2cwh(bb); + + if (!bb) return; + + if (this.flipx) cwh.c.x *= -1; + if (this.flipy) cwh.c.y *= -1; + + cwh.c = cwh.c.add(this.pos); + bb = cwh2bb(cwh.c, cwh.wh); + + return bb ? bb : cwh2bb([0,0], [0,0]); + }, + dup(diff) { var dup = Primum.spawn(this.ur); Object.assign(dup, this); @@ -312,7 +321,9 @@ var gameobject = { down() { return [0,-1].rotate(Math.deg2rad(this.angle));}, right() { return [1,0].rotate(Math.deg2rad(this.angle));}, left() { return [-1,0].rotate(Math.deg2rad(this.angle));}, - + + /* Given an ur-type, spawn one attached to us */ + toJSON() { var ret = {}; for (var key in this) { @@ -371,6 +382,7 @@ var gameobject = { gameobject.make_parentable = function(obj) { var objects = []; + Object.defHidden(obj, 'level'); obj.remove_child = function(child) { objects.remove(child); @@ -434,19 +446,20 @@ prototypes.from_file = function(file) return; } - var newobj = gameobject.clone(file, {}); + var newur = gameobject.clone(file, {}); var script = IO.slurp(file); - newobj.$ = {}; + Object.defHidden(newur, '$'); + newur.$ = {}; var json = {}; if (IO.exists(file.name() + ".json")) { json = JSON.parse(IO.slurp(file.name() + ".json")); - Object.assign(newobj.$, json.$); + Object.assign(newur.$, json.$); delete json.$; } - compile_env(`var self = this; var $ = self.$; ${script}`, newobj, file); - dainty_assign(newobj, json); + compile_env(`var self = this; var $ = self.$; ${script}`, newur, file); + dainty_assign(newur, json); file = file.replaceAll('/', '.'); var path = file.name().split('.'); @@ -457,12 +470,13 @@ prototypes.from_file = function(file) return base; }; var a = nested_access(ur, path); - + Object.defHidden(a, 'instances'); + a.instances = []; a.tag = file.name(); prototypes.list.push(a.tag); - a.type = newobj; + a.type = newur; a.instances = []; - newobj.ur = a; +// newur.ur = a; return a; } diff --git a/source/engine/ffi.c b/source/engine/ffi.c index 3128cb2..544e314 100644 --- a/source/engine/ffi.c +++ b/source/engine/ffi.c @@ -1249,7 +1249,7 @@ JSValue duk_sys_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *ar break; case 10: - render_dirty = 1; + editor_mode = js2bool(argv[1]); break; } diff --git a/source/engine/render.c b/source/engine/render.c index d3777ca..95393e1 100644 --- a/source/engine/render.c +++ b/source/engine/render.c @@ -430,15 +430,6 @@ void full_2d_pass(struct window *window) //////////// 2D projection cpVect pos = cam_pos(); -#if defined SOKOL_GLCORE33 || defined SOKOL_GLES3 - projection = HMM_Orthographic_RH_ZO( - pos.x - zoom * window->rwidth / 2, - pos.x + zoom * window->rwidth / 2, - pos.y + zoom * window->rheight / 2, - pos.y - zoom * window->rheight / 2, -1.f, 1.f); - - hudproj = HMM_Orthographic_RH_ZO(0, window->width, window->height, 0, -1.f, 1.f); -#else projection = HMM_Orthographic_LH_ZO( pos.x - zoom * window->rwidth / 2, pos.x + zoom * window->rwidth / 2, @@ -446,7 +437,6 @@ void full_2d_pass(struct window *window) pos.y + zoom * window->rheight / 2, -1.f, 1.f); hudproj = HMM_Orthographic_LH_ZO(0, window->rwidth, 0, window->rheight, -1.f, 1.f); -#endif sprite_draw_all(); call_draw(); diff --git a/source/engine/render.h b/source/engine/render.h index 5b42815..418fcd3 100644 --- a/source/engine/render.h +++ b/source/engine/render.h @@ -6,7 +6,7 @@ #elif __EMSCRIPTEN__ #define SOKOL_GLES3 #elif __WIN32 - #define SOKOL_GLCORE33 + #define SOKOL_D3D11 #define SOKOL_WIN32_FORCE_MAIN #elif __APPLE__ #define SOKOL_METAL diff --git a/source/engine/yugine.c b/source/engine/yugine.c index 1075f38..414fa4b 100644 --- a/source/engine/yugine.c +++ b/source/engine/yugine.c @@ -58,7 +58,6 @@ static struct d_prof prof_input; static struct d_prof prof_physics; double physlag = 0; -int render_dirty = 0; double physMS = 1 / 60.f; @@ -74,6 +73,8 @@ static float timescale = 1.f; static int sim_play = SIM_PLAY; +static int editor_mode = 0; + #ifdef __TINYC__ int backtrace(void **buffer, int size) { extern uint64_t *__libc_stack_end; @@ -168,25 +169,21 @@ static void process_frame() prof(&prof_physics); } - if (sim_play == SIM_STEP) { + if (sim_play == SIM_STEP) sim_pause(); - render_dirty = 1; - } } - if (sim_play == SIM_PLAY || render_dirty) { - prof_start(&prof_draw); - window_render(&mainwin); - prof(&prof_draw); - render_dirty = 0; - } + prof_start(&prof_draw); + window_render(&mainwin); + prof(&prof_draw); + gameobjects_cleanup(); } void c_frame() { - if (sim_play != SIM_PLAY) return; + if (editor_mode) return; process_frame(); } @@ -196,8 +193,6 @@ void c_clean() { void c_event(const sapp_event *e) { - render_dirty = 1; - #ifndef NO_EDITOR snk_handle_event(e); #endif @@ -260,7 +255,7 @@ void c_event(const sapp_event *e) break; } - if (sim_play != SIM_PLAY) + if (editor_mode) process_frame(); } diff --git a/source/engine/yugine.h b/source/engine/yugine.h index 72b4564..13c77ff 100644 --- a/source/engine/yugine.h +++ b/source/engine/yugine.h @@ -21,6 +21,5 @@ extern double appTime; extern double renderMS; extern double physMS; extern double updateMS; -extern int render_dirty; - +extern int editor_mode; #endif