59784048cd
Renders the full worldgen output as a Godot scene at visual parity with
worldgen-dump's PNG output: biome tiles, rivers/roads/rails as Line2D
polylines, settlements as filled circles. Pan + zoom via Camera2D.
WorldMapView.cs:
- Loads Content/Data via res:// walk-up, runs WorldGenerator.RunAll
- Tile palette built from BiomeDef.ParsedColor() — same source as the
PNG dump, so colours are identical
- Tiles rendered as a 256x256 Image scaled by WORLD_TILE_PIXELS to
cover world-pixel space (matches polyline coord system)
- Polyline draw order mirrors LineFeatureRenderer.cs: roads (smaller
first) -> rivers -> rail tie underlay -> rail line. Bridges as
short Line2Ds; settlements as SettlementDot (Node2D + _Draw circle)
- Line widths in world-pixel space, tuned for visibility at world-map
zoom; M4 will add zoom-aware width scaling for tactical view
- Camera fits the whole world (95% of viewport) on first frame
PanZoomCamera.cs:
- Mouse-wheel zoom centered on cursor (cursor world-point stays fixed)
- Middle/right click + drag to pan
- MinZoom/MaxZoom configurable per-instance
Main.cs:
- --world-map [seed] flag launches the view (default seed 12345)
- Arg parser now reads both GetCmdlineArgs and GetCmdlineUserArgs so
callers don't need to remember the "--" separator
- --smoke-test path and M0 hello-world fallback unchanged
Visual diff against world_seed12345.png (generated by
worldgen-dump --seed 12345) confirmed manually: same biome palette, same
rivers/roads topology, same settlement placement and tier colours.
3 rivers, 91 roads, 226 settlements (138 PoIs), 0 rails (ENABLE_RAIL=false),
0 bridges (this seed has no road/river crossings). All match the PNG.
Settlement dot sizes iterated twice from user feedback — final values
in tile units, scaled to world-pixel space, so they shrink at world-map
zoom and grow toward tactical zoom (the right "scale with the map"
behaviour).
Closes M2 of theriapolis-rpg-implementation-plan-godot-port.md.
Next: M3 (asset pipeline).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
70 lines
2.4 KiB
C#
70 lines
2.4 KiB
C#
using Godot;
|
|
|
|
namespace Theriapolis.GodotHost.Rendering;
|
|
|
|
/// <summary>
|
|
/// Camera2D with mouse-wheel zoom and middle/right-button click-drag pan.
|
|
/// Zoom is centered on the cursor so zooming feels natural. Bounds:
|
|
/// the zoom factor is clamped between MinZoom (everything fits) and
|
|
/// MaxZoom (1 tile fills the screen).
|
|
/// </summary>
|
|
public partial class PanZoomCamera : Camera2D
|
|
{
|
|
[Export] public float MinZoom { get; set; } = 0.04f;
|
|
[Export] public float MaxZoom { get; set; } = 4.0f;
|
|
[Export] public float ZoomStep { get; set; } = 1.15f;
|
|
|
|
private bool _dragging;
|
|
private Vector2 _dragStartCursor;
|
|
private Vector2 _dragStartCameraPos;
|
|
|
|
public override void _UnhandledInput(InputEvent @event)
|
|
{
|
|
switch (@event)
|
|
{
|
|
case InputEventMouseButton mb when mb.Pressed:
|
|
HandleMouseButtonPressed(mb);
|
|
break;
|
|
case InputEventMouseButton mb when !mb.Pressed:
|
|
if (mb.ButtonIndex is MouseButton.Middle or MouseButton.Right)
|
|
_dragging = false;
|
|
break;
|
|
case InputEventMouseMotion mm when _dragging:
|
|
Position = _dragStartCameraPos
|
|
+ (_dragStartCursor - mm.Position) / Zoom;
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void HandleMouseButtonPressed(InputEventMouseButton mb)
|
|
{
|
|
switch (mb.ButtonIndex)
|
|
{
|
|
case MouseButton.WheelUp:
|
|
ApplyZoom(ZoomStep, mb.Position);
|
|
break;
|
|
case MouseButton.WheelDown:
|
|
ApplyZoom(1f / ZoomStep, mb.Position);
|
|
break;
|
|
case MouseButton.Middle:
|
|
case MouseButton.Right:
|
|
_dragging = true;
|
|
_dragStartCursor = mb.Position;
|
|
_dragStartCameraPos = Position;
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void ApplyZoom(float factor, Vector2 cursorScreen)
|
|
{
|
|
float newZoom = Mathf.Clamp(Zoom.X * factor, MinZoom, MaxZoom);
|
|
if (Mathf.IsEqualApprox(newZoom, Zoom.X)) return;
|
|
|
|
// Zoom toward the cursor: keep the world point under the cursor fixed.
|
|
Vector2 worldBefore = GetCanvasTransform().AffineInverse() * cursorScreen;
|
|
Zoom = new Vector2(newZoom, newZoom);
|
|
Vector2 worldAfter = GetCanvasTransform().AffineInverse() * cursorScreen;
|
|
Position += worldBefore - worldAfter;
|
|
}
|
|
}
|