물리 시뮬레이션 루프를 개선하는 데 필요한 단계는 다음과 같습니다.
1. 타임 스텝
코드에서 볼 수있는 주요 문제는 물리 단계 시간을 고려하지 않는다는 것입니다. Position += Velocity;
단위가 일치하지 않기 때문에 문제가 있음을 분명히해야합니다 . 하나는 Velocity
실제로 속도가 아니거나 뭔가가 없습니다.
각 프레임이 시간 단위로 발생하도록 속도 및 중력 값이 조정 되더라도 1
( 예 : Velocity
실제로 1 초 단위로 이동 한 거리를 의미 함 ) 시간은 코드의 어딘가에 암시 적으로 나타나야합니다 (변수를 수정하여) 그들의 이름은 실제로 저장하는 것을 반영하거나 명시 적으로 (시간 단계를 도입하여) 반영합니다. 가장 쉬운 방법은 시간 단위를 선언하는 것입니다.
float TimeStep = 1.0;
필요한 곳이면 어디에서나 해당 값을 사용하십시오.
Velocity += Physics.Gravity.Force * TimeStep;
Position += Velocity * TimeStep;
...
괜찮은 컴파일러는로 곱셈을 단순화 1.0
하므로 부분이 느려지지는 않습니다.
지금은 Position += Velocity * TimeStep
아직 꽤 정확한이다 ( 이 질문에 이유를 이해하기를)하지만 아마 지금 할 것입니다.
또한 시간을 고려해야합니다.
Velocity *= Physics.Air.Resistance;
고치는 것은 조금 까다 롭습니다. 한 가지 가능한 방법은 다음과 같습니다.
Velocity -= Vector2(Math.Pow(Physics.Air.Resistance.X, TimeStep),
Math.Pow(Physics.Air.Resistance.Y, TimeStep))
* Velocity;
2. 이중 업데이트
수신 거부시 수행 할 작업을 확인하십시오 (관련 코드 만 표시됨).
Position += Velocity * TimeStep;
if (Position.Y < 0)
{
Velocity.Y = -Velocity.Y * Physics.Surfaces.Grass;
Position.Y = Position.Y + Velocity.Y * TimeStep;
}
TimeStep
반송 중에 두 번 사용 된 것을 볼 수 있습니다 . 이것은 기본적으로 공을 업데이트하는 데 두 배의 시간을줍니다. 이것은 대신 일어날 것입니다 :
Position += Velocity * TimeStep;
if (Position.Y < 0)
{
/* First, stop at Y = 0 and count how much time is left */
float RemainingTime = -Position.Y / Velocity.Y;
Position.Y = 0;
/* Then, start from Y = 0 and only use how much time was left */
Velocity.Y = -Velocity.Y * Physics.Surfaces.Grass;
Position.Y = Velocity.Y * RemainingTime;
}
3. 중력
코드의이 부분을 지금 확인하십시오.
if(Position.Y < GraphicsViewport.Height - Texture.Height)
{
Velocity += Physics.Gravity.Force * TimeStep;
}
프레임 전체 기간 동안 중력을 추가합니다. 그러나 해당 프레임 동안 공이 실제로 튀면 어떻게 될까요? 그러면 속도가 반전되지만 추가 된 중력은 공을 땅에서 멀어지게합니다! 따라서 수신 거부시 과도한 중력을 제거 하고 올바른 방향으로 다시 추가해야합니다.
올바른 방향으로 중력을 다시 추가하더라도 속도가 너무 빨라질 수 있습니다. 이것을 피하기 위해 중력 추가를 건너 뛰거나 (결국 그렇게 많지 않고 프레임 만 지속) 속도를 0으로 고정 할 수 있습니다.
4. 고정 코드
그리고 완전히 업데이트 된 코드는 다음과 같습니다.
public void Update()
{
float TimeStep = 1.0;
Update(TimeStep);
}
public void Update(float TimeStep)
{
float RemainingTime;
// Apply gravity if we're not already on the ground
if(Position.Y < GraphicsViewport.Height - Texture.Height)
{
Velocity += Physics.Gravity.Force * TimeStep;
}
Velocity -= Vector2(Math.Pow(Physics.Air.Resistance.X, RemainingTime),
Math.Pow(Physics.Air.Resistance.Y, RemainingTime))
* Velocity;
Position += Velocity * TimeStep;
if (Position.X < 0 || Position.X > GraphicsViewport.Width - Texture.Width)
{
// We've hit a vertical (side) boundary
if (Position.X < 0)
{
RemainingTime = -Position.X / Velocity.X;
Position.X = 0;
}
else
{
RemainingTime = (Position.X - (GraphicsViewport.Width - Texture.Width)) / Velocity.X;
Position.X = GraphicsViewport.Width - Texture.Width;
}
// Apply friction
Velocity -= Vector2(Math.Pow(Physics.Surfaces.Concrete.X, RemainingTime),
Math.Pow(Physics.Surfaces.Concrete.Y, RemainingTime))
* Velocity;
// Invert velocity
Velocity.X = -Velocity.X;
Position.X = Position.X + Velocity.X * RemainingTime;
}
if (Position.Y < 0 || Position.Y > GraphicsViewport.Height - Texture.Height)
{
// We've hit a horizontal boundary
if (Position.Y < 0)
{
RemainingTime = -Position.Y / Velocity.Y;
Position.Y = 0;
}
else
{
RemainingTime = (Position.Y - (GraphicsViewport.Height - Texture.Height)) / Velocity.Y;
Position.Y = GraphicsViewport.Height - Texture.Height;
}
// Remove excess gravity
Velocity.Y -= RemainingTime * Physics.Gravity.Force;
// Apply friction
Velocity -= Vector2(Math.Pow(Physics.Surfaces.Grass.X, RemainingTime),
Math.Pow(Physics.Surfaces.Grass.Y, RemainingTime))
* Velocity;
// Invert velocity
Velocity.Y = -Velocity.Y;
// Re-add excess gravity
float OldVelocityY = Velocity.Y;
Velocity.Y += RemainingTime * Physics.Gravity.Force;
// If velocity changed sign again, clamp it to zero
if (Velocity.Y * OldVelocityY <= 0)
Velocity.Y = 0;
Position.Y = Position.Y + Velocity.Y * RemainingTime;
}
}
5. 추가 추가
시뮬레이션 안정성을 향상시키기 위해 물리 시뮬레이션을 더 높은 주파수에서 실행하기로 결정할 수 있습니다. 이 만든 사소한 관련된 위의 변경에 의해 TimeStep
당신은 당신이 원하는대로 여러 덩어리로 당신의 프레임을 분할해야하기 때문에. 예를 들어 :
public void Update()
{
float TimeStep = 1.0;
Update(TimeStep / 4);
Update(TimeStep / 4);
Update(TimeStep / 4);
Update(TimeStep / 4);
}