using Luneraria.Core; using UnityEngine; namespace Luneraria.View { /// /// 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. /// public sealed class LuneCameraRig : MonoBehaviour { public LuneGround Ground; public bool Interactive = true; /// True while the pointer is over the HUD: no wheel zoom, no middle-mouse drag (U3). public System.Func PointerBlocked; /// True while a window takes the keyboard (options): no panning or turning keys. public System.Func KeysBlocked; /// R: the game controller's reset (own lane, Godot reset_camera); without it R resets yaw and zoom. 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; } /// Yaw back to 0 and the default zoom at a point (Godot reset_view). public void ResetView(Vec2 point, bool instant = false) { _goalYaw = 0f; LookAt(point, LuneCameraMath.DefaultZoom, instant); } /// /// 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. /// 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(); } } /// Camera heading in radians (Godot yaw: 0 looks from the map's +y side). public void SetYaw(float radians, bool instant = false) { _goalYaw = radians; if (instant) { Yaw = _goalYaw; Apply(); } } /// Yaw that puts the camera on the side of `from` that faces `towards`. 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)); } } } }