Fester Simulationstakt und Ansichten, die nur lesen; Nachweis, dass die Partie bei jeder Bildrate dieselbe ist. - LuneClock: Akkumulator, ganze Schritte zu 0,05 s, Alpha zum Interpolieren. - LuneMatchRunner: Host-Ablauf einer Bot-Partie (Planung über die Befehlsschnittstelle, Kampf im festen Takt, Abrechnung, nächste Runde); LuneMatchDriver reicht in Update nur Bildzeit weiter. - LuneTrace: Fingerabdruck der Partie (jeder Schritt, jede Abrechnung) und der reine Kernlauf zum Vergleich. - Ansichten: LuneMatchView, LuneUnitView (interpolierte Position, Blickrichtung, Clips aus Kampfereignissen, Teamring, Lebensbalken), LuneBuildingView mit Einsturz und Ruine aus den autoritativen HP, Turmblitze, LuneCameraRig wie lune_camera.gd, vorläufige Debug-Anzeige (kein HUD; U3 portiert das G3-HUD). - Einheitenbibliothek: Kohlenbeißer aus 131 Teilen zu einem Mesh, Animator- Controller mit den Blender-Clips, Ring- und Balkenmeshes, Markermaterialien. - Tests: EditMode (30/60/120 fps, unbegrenzt, Ruckler, 8x Tempo; Seed 2 und Seed 9 ab Runde 21) und PlayMode in der Szene bei echten Bildraten 30, 60, 120 und unbegrenzt: überall derselbe Fingerabdruck wie der Kern. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
130 lines
5 KiB
C#
130 lines
5 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;
|
|
public float Zoom = LuneCameraMath.DefaultZoom;
|
|
public float Yaw;
|
|
Vec2 _focus;
|
|
Vec2 _goalFocus;
|
|
float _goalZoom = LuneCameraMath.DefaultZoom;
|
|
float _goalYaw;
|
|
Vector3 _dragAnchor;
|
|
|
|
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)
|
|
{
|
|
var pan = 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 = Input.mouseScrollDelta.y;
|
|
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 (Input.GetKeyDown(KeyCode.Q))
|
|
_goalYaw -= Mathf.PI * .25f;
|
|
if (Input.GetKeyDown(KeyCode.E))
|
|
_goalYaw += Mathf.PI * .25f;
|
|
if (Input.GetKeyDown(KeyCode.R))
|
|
{
|
|
_goalYaw = 0f;
|
|
LookAt(_goalFocus, LuneCameraMath.DefaultZoom);
|
|
}
|
|
if (Input.GetKeyDown(KeyCode.O))
|
|
Overview();
|
|
if (Input.GetMouseButtonDown(2))
|
|
_dragAnchor = Input.mousePosition;
|
|
if (Input.GetMouseButton(2))
|
|
{
|
|
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));
|
|
}
|
|
}
|
|
}
|
|
}
|