Fisika Bola: Menghaluskan pantulan terakhir saat bola tiba untuk beristirahat


12

Saya menghadapi masalah lain dalam permainan bola kecil saya yang memantul.

Bola saya memantul dengan baik kecuali untuk saat-saat terakhir ketika akan beristirahat. Pergerakan bola halus untuk bagian utama tetapi, menjelang akhir, bola tersentak untuk sementara waktu saat mengendap di bagian bawah layar.

Saya bisa mengerti mengapa ini terjadi tetapi saya tidak bisa memuluskannya.

Saya akan berterima kasih atas saran yang dapat ditawarkan.

Kode pembaruan saya adalah:

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

Mungkin saya juga harus menunjukkan itu Gravity, Resistance Grassdan Concretesemuanya tipenya Vector2.


Hanya untuk mengkonfirmasi ini: "gesekan" Anda ketika bola menyentuh permukaan adalah nilai <1, yang pada dasarnya adalah koefisien ganti rugi yang benar?
Jorge Leitao

@ JCLeitão - Benar.
Ste

Tolong jangan bersumpah untuk mematuhi suara ketika Anda memberi hadiah dan jawaban yang benar. Pergi untuk apa pun yang membantu Anda.
aaaaaaaaaaaa

Itu cara yang buruk untuk menangani hadiah, pada dasarnya Anda mengatakan bahwa Anda tidak bisa menilai diri sendiri sehingga Anda membiarkan upvotes memutuskan ... Pokoknya, apa yang Anda alami adalah jitter collision yang umum. Itu dapat dipecahkan dengan mengatur jumlah antarpenetrasi maksimum, kecepatan minimum atau bentuk 'batas' lain yang pernah dicapai akan menyebabkan rutinitas Anda menghentikan gerakan dan meletakkan objek untuk beristirahat. Anda mungkin juga ingin menambahkan status istirahat ke objek Anda untuk menghindari pemeriksaan yang tidak berguna.
Darkwings

@Darkwings - Saya pikir komunitas dalam skenario ini lebih tahu daripada saya tentang apa jawaban terbaik. Itulah sebabnya upvotes akan memengaruhi keputusan saya. Jelas, jika saya mencoba solusi dengan upvotes paling banyak dan itu tidak membantu saya, maka saya tidak akan memberi penghargaan untuk jawaban itu.
Ste

Jawaban:


19

Berikut langkah-langkah yang diperlukan untuk meningkatkan loop simulasi fisika Anda.

1. Timestep

Masalah utama yang bisa saya lihat dengan kode Anda adalah bahwa ia tidak memperhitungkan waktu langkah fisika. Harus jelas bahwa ada sesuatu yang salah dengan Position += Velocity;unit tidak cocok. Entah Velocitysebenarnya bukan kecepatan, atau ada sesuatu yang hilang.

Bahkan jika kecepatan dan nilai gravitasi Anda diskalakan sedemikian rupa sehingga setiap frame terjadi pada satuan waktu 1(yang berarti bahwa mis. Velocity Sebenarnya berarti jarak yang ditempuh dalam satu detik), waktu harus muncul di suatu tempat dalam kode Anda, baik secara implisit (dengan memperbaiki variabel sehingga nama mereka mencerminkan apa yang sebenarnya mereka simpan) atau secara eksplisit (dengan memperkenalkan stempel waktu). Saya percaya hal yang paling mudah untuk dilakukan adalah mendeklarasikan satuan waktu:

float TimeStep = 1.0;

Dan gunakan nilai itu di mana pun dibutuhkan:

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

Perhatikan bahwa kompiler yang layak akan menyederhanakan perkalian dengan 1.0, sehingga bagian itu tidak akan membuat segalanya lebih lambat.

Sekarang Position += Velocity * TimeStepmasih belum sepenuhnya tepat (lihat pertanyaan ini untuk memahami mengapa) tetapi mungkin akan dilakukan untuk saat ini.

