Belajar Godot - Game State & Data Persistence
Episode 9 of 23

Belajar Godot - Game State & Data Persistence

Episode ini membahas pengelolaan kondisi game: autoload singleton sebagai penyimpan state global, save dan load data memakai JSON, ConfigFile, hingga format kustom, lalu scene transition, state machine, pause menu, dan settings yang tetap tersimpan antar sesi.

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

Pendahuluan

Di episode 8 kalian belajar merancang UI yang rapi dan responsive. Tapi ada satu pertanyaan yang selalu menyusul: ke mana skor, level, dan pengaturan itu pergi saat game ditutup? Atau saat berpindah scene, apakah semua variabel meledak begitu saja? Episode 9 ini menjawab keduanya: Game State & Data Persistence. Kalian akan belajar menyimpan state global yang tidak hilang antar scene, menulis data ke disk, dan mengatur alur scene dengan disiplin.

Roadmap episode 9: kita mulai dari autoload singleton sebagai rumah bagi state global, lalu menyimpan dan memuat data memakai JSON dan ConfigFile, menyentuh format kustom, kemudian merancang scene transition dan state machine, dan menutupnya dengan pause menu serta settings yang benar-benar persisten.

Autoload & Singleton

Scene di Godot tidak berbagi variabel — begitu scene berubah, semua node-nya dimusnahkan dan variabelnya lenyap. Solusi resminya adalah autoload. Autoload adalah node yang dimuat sekali saat game start dan hidup di atas semua scene. Karena ia selalu ada, semua scene lain bisa mengaksesnya kapan saja.

Caranya: buat scene dengan script (misal game_state.gd), lalu daftarkan di Project Settings > Autoload. Godot menamainya secara otomatis sesuai nama file, dan node itu langsung tersedia di mana pun sebagai singleton — misalnya GameState.

Pythongame_state.gd
extends Node
 
var player_name := "Player"
var score := 0
var unlocked_levels: Array[String] = ["level_1"]
 
func reset() -> void:
    score = 0
    player_name = "Player"
    unlocked_levels = ["level_1"]

Dari scene mana pun, cukup tulis GameState.score += 10 atau GameState.player_name = "Budi". Nilainya langsung berubah secara global dan tetap bertahan walau scene berganti. Ini adalah fondasi pengelolaan state di Godot — tempat nyawa, skor, inventaris, dan pengaturan tinggal.

Info

Autoload tersedia di mana pun karena ia menjadi child dari root scene tree, yang tidak pernah di-unload saat scene berganti. Perhatikan satu jebakan: karena selalu ada, ia tidak pernah melewati _ready() ulang — jadi tempatkan inisialisasi state di _ready() sekali, atau buat fungsi reset() yang dipanggil manual saat memulai game baru.

Menyimpan State dengan JSON

State di memori hanya bertahan selama aplikasi berjalan. Untuk menyimpan antar sesi, kalian menulis ke disk. Godot menyediakan path user:// — lokasi yang aman dan writable di semua platform, tersembunyi dari direktori project. Format paling universal untuk menyimpan data adalah JSON: ringan, dibaca manusia, dan mudah diproses.

Godot punya helper bawaan: JSON.stringify(){gdscript} untuk mengubah dictionary menjadi string, dan JSON.parse_string(){gdscript} untuk kebalikannya. Contoh save sederhana:

Pythonsave_json.gd
extends Node
 
const SAVE_PATH := "user://savegame.json"
 
func save_game() -> void:
    var data := {
        "player_name": GameState.player_name,
        "score": GameState.score,
        "unlocked_levels": GameState.unlocked_levels,
    }
    var file := FileAccess.open(SAVE_PATH, FileAccess.WRITE)
    file.store_string(JSON.stringify(data))
    file.close()
 
func load_game() -> void:
    if not FileAccess.file_exists(SAVE_PATH):
        return
    var file := FileAccess.open(SAVE_PATH, FileAccess.READ)
    var data: Dictionary = JSON.parse_string(file.get_as_text())
    file.close()
    GameState.player_name = data["player_name"]
    GameState.score = data["score"]
    GameState.unlocked_levels = data["unlocked_levels"]

Perhatikan alurnya: buka file dengan FileAccess.open, tulis atau baca dengan store_string dan get_as_text, lalu tutup. Selalu cek file_exists sebelum membaca, dan selalu periksa apakah hasil JSON.parse_string adalah null — JSON yang korup bisa mengembalikan null dan memicu crash jika langsung diakses.

Menyimpan dengan ConfigFile

Kalau data kalian berbentuk key-value sederhana seperti pengaturan, ada format yang lebih nyaman: ConfigFile. Strukturnya mirip INI — tersusun dari section dan key. ConfigFile punya API langsung tanpa perlu manual stringify:

Pythonsettings_config.gd
extends Node
 
const SETTINGS_PATH := "user://settings.cfg"
 
func save_settings() -> void:
    var config := ConfigFile.new()
    config.set_value("video", "fullscreen", DisplayServer.window_get_mode())
    config.set_value("audio", "music_volume", 0.8)
    config.set_value("audio", "sfx_volume", 1.0)
    config.save(SETTINGS_PATH)
 
func load_settings() -> void:
    var config := ConfigFile.new()
    var error := config.load(SETTINGS_PATH)
    if error != OK:
        return
    var music: float = config.get_value("audio", "music_volume", 1.0)
    AudioServer.set_bus_volume_db(
        AudioServer.get_bus_index("Music"),
        linear_to_db(music),
    )

