Legacy GM [SOLVED] Ball on chain.

D

DyingSilence

Guest
I'm creating a 2D top down action roguelike called Nephilim Hunt, and I want one of my equipable items to be a ball on chain. It would be attached to the character's foot, slowing him down if he's far enough to pull the heavy thing.
Using physics engine for the entire game is a big no-no, I think the game's systems are too closely integrated to not lose months of progress by including one and I've heard that once there's a physics engine, you can't really do movements by coordinate modifications, which is extra inconvenient in my case.
Do you have any idea how to make the ball follow the player on a tether, also respecting enviroment collisions?

My idea for now is to use pathfinding, but that's expensive for a mobile game, or to do a straight raycast from player to ball and in case of a collision, find the nearest "corner", create a marker there, and make the chain bend around the marker, then shortening the whole thing to fit the chain's length.

The second solution sounds moderately good, but I don't know, maybe you, ladies and gentlemen, have something more elegant out there?

008.png 009.png
 

angelwire

Member
How long will the chain be? If it's a shorter chain you could get away with doing some more simpler coding, but if it's longer you would need something more complex.
 
D

DyingSilence

Guest
How long will the chain be? If it's a shorter chain you could get away with doing some more simpler coding, but if it's longer you would need something more complex.
A tile is 16x16 collision-wise, I think the chain would be about 24 pixels long (so a max distance player can travel without pulling a ball is three tiles).
 

angelwire

Member
Since your game is tile based there may be a great little trick you can do. Are all your wall objects 16x16? If so I may have a solution.
 
D

DyingSilence

Guest
If you want it to bend around corners, this discussion might help you:

https://forum.yoyogames.com/index.p...ght-rope-physics-without-using-physics.36829/
An interesting read indeed, thank you!

Since your game is tile based there may be a great little trick you can do. Are all your wall objects 16x16? If so I may have a solution.
All my walls have collision masks of 16x16 but are 16x24 for the perspective purposes.

I may later on have non-wall objects that are not 16x16.

I'm still interested in the trick.
 

angelwire

Member
Okay, here's what I have, the more I play with the more I realize that only one bend point will probably not be enough: (I think it's worth noting that this is from 1.4)
But if you like it I can go back into it and add multiple bend points.

PLAYER OBJECT CREATE:
Code:
chained_to = ball_object; //the ball object that the player is chained to
chain_length = 128; //how long the chain should be
movement_speed = 4; //how fast the player should currently move
max_movement_speed = 4; //how fast the player moves without being held back by the chain
chain_movement_speed = 1; //how fast the player moves when being held back by the chain

collision_padding = 4; //How close to the wall the chain can get - must be at least 1

chain_is_bent = false; //If there is a bend in the chain
chain_bend_x = 0; //where the bend is
chain_bend_y = 0;
PLAYER OBJECT STEP:
Code:
var ball_distance = point_distance(x,y,chained_to.x,chained_to.y); //Distance between the player and the ball
var ball_collision_distance = point_distance(chain_bend_x,chain_bend_y,chained_to.x,chained_to.y); //Distance between the ball and the chain bend
var player_collision_distance = point_distance(chain_bend_x,chain_bend_y,x,y); //Distance between the bend and the player

if chain_is_bent //If the chain is bent calculate the chain distance
{
    ball_distance = ball_collision_distance + player_collision_distance;
}

if position_meeting(chained_to.x, chained_to.y, block_object) //If the ball is stuck on the wall just go straight towards the player
{
chain_bend_x = player_object.x;
chain_bend_y = player_object.y;
}

if ball_distance > chain_length //If the player is too far
{
    movement_speed = lerp(movement_speed,chain_movement_speed,.2); //Change the movement speed to the slow speed
    //I use lerp to keep the movement from looking jumpy
  
    if chain_is_bent //If the chain is bent move the ball towards the bend
    {
    chained_to.x = lerp(chained_to.x,chain_bend_x,.02*(ball_distance/ball_collision_distance));
    chained_to.y = lerp(chained_to.y,chain_bend_y,.02*(ball_distance/ball_collision_distance));
    }
    else //If the chain isn't bent move the ball toward the player
    {
    chained_to.x = lerp(chained_to.x,player_object.x,.02);
    chained_to.y = lerp(chained_to.y,player_object.y,.02);
    }
}
else //If the player is not too far away set the player's speed to the max speed
{
movement_speed = lerp(movement_speed,max_movement_speed,.2);
}

