›_ebskola.lv
sākt stundu →
Prog II · 5. tēma · 6 stundas - C# · Save/Load · Roguelike

Datu pastāvība //
un algoritmi

Saglabā un ielādē spēles stāvokli ar FileAccess un Json. Ģenerē labirintus ar Random Walk algoritmu. Izproti Big-O.

6 stundas FileAccess · Json Roguelike labirints
# 01 stundu plāns

6 stundas - save/load, procedurālā ģenerēšana un Big-O

noslēguma projekts →
user://saves/save.jsonconfig.cfgFileAccess.Open(path, Write)StoreString()// FileAccess
Godot · FileAccessI/O

Failu I/O Godot vidē

FileAccess.Open(), StoreString(), GetAsText(), user:// direktorija.

5.1 stundaatvērt ↗
Godot Dictionary{"hp": 80, "level": 3, "pos": [12,8]}Json.Stringify()JSON tekstsJson.Parse()// JSON serialize
Json · serializācijaformāts

JSON serializācija un Resource sistēma

Json.Stringify(), Json.Parse(), Godot Resource saglabāšana, .tres faili.

5.2 stundaatvērt ↗
GameStateSaveManagerdiskload ←saglabāt() · ielādēt() · atiestatīt()// save/load
arhitektūrasave/load

Save/Load arhitektūra

Viena atbildīgā SaveManager klase, GameState serializācija, ielāde spēles uzsākšanā.

5.3 stundaatvērt ↗
Random Walk - ej nejauši un izrok ejas// Random Walk dungeon
Random Walk · procedurālsģenerācija

Procedural generation ar Random Walk

Binary Space Partitioning - rekursīva telpu dalīšana, istabu izveide, koridoru savienošana.

5.4 stundaatvērt ↗
O(1)O(log n)O(n)O(n²)n (ievades lielums)// Big-O
Big-O · profilingefektivitāte

Algoritmu efektivitāte

Big-O notācija, O(1)/O(log n)/O(n)/O(n²) salīdzinājums, Godot Profiler izmantošana.

5.5 stundaatvērt ↗
##.........###.++++.....+##.+@...*....##.++++......##...........##.....E.....###.........##@ - spēlētājs · E - ienaidnieks · * - prece// roguelike projekts
C# · Godotprojekts

Noslēguma projekts: Procedurālais labirints

Procedurāli ģenerēts labirints ar save/load, meta-progresu un savācamiem materiāliem.

5.6 projektsatvērt ↗
# 02 špikeris

5. tēmas špikeris - dati, saglabāšana un algoritmi

Failu darbs, JSON, saglabāšanas sistēma, procedurālā ģenerēšana un efektivitāte. Katrs bloks atbilst vienai stundai.

5.1 Failu lasīšana un rakstīšana

using Godot;

// res://  - projekta faili, TIKAI lasīšanai gatavā spēlē
// user:// - spēlētāja dati, te saglabā (Project -> Open User Data Folder)

using FileAccess file = FileAccess.Open("user://save.txt", FileAccess.ModeFlags.Write);
if (file != null)                         // VIENMĒR pārbaudi
{
    file.StoreString("Sveiks!");
    file.Store32(150);
}

// Nolasīšana ar trūkstoša faila apstrādi (= Python FileNotFoundError)
int LoadScore()
{
    if (!FileAccess.FileExists("user://save.txt")) return 0;
    using FileAccess readFile = FileAccess.Open("user://save.txt", FileAccess.ModeFlags.Read);
    return readFile != null ? (int)readFile.Get32() : 0;
}

Nolasīšanas secībai jābūt TIEŠI tādai pašai kā rakstīšanas secībai, citādi dati sajūk.

5.2 JSON - salasāms saglabāšanas fails

using Godot;
using Godot.Collections;

// Dictionary -> teksts
Dictionary save = new();
save["player_name"] = "Anna";
save["level"] = 5;
string txt = Json.Stringify(save, "    ");   // "    " = atkāpes, salasāmi

// Teksts -> Dictionary AR kļūdas pārbaudi
Json json = new Json();
Error err = json.Parse(txt);
if (err != Error.Ok)
{
    GD.PrintErr("Bojāts save fails, sāku no jauna");
    return DefaultData();          // nevis avarē
}
Dictionary data = json.Data.AsGodotDictionary();

Salasāms fails ir ērts atkļūdošanai, bet spēlētājs to var arī izlabot - tāpēc nekad neuzticies tā saturam akli.

5.3 Save sistēma ar slotiem un versiju

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

public interface ISaveable             // interfeiss: ko prot katrs objekts
{
    Dictionary SaveState();
    void LoadState(Dictionary data);
}

public class SaveManager
{
    private readonly List<ISaveable> objects = new();

    public string SlotPath(int slot) => $"user://slot{slot}.json";

    public Dictionary Collect()
    {
        Dictionary all = new();
        all["version"] = 1;          // versija: vecs fails nesabojā spēli
        for (int i = 0; i < objects.Count; i++)
            all[i] = objects[i].SaveState();
        return all;
    }
}

// Ielādējot:
if (data["version"].AsInt32() != 1) { /* brīdini un lieto noklusējumus */ }

SaveManager nezina, kas ir spēlētāja iekšienē - katrs objekts saglabā sevi pats.

5.4 Procedurālā ģenerēšana ar Random Walk

using System;

// 1 = siena, 0 = eja
int[,] grid = new int[15, 15];
for (int yy = 0; yy < 15; yy++)
    for (int xx = 0; xx < 15; xx++)
        grid[yy, xx] = 1;

int x = 7, y = 7;                       // sāc no centra
grid[y, x] = 0;
Random rng = new Random();

for (int i = 0; i < 200; i++)
{
    int dir = rng.Next(0, 4);
    if (dir == 0 && x > 1) x--;          // robežu pārbaude OBLIGĀTA
    else if (dir == 1 && x < 13) x++;
    else if (dir == 2 && y > 1) y--;
    else if (dir == 3 && y < 13) y++;
    grid[y, x] = 0;
}

// OBLIGĀTI: pārbaudi, vai izeja ir sasniedzama, un ģenerē atkārtoti, ja nav.
// Režģis -> ekrāns:
//   wall.Position = new Vector2(x * 64, y * 64);

Bez izejamības pārbaudes spēlētājs var sākt līmenī, kuru nav iespējams izspēlēt.

5.5 Efektivitāte: mēri, nevis min

using Godot;

ulong t0 = Time.GetTicksUsec();
Darbiba();
ulong t1 = Time.GetTicksUsec();
GD.Print("Aizņēma ", t1 - t0, " mikrosekundes");

// Big O - kā laiks aug, palielinoties datiem 10x:
//   O(1)        nemainās          saraksts[0]
//   O(log n)    +daži soļi        binārā meklēšana, Dictionary
//   O(n)        10x               viens cikls
//   O(n log n)  ~13x              Sort()
//   O(n^2)      100x              divi ligzdoti cikli

// Godot: Debugger -> Profiler -> Start -> spēlē 30 s -> kārto pēc laika

Neoptimizē kodu, kas izpildās reti. Vispirms ar profileri atrodi, kur laiks tiešām aiziet.

C# FileAccess.Open("user://save.json", FileAccess.ModeFlags.Write); // saglabā starp sesijām