Files
Cielonos/Assets/OtherPlugins/GraphicsCat/DemoShared/Scripts/CameraRig.cs
SoulliesOfficial d15957c719 更新
2025-12-17 04:19:38 -05:00

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);
}
}
}