Параллакс 2D: камера, слои и повтор фона
Два готовых слоя с разной глубиной, корректным периодом и запасом повторов при изменении окна.
Что получится
Два слоя повторяющегося фона движутся с разной скоростью относительно Camera2D. Белые точки находятся на дальнем слое, синие прямоугольники — на ближнем. Стрелки перемещают камеру. Рисунок создаётся кодом, поэтому внешние текстуры не нужны.
Это визуальный фон: повторение не создаёт копии Entity, физические тела или дополнительные уровни. Parallax2D подходит для слоёв декорации, а не для потоковой загрузки игрового мира.
Запустить полный пример
- Создайте C#-проект с Reckfell.NET.Sdk 1.0.0 и net10.0.
- Сохраните два файла ниже и выполните dotnet build.
- Создайте уровень с корнем Entity2D, прикрепите ParallaxExample.cs и сохраните parallax.rscene.
- Запустите этот уровень, дайте окну игры фокус и удерживайте стрелку вправо: узор синих прямоугольников уезжает влево быстрее белых точек.
Используются стандартные действия ui_left/ui_right/ui_up/ui_down. Если проект переназначил их, используются ваши назначения. Пример сам создаёт Camera2D и два слоя; не добавляйте второй комплект камеры или фона.
ParallaxPattern.cs
using Reckfell;
public partial class ParallaxPattern : Entity2D
{
public bool Distant { get; set; }
public override void _Draw()
{
if (Distant)
{
DrawRect(new Rect2(0, 0, 128, 96), new Color(0.1f, 0.11f, 0.13f));
DrawCircle(new Vector2(32, 24), 6, Colors.White);
DrawCircle(new Vector2(96, 56), 3, Colors.White);
}
else
{
DrawRect(new Rect2(88, 64, 20, 32), new Color(0.25f, 0.45f, 1));
}
}
}ParallaxExample.cs
using Reckfell;
public partial class ParallaxExample : Entity2D
{
public Parallax2D Far { get; private set; } = null!;
public Parallax2D Near { get; private set; } = null!;
public Camera2D Camera { get; private set; } = null!;
private Vector2 _visibleSize;
public override void _Ready()
{
Camera = new Camera2D { Position = GetViewportRect().Size * 0.5f };
AddChild(Camera);
Camera.MakeCurrent();
Far = MakeLayer(0.25f, true, -20);
Near = MakeLayer(0.75f, false, -10);
FitRepeats();
}
private Parallax2D MakeLayer(float speed, bool distant, int order)
{
var layer = new Parallax2D
{
ScrollScale = new Vector2(speed, speed),
RepeatSize = new Vector2(128, 96),
ZIndex = order
};
layer.AddChild(new ParallaxPattern { Distant = distant });
AddChild(layer);
return layer;
}
public void FitRepeats()
{
Vector2 zoom = Camera.Zoom.Abs();
if (zoom.X <= 0 || zoom.Y <= 0 || !zoom.IsFinite()) return;
Vector2 visible = GetViewportRect().Size / zoom;
if (visible == _visibleSize) return;
_visibleSize = visible;
int repeats = Mathf.CeilToInt(Mathf.Max(visible.X / 128, visible.Y / 96)) * 2 + 2;
Far.RepeatTimes = repeats;
Near.RepeatTimes = repeats;
}
public override void _Process(double delta)
{
Camera.Position += Input.GetVector("ui_left", "ui_right", "ui_up", "ui_down") * 200 * (float)delta;
FitRepeats();
}
}Выбрать скорость слоя
При стандартных FollowViewport=true, IgnoreCameraScroll=false, масштабе 1 и Zoom=(1,1) сдвиг камеры вправо на 40 единиц даёт такие смещения узора на экране:
| ScrollScale.X | Сдвиг узора влево | Применение |
|---|---|---|
| 0 | 0 | Неподвижный относительно камеры дальний фон |
| 0,25 | 10 | Дальний слой примера |
| 0,75 | 30 | Ближний слой примера |
| 1 | 40 | Скорость обычного мира |
X и Y задаются отдельно. Меньшее значение обычно воспринимается как большая удалённость, но не меняет реальную геометрию или перспективу. При повторении координата слоя может скачком обернуться на период: одинаковый узор при этом сохраняется. Сравнивайте фазу и картинку, а не только сырое Position.
Настроить повтор без дыр
RepeatSize — период узора в локальных единицах, а не размер экрана. Наш рисунок занимает область (0,0)—(128,96), поэтому RepeatSize=(128,96). Ноль отключает повторение по соответствующей оси. Если нужны только горизонтальные повторы, задайте (128,0).
RepeatTimes определяет запас отрисовываемых повторов. Он не увеличивает размер рисунка и не создаёт дочерние Entity. FitRepeats пересчитывает запас под видимую область Viewport и положительную величину Camera.Zoom. При уменьшении Zoom видно больше мира, поэтому повторов требуется больше. Это простой консервативный расчёт для не повёрнутой камеры, масштаба слоёв 1 и данного периода; он не обещает минимальную стоимость отрисовки.
Если подставляете текстуру, поместите её начало в (0,0). Для Sprite2D с картинкой размером в один период обычно отключают Centered. Регионы атласа, смещение спрайта и масштаб дочернего рисунка должны согласовываться с периодом. Сам RepeatSize не обрезает изображение до прямоугольника и не делает несовпадающие края бесшовными.
Масштабирование картинки вдвое без изменения периода приводит к наложению соседних повторов. Слишком большой период оставляет промежуток. Для вертикально неповторяемой декорации отдельно обеспечьте покрытие по высоте: (128,0) не заполняет экран вверх и вниз автоматически.
Добавить движение без камеры
Задайте, например, Far.Autoscroll=(60,0): движок изменяет ScrollOffset со скоростью 60 локальных единиц в секунду. Это добавочное движение узора, а не скорость самой камеры. На повторяемой оси смещение оборачивается по RepeatSize.
Для встроенного Autoscroll хотя бы одна компонента RepeatSize должна быть ненулевой. При RepeatSize=(0,0) внутренняя обработка автопрокрутки выключается. ProcessMode=Disabled также останавливает её. В редакторе эта внутренняя автопрокрутка не выполняется как игровой процесс.
Меняйте ScrollOffset для ручного смещения фазы. Не записывайте Position каждый кадр: Parallax2D рассчитывает его сам. ScreenOffset по умолчанию обновляет камера; ручное управление им требует осознанной настройки IgnoreCameraScroll и FollowViewport. Для обычного фона оставьте оба параметра как в примере; для HUD используйте CanvasLayer.
Найти причину дефекта
| Симптом | Причина и действие |
|---|---|
| Не видно разницы глубины | Проверьте разные ScrollScale и активную Camera2D в том же Viewport |
| На широком окне или при zoom out появились пустоты | Увеличьте RepeatTimes; не меняйте период только ради покрытия экрана |
| Видны швы или наложения | Согласуйте RepeatSize с фактическими размерами, смещением и краями рисунка |
| Autoscroll ничего не делает | Проверьте ненулевой RepeatSize, ProcessMode и запуск игры вместо ожидания движения в редакторе |
| Объект снова смещается после ручной установки Position | Используйте ScrollOffset; Position управляется реализацией параллакса |
| Копии декорации не сталкиваются с игроком | Повторы здесь только визуальные; игровую геометрию создавайте отдельно |
Проверено в Compatibility/OpenGL: повтор рисунка по двум осям, смещения камеры 40 и 512 единиц, период ScrollOffset, автопрокрутка и её остановка, изменение Viewport и Zoom. Поворот камеры, неравномерный масштаб, LimitBegin/LimitEnd, другие рендереры и экспорт этим примером не проверены.