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:
- We're working with a 2D grid
- The ship has a single engine where you can set the power from 0 to 1 to indicate full power.
- The engine has a max speed
- 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
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);
}
}
Answer
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
No comments:
Post a Comment