Estou tentando implementar algum tipo de física espacial falsa no meu jogo 2D. Eu tenho uma visão de cima para baixo da minha nave espacial. Você pode mudar de direção e definir uma velocidade máxima, o que acelera o navio nessa direção de acordo com a quantidade de aceleração do motor do navio.
Eu tenho um código funcionando bem para fazer com que o navio comece lentamente a se mover nessa direção e aumente a velocidade até que a velocidade máxima seja atingida.
Atualizar
Embora as respostas tenham sido um pouco úteis, isso não está me levando à minha solução final. Não consigo transformar as teorias em código funcional. Aqui estão mais alguns parâmetros:
- Estamos trabalhando com uma grade 2D
- O navio possui um único mecanismo onde você pode definir a potência de 0 a 1 para indicar potência total.
- O motor tem uma velocidade máxima
- Há uma fricção espacial falsa onde, se você não aplicar mais energia à nave, ela acabará parando.
Problema
O problema que estou tendo é quando mudo de direção. Se eu estiver viajando em uma direção a 300 velocidades, mude a direção para o oposto, agora estou viajando instantaneamente na velocidade definida, em vez de diminuir a velocidade e voltar à velocidade nessa direção.
Estado desejado
Código atual
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")}");
}
}
Código 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);
}
}
Respostas:
Não conheço
xna
... mas sei matemática. E implementar a física sem entender a matemática por trás disso é como entrar na política sem saber mentir. Então vamos começar!Primeiro de tudo, sua maneira de mover o navio não é realmente baseada na física. Você não quer que o jogador mude a posição do navio diretamente. O que você quer fazer é deixar o jogador aplicar aceleração ao navio, depois deixar a física calcular a velocidade do navio e deixar o mundo mudar a posição do navio por essa velocidade recém-calculada. Velocidade é a diferença na posição do navio no tempo. Se movia 5 unidades para a direita e 1 unidade para cima, movia-se pela velocidade de
(5,-1)
. Aceleração é a diferença de velocidade do navio - apenas influencia a posição do navio, alterando sua velocidade. Se sua nave estava indo 2 unidades para a esquerda e 1 unidade para baixo, significando a velocidade de(2,1)
, e o jogador o acelera na direção oposta, ou seja(-2,-1)
), ele parará no local com a próxima unidade de tempo (seja frame ou tick ou qualquer outra coisa). Em outras palavras, você precisa adicionar o vetor de aceleração ao vetor de velocidade e depois calcular onde o navio será o próximo.Vetores
Imagine uma flecha que comece em algum lugar (origem), aponte para algum lugar (direção) e tenha um certo comprimento (magnitude). Agora descreva-o com dois valores - quanto X e quanto Y é o seu fim desde o início. Para simplificar, falarei apenas do eixo X, o que significa que seus vetores apontam para algo que é "tanto X" para a direita (positiva) ou para a esquerda (negativa).
Velocidade
Agora, como a posição do navio deve mudar entre os quadros? Com vetor de velocidade. Vamos supor que sua nave comece no local (0,0) com velocidade (12,0). Isso significa que ele mudará de posição da seguinte maneira:
Aceleração
Como mudamos a direção? Você não quer apenas mudar a velocidade para
(-12,0)
. Isso significa que o navio passa de 100 parsecs à direita para 100 parsecs restantes em um "quadro". Eu não gostaria de estar naquele navio quando isso acontecer. Novamente, o "comprimento" do vetor é chamado de "magnitude" e, no caso de velocidade, passa a ser velocidade. Portanto, você deseja que a magnitude da velocidade (velocidade da nave) diminua lentamente para 0 e depois acelere para 12 negativo (o que significa que ela se move na direção oposta). Você pode fazer isso adicionando aceleração à velocidade, por exemplo, a aceleração de(-4,0)
, agora o navio se move da seguinte maneira (jogador pressionado à esquerda no terceiro "quadro" e depois no 9):Então você deseja aplicar uma aceleração de
(4,0)
para fazer o navio gradualmente ganhar velocidade na direção X positiva quando o jogador pressiona a seta direita e aplicar uma aceleração de(-4,0)
quando a seta esquerda é pressionada. Obviamente, quando nenhuma tecla é pressionada, você não aplica nenhuma aceleração, o que significa que a nave está mantendo sua velocidade (se movendo a uma velocidade constante em uma determinada direção). Se você quiser que ele diminua gradualmente quando nenhuma tecla for pressionada, adicione outro vetor, chame-oDrag
e dê-lhe a direção sempre oposta à velocidade (ou seja, na direção da parte traseira do navio) até que a magnitude da velocidade atinja 0. Esperamos que você entenda a idéia .Código
O que eu faria (pseudo-código, você terá que corrigi-lo, adicionar encapsulamento, etc., também ignora alguns aspectos, por exemplo, a diagonal é um pouco mais rápida que a esquerda, direita, para cima ou para baixo):
fonte
Para fazer isso, você precisa simular inércia. É assim que eu recomendaria:
fonte
if (this.Vel.LengthSquared() > this.MaxSpeed * MaxSpeed)
você tem o MaxSpeed lá duas vezes. Além disso,ThrustForward
usa ,this.Accel
mas o seu comentário diz que isso é aceleração máxima, isso também está correto?this.MaxSpeed
existe duas vezes para otimizar o código.Vector2.Length()
leva mais tempo para calcular do queVector2.LengthSquared()
a seguir se instrução faz a mesma coisa, mas é un otimizado e fácil de entender:if (this.Vel.Length() > this.MaxSpeed)
Ok, é realmente muito simples de conseguir. Antes de tudo, como você mencionou, a direção do motor descreve o caminho do movimento. Isso facilita o trabalho.
Antes de tudo, armazene sempre um vetor da direção em que você está se movendo.
Em seguida, você deve ter um vetor da aparência de seu mecanismo.
Então, por enquanto, quando você começa a se mover, digamos certo, a direção e a aparência do vetor do mecanismo estão apontando para a direita. Quando você agora quer virar digamos para o topo (90 graus), então você simplesmente gira o vetor do mecanismo lookat.
Agora vem a parte divertida. Determine, com qualquer função, quão forte é o efeito da mudança de direção e quebra.
primeiro da mudança de direção.
Dependendo de sua velocidade e mudança de ângulo, você pode diminuir a velocidade e transformar o vetor de direção.
Se você quer uma mudança completa de direção (180 graus), é simples matemática. Em sua atualização, basta alterar sua velocidade lentamente. Quando a velocidade chegar a zero, gire o vetor de direção em 180 graus e comece a adicionar velocidade novamente.
Em uma curva de 90 graus, fica um pouco mais complicado. Você precisa definir uma função para calcular quanto o navio pode girar de acordo com a velocidade e se vai diminuir a velocidade. Mas você pode brincar com os valores até que caiba no que você quer.
fonte