Top Fiziği: Top dinlenirken son sekmeleri yumuşatmak


12

Küçük zıplayan top oyunumda başka bir konuya karşı geldim.

Topum dinlenmek üzere olan son anlar dışında topun etrafında zıplıyor. Topun hareketi ana kısım için pürüzsüzdür, ancak sona doğru, top ekranın altına yerleşirken bir süre sarsılır.

Bunun neden olduğunu anlayabiliyorum ama düzeltemiyorum.

Sunulabilecek herhangi bir tavsiye için minnettar olurum.

Güncelleme kodum:

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

Belki de işaret olmalıdır Gravity, Resistance Grassve Concretetüre aittir Vector2.


Sadece bunu doğrulamak için: top bir yüzeye çarptığında "sürtünme" değeriniz <1 değeridir, temel olarak restitüsyon katsayısı doğru mu?
Jorge Leitao

@ JCLeitão - Doğru.
Ste

Ödül ve doğru cevabı verirken lütfen oylara uymaya yemin etmeyin. Sana yardım eden her şeyi yap.
aaaaaaaaaaaa

Bu bir ödülün üstesinden gelmenin kötü bir yolu, temelde kendinizi yargılayamayacağınızı söylüyorsunuz, bu yüzden yukarı oyların karar vermesine izin verdiniz ... Her neyse, karşılaştığınız şey ortak bir çarpışma titremesidir. Bu, maksimum bir iç içe geçme miktarı, minimum hız veya bir kez ulaşıldığında, rutininizin hareketi durdurmasına ve nesneyi dinlendirmesine neden olacak herhangi bir 'sınır' biçimini ayarlayarak çözülebilir. Ayrıca, gereksiz kontrollerden kaçınmak için nesnelerinize bir dinlenme durumu eklemek isteyebilirsiniz.
Darkwings

@Darkwings - Bence bu senaryodaki topluluk en iyi cevabın ne olduğunu benden daha iyi biliyor. Bu yüzden upvotes kararımı etkileyecek. Açıkçası, çözümü en fazla oyla denedim ve bana yardımcı olmadıysa , bu cevaba ödül vermem.
Ste

Yanıtlar:


19

İşte fizik simülasyon döngünüzü geliştirmek için gerekli adımlar.

1. Zaman Aşımı

Kodunuzla görebildiğim temel sorun, fizik adım süresini hesaba katmamasıdır. Position += Velocity;Birimlerin eşleşmediği için yanlış bir şey olduğu açık olmalıdır . Ya Velocityaslında bir hız değil ya da bir şey eksik.

Senin hız ve yerçekimi değerleri her kare bir zaman biriminde gerçekleşir şekilde ölçekli bile 1(yani mesela. Velocity Aslında demektir mesafe , bir saniyede gitti) zaman yere görünmelidir ya kodunuzda örtük böylece değişkenleri düzelterek ( isimleri gerçekten depoladıkları şeyi yansıtır) veya açıkça (bir zaman çizelgesi getirerek). Yapılacak en kolay şeyin zaman birimini beyan etmek olduğuna inanıyorum:

float TimeStep = 1.0;

Ve bu değeri ihtiyaç duyulan her yerde kullanın:

Velocity += Physics.Gravity.Force * TimeStep;
Position += Velocity * TimeStep;
...

İyi bir derleyicinin çarpımları basitleştireceğine dikkat edin 1.0, böylece bu parça işleri yavaşlatmaz.

Şimdi Position += Velocity * TimeStephala tam olarak doğru değil ( nedenini anlamak için bu soruya bakın ) ama muhtemelen şimdilik yapacak.

Ayrıca, bunun dikkate alınması gerekir:

Velocity *= Physics.Air.Resistance;

Düzeltmek biraz daha zor; olası bir yol:

Velocity -= Vector2(Math.Pow(Physics.Air.Resistance.X, TimeStep),
                    Math.Pow(Physics.Air.Resistance.Y, TimeStep))
          * Velocity;

2. Çift güncelleme

