Merge levels and entities
This commit is contained in:
parent
cc879746e3
commit
b5b7f3f0e2
25
docs/game.md
25
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
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
World.run = function(file)
|
||||
{
|
||||
var newobject = {};
|
||||
newobject.kill = function() {
|
||||
Register.unregister_obj(newobject);
|
||||
}
|
||||
});
|
||||
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;
|
||||
var script = IO.slurp(file);
|
||||
compile_env(`var self = this;${script}`, newobject, file);
|
||||
}
|
||||
|
||||
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);
|
||||
},
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
|
|
454
source/scripts/entity.js
Normal file
454
source/scripts/entity.js
Normal file
|
@ -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));
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue