Pemisahan negara dunia dan animasi dalam game berbasis giliran

9

Bagaimana Anda menangani pemisahan animasi dari negara dunia dalam game berbasis giliran? Saya sedang mengerjakan game berbasis grid 2D saat ini. Kode di bawah ini disederhanakan untuk lebih menjelaskan.

Ketika seorang aktor bergerak, saya ingin menghentikan aliran putaran sementara makhluk itu menghidupkan dan bergerak ke posisi yang baru. Jika tidak, layar bisa jauh tertinggal dari keadaan dunia, yang akan menyebabkan penampilan visual yang aneh. Saya juga ingin memiliki animasi yang tidak menghalangi aliran permainan - efek partikel dapat terungkap melalui beberapa putaran tanpa mempengaruhi gameplay.

Apa yang saya lakukan adalah memperkenalkan dua jenis animasi, yang saya sebut animasi pemblokiran dan animasi non-pemblokiran. Ketika pemain ingin pindah, kode yang dieksekusi adalah

class Actor {
    void move(PositionInfo oldPosition, PositionInfo newPosition) {
        if(isValidMove(oldPosition, newPosition) {
             getGame().pushBlockingAnimation(new MoveAnimation(this, oldPosition, newPosition, ....));
             player.setPosition(newPosition);
        }
    }
}

Maka loop pembaruan utama tidak:

class Game {
    void update(float dt) {
        updateNonBlockingAnimations(dt); //Effects like particle explosions, damage numbers, etc. Things that don't block the flow of turns.
        if(!mBlockingAnimations.empty()) {
            mBlockingAnimations.get(0).update(dt);
        } else {
             //.. handle the next turn. This is where new animations will be enqueued..//
        }
        cleanUpAnimations(); // remove finished animations
    }
}

... di mana animasi memperbarui posisi layar Aktor.

Satu ide lain yang saya terapkan adalah memiliki animasi pemblokiran bersamaan, di mana beberapa animasi pemblokiran akan diperbarui secara bersamaan, tetapi perubahan berikutnya tidak akan terjadi sampai semuanya selesai.

Apakah ini tampak seperti cara yang waras untuk melakukan sesuatu? Adakah yang punya saran, atau bahkan referensi bagaimana permainan serupa lainnya melakukan hal seperti itu.

mdkess
sumber

Jawaban:

2

Jika sistem Anda bekerja untuk Anda, saya tidak melihat alasan untuk tidak melakukannya.

Nah, satu alasan: Ini mungkin menjadi berantakan ketika Anda tidak dapat dengan mudah menilai konsekuensi dari "player.setPosition (newPosition);" lagi.

Contoh (hanya mengada-ada): Bayangkan Anda menggerakkan pemain dengan setPosisi Anda di atas beberapa jebakan. Panggilan ke "player.setPosition" itu sendiri akan memicu beberapa panggilan lain ke .. katakanlah kode pendengar perangkap yang pada gilirannya akan mendorong animasi pemblokiran dengan sendirinya yang menampilkan "hurting splash" di atasnya.

Anda melihat masalahnya: splash ditampilkan secara bersamaan dengan gerakan pemain ke arah perangkap. (Mari kita asumsikan di sini bahwa Anda tidak menginginkan ini, tetapi ingin animasi perangkap dimainkan segera setelah langkah selesai;)).

Jadi selama Anda dapat menjaga semua konsekuensi dari tindakan Anda dalam pikiran yang Anda lakukan setelah panggilan "pushBlockingAnimation", semuanya baik-baik saja.

Anda mungkin perlu mulai membungkus logika game menjadi "memblokir sesuatu" - kelas sehingga mereka dapat antri untuk mengeksekusi setelah animasi selesai. Atau Anda memberikan callback "callThisAfterAnimationFinishes" ke pushBlockingAnimation dan pindahkan setPosition ke dalam fungsi callback.

Pendekatan lain adalah bahasa scripting. Misalnya Lua memiliki "coroutine.yield" yang mengembalikan fungsi dengan keadaan khusus sehingga dapat dipanggil nanti untuk melanjutkan pada pernyataan berikutnya. Dengan cara ini Anda dapat dengan mudah menunggu akhir animasi untuk mengeksekusi logika game tanpa mengacaukan tampilan alur program "normal" dari kode Anda. (Berarti kode Anda masih akan terlihat sedikit seperti "playAnimation (); setPosition ();" alih-alih meneruskan panggilan balik dan mengacaukan logika game pada banyak fungsi).

Unity3D (dan setidaknya satu sistem gim lain yang saya tahu) menampilkan pernyataan C # "imbal hasil" untuk mensimulasikan ini langsung dalam kode C #.

// instead of simple call to move(), you have to "iterate" it in a foreach-like loop
IEnumerable<Updatable> move(...) {
    // playAnimation returns an object that contain an update() and finished() method.
    // the caller will not iterate over move() again until the object is "finished"
    yield return getGame().playAnimation(...)
    // the next statement will be executed *after* the animation finished
    player.setPosition(newPosition);
}

Tentu saja, dalam skema ini panggilan untuk pindah () mendapatkan semua pekerjaan kotor sekarang. Anda mungkin akan menerapkan teknik ini hanya untuk fungsi tertentu di mana Anda tahu persis siapa yang memanggil mereka dari mana. Jadi anggap ini hanya sebagai ide dasar bagaimana mesin lain diatur - mungkin terlalu rumit untuk masalah spesifik Anda saat ini.

Saya sarankan Anda tetap berpegang pada ide PushBlockingAnimation hingga Anda mengalami masalah di dunia nyata. Kemudian modifikasi itu. ;)

I MI
sumber
Terima kasih banyak telah meluangkan waktu untuk menulis tanggapan ini, saya sangat menghargainya.
mdkess