Jump to content

Top-Down Strafing, Acceleration, and Max Speeds


Duncan
 Share

Recommended Posts

Hello, everyone.

This is my first post on the HTML 5 Game Devs forum. I'm currently working on a top-down shooter using Phaser, and this forum has been very helpful on this project. However, there is one problem to which I can't seem to find the solution.

In my game there are vessels, and the vessels have engines. The engines determine vertical and horizontal acceleration and maximum speed. Everything is working wonderfully, but I'd like to create sprites that aren't just facing straight up or down. In other words, I'd like to replace vertical and horizontal movement with forward and lateral movement. Here's what my moveUp function looks like right now:

this.body.velocity.y += verticalAcceleration;

And then I ensure we haven't exceeded the maximum vertical speed:

if (this.body.velocity.y > maxVerticalSpeed) {
  this.body.velocity.y = maxVerticalSpeed;
}

Now I know there are many examples showing how to manipulate the sprite's velocity based on its angle by doing something like this:

this.body.velocity.x = Math.cos(this.rotation) * maxSpeed;
this.body.velocity.y = Math.sin(this.rotation) * maxSpeed;

But none of the articles or posts I've seen deal with separate forward and lateral maximum velocities. I've tried approaching this problem from many angles (pun intended), but I can't seem to wrap my head around it. The sprites need to be able to move forward and backward without exceeding the forward speed limit and negative forward speed limit respectively, and they need to strafe left and right without exceeding the lateral speed limit. They also need to be able to move forward/backward and laterally at the same time.

I've spent far too long searching for examples, rewriting my code, etc., and now I'm ready to ask for help.

TL;DR: How can I accelerate a sprite forward, backward, left, or right while using different forward and lateral maximum speeds?

EDIT: I probably should have mentioned that I'm using Phaser to create this game.

Link to comment
Share on other sites

Nothing wrong with your code at the moment, you just need to consider that lateral movement is 90 degrees clockwise or counter-clockwise from your facing vector and apply extra forces.

Couple this with the fact that your new position is the sum of all the forces currently acting on your body and you'll work out a way to apply multiple forces to your body and have your position update.

Most physics libraries can help you out with this.

If you treat each force as a discrete thing acting on your body you can also apply damping to each force to help your body come to rest, this doesnt make much sense in space but it makes control far easier for humans and you can always say that the spaceship has 'inertial dampers' (or something more technical), or, if all else fails, just say a wizard did it.

 

Link to comment
Share on other sites

mattstyles,

Thanks for your reply. I think I understand what you're getting at regarding applying forces (or vectors, since my sprites don't have mass), but i have a few questions:

  • I know that Phaser's Arcade physics engine has methods like accelerationFromRotation and velocityFromRotation. Is this the kind of thing you're suggesting?
  • The Phaser Point class apparently functions as a 2D vector. Should I be treating the forces you mentioned as points and adding them together?
  • I still don't understand how to limit forward and lateral velocity independently. Sprites have a maxVelocity property, but this does not take direction into account.
  • Damping seems out of my league right now, and it looks like I would have to switch to Phaser's P2 physics engine to make use of its damping features.

If you could provide a simple example of adding forces together and limiting the velocities, that would be awesome!

Thanks!

EDIT: Here's an example of where I'm getting stuck:

var vector = new Phaser.Point();

vector.x = Math.cos(this.rotation - (Math.PI / 2)) * verticalAcceleration;
vector.y = Math.sin(this.rotation - (Math.PI / 2)) * verticalAcceleration;

this.body.velocity.add(vector.x, vector.y);

if (this.body.velocity.getMagnitude() > maxVerticalSpeed) {
  this.body.velocity.x = Math.cos(this.rotation - (Math.PI / 2)) * maxVerticalSpeed;
  this.body.velocity.y = Math.sin(this.rotation - (Math.PI / 2)) * maxVerticalSpeed;
}

This code works fine if I'm simply moving forward, but it isn't limiting my forward velocity. It's actually limiting my total velocity (just like the velocityFromRotation method).

I feel like I need to somehow divide the current velocity of the sprite into its forward and lateral velocities, then limit them individually, but I have tried that numerous times and keep running into problems. Where do I go from here?

