Чтение файлов и внешних ресурсов
Проверяемое чтение UTF-8, ошибки записи и загрузка PNG без редакторского импорта.
Текст: полный пример
Прикрепите FileDemo.cs к обычной Entity, соберите проект и запустите уровень. Скрипт создаёт user://io-demo/message.txt, записывает две строки UTF-8, читает их и выводит текст с File demo: Ok. Повторный запуск перезаписывает этот учебный файл.
WriteText() принимает путь, заданный вашим кодом. ReadText() возвращает пустую строку при ошибке и ограничивает чтение 1 МиБ. OutOfMemory здесь означает превышение лимита примера. Внутри using файл закрывается и при раннем return.
FileDemo.cs
using System.Text;
using Reckfell;
public partial class FileDemo : Entity
{
public const string Folder = "user://io-demo";
public const string Note = Folder + "/message.txt";
public const int MaxBytes = 1024 * 1024;
private static readonly UTF8Encoding Utf8 = new(false, true);
public static Error WriteNote(string text) => WriteText(Note, text);
public static Error WriteText(string path, string text)
{
try
{
if (Utf8.GetByteCount(text) > MaxBytes)
return Error.OutOfMemory;
}
catch (EncoderFallbackException)
{
return Error.InvalidData;
}
Error error = DirAccess.MakeDirRecursiveAbsolute(path.GetBaseDir());
if (error != Error.Ok)
return error;
using var file = FileAccess.Open(path, FileAccess.ModeFlags.Write);
if (file is null)
return FileAccess.GetOpenError();
return file.StoreString(text) ? file.GetError() : Error.FileCantWrite;
}
public static Error ReadText(string path, out string text)
{
text = "";
using var file = FileAccess.Open(path, FileAccess.ModeFlags.Read);
if (file is null)
return FileAccess.GetOpenError();
ulong length = file.GetLength();
if (length > MaxBytes)
return Error.OutOfMemory;
byte[] bytes = file.GetBuffer((long)length);
if ((ulong)bytes.Length != length)
return Error.FileCorrupt;
try
{
text = Utf8.GetString(bytes);
return Error.Ok;
}
catch (DecoderFallbackException)
{
return Error.InvalidData;
}
}
public override void _Ready()
{
Error error = WriteNote("Reckfell: привет\nSecond line");
if (error == Error.Ok)
{
error = ReadText(Note, out string text);
if (error == Error.Ok)
ReckfellApi.Print(text);
}
ReckfellApi.Print($"File demo: {error}");
}
}Open() возвращает null при неудаче. Сразу получите GetOpenError(), прежде чем открывать другой файл в том же потоке. Проверяйте и открытие, и результат записи. Чтение отличает пустой файл от ошибки и отвергает некорректный UTF-8.
Это прямая синхронная перезапись, без резервной копии и гарантии восстановления при отключении питания. Для прогресса используйте слот с заменой и резервной копией. Не пишите в один слот из нескольких задач одновременно.
Режим открытия
| Режим | Поведение |
|---|---|
Read | Чтение существующего файла |
Write | Создаёт файл или очищает существующий |
ReadWrite | Читает и пишет существующий файл без начального обнуления |
WriteRead | Создаёт либо очищает файл; затем доступны запись и чтение |
Для добавления в существующий файл откройте ReadWrite и вызовите SeekEnd(). Если файла ещё нет, выберите Write. Перезапись с начала без усечения может оставить старый хвост.
Внешнее изображение
Скопируйте PNG в user://io-demo/preview.png. В уровне создайте TextureRect; рядом добавьте обычную Entity со скриптом ExternalPreview.cs и назначьте ей Target. Первая загрузка выполняется в _Ready(). Для повторной вызовите Reload() из своего обработчика кнопки. Error.Ok означает, что новая текстура назначена; при ошибке прежняя остаётся. Для видимости задайте TextureRect ненулевой размер. WithMipmaps нужен при уменьшении текстуры с подходящим фильтром или для использования в 3D.
ExternalPreview.cs
using Reckfell;
public partial class ExternalPreview : Entity
{
[Export] public string ImagePath { get; set; } = "user://io-demo/preview.png";
[Export] public TextureRect Target { get; set; } = null!;
[Export] public bool WithMipmaps { get; set; }
public override void _Ready() => ReckfellApi.Print(Reload());
public Error Reload()
{
if (!ReckfellObject.IsInstanceValid(Target))
return Error.Unconfigured;
if (!FileAccess.FileExists(ImagePath))
return Error.FileNotFound;
using var image = new Image();
Error error = image.Load(ImagePath);
if (error != Error.Ok)
return error;
if (WithMipmaps)
{
error = image.GenerateMipmaps();
if (error != Error.Ok)
return error;
}
ImageTexture texture = ImageTexture.CreateFromImage(image);
Target.Texture = texture;
return Error.Ok;
}
}Изображение из самого проекта загружайте через ResourceLoader.Load<Texture2D>(): экспорт может содержать импортированный ресурс вместо исходного PNG.
Другие внешние форматы
| Данные | API и следующий шаг |
|---|---|
| WAV, Ogg Vorbis, MP3 | AudioStreamWav.LoadFromFile(), AudioStreamOggVorbis.LoadFromFile(), AudioStreamMP3.LoadFromFile(); проверьте результат, затем назначьте AudioStreamPlayer.Stream и вызовите Play() |
| TTF / OTF | Создайте новый FontFile, загрузите через LoadDynamicFont(path) и проверьте файл и данные; одного Error.Ok недостаточно. Назначьте через тему или AddThemeFontOverride() после проверки |
| glTF / GLB | GltfDocument.AppendFromFile(path, state) → проверка Error.Ok → GenerateScene(state) → AddChild() в главном потоке |
| ZIP | ZipReader.Open() → GetFiles() / FileExists() → ReadFile() → Close(); чтение записи не извлекает её на диск |
| Запись ZIP | ZipPacker.Open() → StartFile() → WriteFile() → CloseFile() → Close(); проверяйте каждый Error |
| Свой бинарный формат в C# | byte[], System.Buffers.Binary.BinaryPrimitives и явное соглашение о порядке байтов; у byte[] нет методов Variant-массива decode_* |
Для шрифта сначала проверьте существование файла, непустой размер и допустимый предел чтения. В текущей реализации LoadDynamicFont() сбрасывает состояние, читает байты, назначает их и возвращает Ok без проверки результата декодирования. Непустые FontFile.Data подтверждают только наличие данных: дополнительно проверьте нужные символы и отображение. Для повторной загрузки используйте новый ресурс, чтобы не сбросить уже используемый шрифт до проверки.
Внешняя модель не проходит весь редакторский конвейер импорта: код должен сам настроить поведение, столкновения и допустимые зависимости. Для внешних файлов задайте предел размера и допустимые форматы в правилах своего приложения. Чтение ZIP в память само по себе не ограничивает распакованный размер.
Диагностика
| Сбой | Проверка |
|---|---|
FileNotFound | Фактический путь, регистр и существование файла |
CantCreate, FileNoPermission | Родительская папка, права, место и доступ к файлу |
FileCorrupt | Количество прочитанных байтов не совпало с длиной |
InvalidData в текстовом примере | Файл не является корректным UTF-8 |
Шрифт вернул Ok, но текста нет | Файл, лимит и данные FontFile.Data, пригодность формата и нужных символов; Ok сам не подтверждает декодирование |
| Ресурс работает в редакторе, но пропал в экспорте | Используйте загрузчик импортированных ресурсов либо включите исходный внешний файл в поставку явно |
| Текстура загружена, но не видна | Target, размер и видимость TextureRect; для 3D — материал и поверхность |