Apa sajakah cara untuk memisahkan logika game dari animasi dan loop pengundian?

9

Saya sebelumnya hanya membuat game flash, menggunakan MovieClips dan semacamnya untuk memisahkan animasi saya dari logika game saya. Sekarang saya mulai mencoba membuat game untuk Android, tetapi teori pemrograman game tentang memisahkan hal-hal ini masih membingungkan saya. Saya berasal dari latar belakang mengembangkan aplikasi web non-game jadi saya berpengalaman dalam lebih banyak pola MVC seperti dan terjebak dalam pola pikir itu ketika saya mendekati pemrograman game.

Saya ingin melakukan hal-hal seperti abstrak permainan saya dengan memiliki, misalnya, kelas papan permainan yang berisi data untuk kotak ubin dengan contoh kelas ubin yang masing-masing berisi properti. Saya dapat memberikan draw loop saya akses ke ini dan minta menggambar papan permainan berdasarkan pada properti setiap ubin di papan permainan, tapi saya tidak mengerti di mana tepatnya animasi harus pergi. Sejauh yang saya tahu, animasi semacam berada di antara logika permainan abstrak (model) dan loop pengundian (tampilan). Dengan pola pikir MVC saya, frustasi mencoba memutuskan ke mana sebenarnya animasi seharusnya pergi. Itu akan memiliki sedikit data yang terkait dengan itu seperti model, tetapi tampaknya perlu sangat erat dengan loop pengulangan untuk memiliki hal-hal seperti frame frame animation.

Bagaimana saya bisa keluar dari pola pikir ini dan mulai berpikir tentang pola yang lebih masuk akal untuk permainan?

TMV
sumber

Jawaban:

6

Animasi masih dapat dengan sempurna dipisah antara logika dan rendering. Keadaan abstrak data animasi akan menjadi informasi yang diperlukan untuk API grafik Anda untuk membuat animasi.

Dalam game 2D misalnya, itu bisa berupa area persegi panjang yang menandai area yang menampilkan bagian sprite sheet Anda saat ini yang perlu digambar (bila Anda memiliki sheet yang terdiri dari katakanlah 30 gambar 80x80 yang berisi berbagai langkah karakter Anda melompat, duduk, bergerak dll.) Ini juga bisa berupa data apa pun yang tidak Anda perlukan untuk render, tetapi mungkin untuk mengelola status animasi itu sendiri, seperti waktu yang tersisa hingga langkah animasi saat ini berakhir atau nama animasi ("berjalan", "berdiri" dll) Semua itu dapat diwakili dengan cara apa pun yang Anda inginkan. Itu bagian logika.

Di bagian rendering, Anda cukup melakukannya seperti biasa, dapatkan persegi panjang itu dari model Anda dan gunakan renderer Anda untuk benar-benar melakukan panggilan ke API grafik.

Dalam kode (menggunakan sintaks C ++ di sini):

class Sprite //Model
{
    private:
       Rectangle subrect;
       Vector2f position;
       //etc.

    public:
       Rectangle GetSubrect() 
       {
           return subrect;
       }
       //etc.
};

class AnimatedSprite : public Sprite, public Updatable //arbitrary interface for classes that need to change their state on a regular basis
{
    AnimationController animation_controller;
    //etc.
    public:
        void Update()
        {
            animation_controller.Update(); //Good OOP design ;) It will take control of changing animations in time etc. for you
            this.SetSubrect(animation_controller.GetCurrentAnimation().GetRect());
        }
        //etc.
};

Itu datanya. Perender Anda akan mengambil data itu dan menggambarnya. Karena Sprite normal dan animasi digambar dengan cara yang sama, Anda dapat menggunakan polimorf di sini!

class Renderer
{
    //etc.
    public:
       void Draw(const Sprite &spr)
       {
           graphics_api_pointer->Draw(spr.GetAllTheDataThatINeed());
       }
};

TMV:

Saya datang dengan contoh lain. Katakanlah Anda memiliki RPG. Model Anda yang mewakili peta dunia, misalnya, mungkin perlu menyimpan posisi karakter di dunia sebagai koordinat petak di peta. Namun, ketika Anda memindahkan karakter, mereka berjalan beberapa piksel sekaligus ke alun-alun berikutnya. Apakah Anda menyimpan posisi "antar ubin" ini di objek animasi? Bagaimana Anda memperbarui model ketika karakter akhirnya "tiba" di koordinat ubin berikutnya di peta?

