Unlimited WordPress themes, graphics, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Game Development
  2. Programming
Gamedevelopment

Cara Buat Custom Enjin Fizik 2D: Enjin Teras

by
Difficulty:IntermediateLength:LongLanguages:
This post is part of a series called How to Create a Custom Physics Engine.
How to Create a Custom 2D Physics Engine: The Basics and Impulse Resolution
How to Create a Custom 2D Physics Engine: Friction, Scene and Jump Table

Malay (Melayu) translation by Aisyah Arrafah (you can also view the original English article)

Dalam bahagian ini saya mencipta enjin fizik 2D adat untuk permainan anda, kami akan menambah lebih banyak ciri kepada resolusi impuls yang kami buat pada bahagian pertama. Khususnya, kita akan melihat integrasi, penentuan masa, menggunakan reka bentuk modular untuk kod kami, dan pengesanan perlanggaran fasa yang meluas.


Pengenalan

Dalam jawatan terakhir dalam siri ini saya merangkumi topik resolusi impuls. Baca dahulu, jika anda belum lagi!

Mari kita menyelam terus ke dalam topik yang diliputi dalam artikel ini. Topik-topik ini adalah semua keperluan dari mana-mana enjin fizik yang layak, jadi sekarang adalah masa yang sesuai untuk membina lebih banyak ciri di atas resolusi inti dari artikel terakhir.


Integrasi

Integrasi sangat mudah dilaksanakan, dan terdapat banyak bidang di internet yang memberikan maklumat yang baik untuk integrasi berulang. Bahagian ini kebanyakannya akan menunjukkan bagaimana untuk melaksanakan fungsi integrasi yang betul, dan menunjuk ke beberapa lokasi yang berlainan untuk bacaan lanjut, jika dikehendaki.

Mula-mula ia harus tahu percepatan sebenarnya. Undang-undang Kedua Newton menyatakan:

\[Persamaan 1:\\
F = ma\]

Ini menyatakan bahawa jumlah semua kuasa yang bertindak ke atas objek adalah m sama dengan jisim objek yang didarab dengan percepatannya a. m adalah dalam kilogram, a adalah dalam meter/saat, dan F berada di Newtons.

Susun semula persamaan ini sedikit untuk diselesaikan untuk a hasil:

\[Persamaan 2:\\
a = \frac{F}{m}\\
\therefore\\
a = F * \frac{1}{m}\]

Langkah seterusnya melibatkan penggunaan pecutan untuk melancarkan objek dari satu lokasi ke lokasi yang lain. Oleh kerana permainan dipaparkan dalam bingkai berasingan diskret dalam animasi seperti ilusi, lokasi setiap kedudukan pada langkah-langkah diskret ini harus dikira. Untuk liputan yang lebih mendalam mengenai persamaan ini sila lihat: Demon Integrasi Erin Catto dari GDC 2009 dan tambahan Hannu kepada Euler yang simpatik untuk kestabilan yang lebih rendah dalam persekitaran FPS yang rendah.

Eksperimen Euler yang jelas (disebut "minyak") ditunjukkan dalam coretan berikut, di mana x adalah kedudukan dan v adalah halaju. Sila ambil perhatian bahawa 1/m * F ialah pecutan, seperti yang dijelaskan di atas:

dt di sini merujuk kepada masa delta. Δ ialah simbol untuk delta, dan boleh dibaca literal sebagai "perubahan dalam", atau ditulis sebagai Δt. Oleh itu, apabila anda melihat dt ia boleh dibaca sebagai "perubahan dalam masa". dv akan "berubah dalam halaju".

Ini akan berfungsi, dan biasanya digunakan sebagai titik permulaan. Walau bagaimanapun, ia mempunyai ketidaktepatan berangka yang boleh kita hapus tanpa usaha tambahan. Inilah yang dikenali sebagai Symplectic Euler:

Perhatikan bahawa semua yang saya lakukan adalah menyusun susunan dua baris kod - lihat artikel ">yang telah disebutkan di atas dari Hannu.

Siaran ini menerangkan ketidaktepatan berangka Euler Eksplisit, tetapi diberi amaran bahawa dia mula merangkumi RK4, yang saya tidak mencadangkan secara peribadi: gafferongames.com: Euler Inaccuracy.

Persamaan mudah ini adalah semua yang kita perlu untuk menggerakkan semua objek di sekitar dengan halaju linear dan percepatan.


Penangguhan masa