Şimdi zıplarken ne yaptığınızı kontrol edin (yalnızca ilgili kod gösterilir):

Position += Velocity * TimeStep;
if (Position.Y < 0)
{
    Velocity.Y = -Velocity.Y * Physics.Surfaces.Grass;
    Position.Y = Position.Y + Velocity.Y * TimeStep;
}

TimeStepSıçrama sırasında bunun iki kez kullanıldığını görebilirsiniz . Bu temelde topa kendini güncellemesi için iki kat daha fazla zaman veriyor. Bunun yerine ne olması gerekiyor:

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. Yerçekimi

Kodun bu bölümünü şimdi kontrol edin:

if(Position.Y < GraphicsViewport.Height - Texture.Height)
{
    Velocity += Physics.Gravity.Force * TimeStep;
}            

Çerçevenin tüm süresi boyunca yerçekimi eklersiniz. Peki ya top gerçekten bu çerçevede sekerse? Daha sonra hız ters çevrilecek, ancak eklenen yerçekimi topun yerden uzaklaşmasını sağlayacaktır! Bu nedenle, zıplarken aşırı yer çekiminin kaldırılması ve ardından doğru yönde yeniden eklenmesi gerekecektir .

Yerçekiminin doğru yönde yeniden eklenmesi bile hızın çok fazla hızlanmasına neden olabilir. Bundan kaçınmak için yerçekimi ilavesini atlayabilirsiniz (sonuçta o kadar da değil ve sadece bir çerçeve sürer) veya hızı sıfıra kelepçeleyebilirsiniz.

4. Sabit kod

İşte tam güncellenmiş kod:

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. Diğer ilaveler

Daha iyi simülasyon kararlılığı için fizik simülasyonunuzu daha yüksek bir frekansta çalıştırmaya karar verebilirsiniz. Bu, yukarıdaki değişikliklerle önemsizdirTimeStep , çünkü çerçevenizi istediğiniz kadar parçaya bölmeniz yeterlidir. Örneğin:

public void Update()
{
    float TimeStep = 1.0;
    Update(TimeStep / 4);
    Update(TimeStep / 4);
    Update(TimeStep / 4);
    Update(TimeStep / 4);
}

"zaman kodunuzda bir yerde görünmelidir." Her yerde 1 ile çarpmanın sadece iyi bir fikir olmadığını, zorunlu olduğunu mu söylüyorsunuz? Ayarlanabilir bir zaman aşımı güzel bir özellik olduğundan emin olun, ancak kesinlikle zorunlu değildir.
aaaaaaaaaaaa

@eBusiness: Benim iddiam, tutarlılık ve hataları algılama hakkında ayarlanabilir zaman aralıklarından çok daha fazlası. 1 ile çarpmanın gerekli olduğunu söylemiyorum velocity += gravity, yanlış olduğunu ve sadece velocity += gravity * timestepmantıklı olduğunu söylüyorum . Sonunda aynı sonucu verebilir, ancak "Burada ne yaptığımı biliyorum" diyen bir yorum olmadan, yine de bir kodlama hatası, özensiz bir programcı, fizik hakkında bilgi eksikliği veya sadece gereken prototip kodu anlamına gelir. geliştirilebilir.
sam hocevar

Bunun yanlış olduğunu söylüyorsun, demek istediğini söylemek istediğin kötü uygulama. Konuyla ilgili öznel görüşünüz ve bunu ifade etmeniz iyi, ancak bu bağlamdaki kodun tam olarak amaçlandığı gibi öznel olduğu. Tek istediğim, gönderinizde öznel ve objektif arasındaki farkı netleştirmeniz.
aaaaaaaaaaaa

2
@eBusiness: dürüst olmak gerekirse, olduğu herhangi aklı başında standardına göre yanlış. Kod, “olması gerektiği gibi yapmaz”, çünkü 1) hız ve yerçekimi eklemek aslında hiçbir şey ifade etmez; ve 2) makul bir sonuç verirse, bunun nedeni depolanan değerin gravityaslında yerçekimi olmamasıdır . Ama bunu postta daha net yapabilirim.
sam hocevar

