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 CPPFLAGS += -ffast-math
ifeq ($(DBG),1) ifeq ($(DBG),1)
CPPFLAGS += -g -fsanitize=address CPPFLAGS += -g #-fsanitize=address
INFO += _dbg INFO += _dbg
else else
CPPFLAGS += -DNDEBUG CPPFLAGS += -DNDEBUG
@ -78,7 +78,7 @@ else
endif endif
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 # ENABLE_SINC_[BEST|FAST|MEDIUM]_CONVERTER
# default, fast and medium available in game at runtime; best available in editor # 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. 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. 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| |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| |update(dt)|called once per game frame tick|
|physupdate(dt)|called once per physics tick| |physupdate(dt)|called once per physics tick|
|stop|called when the object is killed| |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', { Object.defineProperty(Array.prototype, 'remove', {
value: function(b) { value: function(b) {
var idx = this.indexOf(b); var idx = this.indexOf(b);
@ -1201,11 +1208,6 @@ Math.nearest = function(n, incr)
return Math.round(n/incr)*incr; return Math.round(n/incr)*incr;
} }
Number.prec = function(num)
{
return parseFloat(num.toFixed(3));
}
Number.hex = function(n) Number.hex = function(n)
{ {
var s = Math.floor(n).toString(16); var s = Math.floor(n).toString(16);
@ -1257,9 +1259,13 @@ Math.angledist = function (a1, a2) {
return wrap; return wrap;
}; };
Math.angledist.doc = "Find the shortest angle between two angles."; 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.deg2rad = function(deg) { return deg * 0.0174533; };
Math.rad2deg = function(rad) { return rad / 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); }; Math.randomint = function(max) { return Math.clamp(Math.floor(Math.random() * max), 0, max-1); };
/* BOUNDINGBOXES */ /* BOUNDINGBOXES */
@ -1397,12 +1403,13 @@ var Vector = {
}, },
angle(v) { angle(v) {
return Math.atan2(v.y, v.x); return Math.rad2turn(Math.atan2(v.y, v.x));
}, },
rotate(v,angle) { rotate(v,angle) {
var r = Vector.length(v); var r = Vector.length(v);
var p = Vector.angle(v) + angle; var p = Vector.angle(v) + angle;
p = Math.turn2rad(angle);
return [r*Math.cos(p), r*Math.sin(p)]; 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); }, set layer(x) { cmd(60, this.id, x); },
get layer() { return undefined; }, get layer() { return undefined; },
emissive(x) { cmd(170, this.id, x); }, emissive(x) { cmd(170, this.id, x); },
pick() { return this; }, pickm() { return this; },
move(d) { this.pos = this.pos.add(d); }, move(d) { this.pos = this.pos.add(d); },
boundingbox() { boundingbox() {
@ -183,10 +183,6 @@ var SpriteAnim = {
} }
anim.dim = cmd(64,path); anim.dim = cmd(64,path);
anim.dim.x /= frames; anim.dim.x /= frames;
anim.toJSON = function()
{
return anim.path;
}
return anim; return anim;
}, },
@ -332,19 +328,6 @@ component.char2dimpl = {
this.play_anim(anim); 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); Object.assign(component.char2d, component.char2dimpl);

View file

