function move_init() {
moveable = false; // can be pushed or carried
can_push = false; // can push things
can_carry = false; // can carry things
carry_dist = {l:0, r:0} // how far the object has been carried left or right
subpixel=[0,0];
collision_at = function(xp,yp) {
// can override for custom collision behaviour
return !place_free(xp,yp);
}
}
// call this in begin step, it should run every frame before calls to move
function move_bstep() {
if moveable {
carry_dist = {l:0,r:0}
}
}
// internal function for resetting positions after a push fails
function _move_reset(reset) {
if is_array(reset) {
for(var i=0; i<array_length(reset); ++i) {
var data = reset[i];
if instance_exists(data.id) {
data.id.x = data.x;
data.id.y = data.y;
}
}
}
}
function move(amount, axis) {
// split the amount into pixel movement (target) and subpixel movement
// we use ceil(n - 1/2) instead of round(n) so that round(#.5) always rounds down
// ensures subpixel range is precisely -0.5 <= subpixel < 0.5
// this is important for vertical carrying, if i remember right
var target = ceil(subpixel[axis] + amount - 1/2);
subpixel[axis] += amount - target;
// shorthands:
var xa = 1-axis; // true if we're moving on the x-axis
var ya = axis; // true if we're moving on the y-axis
var s = sign(target); // direction of movement
// temporaries:
var souls = undefined; // holds other pushable/carriable objects we collide with
var reset = undefined; // holds reset info, if we need to take back a sequence of moves
var dist = 0; // total pixel distance we have moved
// now move towards the target, one pixel at a time
while target != 0 {
// before moving, try to push objects out of the way
if (xa and can_push) // pushing horizontally
or ((ya and s < 0) and can_carry) // or carrying an object on top of us
or ((ya and s > 0) and can_push and !moveable) { // or pushing an object below us
// clear the reset data
reset = [];
// we temporarily toggle our moveableness off
// it's to avoid infinite loops (the thing we push tries to push us, etc.)
var moveable_restore = moveable;
moveable = false;
// gather all souls in our path and push them
souls = instance_place_array(x+xa*s,y+ya*s,oSoul);
for(var i=0; i<array_length(souls); ++i) {
var soul = souls[i];
// if we can push the soul, try to push it
if soul.moveable {
// add the soul to the reset data, in case we need to revert the push
array_push(reset,{id: soul.id, x:soul.x, y:soul.y});
// now do the actual push
// note that this might recursively push or carry other things!
with soul {
move(s,axis);
}
}
}
// restore our original moveable status
moveable = moveable_restore;
}
// now, check for a collision one step ahead
if collision_at(x+xa*s,y+ya*s) {
// we hit something, abort!
// this first part is optional
// you can do a collision callback for any souls you collided with
souls = instance_place_array(x+xa*s,y+ya*s,oSoul);
for(var i=0; i<array_length(souls); ++i) {
var soul = souls[i];
with soul {
with other {
// callback goes here
// "self" is the soul that move was called on
// "other" is the soul we collided with
// on_collide(xa,ya,s);
}
}
}
// this part is not optional
// if we pushed things, but then were unable to move ourselves,
// that means the push failed at some point
// reset the positions of everything we pushed
if can_push {
_move_reset(reset);
}
// this part comes down to personal preference
// when you hit a wall there are various things you can do with subpixels
// set to 0, set to bounds of subpixel range, do nothing etc.
subpixel[axis] = 0
// returning true indicates movement was successful
// returning false indicates we hit something
// you could customize this to return more complex data if you need it
return false;
}
// there is nothing in our way, so we're going to move
// before moving, determine what needs to be carried
// we only carry objects that are riding on top of us
if can_carry {
souls = instance_place_array(x,y-1,oSoul);
}
// finally, we actually move one pixel
x += xa*s;
y += ya*s;
target -= s;
dist++;
// now that we've moved, carry things along with us
if can_carry {
// again we temporarily toggle our moveableness off
// to avoid infinite carry loops
var moveable_restore = moveable;
moveable = false;
// finally, carrying time
for(var i=0; i<array_length(souls); ++i) {
var soul = souls[i];
if soul.moveable {
with soul {
if xa { // if we're carrying to the left or right
// we keep track of the maximum distance, per-step, the object has been carried
// separate left and right distances are tracked, the count is reset in begin step
// we only do the carry if it will increase the maximum distance in that direction
// why? this is to avoid the "double carry problem":
// imagine a soul is standing on top of two souls, both moving left
// the soul on top gets carried 2px for every 1px the bottom ones move, this shouldn't happen
// i don't have a mathematical proof this carry rule is correct or anything
// but i think it worked well in my experiments
// anyways
if s < 0 {
if dist > carry_dist.l {
carry_dist.l++;
move(s,axis);
}
} else {
if dist > carry_dist.r {
carry_dist.r++;
move(s,axis);
}
}
} else if ya {
// only triggers if we're carrying downwards (an upwards carry is a push)
move(s,axis);
}
}
}
}
// restore moveable status
moveable = moveable_restore;
}
}
// we're out of the while loop which means we moved the full target distance without hitting anything
return true;
}
Comments
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