GameMaker 2D vectors with acceleration, friction and top speed

F

Freddie The Potato

Guest
Heya!
I am struggling to find a solution to a problem that has plagued me for all eternity!

Imagine a 2D vector holding horizontal and vertical inputs:
Code:
xinput = keyboard_check(vk_right) - keyboard_check(vk_left);
yinput = keyboard_check(vk_down) - keyboard_check(vk_up);
Implementing player movement afterwards should be easy right?
Code:
spd = 4;

xspd = xinput * spd;
yspd = yinput * spd;
Unfortunately we encounter a problem wherein if both xinput and yinput have non zero values, then the player's actual speed exceeds our desired top speed.



Fear not, for I have a solution! We can normalize the input vector to create a directional vector, then multiply its components by our speed value to get a velocity vector with a consistent length. What's more is that this solution also works with input methods that have more than 8 directions, such as gamepads.
Code:
//Normalize the input vector to create a directional vector
len = sqrt(sqr(xinput) + sqr(yinput));
xinput /= len;
yinput /= len;

//Multiply the directional components by speed to get true xspd and yspd
spd = 4;

xspd = spd * xinput;
yspd = spd * yinpit;


But alas, we have another problem. I am unsure how to implement acceleration, friction and top speed into this approach. If the player is holding inputs, then they should accelerate towards the top speed in the specified direction, then decelerate to a stop when the player is not holding inputs.
xspd and yspd should not accelerate, decelerate or be clamped independently of each other, as this creates the janky and incorrect movement as seen in the first example above. I think speed and acceleration should be treated as 2D vectors.

Any help would be greatly appreciated. Thank you :)
 

TheSnidr

Heavy metal viking dentist
GMC Elder
What you're doing, finding a parallel vector of length one, is called vector normalization, and as you've found out, it's very useful :D

Acceleration is actually pretty simple - you can just add the vector to the object's speed variable like so:
Code:
len = max(sqrt(sqr(xinput) + sqr(yinput)), 1); //Need to use max() to avoid division by 0
xinput /= len;
yinput /= len;

//Acceleration factor
acc = 4;

//Accelerate
xspd += acc * xinput;
yspd += acc * yinput;
Now, to limit the speed, you need to limit the length of the speed vector. You can do this by dividing the vector by its own length, and multiplying by the max speed:
Code:
spd = sqrt(sqr(xspd) + sqr(yspd));
maxSpd = 15;
if (spd > maxSpd)
{
    var limiter = maxSpd / spd;
    xspd *= limiter;
    yspd *= limiter;
}
Also, as a side note, I'm more a fan of what's called "verlet integration" than keeping the actual speed variables around. In this approach, you only store the object's previous coordinates from one step to another, and find the speed by subtracting the previous coords from the new ones. Like this:
Code:
///////////////////
//Create event
prevX = x;
prevY = y;

///////////////////
//Step event
//Find speed
spdX = x - prevX;
spdY = y - prevY;

//Store previous coordinates
prevX = x;
prevY = y;

//Multiplicative friction, which is physically incorrect, but looks good IMO
var frc = 0.9;
spdX *= frc;
spdY *= frc;

//Accelerate
var acc = 4;
spdX += acc * xinput;
spdY += acc * yinput;

//Apply speed to position
x += spdX;
y += spdY;
 

NightFrost

Member
You treat velocity change as a vector too. Your ship has acceleration / deceleration rate, which gives vector length, and direction is either where the ship points or the opposite direction. You turn this into an x/y vector, add to velocity, and clamp the resulting vector's length to max velocity. When decelerating, you just have to make sure the vector is no longer than current velocity (clamp length to current velocity).

EDIT - Ninjas abound!
 

NightFrost

Member
Also, it should be noted that GMS has vectors on the pony list, but before that happens you should do yourself a favor and turn the common vector operations into a bunch of scripts. Use two-slot arrays as your vector variables.
 
F

Freddie The Potato

Guest
@TheSnidr
Thanks for the response! It was very helpful :)

@NightFrost
Thanks for the tip! I've used arrays to represent vectors in the past, but I wasn't sure if it was considered good practice in GMS or not.
 

TheSnidr

Heavy metal viking dentist
GMC Elder
Arrays for vectors is not a good practice if you can avoid it. Declaring arrays on the fly is actually ridiculously slow.
 

NightFrost

Member
Incidentally, I went to check what the current status of vector support is, and they're no longer mentioned on the roadmaps at all. That doesn't sound too encouraging...
 

GMWolf

aka fel666
Thanks for the tip! I've used arrays to represent vectors in the past, but I wasn't sure if it was considered good practice in GMS or not.
It's ok but very, very slow.
You could have a setup where you have to pass in an array to fill the result into to reduce array creation but that is quite unwieldy.

If you have to return vectors from functions that may be the best solution I'm afraid.
Another option is to have return_x and return_y instance variables... But that is decidedly not nice.


You could also have a global array that is used just for multiple return values. That's not too bad.
 
Top