Snapshot before project reorganization
This commit is contained in:
@@ -9,6 +9,9 @@ namespace Unheimlich.Gameplay.CameraControl
|
||||
/// <summary>
|
||||
/// Handles camera panning and player/camera alignment to targets.
|
||||
/// Uses CameraPanSettings ScriptableObject for configuration.
|
||||
/// Supports two modes:
|
||||
/// - Pan: Smooth panning with edge detection (for object inspection)
|
||||
/// - FreeLook: Direct mouse/stick control with angle clamping (for detective board)
|
||||
/// </summary>
|
||||
public class CameraPanController : MonoBehaviour
|
||||
{
|
||||
@@ -16,8 +19,10 @@ namespace Unheimlich.Gameplay.CameraControl
|
||||
[SerializeField] private UnityEngine.Camera _camera;
|
||||
[SerializeField] private Transform _playerTransform;
|
||||
|
||||
[Header("Settings")]
|
||||
[SerializeField] private CameraPanSettings _settings;
|
||||
[Header("Default Settings")]
|
||||
[SerializeField] private CameraPanSettings _defaultSettings;
|
||||
|
||||
private CameraPanSettings _settings;
|
||||
|
||||
private Vector2 _originalCameraRotation;
|
||||
private Vector3 _originalCameraLocalPosition;
|
||||
@@ -28,7 +33,17 @@ namespace Unheimlich.Gameplay.CameraControl
|
||||
private Coroutine _returnCoroutine;
|
||||
private float _timeSinceLastInput;
|
||||
|
||||
private float _targetYaw;
|
||||
private float _targetPitch;
|
||||
private float _currentYaw;
|
||||
private float _currentPitch;
|
||||
private float _lerpSpeed;
|
||||
private bool _needsUpdate;
|
||||
private CameraPanMode _mode = CameraPanMode.Pan;
|
||||
private Vector2 _lastMousePosition;
|
||||
|
||||
public bool IsPanning { get; private set; }
|
||||
public CameraPanMode Mode => _mode;
|
||||
|
||||
public CameraPanSettings Settings
|
||||
{
|
||||
@@ -52,10 +67,39 @@ namespace Unheimlich.Gameplay.CameraControl
|
||||
|
||||
_originalPlayerPosition = _playerTransform.position;
|
||||
_originalPlayerRotation = _playerTransform.eulerAngles;
|
||||
|
||||
if (_settings == null && _defaultSettings != null)
|
||||
_settings = _defaultSettings;
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
if (IsPanning && _needsUpdate)
|
||||
{
|
||||
if (_mode == CameraPanMode.FreeLook)
|
||||
{
|
||||
float normalizedYaw = Mathf.Repeat(_currentYaw, 360f);
|
||||
float normalizedPitch = _currentPitch;
|
||||
|
||||
_playerTransform.eulerAngles = new Vector3(0f, normalizedYaw, 0f);
|
||||
_camera.transform.localEulerAngles = new Vector3(normalizedPitch, 0f, 0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
_currentYaw = Mathf.LerpAngle(_currentYaw, _targetYaw, Time.deltaTime * _lerpSpeed);
|
||||
_currentPitch = Mathf.LerpAngle(_currentPitch, _targetPitch, Time.deltaTime * _lerpSpeed);
|
||||
|
||||
float normalizedYaw = Mathf.Repeat(_currentYaw, 360f);
|
||||
float normalizedPitch = _currentPitch;
|
||||
|
||||
_playerTransform.eulerAngles = new Vector3(0f, normalizedYaw, 0f);
|
||||
_camera.transform.localEulerAngles = new Vector3(normalizedPitch, 0f, 0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts camera pan mode.
|
||||
/// Starts camera pan mode. The mode (Pan or FreeLook) is determined by the current settings.
|
||||
/// </summary>
|
||||
public void StartPan()
|
||||
{
|
||||
@@ -66,13 +110,26 @@ namespace Unheimlich.Gameplay.CameraControl
|
||||
}
|
||||
|
||||
IsPanning = true;
|
||||
_mode = _settings.PanMode;
|
||||
_timeSinceLastInput = 0f;
|
||||
|
||||
if (Mouse.current != null)
|
||||
_lastMousePosition = Mouse.current.position.ReadValue();
|
||||
|
||||
float currentPitch = _camera.transform.localEulerAngles.x;
|
||||
if (currentPitch > 180f)
|
||||
currentPitch -= 360f;
|
||||
|
||||
_originalCameraRotation = new Vector2(_playerTransform.eulerAngles.y, currentPitch);
|
||||
float currentYaw = _playerTransform.eulerAngles.y;
|
||||
if (currentYaw > 180f)
|
||||
currentYaw -= 360f;
|
||||
|
||||
_originalCameraRotation = new Vector2(currentYaw, currentPitch);
|
||||
_currentYaw = currentYaw;
|
||||
_currentPitch = currentPitch;
|
||||
_targetYaw = currentYaw;
|
||||
_targetPitch = currentPitch;
|
||||
_needsUpdate = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -88,6 +145,52 @@ namespace Unheimlich.Gameplay.CameraControl
|
||||
/// Call this from Update when pan is active.
|
||||
/// </summary>
|
||||
public void UpdatePan()
|
||||
{
|
||||
if (!IsPanning || _camera == null || _settings == null)
|
||||
return;
|
||||
|
||||
if (_mode == CameraPanMode.FreeLook)
|
||||
{
|
||||
UpdateFreeLook();
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdatePanMode();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateFreeLook()
|
||||
{
|
||||
float lookX = 0f;
|
||||
float lookY = 0f;
|
||||
bool hasInput = false;
|
||||
|
||||
var lookAction = PlayerInput.GetLookAction();
|
||||
if (lookAction != null)
|
||||
{
|
||||
Vector2 lookInput = lookAction.ReadValue<Vector2>();
|
||||
if (lookInput.sqrMagnitude > 0.01f)
|
||||
{
|
||||
lookX = lookInput.x;
|
||||
lookY = lookInput.y;
|
||||
hasInput = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasInput)
|
||||
{
|
||||
float sensitivity = _settings.PanSpeed * 0.1f;
|
||||
_currentYaw += lookX * sensitivity;
|
||||
_currentPitch -= lookY * sensitivity;
|
||||
|
||||
_currentYaw = Mathf.Clamp(_currentYaw, _originalCameraRotation.x - _settings.MaxPanAngleX, _originalCameraRotation.x + _settings.MaxPanAngleX);
|
||||
_currentPitch = Mathf.Clamp(_currentPitch, _originalCameraRotation.y - _settings.MaxPanAngleY, _originalCameraRotation.y + _settings.MaxPanAngleY);
|
||||
|
||||
_needsUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdatePanMode()
|
||||
{
|
||||
if (!IsPanning || _camera == null || _settings == null)
|
||||
return;
|
||||
@@ -111,31 +214,38 @@ namespace Unheimlich.Gameplay.CameraControl
|
||||
if (!hasInput && Mouse.current != null)
|
||||
{
|
||||
Vector2 mousePos = Mouse.current.position.ReadValue();
|
||||
float screenWidth = Screen.width;
|
||||
float screenHeight = Screen.height;
|
||||
|
||||
float borderPixels = _settings.PanBorderSize * Mathf.Min(screenWidth, screenHeight);
|
||||
bool mouseHasMoved = Vector2.Distance(mousePos, _lastMousePosition) > 0.1f;
|
||||
_lastMousePosition = mousePos;
|
||||
|
||||
if (mousePos.x < borderPixels)
|
||||
if (mouseHasMoved)
|
||||
{
|
||||
panX = -(borderPixels - mousePos.x) / borderPixels;
|
||||
hasInput = true;
|
||||
}
|
||||
else if (mousePos.x > screenWidth - borderPixels)
|
||||
{
|
||||
panX = (mousePos.x - (screenWidth - borderPixels)) / borderPixels;
|
||||
hasInput = true;
|
||||
}
|
||||
|
||||
if (mousePos.y < borderPixels)
|
||||
{
|
||||
panY = -(borderPixels - mousePos.y) / borderPixels;
|
||||
hasInput = true;
|
||||
}
|
||||
else if (mousePos.y > screenHeight - borderPixels)
|
||||
{
|
||||
panY = (mousePos.y - (screenHeight - borderPixels)) / borderPixels;
|
||||
hasInput = true;
|
||||
float screenWidth = Screen.width;
|
||||
float screenHeight = Screen.height;
|
||||
|
||||
float borderPixels = _settings.PanBorderSize * Mathf.Min(screenWidth, screenHeight);
|
||||
|
||||
if (mousePos.x < borderPixels)
|
||||
{
|
||||
panX = -(borderPixels - mousePos.x) / borderPixels;
|
||||
hasInput = true;
|
||||
}
|
||||
else if (mousePos.x > screenWidth - borderPixels)
|
||||
{
|
||||
panX = (mousePos.x - (screenWidth - borderPixels)) / borderPixels;
|
||||
hasInput = true;
|
||||
}
|
||||
|
||||
if (mousePos.y < borderPixels)
|
||||
{
|
||||
panY = -(borderPixels - mousePos.y) / borderPixels;
|
||||
hasInput = true;
|
||||
}
|
||||
else if (mousePos.y > screenHeight - borderPixels)
|
||||
{
|
||||
panY = (mousePos.y - (screenHeight - borderPixels)) / borderPixels;
|
||||
hasInput = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,48 +258,39 @@ namespace Unheimlich.Gameplay.CameraControl
|
||||
_timeSinceLastInput += Time.deltaTime;
|
||||
}
|
||||
|
||||
float currentYaw = _playerTransform.eulerAngles.y;
|
||||
float currentPitch = _camera.transform.localEulerAngles.x;
|
||||
if (currentPitch > 180f)
|
||||
currentPitch -= 360f;
|
||||
|
||||
float targetYaw;
|
||||
float targetPitch;
|
||||
float lerpSpeed;
|
||||
|
||||
bool shouldRecenter = _settings.EnableRecentering && _timeSinceLastInput >= _settings.RecenterDelay;
|
||||
|
||||
if (hasInput)
|
||||
{
|
||||
targetYaw = _originalCameraRotation.x + panX * _settings.MaxPanAngle;
|
||||
targetPitch = _originalCameraRotation.y - panY * _settings.MaxPanAngle;
|
||||
targetYaw = Mathf.Clamp(targetYaw, _originalCameraRotation.x - _settings.MaxPanAngle, _originalCameraRotation.x + _settings.MaxPanAngle);
|
||||
targetPitch = Mathf.Clamp(targetPitch, _originalCameraRotation.y - _settings.MaxPanAngle, _originalCameraRotation.y + _settings.MaxPanAngle);
|
||||
lerpSpeed = _settings.PanSpeed;
|
||||
_targetYaw = _originalCameraRotation.x + panX * _settings.MaxPanAngleX;
|
||||
_targetPitch = _originalCameraRotation.y - panY * _settings.MaxPanAngleY;
|
||||
_targetYaw = Mathf.Clamp(_targetYaw, _originalCameraRotation.x - _settings.MaxPanAngleX, _originalCameraRotation.x + _settings.MaxPanAngleX);
|
||||
_targetPitch = Mathf.Clamp(_targetPitch, _originalCameraRotation.y - _settings.MaxPanAngleY, _originalCameraRotation.y + _settings.MaxPanAngleY);
|
||||
_lerpSpeed = _settings.PanSpeed;
|
||||
_needsUpdate = true;
|
||||
}
|
||||
else if (shouldRecenter)
|
||||
{
|
||||
targetYaw = _originalCameraRotation.x;
|
||||
targetPitch = _originalCameraRotation.y;
|
||||
lerpSpeed = _settings.RecenterSpeed;
|
||||
_targetYaw = _originalCameraRotation.x;
|
||||
_targetPitch = _originalCameraRotation.y;
|
||||
_lerpSpeed = _settings.RecenterSpeed;
|
||||
_needsUpdate = true;
|
||||
}
|
||||
else
|
||||
else if (_needsUpdate)
|
||||
{
|
||||
targetYaw = currentYaw;
|
||||
targetPitch = currentPitch;
|
||||
lerpSpeed = 0f;
|
||||
float yawDiff = Mathf.Abs(Mathf.DeltaAngle(_currentYaw, _targetYaw));
|
||||
float pitchDiff = Mathf.Abs(Mathf.DeltaAngle(_currentPitch, _targetPitch));
|
||||
|
||||
if (yawDiff < 0.01f && pitchDiff < 0.01f)
|
||||
{
|
||||
_needsUpdate = false;
|
||||
}
|
||||
}
|
||||
|
||||
float newYaw = Mathf.LerpAngle(currentYaw, targetYaw, Time.deltaTime * lerpSpeed);
|
||||
float newPitch = Mathf.LerpAngle(currentPitch, targetPitch, Time.deltaTime * lerpSpeed);
|
||||
|
||||
_playerTransform.eulerAngles = new Vector3(0f, newYaw, 0f);
|
||||
_camera.transform.localEulerAngles = new Vector3(newPitch, 0f, 0f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Aligns the player and camera to the target object.
|
||||
/// - Player moves along target's local X axis to align with target center
|
||||
/// - Player moves to a fixed distance in front of the target
|
||||
/// - Player rotates to face the target
|
||||
/// - Camera pitch adjusts to look at target center
|
||||
/// </summary>
|
||||
@@ -259,6 +360,10 @@ namespace Unheimlich.Gameplay.CameraControl
|
||||
if (startPitch > 180f)
|
||||
startPitch -= 360f;
|
||||
|
||||
Vector3 targetForward = target.forward;
|
||||
Vector3 targetPlayerPos = targetCenter - targetForward * _settings.FixedDistanceFromTarget;
|
||||
targetPlayerPos.y = startPlayerPos.y;
|
||||
|
||||
float elapsed = 0f;
|
||||
float duration = 1f / _settings.AlignmentSpeed;
|
||||
|
||||
@@ -275,11 +380,6 @@ namespace Unheimlich.Gameplay.CameraControl
|
||||
float targetYaw = targetRotation.eulerAngles.y;
|
||||
float targetPitch = -Mathf.Asin(directionToTarget.y) * Mathf.Rad2Deg;
|
||||
|
||||
Vector3 targetRightAxis = target.right;
|
||||
Vector3 playerToTargetCenter = targetCenter - _playerTransform.position;
|
||||
float offsetAlongTargetRight = Vector3.Dot(playerToTargetCenter, targetRightAxis);
|
||||
Vector3 targetPlayerPos = _playerTransform.position + targetRightAxis * offsetAlongTargetRight;
|
||||
|
||||
Vector3 currentPlayerPos = Vector3.Lerp(startPlayerPos, targetPlayerPos, t);
|
||||
_playerTransform.position = currentPlayerPos;
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace Unheimlich.Gameplay.Environment
|
||||
InteractionText = IsOpen ? "Close" : "Open";
|
||||
}
|
||||
|
||||
public override void Interact(GameObject interactor)
|
||||
public override void Interact()
|
||||
{
|
||||
ToggleDoor();
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace Unheimlich.Gameplay.Items
|
||||
_levitatingObject = GetComponent<LevitatingObject>();
|
||||
}
|
||||
|
||||
public override void Interact(GameObject interactor)
|
||||
public override void Interact()
|
||||
{
|
||||
if (_blockInteractionDuringAnimation)
|
||||
{
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace Unheimlich.Gameplay.Player
|
||||
|
||||
private HeadBobMode _overrideMode = HeadBobMode.Normal;
|
||||
private bool _hasOverride = false;
|
||||
private Coroutine _resetCoroutine;
|
||||
|
||||
void Start()
|
||||
{
|
||||
@@ -80,6 +81,40 @@ namespace Unheimlich.Gameplay.Player
|
||||
public void ClearOverride()
|
||||
{
|
||||
_hasOverride = false;
|
||||
|
||||
if (_resetCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_resetCoroutine);
|
||||
_resetCoroutine = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Smoothly lerps the head bob position to zero over the specified duration.
|
||||
/// </summary>
|
||||
public void LerpToZero(float duration)
|
||||
{
|
||||
if (_resetCoroutine != null)
|
||||
StopCoroutine(_resetCoroutine);
|
||||
|
||||
_resetCoroutine = StartCoroutine(LerpToZeroCoroutine(duration));
|
||||
}
|
||||
|
||||
private System.Collections.IEnumerator LerpToZeroCoroutine(float duration)
|
||||
{
|
||||
Vector3 startPos = transform.localPosition;
|
||||
float elapsed = 0f;
|
||||
|
||||
while (elapsed < duration)
|
||||
{
|
||||
elapsed += Time.deltaTime;
|
||||
float t = Mathf.Clamp01(elapsed / duration);
|
||||
transform.localPosition = Vector3.Lerp(startPos, Vector3.zero, t);
|
||||
yield return null;
|
||||
}
|
||||
|
||||
transform.localPosition = Vector3.zero;
|
||||
_resetCoroutine = null;
|
||||
}
|
||||
|
||||
private void ApplyBob(float speed, float amplitude)
|
||||
|
||||
+51
-7
@@ -66,6 +66,7 @@ namespace Unheimlich.Gameplay.Player
|
||||
private AudioListenable _currentTargetEmitter;
|
||||
private AudioSource _playerAudioSource;
|
||||
private Coroutine _listeningCoroutine;
|
||||
private Coroutine _vibrationCoroutine;
|
||||
private int _diapasonTweenId = -1;
|
||||
private int _audioListenerFadeTweenId = -1;
|
||||
private int _playerAudioFadeTweenId = -1;
|
||||
@@ -74,7 +75,9 @@ namespace Unheimlich.Gameplay.Player
|
||||
private Vector3 _diapasonHiddenPosition;
|
||||
private bool _canUseDiapason;
|
||||
private bool _isFadingOutConcentration;
|
||||
private Coroutine _vibrationCoroutine;
|
||||
|
||||
private CameraHeadBob _cameraHeadBob;
|
||||
private CameraPanSettings _originalCameraSettings;
|
||||
|
||||
protected override void OnAwake()
|
||||
{
|
||||
@@ -91,6 +94,8 @@ namespace Unheimlich.Gameplay.Player
|
||||
_cameraPanController = gameObject.AddComponent<CameraPanController>();
|
||||
}
|
||||
}
|
||||
|
||||
_cameraHeadBob = GetComponentInChildren<CameraHeadBob>();
|
||||
|
||||
if (_diapasonObject != null)
|
||||
{
|
||||
@@ -236,6 +241,10 @@ namespace Unheimlich.Gameplay.Player
|
||||
|
||||
if (_currentTargetEmitter != null && _cameraPanController != null)
|
||||
{
|
||||
_originalCameraSettings = _cameraPanController.Settings;
|
||||
if(_currentTargetEmitter.CameraPanSettings != null)
|
||||
_cameraPanController.Settings = _currentTargetEmitter.CameraPanSettings;
|
||||
|
||||
_currentTargetEmitter.SetConcentrating(true);
|
||||
|
||||
_cameraPanController.AlignToTarget(_currentTargetEmitter.transform);
|
||||
@@ -263,18 +272,23 @@ namespace Unheimlich.Gameplay.Player
|
||||
|
||||
PlayBreathIn();
|
||||
yield return new WaitForSeconds(_breathInDuration);
|
||||
|
||||
if (_cameraHeadBob != null)
|
||||
{
|
||||
float resetDuration = _concentrationDuration - _breathInDuration;
|
||||
_cameraHeadBob.LerpToZero(resetDuration);
|
||||
}
|
||||
|
||||
yield return new WaitForSeconds(_concentrationDuration - _breathInDuration);
|
||||
|
||||
IsConcentrating = false;
|
||||
IsListening = true;
|
||||
|
||||
EnableVignette(true);
|
||||
|
||||
if (_cameraPanController != null)
|
||||
{
|
||||
_cameraPanController.StartPan();
|
||||
}
|
||||
EnableVignette(true);
|
||||
|
||||
ApplyHeadBobSettings(_cameraPanController.Settings);
|
||||
_cameraPanController.StartPan();
|
||||
|
||||
|
||||
Cursor.lockState = CursorLockMode.Confined;
|
||||
Cursor.visible = false;
|
||||
@@ -311,7 +325,15 @@ namespace Unheimlich.Gameplay.Player
|
||||
{
|
||||
_cameraPanController.StopPan();
|
||||
_cameraPanController.ReturnToOriginalPosition();
|
||||
|
||||
if (_originalCameraSettings != null)
|
||||
{
|
||||
_cameraPanController.Settings = _originalCameraSettings;
|
||||
_originalCameraSettings = null;
|
||||
}
|
||||
}
|
||||
|
||||
RestoreHeadBob();
|
||||
|
||||
if (_playerAudioSource != null && _playerAudioSource.isPlaying)
|
||||
{
|
||||
@@ -528,6 +550,28 @@ namespace Unheimlich.Gameplay.Player
|
||||
_vignetteEffect.SetIntensity(_vignetteIntensity);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies head bob settings based on CameraPanSettings configuration.
|
||||
/// </summary>
|
||||
private void ApplyHeadBobSettings(CameraPanSettings settings)
|
||||
{
|
||||
if (_cameraHeadBob == null || settings == null)
|
||||
return;
|
||||
|
||||
_cameraHeadBob.SetMode(settings.HeadBobMode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restores head bob to normal behavior.
|
||||
/// </summary>
|
||||
private void RestoreHeadBob()
|
||||
{
|
||||
if (_cameraHeadBob == null)
|
||||
return;
|
||||
|
||||
_cameraHeadBob.ClearOverride();
|
||||
}
|
||||
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace Unheimlich.Gameplay.Portals
|
||||
/// Handles interaction with the door.
|
||||
/// Toggles both this door and the linked portal's door simultaneously.
|
||||
/// </summary>
|
||||
public override void Interact(GameObject interactor)
|
||||
public override void Interact()
|
||||
{
|
||||
SyncToggle();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user