reckfellENGINE

Сохранение прогресса

Позиция персонажа, проверка данных и резервная копия файла.

На этой странице

Запись положения

ConfigFile хранит значения по секциям и ключам. SetValue() меняет данные в памяти; Save() записывает файл. Для прогресса игрока используйте user://.

Методы ниже разместите внутри своего C#-скрипта. player — существующая Entity3D в дереве игры, path — например "user://progress.cfg".

C#
public Error SavePosition(Entity3D player, string path)
{
    using var data = new ConfigFile();
    data.SetValue("player", "position", player.GlobalPosition);
    return data.Save(path);
}

Проверяйте результат: Error.Ok означает успешную запись. Этот короткий вариант перезаписывает один файл без резервной копии.

Загрузка и проверка

Сначала загрузите файл и проверьте тип данных. При ошибке положение персонажа остаётся прежним. После переноса сбросьте интерполяцию, а для CharacterBody3D — скорость.

C#
public Error LoadPosition(Entity3D player, string path)
{
    using var data = new ConfigFile();
    Error error = data.Load(path);
    if (error != Error.Ok)
        return error;

    if (!data.HasSectionKey("player", "position"))
        return Error.InvalidData;
    using Variant value = data.GetValue("player", "position");
    if (value.VariantType != Variant.Type.Vector3)
        return Error.InvalidData;
    Vector3 position = value.AsVector3();
    if (!position.IsFinite())
        return Error.InvalidData;

    player.GlobalPosition = position;
    player.ResetPhysicsInterpolation();
    if (player is CharacterBody3D character)
        character.Velocity = Vector3.Zero;
    return Error.Ok;
}

Отсутствующий файл возвращает Error.FileNotFound: это обычная ситуация при первом запуске. Не заменяйте существующий прогресс начальными данными автоматически при любой ошибке загрузки.

Готовый скрипт с резервной копией

Добавьте в уровень обычную Entity, прикрепите ProgressSave.cs, соберите проект и назначьте персонажа свойству Target. В Input Map добавьте save_game на F2 и load_game на F3.

ProgressSave.cs — запись, проверка формата и резервная копия
C#
using System;
using System.Text;
using Reckfell;
using IO = System.IO;

public partial class ProgressSave : Entity
{
    [Export] public Entity3D Target { get; set; } = null!;
    [Export] public string SavePath { get; set; } = "user://progress.cfg";

    public Error SaveProgress()
    {
        if (!ReckfellObject.IsInstanceValid(Target))
            return Error.Unconfigured;

        if (string.IsNullOrWhiteSpace(SavePath) || SavePath.IndexOf('\0') >= 0 ||
            !SavePath.StartsWith("user://", StringComparison.Ordinal) || SavePath.Length <= 7)
            return Error.FileBadPath;

        Vector3 position = Target.GlobalPosition;
        if (!position.IsFinite())
            return Error.InvalidData;

        using var data = new ConfigFile();
        data.SetValue("save", "format", 1);
        data.SetValue("player", "position", position);
        try
        {
            string path = ProjectSettings.GlobalizePath(SavePath);
            byte[] bytes = Encoding.UTF8.GetBytes(data.EncodeToText());
            using (var stream = new IO.FileStream(path + ".tmp", IO.FileMode.Create, IO.FileAccess.Write))
            {
                stream.Write(bytes);
                stream.Flush(flushToDisk: true);
            }

            if (IO.File.Exists(path))
                IO.File.Replace(path + ".tmp", path, path + ".bak");
            else
                IO.File.Move(path + ".tmp", path);

            return Error.Ok;
        }
        catch (UnauthorizedAccessException)
        {
            return Error.FileNoPermission;
        }
        catch (IO.IOException)
        {
            return Error.CantCreate;
        }
        catch (ArgumentException)
        {
            return Error.FileBadPath;
        }
        catch (NotSupportedException)
        {
            return Error.FileBadPath;
        }
    }

