SOLVED Creating a collision system with a Virtual Joystick

wimaxou

Member
Hi!
I've just recently started out with GameMaker and was wondering about collisions...
There are many tutorials on typical x and y collisions, but I am looking to implement these with a virtual joystick, and I cannot seem to find any help online.
It would also be nice to have smooth collisions as I am planning on making a top-down game and would like to get the mechanics going before anything else.
If you think you can help, please do take a look at the code I have below for the virtual joystick, and possibly help me if you can :)

Many thanks!

To clarify: joystickX and Y are the bottom of the joystick whereas joystickControllerX and Y are the bit of the joystick which you actually move.


--CREATE EVENT--

Code:
joystickX = device_mouse_x(0); // joystick = mouse position
joystickY = device_mouse_y(0);

joystickControllerX = joystickX; // controller position = joystick outline position
joystickControllerY = joystickY;

joystickRadius = sprite_get_width(sJoystickOutline) / 2;
--STEP EVENT--
Code:
#region Variables

//-----------------------------------------------------------------------------------------
// Check if left button is clicked / screen is tapped. (global bc is used elsewhere)

global.leftClick = device_mouse_check_button(0, mb_left);

//Find position of mouse / finger
var fingerX = device_mouse_x(0);
var fingerY = device_mouse_y(0);

//Find distance from centre of joystickOutline to finger position
//and find which direction the finger is going
distToJoystickOutline = point_distance(fingerX, fingerY, joystickX, joystickY);
directionOfFinger = point_direction(fingerX, fingerY, joystickX, joystickY) + 180;

#endregion

//-----------------------------------------------------------------------------------------
//Set player direction and speed, and defines the maximum position of the joystickController

if (global.leftClick) {

    //if finger position is within outline, set controller position to finger position
       if (distToJoystickOutline < joystickRadius) {
           joystickControllerX = fingerX;
           joystickControllerY  = fingerY;
    
        //set player speed depending on position of finger to joystick centre
           oPlayer.move = 1;
           oPlayer.speed = (distToJoystickOutline * oPlayer.spd) / 75;
           oPlayer.direction = directionOfFinger;
    }

 

    //if finger position is outside joystick outline, set joystick controller position to joystick outline.
    if (distToJoystickOutline > joystickRadius) {
           joystickControllerX = joystickX + lengthdir_x(joystickRadius, directionOfFinger);
           joystickControllerY = joystickY + lengthdir_y(joystickRadius, directionOfFinger);

           oPlayer.move = 1;
           oPlayer.speed = oPlayer.spd;
           oPlayer.direction = directionOfFinger;
       }
}

//return joystick to position
else {

       oPlayer.move = 0;

       joystickX = mouse_x;
       joystickY = mouse_y;
       joystickControllerX = joystickX;
       joystickControllerY = joystickY;

}
 
Last edited:
So, to get "smooth collision" I think you would need to separate your move vector into its X and Y components and perform movement/collisions off of those as opposed to trying to simply move in a given direction at a given speed.

I would also highly recommend not having your Joystick object handle the player movement. It would make more sense for the player to be in charge of its own movement, based on input parameters.

So, to control input I'd add the following vars to the Create event of oJoystick:

GML:
...

// the offset of the joystick against the joystick outline
deltaX = 0;
deltaY = 0;
Update the Step event of your oJoystick:

GML:
#region Variables

//-----------------------------------------------------------------------------------------
// Check if left button is clicked / screen is tapped. (global bc is used elsewhere)

global.leftClick = device_mouse_check_button(0, mb_left);

//Find position of mouse / finger
var fingerX = device_mouse_x(0);
var fingerY = device_mouse_y(0);

//Find distance from centre of joystickOutline to finger position
//and find which direction the finger is going
distToJoystickOutline = point_distance(fingerX, fingerY, joystickX, joystickY);
directionOfFinger = point_direction(fingerX, fingerY, joystickX, joystickY) + 180;

#endregion

//-----------------------------------------------------------------------------------------
//Set player direction and speed, and defines the maximum position of the joystickController

if (global.leftClick) {
   
    joystickControllerX = fingerX;
    joystickControllerY = fingerY;
   
    // if the finger position is outside the outline, set it to the outline
    if (distToJoystickOutline > joystickRadius) {
        joystickControllerX = joystickX + lengthdir_x(joystickRadius, directionOfFinger);
        joystickControllerY = joystickY + lengthdir_y(joystickRadius, directionOfFinger);
    }

    // if the joystick has been moved outside the center
    var deadzone = 0;
    if (abs(distToJoystickOutline) > deadzone) {
       
        deltaX = joystickControllerX - joystickX;
        deltaY = joystickControllerY - joystickY;
       
    }
   
}

