8
\$\begingroup\$

I'm trying to implement some sort of faux space physics in my 2D game. I have a top down view of my space ship. You can change direction and set a speed up to a maximum which then accelerates the ship in that direction according to the ship's engine acceleration amount.

I have code working fine with getting the ship to slowly start moving in that direction and increase speed up until the max speed is met.

Update

While the answers have been slightly helpful, it's not getting me to my final solution. I cannot seem to transform the theories into working code. Here are some more parameters:

  1. We're working with a 2D grid
  2. The ship has a single engine where you can set the power from 0 to 1 to indicate full power.
  3. The engine has a max speed
  4. There is a fake space-friction where if you no longer apply power to the ship, it will eventually stop.

Problem

The problem I'm having is when I change direction. If I'm travelling in one heading at 300 speed, then change the heading to the opposite, I'm now instantly travelling at the set speed instead of slowing down, and getting back up to that speed in that direction.

Desired state

enter image description here

Current Code

public void Update(Consoles.Space space)
{
    var GameTimeElapsedUpdate = (float)SadConsole.Engine.GameTimeElapsedUpdate;

    Graphic.PositionOffset = viewPortMaster.Position;

    // Update the engine
    ShipDetails.Engine.Update();

    // Degrade the current velocity with friction?? 
    if (velocity.Length() < 0f)
    {
        var accelerationFrame = ShipDetails.Engine.GetAccelerationFrame();

        if (velocity.X > 0)
            velocity.X -= accelerationFrame;
        else if (velocity.X < 0)
            velocity.X += accelerationFrame;

        if (velocity.Y > 0)
            velocity.Y -= accelerationFrame;
        else if (velocity.Y < 0)
            velocity.Y += accelerationFrame;
    }

    // Handle any new course adjustments
    if (IsTurnRightOn)
        SetHeading(heading + (ShipDetails.TurningSpeedRight * GameTimeElapsedUpdate));

    if (IsTurnLeftOn)
        SetHeading(heading - (ShipDetails.TurningSpeedLeft * GameTimeElapsedUpdate));

    // Handle any power changes 
    if (IsPowerIncreasing)
    {
        SetPower(ShipDetails.Engine.DesiredPower + (GameTimeElapsedUpdate * ((ShipDetails.Engine.MaxSpeed / Settings.SecondsForFullPowerAdjustment) / ShipDetails.Engine.MaxSpeed)));

        if (ShipDetails.Engine.DesiredPower > 1.0d)
            ShipDetails.Engine.DesiredPower = 1.0d;
    }

    if (IsPowerDecreasing)
    {
        SetPower(ShipDetails.Engine.DesiredPower - (GameTimeElapsedUpdate * ((ShipDetails.Engine.MaxSpeed / Settings.SecondsForFullPowerAdjustment) / ShipDetails.Engine.MaxSpeed)));

        if (ShipDetails.Engine.DesiredPower < 0.0d)
            ShipDetails.Engine.DesiredPower = 0.0d;
    }

    // Calculate new velocity based on heading and engine

    // Are we changing direction?
    if (vectorDirectionDesired != vectorDirection)
    {
        // I think this is wrong, I don't think this is how I'm supposed to do this. I don't really want to
        // animate the heading change, which is what I think this is actually doing..

        if (vectorDirectionDesired.X < vectorDirection.X)
            vectorDirection.X = Math.Min(vectorDirection.X + (vectorDirectionDesired.X * Settings.SpeedSquareSecond * GameTimeElapsedUpdate), vectorDirectionDesired.X);
        else if (vectorDirectionDesired.X > vectorDirection.X)
            vectorDirection.X = Math.Max(vectorDirection.X + (vectorDirectionDesired.X * Settings.SpeedSquareSecond * GameTimeElapsedUpdate), vectorDirectionDesired.X);

        if (vectorDirectionDesired.Y < vectorDirection.Y)
            vectorDirection.Y = Math.Min(vectorDirection.Y + (vectorDirectionDesired.Y * Settings.SpeedSquareSecond * GameTimeElapsedUpdate), vectorDirectionDesired.Y);
        else if (vectorDirectionDesired.Y > vectorDirection.Y)
            vectorDirection.Y = Math.Max(vectorDirection.Y + (vectorDirectionDesired.Y * Settings.SpeedSquareSecond * GameTimeElapsedUpdate), vectorDirectionDesired.Y);
    }

    vectorDirection = vectorDirectionDesired;

    if (ShipDetails.Engine.Power != 0)
    {

        var force = new Vector2(vectorDirection.X * (float)ShipDetails.Engine.Speed, vectorDirection.Y * (float)ShipDetails.Engine.Speed);
        var acceleration = new Vector2(force.X / ShipDetails.Engine.Acceleration, force.Y / ShipDetails.Engine.Acceleration) * GameTimeElapsedUpdate;

        velocity = new Vector2(velocity.X + acceleration.X, velocity.Y + acceleration.Y);

        Point endingLocation;
        endingLocation.X = (int)velocity.X;
        endingLocation.Y = (int)velocity.Y;
        velocity.X -= endingLocation.X;
        velocity.Y -= endingLocation.Y;

        MapPosition += endingLocation;
    }


    if (this == Settings.GameWorld.CurrentShip)
    {
        var debug = space.GetDebugLayer();
        debug.Clear();
        debug.Print(0 + space.ViewArea.X, 0 + space.ViewArea.Y, $"Ship: {MapPosition}");
        debug.Print(0 + space.ViewArea.X, 1 + space.ViewArea.Y, $"Speed: {ShipDetails.Engine.Speed} Desired: {ShipDetails.Engine.DesiredPower}");
        debug.Print(0 + space.ViewArea.X, 2 + space.ViewArea.Y, $"Heading: {heading} Adjusted: {adjustedHeading}");
        debug.Print(0 + space.ViewArea.X, 3 + space.ViewArea.Y, $"Dir: {vectorDirection.X.ToString("0.00")}, {vectorDirection.Y.ToString("0.00")} DirDes: {vectorDirectionDesired.X.ToString("0.00")}, {vectorDirectionDesired.Y.ToString("0.00")}");
    }

}

