forked from Shardstone/trail-into-darkness
172 lines
6.9 KiB
C#
172 lines
6.9 KiB
C#
#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<Texture2D>("Assets/Art/UI/Cursor_Pointer").WaitForCompletion();
|
|
cursorDrag = Addressables.LoadAssetAsync<Texture2D>("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<BoxCollider>().bounds;
|
|
SetupCamera();
|
|
}
|
|
|
|
private void SetupCamera() {
|
|
CameraReference = Object.FindAnyObjectByType<CameraReference>();
|
|
|
|
if(!CameraReference) {
|
|
if(platformSettings?.cameraPrefab != null) {
|
|
GameObject? go = Object.Instantiate(platformSettings.cameraPrefab);
|
|
CameraReference = go.GetComponentInChildren<CameraReference>();
|
|
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<Vector2>().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<Vector2>();
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
}
|