This commit is contained in:
John Alanbrook 2023-12-24 15:14:46 +00:00
parent ccfd233207
commit 2f086947b9
14 changed files with 260 additions and 262 deletions

View file

@ -53,7 +53,7 @@ endif
CPPFLAGS += -ffast-math
ifeq ($(DBG),1)
CPPFLAGS += -g -fsanitize=address
CPPFLAGS += -g #-fsanitize=address
INFO += _dbg
else
CPPFLAGS += -DNDEBUG
@ -78,7 +78,7 @@ else
endif
endif
CPPFLAGS += -DHAVE_CEIL -DCP_USE_CGTYPES=0 -DCP_USE_DOUBLES=0 -DTINYSPLINE_FLOAT_PRECISION -DHAVE_FLOOR -DHAVE_FMOD -DHAVE_LRINT -DHAVE_LRINTF $(includeflag) -MD $(WARNING_FLAGS) -I. -DVER=\"$(VER)\" -DINFO=\"$(INFO)\" #-DENABLE_SINC_MEDIUM_CONVERTER -DENABLE_SINC_FAST_CONVERTER -DCP_COLLISION_TYPE_TYPE=uintptr_t -DCP_BITMASK_TYPE=uintptr_t
CPPFLAGS += -DHAVE_CEIL -DCP_USE_CGTYPES=0 -DCP_USE_DOUBLES=0 -DHAVE_FLOOR -DHAVE_FMOD -DHAVE_LRINT -DHAVE_LRINTF $(includeflag) -MD $(WARNING_FLAGS) -I. -DVER=\"$(VER)\" -DINFO=\"$(INFO)\" #-DENABLE_SINC_MEDIUM_CONVERTER -DENABLE_SINC_FAST_CONVERTER -DCP_COLLISION_TYPE_TYPE=uintptr_t -DCP_BITMASK_TYPE=uintptr_t
# ENABLE_SINC_[BEST|FAST|MEDIUM]_CONVERTER
# default, fast and medium available in game at runtime; best available in editor

View file

@ -15,3 +15,22 @@ Components, if they have a defined transform, are relative to the entity they re
Entities are defined via a typical scene graph system. This allows for easy reuse of components when designing levels.
When the game is run, the movement relationships are broken, and the physics system takes over. In the physics engine, each entity is a direct child of the world. Objects can still be constrained to other objects via the physics system.
The transform properties have local, global, and screen space accessors. For example, for position ..
- pos: the relative position of the object to its master
- worldpos: the position of the object in the world
- screenpos: the position of the object in screen space
The core engine only remembers each object's world position. When its position is requested, it figures its position based on its parent.
## Inheritence? Composition?
The idea behind how the engine is oragnized is to encourage composition. Compisition creates cleaner objects than does inheritence, and is easier to understand. And yet, inheritence is useful for specific circumstances.
- Creating a specific unique object in a level
There are only two cases where inheritence can be utilized in this engine:
1. Whole entities can be subtyped. Only values can be substituted. An ur-type must define all functions and behaviors of an object. but then that can be subtyped to change only a handful of properties.
2. Entities can be subtyped when thrall to another entity. This can only be done once. Thrall objects, like components, can be defined only once on an ur-type. Sub types can only change parameters, and not of the sub objects.

23
docs/scene.md Normal file
View file

@ -0,0 +1,23 @@
# The Scene
There are a multitude of representations of your entities in the engine. The two most prominent are the entity system, where entities can be master or thrall to a master. And then there is the physics view, which is mostly flat, where objects are all part of a "world", but can be connected to one another via various physics constraints:
- Pin joints, rigidly connected
- Slide joints, with a mix or max distance
- Pivot points, stuck to position but allow rotation
- Groove joint
- Damped spring
- Damped rotary spring
- Rotary limit
- Ratchet joint
- Gear joint
- Simple motor
The most common sort of connection will be master and thrall. When editing, this relationship allows for simple
## Entity vs Global
The physics engine stores all objects on the global level. The center of the world is [0,0]. Physics does not handle scaling at all.
The master/thrall relationship is closer to a scene graph. When the master moves, its thralls move, and their thralls move, etc. When a master scales, it is the same.
When the game starts simulating, objects ignore their parent relationship after they are created. They become only part of the world and simulate as such.

