Я пытаюсь реализовать какую-то физику искусственного пространства в моей 2D игре. У меня есть вид сверху на мой космический корабль. Вы можете изменить направление и установить максимальную скорость, которая затем ускоряет корабль в этом направлении в зависимости от величины ускорения двигателя.
У меня есть код, который работает нормально, заставляя корабль медленно начинать движение в этом направлении и увеличивать скорость до достижения максимальной скорости.
Обновить
Хотя ответы были немного полезны, я не могу прийти к окончательному решению. Я не могу преобразовать теории в рабочий код. Вот еще несколько параметров:
- Мы работаем с 2D сеткой
- Корабль имеет один двигатель, где вы можете установить мощность от 0 до 1, чтобы указать полную мощность.
- Двигатель имеет максимальную скорость
- Существует фальшивое космическое трение, где, если вы больше не применяете силу к кораблю, он в конечном итоге остановится.
проблема
У меня проблема, когда я меняю направление. Если я еду в одном направлении на скорости 300, а затем меняю направление на противоположное, я теперь сразу же двигаюсь на заданной скорости вместо того, чтобы замедляться и возвращаюсь к этой скорости в этом направлении.
Желаемое состояние
Текущий код
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
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);
}
}
Ответы:
Я не знаком с
xna
... но я знаю математику. А реализация физики без понимания математики, стоящей за ней, это все равно, что идти в политику, не зная, как лгать. Итак, начнем!Прежде всего, ваш способ перемещения корабля не основан на физике. Вы не хотите, чтобы игрок изменил положение корабля напрямую. То, что вы хотите сделать, это позволить игроку применить ускорение к кораблю, затем позволить физике рассчитать скорость корабля , а затем позволить миру изменить положение корабля на эту вновь рассчитанную скорость. Скорость - это разница в положении корабля во времени. Если он двигался на 5 единиц вправо и на 1 единицу вверх, он двигался со скоростью
(5,-1)
. Ускорение - это разница в скорости корабля - она влияет только на положение корабля, изменяя его скорость. Если ваш корабль шел 2 единицы влево и 1 единицу вниз, то есть скорость(2,1)
и игрок ускоряет его в противоположном направлении, то есть(-2,-1)
), он остановится на месте в следующую единицу времени (будь то кадр, тик или что-то еще). Другими словами, вам нужно добавить вектор ускорения к вектору скорости и затем вычислить, где корабль будет следующим.векторы
Представьте себе стрелку, которая начинается где-то (начало координат), указывает куда-то (направление) и имеет определенную длину (величину). Теперь опишите его двумя значениями - сколько X и сколько Y является его концом с начала. Для упрощения я буду говорить только об оси X, что означает, что ваши векторы указывают на то, что «так много X» вправо (положительно) или влево (отрицательно).
Скорость
Теперь, как позиция корабля должна меняться между кадрами? С вектором скорости. Предположим, ваш корабль стартует в точке (0,0) со скоростью (12,0). Это означает, что он изменит свою позицию следующим образом:
ускорение
Как мы меняем направление? Вы не хотите просто изменить скорость на
(-12,0)
. Это означало бы, что корабль проходит от 100 парсек вправо до 100 парсек слева в одном «кадре». Я не хотел бы быть на этом корабле, когда это произойдет. Опять же, «длина» вектора называется «величиной», и в случае скорости она оказывается скоростью. Таким образом, вы хотите, чтобы величина скорости (скорость корабля) медленно уменьшалась до 0, а затем увеличивалась до отрицательной 12 (что означает, что она движется в противоположном направлении). Вы можете сделать это, добавив ускорение к скорости, например, ускорение(-4,0)
, так что теперь корабль движется следующим образом (игрок нажал влево на 3-м «кадре», затем отпустил его на 9-м):Таким образом, вы хотите применить ускорение,
(4,0)
чтобы корабль постепенно набирал скорость в положительном направлении X, когда игрок нажимает стрелку вправо, и ускорение,(-4,0)
когда нажимается стрелка влево. Очевидно, что когда ни одна клавиша не нажата, вы не применяете никакого ускорения, что означает, что корабль сохраняет свою скорость (движется с постоянной скоростью в заданном направлении). Если вы хотите, чтобы он постепенно замедлялся, когда ни одна клавиша не нажата, добавьте еще один вектор, назовите егоDrag
и задайте направление, всегда противоположное скорости (то есть к задней части корабля), пока величина скорости не достигнет 0. Надеемся, вы поняли идею ,Код
Что бы я сделал (псевдокод, вам придется его исправить, добавить инкапсуляцию и т. Д., Также он игнорирует некоторые аспекты, например, диагональ идет немного быстрее, чем прямо влево, вправо, вверх или вниз):
источник
Для этого вам нужно смоделировать инерцию. Вот как я бы порекомендовал это сделать:
источник
if (this.Vel.LengthSquared() > this.MaxSpeed * MaxSpeed)
у вас есть MaxSpeed там дважды. Кроме того,ThrustForward
использует,this.Accel
но ваш комментарий говорит, что это Макс ускорение это тоже правильно?this.MaxSpeed
есть ли дважды для оптимизации кода.Vector2.Length()
вычисление занимает больше времени, чемVector2.LengthSquared()
следующее, если оператор делает то же самое, но не оптимизирован и его легче понять:if (this.Vel.Length() > this.MaxSpeed)
Хорошо, это действительно очень просто. Прежде всего, как вы упомянули, направление движения вашего двигателя описывает траекторию движения. Это делает его удобным для работы.
Прежде всего, всегда сохраняйте вектор направления движения.
Далее у вас должен быть вектор вида вашего двигателя.
Итак, сейчас, когда вы начинаете движение, скажем, вправо, и направление, и направление вектора двигателя указывают вправо. Когда вы теперь хотите повернуть, скажем, на верх (скажем, на 90 градусов), вы просто поворачиваете вектор двигателя lookat.
Теперь самое интересное. С помощью любой функции определите, насколько сильным может быть изменение направления и обрыва.
сначала изменение направления.
В зависимости от скорости и изменения угла вы можете замедлить ход и повернуть вектор направления.
Если вы хотите полное изменение направления (180 градусов), то это простая математика. В вашем обновлении просто меняйте скорость медленно. Когда скорость обернется до нуля, переверните вектор направления на 180 градусов и начните снова добавлять скорость.
На повороте на 90 градусов все становится немного сложнее. Вам нужно определить функцию, чтобы вычислить, сколько кораблю разрешено поворачивать в зависимости от скорости, и насколько оно замедлится. Но вы можете играть со значениями, пока они не соответствуют тому, что вы хотите.
источник