Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- 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;
- }
Advertisement