reckfellENGINE

TileMapLayer: уровень, координаты и правки клеток

Запустить карту с персонажем, менять стены и понимать границы бинарного сохранения.

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

Запустить уровень

Нужен C#-проект Reckfell.NET.Sdk 1.0.0, net10.0 с рендерером Compatibility. Сохраните три файла ниже, выполните dotnet build, создайте уровень с корнем Entity2D и прикрепите TileLevel.cs. Сохраните tiles.rscene и запустите этот уровень.

Стрелки перемещают белого персонажа. Левая кнопка мыши переключает внутреннюю клетку между полом и стеной; граница уровня защищена от этого переключения. В стандартных назначениях используются ui_left/ui_right/ui_up/ui_down. Дайте окну игры фокус.

Скрипт создаёт карту 20×15, TileSet и персонажа. Каждая клетка занимает 16×16 единиц; логический размер окна во время запуска — 320×240. Не добавляйте второй комплект карты и тел вручную. Это демонстрация правок карты, а не готовый игровой редактор: не вставляйте коллизию внутрь персонажа без отдельного разрешения пересечения.

ExampleTileSet.cs
C#
using Reckfell;

public static class ExampleTileSet
{
    public const int SourceId = 0;
    public static readonly Vector2I Floor = new(0, 0);
    public static readonly Vector2I Wall = new(1, 0);

    public static TileSet Create()
    {
        using var image = Image.CreateEmpty(32, 16, false, Image.Format.Rgba8);
        image.Fill(new Color(0.12f, 0.14f, 0.18f));
        image.FillRect(new Rect2I(1, 1, 14, 14), new Color(0.17f, 0.19f, 0.23f));
        image.FillRect(new Rect2I(16, 0, 16, 16), new Color(0.2f, 0.38f, 0.9f));
        image.FillRect(new Rect2I(17, 1, 14, 3), new Color(0.4f, 0.57f, 1));
        var atlas = new TileSetAtlasSource
        {
            Texture = ImageTexture.CreateFromImage(image),
            TextureRegionSize = new Vector2I(16, 16)
        };
        var tiles = new TileSet { TileSize = new Vector2I(16, 16) };
        tiles.AddPhysicsLayer();
        tiles.SetPhysicsLayerCollisionLayer(0, 1);
        tiles.SetPhysicsLayerCollisionMask(0, 1);
        tiles.AddCustomDataLayer();
        tiles.SetCustomDataLayerName(0, "walkable");
        tiles.SetCustomDataLayerType(0, Variant.Type.Bool);
        tiles.AddSource(atlas, SourceId);
        atlas.CreateTile(Floor);
        atlas.CreateTile(Wall);
        atlas.GetTileData(Floor, 0).SetCustomData("walkable", true);
        var wall = atlas.GetTileData(Wall, 0);
        wall.SetCustomData("walkable", false);
        wall.SetCollisionPolygonsCount(0, 1);
        wall.SetCollisionPolygonPoints(0, 0, new[]
        {
            new Vector2(-8, -8), new Vector2(8, -8),
            new Vector2(8, 8), new Vector2(-8, 8)
        });
        return tiles;
    }
}
TileWalker.cs
C#
using Reckfell;

public partial class TileWalker : CharacterBody2D
{
    public override void _Ready()
    {
        MotionMode = MotionModeEnum.Floating;
        AddChild(new CollisionShape2D { Shape = new CircleShape2D { Radius = 6 } });
    }

    public override void _PhysicsProcess(double delta)
    {
        Velocity = Input.GetVector("ui_left", "ui_right", "ui_up", "ui_down") * 120;
        MoveAndSlide();
    }

    public override void _Draw() => DrawCircle(Vector2.Zero, 6, Colors.White);
}
TileLevel.cs
C#
using Reckfell;

public partial class TileLevel : Entity2D
{
    public TileMapLayer Map { get; private set; } = null!;
    public TileWalker Player { get; private set; } = null!;

    public override void _Ready()
    {
        if (GetViewport() is Window window)
        {
            window.ContentScaleSize = new Vector2I(320, 240);
            window.ContentScaleMode = Window.ContentScaleModeEnum.Viewport;
        }
        Map = new TileMapLayer { TileSet = ExampleTileSet.Create(), TextureFilter = TextureFilterEnum.Nearest };
        AddChild(Map);
        for (int y = 0; y < 15; y++)
        for (int x = 0; x < 20; x++)
        {
            bool wall = x == 0 || x == 19 || y == 0 || y == 14 || (x == 12 && y >= 3 && y <= 11);
            Map.SetCell(new Vector2I(x, y), ExampleTileSet.SourceId, wall ? ExampleTileSet.Wall : ExampleTileSet.Floor);
        }
        Map.UpdateInternals();
        Player = new TileWalker { Position = Map.MapToLocal(new Vector2I(5, 7)) };
        AddChild(Player);
    }