ShipEngine Code

class ShipEngine
{
    public int Acceleration;
    public int AccelerationBonus;
    public int MaxSpeed;
    public int MaxAfterburner;

    public int Speed { get { return (int)(Power * MaxSpeed); } }

    // This is a 0-1 no power to full power rating where MaxSpeed is full power
    public double DesiredPower { get { return desiredPower; } set { desiredPower = value;  if (value != Power) isDesiredTriggered = true; } }
    public double Power;

    public bool IsAdjusting { get { return Speed != 0; } }

    private double desiredPower;
    private bool isDesiredTriggered;

    public void Update()
    {
        if (DesiredPower != Power)
        {
            var GameTimeElapsedUpdate = (float)SadConsole.Engine.GameTimeElapsedUpdate;
            var accelerationFrame = (((float)(Acceleration + AccelerationBonus) / Settings.SpeedSquareSecond) * GameTimeElapsedUpdate);

            if (DesiredPower > Power)
            {
                Power += accelerationFrame;

                if (Power > DesiredPower)
                    Power = DesiredPower;
            }
            else if (DesiredPower < Power)
            {
                Power -= accelerationFrame;

                if (Power < DesiredPower)
                    Power = DesiredPower;
            }
        }
    }

    public float GetAccelerationFrame()
    {
        return (((float)Acceleration / Settings.SpeedSquareSecond) * (float)SadConsole.Engine.GameTimeElapsedUpdate);
    }

}
\$\endgroup\$
  • \$\begingroup\$ Are you talking about adding drag? \$\endgroup\$ – Daniel Holst Jul 6 '15 at 10:11
  • \$\begingroup\$ I don't know. I've reworded the title and some of the description to be more "what I want" focused. :) \$\endgroup\$ – Thraka Jul 6 '15 at 15:29
  • 1
    \$\begingroup\$ It's still not 100% clear what behaviour you want your spaceship to have. Maybe read some similar questions and see if those give you what you need, or help you isolate what specific behaviour you want that's different from theirs. Diagramming out a play-by-play of what you want the ship to do at each part of the turn might help a lot. \$\endgroup\$ – DMGregory Jul 6 '15 at 15:40
  • \$\begingroup\$ That question may help me out, but it seems like it may be trying to do more than I want. Thanks for the tips on the diagramming! I'll do that after work today. \$\endgroup\$ – Thraka Jul 6 '15 at 15:50
  • 1
    \$\begingroup\$ Look into basic 2d physics. Sounds like all you need to do is apply acceleration to you velocity vector. \$\endgroup\$ – ClassicThunder Jul 6 '15 at 20:04