Aksine, yanlış söylemek herhangi bir aklı başında standart tarafından yanlıştır. Yerçekiminin yerçekimi adlı değişkende depolanmadığından haklısınız, bunun yerine bir sayı var ve her şey olacak, fizikle hayal ettiğimiz ilişkinin ötesinde, onunla çarparak herhangi bir ilişkisi yok. başka bir sayı bunu değiştirmez. Görünüşte değiştiği, kod ve fizik arasındaki zihinsel bağlantıyı kurma yeteneğiniz ve / veya isteğinizdir. Bu arada oldukça ilginç bir psikolojik gözlem.
aaaaaaaaaaaa

6

Minimum dikey hız kullanarak zıplamayı durdurmak için bir kontrol ekleyin. Ve en az zıplamayı aldığınızda, topu yere koyun.

MIN_BOUNCE = <0.01 e.g>;

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

3
Bu çözümü beğendim, ancak sıçramayı Y ekseniyle sınırlamam. Çarpışma noktasında çarpıştırıcının normalini hesaplar ve çarpışma hızının büyüklüğünün sıçrama eşiğinden büyük olup olmadığını kontrol ederdim. OP dünyası sadece Y sıçramasına izin verse bile, diğer kullanıcılar daha genel bir çözümü faydalı bulabilir. (Eğer net değilsem, iki küreyi rastgele bir noktada zıplamayı düşünün)
brandon

@brandon, harika, normal ile daha iyi çalışmalı.
Zhen

1
@Zhen, yüzeyin normalini kullanırsanız, topun yerçekimine paralel olmayan normal bir yüzeye yapışmasını sağlayabilirsiniz. Mümkünse yerçekimini hesaplamaya dahil ederdim.
Nic Foster

Bu çözümlerin hiçbiri 0 değerini ayarlamamalıdır. Sıçrama eşiğine bağlı olarak yansımayı yalnızca vektörün normaline sınırlandırırsınız
brandon

1

Sanırım, bunun olmasının nedeni topun bir sınıra yaklaşması. Matematiksel olarak, top asla yüzeyde durmaz, yüzeye yaklaşır.

