From b5b7f3f0e2c97584381951036eb2d4e21fdc5841 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Fri, 8 Sep 2023 17:35:06 +0000 Subject: [PATCH] Merge levels and entities --- docs/game.md | 25 +- source/scripts/components.js | 2 + source/scripts/editor.js | 8 +- source/scripts/engine.js | 460 +++-------------------------------- source/scripts/entity.js | 454 ++++++++++++++++++++++++++++++++++ source/scripts/level.js | 10 + 6 files changed, 530 insertions(+), 429 deletions(-) create mode 100644 source/scripts/entity.js diff --git a/docs/game.md b/docs/game.md index 18d827c..52294dc 100644 --- a/docs/game.md +++ b/docs/game.md @@ -44,28 +44,43 @@ There are three distinct hierarchies of object-existence. ### 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. -Components are rendered in an "ECS" style. To work, components must be installed on an entity. +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. + +AI would be components. You could have a "sensor" AI component that detects the world around it, and a locomotion AI component, etc, and reserve scripting for hooking them up, etc. Or do it all in scripting. + +Components cannot be scripted; they are essentially a hardwired thing that you set different flags and values on, and then can query it for information. ### Entity Entities are holders of components. Anything that needs a component will be an entity. Components rely on entites to render correctly. For example, the engine knows where to draw a sprite wherever its associated entity is. -### Levels -Levels are nothing more than groups of entities However, levels can also have a script on them. This level of scripting is the least efficient. +Entities can be composed of other entities. When that is the case, an entity "under" a different entity will move when the above entity moves. -Levels do not have an associated gameobject inside the engine, and so are what you want if you just want to run some code. +The outermost entity that all other entities must exist in is the Primum. It always exists and cannot be removed. + +## Traits +It is better to think of Primum as trait-based, intead of object-based. Any thing in your game that has particular properties can be used as a particular sort of object. ## Prototyping model All objects follow the prototyping model of inheritence. This makes it trivial to change huge swathes of the game, or make tiny adjustments to single objects, in a natural and intuitive way. -Components cannot be prototyped. They are tied directly to the entity they are bound to. +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. +entity.clone(parent) -> create a subtyped version of the entity +entity.dup(parent) -> create a copy of the entity. +entity.promote() -> promote the entity to a new Ur-type, as it currently exists. +entity.revert() -> remove all changes of this entity so it again matches its Ur-type. +entity.push() -> push changes to this entity to its Ur-type to it matches. + ### Ur-types An Ur-type is a thing which cannot be seen but which can stamp out copies of itself. Objects can be promoted to an ur-type, so if it is deleted, another one can later be made. Levels can be subtyped, sidetyped, and urtyped, just like entities. +## Resources +Assets can generally be used just with their filename. It will be loaded with default values. However, how the engine interprets it can be altered with a sidecar file, named "filename.asset", so "ball.png" will be modified via "ball.png.asset". These are typical JSON files. For images, specify gamma, if it's a sprite or texture, etc, for sound, specify its gain, etc. + ## Level model The game world is made up of objects. Levels are collections of objects. The topmost level is called "World". Objects are spawned into diff --git a/source/scripts/components.js b/source/scripts/components.js index f48c62a..848ea07 100644 --- a/source/scripts/components.js +++ b/source/scripts/components.js @@ -78,6 +78,8 @@ var sprite = clone(component, { input_kp3_pressed() { this.pos = [0, -1]; }, input_kp2_pressed() { this.pos = [-0.5,-1]; }, input_kp1_pressed() { this.pos = [-1,-1]; }, + + POS_MID: [-0.5, -0.5], }); /* Container to play sprites and anim2ds */ diff --git a/source/scripts/editor.js b/source/scripts/editor.js index dee695f..002068e 100644 --- a/source/scripts/editor.js +++ b/source/scripts/editor.js @@ -9,8 +9,9 @@ required_files.forEach(x => { if (!IO.exists(x)) IO.slurpwrite("", x); }); -var editor_level = Level.create(); -var editor_camera = editor_level.spawn(camera2d); +/* This is the editor level & camera - NOT the currently edited level, but a level to hold editor things */ +var editor_level = gameobject.make(Primum); +var editor_camera = camera2d.make(editor_level); editor_camera.save = false; Game.view_camera(editor_camera); @@ -2541,7 +2542,8 @@ Debug.register_call(editor.ed_debug, editor); if (IO.exists("editor.config")) load_configs("editor.config"); -editor.edit_level = Level.create(); + +editor.edit_level = editor_level; Game.stop(); Game.render(); diff --git a/source/scripts/engine.js b/source/scripts/engine.js index 3917f6d..60e2b66 100644 --- a/source/scripts/engine.js +++ b/source/scripts/engine.js @@ -602,9 +602,31 @@ var Game = { Register.update.register(Game.exec, Game); -load("scripts/level.js"); +//load("scripts/level.js"); + + + +load("scripts/entity.js"); + +var World = Object.create(gameobject); +var Primum = World; +var objects = []; + +World.remove_child = function(child) { + objects.remove(child); +} + +World.add_child = function(child) { + child.unparent(); + objects.push(child); + child.level = World; +} + +/* Reparent this object to a new one */ +World.reparent = function(parent) { } + +World.unparent = function() { } -var World = Level.create(); World.name = "World"; World.fullpath = function() { return World.name; }; World.load = function(lvl) { @@ -615,426 +637,17 @@ World.load = function(lvl) { return World.loaded; }; -var gameobjects = {}; -var Prefabs = gameobjects; - -function grab_from_points(pos, points, slop) { - var shortest = slop; - var idx = -1; - points.forEach(function(x,i) { - if (Vector.length(pos.sub(x)) < shortest) { - shortest = Vector.length(pos.sub(x)); - idx = i; - } - }); - return idx; -}; - -var gameobject = { - scale: 1.0, - - save: true, - - selectable: true, - - layer: 0, /* Collision layer; should probably have been called "mask" */ - layer_nuke() { - Nuke.label("Collision layer"); - Nuke.newline(Collision.num); - for (var i = 0; i < Collision.num; i++) - this.layer = Nuke.radio(i, this.layer, i); - }, - - draw_layer: 1, - draw_layer_nuke() { - Nuke.label("Draw layer"); - Nuke.newline(5); - for (var i = 0; i < 5; i++) - this.draw_layer = Nuke.radio(i, this.draw_layer, i); - }, - - in_air() { - return q_body(7, this.body); - }, - - on_ground() { return !this.in_air(); }, - - name: "gameobject", - - toString() { return this.name; }, - - clone(name, ext) { - var obj = Object.create(this); - complete_assign(obj, ext); - gameobjects[name] = obj; - obj.defc('name', name); - obj.from = this.name; - obj.defn('instances', []); - obj.obscure('from'); - - return obj; - }, - - dup(diff) { - var dup = World.spawn(gameobjects[this.from]); - Object.assign(dup, diff); - return dup; - }, - - instandup() { - var dup = World.spawn(gameobjects[this.from]); - dup.pos = this.pos; - dup.velocity = this.velocity; - }, - - ed_locked: false, - - _visible: true, - get visible(){ return this._visible; }, - set visible(x) { - this._visible = x; - for (var key in this.components) { - if ('visible' in this.components[key]) { - this.components[key].visible = x; - } - } - }, - - mass: 1, - bodytype: { - dynamic: 0, - kinematic: 1, - static: 2 - }, - - get moi() { return q_body(6, this.body); }, - set moi(x) { set_body(13, this.body, x); }, - - phys: 2, - phys_nuke() { - Nuke.newline(1); - Nuke.label("phys"); - Nuke.newline(3); - this.phys = Nuke.radio("dynamic", this.phys, 0); - this.phys = Nuke.radio("kinematic", this.phys, 1); - this.phys = Nuke.radio("static", this.phys, 2); - }, - friction: 0, - elasticity: 0, - flipx: false, - flipy: false, - - body: -1, - get controlled() { - return Player.obj_controlled(this); - }, - - set_center(pos) { - var change = pos.sub(this.pos); - this.pos = pos; - - for (var key in this.components) { - this.components[key].finish_center(change); - } - }, - - varname: "", - - pos: [0,0], - - set relpos(x) { - if (!this.level) { - this.pos = x; - return; - } - - this.pos = Vector.rotate(x, Math.deg2rad(this.level.angle)).add(this.level.pos); - }, - - get relpos() { - if (!this.level) return this.pos; - - var offset = this.pos.sub(this.level.pos); - return Vector.rotate(offset, -Math.deg2rad(this.level.angle)); - }, - - angle: 0, - - get relangle() { - if (!this.level) return this.angle; - - return this.angle - this.level.angle; - }, - - get velocity() { return q_body(3, this.body); }, - set velocity(x) { set_body(9, this.body, x); }, - get angularvelocity() { return Math.rad2deg(q_body(4, this.body)); }, - set angularvelocity(x) { if (this.alive) set_body(8, this.body, Math.deg2rad(x)); }, - - get alive() { return this.body >= 0; }, - - disable() { - this.components.forEach(function(x) { x.disable(); }); - - }, - - enable() { - this.components.forEach(function(x) { x.enable(); }); - }, - - sync() { - if (this.body === -1) return; - cmd(55, this.body, this.flipx); - cmd(56, this.body, this.flipy); - set_body(2, this.body, this.pos); - set_body(0, this.body, Math.deg2rad(this.angle)); - cmd(36, this.body, this.scale); - set_body(10,this.body,this.elasticity); - set_body(11,this.body,this.friction); - set_body(1, this.body, this.phys); - cmd(75,this.body,this.layer); - cmd(54, this.body); - }, - - syncall() { - this.instances.forEach(function(x) { x.sync(); }); - }, - - pulse(vec) { /* apply impulse */ - set_body(4, this.body, vec); - }, - - push(vec) { /* apply force */ - set_body(12,this.body,vec); - }, - - gizmo: "", /* Path to an image to draw for this gameobject */ - - /* Bounding box of the object in world dimensions */ - get 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]); - }, - - set width(x) {}, - get width() { - var bb = this.boundingbox; - return bb.r - bb.l; - }, - set height(x) {}, - get height() { - var bb = this.boundingbox; - return bb.t-bb.b; - }, - - stop() {}, - - kill() { - if (this.body === -1) { - Log.warn(`Object is already dead!`); - return; - } - Register.endofloop(() => { - cmd(2, this.body); - delete Game.objects[this.body]; - - if (this.level) - this.level.unregister(this); - - Player.uncontrol(this); - this.instances.remove(this); - Register.unregister_obj(this); -// Signal.clear_obj(this); - - this.body = -1; - for (var key in this.components) { - Register.unregister_obj(this.components[key]); - this.components[key].kill(); - } - - this.stop(); - }); - }, - - get up() { - return [0,1].rotate(Math.deg2rad(this.angle)); - }, - - get down() { - return [0,-1].rotate(Math.deg2rad(this.angle)); - }, - - get right() { - return [1,0].rotate(Math.deg2rad(this.angle)); - }, - - get left() { - return [-1,0].rotate(Math.deg2rad(this.angle)); - }, - - /* Make a unique object the same as its prototype */ - revert() { - unmerge(this, this.prop_obj()); - this.sync(); - }, - - gui() { - var go_guis = walk_up_get_prop(this, 'go_gui'); - Nuke.newline(); - - go_guis.forEach(function(x) { x.call(this); }, this); - - for (var key in this) { - if (typeof this[key] === 'object' && 'gui' in this[key]) this[key].gui(); - } - }, - - world2this(pos) { return cmd(70, this.body, pos); }, - this2world(pos) { return cmd(71, this.body,pos); }, - - check_registers(obj) { - Register.unregister_obj(this); - - if (typeof obj.update === 'function') - Register.update.register(obj.update, obj); - - if (typeof obj.physupdate === 'function') - Register.physupdate.register(obj.physupdate, obj); - - if (typeof obj.collide === 'function') - obj.register_hit(obj.collide, obj); - - if (typeof obj.separate === 'function') - obj.register_separate(obj.separate, obj); - - if (typeof obj.draw === 'function') - Register.draw.register(obj.draw,obj); - - if (typeof obj.debug === 'function') - Register.debug.register(obj.debug, obj); - - obj.components.forEach(function(x) { - if (typeof x.collide === 'function') - register_collide(1, x.collide, x, obj.body, x.shape); - }); - }, - - make(props, level) { - level ??= World; - var obj = Object.create(this); - this.instances.push(obj); - obj.toString = function() { - var props = obj.prop_obj(); - for (var key in props) - if (typeof props[key] === 'object' && !props[key] === null && props[key].empty) - delete props[key]; - - var edited = !props.empty; - return (edited ? "#" : "") + obj.name + " object " + obj.body + ", layer " + obj.draw_layer + ", phys " + obj.layer; - }; - - obj.fullpath = function() { - return `${obj.level.fullpath()}.${obj.name}`; - }; - obj.deflock('toString'); - obj.defc('from', this.name); - obj.defn('body', make_gameobject(this.scale, - this.phys, - this.mass, - this.friction, - this.elasticity) ); - complete_assign(obj, props); - obj.sync(); - obj.defn('components', {}); - - cmd(113, obj.body, obj); - - complete_assign(obj, { - set scale(x) { - Log.warn(obj.body); cmd(36, obj.body, x); }, - get scale() { return cmd(103, obj.body); }, - get flipx() { return cmd(104,obj.body); }, - set flipx(x) { cmd(55, obj.body, x); }, - get flipy() { return cmd(105,obj.body); }, - set flipy(x) { cmd(56, obj.body, x); }, - - get angle() { return Math.rad2deg(q_body(2,obj.body))%360; }, - set angle(x) { set_body(0,obj.body, Math.deg2rad(x)); }, - - set pos(x) { set_body(2,obj.body,x); }, - get pos() { return q_body(1,obj.body); }, - - get elasticity() { return cmd(107,obj.body); }, - set elasticity(x) { cmd(106,obj.body,x); }, - - get friction() { return cmd(109,obj.body); }, - set friction(x) { cmd(108,obj.body,x); }, - - set mass(x) { set_body(7,obj.body,x); }, - get mass() { return q_body(5, obj.body); }, - - set phys(x) { set_body(1, obj.body, x); }, - get phys() { return q_body(0,obj.body); }, - }); - - for (var prop in obj) { - if (typeof obj[prop] === 'object' && 'make' in obj[prop]) { - if (prop === 'flipper') return; - obj[prop] = obj[prop].make(obj.body); - obj[prop].defn('gameobject', obj); - obj.components[prop] = obj[prop]; - } - }; - - obj.check_registers(obj); - - if ('begin' in obj) obj.begin(); - - return obj; - }, - - register_hit(fn, obj) { - if (!obj) - obj = this; - - Signal.obj_begin(fn, obj, this); - }, - - register_separate(fn, obj) { - if (!obj) - obj = this; - - Signal.obj_separate(fn,obj,this); - }, +World.run = function(file) +{ + var newobject = {}; + newobject.kill = function() { + Register.unregister_obj(newobject); + } + var script = IO.slurp(file); + compile_env(`var self = this;${script}`, newobject, file); } -var locks = ['height', 'width', 'visible', 'body', 'controlled', 'selectable', 'save', 'velocity', 'angularvelocity', 'alive', 'boundingbox', 'name', 'scale', 'angle', 'properties', 'moi', 'relpos', 'relangle', 'up', 'down', 'right', 'left', 'bodytype', 'gizmo', 'pos']; -locks.forEach(x => gameobject.obscure(x)); + /* Load configs */ function load_configs(file) { Log.info(`Loading config file ${file}.`); @@ -1116,7 +729,7 @@ Game.view_camera = function(cam) cmd(61, Game.camera.body); } -Game.view_camera(World.spawn(camera2d)); +Game.view_camera(camera2d.make(World)); win_make(Game.title, Game.resolution[0], Game.resolution[1]); @@ -1185,6 +798,11 @@ prototypes.load_config = function(name) if (!prototypes.ur[name]) prototypes.ur[name] = gameobject.clone(name); + if (prototypes.config[name]) { + Log.warn(`Assigning ${name} from config.`); + dainty_assign(prototypes.config[name], prototypes.ur[name]); + } + return prototypes.ur[name]; } diff --git a/source/scripts/entity.js b/source/scripts/entity.js new file mode 100644 index 0000000..71ec652 --- /dev/null +++ b/source/scripts/entity.js @@ -0,0 +1,454 @@ +var gameobjects = {}; +var Prefabs = gameobjects; + +function grab_from_points(pos, points, slop) { + var shortest = slop; + var idx = -1; + points.forEach(function(x,i) { + if (Vector.length(pos.sub(x)) < shortest) { + shortest = Vector.length(pos.sub(x)); + idx = i; + } + }); + return idx; +}; + +var gameobject = { + scale: 1.0, + + save: true, + + selectable: true, + + spawn(ur) { + Log.warn("DEPRECIATED"); + return ur.make(this); + }, + + layer: 0, /* Collision layer; should probably have been called "mask" */ + layer_nuke() { + Nuke.label("Collision layer"); + Nuke.newline(Collision.num); + for (var i = 0; i < Collision.num; i++) + this.layer = Nuke.radio(i, this.layer, i); + }, + + draw_layer: 1, + draw_layer_nuke() { + Nuke.label("Draw layer"); + Nuke.newline(5); + for (var i = 0; i < 5; i++) + this.draw_layer = Nuke.radio(i, this.draw_layer, i); + }, + + in_air() { + return q_body(7, this.body); + }, + + on_ground() { return !this.in_air(); }, + + name: "gameobject", + + toString() { return this.name; }, + + clone(name, ext) { + var obj = Object.create(this); + complete_assign(obj, ext); + gameobjects[name] = obj; + obj.defc('name', name); + obj.from = this.name; + obj.defn('instances', []); + obj.obscure('from'); + + return obj; + }, + + dup(diff) { + var dup = World.spawn(gameobjects[this.from]); + Object.assign(dup, diff); + return dup; + }, + + instandup() { + var dup = World.spawn(gameobjects[this.from]); + dup.pos = this.pos; + dup.velocity = this.velocity; + }, + + ed_locked: false, + + _visible: true, + get visible(){ return this._visible; }, + set visible(x) { + this._visible = x; + for (var key in this.components) { + if ('visible' in this.components[key]) { + this.components[key].visible = x; + } + } + }, + + mass: 1, + bodytype: { + dynamic: 0, + kinematic: 1, + static: 2 + }, + + get moi() { return q_body(6, this.body); }, + set moi(x) { set_body(13, this.body, x); }, + + phys: 2, + phys_nuke() { + Nuke.newline(1); + Nuke.label("phys"); + Nuke.newline(3); + this.phys = Nuke.radio("dynamic", this.phys, 0); + this.phys = Nuke.radio("kinematic", this.phys, 1); + this.phys = Nuke.radio("static", this.phys, 2); + }, + friction: 0, + elasticity: 0, + flipx: false, + flipy: false, + + body: -1, + get controlled() { + return Player.obj_controlled(this); + }, + + set_center(pos) { + var change = pos.sub(this.pos); + this.pos = pos; + + for (var key in this.components) { + this.components[key].finish_center(change); + } + }, + + varname: "", + + pos: [0,0], + + set relpos(x) { + if (!this.level) { + this.pos = x; + return; + } + + this.pos = Vector.rotate(x, Math.deg2rad(this.level.angle)).add(this.level.pos); + }, + + get relpos() { + if (!this.level) return this.pos; + + var offset = this.pos.sub(this.level.pos); + return Vector.rotate(offset, -Math.deg2rad(this.level.angle)); + }, + + angle: 0, + + get relangle() { + if (!this.level) return this.angle; + + return this.angle - this.level.angle; + }, + + get velocity() { return q_body(3, this.body); }, + set velocity(x) { set_body(9, this.body, x); }, + get angularvelocity() { return Math.rad2deg(q_body(4, this.body)); }, + set angularvelocity(x) { if (this.alive) set_body(8, this.body, Math.deg2rad(x)); }, + + get alive() { return this.body >= 0; }, + + disable() { + this.components.forEach(function(x) { x.disable(); }); + + }, + + enable() { + this.components.forEach(function(x) { x.enable(); }); + }, + + sync() { + if (this.body === -1) return; + cmd(55, this.body, this.flipx); + cmd(56, this.body, this.flipy); + set_body(2, this.body, this.pos); + set_body(0, this.body, Math.deg2rad(this.angle)); + cmd(36, this.body, this.scale); + set_body(10,this.body,this.elasticity); + set_body(11,this.body,this.friction); + set_body(1, this.body, this.phys); + cmd(75,this.body,this.layer); + cmd(54, this.body); + }, + + syncall() { + this.instances.forEach(function(x) { x.sync(); }); + }, + + pulse(vec) { /* apply impulse */ + set_body(4, this.body, vec); + }, + + push(vec) { /* apply force */ + set_body(12,this.body,vec); + }, + + gizmo: "", /* Path to an image to draw for this gameobject */ + + /* Bounding box of the object in world dimensions */ + get 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]); + }, + + set width(x) {}, + get width() { + var bb = this.boundingbox; + return bb.r - bb.l; + }, + set height(x) {}, + get height() { + var bb = this.boundingbox; + return bb.t-bb.b; + }, + + stop() {}, + + kill() { + if (this.body === -1) { + Log.warn(`Object is already dead!`); + return; + } + Register.endofloop(() => { + cmd(2, this.body); + delete Game.objects[this.body]; + + if (this.level) + this.level.unregister(this); + + Player.uncontrol(this); + this.instances.remove(this); + Register.unregister_obj(this); +// Signal.clear_obj(this); + + this.body = -1; + for (var key in this.components) { + Register.unregister_obj(this.components[key]); + this.components[key].kill(); + } + + this.stop(); + }); + }, + + get up() { + return [0,1].rotate(Math.deg2rad(this.angle)); + }, + + get down() { + return [0,-1].rotate(Math.deg2rad(this.angle)); + }, + + get right() { + return [1,0].rotate(Math.deg2rad(this.angle)); + }, + + get left() { + return [-1,0].rotate(Math.deg2rad(this.angle)); + }, + + /* Make a unique object the same as its prototype */ + revert() { + unmerge(this, this.prop_obj()); + this.sync(); + }, + + gui() { + var go_guis = walk_up_get_prop(this, 'go_gui'); + Nuke.newline(); + + go_guis.forEach(function(x) { x.call(this); }, this); + + for (var key in this) { + if (typeof this[key] === 'object' && 'gui' in this[key]) this[key].gui(); + } + }, + + world2this(pos) { return cmd(70, this.body, pos); }, + this2world(pos) { return cmd(71, this.body,pos); }, + + check_registers(obj) { + Register.unregister_obj(this); + + if (typeof obj.update === 'function') + Register.update.register(obj.update, obj); + + if (typeof obj.physupdate === 'function') + Register.physupdate.register(obj.physupdate, obj); + + if (typeof obj.collide === 'function') + obj.register_hit(obj.collide, obj); + + if (typeof obj.separate === 'function') + obj.register_separate(obj.separate, obj); + + if (typeof obj.draw === 'function') + Register.draw.register(obj.draw,obj); + + if (typeof obj.debug === 'function') + Register.debug.register(obj.debug, obj); + + obj.components.forEach(function(x) { + if (typeof x.collide === 'function') + register_collide(1, x.collide, x, obj.body, x.shape); + }); + }, + instances: [], + + make(level) { + level ??= World; + var obj = Object.create(this); + this.instances.push(obj); + obj.toString = function() { + var props = obj.prop_obj(); + for (var key in props) + if (typeof props[key] === 'object' && !props[key] === null && props[key].empty) + delete props[key]; + + var edited = !props.empty; + return (edited ? "#" : "") + obj.name + " object " + obj.body + ", layer " + obj.draw_layer + ", phys " + obj.layer; + }; + + obj.fullpath = function() { + return `${obj.level.fullpath()}.${obj.name}`; + }; + obj.deflock('toString'); + obj.defc('from', this.name); + obj.defn('body', make_gameobject(this.scale, + this.phys, + this.mass, + this.friction, + this.elasticity) ); + obj.sync(); + obj.defn('components', {}); + + var objects = []; + obj.objects = objects; + + obj.remove_child = function(child) { + objects.remove(child); + } + + obj.add_child = function(child) { + child.unparent(); + objects.push(child); + child.level = obj; + } + + /* Reparent this object to a new one */ + obj.reparent = function(parent) { + if (parent === obj.level) + return; + + parent.add_child(obj); + } + + obj.unparent = function() { + if (!obj.level) return; + obj.level.remove_child(obj); + } + + cmd(113, obj.body, obj); + + /* Now that it's concrete in the engine, these functions update to return engine data */ + complete_assign(obj, { + set scale(x) { cmd(36, obj.body, x); }, + get scale() { return cmd(103, obj.body); }, + get flipx() { return cmd(104,obj.body); }, + set flipx(x) { cmd(55, obj.body, x); }, + get flipy() { return cmd(105,obj.body); }, + set flipy(x) { cmd(56, obj.body, x); }, + + get angle() { return Math.rad2deg(q_body(2,obj.body))%360; }, + set angle(x) { set_body(0,obj.body, Math.deg2rad(x)); }, + + set pos(x) { + var diff = x.sub(this.pos); + objects.forEach(function(x) { x.pos = x.pos.add(diff); }); + set_body(2,obj.body,x); }, + get pos() { return q_body(1,obj.body); }, + + get elasticity() { return cmd(107,obj.body); }, + set elasticity(x) { cmd(106,obj.body,x); }, + + get friction() { return cmd(109,obj.body); }, + set friction(x) { cmd(108,obj.body,x); }, + + set mass(x) { set_body(7,obj.body,x); }, + get mass() { return q_body(5, obj.body); }, + + set phys(x) { set_body(1, obj.body, x); }, + get phys() { return q_body(0,obj.body); }, + }); + + for (var prop in obj) { + if (typeof obj[prop] === 'object' && 'make' in obj[prop]) { + if (prop === 'flipper') return; + obj[prop] = obj[prop].make(obj.body); + obj[prop].defn('gameobject', obj); + obj.components[prop] = obj[prop]; + } + }; + + obj.check_registers(obj); + + if ('begin' in obj) obj.begin(); + + return obj; + }, + + register_hit(fn, obj) { + if (!obj) + obj = this; + + Signal.obj_begin(fn, obj, this); + }, + + register_separate(fn, obj) { + if (!obj) + obj = this; + + Signal.obj_separate(fn,obj,this); + }, +} + +var locks = ['height', 'width', 'visible', 'body', 'controlled', 'selectable', 'save', 'velocity', 'angularvelocity', 'alive', 'boundingbox', 'name', 'scale', 'angle', 'properties', 'moi', 'relpos', 'relangle', 'up', 'down', 'right', 'left', 'bodytype', 'gizmo', 'pos']; +locks.forEach(x => gameobject.obscure(x)); diff --git a/source/scripts/level.js b/source/scripts/level.js index a24a87c..2d1ece2 100644 --- a/source/scripts/level.js +++ b/source/scripts/level.js @@ -247,6 +247,12 @@ var Level = { this.dirty = this.save() !== this.filejson; }, + add_child(obj) { + obj.unparent(); + this.objects.push(obj); + obj.level = this; + }, + start() { this.objects.forEach(function(x) { if ('start' in x) x.start(); }); }, @@ -414,6 +420,10 @@ var Level = { delete this[obj.varname]; }, + remove_child(child) { + this.objects.remove(child); + }, + get pos() { return this._pos; }, set pos(x) { var diff = x.sub(this._pos);