6
+50
\$\begingroup\$

I'm not familiar with xna... but I know maths. And implementing physics without understanding the math behind it is like going into politics without knowing how to lie. So let's get started!

First of all, your way of moving the ship around isn't really physics based. You don't want the player to change the position of the ship directly. What you want to do is let the player apply acceleration to the ship, then let the physics calculate the ship's velocity, then let the world change the ship's position by that newly calculated velocity. Velocity is the difference in the position of the ship in time. If it moved 5 units right and 1 unit up, it moved by velocity of (5,-1). Acceleration is the difference in velocity of the ship - it only influences the ship's position by changing its velocity. If your ship was going 2 units to the left and 1 unit down, meaning the velocity of (2,1), and the player accelerates it in the opposite direction, meaning (-2,-1)), it'll stop in place with the next unit of time (be it frame or tick or whatever). In other words, you need to add acceleration vector to the velocity vector and then calculate where the ship will be next.

Vectors

Imagine an arrow that starts somewhere (origin), points to somewhere (direction) and has a certain length (magnitude). Now describe it with two values - how much X and how much Y is its end from its start. For simplification I'll only talk about X axis which means your vectors point at something that is "that much X" to the right (positive) or to the left (negative).

Velocity

Now, how the ship's position should change between frames? With velocity vector. Let's assume your ship starts at location (0,0) with velocity (12,0). It means it will change it's position as follows:

Position:   Velocity:
(0,0)       (12,0)
(12,0)      (12,0)
(24,0)      (12,0)
(36,0)      (12,0)

Acceleration

How do we change the direction? You don't want to just change the velocity to (-12,0). That'd mean the ship goes from 100 parsecs right to 100 parsecs left in one "frame". I wouldn't want to be on that ship when it happens. Again, the "length" of the vector is called "magnitude" and in case of velocity it happens to be speed. So you want the magnitude of velocity (ship's speed) to slowly decrease to 0 and then to accelerate to negative 12 (which means it moves in the opposite direction). You can do so by adding acceleration to the velocity, e.g. the acceleration of (-4,0), so now the ship moves as follows (player pressed left on a 3rd "frame" then released it on a 9th):

Position:   Velocity:   Acceleration:
(0,0)       (12,0)      (0,0)     # starts in 0,0 going right
(12,0)      (12,0)      (0,0)
(24,0)      (12,0)      (-4,0)
(36,0)      (8,0)       (-4,0)    # starts to slow down
(44,0)      (4,0)       (-4,0)
(48,0)      (0,0)       (-4,0)    # stops
(48,0)      (-4,0)      (-4,0)    # changes direction
(44,0)      (-8,0)      (-4,0)    # starts to go left
(36,0)      (-12,0)     (0,0)     # goes left at steady speed
(24,0)      (-12,0)     (0,0)
(12,0)      (-12,0)     (0,0)
(0,0)       (-12,0)     (0,0)     # passes 0,0 starting point
(-12,0)     (-12,0)     (0,0)     # keeps going left with the same speed
(-24,0)     (-12,0)     (0,0)

So you want to apply an acceleration of (4,0) to make the ship gradually gain speed in a positive X direction when the player presses the right arrow and apply an acceleration of (-4,0) when left arrow is pressed. Obviously when no keys are pressed you don't apply any acceleration meaning the ship is keeping it's velocity (moving at constant speed in given direction). If you want it to slow down gradually when no key is pressed, add another vector, call it Drag and give it direction always opposite to velocity (i.e. towards the back of the ship) until the magnitude of velocity reaches 0. Hopefully you get the idea.

Code

What I would do (pseudo code, you'll have to fix it, add encapsulation, etc., also it ignores some aspects, e.g. going diagonal is slightly faster than straight left, right, up or down):

class Vector {
    x = 0;
    y = 0;

    add(Vector v) {
        this.x += v.x;
        this.y += v.y;
    }
}

class Ship {
    position = new Vector;
    velocity = new Vector;
    maxSpeed = 12;

    accelerate(Vector acceleration) {
        this.velocity.add(acceleration);
        if (this.velocity.x > this.maxSpeed)
            this.velocity.x = this.maxSpeed);
        if (this.velocity.x < -1*this.maxSpeed)
            this.velocity.x = -1*this.maxSpeed); // do the same for y
    }
}

