Support me on Ko-fi

Pandangan Saya tentang Manajemen Status Aksi dalam Pengembangan Game

20 Maret 2026
Daniel LuInsinyur Full-Stack | Pembuat Konten

Artikel ini membahas bug umum di mana status aksi saling menindih dan gagal pulih kembali. Berdasarkan pengalaman saya di tim yang baru merintis, saya berbagi cara membangun action state machine yang memiliki skalabilitas tinggi dan ketergantungan rendah di Unity menggunakan Active State Pool dan evaluasi prioritas dinamis.

KategoriTeknologiUnityPengembangan Web

Titik Sakit Bisnis: Bug Penindihan Status Aksi yang Saya Temui

Baru-baru ini saya bergabung dengan tim yang baru saja merintis. Saat membantu mereka merapikan sistem pertarungan dan logika presentasi, saya menemui sebuah bug yang sangat klasik dan menjengkelkan.

Skenarionya seperti ini: Karakter terkena Debuff "Frozen" (Membeku) selama 5 detik; saat itu aksi berubah menjadi [Frozen] (karakter membeku di tempat dan gemetar). Pada detik ke-2, karakter terkena pukulan berat dari musuh yang memiliki efek pusing (stun), memicu gerakan [Stun] selama 2 detik (karakter memegang kepala karena pusing).

Masalah sebenarnya muncul setelah gerakan pusing berakhir: Ketika 2 detik aksi Stun selesai, secara logika sistem, karakter seharusnya masih memiliki sisa 1 detik Debuff "Frozen". Namun, alih-alih kembali ke gerakan [Frozen], karakter justru langsung jatuh ke status [Idle] (diam) default. Hal ini menyebabkan pemain melihat karakter bernapas normal dalam posisi diam, padahal joystick tidak bisa mengontrol pergerakan (karena logika dasarnya masih membeku). Ini menciptakan pemutusan hubungan yang serius antara visual dan logika dasar.

Setelah memeriksa kode, saya menemukan alasannya sangat sederhana: sistem lama yang digunakan tim bersifat berbasis pemicu (trigger-based). Saat Stun dipicu, sistem memaksa Animator memutar gerakan Stun; saat Stun berakhir, sistem secara kasar memaksa Animator kembali ke status default tanpa mempertimbangkan efek logika lain yang masih berlangsung.

Prevalensi Masalah dan Keterbatasan Solusi Tradisional

Secara pribadi, saya merasa masalah ini memiliki prevalensi yang sangat tinggi dalam pengembangan game. Selama game Anda memiliki sistem Buff yang kompleks atau efek skill yang bertumpuk, Anda pasti akan menghadapi masalah penindihan dan pemulihan status aksi.

Banyak tim di tahap awal, demi mengejar hasil cepat, cenderung mengadopsi dua cara berikut yang menurut saya memiliki risiko besar:

Risiko A: Berbagai sistem mengontrol variabel Animator secara langsung

Setiap sistem (sistem Buff, sistem skill, sistem terkena hit) langsung memanggil animator.SetBool("isFrozen", true). Saat sistem menjadi besar, Anda tidak akan tahu kode mana yang mengatur Bool kembali ke false dan kapan itu terjadi, sehingga menyebabkan kekacauan pada state machine dan sangat sulit untuk di-debug.

Risiko B: Penyalahgunaan jaringan koneksi Unity Mecanim

Jendela Animator di Unity terlihat sangat intuitif saat koneksinya sedikit. Namun, jika Anda mengikat logika bisnis pada koneksi tersebut — dengan mengatur banyak syarat seperti Speed > 0, isFrozen == true, isStunned == false — pada akhirnya state machine akan menjadi "jaring laba-laba" yang rumit. Menambah satu status baru berarti harus menghubungkan banyak garis baru, dan biaya pemeliharaannya meningkat secara eksponensial.

Ide Terobosan Saya: Active State Pool dan Evaluasi Prioritas Dinamis

Untuk menyelesaikan masalah status yang saling menindih, gagal pulih, serta ketergantungan yang terlalu tinggi, saya memperkenalkan sebuah ide kepada tim: mekanisme Active State Pool (Pool Status Aktif) dan Evaluasi Prioritas Dinamis.

Inti pemikirannya hanya satu: Permintaan status dan eksekusi status harus dipisahkan sepenuhnya.

Lapisan bisnis (sistem Buff atau skill) hanya bertanggung jawab untuk Mendaftarkan dan Membatalkan pendaftaran status aksi yang diinginkan. Kemudian, sebuah AnimationStateManager yang terpadu akan memutuskan pada frame saat ini, instruksi gerakan apa yang sebenarnya harus dikirimkan ke Animator.

Bagan Alir Mekanisme Inti:

Loading diagram...

Dalam arsitektur ini, ketika "Stun" dengan prioritas 100 berakhir dan dibatalkan pendaftarannya, manajer akan mengevaluasi kembali dan menemukan bahwa prioritas tertinggi yang tersisa di pool adalah "Frozen" dengan prioritas 50. Maka, manajer secara otomatis memerintahkan Animator untuk kembali ke gerakan [Frozen]. Bug yang sebelumnya pun terselesaikan dengan sendirinya.