//Check the line to see if a bend should be added
collision_object = collision_line(x,y,chained_to.x,chained_to.y,block_object,2,false);

if collision_object != noone //If there' a collision
{
    var xoff = (collision_object.sprite_width*.5)+collision_padding; //this assumes the axis is in the middle of the object
    var yoff = (collision_object.sprite_height*.5)+collision_padding; //this assumes the axis is in the middle of the object 

    /*
    If the wall object axis is not in the middle of the sprite, remove the "*.5"
    from the two lines above and change all -xoff and -yoff to -collision_padding
    */
      
    if !chain_is_bent //If there is a bend, check wich corner of the wall the bend should be on
    {
        if check_chain_bend(collision_object.x+xoff,collision_object.y+yoff)
        {
        chain_bend_x = collision_object.x+xoff;
        chain_bend_y = collision_object.y+yoff;     
        }
        if check_chain_bend(collision_object.x-xoff,collision_object.y+yoff)
        {
        chain_bend_x = collision_object.x-xoff;
        chain_bend_y = collision_object.y+yoff;     
        }
        if check_chain_bend(collision_object.x+xoff,collision_object.y-yoff)
        {
        chain_bend_x = collision_object.x+xoff;
        chain_bend_y = collision_object.y-yoff;     
        }
        if check_chain_bend(collision_object.x-xoff,collision_object.y-yoff)
        {
        chain_bend_x = collision_object.x-xoff;
        chain_bend_y = collision_object.y-yoff;     
        }                     
    chain_is_bent = true;
    }
}
else
{
chain_is_bent = false;
}



//this is just for movement you should already have something better
x+= keyboard_check(vk_right)*movement_speed;
x-= keyboard_check(vk_left)*movement_speed;
y+= keyboard_check(vk_down)*movement_speed;
y-= keyboard_check(vk_up)*movement_speed;
PLAYER OBJECT DRAW:
Code:
if chain_is_bent
{
draw_cool_line(x,y,chain_bend_x,chain_bend_y,chain_sprite,5);
draw_cool_line(chain_bend_x,chain_bend_y,chained_to.x,chained_to.y,chain_sprite,5);
}
else
{
draw_cool_line(x,y,chained_to.x,chained_to.y,chain_sprite,5);
}
SCRIPT: draw_cool_line (don't forget to change the script's name)
Code:
///draw_cool_line(x1,y1,x2,y2,sprite,distance)

var x1 = argument0;
var y1 = argument1;
var x2 = argument2;
var y2 = argument3;

var sprite = argument4;
var distance = argument5;

var dir = point_direction(x1,y1,x2,y2);
var dist = point_distance(x1,y1,x2,y2);

var ii = 0;
repeat(floor(point_distance(x1,y1,x2,y2)/distance))
{
draw_sprite_ext(sprite,ii,x1+(dcos(dir)*(ii*distance)),y1-(dsin(dir)*(ii*distance)),1,1,dir,c_white,1)
ii+=1;
}
SCRIPT: check_chain_bend
Code:
///check_chain_bend(x,y)
var xx = argument0;
var yy = argument1;

return !collision_line(x,y,xx,yy,block_object,2,false) and !collision_line(xx,yy,chained_to.x,chained_to.y,block_object,2,false);
BTW: that trick that I mentioned didn't turn out to work as well as I thought
 
D

DyingSilence

Guest
Okay, here's what I have, the more I play with the more I realize that only one bend point will probably not be enough: (I think it's worth noting that this is from 1.4)
But if you like it I can go back into it and add multiple bend points.

PLAYER OBJECT CREATE:
Code:
chained_to = ball_object; //the ball object that the player is chained to
chain_length = 128; //how long the chain should be
movement_speed = 4; //how fast the player should currently move
max_movement_speed = 4; //how fast the player moves without being held back by the chain
chain_movement_speed = 1; //how fast the player moves when being held back by the chain

