2D parallax: camera, layers and background repetition
Two complete depth layers with a correct repeat period and coverage that adapts to window size.
Result
Two repeating background layers move at different speeds relative to Camera2D. White dots belong to the distant layer and blue rectangles to the nearer one. Arrow keys move the camera. Code draws the pattern; no external textures are needed.
This is a visual background. Repetition does not create entity copies, physics bodies or additional levels. Parallax2D organizes decorative layers; it does not stream a gameplay world.
Run the complete example
- Create a C# project with Reckfell.NET.Sdk 1.0.0 and net10.0.
- Save the two files below and run dotnet build.
- Create a level with an Entity2D root, attach ParallaxExample.cs and save parallax.rscene.
- Run that level, focus the game window and hold the right arrow. Blue rectangles move left faster than white dots.
The sample uses the standard ui_left/ui_right/ui_up/ui_down actions. Existing project remappings apply. It creates its Camera2D and both layers; do not add a second camera/background setup.
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();
}
}Choose layer speed
With the default FollowViewport=true, IgnoreCameraScroll=false, unit scale and Zoom=(1,1), moving the camera right by 40 units produces these pattern displacements on screen:
| ScrollScale.X | Pattern movement left | Use |
|---|---|---|
| 0 | 0 | A distant background fixed relative to the camera |
| 0.25 | 10 | The sample distant layer |
| 0.75 | 30 | The sample nearer layer |
| 1 | 40 | Ordinary world speed |
X and Y are independent. A smaller value usually suggests more distance but does not change actual geometry or perspective. Repetition can wrap the layer coordinate by a period while preserving the visible pattern. Compare phase and rendered output rather than raw Position alone.
Repeat without gaps
RepeatSize is the pattern period in local units, not screen dimensions. Our drawing occupies (0,0)—(128,96), so RepeatSize=(128,96). Zero disables repetition on that axis. Use (128,0) for horizontal repetition only.
RepeatTimes sets the budget of rendered repeats. It does not resize the artwork or create child entities. FitRepeats updates that budget for the visible Viewport area and the positive magnitude of Camera.Zoom. Zooming out exposes more world, requiring more repeats. This is a conservative calculation for an unrotated camera, unit layer scale and this pattern period; it does not promise minimal rendering cost.
When substituting a texture, place its origin at (0,0). A Sprite2D covering one period usually needs Centered disabled. Atlas regions, sprite offsets and child drawing scale must agree with the period. RepeatSize neither clips artwork to a rectangle nor makes mismatched edges seamless.
Doubling artwork scale without changing the period overlaps neighboring repeats. An oversized period leaves gaps. Provide vertical coverage separately for decorations that do not repeat vertically: (128,0) does not automatically fill the screen above and below.
Add motion without moving the camera
Set Far.Autoscroll=(60,0), for example. The engine advances ScrollOffset at 60 local units per second. This is additional pattern motion, not camera velocity. Offset wraps by RepeatSize on a repeated axis.
Built-in Autoscroll requires at least one nonzero RepeatSize component. RepeatSize=(0,0) disables its internal processing. ProcessMode=Disabled also stops it. This internal autoscroll does not run as gameplay processing in the editor.
Use ScrollOffset to move the pattern phase manually. Do not assign Position every frame: Parallax2D calculates it. The camera normally updates ScreenOffset; manual control requires deliberate IgnoreCameraScroll and FollowViewport settings. Keep the sample defaults for ordinary backgrounds and use CanvasLayer for HUD elements.
Diagnose defects
| Symptom | Cause and action |
|---|---|
| No visible depth difference | Check distinct ScrollScale values and an active Camera2D in the same Viewport |
| Gaps after widening the window or zooming out | Increase RepeatTimes; do not alter the period only to cover the screen |
| Seams or overlaps | Match RepeatSize to actual dimensions, offsets and artwork edges |
| Autoscroll does nothing | Check nonzero RepeatSize, ProcessMode and that the game is running rather than expecting editor motion |
| Position changes after manual assignment | Use ScrollOffset; the parallax implementation controls Position |
| Repeated decorations do not collide with the player | These repeats are visual only; create gameplay geometry separately |
Verified in Compatibility/OpenGL: drawing repeats on both axes, 40-unit and 512-unit camera motion, ScrollOffset periodicity, autoscroll and its stop conditions, Viewport resize and Zoom changes. Camera rotation, nonuniform scale, LimitBegin/LimitEnd, other renderers and exports were not exercised by this example.