//return joystick to position
else {
       
    joystickX = mouse_x;
    joystickY = mouse_y;
    joystickControllerX = joystickX;
    joystickControllerY = joystickY;
   
    deltaX = 0;
    deltaY = 0;

}
Now you can use the deltaX and deltaY values from your oJoystick to have oPlayer handle its movement.

oPlayer Create event:

GML:
moving = false;
spd = 5;

xSpeed = 0;
ySpeed = 0;
oPlayer Step event:

GML:
// set xSpeed/ySpeed based on input from oJoystick
xSpeed = oJoystick.deltaX * spd / 75;
ySpeed = oJoystick.deltaY * spd / 75;

// fancy way of saying if (xSpeed != 0 || ySpeed != 0) moving = true; else moving = false;
moving = xSpeed != 0 || ySpeed != 0 ? true : false;

// horizontal movement
if (xSpeed != 0) {
   
    if (place_free(x + xSpeed, y)) {
        x += xSpeed;  
    }
   
}

// vertical movement
if (ySpeed != 0) {
   
    if (place_free(x, y + ySpeed)) {
        y += ySpeed;
    }
   
}
 
Last edited:

wimaxou

Member
So, to get "smooth collision" I think you would need to separate your move vector into its X and Y components and perform movement/collisions off of those as opposed to trying to simply move in a given direction at a given speed.

I would also highly recommend not having your Joystick object handle the player movement. It would make more sense for the player to be in charge of its own movement, based on input parameters.

So, to control input I'd add the following vars to the Create event of oJoystick:

GML:
...

// the offset of the joystick against the joystick outline
deltaX = 0;
deltaY = 0;
Update the Step event of your oJoystick:

GML:
#region Variables

//-----------------------------------------------------------------------------------------
// Check if left button is clicked / screen is tapped. (global bc is used elsewhere)

global.leftClick = device_mouse_check_button(0, mb_left);

//Find position of mouse / finger
var fingerX = device_mouse_x(0);
var fingerY = device_mouse_y(0);

//Find distance from centre of joystickOutline to finger position
//and find which direction the finger is going
distToJoystickOutline = point_distance(fingerX, fingerY, joystickX, joystickY);
directionOfFinger = point_direction(fingerX, fingerY, joystickX, joystickY) + 180;

#endregion

//-----------------------------------------------------------------------------------------
//Set player direction and speed, and defines the maximum position of the joystickController

if (global.leftClick) {
  
    joystickControllerX = fingerX;
    joystickControllerY = fingerY;
  
    // if the finger position is outside the outline, set it to the outline
    if (distToJoystickOutline > joystickRadius) {
        joystickControllerX = joystickX + lengthdir_x(joystickRadius, directionOfFinger);
        joystickControllerY = joystickY + lengthdir_y(joystickRadius, directionOfFinger);
    }

    // if the joystick has been moved outside the center
    var deadzone = 0;
    if (abs(distToJoystickOutline) > deadzone) {
      
        deltaX = joystickControllerX - joystickX;
        deltaY = joystickControllerY - joystickY;
      
    }
  
}

//return joystick to position
else {
      
    joystickX = mouse_x;
    joystickY = mouse_y;
    joystickControllerX = joystickX;
    joystickControllerY = joystickY;
  
    deltaX = 0;
    deltaY = 0;

}
Now you can use the deltaX and deltaY values from your oJoystick to have oPlayer handle its movement.

oPlayer Create event:

GML:
moving = false;
spd = 5;

xSpeed = 0;
ySpeed = 0;
oPlayer Step event:

GML:
// set xSpeed/ySpeed based on input from oJoystick
xSpeed = oJoystick.deltaX * spd / 75;
ySpeed = oJoystick.deltaY * spd / 75;

// fancy way of saying if (xSpeed != 0 || ySpeed != 0) moving = true; else moving = false;
moving = xSpeed != 0 || ySpeed != 0 ? true : false;

// horizontal movement
if (xSpeed != 0) {
  
    if (place_free(x + xSpeed, y)) {
        x += xSpeed; 
    }
  
}

// vertical movement
if (ySpeed != 0) {
  
    if (place_free(x, y + ySpeed)) {
        y += ySpeed;
    }
  
}
Wow... Thank you so much, I've been trying to work this out for too long haha :)
I've tried implementing the code and the collision is very smooth indeed, I couldn't thank you enough!