collision_padding = 4; //How close to the wall the chain can get - must be at least 1

chain_is_bent = false; //If there is a bend in the chain
chain_bend_x = 0; //where the bend is
chain_bend_y = 0;
PLAYER OBJECT STEP:
Code:
var ball_distance = point_distance(x,y,chained_to.x,chained_to.y); //Distance between the player and the ball
var ball_collision_distance = point_distance(chain_bend_x,chain_bend_y,chained_to.x,chained_to.y); //Distance between the ball and the chain bend
var player_collision_distance = point_distance(chain_bend_x,chain_bend_y,x,y); //Distance between the bend and the player

if chain_is_bent //If the chain is bent calculate the chain distance
{
    ball_distance = ball_collision_distance + player_collision_distance;
}

if position_meeting(chained_to.x, chained_to.y, block_object) //If the ball is stuck on the wall just go straight towards the player
{
chain_bend_x = player_object.x;
chain_bend_y = player_object.y;
}

if ball_distance > chain_length //If the player is too far
{
    movement_speed = lerp(movement_speed,chain_movement_speed,.2); //Change the movement speed to the slow speed
    //I use lerp to keep the movement from looking jumpy
 
    if chain_is_bent //If the chain is bent move the ball towards the bend
    {
    chained_to.x = lerp(chained_to.x,chain_bend_x,.02*(ball_distance/ball_collision_distance));
    chained_to.y = lerp(chained_to.y,chain_bend_y,.02*(ball_distance/ball_collision_distance));
    }
    else //If the chain isn't bent move the ball toward the player
    {
    chained_to.x = lerp(chained_to.x,player_object.x,.02);
    chained_to.y = lerp(chained_to.y,player_object.y,.02);
    }
}
else //If the player is not too far away set the player's speed to the max speed
{
movement_speed = lerp(movement_speed,max_movement_speed,.2);
}

//Check the line to see if a bend should be added
collision_object = collision_line(x,y,chained_to.x,chained_to.y,block_object,2,false);

if collision_object != noone //If there' a collision
{
    var xoff = (collision_object.sprite_width*.5)+collision_padding; //this assumes the axis is in the middle of the object
    var yoff = (collision_object.sprite_height*.5)+collision_padding; //this assumes the axis is in the middle of the object

    /*
    If the wall object axis is not in the middle of the sprite, remove the "*.5"
    from the two lines above and change all -xoff and -yoff to -collision_padding
    */
     
    if !chain_is_bent //If there is a bend, check wich corner of the wall the bend should be on
    {
        if check_chain_bend(collision_object.x+xoff,collision_object.y+yoff)
        {
        chain_bend_x = collision_object.x+xoff;
        chain_bend_y = collision_object.y+yoff;    
        }
        if check_chain_bend(collision_object.x-xoff,collision_object.y+yoff)
        {
        chain_bend_x = collision_object.x-xoff;
        chain_bend_y = collision_object.y+yoff;    
        }
        if check_chain_bend(collision_object.x+xoff,collision_object.y-yoff)
        {
        chain_bend_x = collision_object.x+xoff;
        chain_bend_y = collision_object.y-yoff;    
        }
        if check_chain_bend(collision_object.x-xoff,collision_object.y-yoff)
        {
        chain_bend_x = collision_object.x-xoff;
        chain_bend_y = collision_object.y-yoff;    
        }                    
    chain_is_bent = true;
    }
}
else
{
chain_is_bent = false;
}



//this is just for movement you should already have something better
x+= keyboard_check(vk_right)*movement_speed;
x-= keyboard_check(vk_left)*movement_speed;
y+= keyboard_check(vk_down)*movement_speed;
y-= keyboard_check(vk_up)*movement_speed;
PLAYER OBJECT DRAW:
Code:
if chain_is_bent
{
draw_cool_line(x,y,chain_bend_x,chain_bend_y,chain_sprite,5);
draw_cool_line(chain_bend_x,chain_bend_y,chained_to.x,chained_to.y,chain_sprite,5);
}
else
{
draw_cool_line(x,y,chained_to.x,chained_to.y,chain_sprite,5);
}
SCRIPT: draw_cool_line (don't forget to change the script's name)
Code:
///draw_cool_line(x1,y1,x2,y2,sprite,distance)