View file

@ -27,7 +27,8 @@ Computation takes place in turns. Each entity has functions called, if they exis
|function|description|
|---|---|
|start|called when the object is loaded|
|load|called after the object is loaded, including in editor|
|start|called when the object is loaded during gameplay|
|update(dt)|called once per game frame tick|
|physupdate(dt)|called once per physics tick|
|stop|called when the object is killed|

View file

@ -1041,6 +1041,13 @@ Object.defineProperty(Array.prototype, 'mapc', {
}
});
Object.defineProperty(Array.prototype, 'mapvec', {
value: function(fn, b) {
return this.map((x,i) => fn(x,b[i]));
}
});
Object.defineProperty(Array.prototype, 'remove', {
value: function(b) {
var idx = this.indexOf(b);
@ -1201,11 +1208,6 @@ Math.nearest = function(n, incr)
return Math.round(n/incr)*incr;
}
Number.prec = function(num)
{
return parseFloat(num.toFixed(3));
}
Number.hex = function(n)
{
var s = Math.floor(n).toString(16);
@ -1257,9 +1259,13 @@ Math.angledist = function (a1, a2) {
return wrap;
};
Math.angledist.doc = "Find the shortest angle between two angles.";
Math.patan2 = function(p) { return Math.atan2(p.y,p.x); };
Math.TAU = Math.PI*2;
Math.deg2rad = function(deg) { return deg * 0.0174533; };
Math.rad2deg = function(rad) { return rad / 0.0174533; };
Math.deg2rad = function(x) { return x; };
Math.rad2deg = function(x) { return x; };
Math.turn2rad = function(x) { return x*Math.TAU; };
Math.rad2turn = function(x) { return x/Math.TAU; };
Math.randomint = function(max) { return Math.clamp(Math.floor(Math.random() * max), 0, max-1); };
/* BOUNDINGBOXES */
@ -1397,12 +1403,13 @@ var Vector = {
},
angle(v) {
return Math.atan2(v.y, v.x);
return Math.rad2turn(Math.atan2(v.y, v.x));
},
rotate(v,angle) {
var r = Vector.length(v);
var p = Vector.angle(v) + angle;
p = Math.turn2rad(angle);
return [r*Math.cos(p), r*Math.sin(p)];
},

View file

@ -97,7 +97,7 @@ component.sprite.impl = {
set layer(x) { cmd(60, this.id, x); },
get layer() { return undefined; },
emissive(x) { cmd(170, this.id, x); },
pick() { return this; },
pickm() { return this; },
move(d) { this.pos = this.pos.add(d); },
boundingbox() {
@ -183,10 +183,6 @@ var SpriteAnim = {
}
anim.dim = cmd(64,path);
anim.dim.x /= frames;
anim.toJSON = function()
{
return anim.path;
}
return anim;
},
@ -332,19 +328,6 @@ component.char2dimpl = {
this.play_anim(anim);
}
},
post() {
/*
this.timer = timer.make(this.advance.bind(this), 1);
this.timer.loop = true;
Object.hide(this,'timer');
for (var k in this.anims) {
var path = this.anims[k];
this.anims[k] = run_env(path + ".asset", path);
this.add_anim(this.anims[k], k);
}
Object.hide(this, 'acur');*/
},
};
Object.assign(component.char2d, component.char2dimpl);

View file

@ -1,20 +1,4 @@
function deep_copy(from) {
if (typeof from !== 'object')
return from;
if (Array.isArray(from)) {
var c = [];
from.forEach(function(x,i) { c[i] = deep_copy(x); });
return c;
}
var obj = {};
for (var key in from)
obj[key] = deep_copy(from[key]);
return obj;
};
function deep_copy(from) { return json.decode(json.encode(from)); }
var walk_up_get_prop = function(obj, prop, endobj) {
var props = [];
@ -58,8 +42,7 @@ function deep_merge(target, source)
function equal(x,y) {
if (typeof x === 'object')
for (var key in x)
return equal(x[key],y[key]);
return json.encode(x) === json.encode(y);
return x === y;
};
@ -83,11 +66,6 @@ function diffassign(target, from) {
}
};
function diffkey(from,to,key)
{
}
function objdiff(from,to)
{
var ret = {};
@ -118,9 +96,8 @@ function objdiff(from,to)
}
if (typeof v === 'number') {
var a = Number.prec(v);
if (!to || a !== to[key])
ret[key] = a;
if (!to || v !== to[key])
ret[key] = v;
return;
}
@ -139,7 +116,6 @@ function valdiff(from,to)
if (typeof from === 'undefined') return undefined;
if (typeof from === 'number') {
if (Number.prec(from) !== Number.prec(to))
return to;
return undefined;
@ -153,7 +129,16 @@ function valdiff(from,to)
return undefined;
}
/* Returns the json encoded object, assuming it has an implementation it must check through */
function impl_json(obj)
{
}
function ndiff(from,to)
{
}
function ediff(from,to)
{
@ -189,9 +174,8 @@ function ediff(from,to)
}
if (typeof v === 'number') {
var a = Number.prec(v);
if (!to || a !== to[key])
ret[key] = a;
if (!to || v !== to[key])
ret[key] = v;
return;
}

View file

@ -14,7 +14,6 @@ var editor = {
machine: undefined,
device_test: undefined,
selectlist: [],
grablist: [],
scalelist: [],
rotlist: [],
camera: undefined,
@ -43,9 +42,8 @@ var editor = {
if (obj.level !== this.edit_level) {
var testlevel = obj.level;
while (testlevel && testlevel.level !== this.edit_level) {
while (testlevel && testlevel.level !== this.edit_level && testlevel !== testlevel.level)
testlevel = testlevel.level;
}
return testlevel;
}
@ -458,7 +456,6 @@ var editor = {
var p = x[1].namestr();
GUI.text(p, x[1].screenpos().add([0,16]),1,editor.color_depths[depth]);
Shape.circle(x[1].screenpos(),10,Color.blue.alpha(0.3));
// Shape.arrow(x[1].screenpos(), x[1].screenpos().add(x[1].up().scale(15)), Color.red, 10);
});
var mg = physics.pos_query(Mouse.worldpos,10);
@ -514,25 +511,7 @@ var editor = {
this.curpanels.forEach(function(x) {
if (x.on) x.gui();
});
/*
var o = editor.get_that();
var pos = [6,600];
var offset = [0,0];
var os = Object.entries(o.objects);
GUI.text(o.toString(), pos.add(offset));
offset = offset.add([5,-16]);
os.forEach(function(x) {
GUI.text(x.toString(), pos.add(offset));
offset = offset.add([0,-16]);
});
GUI.text(JSON.stringify(o._ed.urdiff,null,1), [500,500]);
*/
});
},
ed_debug() {
@ -540,17 +519,6 @@ var editor = {
this.selectlist.forEach(function(x) { Debug.draw_obj_phys(x); });
},
viewasset(path) {
Log.info(path);
var fn = function(x) { return path.endsWith(x); };
if (Resources.images.any(fn)) {
var newtex = Object.copy(texgui, { path: path });
this.addpanel(newtex);
}
else if (sounds.any(fn))
Log.info("selected a sound");
},
killring: [],
killcom: [],
@ -614,6 +582,10 @@ editor.inputs.drop = function(str) {
console.warn("NOT AN IMAGE");
return;
}
if (this.selected.length === 0) {
}
if (this.sel_comp?.comp === 'sprite') {
this.sel_comp.path = str;
@ -727,8 +699,8 @@ editor.inputs.m = function() {
return;
}
editor.selectlist.forEach(x => x.mirror([1,0]));
editor.selectlist.forEach(obj => obj.scale = [-obj.scale[0], obj.scale[1]]);
};
editor.inputs.m.doc = "Mirror selected objects on the X axis.";
@ -897,6 +869,8 @@ editor.inputs['M-t'] = function() { editor.edit_level.objects.forEach(function(x
editor.inputs['M-t'].doc = "Unlock all objects in current level.";
editor.inputs['C-n'] = function() {
gameobject.make(editor.edit_level);
console.warn("MADE A NEW OBJECT");
/* if (editor.edit_level._ed.dirty) {
Log.info("Level has changed; save before starting a new one.");
editor.openpanel(gen_notify("Level is changed. Are you sure you want to close it?", _ => editor.clear_level()));
@ -967,14 +941,18 @@ editor.inputs['C-f1'].doc = "Enter basic edit mode.";
editor.inputs['C-f2'] = function() { editor.edit_mode = "brush"; };
editor.inputs['C-f2'].doc = "Enter brush mode.";
editor.inputs.f2 = function() {
objectexplorer.on_close = save_configs;
objectexplorer.obj = configs;
if (this.selectlist.length !== 1) return;
objectexplorer.obj = this.selectlist[0];
this.openpanel(objectexplorer);
};
editor.inputs.f2.doc = "Open configurations object.";
editor.inputs.f3 = function() {
if (this.selectlist.length !== 1) return;
this.openpanel(componentexplorer);
};
editor.inputs.lm = function() { editor.sel_start = Mouse.worldpos; };
editor.inputs.lm.doc = "Selection box.";
@ -1144,9 +1122,9 @@ editor.inputs.mouse.move = function(pos, dpos)
editor.rotlist?.forEach(function(x) {
var anglediff = Math.atan2(relpos.y, relpos.x) - x.rotoffset;
x.obj.angle = x.angle + Math.rad2deg(anglediff);
x.obj.angle = x.angle + Math.rad2turn(anglediff);
if (Keys.shift())
x.obj.angle = Math.nearest(x.obj.angle, 45);
x.obj.angle = Math.nearest(x.obj.angle, (1/24));
if (x.pos)
x.obj.pos = x.pos.sub(x.offset).add(x.offset.rotate(anglediff));
});
@ -1289,7 +1267,7 @@ editor.inputs['C-c'] = function() {
this.killcom = physics.com(this.selectlist.map(x=>x.pos));
this.selectlist.forEach(function(x) {
this.killring.push(x.make_ur());
this.killring.push(x.instance_obj());
},this);
};
editor.inputs['C-c'].doc = "Copy selected objects to killring.";
@ -1686,7 +1664,6 @@ var objectexplorer = Object.copy(inputpanel, {
previous: [],
start() {
this.previous = [];
Input.setnuke();
},
goto_obj(obj) {
@ -1701,7 +1678,8 @@ var objectexplorer = Object.copy(inputpanel, {
guibody() {
var items = [];
items.push(Mum.text({str:"Examining " + this.obj.toString()}));
items.push(Mum.text({str:"Examining " + this.obj.toString() + " entity"}));
items.push(Mum.text({str: JSON.stringify(this.obj,undefined,1)}));
return items;
var n = 0;
@ -1936,6 +1914,18 @@ var assetexplorer = Object.copy(openlevelpanel, {
},
});
var componentexplorer = Object.copy(inputpanel, {
title: "component menu",
assets: ['sprite', 'model', 'edge2d', 'polygon2d', 'circle2d'],
click(name) {
if (editor.selectlist.length !== 1) return;
editor.selectlist[0].add_component(component[name]);
},
guibody() {
return componentexplorer.assets.map(x => Mum.text({str:x, action:this.click, color: Color.blue, hovered:{Color:Color.red}, selectable:true}));
},
});
function tab_complete(val, list) {
if (!val) return val;
list.dofilter(function(x) { return x.startsWith(val); });

View file

@ -524,6 +524,7 @@ function world_start() {
Primum.toString = function() { return "Primum"; };
Primum.ur = undefined;
Primum.kill = function() { this.clear(); };
gameobject.level = Primum;
}
load("scripts/physics.js");

View file

@ -1,3 +1,16 @@
function obj_unique_name(name, obj)
{
name = name.replaceAll('.', '_');
if (!(name in obj)) return name;
var t = 1;
var n = name + t;
while (n in obj) {
t++;
n = name+t;
}
return n;
}
var actor = {};
actor.spawn = function(script, config){
if (typeof script !== 'string') return;
@ -24,10 +37,7 @@ actor.kill = function(){
this.padawans.forEach(p => p.kill());
this.__dead__ = true;
};
actor.toJSON = function() {
if (this.__dead__) return undefined;
return this;
}
actor.delay = function(fn, seconds) {
var t = Object.create(timer);
t.remain = seconds;
@ -55,6 +65,93 @@ actor.remaster = function(to){
to.padawans.push(this);
};
var gameobject_impl = {
get pos() {
Debug.assert(this.level, `Entity ${this.toString()} has no level.`);
return this.level.world2this(this.worldpos());
},
set pos(x) {
Debug.assert(this.level, `Entity ${this.toString()} has no level.`);
this.set_worldpos(this.level.this2world(x));
},
get angle() {
Debug.assert(this.level, `No level set on ${this.toString()}`);
return this.worldangle() - this.level.worldangle();
},
set angle(x) {
var diff = x - this.angle;
this.objects.forEach(function(x) {
x.rotate(diff);
x.pos = Vector.rotate(x.pos, diff);
});
this.sworldangle(x-this.level.worldangle());
},
get scale() {
Debug.assert(this.level, `No level set on ${this.toString()}`);
return this.gscale().map((x,i) => x/this.level.gscale()[i]);
},
set scale(x) {
if (typeof x === 'number')
x = [x,x];
var pct = this.scale.map((s,i) => x[i]/s);
this.spread(pct);
/* TRANSLATE ALL SUB OBJECTS */
this.objects.forEach(obj => {
obj.spread(pct);
obj.pos = obj.pos.map((x,i)=>x*pct[i]);
});
},
get draw_layer() { return cmd(171, this.body); },
set draw_layer(x) { cmd(172, this.body, x); },
set layer(x) { cmd(75,this.body,x); },
get layer() { cmd(77,this.body); },
set mass(x) { set_body(7,this.body,x); },
get mass() {
if (!(this.phys === Physics.dynamic))
return this.__proto__.mass;
return q_body(5, this.body);
},
get elasticity() { return cmd(107,this.body); },
set elasticity(x) { cmd(106,this.body,x); },
get friction() { return cmd(109,this.body); },
set friction(x) { cmd(108,this.body,x); },
set gravity(x) { cmd(167,this.body, x); },
get gravity() { return cmd(159,this.body); },
set timescale(x) { cmd(168,this.body,x); },
get timescale() { return cmd(169,this.body); },
set phys(x) { set_body(1, this.body, x); },
get phys() { return q_body(0,this.body); },
get velocity() { return q_body(3, this.body); },
set velocity(x) { set_body(9, this.body, x); },
get damping() { return cmd(157,this.body); },
set damping(x) { cmd(156, this.body, x); },
get angularvelocity() { return Math.rad2turn(q_body(4,this.body)); },
set angularvelocity(x) { set_body(8, this.body, Math.turn2rad(x)); },
get max_velocity() { return cmd(152, this.body); },
set max_velocity(x) { cmd(151, this.body, x); },
get max_angularvelocity() { return cmd(155,this.body); },
set max_angularvelocity(x) { cmd(154,this.body,x); },
get_moi() { return q_body(6, this.body); },
set_moi(x) {
if(x <= 0) {
Log.error("Cannot set moment of inertia to 0 or less.");
return;
}
set_body(13, this.body, x);
},
};
var gameobject = {
get_comp_by_name(name) {
var comps = [];
@ -128,10 +225,6 @@ var gameobject = {
return killfn;
},
set max_velocity(x) { cmd(151, this.body, x); },
get max_velocity() { return cmd(152, this.body); },
set max_angularvelocity(x) { cmd(154, this.body, Math.deg2rad(x)); },
get max_angularvelocity() { return Math.rad2deg(cmd(155, this.body)); },
set torque(x) { if (!(x >= 0 && x <= Infinity)) return; cmd(153, this.body, x); },
gscale() { return cmd(103,this.body); },
sgscale(x) {
@ -139,65 +232,14 @@ var gameobject = {
x = [x,x];
cmd(36,this.body,x)
},
get scale() {
Debug.assert(this.level, `No level set on ${this.toString()}`);
return this.gscale().map((x,i) => x/this.level.gscale()[i]);
},
set scale(x) {
if (typeof x === 'number')
x = [x,x];
if (this.level) {
var g = this.level.gscale();
x = x.map((y,i) => y * g[i]);
}
var pct = x.map(function(y,i) { return y/this.gscale()[i]; }, this);
this.sgscale(x);
/* TRANSLATE ALL SUB OBJECTS */
phys_material() {
var mat = {};
mat.elasticity = this.elasticity;
mat.friction = this.friction;
return mat;
},
set pos(x) {
Debug.assert(this.level, `Entity ${this.toString()} has no level.`);
this.set_worldpos(this.level.this2world(x));
},
get pos() {
Debug.assert(this.level, `Entity ${this.toString()} has no level.`);
return this.level.world2this(this.worldpos());
},
get draw_layer() { return cmd(171, this.body); },
set draw_layer(x) { cmd(172, this.body, x); },
get elasticity() { return cmd(107,this.body); },
set elasticity(x) { cmd(106,this.body,x); },
get friction() { return cmd(109,this.body); },
set friction(x) { cmd(108,this.body,x); },
set mass(x) { set_body(7,this.body,x); },
get mass() {
if (!(this.phys === Physics.dynamic))
return this.__proto__.mass;
return q_body(5, this.body);
},
set gravity(x) { cmd(158,this.body, x); },
get gravity() { return cmd(159,this.body); },
set_gravity(x) { cmd(167, this.body, x); },
set timescale(x) { cmd(168,this.body,x); },
get timescale() { return cmd(169,this.body); },
set phys(x) { set_body(1, this.body, x); },
get phys() { return q_body(0,this.body); },
get velocity() { return q_body(3, this.body); },
set velocity(x) { set_body(9, this.body, x); },
get damping() { return cmd(157,this.body); },
set_damping(x) { cmd(156, this.body, x); },
get angularvelocity() { return Math.rad2deg(q_body(4, this.body)); },
set angularvelocity(x) { set_body(8, this.body, Math.deg2rad(x)); },
worldpos() { return q_body(1,this.body); },
set_worldpos(x) {
var poses = this.objects.map(x => x.pos);
@ -206,34 +248,9 @@ var gameobject = {
},
screenpos() { return world2screen(this.worldpos()); },
worldangle() { return Math.rad2deg(q_body(2,this.body))%360; },
sworldangle(x) { set_body(0,this.body,Math.deg2rad(x)); },
get angle() {
Debug.assert(this.level, `No level set on ${this.toString()}`);
return this.worldangle() - this.level.worldangle();
},
set angle(x) {
var diff = x - this.angle;
var thatpos = this.pos;
this.objects.forEach(function(x) {
x.rotate(diff);
var opos = x.pos;
var r = Vector.length(opos);
var p = Math.rad2deg(Math.atan2(opos.y, opos.x));
p += diff;
p = Math.deg2rad(p);
x.pos = [r*Math.cos(p), r*Math.sin(p)];
});
worldangle() { return Math.rad2turn(q_body(2,this.body)); },
sworldangle(x) { set_body(0,this.body,Math.turn2rad(x)); },
if (this.level)
set_body(0,this.body, Math.deg2rad(x - this.level.worldangle()));
else
set_body(0,this.body,x);
},
rotate(x) { this.sworldangle(this.worldangle()+x); },
spawn_from_instance(inst) {
return this.spawn(inst.ur, inst);
},
@ -290,16 +307,7 @@ var gameobject = {
components: {},
objects: {},
level: undefined,
get_moi() { return q_body(6, this.body); },
set_moi(x) {
if(x <= 0) {
Log.error("Cannot set moment of inertia to 0 or less.");
return;
}
set_body(13, this.body, x);
},
pulse(vec) { set_body(4, this.body, vec);},
shove(vec) { set_body(12,this.body,vec);},
shove_at(vec, at) { set_body(14,this.body,vec,at); },
@ -310,19 +318,12 @@ var gameobject = {
dir_world2this(dir) { return cmd(160, this.body, dir); },
dir_this2world(dir) { return cmd(161, this.body, dir); },
set layer(x) { cmd(75,this.body,x); },
get layer() { cmd(77,this.body); },
alive() { return this.body >= 0; },
in_air() { return q_body(7, this.body);},
hide() { this.components.forEach(x=>x.hide()); this.objects.forEach(x=>x.hide());},
show() { this.components.forEach(function(x) { x.show(); }); this.objects.forEach(function(x) { x.show(); }); },
get_relangle() {
if (!this.level) return this.angle;
return this.angle - this.level.angle;
},
width() {
var bb = this.boundingbox();
return bb.r - bb.l;
@ -334,15 +335,11 @@ var gameobject = {
},
move(vec) { this.pos = this.pos.add(vec); },
rotate(amt) { this.angle += amt; },
rotate(x) { this.sworldangle(this.worldangle()+x); },
spread(vec) { this.sgscale(this.gscale().map((x,i)=>x*vec[i])); },
/* Make a unique object the same as its prototype */
revert() {
// var keys = Object.samenewkeys(this, this.__proto__);
// keys.unique.forEach(x => delete this[x]);
// keys.same.forEach(x => this[x] = this.__proto__[x]);
var jobj = this.json_obj();
var lobj = this.level.__proto__.objects[this.toString()];
delete jobj.objects;
@ -433,8 +430,10 @@ var gameobject = {
return bb ? bb : cwh2bb([0,0], [0,0]);
},
/* The unique components of this object. Its diff. */
json_obj() {
var d = ediff(this,this.__proto__);
d ??= {};
var objects = {};
@ -454,51 +453,34 @@ var gameobject = {
return d;
},
transform_obj() {
var t = this.json_obj();
Object.assign(t, this.transform());
return t;
},
/* The object needed to store an object as an instance of a level */
instance_obj() {
var t = this.transform_obj();
var t = this.transform();
var j = this.json_obj();
Object.assign(t,j);
t.ur = this.ur;
return t;
},
ur_obj() {
var ur = this.json_obj();
for (var k in ur)
if (ur[k].ur)
delete ur[k];
return ur;
},
make_ur() {
var thisur = this.json_obj();
thisur.pos = this.pos;
thisur.angle = this.angle;
return thisur;
},
transform() {
var t = {};
t.pos = this.pos.map(Number.prec);
t.angle = Number.prec(this.angle);
t.pos = this.pos;
t.angle = this.angle;
t.scale = this.scale;
return t;
},
/* Velocity and angular velocity of the object */
phys_obj() {
var phys = {};
phys.velocity = this.velocity.map(Number.prec);
phys.angularvelocity = Number.prec(this.angularvelocity);
phys.velocity = this.velocity;
phys.angularvelocity = this.angularvelocity;
return phys;
},
dup(diff) {
var n = this.level.spawn(this.__proto__);
Object.totalmerge(n, this.make_ur());
Object.totalmerge(n, this.instance_obj());
return n;
},
@ -531,14 +513,16 @@ var gameobject = {
this.stop();
},
up() { return [0,1].rotate(Math.deg2rad(this.angle));},
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));},
up() { return [0,1].rotate(this.angle);},
down() { return [0,-1].rotate(this.angle);},
right() { return [1,0].rotate(this.angle);},
left() { return [-1,0].rotate(this.angle); },
make(level, data) {
var obj = Object.create(this);
obj.make = undefined;
Object.mixin(obj,gameobject_impl);
if (this.instances)
this.instances.push(obj);
@ -561,7 +545,7 @@ var gameobject = {
obj.reparent(level);
cmd(113, obj.body, obj); // set the internal obj reference to this obj
for (var [prop,p] of Object.entries(this)) {
if (!p) continue;
if (typeof p.make === 'function') {
@ -582,7 +566,7 @@ var gameobject = {
if (data)
Object.dainty_assign(obj,data);
if (typeof obj.warmup === 'function') obj.warmup();
if (typeof obj.load === 'function') obj.load();
if (Game.playing() && typeof obj.start === 'function') obj.start();
return obj;
@ -617,9 +601,13 @@ var gameobject = {
return this.objects[newname];
},
add_component(comp) {
add_component(comp, data) {
data ??= undefined;
if (typeof comp.make !== 'function') return;
return {comp:comp.toString()};
var name = obj_unique_name(comp.toString(), this);
this[name] = comp.make(this.body);
Object.assign(this[name], data);
return this[name];
},
register_hit(fn, obj) {
@ -632,6 +620,7 @@ var gameobject = {
Signal.obj_separate(fn,obj,this);
},
}
Object.mixin(gameobject,gameobject_impl);
gameobject.body = make_gameobject();
cmd(113,gameobject.body, gameobject);
@ -678,6 +667,7 @@ gameobject.doc = {
level: "The entity this entity belongs to.",
delay: 'Run the given function after the given number of seconds has elapsed.',
cry: 'Make a sound. Can only make one at a time.',
add_component: 'Add a component to the object by name.',
};
/* Default objects */

View file

@ -4,6 +4,7 @@
#include <string.h>
#include "debugdraw.h"
#include "log.h"
#include "math.h"
#include "stb_ds.h"
@ -129,7 +130,9 @@ static void velocityFn(cpBody *body, cpVect gravity, cpFloat damping, cpFloat dt
}
cpFloat d = isnan(go->damping) ? damping : d;
cpVect g = go->gravity ? gravity : go->cgravity.cp;
cpVect g = gravity;
if (isfinite(go->gravity.x) && isfinite(go->gravity.y))
g = go->gravity.cp;
cpBodyUpdateVelocity(body,g,d,dt*go->timescale);
@ -154,9 +157,8 @@ gameobject *MakeGameobject() {
.next = -1,
.drawlayer = 0,
.shape_cbs = NULL,
.gravity = 1,
.cgravity = (HMM_Vec2){0,0},
.damping = NAN,
.gravity = (HMM_Vec2){INFINITY,INFINITY},
.damping = INFINITY,
.timescale = 1.0,
.ref = JS_UNDEFINED,
};

View file

@ -36,8 +36,7 @@ struct gameobject {
float timescale;
float maxvelocity;
float maxangularvelocity;
int gravity;
HMM_Vec2 cgravity;
HMM_Vec2 gravity; /* its own gravity */
float damping;
unsigned int layer;
cpShapeFilter filter;

View file

@ -1173,10 +1173,9 @@ JSValue duk_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *argv)
ret = num2js(js2gameobject(argv[1])->damping);
break;
case 158:
js2gameobject(argv[1])->gravity = js2bool(argv[2]);
break;
case 159:
ret = bool2js(js2gameobject(argv[1])->gravity);
ret = vec2js(js2gameobject(argv[1])->gravity);
break;
case 160:
ret = vec2js(mat_t_dir(t_world2go(js2gameobject(argv[1])), js2vec2(argv[2])));
@ -1205,7 +1204,7 @@ JSValue duk_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *argv)
ret = int2js(cp(str, str2));
break;
case 167:
js2gameobject(argv[1])->cgravity = js2vec2(argv[2]);
js2gameobject(argv[1])->gravity = js2vec2(argv[2]);
break;
case 168:
js2gameobject(argv[1])->timescale = js2number(argv[2]);

View file

@ -123,8 +123,8 @@ static void process_frame()
if (stm_sec(stm_diff(frame_t, updatelast)) > updateMS) {
double dt = stm_sec(stm_diff(frame_t, updatelast));
updatelast = frame_t;
// prof_start(&prof_update);
call_updates(dt * timescale);
// prof_lap(&prof_update);