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(); 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(); } /// /// Toggles the free roam mode on/off. /// 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; } } /// /// 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. /// 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 } }