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

306 lines
11 KiB
C#

using UnityEngine;
namespace GraphicsCat
{
public class SlideRotationController : MonoBehaviour, IMGUIDockable
{
private const float INERTIA_THRESHOLD = 0.1f; // Threshold to stop inertia rotation
[Header("Slide Rotation")]
[InspectorDisplayName("Enable")]
public bool slideRotationEnabled = false;
[InspectorDisplayName("Sensitivity")]
public Vector3 slideRotationSensitivity = new(0, 20, 0);
[InspectorDisplayName("Inertia Damping")]
[Range(0f, 1f)]
public float inertiaDamping = 0.8f; // 0 = no inertia, 1 = maximum inertia
[Header("Auto Rotation")]
[InspectorDisplayName("Enable")]
public bool autoRotationEnabled = true;
[InspectorDisplayName("Speed")]
public Vector3 autoRotationSpeed = new(0, 5, 0);
[Header("Other")]
public bool displayInGameGUI = false;
// Input state
private bool m_IsDragging = false;
private Vector3 m_LastPointerPosition = Vector3.zero;
private Vector3 m_PointerDelta = Vector3.zero;
private Transform m_Target = null;
// Touch input
private Touch m_MainTouch;
private bool m_IsTouchActive = false;
// Auto rotation
private bool m_WasAutoRotationEnabled;
private bool m_IsAutoRotationPaused = false;
// Inertia
private Vector3 m_CurrentInertia = Vector3.zero;
private Vector3 m_LastAngularVelocity = Vector3.zero;
private bool m_HasAngularMovement = false; // Track if there was actual angular movement
private void Start()
{
m_Target = transform;
IMGUIDock.topRight.DockGUI(this);
}
private void Update()
{
if (!slideRotationEnabled)
return;
m_PointerDelta = Vector3.zero;
HandleMouseInput();
if (Application.isMobilePlatform)
HandleTouchInput();
}
private void FixedUpdate()
{
Vector3 slideRotationDelta = Vector3.zero;
if (slideRotationEnabled)
{
// Handle manual rotation
if (m_PointerDelta != Vector3.zero)
{
slideRotationDelta = ComputeRotationDelta(m_PointerDelta.x, m_PointerDelta.y);
}
// Apply inertia when not actively rotating
else if (m_CurrentInertia.magnitude > INERTIA_THRESHOLD)
{
slideRotationDelta = m_CurrentInertia;
m_CurrentInertia *= inertiaDamping;
if (m_CurrentInertia.magnitude < INERTIA_THRESHOLD)
m_CurrentInertia = Vector3.zero;
}
// Apply manual rotation or inertia
if (slideRotationDelta != Vector3.zero)
{
Quaternion deltaRotation = Quaternion.Euler(slideRotationDelta * Time.fixedDeltaTime);
m_Target.localRotation = deltaRotation * m_Target.localRotation;
m_LastAngularVelocity = slideRotationDelta;
m_HasAngularMovement = true; // There was actual rotation this frame
}
else
{
// No rotation this frame
m_HasAngularMovement = false;
}
}
// Apply auto rotation
if (slideRotationDelta == Vector3.zero && autoRotationEnabled && !m_IsAutoRotationPaused)
{
ApplyAutoRotation();
}
}
/// <summary>
/// Apply auto rotation according to configured speed.
/// </summary>
private void ApplyAutoRotation()
{
Quaternion rotation = Quaternion.Euler(autoRotationSpeed * Time.fixedDeltaTime);
m_Target.localRotation = rotation * m_Target.localRotation;
}
/// <summary>
/// Compute rotation delta based on pointer movement.
/// </summary>
private Vector3 ComputeRotationDelta(float x, float y)
{
return Vector3.Scale(new Vector3(y, x, 0), -slideRotationSensitivity);
}
/// <summary>
/// Handle mouse input for slide rotation.
/// </summary>
private void HandleMouseInput()
{
if (Input.GetMouseButtonDown(0))
{
if (IsPointerOverTarget(Input.mousePosition))
{
m_IsDragging = true;
m_LastPointerPosition = Input.mousePosition;
m_CurrentInertia = Vector3.zero; // Reset inertia when starting drag
m_HasAngularMovement = false;
PauseAutoRotation();
}
}
else if (Input.GetMouseButtonUp(0))
{
if (m_IsDragging)
{
m_IsDragging = false;
// Only apply inertia if there was actual angular movement
if (m_HasAngularMovement)
ApplyInertia();
else
m_CurrentInertia = Vector3.zero; // No movement, no inertia
ResumeAutoRotation();
}
}
else if (Input.GetMouseButton(0) && m_IsDragging)
{
Vector3 currentPosition = Input.mousePosition;
m_PointerDelta = currentPosition - m_LastPointerPosition;
m_LastPointerPosition = currentPosition;
}
else
{
m_IsDragging = false;
}
}
/// <summary>
/// Handle touch input for slide rotation.
/// </summary>
private void HandleTouchInput()
{
int touchCount = Input.touchCount;
if (touchCount <= 0)
{
m_IsTouchActive = false;
return;
}
for (int i = 0; i < touchCount; i++)
{
Touch touch = Input.GetTouch(i);
switch (touch.phase)
{
case TouchPhase.Began:
if (IsPointerOverTarget(touch.position))
{
m_MainTouch = touch;
m_IsTouchActive = true;
m_LastPointerPosition = touch.position;
m_CurrentInertia = Vector3.zero; // Reset inertia when starting touch
m_HasAngularMovement = false;
PauseAutoRotation();
}
break;
case TouchPhase.Moved:
if (m_IsTouchActive && touch.fingerId == m_MainTouch.fingerId)
{
Vector3 currentTouchPosition = touch.position;
m_PointerDelta = currentTouchPosition - m_LastPointerPosition;
m_LastPointerPosition = currentTouchPosition;
}
break;
case TouchPhase.Ended:
case TouchPhase.Canceled:
if (m_IsTouchActive && touch.fingerId == m_MainTouch.fingerId)
{
m_IsTouchActive = false;
m_PointerDelta = Vector3.zero;
// Only apply inertia if there was actual angular movement
if (m_HasAngularMovement)
ApplyInertia();
else
m_CurrentInertia = Vector3.zero; // No movement, no inertia
ResumeAutoRotation();
}
break;
}
}
}
/// <summary>
/// Apply inertia based on the last angular velocity.
/// Inertia is directly proportional to the angular velocity at the moment of release.
/// </summary>
private void ApplyInertia()
{
// Only apply inertia if there was significant angular velocity
// This ensures inertia magnitude correlates with rotation speed at release
if (inertiaDamping > 0 && m_LastAngularVelocity.magnitude > INERTIA_THRESHOLD)
m_CurrentInertia = m_LastAngularVelocity * inertiaDamping;
else
m_CurrentInertia = Vector3.zero;
}
/// <summary>
/// Temporarily pause auto rotation during user interaction.
/// </summary>
private void PauseAutoRotation()
{
if (autoRotationEnabled)
{
m_WasAutoRotationEnabled = autoRotationEnabled;
autoRotationEnabled = false;
m_IsAutoRotationPaused = true;
}
}
/// <summary>
/// Resume auto rotation after user interaction ends.
/// </summary>
private void ResumeAutoRotation()
{
if (m_IsAutoRotationPaused)
{
autoRotationEnabled = m_WasAutoRotationEnabled;
m_IsAutoRotationPaused = false;
}
}
/// <summary>
/// Check if the given screen point is over this object using renderer bounds.
/// </summary>
private bool IsPointerOverTarget(Vector3 screenPoint)
{
Camera camera = Camera.main;
if (camera == null)
return false;
Renderer[] renderers = m_Target.GetComponentsInChildren<Renderer>();
if (renderers.Length == 0)
return false;
Ray ray = camera.ScreenPointToRay(screenPoint);
foreach (Renderer renderer in renderers)
{
if (renderer.enabled && renderer.bounds.IntersectRay(ray))
return true;
}
return false;
}
public void OnDockGUI()
{
if (!displayInGameGUI)
return;
GUILayout.BeginHorizontal();
if (GUILayout.Button("Slide Rotation " + (slideRotationEnabled ? "O" : "X")))
slideRotationEnabled = !slideRotationEnabled;
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
if (GUILayout.Button("Auto Rotation " + (autoRotationEnabled ? "O" : "X")))
autoRotationEnabled = !autoRotationEnabled;
GUILayout.EndHorizontal();
}
}
}