Pola Signal Godot 4 — Panduan Lengkap

1. Pengantar — Apa yang Berubah di Godot 4

Godot 4 benar-benar merombak cara kerja signal. Jika kamu datang dari Godot 3: sintaks lama berbasis string object.connect("signal_name", target, "method_name") sudah tidak ada lagi. Sebagai gantinya, Godot 4 menggunakan API berbasis Callable yang type-safe, ramah refactoring, dan menangkap kesalahan saat kompilasi, bukan saat runtime.

Panduan ini membahas semua yang perlu kamu ketahui tentang signal di Godot 4.4+: mendeklarasikan, menghubungkan, memutus, memancarkan, await, dan pola praktis yang paling berguna di dunia nyata.

Poin Penting

Di Godot 4, signal adalah objek kelas satu (first-class). Kamu mengaksesnya sebagai properti (mis. button.pressed) dan memanggil metode padanya (.connect(), .emit(), .disconnect()). Tidak ada lagi API berbasis string.

2. Mendeklarasikan Signal Kustom

Deklarasikan signal di bagian atas script-mu dengan kata kunci signal. Secara opsional kamu dapat menentukan nama dan tipe parameter, untuk keperluan dokumentasi dan autocomplete di editor.

player.gd
# Simple signal with no parameters
signal game_over

# Signal with typed parameters
signal health_changed(new_health: int)

# Signal with multiple parameters
signal item_picked_up(item_name: String, quantity: int)

# Signal with no type hints (works, but typed is recommended)
signal something_happened(data)
Tips

Selalu tambahkan petunjuk tipe pada parameter signal. Ini mengaktifkan autocomplete di editor dan membuat kode-mu terdokumentasi sendiri. Signal dengan petunjuk tipe juga menampilkan info parameter di tab Signals pada Node dock.

3. Menghubungkan Signal (Cara Baru)

Perubahan terbesar di Godot 4 adalah cara kamu menghubungkan signal. Alih-alih meneruskan string, kamu menggunakan signal sebagai properti dan memanggil .connect() dengan sebuah Callable (referensi ke sebuah fungsi).

Koneksi Dasar

GDScript
# Godot 3 (OLD — no longer works in Godot 4)
# button.connect("pressed", self, "_on_button_pressed")

# Godot 4 — callable-based connection
button.pressed.connect(_on_button_pressed)

func _on_button_pressed() -> void:
    print("Button was pressed!")

Koneksi dengan Argumen Tambahan via bind()

Gunakan .bind() untuk meneruskan data tambahan bersama signal. Ini berguna saat menghubungkan beberapa signal ke metode yang sama.

GDScript
# Pass extra data via bind()
buy_button.pressed.connect(_on_item_button.bind("sword"))
sell_button.pressed.connect(_on_item_button.bind("shield"))

func _on_item_button(item_id: String) -> void:
    print("Selected item: ", item_id)

Koneksi Lambda (Inline)

Untuk handler pendek, kamu dapat langsung menggunakan fungsi lambda. Ini menjaga logika sederhana tetap dekat dengan titik koneksi.

GDScript
# Lambda — great for one-liners
button.pressed.connect(func(): print("Button pressed!"))

# Lambda with parameters
health_changed.connect(func(hp: int): health_label.text = str(hp))

# Multi-line lambda
enemy.died.connect(func():
    score += 100
    score_label.text = "Score: %d" % score
    print("Enemy defeated!")
)
Peringatan

Koneksi lambda tidak bisa diputus dengan mudah, karena kamu tidak memiliki referensi ke fungsi anonim tersebut. Jika kamu perlu memutusnya nanti, simpan Callable dalam sebuah variabel atau gunakan metode bernama sebagai gantinya.

Menghubungkan di Editor (Node Dock)

Kamu tetap dapat menghubungkan signal melalui UI editor Godot. Pilih sebuah node, buka Node dock > tab Signals, klik ganda sebuah signal, lalu pilih node dan metode target. Editor akan otomatis membuat metode seperti _on_button_pressed() di script-mu. Ini identik dengan memanggil .connect() di dalam kode — hanya menghemat pengetikan.

4. Memutus Signal

Putuskan sebuah signal ketika kamu tidak lagi ingin menerimanya. Ini penting untuk menghindari kesalahan saat node dibebaskan atau saat state permainan berganti.

GDScript
# Disconnect a signal
button.pressed.disconnect(_on_button_pressed)

# Always check before disconnecting to avoid errors
if button.pressed.is_connected(_on_button_pressed):
    button.pressed.disconnect(_on_button_pressed)
Praktik Terbaik

Untuk koneksi signal lintas-scene (mis. koneksi ke sebuah Autoload), selalu putuskan di _exit_tree() untuk mencegah referensi menggantung ketika node dibebaskan:

GDScript
func _ready() -> void:
    EventBus.player_died.connect(_on_player_died)