get_value(section, key, default){gdscript} menerima nilai default — jadi kalau key tidak ada, kalian tetap mendapat nilai aman alih-alih error. Bandingkan dengan JSON: ConfigFile pas untuk settings yang datar dan mudah dibaca, JSON pas untuk data kompleks seperti save game berstruktur, dan keduanya bisa digabung dalam satu project.

Format Kustom & Catatan Keamanan

Kadang dua format di atas tidak cukup: kalian butuh encode biner, enkripsi, atau checksum agar data tidak mudah diutak-atik. Untuk itu Godot punya FileAccess.open(path, FileAccess.WRITE) yang bisa menulis data biner dengan store_32, store_float, dan sejenisnya. Format kustom memberi kontrol penuh, tapi wajib didokumentasikan karena kalian yang mengelola kompatibilitas antar versi game.

Satu hal yang sering dilupakan: save game adalah kode rahasia yang terlihat. Pemain bisa membuka file JSON dan mengubah skor mereka. Kalau ini masalah, tambahkan checksum sederhana — misalnya simpan nilai sum dari semua field dan validasi saat load. Untuk penyimpanan di cloud, pertimbangkan service pihak ketiga; untuk lokal, user:// sudah cukup.

Scene Transitions

Game nyata tidak pernah cuma satu scene. Berpindah scene harus terasa mulus, tidak membuang user ke layar hitam mendadak. Cara paling sederhana adalah get_tree().change_scene_to_file(path){gdscript}:

Pythontransition.gd
extends Node
 
func go_to_level(path: String) -> void:
    get_tree().paused = false
    get_tree().change_scene_to_file(path)

Tapi ada pola yang lebih halus: bungkus perpindahan dengan animasi fade. Karena autoload selalu ada, ia sempurna untuk mengelola transisi — tampilkan ColorRect fullscreen, fade in, ganti scene, lalu fade out. Semua ini tanpa takut autoload ikut ter-unload. Hasilnya perpindahan scene yang terasa premium dengan kode yang tetap sederhana.

State Machine untuk Alur Game

State game bisa cepat rumit: menu utama, loading, gameplay, pause, game over, victory. Kalau semua dirawat dengan if-else bersarang, kode akan berantakan. Solusinya state machine — memodelkan game sebagai kumpulan state dan aturan perpindahan antar state.

Implementasi paling sederhana di GDScript memakai enum dan match:

Pythonstate_machine.gd
extends Node
 
enum GameState { MENU, PLAYING, PAUSED, GAME_OVER }
 
var state := GameState.MENU
 
func set_state(next: GameState) -> void:
    var prev := state
    state = next
    print("state: %s -> %s" % [prev, next])
 
func _unhandled_input(event: InputEvent) -> void:
    if event.is_action_pressed("pause") and state == GameState.PLAYING:
        set_state(GameState.PAUSED)
        get_tree().paused = true

State machine memberi satu sumber kebenaran untuk kondisi game: logika hanya boleh berjalan pada state yang tepat, dan transisi diatur di satu tempat. Dari sini kalian bisa berevolusi ke sistem yang lebih besar, tapi pola enum + match ini sudah menangani mayoritas game.

Pause Menu & Persistence

Mari satukan semuanya: pause menu dengan Control, disiplin state, dan settings yang persisten. Saat pemain menekan pause, kita set get_tree().paused = true, tapi node pause menu harus tetap berjalan. Rahasianya adalah process_mode:

Pythonpause_menu.gd
extends CanvasLayer
 
func _ready() -> void:
    process_mode = Node.PROCESS_MODE_ALWAYS
    visible = false
 
func _unhandled_input(event: InputEvent) -> void:
    if event.is_action_pressed("pause"):
        visible = not visible
        get_tree().paused = visible

Dengan PROCESS_MODE_ALWAYS, node tetap memproses input walau tree sedang paused — inilah kenapa tombol "Resume", "Settings", dan "Quit" di pause menu tetap berfungsi. Saat Settings ditekan, simpan perubahan ke ConfigFile (pola di atas) sehingga lain kali game dibuka, pengaturan masih utuh. Autoload menyimpan state saat bermain, ConfigFile menyimpan preferensi, dan keduanya bekerja sama tanpa tabrakan.

Penutup

Episode 9 menuntaskan urusan state: autoload singleton sebagai gudang state global, save dan load dengan JSON dan ConfigFile plus format kustom dan checksum, perpindahan scene yang terkelola, state machine untuk alur game yang disiplin, serta pause menu dengan process_mode dan settings yang persisten.

Inti yang harus dibawa pulang:

  • Autoload adalah satu-satunya tempat yang aman untuk state yang harus bertahan antar scene.
  • JSON.stringify dan JSON.parse_string adalah pasangan save-load yang paling universal.
  • ConfigFile lebih nyaman untuk settings key-value, dan selalu beri nilai default saat membaca.
  • user:// adalah path aman untuk menyimpan data di semua platform.
  • State machine dengan enum + match menjaga alur game tetap terstruktur.
  • Pause menu butuh process_mode = PROCESS_MODE_ALWAYS agar tetap responsif saat game di-pause.

Di episode 10 kalian akan menghidupkan indra pemain: Audio & Sound Design. Kita akan mengenal AudioStreamPlayer sampai versi 2D/3D, mengatur background music dan sound effects, memahami audio buses, volume, pitch shifting, dan efek audio, sampai strategi integrasi audio yang tidak menguras performa. Sampai jumpa di dunia suara!