There is one thing though, the player seems to be colliding around the mask correctly, but the collision mask is offset to the left and down of the drawn sprite as illustrated in the image below.

Untitled.png

I've checked the collision mask (which is centred and set to automatic & precise), and I've even tried creating a new sprite but the problem stays the same.
The object is also using the same collision mask as the sprite.

I've tried messing with the code a bit, but am not too sure where the issue could be happening.
If you have any idea...?

Getting back to your code, I have a question about this specific section:
GML:
xSpeed = oJoystick.deltaX * spd / 75;
ySpeed = oJoystick.deltaY * spd / 75;
Why is the speed divided by 75 specifically? Just wondering as I am trying to get a better understanding.

Anyway, thanks for your help, you're amazing!
 
I've checked the collision mask (which is centred and set to automatic & precise), and I've even tried creating a new sprite but the problem stays the same.
The object is also using the same collision mask as the sprite.
That's definitely strange and I'm not able to replicate it on my end. Have you double checked the player's collision mask is also set up correctly? Are you doing anything in the draw event for the player or collision object?

Why is the speed divided by 75 specifically? Just wondering as I am trying to get a better understanding.
I only used "spd / 75" because that's what you had used previously and I figured you had tuned your movement to feel good around that speed.

As is, deltaX and deltaY are going to give you fairly large values depending on the size of your joystick outline sprite, so the division is basically there to offset that, otherwise you'd need a very small value for your player's "spd." If anything I'd actually recommend against using a constant number there and using a variable instead.

An arguably better solution might be to calculate your speed using delta_time so that your movement speed is consistent regardless of what framerate your game is running at (really handy for people like me who like to game on 144 Hz monitors).

You could normalize the deltaX/deltaY values, and update your speed calculations to be "deltaX/Y * spd * delta_time * 0.00001" and this would make your player's max move speed = spd * pixels/second.

Updated oJoystick Step Event

GML:
...

// if the joystick has been moved outside the center
var deadzone = 0;
if (abs(distToJoystickOutline) > deadzone) {

    deltaX = (joystickControllerX - joystickX);
    deltaY = (joystickControllerY - joystickY);

    // Normalize deltaX/deltaY so their values are always between -1 & 1
    var mag = sqrt(sqr(deltaX) + sqr(deltaY));
    deltaX /= mag;
    deltaY /= mag;

}

...
Updated oPlayer Step Event

GML:
// set xSpeed/ySpeed based on input from oJoystick
xSpeed = oJoystick.deltaX * spd * delta_time * 0.00001;
ySpeed = oJoystick.deltaY * spd * delta_time * 0.00001;
 
Last edited:

wimaxou

Member
Thank you for your insight, very helpful.

Have you double checked the player's collision mask is also set up correctly?
I have solved the collision offset... as you said it was the player's collision object. *facepalm* for not checking before, thanks.

There are however a couple more things:
When I set the speed of my oPlayer with the code I had previously, I was able to get an incrementing speed as I moved the joystick controller further from the centre:
GML:
oPlayer.speed = (distToJoystickOutline * oPlayer.spd) / 75;
With your code, the movement seems to be 1 or 0; full speed or no speed. I would like it to increment as it used to without having to create many if statements for each joystick position. I.e.:
GML:
if (distToOutline > x || distToOutline < x) {
    spd = x;
}
I've also tried to multiply the speed by distToJoystickOutline then divide it by 75:
GML:
// horizontal movement
if (xSpeed != 0) {

    if (place_free(x + xSpeed, y)) {
        x += xSpeed * oJoystick.distToJoystickOutline / 75;
    }

}

// vertical movement
if (ySpeed != 0) {

    if (place_free(x, y + ySpeed)) {
        y += ySpeed * oJoystick.distToJoystickOutline / 75;
    }

}
However, the problem with this is that once I collide with the collision object, the player movement becomes sticky and the smooth aspect of it disappears. This also means I have to divide by 75 which may be inconvenient when trying to change the speed values later.
If you are aware of a more efficient way of doing this, please let me know. :)


Lastly but not importantly, the collision is not pixel perfect as shown below:
Untitled.png
This seems to only happen at certain times though, sometimes it collides nicely and sometimes it leaves a gap in between... weird.
Now, for the game I will be making, it does not have to be, but just for my information, how would you go about doing that? I am sure I can find tutorials for this online, so if this takes too long, don't worry!
 
