Fix separation trigger; fix timers; add geometry creation; fix transform errors

This commit is contained in:
John Alanbrook 2023-11-16 15:27:04 +00:00
parent 563bacdb9c
commit e712d06244
9 changed files with 195 additions and 67 deletions

View file

@ -174,7 +174,7 @@ tools/libcdb.a:
mv $(CDB)/libcdb.a tools mv $(CDB)/libcdb.a tools
DOCOS = Sound gameobject Game Window physics Profile Time Player Mouse IO Log ColorMap sprite SpriteAnim Render DOCOS = Sound gameobject Game Window physics Profile Time Player Mouse IO Log ColorMap sprite SpriteAnim Render Geometry
DOCHTML := $(addsuffix .api.html, $(DOCOS)) DOCHTML := $(addsuffix .api.html, $(DOCOS))
DOCMD := $(addsuffix .api.md, $(DOCOS)) DOCMD := $(addsuffix .api.md, $(DOCOS))

View file

@ -5,7 +5,8 @@ Script hooks exist to allow to modification of the game.
|config.js|called before any game play, including play from editor| |config.js|called before any game play, including play from editor|
|game.js|called to start the game| |game.js|called to start the game|
|editorconfig.js|called when the editor is loaded, used to personalize| |editorconfig.js|called when the editor is loaded, used to personalize|
|debug.js|called when play in editor is selected| |predbg.js|called when play in editor is selected, before level load|
|debug.js|called when play in editor is selected, after level load|
|dbgret.js|called when play in editor returns to editor| |dbgret.js|called when play in editor returns to editor|
All objects in the Yugine can have an associated script. This script can perform setup, teardown, and handles responses for the object. All objects in the Yugine can have an associated script. This script can perform setup, teardown, and handles responses for the object.
@ -15,4 +16,7 @@ All objects in the Yugine can have an associated script. This script can perform
|start|called when the object is loaded| |start|called when the object is loaded|
|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|
|debug|use draw functions with the object's world position|
|gui|draw functions in screen space|
|draw|draw functions in world space|

View file