    public bool ToggleCell(Vector2I cell)
    {
        if (cell.X <= 0 || cell.X >= 19 || cell.Y <= 0 || cell.Y >= 14) return false;
        Vector2I next = Map.GetCellAtlasCoords(cell) == ExampleTileSet.Wall ? ExampleTileSet.Floor : ExampleTileSet.Wall;
        Map.SetCell(cell, ExampleTileSet.SourceId, next);
        return true;
    }

    public override void _UnhandledInput(InputEvent input)
    {
        if (input is InputEventMouseButton { Pressed: true, ButtonIndex: MouseButton.Left })
            ToggleCell(Map.LocalToMap(Map.GetLocalMousePosition()));
    }
}

Выбрать правильные координаты

ЗначениеПримерСмысл
Координата клетки(2,3)Целочисленная позиция в сетке
MapToLocal((2,3))(40,56)Центр этой клетки в локальных координатах слоя
ToGlobal(MapToLocal(cell))Зависит от Transform слояПозиция центра в canvas
LocalToMap(localPoint)Например (−1,−1) для точки (−1,−1)Преобразование локальной точки в клетку

Для canvas-позиции используйте LocalToMap(ToLocal(position)). Не передавайте InputEvent.Position напрямую: это координаты Viewport, на которые влияют камера и растяжение. В примере GetLocalMousePosition сразу получает подходящую локальную позицию карты.

Не заменяйте LocalToMap делением с приведением к int. Для отрицательной координаты такое приведение округляет не туда; изометрическая или шестигранная раскладка также требует правил TileSet. MapToLocal возвращает центр клетки, а не обязательно центр визуального арта с TextureOrigin.

Поставить, заменить или удалить тайл

SetCell получает координату клетки, sourceId, atlasCoords и alternativeTile. Для этого атласа передавайте sourceId=0, Floor=(0,0) или Wall=(1,0), alternativeTile=0. Координата карты не является координатой атласа.

Важный случай: SetCell(cell, 0) не означает «выбрать первый тайл». Неуказанный atlasCoords соответствует недопустимым (−1,−1), поэтому клетка очищается. Для удаления можно явно использовать EraseCell. Идентификаторы должны соответствовать назначенному TileSet.

Изменение логических данных видно сразу через GetCellSourceId/GetCellAtlasCoords. Рендеринг, физика и прочие внутренние пакеты обновляются отложенно. При необходимости вызовите UpdateInternals один раз после группы изменений; не вызывайте его внутри цикла для каждой клетки. Он не выполняет дополнительный шаг физической симуляции.

На настоящих кадрах ниже персонаж сначала остановлен синей стеной, затем проходит через клетку, заменённую полом. Клетки и их коллизии не становятся отдельными Entity в дереве.

Стена закрыта

Персонаж остановлен коллизией тайловой стены

Стена заменена полом

Персонаж проходит через изменённую клетку

Читать данные карты

GetUsedCells возвращает занятые клетки. GetUsedRect охватывает их в координатах сетки: у нашего уровня позиция (0,0), размер (20,15), а не (320,240). Пустые клетки не входят в этот прямоугольник.

GetCellTileData работает с atlas-тайлами и может вернуть null для пустой клетки или другого типа источника. Данные принадлежат тайлу TileSet, а не уникальному экземпляру клетки. Для дверей, разрушений и другого состояния уровня используйте собственную структуру по координатам либо осознанно меняйте ссылку клетки на другой тайл.

Сохранение и большие координаты

TileMapData содержит бинарные ссылки клеток, но не сам TileSet и не текстуры. При восстановлении требуется тот же согласованный набор идентификаторов ресурса.

В проверенном формате координаты записываются как знаковые 16-битные числа: допустимый диапазон сохранения по каждой оси — от −32768 до 32767. Клетка (40000,0) после сохранения и загрузки стала (−25536,0). Vector2I в API не означает, что бинарный формат сохраняет весь его диапазон.

Не используйте этот массив как бесконтрольный межверсионный формат сейва. Для длительного хранения задайте собственную версию схемы и стабильные игровые идентификаторы. Большие миры требуют проектирования регионов с небольшими локальными координатами; автоматической подгрузки регионов этот пример не реализует.

Если карта ведёт себя неверно

  • Картинка есть, столкновения нет: проверьте physics layer TileSet, полигон тайла и маски тела.
  • Вместо тайла клетка исчезла: передайте atlasCoords явно и проверьте существование source/alternative.
  • Щелчок меняет соседнюю клетку: проверьте пространство координат и трансформацию слоя.
  • Сохранённая клетка сместилась далеко: проверьте диапазон координат бинарного формата.
  • При правке кадр тормозит: сгруппируйте SetCell; не перестраивайте всё после каждой клетки.

Устройство TileSet · Координаты 2D

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

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

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

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