M7.4a: PlayScreen polish — facing tick, road overlap, WASD pan

Facing tick stuck at the initial angle. PlayerMarker._Draw was
computing the tick direction from a FacingAngleRad auto-property,
but Godot caches CanvasItem draw commands and only re-runs _Draw on
QueueRedraw. Setter never called QueueRedraw → tick never rotated.
Fixed by leaning on the Node2D transform instead: tick is drawn
along the local +X axis, PlayScreen sets marker.Rotation = facing
each frame. The transform rotation applies to the cached commands
without re-invoking _Draw — efficient and correct. FacingAngleRad
property removed; ShowFacingTick became a property with QueueRedraw
on change (visibility toggle still needs to invalidate the cache).

Tactical view double-drew roads. TacticalChunkGen.Pass2_Polylines
already bakes roads + rivers + bridges into the surface tiles of
each chunk. WorldRenderNode's Line2D overlay was still visible at
tactical zoom, stroking the same path on top of the rasterised
version — showed as a brown line over every road. Ported the
MonoGame "suppress polyline overlay in tactical" rule into
UpdateLayerVisibility: _polylineLayer and _bridgeLayer hide when
zoom >= TacticalRenderZoomMin.

WASD now pans the world map. Previously WASD did nothing in
world-map mode — only right-drag / middle-drag / mouse-wheel worked.
WASD is now context-sensitive: tactical mode steps the player
(unchanged), world-map mode pans the camera at 400 screen px/sec
(world-pixel speed scales as 1/zoom so the perceived rate stays
constant). Diagonal motion is √2-normalised to match tactical step.
Suppressed during click-to-travel since the camera-follow would
clobber any pan input anyway. HUD hint updated.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Christopher Wiebe
2026-05-10 19:51:44 -07:00
parent 116193c1e3
commit 6f47700820
3 changed files with 69 additions and 31 deletions
+30 -14
View File
@@ -152,7 +152,7 @@ public partial class PlayScreen : Control
_playerMarker = new PlayerMarker
{
Position = new Vector2(_actors.Player!.Position.X, _actors.Player.Position.Y),
FacingAngleRad = _actors.Player.FacingAngleRad,
Rotation = _actors.Player.FacingAngleRad,
};
AddChild(_playerMarker);
@@ -182,23 +182,39 @@ public partial class PlayScreen : Control
bool tactical = _render.Camera.Zoom.X >= WorldRenderNode.TacticalRenderZoomMin;
// Tactical WASD direction (world-map mode ignores keys — middle-drag
// pans, click-to-travel sets the destination).
float dx = 0f, dy = 0f;
if (tactical)
// WASD is context-sensitive: tactical mode steps the player,
// world-map mode pans the camera. Same keys, intent depends on zoom.
float wasdX = 0f, wasdY = 0f;
if (Godot.Input.IsKeyPressed(Key.W) || Godot.Input.IsKeyPressed(Key.Up)) wasdY -= 1f;
if (Godot.Input.IsKeyPressed(Key.S) || Godot.Input.IsKeyPressed(Key.Down)) wasdY += 1f;
if (Godot.Input.IsKeyPressed(Key.A) || Godot.Input.IsKeyPressed(Key.Left)) wasdX -= 1f;
if (Godot.Input.IsKeyPressed(Key.D) || Godot.Input.IsKeyPressed(Key.Right)) wasdX += 1f;
// Controller always ticks (path-follow runs even when WASD is idle).
// Pass step input only in tactical mode.
float stepX = tactical ? wasdX : 0f;
float stepY = tactical ? wasdY : 0f;
_controller.Update(dt, stepX, stepY, tactical, isFocused: true);
// World-map WASD pan. Skip while traveling — the follow logic below
// re-centres the camera on the player and would clobber the pan.
// Speed scales inversely with zoom so the on-screen pan rate feels
// consistent at any zoom level (matches MonoGame's 400 px/sec).
if (!tactical && !_controller.IsTraveling && (wasdX != 0f || wasdY != 0f))
{
if (Godot.Input.IsKeyPressed(Key.W) || Godot.Input.IsKeyPressed(Key.Up)) dy -= 1f;
if (Godot.Input.IsKeyPressed(Key.S) || Godot.Input.IsKeyPressed(Key.Down)) dy += 1f;
if (Godot.Input.IsKeyPressed(Key.A) || Godot.Input.IsKeyPressed(Key.Left)) dx -= 1f;
if (Godot.Input.IsKeyPressed(Key.D) || Godot.Input.IsKeyPressed(Key.Right)) dx += 1f;
const float PanScreenPxPerSec = 400f;
float invLen = (wasdX != 0f && wasdY != 0f) ? 0.70710678f : 1f;
float panSpeed = PanScreenPxPerSec / Mathf.Max(_render.Camera.Zoom.X, 0.01f);
_render.Camera.Position += new Vector2(wasdX * invLen, wasdY * invLen) * panSpeed * dt;
}
_controller.Update(dt, dx, dy, tactical, isFocused: true);
// Sync the player marker from Core state.
// Sync the player marker from Core state. Rotation drives the
// facing tick via the transform — auto-property setters on a
// PlayerMarker field would skip QueueRedraw and the cached
// _Draw commands would stay stuck at the initial angle.
var p = _actors.Player;
_playerMarker.Position = new Vector2(p.Position.X, p.Position.Y);
_playerMarker.FacingAngleRad = p.FacingAngleRad;
_playerMarker.Rotation = p.FacingAngleRad;
// Camera follow when traveling or in tactical (matches MonoGame).
if (_controller.IsTraveling || tactical)
@@ -591,7 +607,7 @@ public partial class PlayScreen : Control
string viewBlock = tactical
? "View: Tactical (WASD to step)"
: "View: World Map (click a tile to travel)";
: "View: World Map (WASD to pan · click a tile to travel)";
string status = _controller.IsTraveling
? "Traveling…"