Peta dunia tidak tahu tentang posisi pemain secara langsung (tidak memiliki Vector2f atau sesuatu seperti itu yang secara langsung menyimpan posisi pemain =, melainkan memiliki referensi langsung ke objek pemain itu sendiri, yang pada gilirannya berasal dari AnimatedSprite sehingga Anda bisa meneruskannya ke renderer dengan mudah, dan mendapatkan semua data yang diperlukan darinya.

Secara umum, tilemap Anda seharusnya tidak dapat melakukan semuanya - Saya memiliki kelas "TileMap" yang menangani pengelolaan semua ubin, dan mungkin juga melakukan deteksi tabrakan antara objek yang saya serahkan ke sana dan ubin di peta. Kemudian, saya akan memiliki kelas "RPGMap" lainnya, atau bagaimanapun Anda ingin menyebutnya, yang memiliki referensi ke tilemap Anda dan referensi ke pemain dan membuat Pembaruan aktual () panggilan ke pemain Anda dan ke Anda tilemap.

Bagaimana Anda ingin memperbarui model ketika pemain bergerak tergantung pada apa yang ingin Anda lakukan.

Apakah pemain Anda diperbolehkan bergerak di antara ubin secara terpisah (gaya Zelda)? Cukup tangani input dan pindahkan pemain sesuai setiap frame. Atau Anda ingin pemain menekan "kanan" dan karakter Anda secara otomatis memindahkan satu ubin ke kanan? Biarkan kelas RPGMap Anda menginterpolasi posisi pemain sampai tiba di tujuannya dan sementara itu mengunci semua penanganan input gerakan-kunci.

Either way, jika Anda ingin membuatnya lebih mudah pada diri Anda sendiri, semua model Anda akan memiliki metode Pembaruan () jika mereka benar-benar memerlukan beberapa logika untuk memperbarui diri mereka sendiri (bukan hanya mengubah nilai variabel) - Anda tidak memberikan controller dalam pola MVC seperti itu, Anda hanya memindahkan kode dari "satu langkah di atas" (controller) ke model, dan semua controller tidak memanggil metode Update () dari model (Controller dalam kasus kami akan menjadi RPGMap). Anda masih dapat dengan mudah menukar kode logika - Anda bisa langsung mengubah kode kelas atau jika Anda memerlukan perilaku yang sama sekali berbeda, Anda hanya dapat berasal dari kelas model dan hanya mengganti metode Pembaruan ().

Pendekatan itu mengurangi pemanggilan metode dan banyak hal seperti itu - yang dulunya merupakan salah satu kelemahan utama dari pola MVC murni (Anda akhirnya memanggil GetThis () GetThat () sangat sering) - itu membuat kode lebih lama dan lebih sedikit lebih sulit untuk dibaca dan juga lebih lambat - meskipun itu mungkin ditangani oleh kompiler Anda yang mengoptimalkan banyak hal seperti itu.

TravisG
sumber
Apakah Anda menyimpan data animasi di kelas yang berisi logika game, kelas yang berisi loop game, atau terpisah dari keduanya? Selain itu, sepenuhnya tergantung pada loop atau kelas yang berisi loop untuk memahami bagaimana menerjemahkan data animasi menjadi menggambar layar, kan? Seringkali tidak sesederhana hanya mendapatkan kotak yang mewakili bagian dari sprite sheet dan menggunakannya untuk memotong bitmap draw dari sprite sheet.
TMV
Saya datang dengan contoh lain. Katakanlah Anda memiliki RPG. Model Anda yang mewakili peta dunia, misalnya, mungkin perlu menyimpan posisi karakter di dunia sebagai koordinat petak di peta. Namun, ketika Anda memindahkan karakter, mereka berjalan beberapa piksel sekaligus ke alun-alun berikutnya. Apakah Anda menyimpan posisi "antar ubin" ini di objek animasi? Bagaimana Anda memperbarui model ketika karakter akhirnya "tiba" di koordinat ubin berikutnya di peta?
TMV
Saya mengedit jawaban untuk pertanyaan Anda, karena komentar tidak memungkinkan karakter yang cukup untuk itu.
TravisG
Jika saya mengerti semuanya dengan benar:
TMV
Anda bisa memiliki instance dari kelas "Animator" di dalam View Anda, dan itu akan memiliki metode "update" publik yang disebut setiap frame oleh tampilan. Metode pembaruan memanggil metode "pembaruan" dari berbagai macam objek animasi individual di dalamnya. Animator dan animasi di dalamnya memiliki referensi ke Model (diturunkan melalui konstruktornya) sehingga mereka dapat memperbarui data model jika animasi akan mengubahnya. Kemudian, dalam loop pengundian, Anda mendapatkan data dari animasi di dalam animator dengan cara yang dapat dipahami oleh View dan digambar.
TMV
2

Saya dapat mengembangkan ini jika Anda mau, tetapi saya memiliki penyaji pusat yang diminta untuk menggambar di loop. Daripada

handle input

for every entity:
    update entity

for every entity:
    draw entity

Saya memiliki sistem yang lebih suka

handle input (well, update the state. Mine is event driven so this is null)

for every entity:
    update entity //still got game logic here

renderer.draw();

Kelas renderer hanya menyimpan daftar referensi untuk komponen objek yang dapat digambar. Ini ditugaskan dalam konstruktor untuk kesederhanaan.

Sebagai contoh Anda, saya akan memiliki kelas GameBoard dengan sejumlah Ubin. Setiap ubin jelas tahu posisinya, dan saya menganggap semacam animasi. Faktor yang keluar ke semacam kelas Animasi yang dimiliki ubin, dan minta agar referensi sendiri ke kelas Renderer. Di sana, semuanya terpisah. Ketika Anda memperbarui Tile, itu disebut Perbarui pada animasi .. atau memperbarui itu sendiri. Ketika Renderer.Draw()dipanggil, itu menarik animasi.

Animasi frame independen tidak perlu terlalu banyak berhubungan dengan loop pengundian.

Bebek Komunis
sumber
0

Saya telah mempelajari paradigma sendiri belakangan ini, jadi jika jawaban ini tidak lengkap, saya yakin seseorang akan menambahkannya.

Metodologi yang tampaknya paling masuk akal untuk desain game adalah memisahkan logika dari output layar.

Dalam kebanyakan kasus, Anda ingin menggunakan pendekatan multi-utas, jika Anda tidak terbiasa dengan topik itu, itu pertanyaan tersendiri, inilah primer wiki . Pada dasarnya Anda ingin logika permainan Anda dieksekusi dalam satu utas, mengunci variabel yang perlu diakses untuk memastikan integritas data. Jika loop logis Anda sangat cepat (animasi super mega 3d pong?), Anda dapat mencoba untuk memperbaiki frekuensi loop dijalankan dengan tidur utas untuk durasi kecil (120 hz telah disarankan dalam forum ini untuk loop fisika permainan). Bersamaan, utas lainnya menggambar ulang layar (60 hz telah disarankan dalam topik lain) dengan variabel yang diperbarui, lagi-lagi meminta kunci pada variabel sebelum mengaksesnya.

Dalam hal itu, animasi atau transisi, dll., Masuk ke utas menggambar tetapi Anda harus memberi isyarat melalui semacam bendera (variabel keadaan global mungkin) bahwa utas logika permainan tidak boleh melakukan apa-apa (atau melakukan sesuatu yang berbeda ... mengatur parameter peta baru mungkin).

Setelah Anda mendapatkan kepala sekitar konkurensi, sisanya cukup dimengerti. Jika Anda tidak memiliki pengalaman dengan konkurensi, saya sangat menyarankan Anda menulis beberapa program pengujian sederhana sehingga Anda dapat memahami bagaimana aliran terjadi.

Semoga ini membantu :)

[sunting] Pada sistem yang tidak mendukung multithreading, animasinya masih dapat masuk ke draw loop, tetapi Anda ingin mengatur keadaan sedemikian rupa untuk memberi sinyal pada logika bahwa sesuatu yang berbeda sedang terjadi dan tidak terus memproses level saat ini / peta / dll ...

Stephen
sumber
1
Saya tidak setuju di sini. Dalam kebanyakan kasus, Anda tidak ingin multi-utas, terutama jika ini adalah permainan kecil.
Bebek Komunis
@TheCommunistDuck Cukup adil, overhead, dan kompleksitas untuk multithreading bisa pasti membuatnya berlebihan, plus jika gimnya kecil, ia harus dapat dengan cepat diperbarui.
Stephen