@ -1,3 +1,16 @@
function make_point_obj(o, p)
{
return {
pos: p,
move(d) {
d = o.gameobject.dir_world2this(d);
p.x += d.x;
p.y += d.y;
},
sync: o.sync.bind(o)
}
}
function assign_impl(obj, impl) function assign_impl(obj, impl)
{ {
var tmp = {}; var tmp = {};
@ -355,10 +368,6 @@ component.char2d.doc = {
add_anim: "Add an animation object with the given name." add_anim: "Add an animation object with the given name."
}; };
// sprite.timer = timer.make(sprite.advance.bind(sprite),1);
// sprite.timer.loop = true;
/* Returns points specifying this geometry, with ccw */ /* Returns points specifying this geometry, with ccw */
var Geometry = { var Geometry = {
box(w, h) { box(w, h) {
@ -373,9 +382,42 @@ var Geometry = {
]; ];
return points; return points;
} },
arc(radius, angle, n, start) {
start ??= 0;
start = Math.deg2rad(start);
if (angle >= 360)
angle = 360;
if (n <= 1) return [];
var points = [];
angle = Math.deg2rad(angle);
var arclen = angle/n;
for (var i = 0; i < n; i++)
points.push(Vector.rotate([radius,0], start + (arclen*i)));
return points;
},
circle(radius, n) {
if (n <= 1) return [];
return Geometry.arc(radius, 360, n);
},
ngon(radius, n) {
return Geometry.arc(radius,360,n);
},
}; };
Geometry.doc = {
doc: "Functions for creating a list of points for various geometric shapes.",
box: "Create a box.",
arc: "Create an arc, made of n points.",
circle: "Create a circle, made of n points."
};
/* For all colliders, "shape" is a pointer to a phys2d_shape, "id" is a pointer to the shape data */ /* For all colliders, "shape" is a pointer to a phys2d_shape, "id" is a pointer to the shape data */
var collider2d = Object.copy(component, { var collider2d = Object.copy(component, {
name: "collider 2d", name: "collider 2d",
@ -416,6 +458,10 @@ component.polygon2d = Object.copy(collider2d, {
hides: ['id', 'shape', 'gameobject'], hides: ['id', 'shape', 'gameobject'],
_enghook: make_poly2d, _enghook: make_poly2d,
points:[], points:[],
setpoints(points) {
this.points = points;
this.sync();
},
/* EDITOR */ /* EDITOR */
get spoints() { get spoints() {
@ -454,16 +500,8 @@ component.polygon2d = Object.copy(collider2d, {
this.points = deep_copy(this.__proto__.points); this.points = deep_copy(this.__proto__.points);
var p = Gizmos.pick_gameobject_points(pos, this.gameobject, this.points); var p = Gizmos.pick_gameobject_points(pos, this.gameobject, this.points);
if (p) { if (p)
return { return make_point_obj(this, p);
set pos(n) {
p.x = n.x;
p.y = n.y;
},
get pos() { return p; },
sync: this.sync.bind(this)
}
}
return undefined; return undefined;
}, },
@ -560,6 +598,11 @@ component.edge2d = Object.copy(collider2d, {
return spoints; return spoints;
}, },
setpoints(points) {
this.cpoints = points;
this.sync();
},
post() { post() {
this.cpoints = []; this.cpoints = [];
}, },
@ -575,7 +618,7 @@ component.edge2d = Object.copy(collider2d, {
return spoints; return spoints;
if (spoints.length < 2) if (spoints.length < 2)
return []; return [];
if (this.samples === spoints.length) { if (this.samples === 1) {
if (this.looped) return spoints.wrapped(1); if (this.looped) return spoints.wrapped(1);
return spoints; return spoints;
} }
@ -585,6 +628,8 @@ component.edge2d = Object.copy(collider2d, {
knots = spoints.length + order knots = spoints.length + order
assert knots%order != 0 assert knots%order != 0
*/ */
n = this.samples * this.sample_calc();
if (this.looped) if (this.looped)
return Spline.sample(degrees, this.dimensions, Spline.type.open, spoints.wrapped(this.degrees), n); return Spline.sample(degrees, this.dimensions, Spline.type.open, spoints.wrapped(this.degrees), n);
@ -618,26 +663,28 @@ component.edge2d = Object.copy(collider2d, {
pick(pos) { pick(pos) {
var p = Gizmos.pick_gameobject_points(pos, this.gameobject, this.cpoints); var p = Gizmos.pick_gameobject_points(pos, this.gameobject, this.cpoints);
if (p) return { if (p)
set pos(n) { p.x = n.x; p.y = n.y; }, return make_point_obj(this, p);
get pos() { return p; },
sync: this.sync.bind(this),
};
return undefined; return undefined;
}, },
pick_all() { pick_all() {
var picks = []; var picks = [];
this.cpoints.forEach(function(x) { this.cpoints.forEach(function(x) {
picks.push({ picks.push(make_point_obj(this,x));
set pos(n) { x.x = n.x; x.y = n.y; },
get pos() { return x; },
sync: this.sync.bind(this),
});
}, this); }, this);
return picks; return picks;
}, },
sample_calc() {
return (this.spoints().length-1);
},
samples_per_cp() {
var s = this.sample_calc();
return this.samples/s;
},
}); });
component.edge2d.impl = Object.mix({ component.edge2d.impl = Object.mix({
@ -646,7 +693,6 @@ component.edge2d.impl = Object.mix({
}, },
get thickness() { return cmd(112,this.id); }, get thickness() { return cmd(112,this.id); },
sync() { sync() {
if (this.samples < this.spoints().length) this.samples = this.spoints().length;
var sensor = this.sensor; var sensor = this.sensor;
var points = this.sample(this.samples); var points = this.sample(this.samples);
cmd_edge2d(0,this.id,points); cmd_edge2d(0,this.id,points);
@ -684,11 +730,16 @@ bucket.inputs['M-b'] = function() { this.thickness++; };
bucket.inputs['M-b'].doc = "Increase spline thickness."; bucket.inputs['M-b'].doc = "Increase spline thickness.";
bucket.inputs['M-b'].rep = true; bucket.inputs['M-b'].rep = true;
bucket.inputs.plus = function() { this.samples++; }; bucket.inputs.plus = function() {
this.samples++;
};
bucket.inputs.plus.doc = "Increase the number of samples of this spline."; bucket.inputs.plus.doc = "Increase the number of samples of this spline.";
bucket.inputs.plus.rep = true; bucket.inputs.plus.rep = true;
bucket.inputs.minus = function() { if (this.samples > this.spoints().length) this.samples--;}; bucket.inputs.minus = function() {
if (this.samples === 1) return;
this.samples--;
};
bucket.inputs.minus.doc = "Decrease the number of samples on this spline."; bucket.inputs.minus.doc = "Decrease the number of samples on this spline.";
bucket.inputs.minus.rep = true; bucket.inputs.minus.rep = true;
@ -698,25 +749,23 @@ bucket.inputs['C-r'].doc = "Reverse the order of the spline's points.";
bucket.inputs['C-l'] = function() { this.looped = !this.looped}; bucket.inputs['C-l'] = function() { this.looped = !this.looped};
bucket.inputs['C-l'].doc = "Toggle spline being looped."; bucket.inputs['C-l'].doc = "Toggle spline being looped.";
bucket.inputs['C-c'] = function() { this.type = Spline.type.clamped; }; bucket.inputs['C-c'] = function() { this.type = Spline.type.clamped; this.looped = false; };
bucket.inputs['C-c'].doc = "Set type of spline to clamped."; bucket.inputs['C-c'].doc = "Set type of spline to clamped.";
bucket.inputs['C-o'] = function() { this.type = Spline.type.open; }; bucket.inputs['C-o'] = function() { this.type = Spline.type.open; this.looped = false; };
bucket.inputs['C-o'].doc = "Set spline to open."; bucket.inputs['C-o'].doc = "Set spline to open.";
bucket.inputs['C-b'] = function() { this.type = Spline.type.bezier; }; bucket.inputs['C-b'] = function() { this.type = Spline.type.bezier; this.looped = false;};
bucket.inputs['C-b'].doc = "Set spline to bezier."; bucket.inputs['C-b'].doc = "Set spline to bezier.";
bucket.inputs['C-M-lm'] = function() { bucket.inputs['C-M-lm'] = function() {
var idx = grab_from_points(Mouse.worldpos, this.cpoints.map(this.gameobject.world2this,this.gameobject), 25); var idx = grab_from_points(Mouse.worldpos, this.cpoints.map(p => this.gameobject.this2world(p)), 25);
if (idx === -1) return; if (idx === -1) return;
this.cpoints = this.cpoints.newfirst(idx); this.cpoints = this.cpoints.newfirst(idx);
}; };
bucket.inputs['C-M-lm'].doc = "Select the given point as the '0' of this spline."; bucket.inputs['C-M-lm'].doc = "Select the given point as the '0' of this spline.";
bucket.inputs.lm = function(){};
bucket.inputs['C-lm'] = function() { bucket.inputs['C-lm'] = function() {
var idx = 0; var idx = 0;
@ -732,15 +781,14 @@ bucket.inputs['C-lm'] = function() {
}; };
bucket.inputs['C-lm'].doc = "Add a point to the spline at the mouse position."; bucket.inputs['C-lm'].doc = "Add a point to the spline at the mouse position.";
bucket.inputs['C-M-lm'] = function() { bucket.inputs['S-lm'] = function() {
// var idx = grab_from_points(screen2world(Mouse.pos), this.cpoints.map(function(x) {return x.sub(this.gameobject.worldpos()); }, this), 25); var idx = grab_from_points(Mouse.worldpos, this.cpoints.map(p => this.gameobject.this2world(p)), 25);
var idx = cmd(59, Mouse.worldpos.sub(this.gameobject.pos), this.cpoints, 250);
if (idx <= 0 || idx > this.cpoints.length) return; if (idx < 0 || idx > this.cpoints.length) return;
this.cpoints.splice(idx-1, 1); this.cpoints.splice(idx, 1);
}; };
bucket.inputs['C-M-lm'].doc = "Remove point from the spline."; bucket.inputs['S-lm'].doc = "Remove point from the spline.";
bucket.inputs.lb = function() { bucket.inputs.lb = function() {
var np = []; var np = [];

View file

@ -210,6 +210,7 @@ var editor = {
Player.players[0].uncontrol(this); Player.players[0].uncontrol(this);
Player.players[0].control(limited_editor); Player.players[0].control(limited_editor);
Register.unregister_obj(this); Register.unregister_obj(this);
load("predbg.js");
editor.dbg_play = Primum.spawn(this.dbg_ur); editor.dbg_play = Primum.spawn(this.dbg_ur);
editor.dbg_play.pos = [0,0]; editor.dbg_play.pos = [0,0];
load("debug.js"); load("debug.js");
@ -596,7 +597,7 @@ var editor = {
var ur = prototypes.get_ur(file); var ur = prototypes.get_ur(file);
if (!ur) return; if (!ur) return;
var obj = editor.edit_level.spawn(ur); var obj = editor.edit_level.spawn(ur);
obj.pos = Mouse.worldpos; obj.set_worldpos(Mouse.worldpos);
this.selectlist = [obj]; this.selectlist = [obj];
}, },
@ -904,6 +905,8 @@ editor.inputs['C-s'] = function() {
Object.values(saveobj.objects).forEach(function(x) { x._ed.check_dirty(); }); Object.values(saveobj.objects).forEach(function(x) { x._ed.check_dirty(); });
Game.objects.forEach(function(x) { Game.objects.forEach(function(x) {
if (typeof x !== 'object') return;
if (!('_ed' in x)) return;
if (x._ed.dirty) return; if (x._ed.dirty) return;
x.revert(); x.revert();
x._ed.check_dirty(); x._ed.check_dirty();
@ -1153,11 +1156,11 @@ editor.inputs.mouse.move = function(pos, dpos)
if (editor.z_start) if (editor.z_start)
editor.camera.zoom -= dpos.y/500; editor.camera.zoom -= dpos.y/500;
else if (editor.joystart) else if (editor.joystart)
editor.camera.pos = editor.camera.pos.sub(dpos.scale(editor.camera.zoom)); editor.camera.pos = editor.camera.pos.sub(Game.camera.dir_view2world(dpos));
} }
editor.grabselect?.forEach(function(x) { editor.grabselect?.forEach(function(x) {
x.pos = x.pos.add(dpos.scale(editor.camera.zoom)); x.move(Game.camera.dir_view2world(dpos));
if ('sync' in x) if ('sync' in x)
x.sync(); x.sync();
}); });
@ -1184,12 +1187,8 @@ editor.inputs.mouse.move = function(pos, dpos)
editor.inputs.mouse.scroll = function(scroll) editor.inputs.mouse.scroll = function(scroll)
{ {
scroll.y *= -1; scroll.y *= -1;
editor.grabselect?.forEach(function(x) { // editor.grabselect?.forEach(x => x.move(Game.camera.dir_view2world(scroll)));
x.pos = x.pos.add(scroll.scale(editor.camera.zoom)); editor.camera.move(Game.camera.dir_view2world(scroll.scale(-3)));
});
scroll.y *= -1;
editor.camera.pos = editor.camera.pos.sub(scroll.scale(editor.camera.zoom * 3).scale([1,-1]));
} }
editor.inputs.mouse['C-scroll'] = function(scroll) editor.inputs.mouse['C-scroll'] = function(scroll)
@ -2000,7 +1999,7 @@ var entitylistpanel = Object.copy(inputpanel, {
this.level = editor.edit_level; this.level = editor.edit_level;
}, },
guibody() { /* guibody() {
Nuke.newline(4); Nuke.newline(4);
Nuke.label("Object"); Nuke.label("Object");
Nuke.label("Visible"); Nuke.label("Visible");
@ -2018,6 +2017,7 @@ var entitylistpanel = Object.copy(inputpanel, {
if (editor.selectlist.includes(x)) Nuke.label("T"); else Nuke.label("F"); if (editor.selectlist.includes(x)) Nuke.label("T"); else Nuke.label("F");
}); });
}, },
*/
}); });
var limited_editor = {}; var limited_editor = {};

View file

@ -272,7 +272,7 @@ var Device = {
load("scripts/gui.js"); load("scripts/gui.js");
var timer = { var ctimer = {
make(fn, secs,obj,loop,app) { make(fn, secs,obj,loop,app) {
obj ??= globalThis; obj ??= globalThis;
app ??= false; app ??= false;
@ -308,6 +308,61 @@ var timer = {
get time() { return cmd(29, this.id); }, get time() { return cmd(29, this.id); },
get pct() { return this.remain / this.time; }, get pct() { return this.remain / this.time; },
}; };
var timer = {
time: 1,
remain: 1,
loop: false,
on: false,
start() {
this.on = true;
},
restart() {
this.remain = this.time;
this.start();
},
update(dt) {
if (!this.on) return;
this.remain -= dt;
if (this.remain <= 0)
this.fire();
},
fire() {
this.fn();
if (this.loop)
this.restart();
},
pct() { return this.remain / this.time; },
kill() {
Register.unregister_obj(this);
},
delay(fn, secs, desc) {
var t = timer.make(fn,secs,desc);
t.loop = false;
t.restart();
t.fn = function() { fn(); t.kill(); };
return function() { t.kill(); };
},
oneshot(fn, secs, obj, desc) {
this.delay(fn,secs,desc);
},
make(fn, secs, desc) {
var t = Object.create(this);
Object.assign(t, desc);
t.time = secs;
t.remain = secs;
t.fn = fn;
Register.update.register(t.update, t);
return t;
},
};
timer.toJSON = function() { return undefined; }; timer.toJSON = function() { return undefined; };
timer.doc = { timer.doc = {
doc: "Quickly make timers to fire off events once or multiple times.", doc: "Quickly make timers to fire off events once or multiple times.",

View file

@ -44,7 +44,7 @@ var gameobject = {
timers:[], timers:[],
delay(fn, seconds) { delay(fn, seconds) {
var t = timer.oneshot(fn, seconds, this, false); var t = timer.oneshot(fn.bind(this), seconds, this, false);
this.timers.push(t); this.timers.push(t);
return function() { t.kill(); }; return function() { t.kill(); };
}, },
@ -223,6 +223,9 @@ var gameobject = {
torque(val) { cmd(153, this.body, val); }, torque(val) { cmd(153, this.body, val); },
world2this(pos) { return cmd(70, this.body, pos); }, world2this(pos) { return cmd(70, this.body, pos); },
this2world(pos) { return cmd(71, this.body,pos); }, this2world(pos) { return cmd(71, this.body,pos); },
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); }, set layer(x) { cmd(75,this.body,x); },
get layer() { cmd(77,this.body); }, get layer() { cmd(77,this.body); },
alive() { return this.body >= 0; }, alive() { return this.body >= 0; },
@ -258,9 +261,13 @@ var gameobject = {
/* Make a unique object the same as its prototype */ /* Make a unique object the same as its prototype */
revert() { revert() {
var jobj = this.json_obj(); var jobj = this.json_obj();
var lobj = this.level.__proto__.objects[this.toString()];
delete jobj.objects; delete jobj.objects;
Object.keys(jobj).forEach(function(x) { Object.keys(jobj).forEach(function(x) {
this[x] = this.__proto__[x]; if (lobj && x in lobj)
this[x] = lobj[x];
else
this[x] = this.__proto__[x];
}, this); }, this);
this.sync(); this.sync();
}, },
@ -301,7 +308,7 @@ var gameobject = {
phys:Physics.static, phys:Physics.static,
flipx() { return this.scale.x < 0; }, flipx() { return this.scale.x < 0; },
flipy() { return this.scale.y < 0; }, flipy() { return this.scale.y < 0; },
scale:[1,1,1], scale:[1,1],
mirror(plane) { mirror(plane) {
this.scale = Vector.reflect(this.scale, plane); this.scale = Vector.reflect(this.scale, plane);
}, },
@ -834,13 +841,18 @@ prototypes.from_obj("camera2d", {
z *= x; z *= x;
cmd(62, z); cmd(62, z);
}, },
realzoom() {
return cmd(135);
},
speedmult: 1.0, speedmult: 1.0,
selectable: false, selectable: false,
world2this(pos) { return cmd(70, this.body, pos); }, dir_view2world(dir) {
this2world(pos) { return cmd(71, this.body,pos); }, return dir.scale(this.realzoom());
},
view2world(pos) { view2world(pos) {
return cmd(137,pos); return cmd(137,pos);

View file

@ -256,3 +256,8 @@ Cmdline.register_cmd("cjson", function(json) {
STD.exit(0); STD.exit(0);
}, "Clean up a jso file."); }, "Clean up a jso file.");
Cmdline.register_cmd("r", function(script) {
run(script);
STD.exit(0);
}, "Run a script.");

View file

@ -587,9 +587,9 @@ void duk_call_phys_cb(HMM_Vec2 norm, struct callee c, int hit, cpArbiter *arb) {
HMM_Vec2 srfv; HMM_Vec2 srfv;
srfv.cp = cpArbiterGetSurfaceVelocity(arb); srfv.cp = cpArbiterGetSurfaceVelocity(arb);
JS_SetPropertyStr(js, obj, "velocity", vec2js(srfv)); JS_SetPropertyStr(js, obj, "velocity", vec2js(srfv));
srfv.cp = cpArbiterGetPointA(arb,0); // srfv.cp = cpArbiterGetPointA(arb,0);
JS_SetPropertyStr(js, obj, "pos", vec2js(srfv)); // JS_SetPropertyStr(js, obj, "pos", vec2js(srfv));
JS_SetPropertyStr(js,obj,"depth", num2js(cpArbiterGetDepth(arb,0))); // JS_SetPropertyStr(js,obj,"depth", num2js(cpArbiterGetDepth(arb,0)));
JS_SetPropertyStr(js, obj, "id", JS_NewInt32(js,hit)); JS_SetPropertyStr(js, obj, "id", JS_NewInt32(js,hit));
JS_SetPropertyStr(js,obj,"obj", JS_DupValue(js,id2go(hit)->ref)); JS_SetPropertyStr(js,obj,"obj", JS_DupValue(js,id2go(hit)->ref));
@ -632,10 +632,8 @@ static cpBool handle_collision(cpArbiter *arb, int type) {
break; break;
case CTYPE_SEP: case CTYPE_SEP:
if (JS_IsObject(go->cbs.separate.obj)) { if (JS_IsObject(go->cbs.separate.obj))
YughWarn("Made it here; separate.");
duk_call_phys_cb(norm1, go->cbs.separate, g2, arb); duk_call_phys_cb(norm1, go->cbs.separate, g2, arb);
}
break; break;
} }

View file

@ -1146,6 +1146,12 @@ JSValue duk_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *argv)
case 159: case 159:
ret = bool2js(js2go(argv[1])->gravity); ret = bool2js(js2go(argv[1])->gravity);
break; break;
case 160:
ret = vec2js(mat_t_dir(t_world2go(js2go(argv[1])), js2vec2(argv[2])));
break;
case 161:
ret = vec2js(mat_t_dir(t_go2world(js2go(argv[1])), js2vec2(argv[2])));
break;
} }
if (str) if (str)