switch (pressedKey) {
    case 'right': Ship.accelerate(new Vector(4,0)); break;
    case 'left': Ship.accelerate(new Vector(-4,0)); break;
}

Ship.position.add(Ship.velocity); // world updates the ship's position
\$\endgroup\$
  • 1
    \$\begingroup\$ Thanks for the detailed answer, I'll read through it and get back to you. I appreciate the help!! \$\endgroup\$ – Thraka Dec 9 '15 at 5:08
  • 1
    \$\begingroup\$ You could also use drag to limit the ships velocity and slow the ship down if they reduce the power. This would have the benefit of smoothly lowering the acceleration as the velocity approaches the maximum velocity (I know that this is beyond the scope of your question but thought that it might be a good addition if you go with this approach) \$\endgroup\$ – Malrig Dec 9 '15 at 14:19
  • 1
    \$\begingroup\$ I do want drag, actually that's my #4 point in my question. :) \$\endgroup\$ – Thraka Dec 9 '15 at 17:49
3
\$\begingroup\$

In order to do this you need to simulate inertia. This is how I would recommend doing it:

class Ship
{
    public Vector2 Pos; //Current ship position
    public Vector2 Vel; //Store current velocity as a vector
    public float Rot; //What direction the ship is facing in radians

    public float Accel; //Maximum acceleration
    public float MaxSpeed; //Maximum velocity 

    public void Update(float elapsedTime)
    {
        this.Pos += this.Vel * elapsedTime; //Update our position based on our current velocity
        this.Rot = MathHelper.WrapAngle(this.Rot); //Wrap our heading angle to always be between -Pi and Pi
        if (this.Vel.LengthSquared() > this.MaxSpeed * MaxSpeed) //Keep the velocity vector's length shorter than our max speed
        {
            this.Vel.Normalize();
            this.Vel *= this.MaxSpeed;
        }
    }

    public void ThrustForward(float elapsedTime) //Apply our acceleration to our current velocity
    {
        this.Vel += Vector2.Transform(-Vector2.UnitY * this.Accel * elapsedTime, Matrix.CreateRotationZ(this.Rot));
    }
}
\$\endgroup\$
  • \$\begingroup\$ Thanks for coming to look at this. This does look like an interesting take. I'm trying to implement it but its not working as I think it should. Is this correct?? if (this.Vel.LengthSquared() > this.MaxSpeed * MaxSpeed) you have MaxSpeed in there twice.. Also, ThrustForward uses this.Accel but your comment says this is Max acceleration is that correct too? \$\endgroup\$ – Thraka Aug 22 '15 at 22:59
  • \$\begingroup\$ Yes these are correct, I copied it straight from a game I am working on that is still in the very early stages. Feel free to use this code as a base and modify it as you need. this.MaxSpeed is there twice to optimize the code. Vector2.Length() takes longer to compute than Vector2.LengthSquared() The following if statement does the same thing but is un-optimized and easier to understand: if (this.Vel.Length() > this.MaxSpeed) \$\endgroup\$ – Ramon J Denham Aug 23 '15 at 3:10
0
\$\begingroup\$

Ok it's actually really simple to achieve. First of all as u mentioned, ur engine direction describes the path of movement. This makes it comfortable to work with.

First of all, always store a vector of the direction ur moving.

Next u must have a vector of the lookat of ur engine.

So for now when u start moving, let's say right, the both the direction and lookat of engine vector are pointing to the right. When u now want to turn let.s say to top (90 degree), then u simply turn the lookat engine vector.

Now comes the fun part. Determine with any function how strong to effect the change of direction and break.

first of the direction change.

Depending on ur Speed and change of angle u could slow down and turn the direction vector.

If u want a complete direction change (180 degree), then it's simple math. In ur update just change ur speed slowly. When speed turns to zero, flip the direction vector for 180 degree and start adding speed again.

On a 90 degree turn, it get's a bit more complicated. u need to define a function to calculate how much the ship is allowed to turn according to speed and if it gonna slow down how much. But u can play with the values till it fits what u want.

\$\endgroup\$
  • \$\begingroup\$ Maybe you're hitting on something I'm missing. A delay needs to be calculated based on new trajectory versus the old and the turn delay... Not sure how to model that though. \$\endgroup\$ – Thraka Jul 7 '15 at 23:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.