Ball Physics : 공이 쉬면서 최종 바운스를 부드럽게합니다.


12

나는 작은 튀는 공 게임에서 또 다른 문제에 직면했습니다.

마지막 공이 멈출 때를 제외하고는 공이 잘 튀어 오르고 있습니다. 공의 움직임은 주요 부분에서는 부드럽지만 끝 부분을 향하여 공이 화면 하단에 고정되면서 잠시 동안 je 소리가납니다.

왜 이런 일이 일어나고 있는지 이해할 수 있지만 부드럽게 할 수는 없습니다.

제공 할 수있는 조언에 감사드립니다.

내 업데이트 코드는 다음과 같습니다

public void Update()
    {
        // Apply gravity if we're not already on the ground
        if(Position.Y < GraphicsViewport.Height - Texture.Height)
        {
            Velocity += Physics.Gravity.Force;
        }            
        Velocity *= Physics.Air.Resistance;
        Position += Velocity;

        if (Position.X < 0 || Position.X > GraphicsViewport.Width - Texture.Width)
        {
            // We've hit a vertical (side) boundary
            // Apply friction
            Velocity *= Physics.Surfaces.Concrete;

            // Invert velocity
            Velocity.X = -Velocity.X;
            Position.X = Position.X + Velocity.X;
        }

        if (Position.Y < 0 || Position.Y > GraphicsViewport.Height - Texture.Height)
        {
            // We've hit a horizontal boundary
            // Apply friction
            Velocity *= Physics.Surfaces.Grass;

            // Invert Velocity
            Velocity.Y = -Velocity.Y;
            Position.Y = Position.Y + Velocity.Y;
        }
    }

아마도 내가 또한 지적해야한다 Gravity, Resistance Grass그리고 Concrete유형을 모두 있습니다 Vector2.


공이 표면에 닿을 때 "마찰"이 1보다 작은 값입니다. 기본적으로 올바른 복원 계수입니다 .
Jorge Leitao

@ JCLeitão-맞습니다.
Ste

현상금과 정답을 수여 할 때 투표를 맹세하지 마십시오. 당신을 도운 것을 위해 가십시오.
aaaaaaaaaaaa

현상금을 처리하는 나쁜 방법입니다. 기본적으로 자신을 판단 할 수 없으므로 공감대가 결정하게합니다 ... 어쨌든, 경험하는 것은 일반적인 충돌 지터입니다. 최대 침투량, 최소 속도 또는 일단 도달 한 '제한'의 다른 형태를 설정하면 루틴이 움직임을 멈추고 물체를 놓을 수 있습니다. 쓸모없는 점검을 피하기 위해 물체에 휴식 상태를 추가 할 수도 있습니다.
Darkwings

@Darkwings-이 시나리오의 커뮤니티는 가장 좋은 답변이 무엇인지에 대해 나보다 더 잘 알고 있다고 생각합니다. 그렇기 때문에 공직자들이 내 결정에 영향을 미칩니다. 내가 가장 upvotes와 솔루션을 시도하고 있다면 분명히 그것은 하지 않았다 나는 그 대답 수상 않을 것, 나에게 도움이됩니다.
Ste

답변:


19

물리 시뮬레이션 루프를 개선하는 데 필요한 단계는 다음과 같습니다.

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);
}

"시간이 코드 어딘가에 나타나야합니다." 당신은 모든 곳에서 1을 곱하는 것이 좋은 생각이 아니라 필수라고 광고하고 있습니다. 조정 가능한 타임 스텝은 훌륭한 기능이지만 반드시 필수는 아닙니다.
aaaaaaaaaaaa

@ eBusiness : 내 주장은 조정 가능한 시간 단계보다 일관성 및 오류 감지에 관한 것입니다. 나는 1을 곱하는 것이 필요하다고 말하지 않고, velocity += gravity틀렸다는 말만 velocity += gravity * timestep이해합니다. 결국 같은 결과를 줄 수 있지만 "내가 무엇을하고 있는지 알고 있습니다"라는 주석이 없으면 여전히 코딩 오류, 조잡한 프로그래머, 물리학에 대한 지식 부족 또는 필요한 프로토 타입 코드를 의미합니다 향상됩니다.
sam hocevar

당신은 그것이 잘못되었다고 말할 때, 그것은 나쁜 습관이라는 것입니다. 문제에 대한 주관적인 의견이며, 표현하는 것이 좋습니다. 그러나 이와 관련하여 코드가 의도 한대로 정확하게 수행하므로 주관적입니다. 내가 요구하는 것은 당신이 당신의 게시물에서 주관과 목표의 차이를 분명히하기 위해서입니다.
aaaaaaaaaaaa

