ebSkola

5.3 Save/Load arhitektūra

Stundas uzdevums: Izveidot pilnvērtīgu saglabāšanas sistēmu ar vairākiem slotiem un autosave.

SR 2.4.17. Failu I/O SR 2.4.6. Plāno izstrādes procesu

80 min plāns: Teorija un paraugs (~10 min) · 1. uzdevums (~20 min) - izveido SaveManager ar slotiem · 2. uzdevums (~25 min) - liec objektiem saglabāt sevi pašiem · 3. uzdevums (~20 min) - pievieno autosave un versiju · Iesniegšana (~5 min).

Pirms sāc: atver savu projektu ar JSON saglabāšanu no 5.2 stundas. Šodien no vienas funkcijas taisīsi sistēmu, kas der visai spēlei.

Teorija: Save sistēmas arhitektūra

Profesionāla save sistēma sastāv no vairākiem komponentiem:

  • SaveManager - galvenais koordinators.
  • Saveable interfeiss - visi saglabājami objekti to implementē.
  • Save slots - vairāki saglabāšanas faili (slot1.json, slot2.json, ...).
  • Autosave - automātiska saglabāšana ik pa N minūtēm vai pie checkpoint.
using Godot;
using Godot.Collections;
using System.Collections.Generic;

public interface ISaveable
{
    Dictionary SaveState();
    void LoadState(Dictionary state);
}

public partial class SaveManager : Node
{
    private readonly List<ISaveable> saveables = new();

    public void RegisterSaveable(ISaveable saveable)
    {
        saveables.Add(saveable);
    }

    public void SaveToSlot(int slot) { }
    public void LoadFromSlot(int slot) { }
}

Kā šo izmantot projektā

Atceries: ar redzamu efektu editorā nepietiek. Paskaidro, kura C# klase glabā stāvokli, kura metode to maina un kā Godot node struktūra izmanto šo kodu.

Pārbaudi: C# kods pārbauda failu kļūdas, validē datus, izmanto versijas lauku un pamato algoritmu sarežģītību.

public class PersistenceCheckpoint
{
    public string Lesson = "5.3 Save/Load arhitektūra";
    public bool UsesCSharp = true;
    public bool HandlesMissingFile = true;
    public bool ValidatesData = true;
    public bool DocumentsComplexity = true;
}

Praktiskie uzdevumi

1. uzdevums -

Izveido SaveManager ar slotiem

Beigās spēlētājs varēs izvēlēties, kurā vietā saglabāt.

  1. Izveido klasi SaveManager ar metodi private string SlotPath(int slot).
  2. Atgriez tajā ceļu user://slot1.json atbilstoši numuram.
  3. Uzraksti metodes public void Save(int slot) un public bool Load(int slot).
  4. Saglabā dažādus datus slotos 1 un 2.
  5. Ielādē slotu 1 un pārbaudi, ka dati ir pareizie.
  6. Ielādē slotu 2 un salīdzini.
  7. Uzraksti metodi, kas pasaka, kuri sloti vispār eksistē.

Gatavs, kad: abi sloti glabā atšķirīgus datus, un metode pareizi pasaka, kuri sloti ir aizpildīti.

2. uzdevums -

Liec objektiem saglabāt sevi pašiem

Beigās katrs objekts pats zinās, ko par sevi saglabāt.

  1. Izveido interfeisu public interface ISaveable ar divām metodēm.
  2. Pieteic tajā Dictionary SaveState();.
  3. Pieteic arī void LoadState(Dictionary state);.
  4. Liec spēlētāja klasei implementēt ISaveable un abas metodes.
  5. Liec arī inventāra klasei to pašu.
  6. Savāc SaveManager visus ISaveable objektus vienā sarakstā.
  7. Izsauc ciklā katram SaveState() un saliec rezultātus vienā Dictionary.

Gatavs, kad: viens Save() izsaukums saglabā gan spēlētāju, gan inventāru, un SaveManager nezina to iekšējo uzbūvi.

3. uzdevums -

Pievieno autosave un versiju