EDIT: If you're wondering why I'm subtracting Math.PI / 2, it corrects for Phaser's default sprite rotation (facing right).

Link to comment
Share on other sites

I'm not sure how the arcade physics module works, normally you dont manipulate velocity directly, velocity is the outcome of applied forces, so, to influence velocity you apply forces, at a high level things often become easier when you think this way and dont directly touch velocity. The thing is that applying forces and updates based on adding vectors etc etc can become computationally expensive so cheaper alternatives exist where you do directly set your velocity or even your position, I'm not sure where the arcade physics sits but certainly if you did swap to P2 you can just use the `applyForceLocal` (or `applyForce`) method and not worry about any of these calculations yourself.

As I say, I'm not sure how the arcade physics works but it looks like you manually set a velocity and it handles updating position for you (I'm basing this purely off of the naming convention in your snippet, others will be better positioned to help in more depth with Phaser and arcade physics). So, (I'm assuming positive y is 'up' on the screen) is you want to go 'left' you apply a negative x value, you want to go down you apply a negative y value. If this assumption is correct then you'd just need something that takes a list of [ x, y ] (these are your forces) and sums them and applies them to velocity.

As you're directly applying velocity presumably you can just do `this.body.velocity.x *= .9` to dampen the velocity (for each component), when your velocity is < .2 (for example), just set it to 0 (if you just keep multiplying by .9 obviously you'll never reach a complete standstill). All numbers here are just examples.


function handleInput() {
  // All rotation calculations omitted for brevity
  this.forces.push([ 0, 1 ])
  this.forces.push([ 1, 0 ])
}


function update() {
  // Apply damping
  this.body.velocity.x *= .9
  this.body.velocity.y *= .9

  // Apply forces
  if ( this.forces.length ) {
    this.forces.forEach( function( force ) {
      this.body.velocity.x += force[ 0 ]
      this.body.velocity.y += force[ 1 ]
    }.bind( this ) )

    // Reset forces now that they have been applied
    this.forces = []
  }
}

The code here shows a `handleInput` function that applies some forces to your object, in this case I have omitted any rotational calculations, but, if we assume that the object is facing 'up' (positive y) then we have applied a force to move the object forward and another force of equal magnitude that strafes the object to the right (positive x).

The update code is assumed to run on a tick. It applies some damping (you may prefer to do this at the end of your update loop) and then applies each force directly to the velocity. If the object was previously at rest (before calling handleInput) then the result will be that its velocity will become [ 1, 1 ], and this velocity will decrease due to damping over time (as each tick is executed), you'd want to also stick in some code that performs the 'shield' duty of stopping the object when its velocity is close to 0.

At some point there must be another update function (is this handled by Arcade physics?) that turns that velocity into an actual positional update.

This does not yet handle maximum velocity, which can be a little trickier. My knowledge of physics falters a little here but due things like air resistance etc etc you need to supply more and more force to keep an object accelerating, things like friction and resistance slow you down (in space things are different again but most games dont want that level of realism as it becomes almost impossible for a solo human to control). We're not simulating friction or resistance or mass so nothing is slowing you down (actually, damping will a little, but its a hard number again, not linear based on anything), you keep applying forces and you'll keep going faster and faster.

A simple solution to this can be simply to limit the velocity to a hard number i.e.

if ( this.body.velocity.x > 2 ) {
  this.body.velocity.x = 2
}

This is crude but can often be enough, what you will find is that you will very quickly accelerate up to a hard point and then stop accelerating, although you will still be moving.