Last edited:
Right, sorry, I did not fully think this part through. By limiting the position of the joystickController to the bounds of the joystickOutline, all we really need to do to properly normalize your deltaX/deltaY are divide them by joystickRadius:

Updated oJoystick Step Event

GML:
...

// if the finger position is outside the outline, set it to the outline
if (distToJoystickOutline > joystickRadius) {
    joystickControllerX = joystickX + lengthdir_x(joystickRadius, directionOfFinger);
    joystickControllerY = joystickY + lengthdir_y(joystickRadius, directionOfFinger);
}

// if the joystick has been moved outside the center
var deadzone = 0;
if (abs(distToJoystickOutline) > deadzone) {

    deltaX = (joystickControllerX - joystickX) / joystickRadius;
    deltaY = (joystickControllerY - joystickY) / joystickRadius;

}

...
-

However, the problem with this is that once I collide with the collision object, the player movement becomes sticky and the smooth aspect of it disappears. This also means I have to divide by 75 which may be inconvenient when trying to change the speed values later.
The stickiness is likely being caused by how the collision masks are interacting. When you're dealing with non-straight boundaries like the ones on ellipses it possible to end up with your objects wedged together like so:

collision_example.PNG

If you hold down/right on the joystick, the player is going to hit a collision to the right and not move horizontally, but it will move down. On the next frame it will be free to move to right and down. On the next frame, it's going to be stuck on the X-axis again, and so on until the player clears the obstacle. I imagine this is what you're observing as sticky collisions.

You could do something like this to try and assist with situations like this, but I don't know that you'll ever fully be able to smooth out collision based on pixel masks:

GML:
if (place_free(x + xSpeed, y)) {
    x += xSpeed;   
} else if (ySpeed != 0 && place_free(x + xSpeed, y + ySpeed)) {
    x += xSpeed;
}
-

Lastly but not importantly, the collision is not pixel perfect as shown below:
That's going to happen if the player's xSpeed/ySpeed is greater than the distance between the player and the collision. If you want to close the remaining gap, you'll need to move pixel by pixel in the current direction until you do hit a collision:

GML:
    if (place_free(x + xSpeed, y)) {
        x += xSpeed;   
    } else {
        while (place_free(x + sign(xSpeed), y)) {
            x += sign(xSpeed);
        }
    }
 

wimaxou

Member
Right, sorry, I did not fully think this part through. By limiting the position of the joystickController to the bounds of the joystickOutline, all we really need to do to properly normalize your deltaX/deltaY are divide them by joystickRadius:

Updated oJoystick Step Event

GML:
...

// if the finger position is outside the outline, set it to the outline
if (distToJoystickOutline > joystickRadius) {
    joystickControllerX = joystickX + lengthdir_x(joystickRadius, directionOfFinger);
    joystickControllerY = joystickY + lengthdir_y(joystickRadius, directionOfFinger);
}

// if the joystick has been moved outside the center
var deadzone = 0;
if (abs(distToJoystickOutline) > deadzone) {

    deltaX = (joystickControllerX - joystickX) / joystickRadius;
    deltaY = (joystickControllerY - joystickY) / joystickRadius;

}

...
-



The stickiness is likely being caused by how the collision masks are interacting. When you're dealing with non-straight boundaries like the ones on ellipses it possible to end up with your objects wedged together like so:

View attachment 29677

If you hold down/right on the joystick, the player is going to hit a collision to the right and not move horizontally, but it will move down. On the next frame it will be free to move to right and down. On the next frame, it's going to be stuck on the X-axis again, and so on until the player clears the obstacle. I imagine this is what you're observing as sticky collisions.

You could do something like this to try and assist with situations like this, but I don't know that you'll ever fully be able to smooth out collision based on pixel masks:

GML:
if (place_free(x + xSpeed, y)) {
    x += xSpeed;  
} else if (ySpeed != 0 && place_free(x + xSpeed, y + ySpeed)) {
    x += xSpeed;
}
-



That's going to happen if the player's xSpeed/ySpeed is greater than the distance between the player and the collision. If you want to close the remaining gap, you'll need to move pixel by pixel in the current direction until you do hit a collision:

GML:
    if (place_free(x + xSpeed, y)) {
        x += xSpeed;  
    } else {
        while (place_free(x + sign(xSpeed), y)) {
            x += sign(xSpeed);
        }
    }
Thank you very much, this has given me everything I was looking for!!
I appreciate the time you put into this, couldn't thank you enough.
Stay awesome! :)
 
Top