    public Error LoadProgress(bool backup = false)
    {
        if (!ReckfellObject.IsInstanceValid(Target))
            return Error.Unconfigured;

        if (string.IsNullOrWhiteSpace(SavePath) || SavePath.IndexOf('\0') >= 0 ||
            !SavePath.StartsWith("user://", StringComparison.Ordinal) || SavePath.Length <= 7)
            return Error.FileBadPath;

        string path = backup ? SavePath + ".bak" : SavePath;
        if (!IO.File.Exists(ProjectSettings.GlobalizePath(path)))
            return Error.FileNotFound;

        using var data = new ConfigFile();
        Error error = data.Load(path);
        if (error != Error.Ok)
            return error;

        using Variant format = data.GetValue("save", "format", 0);
        if (format.VariantType != Variant.Type.Int || format.AsInt64() != 1)
            return Error.InvalidData;
        if (!data.HasSectionKey("player", "position"))
            return Error.InvalidData;

        using Variant value = data.GetValue("player", "position");
        if (value.VariantType != Variant.Type.Vector3)
            return Error.InvalidData;

        Vector3 position = value.AsVector3();
        if (!position.IsFinite())
            return Error.InvalidData;

        Target.GlobalPosition = position;
        Target.ResetPhysicsInterpolation();
        if (Target is CharacterBody3D character)
            character.Velocity = Vector3.Zero;

        return Error.Ok;
    }

    public override void _UnhandledInput(InputEvent @event)
    {
        if (@event.IsActionPressed("save_game"))
            ReckfellApi.Print($"Save: {SaveProgress()}");
        else if (@event.IsActionPressed("load_game"))
            ReckfellApi.Print($"Load: {LoadProgress()}");
        else
            return;

        GetViewport().SetInputAsHandled();
    }
}

F2 сохраняет положение, F3 восстанавливает его. Папку сохранений можно открыть через Project → Open User Data Folder.

Что происходит с файлом

  1. Новые данные целиком записываются в .tmp и сбрасываются на диск.
  2. При замене основной файл становится .bak. Первое сохранение резервной копии ещё не имеет.
  3. LoadProgress(backup: true) явно загружает резервную копию. Автоматического восстановления и автосохранения в примере нет.

Здесь намеренно используется System.IO.File.Replace(). Не заменяйте его на DirAccess.Rename() или RenameAbsolute(): в Windows 1.0.0 существующий файл назначения удаляется до попытки перемещения. При отсутствующем источнике назначение может пропасть, хотя метод вернёт ошибку. Это воспроизведено на одноразовых файлах; см. ограничения DirAccess.

При отсутствующем .tmp и при эксклюзивной блокировке слота проверенный File.Replace() сохранил основной файл и .bak; при блокировке остался и новый .tmp. После ошибки не загружайте временный файл автоматически: он может быть устаревшим или неполным. Устраните причину, повторите сохранение либо явно загрузите проверенную резервную копию. Пример не поддерживает несколько одновременных записывающих задач.

Flush(true) и успешная замена не доказывают восстановление после отключения питания. Проверены обычные операции и указанные случаи отказа на Windows; аварийное завершение, другие файловые системы и платформы здесь не проверялись.

format = 1 — версия структуры сохранения. Загрузка проверяет её, наличие поля, тип Vector3 и конечность координат до изменения персонажа.

Для нескольких уровней сохраняйте также идентификатор уровня. Координаты одного уровня нельзя применять к другому без проверки. См. переходы между уровнями.

Ошибки

РезультатДействие
OkОперация завершена
FileNotFoundСлота или выбранной резервной копии ещё нет
UnconfiguredНазначьте действующий Target
InvalidDataПроверьте формат, обязательное поле и тип; не меняйте текущий прогресс
FileBadPathУкажите непустой допустимый путь к файлу в user://
FileNoPermissionВыберите доступный путь в user://
CantCreateПроверьте путь, доступное место и доступ к файлу

Запись синхронная: пример рассчитан на небольшой слот. Большой снимок мира требует отдельной организации сериализации и ввода-вывода. Замена с резервной копией проверена на Windows x64.

Справочник: ConfigFile, ProjectSettings.

Документация Reckfell EngineНаверх ↑
БЫСТРЫЙ ПЕРЕХОДНачало работыEntity и иерархияC# & .NET 10Сцены и префабы
Руководства и полный API Reckfell K
reckfellENGINE
RECKFELL ACCOUNT

Вход в аккаунт

Войдите в аккаунт Reckfell.

Один аккаунт для всей платформы Reckfell.