champions-of-luneraria/Assets/Luneraria/View/LuneCameraRig.cs
Martin Stickelbroeck 2b86c0d3b5 U3B/U3C Zwischenstand: HUD-Bereiche, Eingabe und Befehle
Das G3-HUD steht im Spiel; die Abnahme (U3D) folgt.

- U3B: alle Bereiche und Komponenten aus Godot übertragen (LuneHud,
  Lanes, Bossgrotte, nächste Welle, Kaufkarten, Armee, Held mit Items und
  Talenten, Angriffschampions, Team, Ereignisse, Minimap, Tooltip,
  Optionen, Partieende). Porträts aus den echten Modellen, einmal gerendert.
- Offene G3-Punkte umgesetzt: „1 WELLE“ als Band am Porträtrand,
  Schadens- und Rüstungssymbole auf den Karten, Armeewert, Heldenporträts
  auf der Minimap. Befehlsplatz für die Ultimates der geplanten Helden
  reserviert (verborgen).
- U3C: LuneGame verdrahtet Befehle (gleiche Schnittstelle wie die Bots),
  Bereit mit 1-s-Sperre nach dem Kampf, Tempo und Pause, Tasten wie in G3,
  Kamera-Ziele, Auswahl im Feld, Planungsvorschau und Aufstellungsflächen.
  Ersetzt die U2-Debug-Anzeige. Bot-Partien und Benchmark unverändert.
- Szenen- und Bibliotheksbauer idempotent: Animator-Controller und
  Post-Processing-Profil werden an Ort und Stelle aktualisiert. Die Szene
  bekommt bei jedem Bau neue Objekt-IDs, der Inhalt ist gleich.
- Prüfung (Port von verify_g3, Teile A–H): 699 Prüfungen ohne Fehler.
  EditMode 23/23, PlayMode 12/12, Fingerabdrücke der Bot-Partien
  unverändert (DCE95C96556F3A1A, 7B8E43AEFC0D60C5).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 01:45:02 +02:00

182 lines
7.4 KiB
C#

