You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
228 lines
8.3 KiB
C#
228 lines
8.3 KiB
C#
using UnityEngine;
|
|
using Cinemachine;
|
|
using System.Collections;
|
|
using Unity.BossRoom.CameraUtils;
|
|
|
|
public class EdgePanningController : MonoBehaviour
|
|
{
|
|
public CinemachineFreeLook virtualCamera;
|
|
private CinemachineCameraOffset cameraOffset;
|
|
|
|
private Coroutine shakeCoroutine;
|
|
|
|
public float edgeThreshold = 50f; // Edge detection distance from screen edges
|
|
public float resetRadiusPercentage = 3f; // Center reset radius in percentage of screen
|
|
public float panSpeed = 0.1f; // How much to offset per frame
|
|
public float resetSpeed = 2f; // Speed of resetting panning
|
|
public Vector2 panLimit = new Vector2(0.5f, 0.5f); // Max panning limit (x, y)
|
|
|
|
// --- Added fields for free roam mode ---
|
|
public float freeRoamSpeed = 10f; // Speed of free roam movement
|
|
private bool isFreeRoamMode = false; // Toggle for free roam mode
|
|
private Vector3 savedCameraPosition; // Stores camera position before free roam
|
|
private Quaternion savedCameraRotation; // Stores camera rotation before free roam
|
|
private Transform savedFollowTarget; // Stores the original follow target
|
|
|
|
private Vector3 defaultOffset;
|
|
private Vector3 panningOffset;
|
|
private bool isPanning = false;
|
|
private bool targetFound = false;
|
|
private bool shouldReset = false; // Flag to trigger reset immediately
|
|
|
|
private Transform targetCharacter; // The character the camera follows
|
|
|
|
private void Start()
|
|
{
|
|
cameraOffset = virtualCamera.GetComponent<CinemachineCameraOffset>();
|
|
if (cameraOffset == null)
|
|
{
|
|
Debug.LogError("[EdgePanningController] No CinemachineCameraOffset component found! Add it to the FreeLook Camera.");
|
|
return;
|
|
}
|
|
|
|
defaultOffset = cameraOffset.m_Offset;
|
|
panningOffset = defaultOffset;
|
|
}
|
|
|
|
private void Awake()
|
|
{
|
|
CameraController.OnCameraAttached += SetTargetCharacter;
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
CameraController.OnCameraAttached -= SetTargetCharacter;
|
|
}
|
|
|
|
private void SetTargetCharacter(Transform characterTransform)
|
|
{
|
|
targetCharacter = characterTransform.parent;
|
|
targetFound = true;
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
// Check for backspace key to toggle free roam mode
|
|
if (Input.GetKeyDown(KeyCode.Backspace))
|
|
{
|
|
ToggleFreeRoamMode();
|
|
}
|
|
|
|
// When in free roam mode, handle free movement and bypass normal edge panning
|
|
if (isFreeRoamMode)
|
|
{
|
|
HandleFreeRoam();
|
|
return; // Skip the rest of Update so the panning logic is not executed
|
|
}
|
|
|
|
// Normal camera behavior (edge panning) if a target is set
|
|
if (targetFound)
|
|
HandleEdgePanning();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Toggles the free roam mode on/off.
|
|
/// </summary>
|
|
private void ToggleFreeRoamMode()
|
|
{
|
|
isFreeRoamMode = !isFreeRoamMode;
|
|
if (isFreeRoamMode)
|
|
{
|
|
// Activate free roam: save current camera state and disable following
|
|
savedCameraPosition = virtualCamera.transform.position;
|
|
savedCameraRotation = virtualCamera.transform.rotation;
|
|
savedFollowTarget = virtualCamera.Follow; // Save the current follow target if any
|
|
virtualCamera.Follow = null; // Detach so the camera can be controlled directly
|
|
}
|
|
else
|
|
{
|
|
// Deactivate free roam: restore the previous camera state and resume following target
|
|
virtualCamera.transform.position = savedCameraPosition;
|
|
virtualCamera.transform.rotation = savedCameraRotation;
|
|
virtualCamera.Follow = savedFollowTarget; // Reattach the follow target
|
|
|
|
// Reset panning variables so the camera aligns with its default offset
|
|
cameraOffset.m_Offset = defaultOffset;
|
|
isPanning = false;
|
|
panningOffset = defaultOffset;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handles free roam camera movement when free roam mode is active.
|
|
/// Uses the Horizontal/Vertical axes and Q/E keys to move up and down.
|
|
/// </summary>
|
|
private void HandleFreeRoam()
|
|
{
|
|
float horizontal = Input.GetAxis("Horizontal"); // A/D or Left/Right
|
|
float vertical = Input.GetAxis("Vertical"); // W/S or Up/Down
|
|
|
|
// Use E to ascend and Q to descend (you can change these keys if needed)
|
|
float ascent = 0f;
|
|
if (Input.GetKey(KeyCode.E))
|
|
ascent = 1f;
|
|
else if (Input.GetKey(KeyCode.Q))
|
|
ascent = -1f;
|
|
|
|
// Compute movement relative to current camera orientation
|
|
Vector3 movement = new Vector3(horizontal, ascent, vertical);
|
|
virtualCamera.transform.position += virtualCamera.transform.rotation * movement * freeRoamSpeed * Time.deltaTime;
|
|
}
|
|
|
|
private void HandleEdgePanning()
|
|
{
|
|
if (targetCharacter == null) return;
|
|
|
|
Vector3 newOffset = panningOffset;
|
|
Vector3 mousePos = Input.mousePosition;
|
|
|
|
float screenWidth = Screen.width;
|
|
float screenHeight = Screen.height;
|
|
|
|
// Center Reset Radius (3% of screen size)
|
|
float resetRadiusX = screenWidth * (resetRadiusPercentage / 100f);
|
|
float resetRadiusY = screenHeight * (resetRadiusPercentage / 100f);
|
|
Vector2 screenCenter = new Vector2(screenWidth / 2, screenHeight / 2);
|
|
|
|
bool isNearCenter = Mathf.Abs(mousePos.x - screenCenter.x) <= resetRadiusX &&
|
|
Mathf.Abs(mousePos.y - screenCenter.y) <= resetRadiusY;
|
|
|
|
bool isHoveringTarget = IsCursorOverTargetCharacter();
|
|
|
|
// If the cursor touches the center or target even for a moment, trigger reset
|
|
if (isNearCenter || isHoveringTarget)
|
|
{
|
|
shouldReset = true;
|
|
}
|
|
|
|
// When resetting, smoothly return to the default position
|
|
if (shouldReset && isPanning)
|
|
{
|
|
cameraOffset.m_Offset = Vector3.Lerp(cameraOffset.m_Offset, defaultOffset, Time.deltaTime * resetSpeed);
|
|
|
|
// Once the camera gets close enough to default, stop panning and reset the flag
|
|
if (Vector3.Distance(cameraOffset.m_Offset, defaultOffset) < 0.01f)
|
|
{
|
|
isPanning = false;
|
|
panningOffset = defaultOffset;
|
|
shouldReset = false;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// If the cursor is near the edges, adjust the offset (within limits)
|
|
if (mousePos.x <= edgeThreshold) newOffset.x = Mathf.Max(defaultOffset.x - panLimit.x, newOffset.x - panSpeed);
|
|
if (mousePos.x >= screenWidth - edgeThreshold) newOffset.x = Mathf.Min(defaultOffset.x + panLimit.x, newOffset.x + panSpeed);
|
|
if (mousePos.y <= edgeThreshold) newOffset.y = Mathf.Max(defaultOffset.y - panLimit.y, newOffset.y - panSpeed);
|
|
if (mousePos.y >= screenHeight - edgeThreshold) newOffset.y = Mathf.Min(defaultOffset.y + panLimit.y, newOffset.y + panSpeed);
|
|
|
|
if (newOffset != panningOffset)
|
|
{
|
|
isPanning = true;
|
|
}
|
|
|
|
cameraOffset.m_Offset = Vector3.Lerp(cameraOffset.m_Offset, newOffset, Time.deltaTime * 3f);
|
|
panningOffset = cameraOffset.m_Offset;
|
|
}
|
|
|
|
private bool IsCursorOverTargetCharacter()
|
|
{
|
|
if (targetCharacter == null) return false;
|
|
|
|
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
|
|
if (Physics.Raycast(ray, out RaycastHit hit, Mathf.Infinity))
|
|
{
|
|
return hit.transform == targetCharacter;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public void ShakeCamera(float duration = 0.3f, float magnitude = 0.2f)
|
|
{
|
|
if (shakeCoroutine != null)
|
|
{
|
|
StopCoroutine(shakeCoroutine);
|
|
}
|
|
shakeCoroutine = StartCoroutine(ShakeRoutine(duration, magnitude));
|
|
}
|
|
|
|
private IEnumerator ShakeRoutine(float duration, float magnitude)
|
|
{
|
|
float elapsed = 0f;
|
|
Vector3 originalOffset = defaultOffset;
|
|
|
|
while (elapsed < duration)
|
|
{
|
|
float x = Random.Range(-1f, 1f) * magnitude;
|
|
float y = Random.Range(-1f, 1f) * magnitude;
|
|
float z = Random.Range(-1f, 1f) * magnitude;
|
|
|
|
cameraOffset.m_Offset = originalOffset + new Vector3(x, y, z);
|
|
elapsed += Time.deltaTime;
|
|
yield return null;
|
|
}
|
|
|
|
cameraOffset.m_Offset = originalOffset; // Reset to default offset
|
|
}
|
|
}
|