var x1 = argument0;
var y1 = argument1;
var x2 = argument2;
var y2 = argument3;

var sprite = argument4;
var distance = argument5;

var dir = point_direction(x1,y1,x2,y2);
var dist = point_distance(x1,y1,x2,y2);

var ii = 0;
repeat(floor(point_distance(x1,y1,x2,y2)/distance))
{
draw_sprite_ext(sprite,ii,x1+(dcos(dir)*(ii*distance)),y1-(dsin(dir)*(ii*distance)),1,1,dir,c_white,1)
ii+=1;
}
SCRIPT: check_chain_bend
Code:
///check_chain_bend(x,y)
var xx = argument0;
var yy = argument1;

return !collision_line(x,y,xx,yy,block_object,2,false) and !collision_line(xx,yy,chained_to.x,chained_to.y,block_object,2,false);
BTW: that trick that I mentioned didn't turn out to work as well as I thought
Thank you much!

After hours of adjustments this worked fairly well, I only don't really get the part with xoff and yoff and it seems that it breaks my collision in strange ways.

010.png

The thing looks like this for now.
 
D

DyingSilence

Guest
My adapted code:

CHECK CHAIN BEND SCRIPT
Code:
///check_chain_bend(x,y)
var xx = argument0;
var yy = argument1;

return !collision_line(x,y,xx,yy,obj_solid_parent,2,false) and !collision_line(xx,yy,chained_to.x,chained_to.y,obj_solid_parent,2,false);
PART OF PLAYER'S CREATE
Code:
///ball and chain init

chained_to = noone; //the ball object that the player is chained to
chain_length = 24; //how long the chain should be
chain_movement_speed = 1; //how fast the player moves when being held back by the chain
max_movement_speed = 4; //how fast the player moves without being held back by the chain

chain_collision_padding = 1; //How close to the wall the chain can get - must be at least 1

chain_is_bent = false; //If there is a bend in the chain
chain_bend_x = 0; //where the bend is
chain_bend_y = 0;
PART OF PLAYER'S STEP
Code:
/// chain code

var ball_distance = point_distance(x,y,chained_to.x,chained_to.y); //Distance between the player and the ball
var ball_collision_distance = point_distance(chain_bend_x,chain_bend_y,chained_to.x,chained_to.y); //Distance between the ball and the chain bend
var player_collision_distance = point_distance(chain_bend_x,chain_bend_y,x,y); //Distance between the bend and the player

if chain_is_bent //If the chain is bent calculate the chain distance
{
    ball_distance = ball_collision_distance + player_collision_distance;
}

if position_meeting(chained_to.x, chained_to.y, obj_solid_parent) //If the ball is stuck on the wall just go straight towards the player
{
    chain_bend_x = obj_player.x;
    chain_bend_y = obj_player.y;
}

if ball_distance > chain_length //If the player is too far
{
    final_speed = lerp(final_speed,chain_movement_speed,.2); //Change the movement speed to the slow speed
    //I use lerp to keep the movement from looking jumpy
 
    if chain_is_bent //If the chain is bent move the ball towards the bend
    {
    chained_to.x = lerp(chained_to.x,chain_bend_x,.1*(ball_distance/ball_collision_distance));
    chained_to.y = lerp(chained_to.y,chain_bend_y,.1*(ball_distance/ball_collision_distance));
    }
    else //If the chain isn't bent move the ball toward the player
    {
    chained_to.x = lerp(chained_to.x,obj_player.x,.06);
    chained_to.y = lerp(chained_to.y,obj_player.y,.06);
    }
}
else //If the player is not too far away set the player's speed to the max speed
{
final_speed = lerp(final_speed,movespeed,.1);
}

//Check the line to see if a bend should be added
collision_object = collision_line(x,y,chained_to.x,chained_to.y,obj_solid_parent,2,false);