Beigās spēle saglabāsies pati, un vecie faili neradīs kļūdas.

  1. Pievieno saglabātajam Dictionary lauku version ar vērtību 1.
  2. Pārbaudi ielādējot, vai versija sakrīt.
  3. Izdrukā brīdinājumu un lieto noklusējumus, ja versija ir citāda.
  4. Pievieno Timer mezglu, kas ik pēc 60 sekundēm izsauc autosave.
  5. Saglabā autosave atsevišķā slotā, lai tas nepārrakstītu spēlētāja slotu.
  6. Palaid spēli un pagaidi vienu autosave ciklu.
  7. Nomaini failā versiju uz 2 un pārbaudi brīdinājumu.

Gatavs, kad: autosave fails parādās pats, un ar nepareizu versiju spēle brīdina, nevis avarē.

Papildu uzdevums - Parādi slotu izvēlni

Ja pamatdarbs ir gatavs, ļauj slotus izvēlēties uz ekrāna.

  1. Izveido UI ar trim pogām - pa vienai katram slotam.
  2. Parādi pie katras, vai slots ir tukšs vai aizpildīts.
  3. Ielādē attiecīgo slotu, nospiežot pogu.
  4. Pievieno pogu slota dzēšanai.
  5. Pārbaudi, ka pēc dzēšanas slots rāda 'tukšs'.

Gatavs, kad: izvēlnē redzams katra slota statuss, un slotus var ielādēt un dzēst ar pogām.

Ko sagatavo

Stundas mērķis: Tavā spēlē ir SaveManager ar vairākiem slotiem, objekti paši saglabā savus datus caur ISaveable interfeisu, darbojas autosave, un saglabātajam failam ir versijas pārbaude.

GitHub krātuvē jābūt:

  1. Faili SaveManager.cs un ISaveable.cs.
  2. Divi paraugi saglabāto JSON failu no dažādiem slotiem.
  3. Ekrānuzņēmums ar brīdinājumu par nepareizu versiju.
  4. README.md: kāpēc SaveManager nav jāzina, kas tieši ir spēlētāja iekšienē.

Kā iesniedz: Veic commit un push. Skolotāja norādītajā vietā ievieto tikai GitHub krātuves saiti; visiem prasītajiem failiem, README.md pierakstiem un pierādījumiem jābūt pašā krātuvē.

Biežākās kļūdas

  • Save fails fragmented: Save daļas atsevišķos failos vs viens - izvēlies vienu pieeju.
  • Race condition: Save un load notiek vienlaikus - lieto mutex vai sekvenciāli.
  • Slot UI nesinhronizējas: Pārbaudi metadata refresh pēc save.

Godot ekrānuzņēmumi

Save/Load UI: 3 slot kartiņas, katra ar 'Slot 1: Procedurālais klases labirints Lv 5, 1h 23m, 2026-05-15', un poga 'Save'/'Load'/'Delete'.
Save/Load UI: 3 slot kartiņas, katra ar 'Slot 1: Procedurālais klases labirints Lv 5, 1h 23m, 2026-05-15', un poga 'Save'/'Load'/'Delete'.

Koda piemērs (paplašināts)

using Godot;
using Godot.Collections;
using System.Collections.Generic;

public partial class SaveManager : Node
{
    private readonly List<Node> saveables = new();
    private float autosaveTimer = 0.0f;

    public override void _Process(double delta)
    {
        autosaveTimer += (float)delta;
        if (autosaveTimer >= 300.0f)  // 5 min
        {
            SaveToSlot(0);  // 0 = autosave
            autosaveTimer = 0.0f;
        }
    }

    public void SaveToSlot(int slot)
    {
        Dictionary allData = new();
        foreach (Node node in saveables)
        {
            if (node.HasMethod("SaveState"))
            {
                allData[node.GetPath().ToString()] = node.Call("SaveState");
            }
        }
        allData["timestamp"] = Time.GetUnixTimeFromSystem();
        allData["version"] = 1;

        string path = $"user://save_{slot}.json";
        using FileAccess file = FileAccess.Open(path, FileAccess.ModeFlags.Write);
        file?.StoreString(Json.Stringify(allData, "  "));
    }
}
3 save slot ar metadatu (level, time played, last save), autosave ik 5 min, korekta load.