Oleh kerana permainan dipaparkan pada selang masa diskret, perlu ada cara untuk memanipulasi masa antara langkah-langkah ini dengan cara yang terkawal. Pernahkah anda melihat permainan yang akan berjalan pada kelajuan yang berbeza bergantung pada komputer yang dimainkan? Itulah contoh permainan yang berjalan pada kelajuan bergantung pada keupayaan komputer untuk menjalankan permainan.

Kami memerlukan satu cara untuk memastikan bahawa enjin fizik kami hanya berjalan apabila masa tertentu telah berlalu. Dengan cara ini, dt yang digunakan dalam pengiraan sentiasa nombor yang sama. Menggunakan nilai dt yang sama tepat di dalam kod anda di mana-mana sebenarnya akan membuat deterministik enjin fizik anda, dan dikenali sebagai tamat tempoh tetap. Ini adalah perkara yang baik.

Enjin fizik deterministik adalah salah satu yang akan sentiasa melakukan perkara yang sama setiap kali ia dijalankan dengan asumsi input yang sama diberikan. Ini penting untuk banyak jenis permainan di mana permainan perlu disesuaikan dengan tingkah laku enjin fizik. Ini juga penting untuk debug enjin fizik anda, kerana untuk menentukan bugs, tingkah laku enjin anda perlu konsisten.

Mari kita terlebih dahulu merangkumi satu versi mudah dari tenggat waktu yang tetap. Berikut adalah contohnya:

Ini menunggu, menjadikan permainan, sehingga masa yang cukup telah berlalu untuk mengemas kini fizik. Waktu yang berlalu direkodkan, dan potongan masa bersaiz dt diskrit diambil dari penumpuk dan diproses oleh fizik. Ini memastikan bahawa nilai yang sama tepat diberikan kepada fizik tidak kira apa, dan bahawa nilai yang disalurkan kepada fizik adalah perwakilan yang tepat dari masa sebenar yang berlalu dalam kehidupan sebenar. Ketulan dt dikeluarkan dari penumpuk sehingga penumpuk lebih kecil daripada sepotong dt.

Terdapat beberapa masalah yang boleh diperbetulkan di sini. Yang pertama melibatkan berapa lama masa yang diperlukan untuk benar-benar melaksanakan kemas kini fizik: Bagaimana jika kemas kini fizik terlalu lama dan penumpuk naik lebih tinggi dan lebih tinggi setiap gelung permainan? Ini dipanggil lingkaran kematian. Sekiranya ini tidak ditetapkan, enjin anda dengan cepat akan mengisar dengan berhenti sepenuhnya sekiranya fizik anda tidak dapat dilakukan dengan cepat.

Untuk menyelesaikannya, enjin sebenarnya perlu menjalankan kemas kini fizik yang lebih sedikit jika penumpuk menjadi terlalu tinggi. Cara mudah untuk melakukan ini adalah untuk mengikat penumpuk di bawah beberapa nilai sewenang-wenangnya.

Sekarang, jika permainan berjalan gelung ini pernah mengalami beberapa jenis stalling atas sebab apa pun, fizik tidak akan tenggelam dalam lingkaran kematian. Permainan ini akan berjalan sedikit lebih perlahan, seperti yang sesuai.

Perkara seterusnya untuk memperbaiki adalah agak kecil berbanding dengan lingkaran kematian. Gelung ini mengambil potongan dt dari penumpuk sehingga penumpuk lebih kecil daripada dt. Ini menyeronokkan, tetapi masih ada sedikit masa yang tinggal di penumpuk. Ini menimbulkan masalah.

Anggapkan penumpuk akan ditinggalkan dengan 1/5 dari sepotong dt setiap bingkai. Pada bingkai keenam penumpuk akan mempunyai masa yang mencukupi untuk melakukan satu lagi pembaharuan fizik daripada semua bingkai yang lain. Ini akan menghasilkan satu bingkai setiap saat atau lebih yang melakukan lompatan diskret yang sedikit lebih besar dalam masa, dan mungkin sangat ketara dalam permainan anda.

Untuk menyelesaikannya, penggunaan interpolasi linear diperlukan. Jika ini menakutkan, jangan bimbang - pelaksanaan akan ditunjukkan. Jika anda ingin memahami pelaksanaannya, terdapat banyak sumber dalam talian untuk interpolasi linier.

Menggunakan ini kita boleh interpolate (anggaran) di mana kita mungkin berada di antara dua selang waktu yang berlainan. Ini boleh digunakan untuk memberikan keadaan permainan di antara dua pembaharuan fizik yang berbeza.

Dengan interpolasi linier, terjemahan enjin boleh berjalan pada kadar yang berbeza daripada enjin fizik. Ini membolehkan pengendalian yang mantap dari penumpuk sisa dari kemas kini fizik.

Berikut adalah contoh penuh:

Di sini, semua objek dalam permainan boleh ditarik pada momen-momen berubah antara masa fizik diskret. Ini akan mengendalikan semua kesilapan dan pengumpulan masa yang selebihnya. Ini sebenarnya menjadikan sedikit sedikit di belakang fizik yang kini diselesaikan, tetapi apabila menonton permainan, semua gerakan dilancarkan dengan sempurna oleh interpolasi.

Pemain tidak akan pernah tahu bahawa rendering itu begitu sedikit di belakang fizik, kerana pemain hanya akan tahu apa yang mereka lihat, dan apa yang mereka akan lihat adalah peralihan sempurna dari satu bingkai ke yang lain.

Anda mungkin tertanya-tanya, "mengapa tidak kita interpolasi dari kedudukan sekarang ke seterusnya?". Saya mencuba ini dan memerlukan rendering untuk "meneka" di mana objek akan berada di masa hadapan. Selalunya, objek dalam enjin fizik membuat perubahan secara tiba-tiba dalam pergerakan, seperti semasa perlanggaran, dan apabila perubahan pergerakan tiba-tiba dibuat, objek akan teleport sekitar disebabkan oleh interpolasi yang tidak tepat ke masa depan.


Reka bentuk Modular

Terdapat beberapa perkara yang perlu setiap objek fizik. Walau bagaimanapun, perkara-perkara spesifik setiap keperluan objek fizik mungkin berubah sedikit dari objek ke objek. Cara yang bijak untuk mengatur semua data ini diperlukan, dan ia akan dianggap bahawa jumlah yang lebih kecil dari kod untuk menulis untuk mencapai organisasi tersebut dikehendaki. Dalam kes ini, beberapa reka bentuk modular akan digunakan dengan baik.

Reka bentuk modular mungkin terdengar agak megah atau terlalu rumit, tetapi ia masuk akal dan agak mudah. Dalam konteks ini, "reka bentuk modular" hanya bermaksud kita hendak memecahkan objek fizik menjadi kepingan yang berasingan, supaya kita boleh menyambung atau melepaskannya tetapi kita lihat patut.

Badan

Badan fizik adalah objek yang mengandungi semua maklumat mengenai beberapa objek fizik yang diberikan. Ia akan menyimpan bentuk (s) yang objek tersebut diwakili oleh, data massa, transformasi (kedudukan, putaran), halaju, tork, dan sebagainya. Inilah yang badan kita sepatutnya kelihatan seperti:

Ini adalah titik permulaan yang baik untuk reka bentuk struktur badan fizik. Terdapat beberapa keputusan pintar yang dibuat di sini yang cenderung kepada organisasi kod yang kuat.

Perkara pertama yang perlu diperhatikan ialah bentuk yang terkandung di dalam tubuh dengan cara penunjuk. Ini mewakili hubungan longgar antara badan dan bentuknya. Tubuh boleh mengandungi apa-apa bentuk, dan bentuk tubuh boleh ditukar di sekeliling pada kehendak. Sebenarnya, tubuh boleh diwakili oleh pelbagai bentuk, dan badan itu akan dikenali sebagai "komposit", kerana ia akan terdiri daripada pelbagai bentuk. (Saya tidak akan menampung komposit dalam tutorial ini.)

Body and Shape interface.
Antara muka Badan dan Bentuk.

Bentuk itu sendiri bertanggungjawab untuk mengira bentuk terikat, mengira jisim berdasarkan ketumpatan, dan rendering.

Mass_data adalah struktur data kecil yang mengandungi maklumat berkaitan massa:

Adalah baik untuk menyimpan semua nilai berkaitan massa dan intertia dalam struktur tunggal. Jisimnya tidak boleh ditetapkan oleh tangan - massa harus selalu dikira dengan bentuk itu sendiri. Massa adalah jenis nilai yang agak tidak disengajakan, dan menetapkannya dengan tangan akan mengambil banyak masa tweaking. Ia ditakrifkan sebagai:

\[Persamaan 3: \\Mass = ketumpatan * kelantangan\]

Apabila seorang pereka mahu bentuk menjadi lebih "besar" atau "berat", mereka harus mengubah ketumpatan bentuk. Ketumpatan ini boleh digunakan untuk mengira jisim bentuk yang diberikan isipadunya. Ini adalah cara yang betul untuk pergi ke keadaan ini, kerana kepadatan tidak dipengaruhi oleh jumlah dan tidak akan berubah semasa runtime permainan (kecuali disokong secara khusus dengan kod khas).

Beberapa contoh bentuk seperti AABB dan Kalangan boleh didapati dalam tutorial sebelumnya dalam siri ini.

Bahan-bahan

Semua perbincangan massa dan ketumpatan ini membawa kepada persoalan: Di mana nilai ketumpatan terletak? Ia terletak di dalam struktur Bahan:

Sebaik sahaja nilai-nilai bahan ditetapkan, bahan ini boleh disalurkan ke bentuk badan supaya tubuh dapat mengira jisim.

Perkara terakhir yang sepatutnya disebut adalah gravity_scale. Gegaran skala untuk objek yang berbeza sering diperlukan untuk permainan tweaker yang terbaik untuk hanya memasukkan nilai dalam setiap badan khusus untuk tugas ini.

Beberapa tetapan bahan yang berguna untuk jenis bahan biasa boleh digunakan untuk membina objek Bahan dari nilai penghitungan:

Angkatan

Terdapat satu lagi perkara yang perlu dibincangkan dalam struktur badan. Terdapat ahli data yang dipanggil daya. Nilai ini bermula pada sifar pada permulaan setiap kemas kini fizik. Pengaruh lain dalam enjin fizik (seperti graviti) akan menambah vektor Vec2 ke dalam anggota data daya ini. Sebelum integrasi semua kuasa ini akan digunakan untuk mengira pecutan badan, dan digunakan semasa integrasi. Selepas integrasi ahli data daya ini tidak dapat dihalang.

Ini membolehkan sejumlah daya untuk bertindak ke atas sesuatu objek apabila ia kelihatan sesuai, dan tiada kod tambahan akan diperlukan untuk ditulis apabila jenis kuasa baru akan digunakan pada objek.

Mari ambil contoh. Katakan kita mempunyai bulatan kecil yang mewakili objek yang sangat berat. Bulatan kecil ini terbang di dalam permainan, dan ia sangat berat sehingga ia menarik objek lain ke arahnya sedikit sekali. Berikut adalah beberapa pseudocode kasar untuk menunjukkan ini:

Fungsi ApplyForcePullOn() mungkin boleh menggunakan kuasa kecil untuk menarik tubuh ke arah HeavyObject, hanya jika badannya cukup dekat.

Two objects pulled towards a larger one. The pull force is dependent upon distance.
Dua objek yang ditarik ke arah yang lebih besar bergerak menyisipkannya. Daya tarikan bergantung kepada jarak mereka ke kotak yang lebih besar.

Ia tidak kira berapa banyak daya yang ditambah kepada daya badan, kerana mereka semua akan menambah satu vektor kuasa yang dijumlahkan untuk badan itu. Ini bermakna bahawa dua daya yang bertindak ke atas badan yang sama boleh membatalkan satu sama lain.


Fasa Luas

Dalam artikel sebelumnya dalam rutin pengesanan perlanggaran siri ini diperkenalkan. Rutin ini sebenarnya adalah apa yang dikenali sebagai "fasa sempit". Perbezaan antara fasa luas dan fasa sempit boleh diteliti dengan mudah dengan carian Google.

(Ringkasnya: kita menggunakan pengesanan perlanggaran fasa yang luas untuk mengetahui mana pasangan objek mungkin bertabrakan, dan kemudian pengesanan perlanggaran fasa sempit untuk memeriksa sama ada mereka sebenarnya bertabrakan.)

Saya ingin memberikan beberapa kod contoh bersama-sama dengan penjelasan tentang bagaimana untuk melaksanakan fasa luas perhitungan masa-kompleks \(O (n ^ 2)\).

\(O (n ^ 2)\) pada dasarnya bermakna bahawa masa yang diambil untuk memeriksa setiap sepasang perlanggaran yang berpotensi akan bergantung pada kuadrat bilangan objek. Ia menggunakan notasi Big-O.

Oleh kerana kita bekerja dengan pasangan objek, ia akan berguna untuk membuat struktur seperti itu:

Fasa yang luas perlu mengumpulkan sekumpulan perlanggaran yang mungkin dan menyimpannya dalam struktur Pasangan. Pasangan ini kemudiannya boleh disalurkan ke bahagian lain enjin (fasa sempit), dan kemudian diselesaikan.