func _exit_tree() -> void:
    if EventBus.player_died.is_connected(_on_player_died):
        EventBus.player_died.disconnect(_on_player_died)

5. Memancarkan Signal

Di Godot 4, gunakan .emit() sebagai ganti emit_signal() yang lama. Signal adalah sebuah objek, jadi kamu memanggil metodenya secara langsung.

player.gd
signal health_changed(new_health: int)
signal died

var health: int = 100

func take_damage(amount: int) -> void:
    health -= amount
    health_changed.emit(health)  # Emit with argument

    if health <= 0:
        died.emit()  # Emit with no arguments
Catatan

emit_signal("signal_name") yang lama secara teknis masih berfungsi di Godot 4, tetapi sudah usang (deprecated). Untuk kode baru, selalu lebih memilih signal_name.emit(). Varian berbasis string mungkin dihapus pada rilis Godot mendatang.

6. Menunggu Signal (await)

Godot 4 menggantikan yield() dengan await. Dengan ini kamu dapat menjeda sebuah fungsi sampai sebuah signal terpancar — sempurna untuk cutscene, tutorial, animasi berurutan, dan peristiwa berjadwal.

GDScript
func play_cutscene() -> void:
    # Wait for a timer
    await get_tree().create_timer(2.0).timeout

    # Wait for an animation to finish
    animation_player.play("intro")
    await animation_player.animation_finished

    # Wait for player input (custom signal)
    dialogue_label.text = "Press any key to continue..."
    await player_pressed_continue

    # Continue execution after the signal fires
    print("Cutscene complete!")

Mendapatkan Nilai dari Signal yang Di-await

Jika signal memancarkan argumen, await mengembalikannya. Untuk argumen tunggal, kamu mendapatkan nilainya secara langsung. Untuk beberapa argumen, kamu mendapatkan sebuah array.

GDScript
# Single parameter — returns the value directly
var final_health: int = await health_changed
print("Health is now: ", final_health)

# Multiple parameters — returns an array
var result = await item_picked_up
var item_name: String = result[0]
var quantity: int = result[1]

7. Flag Koneksi Signal

Godot menyediakan flag koneksi sebagai argumen kedua dari .connect(), yang memungkinkanmu menyesuaikan perilaku koneksi.

CONNECT_ONE_SHOT

Koneksi otomatis dihapus setelah signal terpancar sekali. Sempurna untuk peristiwa sekali pakai seperti animasi kematian atau membuka pencapaian (achievement).

GDScript
# Auto-disconnects after firing once
enemy.died.connect(_on_first_kill, CONNECT_ONE_SHOT)

func _on_first_kill() -> void:
    unlock_achievement("first_blood")

CONNECT_DEFERRED

Metode yang terhubung dipanggil di akhir frame saat ini (selama waktu idle), bukan langsung. Berguna saat handler signal mengubah scene tree, yang tidak aman dilakukan selama pemrosesan fisika atau signal.

GDScript
# Called at end of frame — safe for scene tree changes
button.pressed.connect(_on_restart, CONNECT_DEFERRED)

func _on_restart() -> void:
    get_tree().reload_current_scene()

Menggabungkan Flag

GDScript
# One-shot AND deferred
trigger.body_entered.connect(_on_trigger, CONNECT_ONE_SHOT | CONNECT_DEFERRED)

8. Pola Umum

EventBus (Hub Signal Global)

Pola EventBus menggunakan singleton Autoload untuk memisahkan (decouple) node. Node mana pun dapat memancarkan atau mendengarkan peristiwa global tanpa memerlukan referensi langsung ke node lain. Ini adalah salah satu pola paling ampuh di Godot.

event_bus.gd (Autoload)
extends Node

# Define all global signals in one place
signal player_died
signal score_changed(new_score: int)
signal level_completed(level_id: int)
signal item_collected(item_name: String)
signal settings_changed
player.gd (emitter)
func die() -> void:
    # Any script can emit global signals
    EventBus.player_died.emit()
hud.gd (listener)
func _ready() -> void:
    # Any script can listen to global signals
    EventBus.score_changed.connect(_on_score_changed)
    EventBus.player_died.connect(_on_player_died)

func _on_score_changed(new_score: int) -> void:
    score_label.text = "Score: %d" % new_score

func _on_player_died() -> void:
    game_over_screen.show()
Menyiapkan Autoload

Buka Project > Project Settings > Autoload, tambahkan script event_bus.gd-mu, dan beri nama EventBus. Ia otomatis menjadi singleton yang dapat diakses secara global.

Relay Signal (Komunikasi Induk-Anak)

Sebuah node induk mendengarkan signal dari anak-anaknya dan meneruskan atau menggabungkan informasi. Anak-anak tidak perlu saling mengetahui satu sama lain.

inventory.gd
signal inventory_updated