Juga, ini perlu mempertimbangkan:

Velocity *= Physics.Air.Resistance;

Agak sulit untuk diperbaiki; salah satu cara yang mungkin adalah:

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

2. Menggandakan pembaruan

Sekarang periksa apa yang Anda lakukan ketika memantul (hanya kode yang relevan ditampilkan):

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

Anda dapat melihat bahwa TimeStepdigunakan dua kali selama pentalan. Ini pada dasarnya memberi bola dua kali lebih banyak waktu untuk memperbarui sendiri. Inilah yang seharusnya terjadi:

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. Gravitasi

Periksa bagian kode ini sekarang:

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

Anda menambahkan gravitasi untuk seluruh durasi bingkai. Tetapi bagaimana jika bola benar-benar memantul selama bingkai itu? Maka kecepatan akan terbalik, tetapi gravitasi yang ditambahkan kemudian akan membuat bola berakselerasi dari tanah! Jadi kelebihan gravitasi harus dihilangkan ketika memantul , kemudian ditambahkan kembali ke arah yang benar.

Mungkin saja terjadi bahwa bahkan menambahkan kembali gravitasi ke arah yang benar akan menyebabkan kecepatan terlalu cepat. Untuk menghindari ini, Anda bisa melewati penambahan gravitasi (setelah semua, itu tidak banyak dan hanya berlangsung bingkai) atau menjepit kecepatan ke nol.

4. Kode tetap

Dan di sini adalah kode yang sepenuhnya diperbarui:

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. Penambahan lebih lanjut

Untuk stabilitas simulasi yang lebih baik, Anda dapat memutuskan untuk menjalankan simulasi fisika Anda pada frekuensi yang lebih tinggi. Ini dibuat sepele oleh perubahan-perubahan di atas yang melibatkan TimeStep, karena Anda hanya perlu membagi frame Anda menjadi sebanyak yang Anda inginkan. Contohnya:

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

"Waktu harus muncul di suatu tempat dalam kode Anda." Anda beriklan bahwa mengalikan dengan 1 di semua tempat bukan hanya ide yang baik, itu wajib? Tentu tanda waktu yang dapat disesuaikan adalah fitur yang bagus, tetapi pastinya itu tidak wajib.
aaaaaaaaaaaa

@ eBusiness: argumen saya lebih banyak tentang konsistensi dan mendeteksi kesalahan daripada tentang timesteps yang dapat disesuaikan. Saya tidak mengatakan mengalikan dengan 1 adalah perlu, saya katakan velocity += gravitysalah dan hanya velocity += gravity * timestepmasuk akal. Ini mungkin memberikan hasil yang sama pada akhirnya, tetapi tanpa komentar yang mengatakan "Saya tahu apa yang saya lakukan di sini" itu masih berarti kesalahan pengkodean, programmer yang ceroboh, kurangnya pengetahuan tentang fisika, atau hanya kode prototipe yang perlu ditingkatkan.
sam hocevar

Anda mengatakan itu salah , ketika apa yang seharusnya Anda katakan adalah itu adalah praktik buruk. Itu adalah pendapat subjektif Anda tentang masalah ini, dan Anda boleh menyatakannya, tetapi itu subjektif karena kode dalam hal ini melakukan persis seperti yang dimaksudkan. Yang saya minta adalah Anda membuat perbedaan antara subyektif dan obyektif jelas dalam posting Anda.
aaaaaaaaaaaa

2
@eBusiness: jujur, itu adalah salah dengan standar apapun waras. Kode tidak "melakukan apa yang dimaksudkan untuk" sama sekali, karena 1) menambah kecepatan dan gravitasi sebenarnya tidak berarti apa-apa; dan 2) jika memberikan hasil yang masuk akal itu karena nilai yang disimpan gravitysebenarnya ... bukan gravitasi. Tapi saya bisa membuatnya lebih jelas di posting.
sam hocevar

