Integration of the octo input system and centralization of player values
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
using Unheimlich.Data;
|
||||
using Unheimlich.Managers;
|
||||
|
||||
namespace Unheimlich.Gameplay.Player
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles camera effects: aim assist and shake.
|
||||
/// </summary>
|
||||
public class CameraEffects : MonoBehaviour
|
||||
{
|
||||
[Header("Settings")]
|
||||
public CameraEffectsSettings Settings;
|
||||
|
||||
private float _shakeTimer = 0f;
|
||||
private float _shakeInterval;
|
||||
private Vector3 _shakeTarget;
|
||||
private Vector3 _shakeOriginalPos;
|
||||
private float _shakeSmoothness;
|
||||
private Vector3 _pendingAimAssistTarget;
|
||||
private bool _hasAimAssistTarget;
|
||||
|
||||
public bool IsAimLocked { get; private set; }
|
||||
public bool HasActiveLookInput { get; private set; }
|
||||
public bool IsUsingGamepad { get; private set; }
|
||||
|
||||
void Start()
|
||||
{
|
||||
if (Settings != null)
|
||||
_shakeInterval = 1f / Settings.shakeFrequency;
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
DetectInputDevice();
|
||||
}
|
||||
|
||||
void LateUpdate()
|
||||
{
|
||||
if (_hasAimAssistTarget)
|
||||
{
|
||||
ApplyAimAssistInternal(_pendingAimAssistTarget);
|
||||
}
|
||||
}
|
||||
|
||||
private void DetectInputDevice()
|
||||
{
|
||||
Vector2 lookInput = InputManager.GetLookInput();
|
||||
HasActiveLookInput = lookInput.magnitude > 0.1f;
|
||||
|
||||
if (Gamepad.current != null && Gamepad.current.rightStick.ReadValue().magnitude > 0.1f)
|
||||
IsUsingGamepad = true;
|
||||
else if (Mouse.current != null && Mouse.current.delta.ReadValue().magnitude > 0.1f)
|
||||
IsUsingGamepad = false;
|
||||
}
|
||||
|
||||
public void ShakeCamera(float amplitude, float smoothness = 0f)
|
||||
{
|
||||
if (PlayerController.Instance == null || PlayerController.Instance.CameraPivot == null || Settings == null)
|
||||
return;
|
||||
|
||||
_shakeSmoothness = smoothness;
|
||||
_shakeTimer -= Time.deltaTime;
|
||||
|
||||
if (_shakeTimer <= 0f)
|
||||
{
|
||||
_shakeTimer = _shakeInterval * amplitude;
|
||||
_shakeOriginalPos = PlayerController.Instance.CameraPivot.position;
|
||||
|
||||
float x = Random.Range(-1f, 1f) * amplitude;
|
||||
float y = Random.Range(-1f, 1f) * amplitude;
|
||||
|
||||
_shakeTarget = _shakeOriginalPos + new Vector3(x, y, 0f);
|
||||
}
|
||||
|
||||
if (_shakeSmoothness > 0f)
|
||||
{
|
||||
PlayerController.Instance.CameraPivot.position = Vector3.Lerp(
|
||||
PlayerController.Instance.CameraPivot.position,
|
||||
_shakeTarget,
|
||||
Time.deltaTime * _shakeSmoothness);
|
||||
}
|
||||
else
|
||||
{
|
||||
PlayerController.Instance.CameraPivot.position = _shakeTarget;
|
||||
}
|
||||
}
|
||||
|
||||
public void ApplyAimAssist(Vector3 targetCenter)
|
||||
{
|
||||
_pendingAimAssistTarget = targetCenter;
|
||||
_hasAimAssistTarget = true;
|
||||
}
|
||||
|
||||
private void ApplyAimAssistInternal(Vector3 targetCenter)
|
||||
{
|
||||
if (PlayerController.Instance == null || PlayerController.Instance.CameraTransform == null || Settings == null)
|
||||
return;
|
||||
|
||||
if (PlayerController.Instance.Movement == null)
|
||||
return;
|
||||
|
||||
if (IsAimLocked && HasActiveLookInput)
|
||||
IsAimLocked = false;
|
||||
|
||||
Vector3 directionToTarget = (targetCenter - PlayerController.Instance.CameraTransform.position).normalized;
|
||||
float angleError = Vector3.Angle(PlayerController.Instance.CameraTransform.forward, directionToTarget);
|
||||
|
||||
if (IsAimLocked || angleError < Settings.aimAssistMaxAngle)
|
||||
{
|
||||
Vector3 targetDirection = directionToTarget;
|
||||
float targetYaw = Mathf.Atan2(targetDirection.x, targetDirection.z) * Mathf.Rad2Deg;
|
||||
float targetPitch = -Mathf.Asin(targetDirection.y) * Mathf.Rad2Deg;
|
||||
|
||||
float currentYaw = PlayerController.Instance.Movement.Yaw;
|
||||
float currentPitch = PlayerController.Instance.Movement.Pitch;
|
||||
|
||||
float newYaw = Mathf.LerpAngle(currentYaw, targetYaw, Settings.aimAssistStrength * Time.deltaTime);
|
||||
float newPitch = Mathf.Lerp(currentPitch, targetPitch, Settings.aimAssistStrength * Time.deltaTime);
|
||||
|
||||
PlayerController.Instance.Movement.Yaw = newYaw;
|
||||
PlayerController.Instance.Movement.Pitch = newPitch;
|
||||
}
|
||||
|
||||
_hasAimAssistTarget = false;
|
||||
}
|
||||
|
||||
public void LockAim(bool locked)
|
||||
{
|
||||
IsAimLocked = locked;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e05e5ee4f09d55d4a93324fe0eb85d6e
|
||||
@@ -6,20 +6,9 @@ namespace Unheimlich.Gameplay.Player
|
||||
{
|
||||
/// <summary>
|
||||
/// Simulates camera head bobbing motion during player movement and idle states.
|
||||
/// Applies subtle oscillation based on player grounded and movement state.
|
||||
/// </summary>
|
||||
public class CameraHeadBob : MonoBehaviour
|
||||
{
|
||||
[Header("Bob Settings - Idle")]
|
||||
[SerializeField] private float _idleBobSpeed = 1f;
|
||||
[SerializeField] private float _idleBobAmplitude = 0.005f;
|
||||
|
||||
[Header("Bob Settings - Walk")]
|
||||
[SerializeField] private float _walkBobSpeed = 12f;
|
||||
[SerializeField] private float _walkBobAmplitude = 0.02f;
|
||||
[SerializeField] private float _resetSpeed = 5f;
|
||||
|
||||
private PlayerMovement _playerMovement;
|
||||
private float _timer = 0f;
|
||||
private Vector3 _startLocalPos;
|
||||
|
||||
@@ -29,55 +18,57 @@ namespace Unheimlich.Gameplay.Player
|
||||
|
||||
void Start()
|
||||
{
|
||||
_playerMovement = PlayerMovement.Instance;
|
||||
_startLocalPos = transform.localPosition;
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (_playerMovement == null || _playerMovement.CharacterController == null)
|
||||
if (PlayerController.Instance == null || PlayerController.Instance.CharController == null)
|
||||
return;
|
||||
|
||||
var settings = PlayerController.Instance.CameraEffects?.Settings;
|
||||
if (settings == null) return;
|
||||
|
||||
HeadBobMode currentMode = _hasOverride ? _overrideMode : HeadBobMode.Normal;
|
||||
|
||||
if (currentMode == HeadBobMode.Disabled)
|
||||
{
|
||||
ResetPosition();
|
||||
ResetPosition(settings.resetSpeed);
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentMode == HeadBobMode.Idle || (PlayerInspectionHandler.Instance != null && PlayerInspectionHandler.Instance.IsInspecting))
|
||||
{
|
||||
ApplyBob(_idleBobSpeed, _idleBobAmplitude);
|
||||
ApplyBob(settings.idleBobSpeed, settings.idleBobAmplitude);
|
||||
return;
|
||||
}
|
||||
|
||||
bool isMoving = _playerMovement.IsMoving;
|
||||
bool isGrounded = _playerMovement.CharacterController.isGrounded;
|
||||
bool isMoving = PlayerController.Instance.IsMoving;
|
||||
bool isRunning = PlayerController.Instance.IsRunning;
|
||||
bool isGrounded = PlayerController.Instance.CharController.isGrounded;
|
||||
|
||||
if (isGrounded)
|
||||
{
|
||||
if (isMoving)
|
||||
ApplyBob(_walkBobSpeed, _walkBobAmplitude);
|
||||
if (isMoving)
|
||||
{
|
||||
if (isRunning)
|
||||
ApplyBob(settings.runBobSpeed, settings.runBobAmplitude);
|
||||
else
|
||||
ApplyBob(settings.walkBobSpeed, settings.walkBobAmplitude);
|
||||
}
|
||||
else
|
||||
ApplyBob(_idleBobSpeed, _idleBobAmplitude);
|
||||
ApplyBob(settings.idleBobSpeed, settings.idleBobAmplitude);
|
||||
}
|
||||
else
|
||||
ResetPosition();
|
||||
ResetPosition(settings.resetSpeed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the head bob mode override.
|
||||
/// </summary>
|
||||
public void SetMode(HeadBobMode mode)
|
||||
{
|
||||
_overrideMode = mode;
|
||||
_hasOverride = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the override and returns to normal behavior.
|
||||
/// </summary>
|
||||
public void ClearOverride()
|
||||
{
|
||||
_hasOverride = false;
|
||||
@@ -89,9 +80,6 @@ namespace Unheimlich.Gameplay.Player
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Smoothly lerps the head bob position to zero over the specified duration.
|
||||
/// </summary>
|
||||
public void LerpToZero(float duration)
|
||||
{
|
||||
if (_resetCoroutine != null)
|
||||
@@ -131,9 +119,9 @@ namespace Unheimlich.Gameplay.Player
|
||||
transform.localPosition = newPos;
|
||||
}
|
||||
|
||||
private void ResetPosition()
|
||||
private void ResetPosition(float resetSpeed)
|
||||
{
|
||||
transform.localPosition = Vector3.Lerp(transform.localPosition, _startLocalPos, Time.deltaTime * _resetSpeed);
|
||||
transform.localPosition = Vector3.Lerp(transform.localPosition, _startLocalPos, Time.deltaTime * resetSpeed);
|
||||
|
||||
if (Vector3.Distance(transform.localPosition, _startLocalPos) < 0.001f)
|
||||
_timer = 0f;
|
||||
|
||||
+61
-32
@@ -7,19 +7,12 @@ using Unheimlich.Utility;
|
||||
using Unheimlich.Gameplay.Player;
|
||||
using Unheimlich.Gameplay.CameraControl;
|
||||
using Unheimlich.Data.CameraSettings;
|
||||
using InputManager = Unheimlich.Managers.PlayerInputManager;
|
||||
using InputManager = Unheimlich.Managers.InputManager;
|
||||
|
||||
namespace Unheimlich.Gameplay.Player
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages the tuning fork (diapason) listening mechanic.
|
||||
/// Handles:
|
||||
/// - Equipping/unequipping the diapason with tween animations
|
||||
/// - Concentration phase (breathing sound and duration)
|
||||
/// - Listening mode with camera pan control (via CameraPanController)
|
||||
/// - Tapping the diapason on listenable objects
|
||||
/// - Visual feedback (vignette, cursor, particles)
|
||||
/// - Preventing multiple taps on the same emitter
|
||||
/// </summary>
|
||||
public class DiapasonListeningHandler : Singleton<DiapasonListeningHandler>
|
||||
{
|
||||
@@ -29,8 +22,7 @@ namespace Unheimlich.Gameplay.Player
|
||||
[Header("References")]
|
||||
[SerializeField] private GameObject _diapasonObject;
|
||||
[SerializeField] private Animator _diapasonAnimator;
|
||||
[SerializeField] private UnityEngine.Camera _playerCamera;
|
||||
[SerializeField] private CameraPanController _cameraPanController;
|
||||
private CameraPanController _cameraPanController;
|
||||
|
||||
[Header("Listening Settings")]
|
||||
[SerializeField] private float _listeningRange = 5f;
|
||||
@@ -85,13 +77,20 @@ namespace Unheimlich.Gameplay.Player
|
||||
if (_playerAudioSource == null)
|
||||
_playerAudioSource = gameObject.AddComponent<AudioSource>();
|
||||
|
||||
|
||||
_cameraPanController = GetComponent<CameraPanController>();
|
||||
if (_cameraPanController == null)
|
||||
{
|
||||
_cameraPanController = GetComponent<CameraPanController>();
|
||||
if (_cameraPanController == null)
|
||||
Debug.LogWarning("[Diapason] CameraPanController not found! Adding one automatically.");
|
||||
_cameraPanController = gameObject.AddComponent<CameraPanController>();
|
||||
}
|
||||
|
||||
if (_vignetteEffect == null)
|
||||
{
|
||||
_vignetteEffect = GetComponentInChildren<ListeningVignetteEffect>();
|
||||
if (_vignetteEffect == null)
|
||||
{
|
||||
Debug.LogWarning("[Diapason] CameraPanController not found! Adding one automatically.");
|
||||
_cameraPanController = gameObject.AddComponent<CameraPanController>();
|
||||
Debug.LogError("[Diapason] ListeningVignetteEffect not found! Vignette won't work.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,10 +149,10 @@ namespace Unheimlich.Gameplay.Player
|
||||
|
||||
private void CheckForNearbyEmitter()
|
||||
{
|
||||
if (_playerCamera == null)
|
||||
if (PlayerController.Instance == null || PlayerController.Instance.PlayerCamera == null)
|
||||
return;
|
||||
|
||||
Ray ray = _playerCamera.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0f));
|
||||
Ray ray = PlayerController.Instance.PlayerCamera.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0f));
|
||||
|
||||
if (Physics.Raycast(ray, out RaycastHit hit, _listeningRange, _listenableLayers))
|
||||
{
|
||||
@@ -238,6 +237,12 @@ namespace Unheimlich.Gameplay.Player
|
||||
{
|
||||
IsConcentrating = true;
|
||||
InputManager.SetContext(InputManager.InputContext.Listening);
|
||||
|
||||
// IMMEDIATELY disable OctoPlayer to prevent any player movement
|
||||
if (PlayerController.Instance?.Movement != null)
|
||||
{
|
||||
PlayerController.Instance.Movement.enabled = false;
|
||||
}
|
||||
|
||||
if (_currentTargetEmitter != null && _cameraPanController != null)
|
||||
{
|
||||
@@ -247,7 +252,16 @@ namespace Unheimlich.Gameplay.Player
|
||||
|
||||
_currentTargetEmitter.SetConcentrating(true);
|
||||
|
||||
_cameraPanController.AlignToTarget(_currentTargetEmitter.transform);
|
||||
// Save state for return (before alignment)
|
||||
_cameraPanController.SaveOriginalState();
|
||||
|
||||
// Align camera first, then start pan AFTER alignment
|
||||
_cameraPanController.AlignToTarget(_currentTargetEmitter.transform, () =>
|
||||
{
|
||||
// NOW start pan using the aligned rotation as base
|
||||
_cameraPanController.StartPan();
|
||||
});
|
||||
|
||||
yield return new WaitForSeconds(1f / 3f);
|
||||
}
|
||||
|
||||
@@ -287,8 +301,8 @@ namespace Unheimlich.Gameplay.Player
|
||||
EnableVignette(true);
|
||||
|
||||
ApplyHeadBobSettings(_cameraPanController.Settings);
|
||||
_cameraPanController.StartPan();
|
||||
|
||||
// Pan is already active from the beginning of the sequence
|
||||
|
||||
Cursor.lockState = CursorLockMode.Confined;
|
||||
Cursor.visible = false;
|
||||
@@ -324,12 +338,27 @@ namespace Unheimlich.Gameplay.Player
|
||||
if (_cameraPanController != null)
|
||||
{
|
||||
_cameraPanController.StopPan();
|
||||
_cameraPanController.ReturnToOriginalPosition();
|
||||
|
||||
if (_originalCameraSettings != null)
|
||||
_cameraPanController.ReturnToOriginalPosition(() =>
|
||||
{
|
||||
_cameraPanController.Settings = _originalCameraSettings;
|
||||
_originalCameraSettings = null;
|
||||
if (_originalCameraSettings != null)
|
||||
{
|
||||
_cameraPanController.Settings = _originalCameraSettings;
|
||||
_originalCameraSettings = null;
|
||||
}
|
||||
|
||||
// Re-enable OctoPlayer AFTER the return animation completes
|
||||
if (PlayerController.Instance?.Movement != null)
|
||||
{
|
||||
PlayerController.Instance.Movement.enabled = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// If no camera pan controller, re-enable immediately
|
||||
if (PlayerController.Instance?.Movement != null)
|
||||
{
|
||||
PlayerController.Instance.Movement.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -413,10 +442,10 @@ namespace Unheimlich.Gameplay.Player
|
||||
/// </summary>
|
||||
private void CheckForListenableObject()
|
||||
{
|
||||
if (_playerCamera == null)
|
||||
if (PlayerController.Instance == null || PlayerController.Instance.PlayerCamera == null)
|
||||
return;
|
||||
|
||||
Ray ray = _playerCamera.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0f));
|
||||
Ray ray = PlayerController.Instance.PlayerCamera.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0f));
|
||||
|
||||
if (Physics.Raycast(ray, out RaycastHit hit, _listeningRange, _listenableLayers))
|
||||
{
|
||||
@@ -544,11 +573,11 @@ namespace Unheimlich.Gameplay.Player
|
||||
|
||||
private void EnableVignette(bool enable)
|
||||
{
|
||||
if (_vignetteEffect != null)
|
||||
{
|
||||
_vignetteEffect.EnableVignette(enable);
|
||||
_vignetteEffect.SetIntensity(_vignetteIntensity);
|
||||
}
|
||||
if (_vignetteEffect == null)
|
||||
return;
|
||||
|
||||
_vignetteEffect.EnableVignette(enable);
|
||||
_vignetteEffect.SetIntensity(_vignetteIntensity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -575,10 +604,10 @@ namespace Unheimlich.Gameplay.Player
|
||||
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
if (_playerCamera != null && IsListening)
|
||||
if (PlayerController.Instance != null && PlayerController.Instance.PlayerCamera != null && IsListening)
|
||||
{
|
||||
Gizmos.color = _currentTargetEmitter != null ? Color.green : Color.yellow;
|
||||
Ray ray = _playerCamera.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0f));
|
||||
Ray ray = PlayerController.Instance.PlayerCamera.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0f));
|
||||
Gizmos.DrawRay(ray.origin, ray.direction * _listeningRange);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
using UnityEngine;
|
||||
using OctoCore.Components;
|
||||
using Unheimlich.Core;
|
||||
using Unheimlich.Data;
|
||||
using Unheimlich.Managers;
|
||||
using Unheimlich.Gameplay.CameraControl;
|
||||
|
||||
namespace Unheimlich.Gameplay.Player
|
||||
{
|
||||
/// <summary>
|
||||
/// Central hub for all player systems.
|
||||
/// Exposes components and settings with zero Inspector refs needed for other scripts.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(OctoPlayer))]
|
||||
[RequireComponent(typeof(CharacterController))]
|
||||
public class PlayerController : Singleton<PlayerController>
|
||||
{
|
||||
// Auto-found components (exposed publicly, found once in Awake)
|
||||
public OctoPlayer Movement { get; private set; }
|
||||
public CharacterController CharController { get; private set; }
|
||||
public Camera PlayerCamera { get; private set; }
|
||||
public Transform CameraTransform { get; private set; }
|
||||
public Transform CameraPivot { get; private set; }
|
||||
public CameraEffects CameraEffects { get; private set; }
|
||||
public CameraPanController CameraPanController { get; private set; }
|
||||
public InputManager Inputs => InputManager.Instance;
|
||||
|
||||
public bool IsMoving
|
||||
{
|
||||
get
|
||||
{
|
||||
// Read directly from action instead of cached value
|
||||
var moveAction = InputManager.GetMoveAction();
|
||||
if (moveAction == null) return false;
|
||||
return moveAction.ReadValue<UnityEngine.Vector2>().magnitude > 0.1f;
|
||||
}
|
||||
}
|
||||
public bool IsRunning
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Movement == null || OctoCore.OctoCore.Instance == null) return false;
|
||||
|
||||
var actions = OctoCore.OctoCore.Instance.Inputs?.Actions;
|
||||
if (actions != null)
|
||||
{
|
||||
foreach (var actionInfo in actions)
|
||||
{
|
||||
if (actionInfo.Name == "Sprint" && actionInfo.Reference != null)
|
||||
return actionInfo.Reference.action.IsPressed();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnAwake()
|
||||
{
|
||||
// Auto-find all components once
|
||||
Movement = GetComponent<OctoPlayer>();
|
||||
CharController = GetComponent<CharacterController>();
|
||||
CameraEffects = GetComponent<CameraEffects>();
|
||||
CameraPanController = GetComponent<CameraPanController>();
|
||||
PlayerCamera = GetComponentInChildren<Camera>();
|
||||
|
||||
if (PlayerCamera != null)
|
||||
{
|
||||
CameraTransform = PlayerCamera.transform;
|
||||
CameraPivot = CameraTransform.parent;
|
||||
}
|
||||
}
|
||||
|
||||
// Teleport
|
||||
public void Teleport(Vector3 position, Quaternion rotation)
|
||||
{
|
||||
Movement.Teleport(position, rotation);
|
||||
PlayerEvents.TriggerTeleport(position, rotation);
|
||||
}
|
||||
|
||||
// Helper methods
|
||||
public Vector3 GetForward()
|
||||
{
|
||||
if (PlayerCamera == null) return transform.forward;
|
||||
|
||||
Vector3 forward = PlayerCamera.transform.forward;
|
||||
forward.y = 0;
|
||||
return forward.normalized;
|
||||
}
|
||||
|
||||
public Vector3 GetRight()
|
||||
{
|
||||
if (PlayerCamera == null) return transform.right;
|
||||
|
||||
Vector3 right = PlayerCamera.transform.right;
|
||||
right.y = 0;
|
||||
return right.normalized;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2d192ca4042c25c4ea16f024d0e262c1
|
||||
@@ -1,171 +0,0 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
using Unheimlich.Core;
|
||||
using Unheimlich.Gameplay.Portals;
|
||||
using InputManager = Unheimlich.Managers.PlayerInputManager;
|
||||
|
||||
namespace Unheimlich.Gameplay.Player
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles first-person camera rotation and look mechanics for the player.
|
||||
/// Supports both mouse and gamepad input with adaptive sensitivity.
|
||||
/// Includes aim assist for dialogue choices and camera shake effects.
|
||||
/// </summary>
|
||||
public class PlayerLook : Singleton<PlayerLook>
|
||||
{
|
||||
private const float INPUT_DEADZONE = 0.1f;
|
||||
private const float MOUSE_DELTA_MULTIPLIER = 0.001f;
|
||||
|
||||
[Header("Components")]
|
||||
[SerializeField] private Transform _playerCamera, _playerCameraPivot;
|
||||
|
||||
[Header("Settings")]
|
||||
[SerializeField] private int _frameRate = 120;
|
||||
[SerializeField] private float _mouseSensitivity = 0.2f;
|
||||
[SerializeField] private float _gamepadSensitivity = 100f;
|
||||
[SerializeField] private float _minLookAngle = -60f;
|
||||
[SerializeField] private float _maxLookAngle = 60f;
|
||||
|
||||
[Header("Aim Assist")]
|
||||
[SerializeField] private float _aimAssistStrength = 25f;
|
||||
[SerializeField] private float _aimAssistMaxAngle = 10f;
|
||||
|
||||
public bool HasActiveLookInput { get; private set; }
|
||||
public bool IsAimLocked { get; private set; } = false;
|
||||
|
||||
[Header("Shake Control")]
|
||||
[SerializeField] private float _shakeFrequency = 40f;
|
||||
private float _shakeInterval;
|
||||
private float _shakeTimer = 0f;
|
||||
private Vector3 _shakeTarget = Vector3.zero;
|
||||
private Vector3 _shakeOriginalPos = Vector3.zero;
|
||||
private float _shakeSmoothness = 0f;
|
||||
|
||||
private float _horizontalRotation;
|
||||
private float _verticalRotation;
|
||||
private bool _isUsingGamepad = false;
|
||||
|
||||
protected override void OnAwake()
|
||||
{
|
||||
QualitySettings.vSyncCount = 0;
|
||||
Application.targetFrameRate = _frameRate;
|
||||
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
Cursor.lockState = CursorLockMode.Locked;
|
||||
Cursor.visible = false;
|
||||
_shakeInterval = 1f / _shakeFrequency;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if(!InputManager.IsLookingEnabled)
|
||||
return;
|
||||
|
||||
HandleLook();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes look input from mouse or gamepad and updates camera rotation.
|
||||
/// Automatically detects input device and applies appropriate sensitivity.
|
||||
/// Clamps vertical rotation to prevent over-rotation.
|
||||
/// </summary>
|
||||
private void HandleLook()
|
||||
{
|
||||
Vector2 lookInput = InputManager.Instance.LookInput;
|
||||
|
||||
HasActiveLookInput = lookInput.magnitude > INPUT_DEADZONE;
|
||||
|
||||
if (Gamepad.current != null && Gamepad.current.rightStick.ReadValue().magnitude > PortalConstants.GAMEPAD_DEADZONE)
|
||||
_isUsingGamepad = true;
|
||||
else if (Mouse.current != null && Mouse.current.delta.ReadValue().magnitude > PortalConstants.GAMEPAD_DEADZONE)
|
||||
_isUsingGamepad = false;
|
||||
|
||||
float sensitivity = _isUsingGamepad ? _gamepadSensitivity : _mouseSensitivity;
|
||||
float deltaMultiplier = _isUsingGamepad ? Time.deltaTime : MOUSE_DELTA_MULTIPLIER;
|
||||
|
||||
_horizontalRotation += lookInput.x * sensitivity * deltaMultiplier;
|
||||
transform.rotation = Quaternion.Euler(0, _horizontalRotation, 0f);
|
||||
|
||||
_verticalRotation -= lookInput.y * sensitivity * deltaMultiplier;
|
||||
_verticalRotation = Mathf.Clamp(_verticalRotation, _minLookAngle, _maxLookAngle);
|
||||
|
||||
_playerCamera.localRotation = Quaternion.Euler(_verticalRotation, 0f, 0f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Smoothly rotates the player camera toward a target using spherical interpolation.
|
||||
/// Assists player aiming by gently pulling the view toward dialogue choices when looking near them.
|
||||
/// Automatically unlocks if player provides manual input.
|
||||
/// </summary>
|
||||
public void ApplyAimAssist(Vector3 targetCenter)
|
||||
{
|
||||
if (IsAimLocked && HasActiveLookInput)
|
||||
IsAimLocked = false;
|
||||
|
||||
Vector3 directionToTarget = (targetCenter - _playerCamera.position).normalized;
|
||||
float angleError = Vector3.Angle(_playerCamera.forward, directionToTarget);
|
||||
|
||||
if (IsAimLocked || (angleError > 0.01f && angleError < _aimAssistMaxAngle))
|
||||
{
|
||||
Quaternion targetRotation = Quaternion.LookRotation(directionToTarget);
|
||||
|
||||
Quaternion correctedRotation = Quaternion.Slerp(
|
||||
_playerCamera.rotation,
|
||||
targetRotation,
|
||||
_aimAssistStrength * Time.deltaTime
|
||||
);
|
||||
Quaternion newBodyRotation = Quaternion.Euler(0, correctedRotation.eulerAngles.y, 0);
|
||||
|
||||
_horizontalRotation = newBodyRotation.eulerAngles.y;
|
||||
transform.rotation = newBodyRotation;
|
||||
|
||||
float newVerticalAngle = correctedRotation.eulerAngles.x;
|
||||
|
||||
if (newVerticalAngle > 180) newVerticalAngle -= 360;
|
||||
|
||||
_verticalRotation = Mathf.Clamp(newVerticalAngle, _minLookAngle, _maxLookAngle);
|
||||
_playerCamera.localRotation = Quaternion.Euler(_verticalRotation, 0f, 0f);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a random camera shake effect for feedback (e.g., impacts, events).
|
||||
/// Uses a timer-based interval to control shake frequency.
|
||||
/// Amplitude controls both the intensity and duration of the shake.
|
||||
/// Smoothness controls how quickly the camera interpolates to the shake position (0 = instant, higher = smoother).
|
||||
/// </summary>
|
||||
public void Shake(float amplitude, float smoothness = 0f)
|
||||
{
|
||||
_shakeSmoothness = smoothness;
|
||||
_shakeTimer -= Time.deltaTime;
|
||||
|
||||
if (_shakeTimer <= 0f)
|
||||
{
|
||||
_shakeTimer = _shakeInterval * amplitude;
|
||||
_shakeOriginalPos = _playerCameraPivot.position;
|
||||
|
||||
float x = Random.Range(-1f, 1f) * amplitude;
|
||||
float y = Random.Range(-1f, 1f) * amplitude;
|
||||
|
||||
_shakeTarget = _shakeOriginalPos + new Vector3(x, y, 0f);
|
||||
}
|
||||
|
||||
if (_shakeSmoothness > 0f)
|
||||
{
|
||||
_playerCameraPivot.position = Vector3.Lerp(_playerCameraPivot.position, _shakeTarget, Time.deltaTime * _shakeSmoothness);
|
||||
}
|
||||
else
|
||||
{
|
||||
_playerCameraPivot.position = _shakeTarget;
|
||||
}
|
||||
}
|
||||
|
||||
public void LockAim(bool locked)
|
||||
{
|
||||
IsAimLocked = locked;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1ddcefd49548ddb4b9bc694da032caee
|
||||
@@ -1,63 +0,0 @@
|
||||
using UnityEngine;
|
||||
using Unheimlich.Core;
|
||||
using Unheimlich.Managers;
|
||||
|
||||
namespace Unheimlich.Gameplay.Player
|
||||
{
|
||||
[RequireComponent(typeof(CharacterController))]
|
||||
public class PlayerMovement : Singleton<PlayerMovement>
|
||||
{
|
||||
|
||||
[Header("Settings")]
|
||||
[SerializeField] private float _moveSpeed = 5f;
|
||||
[SerializeField] private float _jumpForce = 10f;
|
||||
[SerializeField] private float _groundedGravity = -2f;
|
||||
|
||||
public CharacterController CharacterController { get; private set; }
|
||||
|
||||
public bool IsMoving { get; private set; }
|
||||
|
||||
private float _ySpeed;
|
||||
private Vector3 _moveDirection;
|
||||
|
||||
protected override void OnAwake()
|
||||
{
|
||||
CharacterController = GetComponent<CharacterController>();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!PlayerInputManager.IsInputEnabled || CharacterController == null)
|
||||
return;
|
||||
|
||||
HandleMovement();
|
||||
}
|
||||
|
||||
private void HandleMovement()
|
||||
{
|
||||
Vector2 input = PlayerInputManager.Instance.MoveInput;
|
||||
|
||||
Vector3 forward = transform.forward * input.y;
|
||||
Vector3 right = transform.right * input.x;
|
||||
|
||||
_moveDirection = forward + right;
|
||||
_moveDirection = _moveDirection.normalized * _moveSpeed;
|
||||
|
||||
IsMoving = _moveDirection.magnitude > 0.1f && CharacterController.isGrounded;
|
||||
|
||||
if (CharacterController.isGrounded)
|
||||
{
|
||||
_ySpeed = _groundedGravity;
|
||||
|
||||
var jumpAction = PlayerInputManager.GetJumpAction();
|
||||
if (jumpAction != null && jumpAction.WasPressedThisFrame())
|
||||
_ySpeed = _jumpForce;
|
||||
}
|
||||
else
|
||||
_ySpeed += Physics.gravity.y * Time.deltaTime;
|
||||
|
||||
_moveDirection.y = _ySpeed;
|
||||
CharacterController.Move(_moveDirection * Time.deltaTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a25b5737428861f4f8460e0ec5c93015
|
||||
Reference in New Issue
Block a user