@ -1,20 +1,4 @@
function deep_copy(from) { function deep_copy(from) { return json.decode(json.encode(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;
};
var walk_up_get_prop = function(obj, prop, endobj) { var walk_up_get_prop = function(obj, prop, endobj) {
var props = []; var props = [];
@ -58,8 +42,7 @@ function deep_merge(target, source)
function equal(x,y) { function equal(x,y) {
if (typeof x === 'object') if (typeof x === 'object')
for (var key in x) return json.encode(x) === json.encode(y);
return equal(x[key],y[key]);
return x === y; return x === y;
}; };
@ -83,11 +66,6 @@ function diffassign(target, from) {
} }
}; };
function diffkey(from,to,key)
{
}
function objdiff(from,to) function objdiff(from,to)
{ {
var ret = {}; var ret = {};
@ -118,9 +96,8 @@ function objdiff(from,to)
} }
if (typeof v === 'number') { if (typeof v === 'number') {
var a = Number.prec(v); if (!to || v !== to[key])
if (!to || a !== to[key]) ret[key] = v;
ret[key] = a;
return; return;
} }
@ -139,7 +116,6 @@ function valdiff(from,to)
if (typeof from === 'undefined') return undefined; if (typeof from === 'undefined') return undefined;
if (typeof from === 'number') { if (typeof from === 'number') {
if (Number.prec(from) !== Number.prec(to))
return to; return to;
return undefined; return undefined;
@ -153,7 +129,16 @@ function valdiff(from,to)
return undefined; 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) function ediff(from,to)
{ {
@ -189,9 +174,8 @@ function ediff(from,to)
} }
if (typeof v === 'number') { if (typeof v === 'number') {
var a = Number.prec(v); if (!to || v !== to[key])
if (!to || a !== to[key]) ret[key] = v;
ret[key] = a;
return; return;
} }

View file

@ -14,7 +14,6 @@ var editor = {
machine: undefined, machine: undefined,
device_test: undefined, device_test: undefined,
selectlist: [], selectlist: [],
grablist: [],
scalelist: [], scalelist: [],
rotlist: [], rotlist: [],
camera: undefined, camera: undefined,
@ -43,9 +42,8 @@ var editor = {
if (obj.level !== this.edit_level) { if (obj.level !== this.edit_level) {
var testlevel = obj.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; testlevel = testlevel.level;
}
return testlevel; return testlevel;
} }
@ -458,7 +456,6 @@ var editor = {
var p = x[1].namestr(); var p = x[1].namestr();
GUI.text(p, x[1].screenpos().add([0,16]),1,editor.color_depths[depth]); 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.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); var mg = physics.pos_query(Mouse.worldpos,10);
@ -515,24 +512,6 @@ var editor = {
this.curpanels.forEach(function(x) { this.curpanels.forEach(function(x) {
if (x.on) x.gui(); 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() { ed_debug() {
@ -540,17 +519,6 @@ var editor = {
this.selectlist.forEach(function(x) { Debug.draw_obj_phys(x); }); 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: [], killring: [],
killcom: [], killcom: [],
@ -615,6 +583,10 @@ editor.inputs.drop = function(str) {
return; return;
} }
if (this.selected.length === 0) {
}
if (this.sel_comp?.comp === 'sprite') { if (this.sel_comp?.comp === 'sprite') {
this.sel_comp.path = str; this.sel_comp.path = str;
return; return;
@ -728,7 +700,7 @@ editor.inputs.m = function() {
return; 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."; 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['M-t'].doc = "Unlock all objects in current level.";
editor.inputs['C-n'] = function() { editor.inputs['C-n'] = function() {
gameobject.make(editor.edit_level);
console.warn("MADE A NEW OBJECT");
/* if (editor.edit_level._ed.dirty) { /* if (editor.edit_level._ed.dirty) {
Log.info("Level has changed; save before starting a new one."); 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())); 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'] = function() { editor.edit_mode = "brush"; };
editor.inputs['C-f2'].doc = "Enter brush mode."; editor.inputs['C-f2'].doc = "Enter brush mode.";
editor.inputs.f2 = function() { editor.inputs.f2 = function() {
objectexplorer.on_close = save_configs; if (this.selectlist.length !== 1) return;
objectexplorer.obj = configs; objectexplorer.obj = this.selectlist[0];
this.openpanel(objectexplorer); this.openpanel(objectexplorer);
}; };
editor.inputs.f2.doc = "Open configurations object."; 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 = function() { editor.sel_start = Mouse.worldpos; };
editor.inputs.lm.doc = "Selection box."; editor.inputs.lm.doc = "Selection box.";
@ -1144,9 +1122,9 @@ editor.inputs.mouse.move = function(pos, dpos)
editor.rotlist?.forEach(function(x) { editor.rotlist?.forEach(function(x) {
var anglediff = Math.atan2(relpos.y, relpos.x) - x.rotoffset; 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()) 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) if (x.pos)
x.obj.pos = x.pos.sub(x.offset).add(x.offset.rotate(anglediff)); 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.killcom = physics.com(this.selectlist.map(x=>x.pos));
this.selectlist.forEach(function(x) { this.selectlist.forEach(function(x) {
this.killring.push(x.make_ur()); this.killring.push(x.instance_obj());
},this); },this);
}; };
editor.inputs['C-c'].doc = "Copy selected objects to killring."; editor.inputs['C-c'].doc = "Copy selected objects to killring.";
@ -1686,7 +1664,6 @@ var objectexplorer = Object.copy(inputpanel, {
previous: [], previous: [],
start() { start() {
this.previous = []; this.previous = [];
Input.setnuke();
}, },
goto_obj(obj) { goto_obj(obj) {
@ -1701,7 +1678,8 @@ var objectexplorer = Object.copy(inputpanel, {
guibody() { guibody() {
var items = []; 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; return items;
var n = 0; 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) { function tab_complete(val, list) {
if (!val) return val; if (!val) return val;
list.dofilter(function(x) { return x.startsWith(val); }); list.dofilter(function(x) { return x.startsWith(val); });

View file

@ -524,6 +524,7 @@ function world_start() {
Primum.toString = function() { return "Primum"; }; Primum.toString = function() { return "Primum"; };
Primum.ur = undefined; Primum.ur = undefined;
Primum.kill = function() { this.clear(); }; Primum.kill = function() { this.clear(); };
gameobject.level = Primum;
} }
load("scripts/physics.js"); 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 = {}; var actor = {};
actor.spawn = function(script, config){ actor.spawn = function(script, config){
if (typeof script !== 'string') return; if (typeof script !== 'string') return;
@ -24,10 +37,7 @@ actor.kill = function(){
this.padawans.forEach(p => p.kill()); this.padawans.forEach(p => p.kill());
this.__dead__ = true; this.__dead__ = true;
}; };
actor.toJSON = function() {
if (this.__dead__) return undefined;
return this;
}
actor.delay = function(fn, seconds) { actor.delay = function(fn, seconds) {
var t = Object.create(timer); var t = Object.create(timer);
t.remain = seconds; t.remain = seconds;
@ -55,6 +65,93 @@ actor.remaster = function(to){
to.padawans.push(this); 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 = { var gameobject = {
get_comp_by_name(name) { get_comp_by_name(name) {
var comps = []; var comps = [];
@ -128,10 +225,6 @@ var gameobject = {
return killfn; 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); }, set torque(x) { if (!(x >= 0 && x <= Infinity)) return; cmd(153, this.body, x); },
gscale() { return cmd(103,this.body); }, gscale() { return cmd(103,this.body); },
sgscale(x) { sgscale(x) {
@ -139,65 +232,14 @@ var gameobject = {
x = [x,x]; x = [x,x];
cmd(36,this.body,x) cmd(36,this.body,x)
}, },
get scale() {
Debug.assert(this.level, `No level set on ${this.toString()}`); phys_material() {
return this.gscale().map((x,i) => x/this.level.gscale()[i]); var mat = {};
mat.elasticity = this.elasticity;
mat.friction = this.friction;
return mat;
}, },
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 */
},
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); }, worldpos() { return q_body(1,this.body); },
set_worldpos(x) { set_worldpos(x) {
var poses = this.objects.map(x => x.pos); var poses = this.objects.map(x => x.pos);
@ -206,33 +248,8 @@ var gameobject = {
}, },
screenpos() { return world2screen(this.worldpos()); }, screenpos() { return world2screen(this.worldpos()); },
worldangle() { return Math.rad2deg(q_body(2,this.body))%360; }, worldangle() { return Math.rad2turn(q_body(2,this.body)); },
sworldangle(x) { set_body(0,this.body,Math.deg2rad(x)); }, sworldangle(x) { set_body(0,this.body,Math.turn2rad(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)];
});
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) { spawn_from_instance(inst) {
return this.spawn(inst.ur, inst); return this.spawn(inst.ur, inst);
@ -290,15 +307,6 @@ var gameobject = {
components: {}, components: {},
objects: {}, objects: {},
level: undefined, 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);}, pulse(vec) { set_body(4, this.body, vec);},
shove(vec) { set_body(12,this.body,vec);}, shove(vec) { set_body(12,this.body,vec);},
@ -310,19 +318,12 @@ var gameobject = {
dir_world2this(dir) { return cmd(160, this.body, dir); }, dir_world2this(dir) { return cmd(160, this.body, dir); },
dir_this2world(dir) { return cmd(161, 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; }, alive() { return this.body >= 0; },
in_air() { return q_body(7, this.body);}, in_air() { return q_body(7, this.body);},
hide() { this.components.forEach(x=>x.hide()); this.objects.forEach(x=>x.hide());}, 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(); }); }, 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() { width() {
var bb = this.boundingbox(); var bb = this.boundingbox();
return bb.r - bb.l; return bb.r - bb.l;
@ -334,15 +335,11 @@ var gameobject = {
}, },
move(vec) { this.pos = this.pos.add(vec); }, move(vec) { this.pos = this.pos.add(vec); },
rotate(x) { this.sworldangle(this.worldangle()+x); },
rotate(amt) { this.angle += amt; }, spread(vec) { this.sgscale(this.gscale().map((x,i)=>x*vec[i])); },
/* Make a unique object the same as its prototype */ /* Make a unique object the same as its prototype */
revert() { 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 jobj = this.json_obj();
var lobj = this.level.__proto__.objects[this.toString()]; var lobj = this.level.__proto__.objects[this.toString()];
delete jobj.objects; delete jobj.objects;
@ -433,8 +430,10 @@ var gameobject = {
return bb ? bb : cwh2bb([0,0], [0,0]); return bb ? bb : cwh2bb([0,0], [0,0]);
}, },
/* The unique components of this object. Its diff. */
json_obj() { json_obj() {
var d = ediff(this,this.__proto__); var d = ediff(this,this.__proto__);
d ??= {}; d ??= {};
var objects = {}; var objects = {};
@ -454,51 +453,34 @@ var gameobject = {
return d; return d;
}, },
transform_obj() { /* The object needed to store an object as an instance of a level */
var t = this.json_obj();
Object.assign(t, this.transform());
return t;
},
instance_obj() { instance_obj() {
var t = this.transform_obj(); var t = this.transform();
var j = this.json_obj();
Object.assign(t,j);
t.ur = this.ur; t.ur = this.ur;
return t; 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() { transform() {
var t = {}; var t = {};
t.pos = this.pos.map(Number.prec); t.pos = this.pos;
t.angle = Number.prec(this.angle); t.angle = this.angle;
t.scale = this.scale;
return t; return t;
}, },
/* Velocity and angular velocity of the object */
phys_obj() { phys_obj() {
var phys = {}; var phys = {};
phys.velocity = this.velocity.map(Number.prec); phys.velocity = this.velocity;
phys.angularvelocity = Number.prec(this.angularvelocity); phys.angularvelocity = this.angularvelocity;
return phys; return phys;
}, },
dup(diff) { dup(diff) {
var n = this.level.spawn(this.__proto__); var n = this.level.spawn(this.__proto__);
Object.totalmerge(n, this.make_ur()); Object.totalmerge(n, this.instance_obj());
return n; return n;
}, },
@ -531,14 +513,16 @@ var gameobject = {
this.stop(); this.stop();
}, },
up() { return [0,1].rotate(Math.deg2rad(this.angle));}, up() { return [0,1].rotate(this.angle);},
down() { return [0,-1].rotate(Math.deg2rad(this.angle));}, down() { return [0,-1].rotate(this.angle);},
right() { return [1,0].rotate(Math.deg2rad(this.angle));}, right() { return [1,0].rotate(this.angle);},
left() { return [-1,0].rotate(Math.deg2rad(this.angle));}, left() { return [-1,0].rotate(this.angle); },
make(level, data) { make(level, data) {
var obj = Object.create(this); var obj = Object.create(this);
obj.make = undefined; obj.make = undefined;
Object.mixin(obj,gameobject_impl);
if (this.instances) if (this.instances)
this.instances.push(obj); this.instances.push(obj);
@ -582,7 +566,7 @@ var gameobject = {
if (data) if (data)
Object.dainty_assign(obj,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(); if (Game.playing() && typeof obj.start === 'function') obj.start();
return obj; return obj;
@ -617,9 +601,13 @@ var gameobject = {
return this.objects[newname]; return this.objects[newname];
}, },
add_component(comp) { add_component(comp, data) {
data ??= undefined;
if (typeof comp.make !== 'function') return; 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) { register_hit(fn, obj) {
@ -632,6 +620,7 @@ var gameobject = {
Signal.obj_separate(fn,obj,this); Signal.obj_separate(fn,obj,this);
}, },
} }
Object.mixin(gameobject,gameobject_impl);
gameobject.body = make_gameobject(); gameobject.body = make_gameobject();
cmd(113,gameobject.body, gameobject); cmd(113,gameobject.body, gameobject);
@ -678,6 +667,7 @@ gameobject.doc = {
level: "The entity this entity belongs to.", level: "The entity this entity belongs to.",
delay: 'Run the given function after the given number of seconds has elapsed.', delay: 'Run the given function after the given number of seconds has elapsed.',
cry: 'Make a sound. Can only make one at a time.', cry: 'Make a sound. Can only make one at a time.',
add_component: 'Add a component to the object by name.',
}; };
/* Default objects */ /* Default objects */

View file

@ -4,6 +4,7 @@
#include <string.h> #include <string.h>
#include "debugdraw.h" #include "debugdraw.h"
#include "log.h" #include "log.h"
#include "math.h"
#include "stb_ds.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; 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); cpBodyUpdateVelocity(body,g,d,dt*go->timescale);
@ -154,9 +157,8 @@ gameobject *MakeGameobject() {
.next = -1, .next = -1,
.drawlayer = 0, .drawlayer = 0,
.shape_cbs = NULL, .shape_cbs = NULL,
.gravity = 1, .gravity = (HMM_Vec2){INFINITY,INFINITY},
.cgravity = (HMM_Vec2){0,0}, .damping = INFINITY,
.damping = NAN,
.timescale = 1.0, .timescale = 1.0,
.ref = JS_UNDEFINED, .ref = JS_UNDEFINED,
}; };

View file

@ -36,8 +36,7 @@ struct gameobject {
float timescale; float timescale;
float maxvelocity; float maxvelocity;
float maxangularvelocity; float maxangularvelocity;
int gravity; HMM_Vec2 gravity; /* its own gravity */
HMM_Vec2 cgravity;
float damping; float damping;
unsigned int layer; unsigned int layer;
cpShapeFilter filter; 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); ret = num2js(js2gameobject(argv[1])->damping);
break; break;
case 158: case 158:
js2gameobject(argv[1])->gravity = js2bool(argv[2]);
break; break;
case 159: case 159:
ret = bool2js(js2gameobject(argv[1])->gravity); ret = vec2js(js2gameobject(argv[1])->gravity);
break; break;
case 160: case 160:
ret = vec2js(mat_t_dir(t_world2go(js2gameobject(argv[1])), js2vec2(argv[2]))); 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)); ret = int2js(cp(str, str2));
break; break;
case 167: case 167:
js2gameobject(argv[1])->cgravity = js2vec2(argv[2]); js2gameobject(argv[1])->gravity = js2vec2(argv[2]);
break; break;
case 168: case 168:
js2gameobject(argv[1])->timescale = js2number(argv[2]); 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) { if (stm_sec(stm_diff(frame_t, updatelast)) > updateMS) {
double dt = stm_sec(stm_diff(frame_t, updatelast)); double dt = stm_sec(stm_diff(frame_t, updatelast));
updatelast = frame_t; updatelast = frame_t;
// prof_start(&prof_update);
// prof_start(&prof_update);
call_updates(dt * timescale); call_updates(dt * timescale);
// prof_lap(&prof_update); // prof_lap(&prof_update);