Pola untuk melakukan aksi permainan

11

Apakah ada pola yang diterima secara umum untuk melakukan berbagai aksi dalam game? Cara pemain dapat melakukan tindakan dan juga bahwa AI dapat melakukan tindakan, seperti bergerak, menyerang, menghancurkan diri sendiri, dll.

Saat ini saya memiliki BaseAction abstrak yang menggunakan .NET generics untuk menentukan objek berbeda yang dikembalikan oleh berbagai tindakan. Ini semua diimplementasikan dalam pola yang mirip dengan Komando, di mana setiap tindakan bertanggung jawab untuk dirinya sendiri dan melakukan semua yang diperlukan.

Alasan saya untuk menjadi abstrak adalah agar saya dapat memiliki ActionHandler tunggal, dan AI hanya dapat mengantri tindakan yang berbeda menerapkan baseAction. Dan alasannya generik adalah agar tindakan yang berbeda dapat mengembalikan informasi hasil yang relevan dengan tindakan tersebut (karena tindakan yang berbeda dapat memiliki hasil yang sama sekali berbeda dalam permainan), bersama dengan beberapa implementasi sebelum Aksi dan pasca Aksi yang umum.

Jadi ... apakah ada cara yang lebih diterima untuk melakukan ini, atau apakah ini terdengar baik-baik saja?

Arkiliknam
sumber
Kedengarannya bagus, pertanyaannya adalah apa yang Anda maksud dengan antrian? Sebagian besar game memiliki respons yang sangat cepat? "AI dapat mengantri aksi yang berbeda"
AturSams
Poin yang bagus. Tidak ada antrian. Hanya perlu tahu apakah sibuk, dan jika tidak, lakukan tindakan.
Arkiliknam

Jawaban:

18

Saya tidak berpikir ada satu cara yang diterima untuk menerapkan konsep ini, tetapi saya benar-benar ingin berbagi bagaimana saya biasanya menangani ini dalam permainan saya. Ini sedikit kombinasi dari pola desain Perintah dan pola desain Komposit .

Saya memiliki kelas dasar abstrak untuk tindakan yang tidak lebih dari pembungkus di sekitar Updatemetode yang dipanggil setiap frame, dan Finishedbendera untuk menunjukkan kapan tindakan selesai berjalan.

abstract class Action
{
    abstract void Update(float elapsed);
    bool Finished;
}

Saya juga menggunakan pola desain komposit untuk membuat jenis tindakan yang mampu menampung dan menjalankan tindakan lain. Ini juga kelas abstrak. Bermuara ke:

abstract class CompositeAction : Action
{
    void Add(Action action) { Actions.Add(action); }
    List<Action> Actions;
}

Kemudian saya memiliki dua implementasi tindakan gabungan, satu untuk eksekusi paralel dan satu untuk eksekusi berurutan . Tetapi keindahannya adalah karena paralel dan urutan adalah tindakan itu sendiri, mereka dapat digabungkan untuk menciptakan aliran eksekusi yang lebih kompleks.

class Parallel : CompositeAction
{
    override void Update(float elapsed) 
    {
        Actions.ForEach(a=> a.Update(elapsed));
        Actions.RemoveAll(a => a.Finished);
        Finished = Actions.Count == 0;
    }
}

Dan yang mengatur tindakan berurutan.

class Sequence : CompositeAction
{
    override void Update(float elapsed) 
    {
        if (Actions.Count > 0) 
        {
            Actions[0].Update(elapsed);
            if (Actions[0].Finished)
                Actions.RemoveAt(0);
        }
        Finished = Actions.Count == 0;
    }
 }

Dengan ini, itu hanya masalah menciptakan implementasi tindakan konkret, dan menggunakan Paralleldan Sequencetindakan untuk mengontrol aliran eksekusi. Saya akan mengakhiri dengan sebuah contoh:

// Create a parallel action to work as an action manager
Parallel actionManager = new Parallel();

// Send character1 to destination
Sequence actionGroup1 = new Sequence();
actionGroup1.Add(new MoveAction(character1, destination));
actionGroup1.Add(new TalkAction(character1, "Arrived at destination!"));
actionManager.Add(actionGroup1);

// Make character2 use a potion on himself
Sequence actionGroup2 = new Sequence();
actionGroup2.Add(new RemoveItemAction(character2, ItemType.Potion));
actionGroup2.Add(new SetHealthAction(character2, character2.MaxHealth));
actionGroup2.Add(new TalkAction(character2, "I feel better now!"));
actionManager.Add(actionGroup2);

// Every frame update the action manager
actionManager.Update(elapsed);

Saya telah berhasil menggunakan sistem ini untuk menggerakkan semua gameplay dalam petualangan grafis sebelumnya, tetapi mungkin bisa bekerja untuk apa saja. Itu juga cukup sederhana untuk menambahkan jenis tindakan komposit lainnya, yang digunakan untuk membuat loop eksekusi dan kondisional.

David Gouveia
sumber
Itu terlihat seperti solusi yang sangat bagus. Karena penasaran, bagaimana Anda memberi tahu UI apa yang harus digambar? Apakah objek game Anda (seperti karakter) berisi status yang digunakan untuk mengidentifikasi apa yang terjadi untuk tujuan rendering, atau apakah tindakan itu sendiri yang melakukan itu?
Arkiliknam
1
Biasanya tindakan saya hanya mengubah keadaan entitas, dan setiap perubahan pada output yang diberikan terjadi sebagai akibat dari perubahan keadaan itu, bukan melalui tindakan itu sendiri. Misalnya, dengan renderer mode langsung, tidak ada langkah tambahan yang diperlukan karena Drawmetode ini sudah dibangun di atas keadaan entitas, dan perubahannya otomatis. Dalam penyaji-mode dipertahankan seperti Flash, Anda bisa menggunakan pola yang bisa diamati untuk membuat perubahan pada entitas Anda menyebar ke objek tampilan, atau membuat koneksi secara manual di dalam entitas itu sendiri.
David Gouveia
1
Dalam situasi pertama, katakanlah Characterkelas Anda memiliki Positionproperti, dan Drawmetode yang membaca apa nilai saat ini Positionadalah dan menggambar gambar yang benar di sana. Dalam situasi ini, Anda hanya perlu memperbarui nilai Positionbahwa hasilnya akan secara otomatis terlihat di layar.
David Gouveia
1
Situasi kedua adalah, ketika Anda Charactermemiliki Positionproperti, tetapi mendelegasikan render ke semacam Spriteobjek yang secara otomatis ditampilkan oleh grafik adegan atau sesuatu. Dalam situasi ini, Anda harus memastikan bahwa posisi karakter dan posisi sprite selalu sinkron, yang melibatkan sedikit lebih banyak pekerjaan. Namun, dalam kedua kasus, saya tidak melihat mengapa manajer tindakan harus ada hubungannya dengan itu. :)
David Gouveia
1
Kedua metode memiliki kelebihan dan kekurangan .. Saya menggunakan metode kedua untuk game 2D saya, dan saya sesekali menyesali karena secara signifikan lebih rumit untuk menjaga semuanya tetap sinkron. Tetapi ada keuntungan juga, misalnya ketika mencoba untuk mendeteksi entitas mana yang diklik, atau apa yang harus ditarik atau tidak, karena semua yang akan di-render terkandung dalam struktur data yang sama dan bukan tersebar di antara tipe entitas N.
David Gouveia