if collision_object != noone //If there' a collision
{
    var xoff = (collision_object.sprite_width*.5)+chain_collision_padding; //this assumes the axis is in the middle of the object
    var yoff = (collision_object.sprite_height*.5)+chain_collision_padding; //this assumes the axis is in the middle of the object

    /*
    If the wall object axis is not in the middle of the sprite, remove the "*.5"
    from the two lines above and change all -xoff and -yoff to -chain_collision_padding
    */
      
    if !chain_is_bent //If there is a bend, check wich corner of the wall the bend should be on
    {
        if check_chain_bend(collision_object.x+xoff,collision_object.y+yoff)
        {
        chain_bend_x = collision_object.x+xoff;
        chain_bend_y = collision_object.y+yoff;     
        }
        if check_chain_bend(collision_object.x-xoff,collision_object.y+yoff)
        {
        chain_bend_x = collision_object.x-xoff;
        chain_bend_y = collision_object.y+yoff;     
        }
        if check_chain_bend(collision_object.x+xoff,collision_object.y-yoff)
        {
        chain_bend_x = collision_object.x+xoff;
        chain_bend_y = collision_object.y-yoff;     
        }
        if check_chain_bend(collision_object.x-xoff,collision_object.y-yoff)
        {
        chain_bend_x = collision_object.x-xoff;
        chain_bend_y = collision_object.y-yoff;     
        }                     
    chain_is_bent = true;
    }
}
else
{
chain_is_bent = false;
}
PART OF PLAYER'S DRAW
Code:
///draw chain

if(chained_to != noone)
{
    if(depth > chained_to.depth)
    {
        if (chain_is_bent)
        {
            draw_chain(x,y,chain_bend_x,chain_bend_y,spr_chain,4);
            draw_chain(chain_bend_x,chain_bend_y,chained_to.x,chained_to.y,spr_chain,4);
        }
        else
        {
            draw_chain(x,y,chained_to.x,chained_to.y,spr_chain,4);
        }
    }
    else
    {
        with(chained_to)
        {
            if (obj_player.chain_is_bent)
            {
                draw_chain(obj_player.x,obj_player.y,obj_player.chain_bend_x,obj_player.chain_bend_y,spr_chain,4);
                draw_chain(obj_player.chain_bend_x,obj_player.chain_bend_y,x,y,spr_chain,4);
            }
            else
            {
                draw_chain(obj_player.x,obj_player.y,x,y,spr_chain,4);
            }
        }
    }
}

My wall sprite is 16x24, has an origin at (7,23) which is middle bottom and it's mask is a 15x15 rectangle at bottom left of the sprite. I'd be very grateful if you could adjust your code to work well with that.
 
D

DyingSilence

Guest
Also, yes, I've noticed your instructions on what to do in such case, but there's so much variation on what could it mean, they aren't clear enough to me.
 
D

DyingSilence

Guest
Alright, I figured the thing out, it looks like this:


Code:
if collision_object != noone //If there' a collision
{
    var xoff = (collision_object.sprite_width*.5)+chain_collision_padding; //this assumes the axis is in the middle of the object
    var yoff = (collision_object.sprite_height*.32)+chain_collision_padding; //this assumes the axis is in the middle of the object

    /*
    If the wall object axis is not in the middle of the sprite, remove the "*.5"
    from the two lines above and change all -xoff and -yoff to -chain_collision_padding
    */
      
    if !chain_is_bent //If there is a bend, check wich corner of the wall the bend should be on
    {
        if check_chain_bend(collision_object.x+xoff,collision_object.y)
        {
        chain_bend_x = collision_object.x+xoff;
        chain_bend_y = collision_object.y;     
        }
         if check_chain_bend(collision_object.x-xoff,collision_object.y)
        {
        chain_bend_x = collision_object.x-xoff;
        chain_bend_y = collision_object.y;     
        }
         if check_chain_bend(collision_object.x+xoff,collision_object.y-2*yoff)
        {
        chain_bend_x = collision_object.x+xoff;
        chain_bend_y = collision_object.y-2*yoff;     
        }
         if check_chain_bend(collision_object.x-xoff,collision_object.y-2*yoff)
        {
        chain_bend_x = collision_object.x-xoff;
        chain_bend_y = collision_object.y-2*yoff;     
        }                     
    chain_is_bent = true;
    }
}
else
{
chain_is_bent = false;
}
I can't thank you enough for your time and effort!
 
Top