Advanced POC - portals, dialogue system + floating words, room loading
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
using UnityEngine;
|
||||
using Unheimlich.Interactions.Core;
|
||||
|
||||
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;
|
||||
|
||||
void Start()
|
||||
{
|
||||
_playerMovement = PlayerMovement.Instance;
|
||||
_startLocalPos = transform.localPosition;
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (_playerMovement == null || _playerMovement.CharacterController == null)
|
||||
return;
|
||||
|
||||
if (PlayerInspectionHandler.Instance != null && PlayerInspectionHandler.Instance.IsInspecting)
|
||||
{
|
||||
ApplyBob(_idleBobSpeed, _idleBobAmplitude);
|
||||
return;
|
||||
}
|
||||
|
||||
bool isMoving = _playerMovement.IsMoving;
|
||||
bool isGrounded = _playerMovement.CharacterController.isGrounded;
|
||||
|
||||
if (isGrounded)
|
||||
{
|
||||
if (isMoving)
|
||||
ApplyBob(_walkBobSpeed, _walkBobAmplitude);
|
||||
else
|
||||
ApplyBob(_idleBobSpeed, _idleBobAmplitude);
|
||||
}
|
||||
else
|
||||
ResetPosition();
|
||||
}
|
||||
|
||||
private void ApplyBob(float speed, float amplitude)
|
||||
{
|
||||
_timer += Time.deltaTime * speed;
|
||||
|
||||
float x = Mathf.Sin(_timer * 0.5f) * amplitude;
|
||||
float y = Mathf.Cos(_timer) * amplitude;
|
||||
|
||||
Vector3 newPos = _startLocalPos;
|
||||
newPos.x += x;
|
||||
newPos.y += y;
|
||||
|
||||
transform.localPosition = newPos;
|
||||
}
|
||||
|
||||
private void ResetPosition()
|
||||
{
|
||||
transform.localPosition = Vector3.Lerp(transform.localPosition, _startLocalPos, Time.deltaTime * _resetSpeed);
|
||||
|
||||
if (Vector3.Distance(transform.localPosition, _startLocalPos) < 0.001f)
|
||||
_timer = 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a2e180d338cc26242a3a9d962eea7f7c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: -200
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,22 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unheimlich.Gameplay.Player
|
||||
{
|
||||
/// <summary>
|
||||
/// Singleton player reference accessible globally throughout the game.
|
||||
/// </summary>
|
||||
public class Player : MonoBehaviour
|
||||
{
|
||||
public static Player Instance { get; private set; }
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance != null && Instance != this)
|
||||
{
|
||||
Destroy(this);
|
||||
return;
|
||||
}
|
||||
Instance = this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d47a9ebc2bd5f9345ad873a400c3d8bf
|
||||
@@ -0,0 +1,165 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
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 : MonoBehaviour
|
||||
{
|
||||
private const float INPUT_DEADZONE = 0.1f;
|
||||
private const float MOUSE_DELTA_MULTIPLIER = 0.001f;
|
||||
|
||||
public static PlayerLook Instance { get; private set; }
|
||||
|
||||
[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 float _horizontalRotation;
|
||||
private float _verticalRotation;
|
||||
private bool _isUsingGamepad = false;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance != null && Instance != this)
|
||||
{
|
||||
Destroy(this);
|
||||
return;
|
||||
}
|
||||
Instance = this;
|
||||
|
||||
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.
|
||||
/// </summary>
|
||||
public void Shake(float amplitude)
|
||||
{
|
||||
_shakeTimer -= Time.deltaTime;
|
||||
|
||||
if (_shakeTimer <= 0f)
|
||||
{
|
||||
_shakeTimer = _shakeInterval * amplitude;
|
||||
|
||||
float x = Random.Range(-1f, 1f) * amplitude;
|
||||
float y = Random.Range(-1f, 1f) * amplitude;
|
||||
|
||||
Vector3 originalPos = _playerCameraPivot.position;
|
||||
_playerCameraPivot.position = originalPos + new Vector3(x, y, 0f);
|
||||
}
|
||||
}
|
||||
|
||||
public void LockAim(bool locked)
|
||||
{
|
||||
IsAimLocked = locked;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1ddcefd49548ddb4b9bc694da032caee
|
||||
@@ -0,0 +1,73 @@
|
||||
using UnityEngine;
|
||||
using Unheimlich.Managers;
|
||||
|
||||
namespace Unheimlich.Gameplay.Player
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles player movement, jumping, and gravity using CharacterController.
|
||||
/// Provides movement state information for other systems like camera head bob.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(CharacterController))]
|
||||
public class PlayerMovement : MonoBehaviour
|
||||
{
|
||||
public static PlayerMovement Instance { get; private set; }
|
||||
|
||||
[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;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance != null && Instance != this)
|
||||
{
|
||||
Destroy(this);
|
||||
return;
|
||||
}
|
||||
Instance = this;
|
||||
|
||||
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;
|
||||
|
||||
if (PlayerInputManager.Instance.JumpAction.action.WasPressedThisFrame())
|
||||
_ySpeed = _jumpForce;
|
||||
}
|
||||
else
|
||||
_ySpeed += Physics.gravity.y * Time.deltaTime;
|
||||
|
||||
_moveDirection.y = _ySpeed;
|
||||
CharacterController.Move(_moveDirection * Time.deltaTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a25b5737428861f4f8460e0ec5c93015
|
||||
Reference in New Issue
Block a user