2
\$\begingroup\$

I have a 2D top-down RPG type game, with movement in 8 directions (N/E/S/W/NE/NW/SE/SW) involving both x and y velocity.

I'm finding when I'm moving in the diagonal directions, I'm building both x and y velocity and it's making my character move faster than they do in a straight line along only x or only y. Is there a fix for this, so no matter which of the 8 directions I travel along, it's the same speed?

My code works like this. I initialize my variables and speed:

vel_x, vel_y=0  
spd=1

Then each frame in my game loop, I apply drag to reduce the velocity:

vel_x*=0.2  
vel_y*=0.2

Then my button presses add/subtract the spd value from their corresponding velocity direction x/y when pressed.

Lastly, I add the vel_x to my sprite's x position and same with vel_y for my sprite's y position.

How would I address this? Would I look at button press actions for when both keys are held, or limits for when vel_x and vel_y are 'high' enough in tandem?

I've done the latter and it helps, but I can't make it accurate.

Images: With velocity removed and just +-1 speed increases/decreases. Left is the proposed adjustment to the vertical movement. Right is no adjustment. Is left moving at same speed all the time and right isn't?

With adjustment No adjustment

\$\endgroup\$
1
  • \$\begingroup\$ Normalize the direction, then multiply by speed. \$\endgroup\$ Commented Aug 7, 2023 at 2:10

3 Answers 3

3
\$\begingroup\$

Is there a fix for this so no matter what of the 8 I travel its the same speed?

Yes... You want to apply a total force and split it between x and y

To do this for any angle, you apply ± totalForce * sin(angle) in one direction, and ± totalForce * cos(angle) in the other.

Your case is simpler as the angle is fixed at 45 degrees when moving diagonally, and conveniently both sin(45) and cos(45) are the same ... 0.70710678118 (which happens to be 1/sqrt(2))

So multiply your X and Y forces by 0.70710678118 when moving diagonally, and the total will be the same as when moving in a cardinal direction.

Even better... If you use the formulas above instead of the fixed value, it works for any direction including N/E/S/W.

You can get the angle from the player's current rotation around the Y axis.

How would I address this...?

If I've understood correctly and you're concerned about how forces add up over time...

Usually, you apply some form of damping so that movement decreases over time if there's no input.

You can do this a number of ways, but the simplest is probably to calculate the current speed (eg by checking how far the sprite had travelled in the timespan since the last frame) then multiply that speed by some number just below 1 (say 0.95) and use that as your speed for the next frame.

That way, when you're applying a force, the sprite accelerates and when you let go, it slows down and eventually comes to a stop.

If you can't find a value that feels "just right", there's a lot of research on different ways to damp... Wikipedia's article on Damping might be a good starting point to get some search terms.

Edit:

Is this equivalent to me irl walking straight 1m/s then turning diag and walking 1ms?

Yes

Think in terms of a 2D grid. We'll draw a triangle with the base being how far we travel in X and the side being how far we travel in y.

The hypotenuse is the resulting motion.

Moving Straight up?

  • x stays at zero
  • y increments by 1

No need to do any fancy calculations as that's obviously 1 unit.

Now let's move diagonally NE...

  • x increases by 0.70710678118
  • y increases by 0.70710678118

How long is the hypotenuse? Well, from Pythagoras, it should be

sqrt(x ^ 2 + y ^ 2)
or
sqrt(0.70710678118 ^ 2 + 0.70710678118 ^ 2)

(Remember I said 0.70710678118 is equivalent to 1/sqrt(2)? This is one reason that's helpful to know ...).

1 / sqrt(2) ^ 2 simplifies to 1/2 or 0.5

So we get

sqrt(0.5 + 0.5)
or
sqrt(1)

Which is -of course- 1

So yes, it's moving at the same speed of 1 unit/s.

\$\endgroup\$
7
  • \$\begingroup\$ If we remove velocity for a sec, and just look at the case of speed and +-1 increases across x/y. Is the above gif rly correct? Here when two keys r hit the speed changes before being added. Am I really moving as fast diagonally as I am when going in a straight line? Is that mimicking irl where I just turn and move at the same direction as before? It just doesn't look right to me. The above is 128x128 pixels so with decimals the animation is gonna be jagged, but overall speed still doesn't seem...right? Is this equivalent to me irl walking straight 1m/s then turning diag and walking 1ms? \$\endgroup\$
    – kite
    Commented Mar 19, 2021 at 2:59
  • \$\begingroup\$ mean the principles should apply the same whether i'm using just basic speed increases or velocity right? \$\endgroup\$
    – kite
    Commented Mar 19, 2021 at 3:12
  • \$\begingroup\$ Doesn't matter whether it's speed, force, acceleration, friction on a slope, whatever... You're taking one direction (vector) and splitting out its x component and its y component. Adding them back together would give the original vector.. I'll add something about checking your numbers \$\endgroup\$
    – Basic
    Commented Mar 19, 2021 at 8:20
  • \$\begingroup\$ So if my origin is (0,0) and I have a total distance of 1 unit which I can travel. At angle 0 I can move to (1,0) which is (cos(0),sin(0)) cause its the same as the unit triangle and sin represents the y change, cos the x. At angle 90 I can move to (0,1) which is (cos(90),sin(90). At angle 45 I can move to (?,?) which is (cos(45),sin(45)) which is (0.701...,0.701..). Okay. The pure math and the 'seeing it in action' parts didn't really connect I guess was the problem. \$\endgroup\$
    – kite
    Commented Mar 19, 2021 at 21:11
  • \$\begingroup\$ I tried implementing the new speed formula by creating the above scenario. Problem was how do I tell if I'm moving diagonally. So I made an origin point 0,0 and the new point after one run of the gameloop which wud be a generic (0,1) type or diagonal (1,1) type if my spd=1. Then I could say if the angle of the triangle was>0 then do the correction. I had to repeat keybindings twice to make this work, and it was super messy. So I settled for checking if two buttons were pressed at same time (up and left etc) and then correcting speed to 1 or 0.701... for that run of the gameloop. \$\endgroup\$
    – kite
    Commented Mar 19, 2021 at 21:21
3
\$\begingroup\$

You use trigonometry. Here we can just use the Pythagorean theorem:

a² + b² = c²

If the player is moving at a 45° angle, a and b are the same, so we can simplify to

2a² = c²
a² = c²/2
a = sqrt(c²/2)

Let's say that your desired movement speed is 10.

2a² = 10²
a² = 100 / 2
a = sqrt(50)
a = 7.071

So if you want the player to move diagonally at a total speed of 10 units/second, move them 7.071 units along X and 7.071 units along Y.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ And as Basic noted in his answer that he apparently submitted while I was still typing, you can use the .7071[...] constant and skip the rest of the math. If you do want to include a formula, using sin and cos is cleaner and possibly better for performance than the Pythagorean theorem, so his answer is better than mine. \$\endgroup\$
    – Kevin
    Commented Mar 19, 2021 at 1:41
-1
\$\begingroup\$

Think intuitively first.

orthogonal speed is a 2D Vector with an X and Y component.

if that Vector was (1,1). Then that makes the multiplier for x and y both 1. That means that diagonally, you're still going to be traversing the grid at 1 /ms in both directions.

Anytime we accelerate / decelerate we modify the speed vector.

the speed vector gets multiplied by the amount of time that has elapsed since the last frame in this case.

You then simply add / subtract the position vector.

\$\endgroup\$
0

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .