Poin Penting
1. Arsitektur perangkat lunak adalah tentang mengelola perubahan dan meminimalkan beban kognitif.
Bagi saya, desain yang baik berarti ketika saya membuat perubahan, seolah-olah seluruh program sudah dirancang untuk mengantisipasinya.
Tujuan utama arsitektur. Arsitektur perangkat lunak pada dasarnya bertujuan memudahkan perubahan. Sistem yang dirancang dengan baik mampu mengantisipasi modifikasi di masa depan, sehingga pengembang dapat menambahkan fitur baru atau memperbaiki bug dengan gangguan seminimal mungkin. Kemudahan sebuah basis kode dalam menampung perubahan menjadi ukuran utama kualitas arsitekturnya.
Pengurangan beban kognitif. Salah satu aspek penting dari arsitektur yang baik adalah meminimalkan jumlah informasi yang harus dipahami pengembang sebelum melakukan perubahan. Pola pemisahan dan desain modular membantu mengurangi beban kognitif ini dengan memungkinkan pengembang memikirkan komponen secara terpisah. Ini mengurangi risiko efek samping yang tidak diinginkan dan membuat basis kode lebih mudah dijelajahi.
Alur pemrograman. Alur pemrograman melibatkan pemahaman kode yang ada, merancang solusi, mengimplementasikan kode, dan merapikan kode. Arsitektur perangkat lunak berfokus pada fase pembelajaran. Memuat kode ke dalam neuron berjalan lambat, jadi penting menemukan strategi untuk mengurangi volume kode yang harus dipahami.
2. Pemisahan (decoupling) mengurangi volume kode yang perlu dipahami untuk sebuah perubahan.
Jika Anda memisahkan mereka, Anda bisa memahami masing-masing sisi secara mandiri.
Definisi pemisahan. Pemisahan berarti sejauh mana dua bagian kode dapat dipahami dan dimodifikasi secara independen. Kode yang sangat terikat mengharuskan pengembang memahami detail kedua komponen sebelum melakukan perubahan, meningkatkan risiko kesalahan dan menyulitkan pemeliharaan. Pemisahan bertujuan meminimalkan ketergantungan ini, sehingga pengembang bisa fokus pada komponen individual tanpa harus memahami seluruh sistem.
Manfaat pemisahan. Pemisahan meminimalkan pengetahuan yang harus dimiliki sebelum bisa melangkah maju. Selain itu, perubahan pada satu bagian kode tidak harus memicu perubahan pada bagian lain. Semakin sedikit keterikatan, semakin kecil efek riak perubahan itu ke seluruh sistem.
Strategi pemisahan. Pemisahan dapat dicapai melalui berbagai pola desain dan prinsip arsitektur, seperti interface, kelas abstrak, dan antrean pesan. Teknik-teknik ini memungkinkan komponen berinteraksi lewat kontrak yang jelas, mengurangi ketergantungan langsung dan mendorong modularitas.
3. Abstraksi dan ekstensi datang dengan biaya kompleksitas dan spekulasi.
Setiap kali Anda menambahkan lapisan abstraksi atau tempat untuk mendukung ekstensi, Anda berspekulasi bahwa fleksibilitas itu akan dibutuhkan nanti.
Daya tarik abstraksi. Abstraksi, modularitas, dan pola desain bisa menghasilkan program yang terarsitektur dengan baik dan menyenangkan untuk dikerjakan. Arsitektur yang baik sangat berpengaruh pada produktivitas. Namun, manfaat ini memiliki harga.
Biaya fleksibilitas. Arsitektur yang baik membutuhkan usaha dan disiplin nyata. Anda harus memikirkan bagian mana dari program yang harus dipisahkan dan memperkenalkan abstraksi di titik-titik tersebut. Demikian pula, Anda harus menentukan di mana ekstensi harus dirancang agar perubahan di masa depan lebih mudah dilakukan.
Prinsip YAGNI. Abstraksi yang berlebihan bisa menghasilkan basis kode dengan interface, sistem plugin, dan metode virtual yang berlebihan, sehingga sulit melacak kode yang benar-benar menjalankan tugas. Prinsip "You Aren't Gonna Need It" (YAGNI) mengingatkan untuk menghindari optimasi prematur dan fokus menyelesaikan masalah yang ada saat ini.
4. Optimasi performa bergantung pada asumsi dan batasan konkret.
Performa adalah soal asumsi.
Fleksibilitas vs Performa. Arsitektur perangkat lunak bertujuan membuat program lebih fleksibel, sehingga perubahan menjadi lebih mudah. Ini berarti mengurangi asumsi yang tertanam dalam program. Namun, performa justru bergantung pada asumsi. Praktik optimasi berkembang dengan adanya batasan konkret.
Tukar tambah optimasi. Optimasi memerlukan waktu rekayasa yang signifikan. Setelah dilakukan, kode cenderung menjadi kaku: kode yang sangat dioptimalkan sulit diubah.
Kompromi prototipe. Salah satu kompromi adalah menjaga kode tetap fleksibel sampai desain stabil, lalu menghilangkan beberapa abstraksi untuk meningkatkan performa. Lebih mudah membuat game yang menyenangkan menjadi cepat daripada membuat game cepat menjadi menyenangkan.
5. Kesederhanaan meringankan batasan pada arsitektur, performa, dan kecepatan pengembangan.
Saya berusaha keras menulis solusi paling bersih dan langsung untuk masalah.
Kesederhanaan sebagai prinsip panduan. Menulis solusi yang bersih dan langsung meminimalkan jumlah kode, yang pada gilirannya mengurangi beban kognitif untuk memahami dan memodifikasinya. Kode sederhana sering berjalan lebih cepat karena overhead berkurang dan baris eksekusi lebih sedikit.
Kesederhanaan dan waktu. Kode sederhana tidak selalu berarti lebih cepat ditulis. Solusi yang baik bukanlah penumpukan kode, melainkan penyaringan kode. Menemukannya seperti mencocokkan pola atau memecahkan teka-teki. Butuh usaha untuk melihat di balik beragam contoh kasus dan menemukan keteraturan tersembunyi.
Nilai keanggunan. Solusi elegan adalah solusi umum: sedikit logika yang tetap mencakup banyak kasus penggunaan dengan benar. Menemukannya seperti mencocokkan pola atau memecahkan teka-teki. Butuh usaha untuk melihat di balik beragam contoh kasus dan menemukan keteraturan tersembunyi.
6. Pola Command mewujudkan pemanggilan metode untuk input yang dapat dikonfigurasi dan undo/redo.
Command adalah pemanggilan metode yang diwujudkan sebagai objek.
Pemanggilan metode yang diwujudkan. Pola Command mengenkapsulasi permintaan sebagai objek, memungkinkan parameterisasi klien dengan permintaan berbeda, antrean, pencatatan, dan dukungan operasi undo. Ini adalah pemanggilan metode yang dibungkus dalam objek.
Konfigurasi input. Pola Command memungkinkan pemetaan input yang dapat dikonfigurasi pengguna dengan mengaitkan setiap tombol dengan objek Command. Ini memungkinkan pemain menyesuaikan kontrol tanpa mengubah logika inti game.
Fungsi undo/redo. Dengan mengimplementasikan metode execute() dan undo() dalam subclass Command, pola ini mempermudah implementasi fungsi undo/redo. Setiap command menyimpan status yang diperlukan untuk membalikkan efeknya, memungkinkan pengguna dengan mudah membatalkan aksi.
7. Pola Flyweight menghemat memori dengan berbagi status intrinsik.
Flyweight, sesuai namanya, digunakan saat Anda memiliki objek yang harus lebih ringan, biasanya karena jumlahnya terlalu banyak.
Objek ringan. Pola Flyweight mengurangi konsumsi memori dengan memisahkan status objek menjadi komponen intrinsik (dibagi) dan ekstrinsik (unik). Status intrinsik disimpan dalam objek bersama, sementara status ekstrinsik diberikan saat dibutuhkan.
Contoh terrain. Pola Flyweight dapat diterapkan pada ubin terrain di dunia game. Setiap ubin menyimpan penunjuk ke objek Terrain bersama, yang berisi properti tipe terrain (misalnya biaya gerak, tekstur). Ini menghindari penyimpanan data berulang untuk setiap ubin.
Dukungan perangkat keras. Pola Flyweight mungkin satu-satunya pola desain Gang of Four yang didukung perangkat keras. Dengan rendering instansiasi, kartu grafis dapat merender data bersama sekali saja, lalu mengirim data unik setiap instance pohon—posisi, warna, dan skala.
8. Pola Observer memungkinkan notifikasi terpisah, tapi perlu pengelolaan hati-hati.
Memungkinkan satu bagian kode mengumumkan sesuatu yang menarik terjadi tanpa peduli siapa yang menerima notifikasi.
Komunikasi terpisah. Pola Observer memungkinkan satu objek (subjek) memberi tahu banyak objek lain (observer) tentang perubahan status tanpa mengetahui tipe spesifik mereka. Ini mendorong keterikatan longgar dan modularitas.
Contoh sistem pencapaian. Pola Observer dapat digunakan untuk sistem pencapaian. Mesin fisika memberi tahu observer saat entitas jatuh, dan sistem pencapaian memeriksa apakah entitas itu pahlawan dan jatuh dari jembatan untuk membuka pencapaian "Jatuh dari Jembatan".
Manajemen memori. Saat menggunakan pola Observer, penting mengelola masa hidup subjek dan observer agar tidak terjadi pointer menggantung atau kebocoran memori. Observer harus mendaftar keluar dari subjek saat dihancurkan.
9. Pola Prototype menawarkan cloning sebagai alternatif instansiasi tradisional.
Ide utamanya adalah objek dapat menghasilkan objek lain yang mirip dengan dirinya.
Pembuatan objek prototipal. Pola Prototype memungkinkan objek baru dibuat dengan mengkloning objek yang sudah ada (prototipe). Ini menghindari logika konstruktor yang rumit dan memungkinkan pembuatan objek dengan status awal yang disesuaikan.
Contoh spawner. Pola Prototype dapat digunakan untuk membuat spawner monster. Alih-alih memiliki kelas spawner terpisah untuk tiap jenis monster, satu kelas spawner dapat mengkloning instance monster prototipe untuk membuat monster baru.
Pemodelan data. Delegasi prototipal cocok untuk mendefinisikan data dalam game. Pedang ajaib yang bisa memenggal kepala, yang sebenarnya hanya pedang panjang dengan bonus, bisa diekspresikan langsung seperti ini:
json
{
"name": "Sword of Head-Detaching",
"prototype": "longsword",
"damageBonus": "20"
}
10. Pola State mengenkapsulasi perilaku spesifik status, tapi bisa disalahgunakan.
Memungkinkan objek mengubah perilakunya saat status internalnya berubah. Objek akan tampak berubah kelas.
Perilaku tergantung status. Pola State memungkinkan objek mengubah perilaku berdasarkan status internalnya. Setiap status diwakili oleh kelas terpisah, dan objek mendelegasikan pemanggilan metode ke objek status saat ini.
Contoh heroine. Pola State dapat digunakan untuk perilaku heroine dalam game platformer. Heroine bisa memiliki status berdiri, melompat, membungkuk, dan menyelam. Setiap kelas status mendefinisikan perilaku heroine untuk setiap input.
Transisi status. Untuk mengganti status, kita perlu mengubah penunjuk state_ ke status baru. Jika objek status tidak memiliki field lain, maka data yang disimpan hanya penunjuk ke tabel metode virtual internal agar metodenya bisa dipanggil. Dalam kasus ini, tidak perlu lebih dari satu instance.
11. Pola Game Loop memisahkan waktu game dari input pengguna dan kecepatan prosesor.
Memisahkan kemajuan waktu game dari input pengguna dan kecepatan prosesor.
Kecepatan gameplay konsisten. Pola Game Loop memastikan game berjalan dengan kecepatan konsisten tanpa tergantung perangkat keras atau input pengguna. Ini dicapai dengan memisahkan logika pembaruan game dari proses rendering.
Langkah waktu variabel. Dalam game loop dengan langkah waktu variabel, durasi waktu antara setiap pembaruan tidak tetap, melainkan berubah sesuai frame rate. Mesin bertanggung jawab memajukan dunia game sesuai durasi tersebut.
Langkah waktu pembaruan tetap, rendering variabel. Game disimulasikan dengan laju konstan menggunakan langkah waktu tetap yang aman di berbagai perangkat keras. Hanya saja, tampilan visual bagi pemain bisa jadi kurang mulus di mesin yang lebih lambat.
12. Pola Update Method mensimulasikan objek independen lewat pembaruan berurutan.
Mensimulasikan kumpulan objek independen dengan memberi tahu masing-masing memproses satu frame perilaku sekaligus.
Simulasi objek konkuren. Pola Update Method mensimulasikan kumpulan objek independen dengan memanggil metode update() pada setiap objek setiap frame. Ini memberi kesempatan tiap objek memperbarui status dan perilakunya.
Contoh entitas. Pola Update Method dapat digunakan untuk mensimulasikan entitas di dunia game. Setiap entitas memiliki metode update() yang dipanggil tiap frame. Metode ini menangani AI, fisika, dan animasi.
Pertimbangan performa. Pola Update Method dapat dioptimalkan dengan teknik lokalitas data. Ini melibatkan penyimpanan data semua entitas dalam blok memori berurutan, yang meningkatkan performa cache.
13. Double Buffering menciptakan ilusi simultan dengan memisahkan akses data dari modifikasi.
Membuat rangkaian operasi berurutan tampak instan atau simultan.
Pembaharuan status atomik. Pola Double Buffer memungkinkan modifikasi status secara bertahap sambil memastikan kode eksternal selalu melihat snapshot data yang konsisten dan atomik. Ini dicapai dengan memelihara dua salinan data: buffer saat ini dan buffer berikutnya.
Contoh rendering grafis. Pola Double Buffer umum digunakan dalam rendering grafis untuk mencegah tearing. Kode rendering menulis ke buffer berikutnya, sementara driver video membaca dari buffer saat ini. Setelah rendering selesai, kedua buffer ditukar.
Pertimbangan performa. Proses pertukaran memakan waktu. Double buffering memerlukan langkah pertukaran setelah modifikasi status selesai. Operasi ini harus atomik—tidak ada kode yang boleh mengakses status saat pertukaran berlangsung.
14. Pola Service Locator menyediakan titik akses global ke layanan dengan keterikatan minimal.
Menyediakan titik akses global ke layanan tanpa mengikat pengguna ke kelas konkret yang mengimplementasikannya.
Akses layanan terpisah. Pola Service Locator menyediakan titik akses global ke layanan tanpa mengikat kode pengguna ke implementasi konkret. Ini memungkinkan fleksibilitas dan kemudahan pengujian lebih besar.
Contoh sistem audio. Pola Service Locator dapat digunakan untuk mengakses sistem audio. Kelas Locator menyediakan metode getAudio() yang mengembalikan instance interface Audio. Implementasi sistem audio bisa diganti tanpa memengaruhi kode pengguna.
Layanan null. Jika layanan tidak ditemukan, locator dapat mengembalikan layanan null. Layanan null mengimplementasikan interface layanan, tapi tidak melakukan apa-apa. Ini memungkinkan game tetap berjalan meski layanan tidak tersedia.
15. Pola Subclass Sandbox mendefinisikan perilaku di subclass menggunakan operasi kelas dasar.
Mendefinisikan perilaku di subclass dengan menggunakan serangkaian operasi yang disediakan oleh kelas dasarnya.
Perilaku subclass terbatas. Pola Subclass Sandbox mendefinisikan perilaku di subclass dengan operasi yang disediakan kelas dasar. Ini membatasi akses subclass ke sistem lain, mendorong enkapsulasi dan mengurangi keterikatan.
Contoh kekuatan super. Pola Subclass Sandbox dapat digunakan untuk mengimplementasikan kekuatan super dalam game. Kelas dasar Superpower menyediakan metode untuk menggerakkan pahlawan, memutar suara, dan memunculkan partikel. Subclass mengimplementasikan kekuatan spesifik dengan memanggil metode ini.
Pohon pewarisan lebar. Pola ini menghasilkan arsitektur dengan hierarki kelas yang dangkal tapi lebar. Rantai pewarisan tidak dalam, tapi banyak kelas langsung turun dari Superpower. Dengan satu kelas induk dan banyak subclass langsung, kita punya titik tumpuan dalam basis kode.
16. Pola Bytecode memberi perilaku fleksibilitas data lewat mesin virtual.
Memberi perilaku fleksibilitas data dengan mengenkode sebagai instruksi untuk mesin virtual.
Perilaku berbasis data. Pola Bytecode memungkinkan perilaku didefinisikan dalam data, bukan kode. Ini memberi fleksibilitas lebih, modifikasi lebih mudah, dan sandboxing lebih aman.
Contoh sistem mantra. Pola Bytecode dapat digunakan untuk sistem mantra dalam game. Setiap mantra didefinisikan sebagai urutan instruksi bytecode yang dijalankan oleh mesin virtual. Ini memungkinkan desainer membuat mantra baru tanpa menulis kode.
Mesin tumpukan. VM memelihara tumpukan internal nilai. Dalam contoh kita, satu-satunya jenis nilai yang diproses instruksi adalah angka, jadi kita bisa menggunakan array int sederhana. Saat data perlu diteruskan antar instruksi, ia lewat tumpukan.
Ringkasan Ulasan
Game Programming Patterns sangat dipuji karena penjelasannya yang jelas, gaya penulisan yang menarik, serta contoh-contoh praktis penerapan pola desain dalam pengembangan game. Para pembaca menghargai humor penulis, wawasan mengenai pertimbangan performa, serta pembahasan yang seimbang tentang kelebihan dan kekurangan setiap pola. Banyak yang menganggap buku ini sangat berguna baik bagi pemula maupun pengembang berpengalaman, bahkan beberapa menyebut relevansinya melampaui dunia pemrograman game. Meski ada beberapa kritik terkait contoh C++ yang sudah usang dan fokus pada jenis game tertentu, secara keseluruhan mayoritas pengulas menilai buku ini sebagai bacaan wajib bagi para pengembang game.
Orang Juga Membaca
FAQ
What's Game Programming Patterns about?
- Focus on Game Development: Game Programming Patterns by Robert Nystrom is a comprehensive guide that delves into design patterns specifically tailored for game development.
- Patterns for Better Code: It presents various design patterns that help create cleaner, more efficient, and maintainable code, crucial as game projects grow in complexity.
- Practical Examples: The book includes practical examples and code snippets, primarily in C++, to demonstrate how these patterns can be implemented in real-world scenarios.
Why should I read Game Programming Patterns?
- Improve Code Quality: The book introduces design patterns that promote better organization and structure in your code, enhancing your coding skills.
- Learn from Experience: Robert Nystrom shares insights from his industry experience, particularly from his time at Electronic Arts, offering valuable lessons to avoid common pitfalls.
- Accessible to All Levels: Designed to be approachable, it is a great resource for both beginners and experienced developers looking to improve their game programming skills.
What are the key takeaways of Game Programming Patterns?
- Importance of Design Patterns: The book emphasizes how design patterns can lead to cleaner and more maintainable code, with detailed discussions on patterns like Command, Observer, and State.
- Decoupling Code: A major theme is the need to decouple code to make it easier to manage and extend, especially in games where different systems must interact.
- Performance Considerations: It addresses performance issues specific to games, such as efficient memory management and optimizing code for real-time execution.
What are the best quotes from Game Programming Patterns and what do they mean?
- "Every kid has dreamed of being a superhero...": This introduces the Subclass Sandbox pattern, emphasizing a data-driven approach to manage multiple behaviors without redundancy.
- "A virtual machine... is often as simple as a stack, a loop, and a switch statement.": Highlights the simplicity of creating complex behaviors through a well-structured virtual machine.
- "The more you can use stuff in that cache line, the faster you go.": From the Data Locality section, it underscores the importance of organizing data in memory to optimize CPU cache usage.
How does the Command pattern work in Game Programming Patterns?
- Encapsulating Requests: The Command pattern encapsulates a request as an object, allowing for parameterization of clients with different requests.
- Decoupling Actions: It decouples the sender of a request from the object that handles it, making code more flexible and easier to manage.
- Example Implementation: Nystrom illustrates this with an input handler example, using command objects to represent actions like jumping or firing a weapon.
How does the State pattern function in Game Programming Patterns?
- Behavioral Changes: The State pattern allows an object to alter its behavior when its internal state changes, useful for managing complex behaviors in game characters.
- Encapsulation of State Logic: Each state is represented by a class implementing a common interface, keeping the code organized and simplifying transitions between states.
- Example Usage: Nystrom provides an example of a game character in different states, each handling its own input and behavior.
What is the Game Loop pattern in Game Programming Patterns?
- Continuous Execution: The Game Loop pattern is essential for running a game continuously, processing user input, updating game state, and rendering graphics.
- Decoupling Time from Input: It decouples game time progression from user input and processor speed, ensuring consistent gameplay across different hardware.
- Implementation Example: The book outlines a basic game loop structure, providing a foundation for any game engine.
How does the Observer pattern work in Game Programming Patterns?
- Decoupled Communication: The Observer pattern allows one object to notify multiple observers about changes in its state without being tightly coupled to them.
- Event Handling: Useful for implementing event-driven systems, such as notifying different parts of a game when a player achieves something.
- Example Implementation: Nystrom illustrates this with a physics engine example that notifies an achievement system when an event occurs.
What is the Double Buffer pattern in Game Programming Patterns?
- Preventing Tearing: The Double Buffer pattern prevents visual tearing in graphics rendering by maintaining two buffers for displaying and drawing frames.
- Atomic Updates: By swapping buffers at the end of each frame, the game presents a complete image without showing intermediate states.
- Implementation Details: The book provides a simple implementation, demonstrating efficient buffer management for smooth visual results.
How does the Object Pool pattern work in Game Programming Patterns?
- Reuse Objects Efficiently: The Object Pool pattern allows for the reuse of objects from a fixed pool, reducing memory fragmentation and improving performance.
- Fixed Pool Size: It creates a collection of objects upfront, avoiding the overhead of frequent memory allocation.
- Manage Active Objects: Objects can be marked as "in use" or available, allowing the pool to manage reuse without new memory allocation.
What is the purpose of the Dirty Flag pattern in Game Programming Patterns?
- Track Changes Efficiently: The Dirty Flag pattern avoids unnecessary recalculations by deferring updates until derived data is needed.
- Set and Clear Flags: When primary data changes, the dirty flag is set, and recalculations occur only if necessary when derived data is requested.
- Improve Performance: This ensures expensive calculations are performed only when required, leading to smoother performance.
How does the Data Locality pattern enhance performance in Game Programming Patterns?
- Optimize Memory Access: The Data Locality pattern arranges data in memory to take advantage of CPU caching, reducing cache misses.
- Contiguous Memory Layout: Organizing data structures contiguously allows the CPU to load larger data chunks efficiently.
- Impact on Game Performance: Optimizing data locality significantly enhances game speed and responsiveness, crucial for performance.