Direct Parallax Layer
Hs2dDirectParallaxLayer is a surface-less parallax strip. It computes a camera-relative offset in update and composes the bitmap directly to the screen with one clipped blit, or two blits when scrollSlack enables wrapping.
Use it for final background strips that do not need compositor effects or cached-surface redraw logic. Gameplay should still use the layer instead of a hand-written draw loop.
Direct parallax also supports a world-space bottom anchor for bands such as water. The surface-parallax path does not support that anchor.
When To Use
Use direct parallax for:
| Good fit | Avoid when |
|---|---|
| Long horizontal sky, hills, clouds, stars, or side strips. | The layer needs a compositor surface, zoom constraints, repeat on Y, or custom painting. |
| Art already authored as final bitmap strips. | The strip must be assembled from many sprites. |
| Background motion with minimal per-frame work. | The image exceeds device texture limits and needs chunking or a different asset layout. |
scrollSlack treats the bitmap as one seamless period. The layer draws the source twice, offset by that period, so wrapped positions cover the viewport without building a doubled bitmap.
Construction Pattern
Manual construction:
world.addDirectParallaxLayer({
id: 'clouds',
source: cloudsBitmap,
parallaxX: 0.2,
parallaxY: 0,
cameraOriginX: startCameraX,
cameraOriginY: startCameraY,
drawX: 0,
drawY: 64,
scrollSlack: 960,
driftXPxPerSec: -18,
});
Tiled construction:
Image layer: clouds
hs2d:asset = clouds
hs2d:compose = direct
hs2d:scrollSlack = 960
hs2d:driftX = -18
The builder also honors native Tiled parallax and offset fields. hs2d:designHeight can scale the bitmap and screen-space pixel properties once at build time for different viewport heights. The two anchor properties are world coordinates and are deliberately not design-height scaled.
Bottom-Anchored Bands
Set anchorBottomWorldY to pin the strip's bottom edge to the screen bottom while the camera's visible bottom is at or below that world-space line. When the camera rises above the line, the band recedes below the screen at parallaxY times the world's projected rate.
Set anchorWorldHeight as well when the band represents a fixed world-space depth. The layer scales the source vertically to anchorWorldHeight * camera.scale, keeping the band's top edge world-aligned across zoom levels. anchorWorldHeight requires anchorBottomWorldY.
Hosanario's water layer is the reference: it uses a bottom anchor at world Y 2176 with a world height of 120.
Image layer: water
hs2d:compose = direct
hs2d:anchorBottomWorldY = 2176
hs2d:anchorWorldHeight = 120
Update, Render, And Compose Behavior
| Phase | Behavior |
|---|---|
| Drift | advanceDrift(deltaMs) accumulates horizontal motion and wraps it by scrollSlack when present. |
| Update | Computes offsetX from camera delta, parallax, drift, and optional wrapping. Computes ordinary vertical parallax or the optional bottom-anchor projection. |
| Render | No compositor surface render. |
| Compose | Draws the source at drawX + offsetX, drawY + offsetY; draws a second copy when wrapping is enabled. |
If the camera is still and drift is nonzero, call world.advanceParallaxDrift(deltaMs) or otherwise advance the layer each frame.
Performance Notes
- This is the cheapest parallax path for finished strips.
- It avoids sprite move, surface redraw, and surface compose work.
- Keep source width and
scrollSlackwithin the target platform's bitmap limits. - Use the bottom-anchor options only with direct parallax; they are ignored by the surface path because the builder does not pass them there.
- Do not use it for gameplay sprites, particles, or tilemaps.
- Use surface parallax when you need zoom behavior or repeat-height handling.
Code Sample
protected override onUpdate(deltaMs: number, inputs: GameInput[]): void {
this.updateRules(deltaMs, inputs);
this.tickSceneCamera(deltaMs);
this.world?.advanceParallaxDrift(deltaMs);
}
protected override buildWorld(): Hs2dWorld {
const world = Hs2dWorld.create({
x: startCameraX,
y: startCameraY,
viewportWidth: screenWidth,
viewportHeight: screenHeight,
worldWidth,
worldHeight,
});
world.addDirectParallaxLayer({
id: 'far-stars',
source: starsBitmap,
parallaxX: 0.08,
parallaxY: 0.02,
cameraOriginX: startCameraX,
cameraOriginY: startCameraY,
scrollSlack: starsBitmap.GetWidth(),
driftXPxPerSec: -6,
});
return world;
}
Source References
| Source | Why it matters |
|---|---|
../games/hosanna-ui/src/hosanna-game/hosanna2d/Hs2dDirectParallaxLayer.ts | Direct parallax options, drift, update offset math, wrapping, and direct compose behavior. |
../games/hosanna-ui/src/hosanna-game/hosanna2d/Hs2dWorld.ts | addDirectParallaxLayer and advanceParallaxDrift. |
../games/hosanna-ui/src/hosanna-game/hosanna2d/Hs2dWorldBuilder.ts | Tiled hs2d:compose=direct, parallax origin, design-height scaling, scroll slack, and drift mapping. |
../games/hosanna-ui/src/hosanna-game/scene/GameSceneRenderer.ts | Renderer layer lifecycle and screen clear/compose order. |