Sebaliknya, menyebutnya salah adalah salah dengan standar waras. Anda benar bahwa gravitasi tidak disimpan dalam variabel bernama gravitasi, melainkan ada bilangan, dan hanya itu yang akan terjadi, tidak ada kaitannya dengan fisika di luar hubungan yang kita bayangkan, gandakan dengan nomor lain tidak mengubah itu. Yang tampaknya tidak berubah adalah kemampuan dan / atau kesediaan Anda untuk membuat hubungan mental antara kode dan fisika. By the way pengamatan psikologis yang agak menarik.
aaaaaaaaaaaa

6

Tambahkan tanda centang untuk menghentikan bouncing, menggunakan kecepatan vertikal minimal. Dan saat Anda mendapatkan pantulan minimal, atur bola ke tanah.

MIN_BOUNCE = <0.01 e.g>;

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

3
Saya suka solusi ini, tapi saya tidak akan membatasi bouncing ke sumbu Y. Saya akan menghitung normal collider pada titik tumbukan dan memeriksa apakah besarnya kecepatan tumbukan lebih besar dari ambang pantulan. Sekalipun dunia OP hanya memungkinkan Y memantul, pengguna lain mungkin menemukan solusi yang lebih umum bermanfaat. (Jika saya tidak jelas, pikirkan untuk memantulkan dua bidang secara bersamaan)
brandon

@ brandon, bagus, itu seharusnya bekerja lebih baik dengan normal.
Zhen

1
@Zhen, jika Anda menggunakan permukaan normal, Anda memiliki kemungkinan bola berakhir menempel ke permukaan yang memiliki normal yang tidak sejajar dengan gravitasi. Saya akan mencoba dan memasukkan faktor gravitasi ke dalam perhitungan jika memungkinkan.
Nic Foster

Tak satu pun dari solusi ini yang dapat mengatur kecepatan apa pun ke 0. Anda hanya membatasi pantulan di seluruh normal vektor tergantung pada ambang pantulan
brandon

1

Jadi, saya pikir masalah mengapa ini terjadi adalah bahwa bola Anda mendekati batas. Secara matematis, bola tidak pernah berhenti di permukaan, ia mendekati permukaan.