Contoh fasa yang luas:

Kod di atas agak mudah: Periksa setiap badan terhadap setiap badan, dan lompatkan diri.

Culling Duplicates

Terdapat satu masalah dari bahagian terakhir: banyak pasangan pendua akan dikembalikan! Pendua ini perlu dibuang dari hasilnya. Beberapa kebiasaan dengan algoritma sorting diperlukan di sini jika anda tidak mempunyai semacam pustaka sorting yang tersedia. Sekiranya anda menggunakan C++ maka anda bernasib baik:

Selepas menyusun semua pasangan dalam susunan tertentu, boleh diandaikan bahawa semua pasangan dalam bekas pasangan akan mempunyai semua pendua bersebelahan satu sama lain. Letakkan semua pasang unik ke dalam bekas baru yang dipanggil unikPairs, dan tugas memansuhkan pendua selesai.

Perkara terakhir yang dinyatakan adalah SortPairs predikat(). Fungsi SortPairs() ini adalah apa yang sebenarnya digunakan untuk melakukan penyisihan, dan mungkin kelihatan seperti ini:

Istilah lhs dan rhs boleh dibaca sebagai "sebelah kiri" dan "sebelah kanan". Istilah-istilah ini biasanya digunakan untuk merujuk kepada parameter fungsi di mana sesuatu secara logik dapat dilihat sebagai sebelah kiri dan kanan beberapa persamaan atau algoritma.

Lapisan

Lapisan merujuk kepada tindakan mempunyai objek yang berbeza tidak pernah bertembung dengan satu sama lain. Ini adalah kunci untuk mempunyai peluru yang dipecat dari objek tertentu tidak menjejaskan objek lain. Sebagai contoh, pemain dalam satu pasukan mungkin mahu roket mereka untuk membahayakan musuh-musuh tetapi tidak satu sama lain.

Representation of layering; some object collide with one another, some do not.
Perwakilan lapisan; sesetengah objek berlanggar dengan satu sama lain, sesetengahnya tidak.

Layering yang terbaik dilaksanakan dengan bitmasks - lihat QuickMaps How-To untuk Programmer dan halaman Wikipedia untuk pengenalan ringkas, dan bahagian Penapisan manual Box2D untuk melihat bagaimana enjin itu menggunakan bitmasks.

Lapisan perlu dilakukan dalam fasa yang luas. Di sini saya hanya akan menampal contoh fasa yang telah siap sepenuhnya:

Lapisan ternyata sangat efisien dan sangat mudah.


Persimpangan Halfspace

Ruang setengah boleh dilihat sebagai satu bahagian garisan dalam 2D. Mengesan sama ada satu titik di satu sisi garis atau yang lain adalah tugas yang cukup biasa, dan harus difahami secara menyeluruh oleh sesiapa sahaja yang membuat enjin fizik mereka sendiri. Sangat buruk bahawa topik ini tidak benar-benar diliputi di mana saja di internet dengan cara yang bermakna, sekurang-kurangnya dari apa yang saya lihat - sehingga sekarang, tentu saja!

Persamaan umum garis dalam 2D ​​adalah:

\[Persamaan 4: \\
Umum \: bentuk: kapak + oleh + c = 0\\
Normal \: to \: line: \begin{bmatrix}
a \\
b \\
\end{bmatrix}\]

custom-physics-line2d

Perhatikan bahawa, walaupun namanya, vektor biasa tidak semestinya dinormalisasi (iaitu, ia tidak semestinya mempunyai panjang 1).

Untuk melihat sama ada satu titik berada di bahagian tertentu garisan ini, semua yang perlu kita lakukan ialah pasangkan titik ke pembolehubah x dan y dalam persamaan dan periksa tanda hasilnya. Hasil 0 bermaksud titik adalah pada garisan, dan positif/negatif bermakna sisi yang berlainan garis.

Itu semua ada padanya! Mengetahui ini jarak dari satu titik ke garisan sebenarnya hasil daripada ujian sebelumnya. Jika vektor normal tidak normal, maka hasilnya akan dikurangkan dengan magnitud vektor biasa.


Kesimpulan

Sekarang, enjin fizik yang lengkap, walaupun mudah, boleh dibina sepenuhnya dari awal. Topik yang lebih maju seperti geseran, orientasi, dan pokok AABB dinamik boleh dibincangkan dalam tutorial masa depan. Sila tanya soalan atau beri komen di bawah, saya suka membaca dan menjawabnya!

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.