2
@eBusiness : 솔직히, 그것은 이다 제정신 표준에 의해 잘못. 1) 속도와 중력을 추가하는 것이 실제로 아무 의미도 없기 때문에 코드는 "의도 한대로"수행하지 않습니다. 이 적당한 결과를 제공하는 경우에 저장되어있는 값이 때문에 2) 그건 gravity... 실제로 하지 중력. 하지만 게시물에서 더 명확하게 만들 수 있습니다.
sam hocevar

반대로, 그것을 잘못 부르는 것은 정상적인 표준에 의해 잘못되었습니다. 여러분은 중력이라는 이름의 변수에 중력이 저장되어 있지 않고, 대신 숫자가 있으며, 그것이 앞으로있을 모든 것, 우리가 상상하는 관계를 넘어서서 물리학과는 아무런 관련이 없습니다. 다른 숫자는 변경되지 않습니다. 외관상 변화하는 것은 코드와 물리 사이의 정신 연결을 만드는 능력 및 / 또는 의지입니다. 그건 그렇고 다소 흥미로운 심리적 관찰입니다.
aaaaaaaaaaaa

6

최소 수직 속도를 사용하여 바운스를 중지하는 검사를 추가하십시오. 최소한의 바운스를 받으면 공을 땅에 놓으십시오.

MIN_BOUNCE = <0.01 e.g>;

if( Velocity.Y < MIN_BOUNCE ){
    Velocity.Y = 0;
    Position.Y = <ground position Y>;
}

3
이 솔루션이 마음에 들지만 바운스를 Y 축으로 제한하지는 않습니다. 충돌 지점에서 충돌체의 법선을 계산하고 충돌 속도의 크기가 바운스 임계 값보다 큰지 확인합니다. OP의 세계에서 Y 바운스 만 허용하더라도 다른 사용자가보다 일반적인 솔루션이 도움이 될 수 있습니다. (확실하지 않은 경우 임의의 지점에서 두 개의 구를 함께 튀는 것을 생각하십시오)
brandon

@ brandon, 좋아, 정상적인 경우 더 잘 작동합니다.
Zhen

1
@ 젠, 표면의 법선을 사용하면 공이 중력의 법선과 평행하지 않은 법선을 가진 표면에 달라 붙을 가능성이 있습니다. 가능한 경우 중력을 계산에 고려하려고합니다.
Nic Foster

이 해법들 중 어느 것도 속도를 0으로 설정해서는 안된다. 바운스 임계 값에 따라 벡터의 법선을 가로 지르는 반사만을 제한한다
brandon

1

그래서, 이것이 왜 일어나는가의 문제는 당신의 공이 한계에 다다 랐다는 것입니다. 수학적으로 볼은 표면에서 멈추지 않고 표면에 접근합니다.