Implementasi Praktis di Unity: Menjadikan Animator Tetap Murni

Setelah menerapkan arsitektur di atas, saya rasa cara penggunaan Animator di Unity juga perlu berubah. Kita bisa membuang koneksi garis yang rumit serta syarat Trigger/Bool, dan membiarkan Animator menjadi pemutar gerakan yang murni.

Di Animator Controller, Anda hanya perlu memasukkan semua State gerakan yang diperlukan tanpa perlu menghubungkan garis apa pun (atau hubungkan semuanya dari AnyState tanpa syarat). Di level kode, gunakan Animator.CrossFade() atau Animator.Play() untuk menggerakkan transisi status secara langsung.

Contoh Kode Palsu (Pseudo-code) Manajer Inti:

// 1. Definisikan struktur data permintaan status aksi
public class AnimStateRequest
{
    public string StateName; // Nama status di Animator
    public int Priority;     // Prioritas
    public object Source;    // Sumber (untuk validasi saat batal daftar, misal instansi Buff tertentu)
}

// 2. Manajer Terpadu
public class AnimationStateManager : MonoBehaviour
{
    private Animator _animator;
    private List<AnimStateRequest> _activeStates = new List<AnimStateRequest>();
    private string _currentPlayingState;

    // Daftarkan status
    public void AddStateRequest(string stateName, int priority, object source)
    {
        _activeStates.Add(new AnimStateRequest { StateName = stateName, Priority = priority, Source = source });
        EvaluateHighestPriorityState();
    }

    // Batalkan pendaftaran status
    public void RemoveStateRequest(object source)
    {
        _activeStates.RemoveAll(req => req.Source == source);
        EvaluateHighestPriorityState();
    }

    // Evaluasi dinamis status prioritas tertinggi saat ini
    private void EvaluateHighestPriorityState()
    {
        if (_activeStates.Count == 0)
        {
            PlayAnimation("Idle"); // Status default
            return;
        }

        // Cari permintaan dengan prioritas tertinggi
        _activeStates.Sort((a, b) => b.Priority.CompareTo(a.Priority));
        string targetState = _activeStates[0].StateName;

        if (_currentPlayingState != targetState)
        {
            PlayAnimation(targetState);
        }
    }

    // Menggerakkan Unity Animator
    private void PlayAnimation(string stateName)
    {
        _currentPlayingState = stateName;
        // Gunakan CrossFade untuk transisi halus, 0.1f adalah waktu transisi
        _animator.CrossFade(stateName, 0.1f); 
    }
}

Perpanjangan Arsitektur: Ide Saya untuk Sistem Aksi yang Lebih Kompleks

Arsitektur Active Pool dan Evaluasi Prioritas ini memberikan skalabilitas yang baik untuk pengembangan game di masa depan. Dalam pengembangan selanjutnya, saya juga mempertimbangkan beberapa perpanjangan berikut:

  1. Penanganan Konflik Prioritas yang Sama: Jika ada dua permintaan aksi dengan prioritas 50 yang sama, logika evaluasi bisa diperluas, misalnya dengan menetapkan permintaan yang baru masuk sebagai prioritas, atau yang memiliki sisa durasi lebih lama sebagai prioritas.
  2. Manajemen Mandiri Avatar Mask: Dalam game 3D, sering kali tubuh bagian atas perlu menembak sementara tubuh bagian bawah terkena hit atau berlari. Kita bisa membuat dua Manager independen (misalnya UpperBodyPool dan LowerBodyPool), mengevaluasi prioritas tertinggi masing-masing, lalu mengirimkan indeks Layer yang berbeda melalui CrossFade untuk mencapai decoupling yang sempurna.
  3. Mengadopsi Playable API: Untuk gerakan skill dengan timing yang sangat ketat, penggunaan Animator secara langsung mungkin menghadapi masalah penundaan pembaruan status. Di sini, manajer status ini dapat digabungkan dengan Unity Playable Graph di tingkat bawah untuk mencampur AnimationClip secara langsung di sisi C#, sehingga kontrol penuh ada di tangan kita.

Itulah pemikiran dan ringkasan saya mengenai manajemen status aksi dalam pengembangan proyek nyata. Secara objektif, solusi ini menyelesaikan titik sakit tim kami saat ini, namun bukan berarti ini adalah solusi yang sempurna untuk segala hal. Pandangan saya mungkin memiliki keterbatasan, jadi jika teman-teman memiliki solusi yang lebih baik atau menemukan kekurangan dalam implementasi saya, saya sangat terbuka untuk berdiskusi. Mohon koreksinya jika ada kesalahan.


Artikel ini asli dibuat oleh tim iknowabit. Dukungan teknis: Eksplorasi berdasarkan mesin Unity dan pola arsitektur tingkat tinggi C#.