You could also use the current velocity to affect applying forces i.e. if you are already travelling fast enough then dont apply ANY further force, however, what happens is that with damping your velocity will drop and then forces will be applied (if you're still holding down a key to apply them) and you'll find that around maximum velocity your ship will 'bounce' or 'pulse' as forces are applied and restricted. It's not a very good solution!

Try the hard limit of velocity and see if your simulation comes out good enough, it is certainly a computationally cheap way of doing it.

Consider also switching to P2 if you can, its makes this sort of more complex physics simulation much easier.

Link to comment
Share on other sites

Wow, thank you for the in-depth response.

I think I now understand what you mean about using forces rather than manipulating the velocity directly. I will look into switching to the P2 physics engine. The sprite movement is the only complex physics component, so hopefully it's an easy transition.

Regarding the hard limit on velocity, wouldn't I still have to make calculations to determine the maximum x and y velocities based on the vessel's maximum forward and lateral velocities? This calculation is the main part of what's tripping me up.

Thanks again!

Link to comment
Share on other sites

I think limiting each component to a hard number, as I have suggested, would probably yield some funky results, for example, if you're trying to travel in a NNE direction, you'll end up NE as the y component of velocity 'bottoms out' before the x component (I hope that makes sense).

You can use pythagoras to derive vector magnitude (or length) which gives you an overall indication of how 'fast' you are travelling, you can do this by treating the velocity as a vector and assume the origin of the vector is [ 0, 0 ] (which is a normal assumption to make):

// Where vec2 is [ x, y ]
function magnitude( vec2 ) {
  return Math.sqrt( ( vec2[ 0 ] * vec2[ 0 ] ) + ( vec2[ 1 ] * vec2[ 1 ] ) )
}

When the magnitude hits a certain value then you know you've hit your 'top speed'.

This is probably going to get trickier as the next logical thing to do would simply not apply any extra forces when the magnitude gets over that threshold, but, thats wont work as you'll get bouncing as all forces stop and then re-apply when the overall velocity drops.

The next thing you can try is to apply extra damping as the magnitude rises, if you get your scales right you'll get to a point where the damping equals the forces you're applying, whereby your change in velocity will be negated, or can alter this scale based on different craft to give you different 'top speeds'. If you treat the damping as a bell curve (rather than linear against magnitude) then you could get to a nice simulation where acceleration will start slowly, then pick up, then slow down again as you approach top speed, which, whilst not a true simulation of how physics works, gives a good visual approximation of what you might expect (again, being in space changes everything, but you probably dont want actual space travel simulation).

You can apply an inverse force whose magnitude is relational to the force/s you are applying, which would simulate something like air resistance, but damping would probably get you a pretty similar result far cheaper.

P2 takes this all a step further and implements mass and friction and stuff which gives you a realistic simulation to back up your visual approximation of movement.

Link to comment
Share on other sites

Thank you very much for your help, mattstyles. I got the movement working, but it isn't as beautiful as you suggested or I had hoped. :P

Here's what the moveForward (formerly moveUp) function now looks like:

this.forwardSpeed += forwardAcceleration;
setVesselSpeed(this);

Then the setVesselSpeed function looks like this:

// If we're past the max speeds, set the speed to max
if (vessel.lateralSpeed > maxLateralSpeed) {
  vessel.lateralSpeed = maxLateralSpeed;
} else if (vessel.lateralSpeed < -maxLateralSpeed) {
  vessel.lateralSpeed = -maxLateralSpeed;
}

if (vessel.forwardSpeed > maxForwardSpeed) {
  vessel.forwardSpeed = maxForwardSpeed;
} else if (vessel.forwardSpeed < -maxForwardSpeed) {
  vessel.forwardSpeed = -maxForwardSpeed;
}

// Set the lateral vector based on lateral speed
var lateralVector = new Phaser.Point();

lateralVector.x = Math.cos(vessel.rotation) * vessel.lateralSpeed;
lateralVector.y = Math.sin(vessel.rotation) * vessel.lateralSpeed;

// Set the forward vector based on forward speed
var forwardVector = new Phaser.Point();

forwardVector.x = Math.cos(vessel.rotation - (Math.PI / 2)) * vessel.forwardSpeed;
forwardVector.y = Math.sin(vessel.rotation - (Math.PI / 2)) * vessel.forwardSpeed;

// Rest the vessel speed to 0
vessel.body.velocity.x = 0;
vessel.body.velocity.y = 0;

// Set the vessel speed based on the new vectors
vessel.body.velocity.add(forwardVector.x, forwardVector.y);
vessel.body.velocity.add(lateralVector.x, lateralVector.y);

It probably looks pretty ugly to you, but this is my first time using any sort of physics in a game. In fact, this is my first graphical game that doesn't involve only cards or tiles.

Regarding the damping and realism, the game is much more arcade than it is simulation, so I'd be satisfied with smooth, linear movement. I'm currently applying inverse forces to simulate drag, but I'll probably tinker with damping and see how that works out.

If you'd like to see my project, send me a message. It's not very playable at the moment, and most of the text, colours, sprites, and sounds are merely placeholders.

Thanks again for your help!

Link to comment
Share on other sites

Code looks good from here, its readable and the intent is immediately clear from reading it.

Just 2 quick observations.

Firstly, and I think this is just the naming conventions you've used, you dont set velocity (speed), velocity is the product of movement over time. Similarly, you don't actually set acceleration either, acceleration is the product of the change in velocity over time. What you are actually applying are forces. You're actually doing that and then limiting the amount of force that can be applied to the object.

Obviously in code you are also providing the simulation, so you need to directly change the velocity there, but there should be a separation between using the simulation (applying forces) and the simulation itself (the result of those forces). You're already doing this, its just a naming thing really. For example, P2 has a `World.step` function which accepts time and moves the simulation on that amount of time (it applies all the forces acting on bodies in the world), to actually move anything you apply various forces, nothing happens until that `step` function gets called which runs the simulation.

Secondly, why do you reset the velocity to 0 each tick? 

If I hold down the throttle of my car it keeps trying to apply forward thrust (a force), after 1 second the car is moving but the force is still being applied so the velocity grows and grows, it does not reset each x amount of milliseconds.

// Rest the vessel speed to 0
//vessel.body.velocity.x = 0;
//vessel.body.velocity.y = 0;

// Set the vessel speed based on the new vectors
vessel.body.velocity.add(forwardVector.x, forwardVector.y);
vessel.body.velocity.add(lateralVector.x, lateralVector.y);

// Apply damping
vessel.body.velocity.multiply(.9, .9);

// Alternatively apply more damping the faster you are travelling
var magnitude = vector.body.velocity.length();
vessel.body.velocity.multiply( 1 / magnitude );

I'm assuming the vector class has a multiply and a length function here and you'll probably need to flesh out the bottom 2 lines there (for example, in the above code if magnitude is < 1 then it wont actually be damping the vector but increasing it! It might also be applying way too much damping so you'll have to play with it, its just an example of something that might work out for you).

 

Link to comment
Share on other sites

Thanks for clearing up the terminology. It probably isn't terribly important for a simple game like this one, but I could see how it would be necessary when handling a number of forces acting in different directions on the same object.

The reason I reset (not rest :P) the velocities is that it was the most obvious way to recalculate the vessel's new velocity. The way I have it, which is probably wrong, is that the forces acting on the sprite are remembered even after the update, so the forward and lateral forces represent the total force acting on the ship, not just what's being added. In other words, I do this:

  • Add the new force to the vessel's current force
  • Check if the force has exceeded the limit
  • Reset the velocities to zero
  • Add the x and y velocities of the total force

This allows me to avoid decomposing the vessel's current x and y velocities into forward and lateral vectors, which I need to do in order to limit their magnitude seperately. If it were to be more realistic, like your example of a car, then I would do something like this:

  • Calculate the car's current forward and lateral forces
  • Add the force to the car's current force
  • Check if the force has exceeded the limit
  • Add the x and y velocities of the new force
  • Reset (or forget) the car's current forces

But of course, a real car has no programmed limit on its maximum velocities. I'm sorry if this is difficult for you. As I mentioned, I am pretty new to this, so thanks for your patience. :)

I also suspect that it may not be correct to call the setVesselSpeed function from the movement functions. I should probably just call it from the update function and exit if there are no forces to apply.

Thanks again for all your help!

Link to comment
Share on other sites

I'm not sure you should be adding forces up over time, its the velocity that changes over time as a result of forces acting upon it. But, if its giving you a good simulation then roll with it.

And yeah, I agree, have your movement functions 'collect' forces, then apply them all in one place. This also kind of touches on another programming concept of separation of concerns, i.e. your ui does a fairly 'cheap' job, it just collects events, something else actually does the heavy lifting of applying logic (in this case, your simulation).

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...