Membedah jantung semua game: game loop dengan fase update & render, hingga arsitektur Entity Component System (ECS) dan scene/entity systems, lengkap dengan implementasi game loop sederhana dan praktik menata arsitektur game yang scalable

Setelah di episode 1 kita memetakan peran game developer, saatnya membedah hal yang paling mendasar: apa yang sebenarnya berjalan saat sebuah game berjalan. Jawabannya selalu sama di semua game dan semua engine: sebuah game loop — perulangan tak berujung yang memproses input, memperbarui dunia game, dan menggambar frame berikutnya.
Mengapa ini penting? Karena hampir semua keputusan arsitektur game berakar pada game loop: bagaimana timing dikelola, bagaimana state disimpan, bagaimana sistem saling berkomunikasi. Memahami game loop dan pola arsitektur seperti ECS akan membuat kalian jauh lebih cepat memahami cara kerja Unity, Godot, dan Unreal di episode 3-5.
Setiap game, apa pun bentuknya, menjalankan tiga langkah berulang:
Versi paling sederhananya dalam bentuk loop biasa terlihat seperti ini. Kalian bisa mencobanya dalam bahasa apa pun — di sini kita pakai TypeScript sederhana untuk menekankan konsepnya, bukan engine:
let running = true
let lastTime = performance.now()
while (running) {
const now = performance.now()
const delta = (now - lastTime) / 1000
lastTime = now
processInput()
update(delta)
render()
}Perhatikan delta — selisih waktu antar frame dalam detik. Ini bagian paling penting dari game loop: delta time membuat gerakan konsisten berapa pun frame rate-nya. Jika karakter bergerak 5 unit per detik, maka per frame ia bergerak 5 * delta unit.
Dalam praktik nyata, engine memisahkan update menjadi dua: variable update (setiap frame) dan fixed update (dengan interval tetap, misalnya 50 kali per detik). Mengapa perlu dua?
| Jenis | Dipakai Untuk | Contoh |
|---|---|---|
| Update (variable) | Logika yang bergantung visual/frame | Animasi, kamera, input |
| Fixed Update | Logika yang harus deterministik | Physics, pergerakan character controller |
Physics butuh fixed timestep agar hasilnya deterministik — jumlah langkah simulasi sama di mesin mana pun, berapa pun fps-nya. Itulah mengapa Unity menyediakan FixedUpdate, dan Godot memiliki _physics_process terpisah dari _process. Ketiga engine di series ini mengadopsi pola yang sama; perbedaannya hanya nama dan pengaturannya.
Semakin besar game, game loop saja tidak cukup — kalian butuh cara menata data dan logika. Tiga pola yang dominan:
Pola tertua: game terdiri dari scene (level), yang berisi object yang saling punya parent-child. Properti mengalir dari parent ke child — jika parent bergerak, semua anak ikut bergerak. Unity (GameObject), Godot (Node), dan Unreal (Actor) semuanya memakai pola ini. Strukturnya kira-kira begini:
Level 1 (Scene)
├── Player (CharacterBody2D)
│ ├── Sprite
│ └── CollisionShape2D
├── Enemies
│ ├── Slime (musuh patrol)
│ └── Bird (musuh terbang)
└── Collectibles
└── FruitECS memecah objek menjadi entity (identitas kosong), component (data murni), dan system (logika). Tidak ada hierarki — hanya kumpulan data yang diproses secara paralel. Contoh sederhana dalam bentuk konsep:
Entity 1: { Position, Velocity, Sprite }
Entity 2: { Position, Velocity }
Entity 3: { Position, Health }
System Movement : untuk tiap entity yang punya Position+Velocity → posisi += velocity * dt
System Render : untuk tiap entity yang punya Position+Sprite → gambar sprite di posisiKelebihan ECS: performa sangat tinggi (data berdekatan di memori, mudah diparalelkan), sangat cocok untuk game dengan ribuan entity. Kekurangannya: pola berpikirnya kurang intuitif bagi pemula dan kode bisa lebih verbose. Unity menawarkan DOTS/ECS, Godot punya dukungan ECS eksperimental; pola klasik hierarki tetap yang paling umum dipakai.
Satu konsep pelengkap yang wajib dikuasai: game state. Loop yang sama biasanya menangani beberapa mode — menu, gameplay, paused, game over. Pola standarnya adalah state machine di level atas:
MAIN_MENU → LOADING → PLAYING → PAUSED → GAME_OVER
↑ │
└─────────┘Kunci dari state machine: setiap state punya aturan transisi yang jelas. PAUSED hanya bisa kembali ke PLAYING (atau QUIT), tidak bisa langsung ke GAME_OVER. Ini mencegah game terjebak dalam state yang tidak konsisten — bug klasik seperti "game over muncul saat pause menu masih terbuka" berasal dari transisi yang longgar.
Tip
Jangan menulis if (mode == "menu") ... else if (mode == "playing") ... dengan ratusan cabang. Untuk game sekecil apa pun, pisahkan setiap state menjadi kelas/fungsi sendiri. Kalian akan berterima kasih saat game mulai besar — dan pola ini akan kalian terapkan di episode 10 untuk AI musuh.
Sekarang mari terapkan. Kita bangun game loop minimal dengan logika lompat sederhana — tanpa engine, cukup memahami irama loop. Versi pseudo-code gaya C#:
void Update(float delta)
{
if (state == State.Playing)
{
if (input.jumpPressed && player.onGround)
player.velocity.y = jumpSpeed;
player.velocity.y -= gravity * delta;
player.position += player.velocity * delta;
}
}
void Render()
{
clearScreen();
drawSprite(player.sprite, player.position);
drawText($"Skor: {score}");
}Inilah esensi yang sama persis dengan yang dilakukan Unity di Update(), Godot di _process(), dan Unreal di Tick(). Ketika kalian menulis script di engine nanti, kalian hanya mengisi bagian dalam fungsi-fungsi itu — engine yang menjalankan loop-nya.
Game loop dan arsitektur adalah fondasi yang menyatukan semua episode ke depan.
Inti yang harus dibawa pulang:
Di episode 3 selanjutnya kita akan masuk ke engine pertama: Unity fundamentals — Scene, GameObject, script dengan MonoBehaviour, dan physics (Rigidbody & Collider), plus membuat game Unity pertama dengan studi kasus Rimba Runner. Sampai jumpa di episode 3!