Skill v1.0.0
currentLLM-judged scan95/100version: "1.0.0" name: grid-movement description: "Grid Movement in FlatRedBall2. Use when implementing tile-by-tile (grid-snapped) movement where one key press = one tile step, input is locked during the movement animation, and the player cannot move into blocked tiles. Trigger on any dungeon-crawler, Pokémon-style, roguelike, or puzzle game with discrete grid movement. Do NOT trigger for analog/free top-down movement — use the top-down-movement skill for that."
Grid Movement in FlatRedBall2
Not `TopDownBehavior` —TopDownBehaviorproduces continuous analog velocity-based movement. For tile-by-tile grid movement, implement the pattern below directly. Do not load thetop-down-movementskill for this use case.
Grid movement has three invariants:
- The entity always sits on a tile boundary (position snapped to multiples of
TileSize). - Input is locked while a move is in progress.
- A pre-move collision check prevents entering blocked tiles.
Minimal Implementation
private const float TileSize = 16f;private bool _moving = false;public override void CustomInitialize(){X = MathF.Round(X / TileSize) * TileSize; // snap to grid on spawnY = MathF.Round(Y / TileSize) * TileSize;}public override async void CustomActivity(FrameTime time){if (_moving) return; // gate: one move at a timevar (dx, dy) = ReadDirectionalInput();if (dx == 0 && dy == 0) return;float targetX = X + dx * TileSize;float targetY = Y + dy * TileSize;if (IsBlocked(targetX, targetY)) return; // pre-move collision check_moving = true;await TweenTo(targetX, targetY, 0.12f); // animate the stepX = targetX; Y = targetY; // snap to exact grid position_moving = false;}
Pre-Move Collision Check
Use TileShapes.GetTileAtWorld(x, y) to test the target tile before committing:
private bool IsBlocked(float x, float y)=> _solidTiles.GetTileAtWorld(x, y) != null;
TileShapes is injected from the screen after spawning via a method on the entity. See the levels skill for how to generate it from a TMX layer.
Namespace gotcha —TileShapeslives inFlatRedBall2.Collision, notFlatRedBall2.Tiled. Addusing FlatRedBall2.Collision;to any file that declares aTileShapesfield.
Tween Helper
A lerp tween using Engine.Time.CurrentScreenTime (accessible from any entity via Engine):
private async Task TweenTo(float tx, float ty, TimeSpan duration){float startX = X, startY = Y;TimeSpan startTime = Engine.Time.CurrentScreenTime;await Engine.Time.DelayUntil(() =>{TimeSpan elapsed = Engine.Time.CurrentScreenTime - startTime;float t = (float)Math.Clamp(elapsed.TotalSeconds / duration.TotalSeconds, 0.0, 1.0);X = startX + (tx - startX) * t;Y = startY + (ty - startY) * t;return t >= 1f;}, CancellationToken.None);}
`CancellationToken` scope —Tokenis a property ofScreen, notEntity. PassCancellationToken.Nonefrom an entity. For tweens this short (<200 ms) that is safe; if you need cancellation on screen exit, thread a token in via a constructor parameter.
Gotchas
- Always snap on spawn — entities loaded from a TMX object layer may have sub-pixel offsets. Round
XandYto the nearest tile inCustomInitialize. - `_moving` guard is required — without it, holding a key queues multiple moves before the first frame completes.
- Diagonal movement: If 8-way is needed, check that both axis tiles are clear before committing. Diagonal into a corner with one axis blocked should push to the open axis or reject.
- Entity origin and draw order: if entities need screen-depth sorting (walking behind/in front of scenery),
(X,Y)should be the ground-contact point andZ = -Yeach frame — seetop-down-movement's Entity Origin and Draw Order section.