using Luneraria.Core;
using UnityEngine;
namespace Luneraria.View
{
/// <summary>
/// The MOBA camera of the reference (lune_camera.gd): WASD or arrows pan, mouse wheel zooms,
/// Q/E turn in 45 degree steps, R resets, O shows the whole map, middle mouse drags. The focus
/// follows the ground. Uses the classic input manager until the HUD port (U3) moves input over.
/// </summary>
public sealed class LuneCameraRig : MonoBehaviour
{
public LuneGround Ground;
public bool Interactive = true;
/// <summary>True while the pointer is over the HUD: no wheel zoom, no middle-mouse drag (U3).</summary>
public System.Func<bool> PointerBlocked;
/// <summary>True while a window takes the keyboard (options): no panning or turning keys.</summary>
public System.Func<bool> KeysBlocked;
/// <summary>R: the game controller's reset (own lane, Godot reset_camera); without it R resets yaw and zoom.</summary>
public System.Action ResetRequested;
public float Zoom = LuneCameraMath.DefaultZoom;
public float Yaw;
Vec2 _focus;
Vec2 _goalFocus;
float _goalZoom = LuneCameraMath.DefaultZoom;
float _goalYaw;
Vector3 _dragAnchor;
bool _dragging;
public Vec2 Focus { get { return _focus; } }
public void RotateStep(int direction)
{
_goalYaw += direction * Mathf.PI * .25f;
}
/// <summary>Yaw back to 0 and the default zoom at a point (Godot reset_view).</summary>
public void ResetView(Vec2 point, bool instant = false)
{
_goalYaw = 0f;
LookAt(point, LuneCameraMath.DefaultZoom, instant);
}
/// <summary>
/// Ground outline of the view (the four screen corners on the ground plane at the focus
/// height), in map coordinates; rays above the horizon end at 120 m.
/// </summary>
public Vec2[] Footprint(Camera camera)
{
float height = Ground != null ? Ground.StandHeight(_focus) : 0f;
var plane = new Plane(Vector3.up, new Vector3(0f, height, 0f));
var result = new Vec2[4];
Vector2[] corners = { new Vector2(0f, 1f), new Vector2(1f, 1f), new Vector2(1f, 0f), new Vector2(0f, 0f) };
for (int index = 0; index < 4; index++)
{
Ray ray = camera.ViewportPointToRay(new Vector3(corners[index].x, corners[index].y, 0f));
Vector3 point = plane.Raycast(ray, out float distance) && distance < 120f ? ray.GetPoint(distance) : ray.GetPoint(120f);
result[index] = LuneSpace.Map(point);
}
return result;
}
public void LookAt(Vec2 point, float zoom = -1f, bool instant = false)
{
_goalFocus = new Vec2(Mathf.Clamp((float)point.X, -LuneCameraMath.FocusLimit, LuneCameraMath.FocusLimit),
Mathf.Clamp((float)point.Y, -LuneCameraMath.FocusLimit, LuneCameraMath.FocusLimit));
if (zoom > 0f)
_goalZoom = Mathf.Clamp(zoom, LuneCameraMath.MinZoom, LuneCameraMath.MaxZoom);
if (instant)
{
_focus = _goalFocus;
Zoom = _goalZoom;
Yaw = _goalYaw;
Apply();
}
}
public void SetYawSteps(int steps, bool instant = false)
{
_goalYaw = steps * Mathf.PI * .25f;
if (instant)
{
Yaw = _goalYaw;
Apply();
}
}
/// <summary>Camera heading in radians (Godot yaw: 0 looks from the map's +y side).</summary>
public void SetYaw(float radians, bool instant = false)
{
_goalYaw = radians;
if (instant)
{
Yaw = _goalYaw;
Apply();
}
}
/// <summary>Yaw that puts the camera on the side of `from` that faces `towards`.</summary>
public static float YawTowards(Vec2 from, Vec2 towards)
{
Vec2 d = towards - from;
return Mathf.Atan2((float)d.X, (float)d.Y);
}
public void Overview(bool instant = false)
{
LookAt(new Vec2(0, 0), LuneCameraMath.OverviewZoom, instant);
}
void Apply()
{
float height = Ground != null ? Ground.StandHeight(_focus) : 0f;
LuneCameraMath.Pose(LuneSpace.World(_focus, height), Zoom, Yaw, out Vector3 position, out Quaternion rotation);
transform.SetPositionAndRotation(position, rotation);
}
void LateUpdate()
{
float dt = Time.unscaledDeltaTime;
if (Interactive)
HandleInput(dt);
float t = Mathf.Min(1f, dt * 7f);
_focus = _focus.Lerp(_goalFocus, t);
Zoom = Mathf.Lerp(Zoom, _goalZoom, t);
Yaw = Mathf.LerpAngle(Yaw * Mathf.Rad2Deg, _goalYaw * Mathf.Rad2Deg, t) * Mathf.Deg2Rad;
Apply();
}
void HandleInput(float dt)
{
bool keys = KeysBlocked == null || !KeysBlocked();
bool pointer = PointerBlocked == null || !PointerBlocked();
var pan = !keys ? Vector2.zero : new Vector2(Input.GetAxisRaw("Horizontal"), -Input.GetAxisRaw("Vertical"));
if (pan != Vector2.zero)
{
// Godot: right = (cos yaw, 0, -sin yaw), forward = (-sin yaw, 0, -cos yaw), map frame.
var right = new Vec2(Mathf.Cos(Yaw), -Mathf.Sin(Yaw));
var forward = new Vec2(-Mathf.Sin(Yaw), -Mathf.Cos(Yaw));
Vec2 move = right * (pan.x * Zoom * .9f * dt) - forward * (pan.y * Zoom * .9f * dt);
LookAt(_goalFocus + move);
}
float wheel = pointer ? Input.mouseScrollDelta.y : 0f;
if (wheel > 0f)
_goalZoom = Mathf.Max(LuneCameraMath.MinZoom, _goalZoom - Mathf.Max(2.5f, _goalZoom * .09f));
else if (wheel < 0f)
_goalZoom = Mathf.Min(LuneCameraMath.MaxZoom, _goalZoom + Mathf.Max(2.5f, _goalZoom * .09f));
if (keys && Input.GetKeyDown(KeyCode.Q))
_goalYaw -= Mathf.PI * .25f;
if (keys && Input.GetKeyDown(KeyCode.E))
_goalYaw += Mathf.PI * .25f;
if (keys && Input.GetKeyDown(KeyCode.R))
{
if (ResetRequested != null)
ResetRequested();
else
{
_goalYaw = 0f;
LookAt(_goalFocus, LuneCameraMath.DefaultZoom);
}
}
if (keys && Input.GetKeyDown(KeyCode.O))
Overview();
if (Input.GetMouseButtonDown(2) && pointer)
{
_dragAnchor = Input.mousePosition;
_dragging = true;
}
if (!Input.GetMouseButton(2))
_dragging = false;
if (_dragging)
{
Vector3 delta = Input.mousePosition - _dragAnchor;
_dragAnchor = Input.mousePosition;
var right = new Vec2(Mathf.Cos(Yaw), -Mathf.Sin(Yaw));
var forward = new Vec2(-Mathf.Sin(Yaw), -Mathf.Cos(Yaw));
// Screen y grows upwards in Unity (downwards in Godot).
LookAt(_goalFocus - (right * delta.x + forward * delta.y) * (Zoom / 700f));
}
}
}
}