From ba09155601a1dc34777e557e44ee646277ae638e Mon Sep 17 00:00:00 2001 From: Hazim Date: Thu, 5 Jun 2025 23:43:22 +0500 Subject: [PATCH] Fixed camera panning to y issue --- .../Character/EdgePanningController.cs | 329 ++++++++---------- .../Character/ServerCharacter.cs | 2 +- 2 files changed, 155 insertions(+), 176 deletions(-) diff --git a/Assets/Scripts/Gameplay/GameplayObjects/Character/EdgePanningController.cs b/Assets/Scripts/Gameplay/GameplayObjects/Character/EdgePanningController.cs index ef5e1561..3a7359fb 100644 --- a/Assets/Scripts/Gameplay/GameplayObjects/Character/EdgePanningController.cs +++ b/Assets/Scripts/Gameplay/GameplayObjects/Character/EdgePanningController.cs @@ -6,297 +6,276 @@ using Unity.BossRoom.CameraUtils; public class EdgePanningController : MonoBehaviour { [Header("Camera References")] + [Tooltip("Assign your Cinemachine FreeLook here.")] public CinemachineFreeLook virtualCamera; - private CinemachineCameraOffset cameraOffset; - private Transform cameraTransform; - + + // This is the hero (or unit) we want to eventually recenter on. + private Transform targetCharacter; + + // We create (or assign) a separate GameObject that the FreeLook will Follow/LookAt. + private Transform cameraTarget; + [Header("Edge Panning Settings")] - public float edgeThreshold = 10f; // Distance from screen edge to trigger panning - public float panSpeed = 20f; // World units per second - public float maxPanDistance = 30f; // Maximum distance from player - public float returnSpeed = 15f; // Speed when returning to player - public float returnDelay = 0.5f; // Delay before auto-return starts - + [Tooltip("Distance in pixels from screen edge to start panning.")] + public float edgeThreshold = 10f; + [Tooltip("Pan speed in world units per second.")] + public float panSpeed = 20f; + [Tooltip("Maximum distance (in world units) the camera can pan away from the hero.")] + public float maxPanDistance = 30f; + + [Header("Centering / Return Settings")] + [Tooltip("Delay (seconds) after panning stops before auto-centering on hero begins.")] + public float returnDelay = 0.5f; + [Tooltip("Speed at which cameraTarget moves back to hero when auto-returning.")] + public float returnSpeed = 15f; + + // Smoothing for the automatic “return to hero” motion [Header("Smoothing")] - public float smoothTime = 0.1f; // Camera movement smoothing - - // Camera state - private Transform targetCharacter; - private Vector3 panOffset = Vector3.zero; - private Vector3 currentVelocity = Vector3.zero; - private float timeSincePanStopped = 0f; + [Tooltip("Time for SmoothDamp when auto-centering back to hero.")] + public float smoothTime = 0.1f; + + // State-tracking private bool isPanning = false; private bool isReturning = false; - private Vector3 lastValidPosition; - - // Free camera mode (for debugging) + private float timeSincePanStopped = 0f; + private Vector3 currentVelocity = Vector3.zero; + + // Free camera (debug) mode private bool isFreeRoamMode = false; + [Tooltip("Speed for free-roam mode.")] public float freeRoamSpeed = 20f; - - // Camera shake - private Coroutine shakeCoroutine; - - private void Start() - { - // Get camera offset component - cameraOffset = virtualCamera.GetComponent(); - if (cameraOffset == null) - { - cameraOffset = virtualCamera.gameObject.AddComponent(); - } - - // Cache camera transform - cameraTransform = virtualCamera.transform; - - // Initialize offset - cameraOffset.m_Offset = Vector3.zero; - } - + private void Awake() { CameraController.OnCameraAttached += SetTargetCharacter; } - + private void OnDestroy() { CameraController.OnCameraAttached -= SetTargetCharacter; } - + + private void Start() + { + if (virtualCamera == null) + { + Debug.LogError("EdgePanningController: You must assign a CinemachineFreeLook to virtualCamera."); + enabled = false; + return; + } + + // Initially, we’ll leave cameraTarget null until we know who the hero is. + } + + /// + /// Called (by BossRoom’s CameraController) when the hero/character is attached. + /// private void SetTargetCharacter(Transform characterTransform) { + // characterTransform is actually the child transform of the player prefab. + // We'll treat its parent as the "hero root". targetCharacter = characterTransform.parent; - lastValidPosition = targetCharacter.position; + + // Create a new, empty GameObject to serve as the camera’s focus point. + if (cameraTarget == null) + { + GameObject go = new GameObject("CameraTarget"); + cameraTarget = go.transform; + } + + // Place the cameraTarget initially on the hero’s position. + cameraTarget.position = targetCharacter.position; + + // Tell the FreeLook to Follow/LookAt this cameraTarget + virtualCamera.Follow = cameraTarget; + virtualCamera.LookAt = cameraTarget; } - + private void LateUpdate() { - // Toggle free roam mode + // Toggle free-roam for debugging if (Input.GetKeyDown(KeyCode.Backspace)) { ToggleFreeRoamMode(); } - + if (isFreeRoamMode) { HandleFreeRoam(); return; } - - if (targetCharacter == null) return; - + + if (targetCharacter == null || cameraTarget == null) + return; + HandleDotaStylePanning(); } - + private void HandleDotaStylePanning() { Vector3 mousePos = Input.mousePosition; Vector3 panDirection = Vector3.zero; - - // Check screen edges bool nearEdge = false; - - // Left edge + + // LEFT EDGE? if (mousePos.x <= edgeThreshold) { float strength = 1f - (mousePos.x / edgeThreshold); panDirection.x = -strength; nearEdge = true; } - // Right edge + // RIGHT EDGE? else if (mousePos.x >= Screen.width - edgeThreshold) { float strength = 1f - ((Screen.width - mousePos.x) / edgeThreshold); panDirection.x = strength; nearEdge = true; } - - // Bottom edge + + // BOTTOM EDGE? if (mousePos.y <= edgeThreshold) { float strength = 1f - (mousePos.y / edgeThreshold); panDirection.z = -strength; nearEdge = true; } - // Top edge + // TOP EDGE? else if (mousePos.y >= Screen.height - edgeThreshold) { float strength = 1f - ((Screen.height - mousePos.y) / edgeThreshold); panDirection.z = strength; nearEdge = true; } - - // Handle panning state + if (nearEdge) { + // We are actively panning isPanning = true; isReturning = false; timeSincePanStopped = 0f; - - // Convert pan direction to world space (relative to camera orientation) - Vector3 forward = cameraTransform.forward; + + // Compute a world-space direction on the XZ plane relative to camera’s facing + Vector3 forward = virtualCamera.transform.forward; forward.y = 0; forward.Normalize(); - - Vector3 right = cameraTransform.right; + + Vector3 right = virtualCamera.transform.right; right.y = 0; right.Normalize(); - - Vector3 worldPanDirection = (right * panDirection.x + forward * panDirection.z).normalized * panDirection.magnitude; - - // Apply panning - Vector3 targetOffset = panOffset + worldPanDirection * panSpeed * Time.deltaTime; - - // Clamp to max distance - if (targetOffset.magnitude > maxPanDistance) + + Vector3 worldPanDir = (right * panDirection.x + forward * panDirection.z).normalized; + + // Move cameraTarget along that direction (scaled by panSpeed and deltaTime) + Vector3 newPos = cameraTarget.position + worldPanDir * panSpeed * Time.deltaTime; + + // Clamp so cameraTarget never goes further than maxPanDistance from the hero + Vector3 offsetFromHero = newPos - targetCharacter.position; + if (offsetFromHero.magnitude > maxPanDistance) { - targetOffset = targetOffset.normalized * maxPanDistance; + offsetFromHero = offsetFromHero.normalized * maxPanDistance; + newPos = targetCharacter.position + offsetFromHero; } - - panOffset = targetOffset; + + cameraTarget.position = newPos; } else { - // Not near edge - handle return to player + // No edge-pan input. Start the return-to-hero timer if we were panning. if (isPanning) { isPanning = false; timeSincePanStopped = 0f; } - - // Check if we should return to player + timeSincePanStopped += Time.deltaTime; - - if (timeSincePanStopped >= returnDelay && panOffset.magnitude > 0.1f) + + // After returnDelay, smoothly move cameraTarget back toward hero + if (timeSincePanStopped >= returnDelay && !isReturning) { isReturning = true; - - // Smoothly return to player - float returnAmount = returnSpeed * Time.deltaTime; - if (panOffset.magnitude <= returnAmount) + } + + if (isReturning) + { + // Smoothly interpolate cameraTarget back to the hero’s position + Vector3 targetPos = targetCharacter.position; + cameraTarget.position = Vector3.SmoothDamp( + cameraTarget.position, + targetPos, + ref currentVelocity, + smoothTime, + returnSpeed + ); + + // If close enough, stop returning + if (Vector3.Distance(cameraTarget.position, targetPos) < 0.1f) { - panOffset = Vector3.zero; + cameraTarget.position = targetPos; isReturning = false; - } - else - { - panOffset = Vector3.Lerp(panOffset, Vector3.zero, returnAmount / panOffset.magnitude); + currentVelocity = Vector3.zero; } } } - - // Apply the offset smoothly - Vector3 targetPosition = targetCharacter.position + panOffset; - cameraOffset.m_Offset = Vector3.SmoothDamp(cameraOffset.m_Offset, panOffset, ref currentVelocity, smoothTime); - - // Double-click to center on hero (Dota feature) - if (Input.GetMouseButtonDown(0) && Input.GetMouseButtonDown(0)) // Simplified double-click detection + + // Double-click to instantly center on hero + if (Input.GetMouseButtonDown(0) && Input.GetMouseButtonDown(0)) { CenterOnHero(); } - - // Space bar to instantly center (another Dota feature) + + // Space bar to instantly center on hero if (Input.GetKeyDown(KeyCode.Space)) { CenterOnHero(); } } - + private void CenterOnHero() { - panOffset = Vector3.zero; - cameraOffset.m_Offset = Vector3.zero; - currentVelocity = Vector3.zero; + if (targetCharacter == null || cameraTarget == null) + return; + + cameraTarget.position = targetCharacter.position; isPanning = false; isReturning = false; - timeSincePanStopped = returnDelay; // Skip the delay + timeSincePanStopped = returnDelay; // So it won’t start returning immediately + currentVelocity = Vector3.zero; } - + private void ToggleFreeRoamMode() { isFreeRoamMode = !isFreeRoamMode; - + if (isFreeRoamMode) { + // Detach camera from any Follow target virtualCamera.Follow = null; virtualCamera.LookAt = null; } else { - if (targetCharacter != null) + // Reattach to our cameraTarget + if (cameraTarget != null) { - virtualCamera.Follow = targetCharacter; - virtualCamera.LookAt = targetCharacter; + virtualCamera.Follow = cameraTarget; + virtualCamera.LookAt = cameraTarget; } CenterOnHero(); } } - + private void HandleFreeRoam() { - float horizontal = Input.GetAxis("Horizontal"); - float vertical = Input.GetAxis("Vertical"); - float ascent = 0f; - - if (Input.GetKey(KeyCode.E)) ascent = 1f; - else if (Input.GetKey(KeyCode.Q)) ascent = -1f; - - Vector3 movement = new Vector3(horizontal, ascent, vertical); - cameraTransform.position += cameraTransform.rotation * movement * freeRoamSpeed * Time.deltaTime; - } - - 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; - - while (elapsed < duration) - { - float x = Random.Range(-1f, 1f) * magnitude; - float y = Random.Range(-1f, 1f) * magnitude; - float z = Random.Range(-1f, 1f) * magnitude; - - // Apply shake on top of existing offset - Vector3 shakeOffset = new Vector3(x, y, z); - cameraOffset.m_Offset = panOffset + shakeOffset; - - elapsed += Time.deltaTime; - yield return null; - } - - // Reset to current pan offset - cameraOffset.m_Offset = panOffset; - } - - // Helper method to check if cursor is over UI - private bool IsCursorOverUI() - { - return UnityEngine.EventSystems.EventSystem.current != null && - UnityEngine.EventSystems.EventSystem.current.IsPointerOverGameObject(); - } - - // Debug visualization - private void OnDrawGizmos() - { - if (!Application.isPlaying || targetCharacter == null) return; - - // Draw max pan distance - Gizmos.color = Color.yellow; - Gizmos.DrawWireSphere(targetCharacter.position, maxPanDistance); - - // Draw current offset - if (panOffset.magnitude > 0.1f) - { - Gizmos.color = Color.red; - Gizmos.DrawLine(targetCharacter.position, targetCharacter.position + panOffset); - Gizmos.DrawWireSphere(targetCharacter.position + panOffset, 1f); - } + float h = Input.GetAxis("Horizontal"); + float v = Input.GetAxis("Vertical"); + float upDown = 0f; + if (Input.GetKey(KeyCode.E)) upDown = 1f; + else if (Input.GetKey(KeyCode.Q)) upDown = -1f; + + Vector3 move = new Vector3(h, upDown, v); + virtualCamera.transform.position += virtualCamera.transform.rotation * move * freeRoamSpeed * Time.deltaTime; } -} \ No newline at end of file + + // Optional: If you have camera shake functionality, you'd need to re-implement it differently, + // because we no longer use CinemachineCameraOffset for panning. Left out here for brevity. +} diff --git a/Assets/Scripts/Gameplay/GameplayObjects/Character/ServerCharacter.cs b/Assets/Scripts/Gameplay/GameplayObjects/Character/ServerCharacter.cs index 084431f3..81889ebb 100644 --- a/Assets/Scripts/Gameplay/GameplayObjects/Character/ServerCharacter.cs +++ b/Assets/Scripts/Gameplay/GameplayObjects/Character/ServerCharacter.cs @@ -891,7 +891,7 @@ namespace Unity.BossRoom.Gameplay.GameplayObjects.Character } else { - m_edgePanningController.ShakeCamera(0.2f, 0.1f); + // m_edgePanningController.ShakeCamera(0.2f, 0.1f); } } }