Namun, game Anda tidak menggunakan waktu terus menerus. Ini adalah peta, yang menggunakan perkiraan persamaan diferensial. Dan perkiraan itu tidak valid dalam situasi terbatas ini (Anda bisa, tetapi Anda harus mengambil langkah-langkah lebih kecil dan lebih kecil, yang saya anggap tidak layak.

Secara fisik, yang terjadi adalah ketika bola sangat dekat dengan permukaan, ia menempel padanya jika kekuatan total di bawah ambang batas yang diberikan.

@Zhen jawab akan baik-baik saja jika sistem Anda homogen, yang tidak Ini memiliki beberapa gravitasi pada sumbu y.

Jadi, saya akan mengatakan bahwa solusinya bukan bahwa kecepatan harus di bawah ambang yang diberikan, tetapi total gaya yang diterapkan pada bola setelah pembaruan harus di bawah ambang yang diberikan.

Gaya itu adalah kontribusi gaya yang diberikan oleh dinding pada bola + gravitasi.

Kondisinya harus seperti itu

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

perhatikan bahwa newVelocity.y adalah kuantitas positif jika bouncingnya ada di dinding kapas, dan gravitasi adalah kuantitas negatif.

Juga perhatikan bahwa newVelocity dan Physics.Gravity.Force tidak memiliki dimensi yang sama, seperti yang Anda tulis

Velocity += Physics.Gravity.Force;

artinya, seperti Anda, saya mengasumsikan delta_time = 1 dan ballMass = 1.

Semoga ini membantu


1

Anda memiliki pembaruan posisi di dalam cek tabrakan, itu mubazir, dan salah. Dan itu menambah energi pada bola sehingga berpotensi membantunya bergerak terus-menerus. Seiring dengan gravitasi yang tidak diterapkan pada beberapa frame, ini memberi Anda gerakan aneh. Singkirkan.

Sekarang Anda mungkin melihat masalah yang berbeda, bahwa bola "macet" di luar area yang ditentukan, terus memantul ke depan dan ke belakang.

Cara sederhana untuk menyelesaikan masalah ini adalah memeriksa apakah bola bergerak ke arah yang benar sebelum mengubahnya.

Dengan demikian Anda harus membuat:

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

Ke:

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

Dan serupa untuk arah Y.

Agar bola berhenti dengan baik, Anda perlu menghentikan gravitasi di beberapa titik. Implementasi Anda saat ini memastikan bahwa bola akan selalu muncul kembali karena gravitasi tidak mengerem selama ia berada di bawah tanah. Anda harus berubah untuk selalu menerapkan gravitasi. Namun ini mengarah ke bola yang perlahan-lahan tenggelam ke tanah setelah menetap. Perbaikan cepat untuk ini adalah, setelah menerapkan gravitasi, jika bola di bawah permukaan dan bergerak ke bawah, hentikan itu:

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

Perubahan-perubahan ini secara total akan memberi Anda simulasi yang layak. Tetapi perlu dicatat bahwa ini masih simulasi yang sangat sederhana.


0

Miliki metode mutator untuk setiap dan semua perubahan kecepatan, maka dalam metode itu Anda dapat memeriksa kecepatan yang diperbarui untuk menentukan apakah kecepatannya bergerak cukup lambat untuk membuatnya diam. Sebagian besar sistem fisika yang saya kenal menyebutnya 'restitusi'.

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;

Dalam metode di atas kita membatasi memantul setiap kali sepanjang sumbu yang sama dengan gravitasi.

Sesuatu yang lain untuk dipertimbangkan akan mendeteksi setiap kali bola bertabrakan dengan tanah, dan jika itu bergerak cukup lambat pada saat tabrakan, atur kecepatan sepanjang sumbu gravitasi ke nol.


Saya tidak akan mengundurkan diri karena ini valid, tetapi pertanyaannya adalah menanyakan tentang pentalan bouncing, bukan ambang batas kecepatan. Ini hampir selalu terpisah dalam pengalaman saya karena efek jittering selama memantul umumnya terpisah dari efek terus menghitung kecepatan setelah secara visual diam.
brandon

Mereka satu yang sama. Mesin fisika, seperti Havok, atau PhysX, dan restitusi dasar JigLibX pada kecepatan linier (dan kecepatan sudut). Metode ini harus bekerja untuk semua dan semua gerakan bola, termasuk memantul. Bahkan, proyek terakhir saya (LEGO Universe) menggunakan metode yang hampir identik dengan ini untuk menghentikan memantul koin setelah mereka melambat. Dalam hal itu kami tidak menggunakan fisika dinamis sehingga kami harus melakukannya secara manual daripada membiarkan Havok merawatnya untuk kami.
Nic Foster

@NicFoster: Saya bingung, dalam pikiran saya sebuah objek bisa bergerak sangat cepat secara horizontal dan hampir tidak secara vertikal dalam hal mana metode Anda tidak akan memicu. Saya pikir OP ingin jarak vertikal ditetapkan ke nol meskipun panjang kecepatannya tinggi.
George Duckett

@ GeorgeDuckett: Ah terima kasih, saya salah mengerti pertanyaan aslinya. OP tidak ingin bola berhenti bergerak, cukup hentikan gerakan vertikal. Saya telah memperbarui jawaban ke akun hanya untuk kecepatan yang memantul.
Nic Foster

0

Hal lain: Anda mengalikan konstanta gesekan. Ubah itu - turunkan konstanta gesekan tetapi tambahkan penyerapan energi tetap pada pantulan. Ini akan meredam pantulan terakhir lebih cepat.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.