Update EdgePanningController.cs

dev-hazim
Hazim 4 weeks ago
parent 6590654c59
commit 1caef53f9d

@ -5,198 +5,245 @@ using Unity.BossRoom.CameraUtils;
public class EdgePanningController : MonoBehaviour public class EdgePanningController : MonoBehaviour
{ {
[Header("Camera References")]
public CinemachineFreeLook virtualCamera; public CinemachineFreeLook virtualCamera;
private CinemachineCameraOffset cameraOffset; private CinemachineCameraOffset cameraOffset;
private Transform cameraTransform;
private Coroutine shakeCoroutine;
[Header("Edge Panning Settings")]
public float edgeThreshold = 50f; // Edge detection distance from screen edges public float edgeThreshold = 10f; // Distance from screen edge to trigger panning
public float resetRadiusPercentage = 3f; // Center reset radius in percentage of screen public float panSpeed = 20f; // World units per second
public float panSpeed = 0.1f; // How much to offset per frame public float maxPanDistance = 30f; // Maximum distance from player
public float resetSpeed = 2f; // Speed of resetting panning public float returnSpeed = 15f; // Speed when returning to player
public Vector2 panLimit = new Vector2(0.5f, 0.5f); // Max panning limit (x, y) public float returnDelay = 0.5f; // Delay before auto-return starts
// --- Added fields for free roam mode --- [Header("Smoothing")]
public float freeRoamSpeed = 10f; // Speed of free roam movement public float smoothTime = 0.1f; // Camera movement smoothing
private bool isFreeRoamMode = false; // Toggle for free roam mode
private Vector3 savedCameraPosition; // Stores camera position before free roam // Camera state
private Quaternion savedCameraRotation; // Stores camera rotation before free roam private Transform targetCharacter;
private Transform savedFollowTarget; // Stores the original follow target private Vector3 panOffset = Vector3.zero;
private Vector3 currentVelocity = Vector3.zero;
private Vector3 defaultOffset; private float timeSincePanStopped = 0f;
private Vector3 panningOffset;
private bool isPanning = false; private bool isPanning = false;
private bool targetFound = false; private bool isReturning = false;
private bool shouldReset = false; // Flag to trigger reset immediately private Vector3 lastValidPosition;
private Transform targetCharacter; // The character the camera follows // Free camera mode (for debugging)
private bool isFreeRoamMode = false;
public float freeRoamSpeed = 20f;
// Camera shake
private Coroutine shakeCoroutine;
private void Start() private void Start()
{ {
// Get camera offset component
cameraOffset = virtualCamera.GetComponent<CinemachineCameraOffset>(); cameraOffset = virtualCamera.GetComponent<CinemachineCameraOffset>();
if (cameraOffset == null) if (cameraOffset == null)
{ {
Debug.LogError("[EdgePanningController] No CinemachineCameraOffset component found! Add it to the FreeLook Camera."); cameraOffset = virtualCamera.gameObject.AddComponent<CinemachineCameraOffset>();
return;
} }
defaultOffset = cameraOffset.m_Offset; // Cache camera transform
panningOffset = defaultOffset; cameraTransform = virtualCamera.transform;
// Initialize offset
cameraOffset.m_Offset = Vector3.zero;
} }
private void Awake() private void Awake()
{ {
CameraController.OnCameraAttached += SetTargetCharacter; CameraController.OnCameraAttached += SetTargetCharacter;
} }
private void OnDestroy() private void OnDestroy()
{ {
CameraController.OnCameraAttached -= SetTargetCharacter; CameraController.OnCameraAttached -= SetTargetCharacter;
} }
private void SetTargetCharacter(Transform characterTransform) private void SetTargetCharacter(Transform characterTransform)
{ {
targetCharacter = characterTransform.parent; targetCharacter = characterTransform.parent;
targetFound = true; lastValidPosition = targetCharacter.position;
} }
private void Update() private void LateUpdate()
{ {
// Check for backspace key to toggle free roam mode // Toggle free roam mode
if (Input.GetKeyDown(KeyCode.Backspace)) if (Input.GetKeyDown(KeyCode.Backspace))
{ {
ToggleFreeRoamMode(); ToggleFreeRoamMode();
} }
// When in free roam mode, handle free movement and bypass normal edge panning
if (isFreeRoamMode) if (isFreeRoamMode)
{ {
HandleFreeRoam(); HandleFreeRoam();
return; // Skip the rest of Update so the panning logic is not executed return;
} }
// Normal camera behavior (edge panning) if a target is set if (targetCharacter == null) return;
if (targetFound)
HandleEdgePanning(); HandleDotaStylePanning();
} }
/// <summary> private void HandleDotaStylePanning()
/// Toggles the free roam mode on/off.
/// </summary>
private void ToggleFreeRoamMode()
{ {
isFreeRoamMode = !isFreeRoamMode; Vector3 mousePos = Input.mousePosition;
if (isFreeRoamMode) Vector3 panDirection = Vector3.zero;
// Check screen edges
bool nearEdge = false;
// Left edge
if (mousePos.x <= edgeThreshold)
{ {
// Activate free roam: save current camera state and disable following float strength = 1f - (mousePos.x / edgeThreshold);
savedCameraPosition = virtualCamera.transform.position; panDirection.x = -strength;
savedCameraRotation = virtualCamera.transform.rotation; nearEdge = true;
savedFollowTarget = virtualCamera.Follow; // Save the current follow target if any
virtualCamera.Follow = null; // Detach so the camera can be controlled directly
} }
else // Right edge
else if (mousePos.x >= Screen.width - edgeThreshold)
{ {
// Deactivate free roam: restore the previous camera state and resume following target float strength = 1f - ((Screen.width - mousePos.x) / edgeThreshold);
virtualCamera.transform.position = savedCameraPosition; panDirection.x = strength;
virtualCamera.transform.rotation = savedCameraRotation; nearEdge = true;
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;
} }
}
// Bottom edge
/// <summary> if (mousePos.y <= edgeThreshold)
/// 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; float strength = 1f - (mousePos.y / edgeThreshold);
panDirection.z = -strength;
nearEdge = true;
} }
// Top edge
// When resetting, smoothly return to the default position else if (mousePos.y >= Screen.height - edgeThreshold)
if (shouldReset && isPanning)
{ {
cameraOffset.m_Offset = Vector3.Lerp(cameraOffset.m_Offset, defaultOffset, Time.deltaTime * resetSpeed); float strength = 1f - ((Screen.height - mousePos.y) / edgeThreshold);
panDirection.z = strength;
// Once the camera gets close enough to default, stop panning and reset the flag nearEdge = true;
if (Vector3.Distance(cameraOffset.m_Offset, defaultOffset) < 0.01f) }
// Handle panning state
if (nearEdge)
{
isPanning = true;
isReturning = false;
timeSincePanStopped = 0f;
// Convert pan direction to world space (relative to camera orientation)
Vector3 forward = cameraTransform.forward;
forward.y = 0;
forward.Normalize();
Vector3 right = cameraTransform.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)
{
targetOffset = targetOffset.normalized * maxPanDistance;
}
panOffset = targetOffset;
}
else
{
// Not near edge - handle return to player
if (isPanning)
{ {
isPanning = false; isPanning = false;
panningOffset = defaultOffset; timeSincePanStopped = 0f;
shouldReset = false; }
// Check if we should return to player
timeSincePanStopped += Time.deltaTime;
if (timeSincePanStopped >= returnDelay && panOffset.magnitude > 0.1f)
{
isReturning = true;
// Smoothly return to player
float returnAmount = returnSpeed * Time.deltaTime;
if (panOffset.magnitude <= returnAmount)
{
panOffset = Vector3.zero;
isReturning = false;
}
else
{
panOffset = Vector3.Lerp(panOffset, Vector3.zero, returnAmount / panOffset.magnitude);
}
} }
return;
} }
// If the cursor is near the edges, adjust the offset (within limits) // Apply the offset smoothly
if (mousePos.x <= edgeThreshold) newOffset.x = Mathf.Max(defaultOffset.x - panLimit.x, newOffset.x - panSpeed); Vector3 targetPosition = targetCharacter.position + panOffset;
if (mousePos.x >= screenWidth - edgeThreshold) newOffset.x = Mathf.Min(defaultOffset.x + panLimit.x, newOffset.x + panSpeed); cameraOffset.m_Offset = Vector3.SmoothDamp(cameraOffset.m_Offset, panOffset, ref currentVelocity, smoothTime);
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); // Double-click to center on hero (Dota feature)
if (Input.GetMouseButtonDown(0) && Input.GetMouseButtonDown(0)) // Simplified double-click detection
if (newOffset != panningOffset)
{ {
isPanning = true; CenterOnHero();
}
// Space bar to instantly center (another Dota feature)
if (Input.GetKeyDown(KeyCode.Space))
{
CenterOnHero();
} }
cameraOffset.m_Offset = Vector3.Lerp(cameraOffset.m_Offset, newOffset, Time.deltaTime * 3f);
panningOffset = cameraOffset.m_Offset;
} }
private bool IsCursorOverTargetCharacter() private void CenterOnHero()
{ {
if (targetCharacter == null) return false; panOffset = Vector3.zero;
cameraOffset.m_Offset = Vector3.zero;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); currentVelocity = Vector3.zero;
if (Physics.Raycast(ray, out RaycastHit hit, Mathf.Infinity)) isPanning = false;
isReturning = false;
timeSincePanStopped = returnDelay; // Skip the delay
}
private void ToggleFreeRoamMode()
{
isFreeRoamMode = !isFreeRoamMode;
if (isFreeRoamMode)
{ {
return hit.transform == targetCharacter; virtualCamera.Follow = null;
virtualCamera.LookAt = null;
}
else
{
if (targetCharacter != null)
{
virtualCamera.Follow = targetCharacter;
virtualCamera.LookAt = targetCharacter;
}
CenterOnHero();
} }
return false;
} }
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) public void ShakeCamera(float duration = 0.3f, float magnitude = 0.2f)
{ {
if (shakeCoroutine != null) if (shakeCoroutine != null)
@ -205,23 +252,51 @@ public class EdgePanningController : MonoBehaviour
} }
shakeCoroutine = StartCoroutine(ShakeRoutine(duration, magnitude)); shakeCoroutine = StartCoroutine(ShakeRoutine(duration, magnitude));
} }
private IEnumerator ShakeRoutine(float duration, float magnitude) private IEnumerator ShakeRoutine(float duration, float magnitude)
{ {
float elapsed = 0f; float elapsed = 0f;
Vector3 originalOffset = defaultOffset;
while (elapsed < duration) while (elapsed < duration)
{ {
float x = Random.Range(-1f, 1f) * magnitude; float x = Random.Range(-1f, 1f) * magnitude;
float y = Random.Range(-1f, 1f) * magnitude; float y = Random.Range(-1f, 1f) * magnitude;
float z = Random.Range(-1f, 1f) * magnitude; float z = Random.Range(-1f, 1f) * magnitude;
cameraOffset.m_Offset = originalOffset + new Vector3(x, y, z); // Apply shake on top of existing offset
Vector3 shakeOffset = new Vector3(x, y, z);
cameraOffset.m_Offset = panOffset + shakeOffset;
elapsed += Time.deltaTime; elapsed += Time.deltaTime;
yield return null; yield return null;
} }
cameraOffset.m_Offset = originalOffset; // Reset to default offset // 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);
}
} }
} }
Loading…
Cancel
Save