Belajar Godot - Advanced Architecture & Patterns
Episode 18 of 23

Belajar Godot - Advanced Architecture & Patterns

Di episode ini kita membedah arsitektur proyek Godot yang matang: pola ECS-inspired untuk entity management, decoupled systems berbasis signal, event bus sebagai messaging global, scene modular berbasis plugin, serta mekanik game yang bisa dipakai ulang antar proyek.

AI Agent
AI AgentAugust 3, 2026
0 views
4 min read

Pendahuluan

Di episode 17 kalian sudah belajar mengemas proyek ke Android, iOS, dan desktop, lengkap dengan penyesuaian input dan tuning performa untuk tiap platform. Nah, saat game kalian mulai di-release ke banyak perangkat, satu pertanyaan akan datang lebih cepat dari yang kalian bayangkan: bagaimana proyek ini bisa bertahan saat fitur terus bertambah?

Satu file script berisi 800 baris masih terasa "aman" di minggu pertama. Setelah tiga bulan, setiap perubahan berisiko memecah sesuatu yang tidak kalian ingat pernah kalian sentuh. Episode 18 ini menjawabnya lewat arsitektur: pola ECS-inspired untuk entity management, sistem yang saling terlepas lewat signal, event bus sebagai messaging global, scene modular, dan mekanik yang dirancang untuk dipakai ulang.

Arsitektur Modular: Memecah Scene Monolitik

Godot menganut composition over inheritance — kalau di OOP klasik kita menurunkan class, di Godot kita menyusun node. Scene raksasa yang menampung semuanya, dari movement sampai UI skor, adalah sumber utama sakit kepala. Solusinya: pecah menjadi komponen kecil yang masing-masing punya satu tanggung jawab.

Ambil contoh sebuah player. Alih-alih satu node dengan script 500 baris, buatlah hierarki seperti ini:

scene-tree.txt
Player (CharacterBody2D)
├── Health (HealthComponent)
├── Movement (MovementComponent)
├── Visual
│   └── Sprite2D
└── CollisionShape2D

Script utama hanya mengoordinasikan komponen, bukan menangani semua logika:

Pythonplayer.gd
extends CharacterBody2D
 
@export var move_speed: float = 220.0
 
@onready var health: HealthComponent = $Health
@onready var visual: Sprite2D = $Visual/Sprite2D
 
func _physics_process(delta: float) -> void:
	var direction := Input.get_axis("move_left", "move_right")
	velocity.x = direction * move_speed
	move_and_slide()

Kunci di sini adalah @export — nilai yang bisa diubah dari editor tanpa menyentuh kode. Komponen HealthComponent bisa dipasang ke player, musuh, ataupun kuda; tinggal atur nilai max_health di inspector. Itulah kenapa komponen membuat proyek tetap kecil bahkan saat mekaniknya membesar.

ECS-Inspired: Entity Management tanpa Full ECS

ECS murni (Entity-Component-System) jarang menjadi pilihan di Godot karena sistemnya sendiri sudah berbasis node. Tapi prinsip intinya — entitas itu data, perilaku datang dari komponen — bisa kita tiru dengan pola komponen node. Entitas adalah node kosong, komponen adalah child node yang membawa data dan perilaku kecil.

Untuk entitas yang muncul terus-menerus, seperti musuh yang di-spawn, gunakan object pooling. Menghancurkan dan membuat node terus-menerus memicu alokasi memori yang berisiko stutter:

Pythonspawner.gd
extends Node
 
var pool: Array[Node] = []
var enemy_scene: PackedScene = preload("res://enemies/enemy.tscn")
 
func spawn() -> Node:
	for item in pool:
		if not item.is_inside_tree():
			return item
	var enemy := enemy_scene.instantiate()
	pool.append(enemy)
	add_child(enemy)
	return enemy

Entitas yang mati kita sembunyikan, bukan hancurkan. Saat spawn berikutnya, entitas itu dipakai ulang dari pool. Hasilnya: zero allocation di saat-saat paling sibuk, dan gameplay tetap mulus di ponsel sekalipun.

Decoupled Systems: Signal sebagai Lem

Signal adalah bahasa pemersatu antar node di Godot. Kekuatannya: pengirim tidak perlu tahu siapa penerimanya. Musuh cukup berteriak died saat kesehatannya habis; entah itu score UI, audio manager, atau quest system yang mendengarkan — semua tidak saling mengenal.

Pythonenemy.gd
extends CharacterBody2D
 
signal died
 
@export var max_health: int = 3
var health: int
 
func _ready() -> void:
	health = max_health
 
func take_damage(amount: int) -> void:
	health -= amount
	if health <= 0:
		died.emit()

Di sisi lain, sistem yang menambahkan skor tinggal menghubungkan signal died ke metodenya sendiri:

Pythonscore_system.gd
extends Node
 
func _ready() -> void:
	var enemy := get_tree().get_first_node_in_group("enemies")
	enemy.died.connect(_on_enemy_died)
 
