global::IDrawable.DrawSprites receives a camera position named camPos. For an ordinary sprite drawn at a world position, subtract that camera position before assigning the Futile node’s x and y values.

When the object stores previous and current positions, interpolate both sides of the view before subtraction:

Vector2 worldPos = Vector2.Lerp(lastPos, pos, timeStacker);
Vector2 screenPos = worldPos - camPos;
sLeaser.sprites[0].x = screenPos.x;
sLeaser.sprites[0].y = screenPos.y;

This is an illustrative drawing fragment. It assumes lastPos and pos are world coordinates maintained by the object. It does not define how those fields should update.

What timeStacker means here

In global::RoomCamera.DrawUpdate(System.Single,System.Single), the camera begins with Vector2.Lerp(lastPos, pos, timeStacker). It then floors both components, subtracts 0.02f from each, and adds the camera offset. The resulting value is passed through each leaser update to DrawSprites as camPos.

The verified code uses timeStacker as the interpolation amount for the camera’s previous and current positions. It is not delta time. The evidence does not establish one update rate for every game state, platform, or modded loop. Use the parameter to blend matching previous and current visual state. Keep timers and simulation changes in the object’s update logic.

Interpolating the world object while subtracting a non-interpolated camera position can produce visible stepping. The reverse mismatch can do the same. The method supplies an already calculated camera position, so custom draw code usually only needs to interpolate the object side.

Pause behavior, slow motion, room boundaries, and camera transitions were not run for this article. Test those cases before treating an interpolation formula as complete for a particular object family.

Containers choose the drawing layer

A position tells Futile where to place a node. Its parent container helps determine where it appears among other nodes. global::RoomCamera.ReturnFContainer(System.String) looks up a layer name through the camera’s SpriteLayerIndex dictionary and returns the corresponding entry from SpriteLayers.

The verified camera uses layer names including Foreground, Background, HUD, and HUD2 for its own nodes. Their presence in this build does not make every one suitable for every custom drawable. A literal name also relies on the active camera supplying that layer. Test the selected container and its ordering in the game states your mod supports.

FContainer.AddChild(FNode) adds a node to the container’s child list. A typical AddToContainer implementation receives a container selected by the camera or chooses one from ReturnFContainer, then adds its leaser sprites:

FContainer target = requestedContainer ?? rCam.ReturnFContainer("Foreground");
target.AddChild(sLeaser.sprites[0]);

Moving an existing drawable through global::RoomCamera.MoveObjectToContainer calls its AddToContainer method with the requested container. Repeatedly moving an unchanged sprite every frame adds work without changing its coordinates. Move it when the mode or intended layer changes.

Drawable lifetime explains when sprites are created, placed, redrawn, and removed. Atlases and shaders covers how an FSprite resolves its visual element before it enters a camera container.

World layers and HUD layers

World sprites commonly use worldPos - camPos because they should move against the camera. A HUD node follows a different layout decision. If its coordinates are already expressed in the HUD container’s local space, subtracting camPos would incorrectly tie it to the room world.

Choose the coordinate space and container together:

  • A world marker derives its position from a room object and subtracts camPos.
  • A screen control derives its position from the interface layout used by its HUD container.
  • A camera effect may use camera helpers or other transforms defined by that effect’s renderer.

These are design categories, rather than a promise that every built-in class follows one formula. Inspect and test the object family you are extending.

Depth is an explicit transform

global::RoomCamera.ApplyDepth(UnityEngine.Vector2,System.Single) applies a depth transform around a camera-derived point offset by (700f, 533.3334f). The helper exists for effects that deliberately use that projection. Static inspection did not show that every standard FSprite should pass through it.

Start with direct subtraction from world coordinates to camera coordinates for an ordinary flat room sprite. Use ApplyDepth only when the intended effect has a defined depth value and the result has been checked across camera movement and supported screen layouts.

Sources

  • Rain World v1.11.8, Steam app 312520, build 22785462. Assembly-CSharp.dll SHA-256 B6BE1D4E18CE219D21091B51564CB6A11C1E4106B41DE903EB8E58849CB16FDB.
  • Assembly-CSharp.dll: global::RoomCamera.DrawUpdate(System.Single,System.Single) and global::RoomCamera.SpriteLeaser.Update(System.Single,global::RoomCamera,UnityEngine.Vector2).
  • Assembly-CSharp.dll: global::IDrawable.DrawSprites(global::RoomCamera.SpriteLeaser,global::RoomCamera,System.Single,UnityEngine.Vector2).
  • Assembly-CSharp.dll: global::RoomCamera.ReturnFContainer(System.String) and global::RoomCamera.ApplyDepth(UnityEngine.Vector2,System.Single).
  • Assembly-CSharp.dll: FContainer.AddChild(FNode) and FSprite..ctor(System.String,System.Boolean).
  • Runtime reference records the same installed assembly baseline and its verification limits.