func _ready() -> void:
    # Connect to all slot children
    for slot in get_children():
        if slot.has_signal("item_changed"):
            slot.item_changed.connect(_on_slot_changed)

func _on_slot_changed() -> void:
    inventory_updated.emit()

Signal + Await untuk Alur Permainan Berurutan

level_manager.gd
func run_level() -> void:
    spawn_enemies()
    await EventBus.all_enemies_defeated

    show_treasure_chest()
    await EventBus.chest_opened

    play_exit_animation()
    await get_tree().create_timer(1.5).timeout

    load_next_level()

Koneksi Dinamis untuk Node yang Di-spawn

enemy_spawner.gd
func spawn_enemy(pos: Vector2) -> void:
    var enemy = enemy_scene.instantiate()
    enemy.position = pos

    # Connect signals before adding to scene tree
    enemy.died.connect(_on_enemy_died.bind(enemy))
    enemy.health_changed.connect(_on_enemy_health_changed)

    add_child(enemy)

func _on_enemy_died(enemy: Node) -> void:
    enemies_alive -= 1
    enemy.queue_free()

9. Contekan Migrasi (Godot 3 → 4)

Tandai tabel ini sebagai referensi cepat saat memindahkan proyek Godot 3-mu.

Operasi Godot 3 Godot 4
Hubungkan connect("sig", obj, "method") sig.connect(method)
Putuskan disconnect("sig", obj, "method") sig.disconnect(method)
Pancarkan emit_signal("sig", args) sig.emit(args)
Periksa koneksi is_connected("sig", obj, "method") sig.is_connected(method)
Menunggu signal yield(obj, "sig") await obj.sig
Bind argumen tambahan connect("sig", obj, "method", [data]) sig.connect(method.bind(data))
One-shot connect("sig", obj, "method", [], CONNECT_ONESHOT) sig.connect(method, CONNECT_ONE_SHOT)

10. Kesalahan Umum

1. Menggunakan connect berbasis string (gaya Godot 3)

GDScript
# WRONG — Godot 3 syntax, will not compile
button.connect("pressed", self, "_on_button_pressed")

# CORRECT — Godot 4 syntax
button.pressed.connect(_on_button_pressed)

2. Menggunakan emit_signal() alih-alih .emit()

Meskipun emit_signal() masih berfungsi, ia melewati pemeriksaan saat kompilasi dan secara resmi sudah usang. Gunakan .emit().

GDScript
# AVOID — deprecated, no compile-time checks
emit_signal("health_changed", health)

# PREFER — type-safe, catches typos at compile time
health_changed.emit(health)

3. Menghubungkan ke node yang telah dibebaskan

Jika kamu menghubungkan sebuah signal ke metode pada sebuah node, dan node itu di-queue_free(), pemancaran signal berikutnya akan crash. Solusi:

  • Putuskan di _exit_tree()
  • Gunakan CONNECT_ONE_SHOT untuk peristiwa sekali pakai
  • Periksa dengan is_instance_valid(target) sebelum memancarkan
GDScript
# Safe emission pattern
for connection in my_signal.get_connections():
    if is_instance_valid(connection["callable"].get_object()):
        pass  # Connection is still valid
my_signal.emit()  # Godot handles invalid connections gracefully in 4.x

4. Lupa memutus signal lintas-scene

Koneksi ke signal Autoload tetap bertahan melintasi pergantian scene. Jika kamu menghubungkan di _ready() tetapi tidak pernah memutus, kamu akan mendapatkan kesalahan atau perilaku tak terduga saat scene dimuat ulang.

GDScript
# WRONG — never disconnects, leaks across scene changes
func _ready() -> void:
    EventBus.score_changed.connect(_on_score_changed)

# CORRECT — clean disconnect
func _ready() -> void:
    EventBus.score_changed.connect(_on_score_changed)

func _exit_tree() -> void:
    EventBus.score_changed.disconnect(_on_score_changed)

5. Menghubungkan signal yang sama dua kali

Jika _ready() dipanggil beberapa kali (mis. saat memindahkan induk sebuah node), kamu mungkin tanpa sengaja menghubungkan metode yang sama dua kali. Handler kemudian akan terpicu dua kali per pemancaran.

GDScript
# Guard against double-connection
func _ready() -> void:
    if not EventBus.score_changed.is_connected(_on_score_changed):
        EventBus.score_changed.connect(_on_score_changed)

Ingin AI mengelola arsitektur signal-mu?

Godot MCP Pro menghubungkan asisten AI seperti Claude langsung ke editor Godot-mu. Ia dapat menghubungkan, memutus, mengaudit, dan memvisualisasikan alur signal di seluruh proyek-mu — secara otomatis.

analyze_signal_flow find_signal_connections connect_signal disconnect_signal get_signals
Dapatkan Godot MCP Pro — $15