531 lines
19 KiB
C#
531 lines
19 KiB
C#
using System;
|
|
using UnityEngine;
|
|
|
|
namespace GraphicsCat
|
|
{
|
|
public struct CameraMovementInput
|
|
{
|
|
public Vector3 translationDelta;
|
|
public Vector3 rotationDelta;
|
|
public float zoomDelta;
|
|
public bool isDragging;
|
|
}
|
|
|
|
public partial class CameraRig : MonoBehaviour, IMGUIDockable
|
|
{
|
|
public enum RigType
|
|
{
|
|
Free,
|
|
TopDown,
|
|
Locked,
|
|
}
|
|
|
|
const int k_SpaceHeight = 10;
|
|
|
|
[Space(k_SpaceHeight)]
|
|
public RigType rigType = RigType.Free;
|
|
|
|
[Space(k_SpaceHeight)]
|
|
[Range(0, 20)]
|
|
public float moveSpeed = 6f;
|
|
[Range(0, 0.2f)]
|
|
public float rotateSpeed = 0.1f;
|
|
[Range(0, 200f)]
|
|
public float zoomSpeed = 40f;
|
|
|
|
[Space(k_SpaceHeight)]
|
|
public Transform targetTransform;
|
|
|
|
[Space(k_SpaceHeight)]
|
|
public bool guiEnabled = true;
|
|
|
|
private Transform m_CachedTransform;
|
|
|
|
private Vector3 m_CameraInitialPosition;
|
|
private Quaternion m_CameraInitialRotation;
|
|
private Vector3 m_TargetInitialPosition;
|
|
private Quaternion m_TargetInitialRotation;
|
|
|
|
private Touch m_MovementTouchInitial;
|
|
private Touch m_MovementTouchCurrent;
|
|
private Vector3 m_LastRotationMousePosition;
|
|
private Vector3 m_LastDragMousePosition;
|
|
private float m_MovementSpeedMultiplier;
|
|
|
|
private Vector3 m_TopDownPanDelta;
|
|
|
|
private bool m_IsInitialized;
|
|
|
|
private bool m_IsRigTypeSwitcherVisible = false;
|
|
|
|
private void ProcessCameraMovement(CameraMovementInput input)
|
|
{
|
|
bool isFreeType = (rigType == RigType.Free);
|
|
bool isTopDownType = (rigType == RigType.TopDown);
|
|
|
|
if (input.translationDelta != Vector3.zero)
|
|
{
|
|
ApplyMovementDelta(input.translationDelta);
|
|
}
|
|
|
|
if (input.rotationDelta != Vector3.zero && targetTransform != null)
|
|
{
|
|
if (isFreeType)
|
|
{
|
|
OrbitAroundTarget(input.rotationDelta);
|
|
}
|
|
}
|
|
|
|
if (input.zoomDelta != 0f)
|
|
{
|
|
if (isFreeType && targetTransform != null)
|
|
{
|
|
AdjustDistanceToTarget(input.zoomDelta);
|
|
}
|
|
else if (isTopDownType)
|
|
{
|
|
Vector3 zoomDeltaVector = m_CachedTransform.forward * input.zoomDelta;
|
|
m_CachedTransform.position += zoomDeltaVector;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ApplyMovementDelta(Vector3 delta)
|
|
{
|
|
m_CachedTransform.position += delta;
|
|
|
|
if (targetTransform != null)
|
|
targetTransform.position += delta;
|
|
}
|
|
|
|
private void OrbitAroundTarget(Vector3 delta)
|
|
{
|
|
if (targetTransform == null) return;
|
|
|
|
m_CachedTransform.RotateAround(targetTransform.position, Vector3.up, delta.x);
|
|
m_CachedTransform.RotateAround(targetTransform.position, -m_CachedTransform.right, delta.y);
|
|
}
|
|
|
|
private void RotateCamera(Vector3 delta)
|
|
{
|
|
if (targetTransform == null) return;
|
|
|
|
Vector3 referenceUp = Vector3.up;
|
|
if (Vector3.Dot(m_CachedTransform.forward, Vector3.up) > 0.99f || Vector3.Dot(m_CachedTransform.forward, Vector3.up) < -0.99f)
|
|
referenceUp = m_CachedTransform.right;
|
|
|
|
Vector3 cameraRight = Vector3.Cross(referenceUp, m_CachedTransform.forward).normalized;
|
|
Vector3 cameraUp = Vector3.Cross(m_CachedTransform.forward, cameraRight).normalized;
|
|
|
|
Quaternion horizontalQuat = Quaternion.AngleAxis(delta.x, cameraUp);
|
|
Quaternion verticalQuat = Quaternion.AngleAxis(delta.y, cameraRight);
|
|
Quaternion totalRotation = horizontalQuat * verticalQuat;
|
|
|
|
Vector3 cameraToTarget = targetTransform.position - m_CachedTransform.position;
|
|
Vector3 newCameraToTarget = totalRotation * cameraToTarget;
|
|
|
|
targetTransform.position = m_CachedTransform.position + newCameraToTarget;
|
|
|
|
m_CachedTransform.LookAt(targetTransform.position);
|
|
}
|
|
|
|
private void AdjustDistanceToTarget(float delta)
|
|
{
|
|
if (targetTransform == null) return;
|
|
|
|
Vector3 cameraToTarget = m_CachedTransform.position - targetTransform.position;
|
|
float newDistance = Mathf.Max(cameraToTarget.magnitude + delta, 0.1f);
|
|
m_CachedTransform.position = targetTransform.position + cameraToTarget.normalized * newDistance;
|
|
}
|
|
|
|
private void ResetToInitialState()
|
|
{
|
|
m_CachedTransform.SetPositionAndRotation(m_CameraInitialPosition, m_CameraInitialRotation);
|
|
|
|
if (targetTransform != null)
|
|
targetTransform.SetPositionAndRotation(m_TargetInitialPosition, m_TargetInitialRotation);
|
|
}
|
|
|
|
public void OnDockGUI()
|
|
{
|
|
GUILayout.BeginHorizontal();
|
|
{
|
|
if (m_IsRigTypeSwitcherVisible)
|
|
{
|
|
var btnText = GetRigTypeDescription(rigType);
|
|
if (GUILayout.Button(btnText))
|
|
rigType = GetNextRigType(rigType);
|
|
}
|
|
|
|
bool defaultStatus = true;
|
|
defaultStatus &= (m_CachedTransform.position == m_CameraInitialPosition);
|
|
defaultStatus &= (m_CachedTransform.rotation == m_CameraInitialRotation);
|
|
|
|
GUI.enabled = !defaultStatus;
|
|
if (GUILayout.Button("Reset Camera"))
|
|
ResetToInitialState();
|
|
GUI.enabled = true;
|
|
}
|
|
GUILayout.EndHorizontal();
|
|
}
|
|
|
|
private void Start()
|
|
{
|
|
m_CachedTransform = transform;
|
|
|
|
m_CameraInitialPosition = m_CachedTransform.position;
|
|
m_CameraInitialRotation = m_CachedTransform.rotation;
|
|
|
|
if (targetTransform != null)
|
|
{
|
|
m_TargetInitialPosition = targetTransform.position;
|
|
m_TargetInitialRotation = targetTransform.rotation;
|
|
}
|
|
|
|
m_MovementTouchInitial.fingerId = int.MinValue;
|
|
|
|
m_IsInitialized = true;
|
|
|
|
UpdateDockGUI();
|
|
}
|
|
|
|
private void OnValidate()
|
|
{
|
|
UpdateDockGUI();
|
|
}
|
|
|
|
private void UpdateDockGUI()
|
|
{
|
|
if (m_IsInitialized == false)
|
|
return;
|
|
|
|
if (guiEnabled)
|
|
IMGUIDock.topRight.DockGUI(this);
|
|
else
|
|
IMGUIDock.topRight.UndockGUI(this);
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (rigType == RigType.Locked)
|
|
return;
|
|
|
|
if (Application.isMobilePlatform)
|
|
HandleMobileInput();
|
|
else
|
|
HandleDesktopInput();
|
|
}
|
|
|
|
private Vector3 CalculateWorldPanDelta(Vector2 screenDelta)
|
|
{
|
|
var cam = GetComponent<Camera>();
|
|
if (cam == null) return Vector3.zero;
|
|
|
|
float distance;
|
|
|
|
if (rigType == RigType.Free && targetTransform != null)
|
|
{
|
|
distance = Vector3.Distance(m_CachedTransform.position, targetTransform.position);
|
|
}
|
|
else if (rigType == RigType.TopDown)
|
|
{
|
|
float camHeight = m_CachedTransform.position.y;
|
|
float forwardY = m_CachedTransform.forward.y;
|
|
|
|
if (Mathf.Abs(forwardY) < 1e-3 || Mathf.Abs(camHeight) < 1e-3)
|
|
{
|
|
distance = camHeight;
|
|
}
|
|
else
|
|
{
|
|
distance = camHeight / Mathf.Abs(forwardY);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return Vector3.zero;
|
|
}
|
|
|
|
float halfFOV = cam.fieldOfView * 0.5f;
|
|
float worldHeightAtDistance = 2f * distance * Mathf.Tan(halfFOV * Mathf.Deg2Rad);
|
|
float worldWidthAtDistance = worldHeightAtDistance * cam.aspect;
|
|
|
|
float percentX = screenDelta.x / Screen.width;
|
|
float percentY = screenDelta.y / Screen.height;
|
|
|
|
Vector3 worldDelta = Vector3.zero;
|
|
worldDelta -= m_CachedTransform.right * (percentX * worldWidthAtDistance);
|
|
|
|
if (rigType == RigType.Free)
|
|
{
|
|
worldDelta -= m_CachedTransform.up * (percentY * worldHeightAtDistance);
|
|
}
|
|
else if (rigType == RigType.TopDown)
|
|
{
|
|
worldDelta -= GetHorizontalForwardVector() * (percentY * worldHeightAtDistance);
|
|
}
|
|
|
|
return worldDelta;
|
|
}
|
|
|
|
private void HandleDesktopInput()
|
|
{
|
|
var input = new CameraMovementInput();
|
|
var isTopDownType = rigType == RigType.TopDown;
|
|
var isFreeType = (rigType == RigType.Free);
|
|
var deltaTime = Time.smoothDeltaTime;
|
|
|
|
Vector3 keyboardMovement = Vector3.zero;
|
|
|
|
if (isFreeType && targetTransform != null)
|
|
{
|
|
if (Input.GetKey(KeyCode.W)) keyboardMovement += m_CachedTransform.forward;
|
|
if (Input.GetKey(KeyCode.S)) keyboardMovement -= m_CachedTransform.forward;
|
|
if (Input.GetKey(KeyCode.D)) keyboardMovement += m_CachedTransform.right;
|
|
if (Input.GetKey(KeyCode.A)) keyboardMovement -= m_CachedTransform.right;
|
|
if (Input.GetKey(KeyCode.E)) keyboardMovement += Vector3.up;
|
|
if (Input.GetKey(KeyCode.Q)) keyboardMovement -= Vector3.up;
|
|
}
|
|
else if (isTopDownType)
|
|
{
|
|
if (Input.GetKey(KeyCode.Q)) input.zoomDelta += moveSpeed * deltaTime;
|
|
if (Input.GetKey(KeyCode.E)) input.zoomDelta -= moveSpeed * deltaTime;
|
|
|
|
if (Input.GetKey(KeyCode.W)) keyboardMovement += GetHorizontalForwardVector();
|
|
if (Input.GetKey(KeyCode.S)) keyboardMovement -= GetHorizontalForwardVector();
|
|
if (Input.GetKey(KeyCode.D)) keyboardMovement += m_CachedTransform.right;
|
|
if (Input.GetKey(KeyCode.A)) keyboardMovement -= m_CachedTransform.right;
|
|
}
|
|
|
|
if (keyboardMovement != Vector3.zero)
|
|
{
|
|
m_MovementSpeedMultiplier += deltaTime;
|
|
input.translationDelta = keyboardMovement.normalized * moveSpeed * deltaTime * m_MovementSpeedMultiplier;
|
|
}
|
|
else
|
|
{
|
|
m_MovementSpeedMultiplier = 1;
|
|
}
|
|
|
|
if (isFreeType && targetTransform != null)
|
|
{
|
|
if (Input.GetMouseButtonDown(0) || Input.GetMouseButtonDown(1))
|
|
m_LastRotationMousePosition = Input.mousePosition;
|
|
if (Input.GetMouseButtonDown(2))
|
|
m_LastDragMousePosition = Input.mousePosition;
|
|
|
|
if (Input.GetMouseButton(0))
|
|
{
|
|
var rotateMouseDelta = (Input.mousePosition - m_LastRotationMousePosition);
|
|
if (rotateMouseDelta.sqrMagnitude > float.Epsilon)
|
|
{
|
|
input.rotationDelta = rotateMouseDelta * rotateSpeed;
|
|
m_LastRotationMousePosition = Input.mousePosition;
|
|
}
|
|
}
|
|
else if (Input.GetMouseButton(1))
|
|
{
|
|
var rotateMouseDelta = (Input.mousePosition - m_LastRotationMousePosition);
|
|
if (rotateMouseDelta.sqrMagnitude > float.Epsilon)
|
|
{
|
|
var adjustedDelta = new Vector3(rotateMouseDelta.x * rotateSpeed, -rotateMouseDelta.y * rotateSpeed, 0);
|
|
RotateCamera(adjustedDelta * 0.5f);
|
|
m_LastRotationMousePosition = Input.mousePosition;
|
|
}
|
|
}
|
|
else if (Input.GetMouseButton(2))
|
|
{
|
|
var mouseDelta = (Input.mousePosition - m_LastDragMousePosition);
|
|
if (mouseDelta.sqrMagnitude > float.Epsilon)
|
|
{
|
|
input.translationDelta = CalculateWorldPanDelta(mouseDelta);
|
|
input.isDragging = true;
|
|
m_LastDragMousePosition = Input.mousePosition;
|
|
}
|
|
}
|
|
}
|
|
else if (isTopDownType)
|
|
{
|
|
if (Input.GetMouseButtonDown(0) || Input.GetMouseButtonDown(1) || Input.GetMouseButtonDown(2))
|
|
m_LastDragMousePosition = Input.mousePosition;
|
|
|
|
if (Input.GetMouseButton(0) || Input.GetMouseButton(1) || Input.GetMouseButton(2))
|
|
{
|
|
var mouseDelta = (Input.mousePosition - m_LastDragMousePosition);
|
|
if (mouseDelta.sqrMagnitude > float.Epsilon)
|
|
{
|
|
input.translationDelta = CalculateWorldPanDelta(mouseDelta);
|
|
input.isDragging = true;
|
|
|
|
m_LastDragMousePosition = Input.mousePosition;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Input.mouseScrollDelta.y != 0 && IsMouseInViewPort())
|
|
{
|
|
if (isFreeType && targetTransform != null)
|
|
{
|
|
input.zoomDelta = -(Input.mouseScrollDelta.y * zoomSpeed * Time.deltaTime);
|
|
}
|
|
else if (isTopDownType)
|
|
{
|
|
input.zoomDelta = Input.mouseScrollDelta.y * zoomSpeed * Time.deltaTime;
|
|
}
|
|
}
|
|
|
|
ProcessCameraMovement(input);
|
|
}
|
|
|
|
private void HandleMobileInput()
|
|
{
|
|
if (rigType == RigType.Free)
|
|
HandleFreeMobileInput();
|
|
else if (rigType == RigType.TopDown)
|
|
HandleTopDownMobileInput();
|
|
}
|
|
|
|
private void HandleFreeMobileInput()
|
|
{
|
|
if (targetTransform == null) return;
|
|
|
|
var input = new CameraMovementInput();
|
|
var touches = Input.touches;
|
|
|
|
if (touches.Length == 2)
|
|
{
|
|
Touch t0 = touches[0];
|
|
Touch t1 = touches[1];
|
|
|
|
Vector2 t0PrevPos = t0.position - t0.deltaPosition;
|
|
Vector2 t1PrevPos = t1.position - t1.deltaPosition;
|
|
|
|
float prevTouchDeltaMag = (t0PrevPos - t1PrevPos).magnitude;
|
|
float currentTouchDeltaMag = (t0.position - t1.position).magnitude;
|
|
|
|
float pinchDelta = prevTouchDeltaMag - currentTouchDeltaMag;
|
|
input.zoomDelta = pinchDelta * zoomSpeed * Time.smoothDeltaTime * 0.002f;
|
|
}
|
|
else
|
|
{
|
|
foreach (var touch in touches)
|
|
{
|
|
if (touch.phase == TouchPhase.Began)
|
|
m_MovementTouchInitial = touch;
|
|
else if (touch.phase == TouchPhase.Ended)
|
|
{
|
|
if (m_MovementTouchInitial.fingerId == touch.fingerId)
|
|
m_MovementTouchInitial.fingerId = int.MinValue;
|
|
}
|
|
}
|
|
|
|
m_MovementTouchCurrent.fingerId = int.MinValue;
|
|
foreach (var touch in touches)
|
|
{
|
|
if (touch.fingerId == m_MovementTouchInitial.fingerId)
|
|
m_MovementTouchCurrent = touch;
|
|
}
|
|
|
|
if (m_MovementTouchCurrent.fingerId != int.MinValue)
|
|
{
|
|
var rotateStrength = (m_MovementTouchCurrent.position - m_MovementTouchInitial.position) / Screen.width;
|
|
if (rotateStrength != Vector2.zero)
|
|
{
|
|
var deltaTime = Time.smoothDeltaTime;
|
|
var rotationDelta = 1000f * 20 * deltaTime * rotateSpeed * rotateStrength;
|
|
input.rotationDelta = new Vector3(rotationDelta.x, rotationDelta.y, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
ProcessCameraMovement(input);
|
|
}
|
|
|
|
private void HandleTopDownMobileInput()
|
|
{
|
|
var input = new CameraMovementInput();
|
|
var touches = Input.touches;
|
|
|
|
foreach (var touch in touches)
|
|
{
|
|
if (touch.phase == TouchPhase.Began)
|
|
m_MovementTouchInitial = touch;
|
|
else if (touch.phase == TouchPhase.Ended)
|
|
{
|
|
if (m_MovementTouchInitial.fingerId == touch.fingerId)
|
|
m_MovementTouchInitial.fingerId = int.MinValue;
|
|
}
|
|
}
|
|
|
|
m_MovementTouchCurrent.fingerId = int.MinValue;
|
|
foreach (var touch in touches)
|
|
{
|
|
if (touch.fingerId == m_MovementTouchInitial.fingerId)
|
|
m_MovementTouchCurrent = touch;
|
|
}
|
|
|
|
if (m_MovementTouchCurrent.fingerId != int.MinValue)
|
|
{
|
|
if (m_MovementTouchCurrent.deltaPosition != Vector2.zero)
|
|
{
|
|
input.translationDelta = CalculateWorldPanDelta(m_MovementTouchCurrent.deltaPosition);
|
|
input.isDragging = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_TopDownPanDelta *= Mathf.Lerp(1f, 0f, Time.smoothDeltaTime * 5);
|
|
input.translationDelta = m_TopDownPanDelta;
|
|
}
|
|
|
|
ProcessCameraMovement(input);
|
|
}
|
|
|
|
private bool IsMouseInViewPort()
|
|
{
|
|
if (Input.mousePosition.x < 0 || Input.mousePosition.x > Screen.width ||
|
|
Input.mousePosition.y < 0 || Input.mousePosition.y > Screen.height)
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private Vector3 GetScreenSpaceUpVector()
|
|
{
|
|
var forward = m_CachedTransform.forward;
|
|
if (Mathf.Abs(forward.y) < 0.01)
|
|
return m_CachedTransform.up;
|
|
else
|
|
{
|
|
forward.y = 0;
|
|
return forward.normalized;
|
|
}
|
|
}
|
|
|
|
private Vector3 GetHorizontalForwardVector()
|
|
{
|
|
var forward = m_CachedTransform.forward;
|
|
forward.y = 0;
|
|
return forward.normalized;
|
|
}
|
|
|
|
private string GetRigTypeDescription(RigType type)
|
|
{
|
|
var description = "Camera: Unknown Type";
|
|
switch (type)
|
|
{
|
|
case RigType.Free: description = "Camera: Free"; break;
|
|
case RigType.TopDown: description = "Camera: TopDown"; break;
|
|
case RigType.Locked: description = "Camera: Locked"; break;
|
|
}
|
|
return description;
|
|
}
|
|
|
|
private RigType GetNextRigType(RigType currentRigType)
|
|
{
|
|
var rigTypeCount = Enum.GetValues(typeof(RigType)).Length;
|
|
return (RigType)(((int)currentRigType + 1) % rigTypeCount);
|
|
}
|
|
}
|
|
} |