Ancak oyununuz sürekli bir zaman kullanmıyor. Diferansiyel denkleme bir yaklaşım kullanan bir haritadır. Ve bu yaklaşım bu sınırlayıcı durumda geçerli değildir (bunu yapabilirsiniz, ancak mümkün olmadığını düşündüğüm daha küçük ve daha küçük zaman adımları atmanız gerekir.

Fiziksel olarak konuşursak, top yüzeye çok yakın olduğunda , toplam kuvvet belirli bir eşiğin altındaysa ona yapışır .

@ Sisteminiz homojen ise zhen cevap iyi olurdu. Y ekseninde bir miktar yerçekimi vardır.

Bu nedenle, çözümün hızın belirli bir eşik değerinin altında olması gerekmeyeceğini söyleyebilirim, ancak güncellemeden sonra topa uygulanan toplam kuvvet belirli bir eşiğin altında olmalıdır.

Bu kuvvet, duvar tarafından topun + yerçekimi üzerindeki kuvvetin katkısıdır.

Durum o zaman böyle bir şey olmalı

eğer (newVelocity + Physics.Gravity.Force <eşik)

Sıçrama botton duvarındaysa ve yerçekimi negatif bir miktarsa, newVelocity.y'nin pozitif bir miktar olduğuna dikkat edin.

Ayrıca, newVelocity ve Physics.Gravity.Force'un yazdığınız boyutlarla aynı olmadığına dikkat edin.

Velocity += Physics.Gravity.Force;

yani sizin gibi delta_time = 1 ve ballMass = 1 olduğunu varsayıyorum.

Bu yardımcı olur umarım


1

Çarpışma kontrolünüz içinde bir pozisyon güncellemeniz var, yedekli ve yanlış. Ve topa enerji katar ve böylece sürekli hareket etmesine yardımcı olur. Bazı karelere uygulanmayan yerçekimi ile birlikte bu garip hareketlerinizi verir. Onu kaldır.

Şimdi, topun belirlenen alanın dışına "sıkışıp" kaldığı ve sürekli ileri geri sıçradığı farklı bir sorun görebilirsiniz.

Bu sorunu çözmenin basit bir yolu, topu değiştirmeden önce topun doğru yönde hareket ettiğini kontrol etmektir.

Böylece şunları yapmalısınız:

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

İçine:

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

Y yönüne benzer.

Topun güzel durması için yerçekimini bir noktada durdurmanız gerekir. Mevcut uygulamanız, yerçekimi yeraltında olduğu sürece topu frenlemediği için topun her zaman yeniden ortaya çıkmasını sağlar. Her zaman yerçekimi uygulamasına geçmelisiniz. Ancak bu, topun yerleştikten sonra yavaşça toprağa batmasına neden olur. Bunun için hızlı bir düzeltme, yerçekimi uygulandıktan sonra, top yüzey seviyesinin altındaysa ve aşağı doğru hareket ediyorsa, durdurun:

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

Toplamdaki bu değişiklikler size iyi bir simülasyon vermelidir. Ancak bunun hala çok basit bir simülasyon olduğunu unutmayın.


0

Herhangi bir ve tüm hız değişiklikleri için bir mutatör yönteminiz var, o zaman bu yöntemde güncellenmiş hızı kontrol etmek için yeterince yavaş hareket edip etmediğini kontrol edebilirsiniz. Bildiğim çoğu fizik sistemi buna 'iade' diyor.

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;

Yukarıdaki yöntemde, yerçekimi ile aynı eksen boyunca olduğunda sıçramayı sınırlandırıyoruz.

Dikkate alınması gereken başka bir şey, bir top yere çarptığında tespit etmek olabilir ve çarpışma anında oldukça yavaş hareket ediyorsa, yerçekimi ekseni boyunca hızı sıfıra ayarlayın.


Bu geçerli olmadığından aşağı oy vermeyeceğim, ancak soru hız eşiklerini değil, sıçrama eşiklerini soruyor. Bunlar deneyimlerime göre neredeyse her zaman ayrıdır çünkü zıplatma sırasında titremenin etkisi genellikle görsel olarak dinlendikten sonra hızı hesaplamaya devam etmenin etkisinden ayrıdır.
brandon

Onlar bir arada. Havok veya PhysX gibi fizik motorları ve doğrusal hızda (ve açısal hızda) JigLibX temel restitüsyonu. Bu yöntem, zıplatma dahil topun her hareketi için çalışmalıdır. Aslında, üzerinde çalıştığım son proje (LEGO Universe), paralar yavaşladığında zıplamayı durdurmak için neredeyse aynı bir yöntem kullandı. Bu durumda dinamik fizik kullanmıyorduk, bu yüzden Havok'un bizim için halletmesine izin vermek yerine manuel olarak yapmak zorunda kaldık.
Nic Foster

@NicFoster: Aklıma göre, bir nesne yatay olarak ve neredeyse hiç dikey olarak çok hızlı hareket ediyor olabilir, bu durumda yönteminiz tetiklemez. Hız hızının yüksek olmasına rağmen OP'nin dikey mesafenin sıfıra ayarlanmasını istediğini düşünüyorum.
George Duckett

@ GeorgeDuckett: Ah teşekkür ederim, orijinal soruyu yanlış anladım. OP topun durmasını istemiyor, sadece dikey hareketi durdurun. Sadece zıplayan hız için hesap yanıtını güncelledim.
Nic Foster

0

Başka bir şey: Sürtünme sabiti ile çarpıyorsunuz. Bunu değiştirin - sürtünme sabitini düşürün, ancak sıçramada sabit bir enerji emilimi ekleyin. Bu, son sıçramaları çok daha hızlı nemlendirir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.