|
|
@ -6,297 +6,276 @@ using Unity.BossRoom.CameraUtils;
|
|
|
|
public class EdgePanningController : MonoBehaviour
|
|
|
|
public class EdgePanningController : MonoBehaviour
|
|
|
|
{
|
|
|
|
{
|
|
|
|
[Header("Camera References")]
|
|
|
|
[Header("Camera References")]
|
|
|
|
|
|
|
|
[Tooltip("Assign your Cinemachine FreeLook here.")]
|
|
|
|
public CinemachineFreeLook virtualCamera;
|
|
|
|
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")]
|
|
|
|
[Header("Edge Panning Settings")]
|
|
|
|
public float edgeThreshold = 10f; // Distance from screen edge to trigger panning
|
|
|
|
[Tooltip("Distance in pixels from screen edge to start panning.")]
|
|
|
|
public float panSpeed = 20f; // World units per second
|
|
|
|
public float edgeThreshold = 10f;
|
|
|
|
public float maxPanDistance = 30f; // Maximum distance from player
|
|
|
|
[Tooltip("Pan speed in world units per second.")]
|
|
|
|
public float returnSpeed = 15f; // Speed when returning to player
|
|
|
|
public float panSpeed = 20f;
|
|
|
|
public float returnDelay = 0.5f; // Delay before auto-return starts
|
|
|
|
[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")]
|
|
|
|
[Header("Smoothing")]
|
|
|
|
public float smoothTime = 0.1f; // Camera movement smoothing
|
|
|
|
[Tooltip("Time for SmoothDamp when auto-centering back to hero.")]
|
|
|
|
|
|
|
|
public float smoothTime = 0.1f;
|
|
|
|
// Camera state
|
|
|
|
|
|
|
|
private Transform targetCharacter;
|
|
|
|
// State-tracking
|
|
|
|
private Vector3 panOffset = Vector3.zero;
|
|
|
|
|
|
|
|
private Vector3 currentVelocity = Vector3.zero;
|
|
|
|
|
|
|
|
private float timeSincePanStopped = 0f;
|
|
|
|
|
|
|
|
private bool isPanning = false;
|
|
|
|
private bool isPanning = false;
|
|
|
|
private bool isReturning = false;
|
|
|
|
private bool isReturning = false;
|
|
|
|
private Vector3 lastValidPosition;
|
|
|
|
private float timeSincePanStopped = 0f;
|
|
|
|
|
|
|
|
private Vector3 currentVelocity = Vector3.zero;
|
|
|
|
// Free camera mode (for debugging)
|
|
|
|
|
|
|
|
|
|
|
|
// Free camera (debug) mode
|
|
|
|
private bool isFreeRoamMode = false;
|
|
|
|
private bool isFreeRoamMode = false;
|
|
|
|
|
|
|
|
[Tooltip("Speed for free-roam mode.")]
|
|
|
|
public float freeRoamSpeed = 20f;
|
|
|
|
public float freeRoamSpeed = 20f;
|
|
|
|
|
|
|
|
|
|
|
|
// Camera shake
|
|
|
|
|
|
|
|
private Coroutine shakeCoroutine;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private void Start()
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
// Get camera offset component
|
|
|
|
|
|
|
|
cameraOffset = virtualCamera.GetComponent<CinemachineCameraOffset>();
|
|
|
|
|
|
|
|
if (cameraOffset == null)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
cameraOffset = virtualCamera.gameObject.AddComponent<CinemachineCameraOffset>();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Cache camera transform
|
|
|
|
|
|
|
|
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 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.
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// Called (by BossRoom’s CameraController) when the hero/character is attached.
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
private void SetTargetCharacter(Transform characterTransform)
|
|
|
|
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;
|
|
|
|
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()
|
|
|
|
private void LateUpdate()
|
|
|
|
{
|
|
|
|
{
|
|
|
|
// Toggle free roam mode
|
|
|
|
// Toggle free-roam for debugging
|
|
|
|
if (Input.GetKeyDown(KeyCode.Backspace))
|
|
|
|
if (Input.GetKeyDown(KeyCode.Backspace))
|
|
|
|
{
|
|
|
|
{
|
|
|
|
ToggleFreeRoamMode();
|
|
|
|
ToggleFreeRoamMode();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (isFreeRoamMode)
|
|
|
|
if (isFreeRoamMode)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
HandleFreeRoam();
|
|
|
|
HandleFreeRoam();
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (targetCharacter == null) return;
|
|
|
|
if (targetCharacter == null || cameraTarget == null)
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
HandleDotaStylePanning();
|
|
|
|
HandleDotaStylePanning();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void HandleDotaStylePanning()
|
|
|
|
private void HandleDotaStylePanning()
|
|
|
|
{
|
|
|
|
{
|
|
|
|
Vector3 mousePos = Input.mousePosition;
|
|
|
|
Vector3 mousePos = Input.mousePosition;
|
|
|
|
Vector3 panDirection = Vector3.zero;
|
|
|
|
Vector3 panDirection = Vector3.zero;
|
|
|
|
|
|
|
|
|
|
|
|
// Check screen edges
|
|
|
|
|
|
|
|
bool nearEdge = false;
|
|
|
|
bool nearEdge = false;
|
|
|
|
|
|
|
|
|
|
|
|
// Left edge
|
|
|
|
// LEFT EDGE?
|
|
|
|
if (mousePos.x <= edgeThreshold)
|
|
|
|
if (mousePos.x <= edgeThreshold)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
float strength = 1f - (mousePos.x / edgeThreshold);
|
|
|
|
float strength = 1f - (mousePos.x / edgeThreshold);
|
|
|
|
panDirection.x = -strength;
|
|
|
|
panDirection.x = -strength;
|
|
|
|
nearEdge = true;
|
|
|
|
nearEdge = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Right edge
|
|
|
|
// RIGHT EDGE?
|
|
|
|
else if (mousePos.x >= Screen.width - edgeThreshold)
|
|
|
|
else if (mousePos.x >= Screen.width - edgeThreshold)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
float strength = 1f - ((Screen.width - mousePos.x) / edgeThreshold);
|
|
|
|
float strength = 1f - ((Screen.width - mousePos.x) / edgeThreshold);
|
|
|
|
panDirection.x = strength;
|
|
|
|
panDirection.x = strength;
|
|
|
|
nearEdge = true;
|
|
|
|
nearEdge = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Bottom edge
|
|
|
|
// BOTTOM EDGE?
|
|
|
|
if (mousePos.y <= edgeThreshold)
|
|
|
|
if (mousePos.y <= edgeThreshold)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
float strength = 1f - (mousePos.y / edgeThreshold);
|
|
|
|
float strength = 1f - (mousePos.y / edgeThreshold);
|
|
|
|
panDirection.z = -strength;
|
|
|
|
panDirection.z = -strength;
|
|
|
|
nearEdge = true;
|
|
|
|
nearEdge = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Top edge
|
|
|
|
// TOP EDGE?
|
|
|
|
else if (mousePos.y >= Screen.height - edgeThreshold)
|
|
|
|
else if (mousePos.y >= Screen.height - edgeThreshold)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
float strength = 1f - ((Screen.height - mousePos.y) / edgeThreshold);
|
|
|
|
float strength = 1f - ((Screen.height - mousePos.y) / edgeThreshold);
|
|
|
|
panDirection.z = strength;
|
|
|
|
panDirection.z = strength;
|
|
|
|
nearEdge = true;
|
|
|
|
nearEdge = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Handle panning state
|
|
|
|
|
|
|
|
if (nearEdge)
|
|
|
|
if (nearEdge)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
|
|
|
|
// We are actively panning
|
|
|
|
isPanning = true;
|
|
|
|
isPanning = true;
|
|
|
|
isReturning = false;
|
|
|
|
isReturning = false;
|
|
|
|
timeSincePanStopped = 0f;
|
|
|
|
timeSincePanStopped = 0f;
|
|
|
|
|
|
|
|
|
|
|
|
// Convert pan direction to world space (relative to camera orientation)
|
|
|
|
// Compute a world-space direction on the XZ plane relative to camera’s facing
|
|
|
|
Vector3 forward = cameraTransform.forward;
|
|
|
|
Vector3 forward = virtualCamera.transform.forward;
|
|
|
|
forward.y = 0;
|
|
|
|
forward.y = 0;
|
|
|
|
forward.Normalize();
|
|
|
|
forward.Normalize();
|
|
|
|
|
|
|
|
|
|
|
|
Vector3 right = cameraTransform.right;
|
|
|
|
Vector3 right = virtualCamera.transform.right;
|
|
|
|
right.y = 0;
|
|
|
|
right.y = 0;
|
|
|
|
right.Normalize();
|
|
|
|
right.Normalize();
|
|
|
|
|
|
|
|
|
|
|
|
Vector3 worldPanDirection = (right * panDirection.x + forward * panDirection.z).normalized * panDirection.magnitude;
|
|
|
|
Vector3 worldPanDir = (right * panDirection.x + forward * panDirection.z).normalized;
|
|
|
|
|
|
|
|
|
|
|
|
// Apply panning
|
|
|
|
// Move cameraTarget along that direction (scaled by panSpeed and deltaTime)
|
|
|
|
Vector3 targetOffset = panOffset + worldPanDirection * panSpeed * Time.deltaTime;
|
|
|
|
Vector3 newPos = cameraTarget.position + worldPanDir * panSpeed * Time.deltaTime;
|
|
|
|
|
|
|
|
|
|
|
|
// Clamp to max distance
|
|
|
|
// Clamp so cameraTarget never goes further than maxPanDistance from the hero
|
|
|
|
if (targetOffset.magnitude > maxPanDistance)
|
|
|
|
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
|
|
|
|
else
|
|
|
|
{
|
|
|
|
{
|
|
|
|
// Not near edge - handle return to player
|
|
|
|
// No edge-pan input. Start the return-to-hero timer if we were panning.
|
|
|
|
if (isPanning)
|
|
|
|
if (isPanning)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
isPanning = false;
|
|
|
|
isPanning = false;
|
|
|
|
timeSincePanStopped = 0f;
|
|
|
|
timeSincePanStopped = 0f;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Check if we should return to player
|
|
|
|
|
|
|
|
timeSincePanStopped += Time.deltaTime;
|
|
|
|
timeSincePanStopped += Time.deltaTime;
|
|
|
|
|
|
|
|
|
|
|
|
if (timeSincePanStopped >= returnDelay && panOffset.magnitude > 0.1f)
|
|
|
|
// After returnDelay, smoothly move cameraTarget back toward hero
|
|
|
|
|
|
|
|
if (timeSincePanStopped >= returnDelay && !isReturning)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
isReturning = true;
|
|
|
|
isReturning = true;
|
|
|
|
|
|
|
|
}
|
|
|
|
// Smoothly return to player
|
|
|
|
|
|
|
|
float returnAmount = returnSpeed * Time.deltaTime;
|
|
|
|
if (isReturning)
|
|
|
|
if (panOffset.magnitude <= returnAmount)
|
|
|
|
{
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
isReturning = false;
|
|
|
|
}
|
|
|
|
currentVelocity = Vector3.zero;
|
|
|
|
else
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
panOffset = Vector3.Lerp(panOffset, Vector3.zero, returnAmount / panOffset.magnitude);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Apply the offset smoothly
|
|
|
|
// Double-click to instantly center on hero
|
|
|
|
Vector3 targetPosition = targetCharacter.position + panOffset;
|
|
|
|
if (Input.GetMouseButtonDown(0) && Input.GetMouseButtonDown(0))
|
|
|
|
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
|
|
|
|
|
|
|
|
{
|
|
|
|
{
|
|
|
|
CenterOnHero();
|
|
|
|
CenterOnHero();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Space bar to instantly center (another Dota feature)
|
|
|
|
// Space bar to instantly center on hero
|
|
|
|
if (Input.GetKeyDown(KeyCode.Space))
|
|
|
|
if (Input.GetKeyDown(KeyCode.Space))
|
|
|
|
{
|
|
|
|
{
|
|
|
|
CenterOnHero();
|
|
|
|
CenterOnHero();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void CenterOnHero()
|
|
|
|
private void CenterOnHero()
|
|
|
|
{
|
|
|
|
{
|
|
|
|
panOffset = Vector3.zero;
|
|
|
|
if (targetCharacter == null || cameraTarget == null)
|
|
|
|
cameraOffset.m_Offset = Vector3.zero;
|
|
|
|
return;
|
|
|
|
currentVelocity = Vector3.zero;
|
|
|
|
|
|
|
|
|
|
|
|
cameraTarget.position = targetCharacter.position;
|
|
|
|
isPanning = false;
|
|
|
|
isPanning = false;
|
|
|
|
isReturning = false;
|
|
|
|
isReturning = false;
|
|
|
|
timeSincePanStopped = returnDelay; // Skip the delay
|
|
|
|
timeSincePanStopped = returnDelay; // So it won’t start returning immediately
|
|
|
|
|
|
|
|
currentVelocity = Vector3.zero;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ToggleFreeRoamMode()
|
|
|
|
private void ToggleFreeRoamMode()
|
|
|
|
{
|
|
|
|
{
|
|
|
|
isFreeRoamMode = !isFreeRoamMode;
|
|
|
|
isFreeRoamMode = !isFreeRoamMode;
|
|
|
|
|
|
|
|
|
|
|
|
if (isFreeRoamMode)
|
|
|
|
if (isFreeRoamMode)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
|
|
|
|
// Detach camera from any Follow target
|
|
|
|
virtualCamera.Follow = null;
|
|
|
|
virtualCamera.Follow = null;
|
|
|
|
virtualCamera.LookAt = null;
|
|
|
|
virtualCamera.LookAt = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
else
|
|
|
|
{
|
|
|
|
{
|
|
|
|
if (targetCharacter != null)
|
|
|
|
// Reattach to our cameraTarget
|
|
|
|
|
|
|
|
if (cameraTarget != null)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
virtualCamera.Follow = targetCharacter;
|
|
|
|
virtualCamera.Follow = cameraTarget;
|
|
|
|
virtualCamera.LookAt = targetCharacter;
|
|
|
|
virtualCamera.LookAt = cameraTarget;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
CenterOnHero();
|
|
|
|
CenterOnHero();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void HandleFreeRoam()
|
|
|
|
private void HandleFreeRoam()
|
|
|
|
{
|
|
|
|
{
|
|
|
|
float horizontal = Input.GetAxis("Horizontal");
|
|
|
|
float h = Input.GetAxis("Horizontal");
|
|
|
|
float vertical = Input.GetAxis("Vertical");
|
|
|
|
float v = Input.GetAxis("Vertical");
|
|
|
|
float ascent = 0f;
|
|
|
|
float upDown = 0f;
|
|
|
|
|
|
|
|
if (Input.GetKey(KeyCode.E)) upDown = 1f;
|
|
|
|
if (Input.GetKey(KeyCode.E)) ascent = 1f;
|
|
|
|
else if (Input.GetKey(KeyCode.Q)) upDown = -1f;
|
|
|
|
else if (Input.GetKey(KeyCode.Q)) ascent = -1f;
|
|
|
|
|
|
|
|
|
|
|
|
Vector3 move = new Vector3(h, upDown, v);
|
|
|
|
Vector3 movement = new Vector3(horizontal, ascent, vertical);
|
|
|
|
virtualCamera.transform.position += virtualCamera.transform.rotation * move * freeRoamSpeed * Time.deltaTime;
|
|
|
|
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);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 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.
|
|
|
|
|
|
|
|
}
|
|
|
|