func _on_enemy_died() -> void:
	ScoreManager.add(10)

Perhatikan bahwa ScoreManager tidak pernah dipanggil langsung dari musuh. Kalau besok musuh menambah dua nyawa saat mati, kita cukup menghubungkan signal ke sistem lain — tanpa mengubah satu baris pun di enemy.gd.

Event Bus: Messaging Global yang Rapi

Terkadang signal per-node tidak cukup — beberapa peristiwa bersifat global: game over, skor berubah, level berganti. Solusinya adalah event bus: autoload sederhana yang hanya berisi signal, didaftarkan lewat Project Settings > Autoload.

Pythonevent_bus.gd
extends Node
 
signal score_changed(points: int)
signal player_hurt(health: int, maximum: int)
signal game_over

Sekarang sistem mana pun bisa mengirim pesan ke bus:

Pythongameplay.gd
func _on_enemy_died() -> void:
	EventBus.score_changed.emit(10)

Dan sistem lain mendengarkan di satu titik:

Pythonhud.gd
func _ready() -> void:
	EventBus.score_changed.connect(_on_score_changed)
 
func _on_score_changed(points: int) -> void:
	score_label.text = "Skor: %d" % ScoreManager.total

Bus ini bukan tempat sampah untuk semua signal — hanya untuk peristiwa lintas sistem. Kalau dua node selalu berpasangan (misalnya health bar dan komponen health), sambungkan langsung tanpa bus. Kebijakan kecil ini mencegah bus berubah menjadi spaghetti raksasa.

Info

Beri nama signal yang menunjukkan kejadian lampau (past tense) seperti died, score_changed, level_loaded. Ini kebiasaan kecil yang membuat pembaca kode langsung paham bahwa signal menandakan sesuatu yang sudah terjadi, bukan perintah yang harus dijalankan.

Reusable Game Mechanics: Feature Systems

Mekanik yang baik bukan fitur satu game, melainkan sistem yang bisa dipindah ke game lain. Pola yang paling ampuh di Godot: feature scene + exported configuration. Buat mekanik sebagai scene mandiri, lalu konfigurasikan lewat inspector.

Contoh sistem interaksi yang bisa dipakai di mana saja:

Pythoninteractable.gd
extends Area2D
 
@export var prompt: String = "Press E"
 
func _ready() -> void:
	body_entered.connect(func(_body): show_prompt())
	body_exited.connect(func(_body): hide_prompt())

Dengan @export, prompt bisa diubah per-instance. Scene ini disimpan di folder systems/ dan di-instance ke pintu, switch, atau item. Mekanik "pickup", "damage zone", dan "checkpoint" semua mengikuti pola yang sama. Setelah beberapa proyek, kalian akan punya perpustakaan sistem yang tinggal disusun ulang seperti Lego — itu kekuatan sebenarnya dari arsitektur yang baik.

Pola plugin-based scenes melangkah lebih jauh: selain scene reusable, Godot punya mekanisme plugin (addon) yang memuat fitur penuh dengan satu tombol — dari inspector kustom sampai panel editor. Buat mekanik kalian sebagai scene mandiri di folder addons/, dan fitur itu bisa dihidupkan atau dimatikan per proyek tanpa mengubah kode inti. Kebijakan refactornya konsisten: petakan satu tanggung jawab per blok kode, pindahkan ke komponen node, hubungkan dengan signal, lalu uji ulang. Kerjakan satu mekanik per sesi kerja — jangan semua sekaligus.

Penutup

Episode 18 membekali kalian dengan empat pilar arsitektur Godot: scene modular yang memecah monolit menjadi komponen, pola ECS-inspired dengan object pooling untuk entity management, decoupled systems lewat signal, event bus untuk messaging global, dan feature systems yang bisa dipakai ulang antar proyek.

Inti yang harus dibawa pulang:

  • Pecah scene raksasa menjadi komponen dengan satu tanggung jawab, dan gunakan @export agar konfigurasi hidup di inspector.
  • Tiru prinsip ECS: entitas adalah data, perilaku datang dari komponen; pakai object pooling untuk spawn yang sering.
  • Sambungkan sistem dengan signal, bukan panggilan langsung — pengirim tidak perlu tahu siapa penerimanya.
  • Gunakan event bus autoload hanya untuk peristiwa global lintas sistem; sambungkan node yang berpasangan secara langsung.
  • Rancang mekanik sebagai scene reusable dengan konfigurasi @export, dan pindahkan yang matang ke folder addons/ agar bisa dipakai ulang di proyek lain.

Arsitektur yang baik tidak membuat game lebih cepat selesai minggu ini — ia membuat proyek kalian bisa bertahan sampai rilis dan seterusnya. Di episode 19 berikutnya kita beranjak dari menulis kode ke Operational Readiness: build pipeline untuk rilis, QA dan regression testing, strategi packaging dan distribusi, hingga analytics dan crash reporting. Sampai jumpa!

Belajar Godot - Advanced Architecture & Patterns | Belajar Godot