그러나 게임에서 연속 시간을 사용하지 않습니다. 미분 방정식에 근사값을 사용하는 맵입니다. 그리고이 제한 상황에서는 근사값이 유효하지 않습니다 (할 수는 있지만 더 작고 작은 시간 단계를 수행해야합니다.

물리적으로 말하면, 볼이 표면에 매우 가까이 있을 때 총 힘이 주어진 임계 값보다 낮 으면 볼이 붙습니다 .

시스템이 동 질적이라면 @Zhen의 대답은 괜찮을 것입니다. y 축에 중력이 있습니다.

따라서 해결책은 속도가 주어진 임계 값보다 커야한다는 것이 아니라 업데이트 후 볼에 적용된 총 힘은 주어진 임계 값보다 커야한다는 것입니다.

그 힘은 공의 벽에 가해진 힘 + 중력의 기여입니다.

조건은 다음과 같아야합니다

if (newVelocity + Physics.Gravity.Force <threshold)

바운스가 바닥 벽에 있고 중력이 음수이면 newVelocity.y는 양수입니다.

또한 newVelocity와 Physics.Gravity.Force의 크기는 동일하지 않습니다.

Velocity += Physics.Gravity.Force;

당신처럼, 나는 delta_time = 1이고 ballMass = 1이라고 가정합니다.

도움이 되었기를 바랍니다


1

충돌 점검 내부의 위치 업데이트가 중복되어 잘못되었습니다. 그리고 공에 에너지를 추가하여 영구적으로 움직일 수 있도록 도와줍니다. 중력이 일부 프레임에 적용되지 않으면 서 이상한 움직임이 발생합니다. 제거하십시오.

이제 공이 지정된 영역 밖에서 "고정되어"앞뒤로 튀는다는 다른 문제가 나타날 수 있습니다.

이 문제를 해결하는 간단한 방법은 공을 변경하기 전에 공이 올바른 방향으로 움직이는 지 확인하는 것입니다.

따라서 다음을 만들어야합니다.

if (Position.X < 0 || Position.X > GraphicsViewport.Width - Texture.Width)

으로:

if ((Position.X < 0 && Velocity.X < 0) || (Position.X > GraphicsViewport.Width - Texture.Width && Velocity.X > 0))

그리고 Y 방향과 비슷합니다.

공이 잘 멈추려면 어느 시점에서 중력을 멈춰야합니다. 현재 구현에서는 공이 지하에있는 한 중력이 제동되지 않기 때문에 항상 공이 다시 표면에 나타납니다. 항상 중력을 적용하도록 변경해야합니다. 그러나 이것은 정착 후 공이 천천히 바닥으로 가라 앉게합니다. 이를위한 빠른 해결책은 중력을 적용한 후 볼이 표면보다 아래에 있고 아래로 이동하는 경우 중지합니다.

Velocity += Physics.Gravity.Force;
if(Position.Y > GraphicsViewport.Height - Texture.Height && Velocity.Y > 0)
{
    Velocity.Y = 0;
}

이러한 총합의 변화는 적절한 시뮬레이션을 제공해야합니다. 그러나 여전히 매우 간단한 시뮬레이션입니다.


0

모든 속도 변화에 대해 뮤 테이터 방법을 사용하고, 그 방법 내에서 업데이트 된 속도를 확인하여 속도가 느리게 움직여 정지 상태인지 판단 할 수 있습니다. 내가 아는 대부분의 물리 시스템은 이것을 '복원'이라고 부릅니다.

public Vector3 Velocity
{
    public get { return velocity; }
    public set
    {
        velocity = value;

        // We get the direction that gravity pulls in
        Vector3 GravityDirection = gravity;
        GravityDirection.Normalize();

        Vector3 VelocityDirection = velocity;
        VelocityDirection.Normalize();

        if ((velocity * GravityDirection).SquaredLength() < 0.25f)
        {
            velocity.Y = 0.0f;
        }            
    }
}
private Vector3 velocity;

위의 방법에서는 중력과 같은 축을 따라 튀는 것을 제한합니다.

볼이지면과 충돌 할 때마다 감지해야 할 것이 있으며 충돌시 상당히 느리게 움직이는 경우 중력 축을 따라 속도를 0으로 설정하십시오.


이것이 유효하기 때문에 공감하지는 않지만 속도 임계 값이 아닌 바운스 임계 값에 대해 질문합니다. 바운싱 중 지 터링 효과는 일반적으로 정지 상태에서 속도를 계속 계산하는 효과와 일반적으로 다르기 때문에 경험상 거의 항상 분리되어 있습니다.
brandon

그들은 같은 것입니다. Havok 또는 PhysX와 같은 물리 엔진 및 JigLibX는 선형 속도 (및 각속도)를 기반으로합니다. 이 방법은 튀는 것을 포함하여 공의 모든 움직임에 효과적입니다. 사실, 마지막으로 진행 한 프로젝트 (LEGO Universe)는 동전이 느려지면 동전이 튀는 것을 막기 위해 거의 동일한 방법을 사용했습니다. 이 경우 우리는 동적 물리학을 사용하지 않았기 때문에 Havok이 처리하도록하는 대신 수동으로 처리해야했습니다.
Nic Foster

@ NicFoster : 내 마음에 관해서는 객체가 수평으로 거의 수직으로 거의 움직이지 않을 수 있으므로 혼란 스럽습니다.이 경우 메서드가 트리거되지 않습니다. OP는 속도 길이가 길어도 수직 거리가 0으로 설정되기를 원한다고 생각합니다.
George Duckett

@GeorgeDuckett : 아 감사합니다, 나는 원래의 질문을 오해했습니다. OP는 공의 움직임을 멈추지 않고 수직 움직임을 멈추기 만합니다. 수신 거부 속도 만 설명하도록 답변을 업데이트했습니다.
Nic Foster

0

다른 것 : 당신은 마찰 상수를 곱하고 있습니다. 마찰 상수는 낮지 만 바운스에 고정 된 에너지 흡수를 추가하십시오. 이렇게하면 마지막 바운스가 훨씬 빨라집니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.