#nullable enable using Nox.Platform; using UnityEngine; using UnityEngine.AddressableAssets; using UnityEngine.InputSystem; namespace Nox.Game { public class CameraController : ICameraController { private readonly PlatformSettings? platformSettings; private readonly InputSystem_Actions? inputActions; private readonly MapReference? mapReference; private CameraSettings? cameraSettings; private InputAction? zoomAction; private InputAction? panAction; private InputAction? holdToDragAction; private Bounds planeBounds; private Vector3 dragWorldOrigin; private bool isDragging; private readonly Plane groundPlane = new Plane(Vector3.up, Vector3.zero); private readonly Texture2D cursorPoint; private readonly Texture2D cursorDrag; public CameraReference? CameraReference { get; private set; } public CameraController(PlatformSettings? platformSettings, MapReference? mapReference) { this.platformSettings = platformSettings; this.mapReference = mapReference; inputActions = platformSettings?.inputSettings.inputActions; cursorPoint = Addressables.LoadAssetAsync("Assets/Art/UI/Cursor_Pointer").WaitForCompletion(); cursorDrag = Addressables.LoadAssetAsync("Assets/Art/UI/Cursor_Drag").WaitForCompletion(); Cursor.SetCursor(cursorPoint, new Vector2(12,4), CursorMode.Auto); } public void Initialize() { if(inputActions == null || !mapReference) { return; } zoomAction = inputActions.Player.Zoom; panAction = inputActions.Player.PanMap; holdToDragAction = inputActions.Player.HoldToDrag; planeBounds = mapReference.mapPlane.GetComponent().bounds; SetupCamera(); } private void SetupCamera() { CameraReference = Object.FindAnyObjectByType(); if(!CameraReference) { if(platformSettings?.cameraPrefab != null) { GameObject? go = Object.Instantiate(platformSettings.cameraPrefab); CameraReference = go.GetComponentInChildren(); if(!CameraReference) { Debug.LogError("Camera prefab does not contain a CameraReference component"); return; } } else { Debug.LogError("No camera prefab found and no camera reference found"); } } cameraSettings = CameraReference!.cameraSettings; if(!cameraSettings) { return; } // Set initial zoom to maximum allowed by map boundaries var camera = CameraReference.mainCamera; if(camera.orthographic) { float maxHalfWidth = planeBounds.size.x * 0.5f; float maxHalfHeight = planeBounds.size.z * 0.5f; float maxOrthoSize = Mathf.Min(maxHalfHeight, maxHalfWidth / camera.aspect); camera.orthographicSize = Mathf.Min(cameraSettings.maxZoom, maxOrthoSize); } else { // Perspective camera: set height so the map fits in view float mapWidth = planeBounds.size.x; float mapHeight = planeBounds.size.z; float aspect = camera.aspect; float fovRad = camera.fieldOfView * Mathf.Deg2Rad; float tanFov = Mathf.Tan(fovRad / 2f); float requiredHeightByWidth = mapWidth / (2f * tanFov * aspect); float requiredHeightByHeight = mapHeight / (2f * tanFov); float requiredHeight = Mathf.Max(requiredHeightByWidth, requiredHeightByHeight); // Center camera on map and set height Vector3 camPos = planeBounds.center; camPos.y = requiredHeight; camera.transform.position = camPos; // Look straight down camera.transform.rotation = Quaternion.Euler(90f, 0f, 0f); } } public void Tick() { PanCamera(); ZoomCamera(); } private void ZoomCamera() { if(zoomAction == null) { return; } float zoomDelta = zoomAction.ReadValue().y; if(zoomDelta == 0) { return; } var camera = CameraReference!.mainCamera; if(camera.orthographic) { camera.orthographicSize = Mathf.Clamp(camera.orthographicSize - (zoomDelta * cameraSettings!.zoomSpeed * Time.deltaTime), cameraSettings.minZoom, cameraSettings.maxZoom); } else { camera.transform.Translate(Vector3.forward * zoomDelta * cameraSettings!.zoomSpeed * Time.deltaTime, Space.Self); } } private void PanCamera() { if(holdToDragAction == null || CameraReference == null) { return; } if(holdToDragAction.IsInProgress()) { Camera camera = CameraReference.mainCamera; Vector2 screenPos = inputActions!.Player.Point.ReadValue(); if(!isDragging) { isDragging = true; Cursor.lockState = CursorLockMode.Confined; Cursor.SetCursor(cursorDrag, Vector2.zero, CursorMode.Auto); dragWorldOrigin = ScreenToGroundPoint(camera, screenPos); return; } Vector3 currentWorldPoint = ScreenToGroundPoint(camera, screenPos); Vector3 offset = dragWorldOrigin - currentWorldPoint; Vector3 camPos = camera.transform.position; camPos.x += offset.x; camPos.z += offset.z; camPos.x = Mathf.Clamp(camPos.x, planeBounds.min.x, planeBounds.max.x); camPos.z = Mathf.Clamp(camPos.z, planeBounds.min.z, planeBounds.max.z); camera.transform.position = camPos; dragWorldOrigin = ScreenToGroundPoint(camera, screenPos); } else { if(!isDragging) { return; } isDragging = false; Cursor.lockState = CursorLockMode.None; Cursor.SetCursor(cursorPoint, new Vector2(12,4), CursorMode.Auto); } } private Vector3 ScreenToGroundPoint(Camera camera, Vector2 screenPos) { Ray ray = camera.ScreenPointToRay(new Vector3(screenPos.x, screenPos.y, 0f)); if(groundPlane.Raycast(ray, out float distance)) { return ray.GetPoint(distance); } return camera.transform.position; } public void Dispose() { inputActions?.Disable(); } } }