Integration of the octo input system and centralization of player values

This commit is contained in:
2026-02-17 17:29:27 +01:00
parent 7b48e385b2
commit 79da2e5722
38 changed files with 2831 additions and 673 deletions
@@ -0,0 +1,41 @@
using UnityEngine;
namespace Unheimlich.Core
{
/// <summary>
/// Central event system for player actions.
/// Decouples player components from each other.
/// </summary>
public static class PlayerEvents
{
// Movement events
public static event System.Action OnJump;
public static event System.Action<Vector3, Quaternion> OnTeleport;
// Camera events
public static event System.Action<float> OnCameraShake;
// Diapason events
public static event System.Action OnDiapasonEquip;
public static event System.Action OnDiapasonUnequip;
public static event System.Action OnDiapasonTap;
// Context events
public static event System.Action OnEnterDialogue;
public static event System.Action OnExitDialogue;
public static event System.Action OnEnterListening;
public static event System.Action OnExitListening;
// Trigger methods
public static void TriggerJump() => OnJump?.Invoke();
public static void TriggerTeleport(Vector3 position, Quaternion rotation) => OnTeleport?.Invoke(position, rotation);
public static void TriggerCameraShake(float intensity) => OnCameraShake?.Invoke(intensity);
public static void TriggerDiapasonEquip() => OnDiapasonEquip?.Invoke();
public static void TriggerDiapasonUnequip() => OnDiapasonUnequip?.Invoke();
public static void TriggerDiapasonTap() => OnDiapasonTap?.Invoke();
public static void TriggerEnterDialogue() => OnEnterDialogue?.Invoke();
public static void TriggerExitDialogue() => OnExitDialogue?.Invoke();
public static void TriggerEnterListening() => OnEnterListening?.Invoke();
public static void TriggerExitListening() => OnExitListening?.Invoke();
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 82279ebe597d0b84eb1f7ab31139587d
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ca9f0ebb935bc2e4eab3700478b41297
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,30 @@
using UnityEngine;
namespace Unheimlich.Data
{
[CreateAssetMenu(fileName = "CameraEffectsSettings", menuName = "Unheimlich/Camera Effects Settings")]
public class CameraEffectsSettings : ScriptableObject
{
[Header("Aim Assist")]
public float aimAssistStrength = 25f;
public float aimAssistMaxAngle = 10f;
[Header("Camera Shake")]
public float shakeFrequency = 40f;
[Header("Head Bob - Idle")]
public float idleBobSpeed = 1f;
public float idleBobAmplitude = 0.005f;
[Header("Head Bob - Walk")]
public float walkBobSpeed = 12f;
public float walkBobAmplitude = 0.02f;
[Header("Head Bob - Run")]
public float runBobSpeed = 18f;
public float runBobAmplitude = 0.03f;
[Header("Head Bob - General")]
public float resetSpeed = 5f;
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 011e98eab6596764fac103ff106476e3
@@ -2,27 +2,22 @@ using System.Collections;
using UnityEngine;
using UnityEngine.InputSystem;
using Unheimlich.Data.CameraSettings;
using PlayerInput = Unheimlich.Managers.PlayerInputManager;
using Unheimlich.Gameplay.Player;
using InputManager = Unheimlich.Managers.InputManager;
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
{
[Header("References")]
[SerializeField] private UnityEngine.Camera _camera;
[SerializeField] private Transform _playerTransform;
[Header("Default Settings")]
[SerializeField] private CameraPanSettings _defaultSettings;
private CameraPanSettings _settings;
private UnityEngine.Camera _camera;
private Transform _playerTransform;
private Vector2 _originalCameraRotation;
private Vector3 _originalCameraLocalPosition;
@@ -53,19 +48,34 @@ namespace Unheimlich.Gameplay.CameraControl
private void Awake()
{
if (_camera == null)
_camera = UnityEngine.Camera.main;
if (_playerTransform == null)
_playerTransform = transform;
// Defer initialization if PlayerController not ready
if (PlayerController.Instance != null)
{
_camera = PlayerController.Instance.PlayerCamera;
_playerTransform = PlayerController.Instance.transform;
}
if (_defaultSettings != null)
_settings = _defaultSettings;
}
private void Start()
{
// Retry if not initialized in Awake
if (_camera == null && PlayerController.Instance != null)
{
_camera = PlayerController.Instance.PlayerCamera;
_playerTransform = PlayerController.Instance.transform;
}
if (_camera != null)
{
_originalCameraLocalPosition = _camera.transform.localPosition;
_originalCameraLocalRotation = _camera.transform.localEulerAngles;
}
_originalPlayerPosition = _playerTransform.position;
if (_playerTransform != null)
_originalPlayerPosition = _playerTransform.position;
_originalPlayerRotation = _playerTransform.eulerAngles;
if (_settings == null && _defaultSettings != null)
@@ -132,9 +142,6 @@ namespace Unheimlich.Gameplay.CameraControl
_needsUpdate = false;
}
/// <summary>
/// Stops camera pan mode and returns to original state.
/// </summary>
public void StopPan()
{
IsPanning = false;
@@ -165,7 +172,7 @@ namespace Unheimlich.Gameplay.CameraControl
float lookY = 0f;
bool hasInput = false;
var lookAction = PlayerInput.GetLookAction();
var lookAction = InputManager.GetLookAction();
if (lookAction != null)
{
Vector2 lookInput = lookAction.ReadValue<Vector2>();
@@ -199,7 +206,7 @@ namespace Unheimlich.Gameplay.CameraControl
float panY = 0f;
bool hasInput = false;
var lookAction = PlayerInput.GetLookAction();
var lookAction = InputManager.GetLookAction();
if (lookAction != null)
{
Vector2 lookInput = lookAction.ReadValue<Vector2>();
@@ -1,43 +1,30 @@
using UnityEngine;
using Unheimlich.Core;
using Unheimlich.Data.CameraSettings;
using Unheimlich.Gameplay.CameraControl;
using Unheimlich.Gameplay.Player;
using PlayerInput = Unheimlich.Managers.PlayerInputManager;
using InputManager = Unheimlich.Managers.InputManager;
using Unheimlich.UI.DetectiveBoard;
namespace Unheimlich.Gameplay.DetectiveBoard
{
/// <summary>
/// Controls player interaction with the detective board in world space.
/// Uses CameraPanController for wide-angle camera movement across the board.
/// Activates when player is in range and looking at the board.
/// Controls player interaction with the detective board.
/// </summary>
public class DetectiveBoardController : MonoBehaviour
public class DetectiveBoardController : Singleton<DetectiveBoardController>
{
[Header("References")]
[SerializeField] private CameraPanController _cameraPanController;
[SerializeField] private Transform _boardTransform;
[Header("Settings")]
[SerializeField] private CameraPanSettings _boardCameraPanSettings;
[SerializeField] private float _activationRange = 2f;
[SerializeField] private LayerMask _boardLayer;
private Camera _playerCamera;
private bool _isBoardActive;
private CameraPanSettings _originalCameraPanSettings;
private CameraHeadBob _cameraHeadBob;
public bool IsBoardActive => _isBoardActive;
private void Awake()
protected override void OnAwake()
{
_playerCamera = Camera.main;
if (_cameraPanController == null)
{
_cameraPanController = FindFirstObjectByType<CameraPanController>();
}
_cameraHeadBob = FindFirstObjectByType<CameraHeadBob>();
}
@@ -53,24 +40,30 @@ namespace Unheimlich.Gameplay.DetectiveBoard
}
}
/// <summary>
/// Checks if player is looking at the board and in range to activate.
/// </summary>
private void CheckForBoardActivation()
{
if (_playerCamera == null || _boardTransform == null)
if (PlayerController.Instance == null ||
PlayerController.Instance.PlayerCamera == null ||
DetectiveBoardCanvas.Instance == null)
return;
float distanceToBoard = Vector3.Distance(_playerCamera.transform.position, _boardTransform.position);
var camera = PlayerController.Instance.PlayerCamera;
var boardTransform = DetectiveBoardCanvas.Instance.transform;
float distanceToBoard = Vector3.Distance(camera.transform.position, boardTransform.position);
if (distanceToBoard > _activationRange)
return;
Ray ray = new Ray(_playerCamera.transform.position, _playerCamera.transform.forward);
Ray ray = new Ray(camera.transform.position, camera.transform.forward);
if (Physics.Raycast(ray, out RaycastHit hit, _activationRange, _boardLayer))
{
if (hit.transform.IsChildOf(_boardTransform) || hit.transform == _boardTransform)
bool isLookingAtBoard = (hit.transform == boardTransform) ||
(boardTransform.IsChildOf(hit.transform)) ||
(hit.transform.IsChildOf(boardTransform));
if (isLookingAtBoard)
{
var interactAction = PlayerInput.GetInteractAction();
var interactAction = InputManager.GetInteractAction();
if (interactAction != null && interactAction.WasPressedThisFrame())
{
ActivateBoard();
@@ -79,12 +72,12 @@ namespace Unheimlich.Gameplay.DetectiveBoard
}
}
/// <summary>
/// Activates the detective board, switching to pan mode with wide-angle settings.
/// </summary>
public void ActivateBoard()
{
if (_cameraPanController == null || _boardCameraPanSettings == null)
if (_isBoardActive ||
PlayerController.Instance?.CameraPanController == null ||
DetectiveBoardCanvas.Instance == null ||
_boardCameraPanSettings == null)
{
Debug.LogError("[DetectiveBoardController] Missing required references!");
return;
@@ -92,71 +85,73 @@ namespace Unheimlich.Gameplay.DetectiveBoard
_isBoardActive = true;
_originalCameraPanSettings = _cameraPanController.Settings;
_cameraPanController.Settings = _boardCameraPanSettings;
_cameraPanController.SaveOriginalState();
_cameraPanController.AlignToTarget(_boardTransform, () =>
// Disable OctoPlayer completely to prevent ANY input processing
if (PlayerController.Instance.Movement != null)
{
_cameraPanController.StartPan();
});
PlayerInput.SetContext(PlayerInput.InputContext.Listening);
ApplyHeadBobSettings(_boardCameraPanSettings);
Debug.Log("[DetectiveBoardController] Detective board activated");
}
/// <summary>
/// Handles board interaction while active (panning and exit).
/// </summary>
private void HandleBoardInteraction()
{
if (_cameraPanController != null)
{
_cameraPanController.UpdatePan();
PlayerController.Instance.Movement.enabled = false;
}
var exitAction = PlayerInput.GetExitAction();
var cameraPan = PlayerController.Instance.CameraPanController;
_originalCameraPanSettings = cameraPan.Settings;
cameraPan.Settings = _boardCameraPanSettings;
// Save state for return (before alignment)
cameraPan.SaveOriginalState();
// Align to target, then start pan AFTER alignment completes
cameraPan.AlignToTarget(DetectiveBoardCanvas.Instance.transform, () =>
{
// NOW start pan using the aligned rotation as base
cameraPan.StartPan();
});
InputManager.SetContext(InputManager.InputContext.Listening);
ApplyHeadBobSettings(_boardCameraPanSettings);
}
private void HandleBoardInteraction()
{
if (PlayerController.Instance?.CameraPanController != null)
{
PlayerController.Instance.CameraPanController.UpdatePan();
}
var exitAction = InputManager.GetExitAction();
if (exitAction != null && exitAction.WasPressedThisFrame())
{
DeactivateBoard();
}
}
/// <summary>
/// Deactivates the board and returns to normal gameplay.
/// </summary>
public void DeactivateBoard()
{
if (!_isBoardActive)
if (!_isBoardActive || PlayerController.Instance?.CameraPanController == null)
return;
_isBoardActive = false;
if (_cameraPanController != null)
var cameraPan = PlayerController.Instance.CameraPanController;
cameraPan.StopPan();
cameraPan.ReturnToOriginalPosition(() =>
{
_cameraPanController.StopPan();
_cameraPanController.ReturnToOriginalPosition(() =>
if (_originalCameraPanSettings != null)
{
if (_originalCameraPanSettings != null)
{
_cameraPanController.Settings = _originalCameraPanSettings;
}
});
}
cameraPan.Settings = _originalCameraPanSettings;
}
// Re-enable OctoPlayer AFTER the return animation completes
if (PlayerController.Instance?.Movement != null)
{
PlayerController.Instance.Movement.enabled = true;
}
});
PlayerInput.SetContext(PlayerInput.InputContext.Normal);
InputManager.SetContext(InputManager.InputContext.Normal);
RestoreHeadBob();
Debug.Log("[DetectiveBoardController] Detective board deactivated");
}
/// <summary>
/// Applies head bob settings based on CameraPanSettings configuration.
/// </summary>
private void ApplyHeadBobSettings(CameraPanSettings settings)
{
if (_cameraHeadBob == null || settings == null)
@@ -165,9 +160,6 @@ namespace Unheimlich.Gameplay.DetectiveBoard
_cameraHeadBob.SetMode(settings.HeadBobMode);
}
/// <summary>
/// Restores head bob to normal behavior.
/// </summary>
private void RestoreHeadBob()
{
if (_cameraHeadBob == null)
@@ -178,11 +170,11 @@ namespace Unheimlich.Gameplay.DetectiveBoard
private void OnDrawGizmosSelected()
{
if (_boardTransform == null)
if (DetectiveBoardCanvas.Instance == null)
return;
Gizmos.color = _isBoardActive ? Color.green : Color.yellow;
Gizmos.DrawWireSphere(_boardTransform.position, _activationRange);
Gizmos.DrawWireSphere(DetectiveBoardCanvas.Instance.transform.position, _activationRange);
}
}
}
@@ -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;
@@ -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
@@ -1,7 +1,7 @@
using UnityEngine;
using Unheimlich.Core;
using Unheimlich.Managers;
using Unheimlich.Utility;
using InputManager = Unheimlich.Managers.InputManager;
namespace Unheimlich.Interactions.Core
{
@@ -44,7 +44,7 @@ namespace Unheimlich.Interactions.Core
private void HandleInteractionInput()
{
var interactAction = PlayerInputManager.GetInteractAction();
var interactAction = InputManager.GetInteractAction();
if (interactAction != null && interactAction.WasPressedThisFrame())
{
if (_currentInteractable != null)
@@ -3,13 +3,13 @@ using UnityEngine;
using UnityEngine.InputSystem;
using Unheimlich.Core;
using Unheimlich.Gameplay.Items;
using InputManager = Unheimlich.Managers.PlayerInputManager;
using Unheimlich.Gameplay.Player;
using InputManager = Unheimlich.Managers.InputManager;
namespace Unheimlich.Interactions.Core
{
/// <summary>
/// Handles player item inspection mode.
/// When inspecting, disables player movement and allows item rotation using look input.
/// </summary>
public class PlayerInspectionHandler : Singleton<PlayerInspectionHandler>
{
@@ -42,7 +42,14 @@ namespace Unheimlich.Interactions.Core
IsInspecting = true;
_currentInspectedItem = item;
InputManager.ToggleInput(false);
// Set Inspection context (handles cursor automatically)
InputManager.SetContext(InputManager.InputContext.Inspection);
// Disable OctoPlayer to prevent movement and look
if (PlayerController.Instance?.Movement != null)
{
PlayerController.Instance.Movement.enabled = false;
}
item.PrepareForInspection(_inspectionHoldPoint);
@@ -77,7 +84,17 @@ namespace Unheimlich.Interactions.Core
private IEnumerator DelayedToggleInput(bool enable)
{
yield return new WaitForEndOfFrame();
InputManager.ToggleInput(enable);
// Re-enable OctoPlayer
if (PlayerController.Instance?.Movement != null)
{
PlayerController.Instance.Movement.enabled = true;
}
// Restore Normal context (handles cursor automatically)
InputManager.SetContext(InputManager.InputContext.Normal);
yield break;
}
@@ -109,21 +126,44 @@ namespace Unheimlich.Interactions.Core
private void HandleObjectRotation()
{
if (InputManager.Instance == null) return;
if (_currentInspectedItem == null) return;
Vector2 lookInput = InputManager.Instance.LookInput;
Vector2 lookInput = Vector2.zero;
bool hasInput = false;
if (lookInput != Vector2.zero)
// Read input directly (not cached)
if (Gamepad.current != null)
{
if (Gamepad.current != null && Gamepad.current.rightStick.ReadValue().magnitude > 0.01f)
Vector2 stickInput = Gamepad.current.rightStick.ReadValue();
if (stickInput.sqrMagnitude > 0.01f)
{
lookInput = stickInput;
hasInput = true;
_isUsingGamepad = true;
else if (Mouse.current != null && Mouse.current.delta.ReadValue().magnitude > 0.01f)
}
}
if (!hasInput && Mouse.current != null)
{
Vector2 mouseDelta = Mouse.current.delta.ReadValue();
if (mouseDelta.sqrMagnitude > 0.01f)
{
lookInput = mouseDelta;
hasInput = true;
_isUsingGamepad = false;
float deltaMultiplier = _isUsingGamepad ? Time.deltaTime : 0.005f;
}
}
// Only rotate if there's actual input
if (hasInput)
{
// Normalize to get constant rotation speed regardless of input magnitude
Vector2 direction = lookInput.normalized;
float rotX = lookInput.y * _currentInspectedItem.RotationSpeed * deltaMultiplier;
float rotY = lookInput.x * _currentInspectedItem.RotationSpeed * deltaMultiplier;
float rotationMultiplier = _isUsingGamepad ? Time.deltaTime : 0.02f;
float rotX = direction.y * _currentInspectedItem.RotationSpeed * rotationMultiplier;
float rotY = direction.x * _currentInspectedItem.RotationSpeed * rotationMultiplier;
_currentInspectedItem.transform.Rotate(transform.up, -rotY, Space.World);
_currentInspectedItem.transform.Rotate(transform.right, rotX, Space.World);
@@ -10,14 +10,12 @@ namespace Unheimlich.Managers
{
/// <summary>
/// Manages gaze-based focus detection for dialogue choices using raycasts.
/// Handles progressive focus timers, aim assist integration, visual feedback,
/// and automatic choice selection when player maintains focus for a duration.
/// </summary>
public class DialogueFocusSystem
{
private readonly DialogueManager _manager;
private readonly Camera _mainCamera;
private readonly PlayerLook _playerLook;
private readonly CameraEffects _cameraEffects;
private ChoiceFocusHandler _currentFocusedHandler;
private float _focusTimer = 0f;
@@ -38,7 +36,7 @@ namespace Unheimlich.Managers
public DialogueFocusSystem(
DialogueManager manager,
Camera mainCamera,
PlayerLook playerLook,
CameraEffects cameraEffects,
LayerMask choiceLayer,
float focusRange,
float focusTime,
@@ -49,7 +47,7 @@ namespace Unheimlich.Managers
{
_manager = manager;
_mainCamera = mainCamera;
_playerLook = playerLook;
_cameraEffects = cameraEffects;
_choiceLayer = choiceLayer;
_focusRange = focusRange;
_focusTime = focusTime;
@@ -101,8 +99,8 @@ namespace Unheimlich.Managers
{
hitHandler = hit.collider.GetComponent<ChoiceFocusHandler>();
if (_playerLook != null && hitHandler != null)
_playerLook.ApplyAimAssist(hit.collider.bounds.center);
if (_cameraEffects != null && hitHandler != null)
_cameraEffects.ApplyAimAssist(hit.collider.bounds.center);
}
if (hitHandler != _currentFocusedHandler)
@@ -121,8 +119,8 @@ namespace Unheimlich.Managers
_focusTimer = 0f;
_retentionTimer = _focusRetentionTime;
if (_playerLook != null)
_playerLook.LockAim(true);
if (_cameraEffects != null)
_cameraEffects.LockAim(true);
ApplyUnfocusEffectToOthers(hitHandler);
@@ -152,11 +150,11 @@ namespace Unheimlich.Managers
DialogueAnimationSettings currentSettings = _manager.CurrentAnimationSettings;
bool shouldShake = currentSettings == null || currentSettings.EnableCameraShakeOnFocus;
if (shouldShake && _playerLook != null)
if (shouldShake && _cameraEffects != null)
{
float shakeIntensity = currentSettings != null ? currentSettings.CameraShakeIntensity : _fallbackShakeIntensity;
float shakeSmoothness = currentSettings != null ? currentSettings.CameraShakeSmoothness : 10f;
_playerLook.Shake(progress / shakeIntensity, shakeSmoothness);
_cameraEffects.ShakeCamera(progress / shakeIntensity, shakeSmoothness);
}
if (_focusTimer >= _focusTime)
@@ -183,8 +181,8 @@ namespace Unheimlich.Managers
selectedHandler.ResetFeedback(_recoveryDuration);
if (_playerLook != null)
_playerLook.LockAim(false);
if (_cameraEffects != null)
_cameraEffects.LockAim(false);
}
}
else if (_currentFocusedHandler != null && _retentionTimer <= 0f)
@@ -202,8 +200,8 @@ namespace Unheimlich.Managers
ResetAnimationTimers();
if (_playerLook != null)
_playerLook.LockAim(false);
if (_cameraEffects != null)
_cameraEffects.LockAim(false);
}
}
@@ -6,6 +6,7 @@ using Unheimlich.Gameplay.Player;
using Unheimlich.UI;
using Unheimlich.Interactions.Dialogues;
using System.Collections.Generic;
using InputManager = Unheimlich.Managers.InputManager;
namespace Unheimlich.Managers
{
@@ -41,7 +42,6 @@ namespace Unheimlich.Managers
private DialogueFocusSystem _focusSystem;
private Camera _mainCamera;
private PlayerLook _playerLook;
private DialogueAnimationSettings _currentAnimationSettings;
private DialogueUI _activeDialogueUI;
private Transform _currentChoiceAnchor;
@@ -57,13 +57,12 @@ namespace Unheimlich.Managers
private void Start()
{
_playerLook = PlayerLook.Instance;
_mainCamera = Camera.main;
_focusSystem = new DialogueFocusSystem(
this,
_mainCamera,
_playerLook,
PlayerController.Instance?.CameraEffects,
_choiceLayer,
_focusRange,
_focusTime,
@@ -107,7 +106,7 @@ namespace Unheimlich.Managers
}
}
var interactAction = PlayerInputManager.GetInteractAction();
var interactAction = InputManager.GetInteractAction();
if (interactAction != null && interactAction.WasPressedThisFrame())
{
EndDialogue();
@@ -0,0 +1,267 @@
using UnityEngine;
using OctoCore;
using OctoCore.Inputs;
using Unheimlich.Core;
namespace Unheimlich.Managers
{
/// <summary>
/// Unified input manager wrapping OctoCore inputs with Unheimlich-specific contexts.
/// Replaces the old PlayerInputManager while keeping context system (Normal, Dialogue, Listening).
/// </summary>
public class InputManager : Singleton<InputManager>
{
public enum InputContext
{
Normal, // All inputs enabled
Dialogue, // Only look and interact
Listening, // Only look and diapason
Inspection // Only look and interact (for item inspection)
}
[Header("Current State")]
[SerializeField] private InputContext _currentContext = InputContext.Normal;
// Public access to current context
public static InputContext CurrentContext => Instance ? Instance._currentContext : InputContext.Normal;
// Input values (cached for compatibility)
public static Vector2 MoveInput { get; private set; }
public static Vector2 LookInput { get; private set; }
// Legacy compatibility
public static bool IsInputEnabled => CurrentContext == InputContext.Normal;
public static bool IsLookingEnabled => CurrentContext != InputContext.Dialogue;
protected override void OnAwake()
{
// Try to subscribe, will retry in Start if needed
SubscribeAllInputs();
SetContext(InputContext.Normal);
}
private void Start()
{
Debug.Log("[InputManager] Start() called");
if (OctoCore.OctoCore.Instance == null)
{
Debug.LogError("[InputManager] OctoCore.Instance is NULL!");
return;
}
Debug.Log("[InputManager] OctoCore.Instance exists");
if (OctoCore.OctoCore.Instance.Inputs == null)
{
Debug.LogError("[InputManager] OctoCore.Instance.Inputs is NULL!");
return;
}
Debug.Log("[InputManager] OctoCore.Instance.Inputs exists");
// Ensure we're subscribed
SubscribeAllInputs();
Debug.Log("[InputManager] Initialized and subscribed to OctoCore inputs.");
}
private void OnDestroy()
{
UnsubscribeAllInputs();
}
// Subscribe to OctoCore inputs
private void SubscribeAllInputs()
{
if (OctoCore.OctoCore.Instance == null) return;
OctoCore.OctoCore.Instance.Inputs.SubscribeAction(OctoActions.Player_Move, OnMove);
OctoCore.OctoCore.Instance.Inputs.SubscribeAction(OctoActions.Player_Look, OnLook);
OctoCore.OctoCore.Instance.Inputs.SubscribeAction(OctoActions.Player_Jump, OnJump);
OctoCore.OctoCore.Instance.Inputs.SubscribeAction(OctoActions.Player_Interact, OnInteract);
OctoCore.OctoCore.Instance.Inputs.SubscribeAction(OctoActions.Player_Diapason, OnDiapason);
OctoCore.OctoCore.Instance.Inputs.SubscribeAction(OctoActions.Player_Fire, OnFire);
OctoCore.OctoCore.Instance.Inputs.SubscribeAction(OctoActions.Player_Exit, OnExit);
}
private void UnsubscribeAllInputs()
{
if (OctoCore.OctoCore.Instance == null) return;
OctoCore.OctoCore.Instance.Inputs.UnsubscribeAction(OctoActions.Player_Move, OnMove);
OctoCore.OctoCore.Instance.Inputs.UnsubscribeAction(OctoActions.Player_Look, OnLook);
OctoCore.OctoCore.Instance.Inputs.UnsubscribeAction(OctoActions.Player_Jump, OnJump);
OctoCore.OctoCore.Instance.Inputs.UnsubscribeAction(OctoActions.Player_Interact, OnInteract);
OctoCore.OctoCore.Instance.Inputs.UnsubscribeAction(OctoActions.Player_Diapason, OnDiapason);
OctoCore.OctoCore.Instance.Inputs.UnsubscribeAction(OctoActions.Player_Fire, OnFire);
OctoCore.OctoCore.Instance.Inputs.UnsubscribeAction(OctoActions.Player_Exit, OnExit);
}
/// <summary>
/// Sets input context and handles cursor/component state
/// </summary>
public static void SetContext(InputContext context)
{
if (Instance == null) return;
Instance._currentContext = context;
switch (context)
{
case InputContext.Normal:
// All inputs active
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
break;
case InputContext.Dialogue:
// Only look and interact (movement disabled)
Cursor.lockState = CursorLockMode.None;
Cursor.visible = true;
break;
case InputContext.Listening:
// Only look, diapason, fire, exit (movement disabled)
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
break;
case InputContext.Inspection:
// Only look and interact (for item inspection, movement disabled)
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
break;
}
}
// Legacy compatibility
public static void ToggleInput(bool enable)
{
SetContext(enable ? InputContext.Normal : InputContext.Dialogue);
}
// Input callbacks - cache values and filter by context
private void OnMove(Vector2 value)
{
MoveInput = _currentContext == InputContext.Normal ? value : Vector2.zero;
}
private void OnLook(Vector2 value)
{
LookInput = value; // Look enabled in all contexts
}
private void OnJump()
{
if (_currentContext != InputContext.Normal) return;
// Broadcast jump event if needed
}
private void OnInteract()
{
if (_currentContext == InputContext.Listening) return;
// Broadcast interact event
}
private void OnDiapason()
{
if (_currentContext == InputContext.Dialogue) return;
// Broadcast diapason event
}
private void OnFire()
{
if (_currentContext != InputContext.Listening) return;
// Broadcast fire event
}
private void OnExit()
{
if (_currentContext != InputContext.Listening) return;
// Broadcast exit event
}
// Public accessors for backward compatibility
public static Vector2 GetMoveInput() => MoveInput;
public static Vector2 GetLookInput() => LookInput;
// Action accessors for direct button checks
public static UnityEngine.InputSystem.InputAction GetMoveAction()
{
var asset = OctoCore.OctoCore.Instance?.Inputs?.Actions;
if (asset == null) return null;
foreach (var actionInfo in asset)
{
if (actionInfo.Name == "Move" && actionInfo.Reference != null)
return actionInfo.Reference.action;
}
return null;
}
public static UnityEngine.InputSystem.InputAction GetDiapasonAction()
{
var asset = OctoCore.OctoCore.Instance?.Inputs?.Actions;
if (asset == null) return null;
foreach (var actionInfo in asset)
{
if (actionInfo.Name == "Diapason" && actionInfo.Reference != null)
return actionInfo.Reference.action;
}
return null;
}
public static UnityEngine.InputSystem.InputAction GetInteractAction()
{
var asset = OctoCore.OctoCore.Instance?.Inputs?.Actions;
if (asset == null) return null;
foreach (var actionInfo in asset)
{
if (actionInfo.Name == "Interact" && actionInfo.Reference != null)
return actionInfo.Reference.action;
}
return null;
}
public static UnityEngine.InputSystem.InputAction GetFireAction()
{
var asset = OctoCore.OctoCore.Instance?.Inputs?.Actions;
if (asset == null) return null;
foreach (var actionInfo in asset)
{
if (actionInfo.Name == "Fire" && actionInfo.Reference != null)
return actionInfo.Reference.action;
}
return null;
}
public static UnityEngine.InputSystem.InputAction GetExitAction()
{
var asset = OctoCore.OctoCore.Instance?.Inputs?.Actions;
if (asset == null) return null;
foreach (var actionInfo in asset)
{
if (actionInfo.Name == "Exit" && actionInfo.Reference != null)
return actionInfo.Reference.action;
}
return null;
}
public static UnityEngine.InputSystem.InputAction GetLookAction()
{
var asset = OctoCore.OctoCore.Instance?.Inputs?.Actions;
if (asset == null) return null;
foreach (var actionInfo in asset)
{
if (actionInfo.Name == "Look" && actionInfo.Reference != null)
return actionInfo.Reference.action;
}
return null;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: e340839b22a784542ba8afdfde612c2e
@@ -1,179 +0,0 @@
using UnityEngine;
using UnityEngine.InputSystem;
using Unheimlich.Core;
using Unheimlich.Gameplay.Player;
namespace Unheimlich.Managers
{
/// <summary>
/// Centralizes Input System handling for the player.
/// Manages input state, action references, and global input enable/disable functionality.
/// Supports different input contexts (Normal, Dialogue, Listening) to control which inputs are active.
/// </summary>
[RequireComponent(typeof(PlayerInput))]
public class PlayerInputManager : Singleton<PlayerInputManager>
{
/// <summary>
/// Defines different input contexts that control which player actions are enabled.
/// </summary>
public enum InputContext
{
Normal, // All inputs enabled (movement, look, jump, interact, diapason)
Dialogue, // Only look and interact enabled (for dialogues/UI)
Listening // Only look enabled, movement disabled (for diapason camera pan)
}
[Header("Input Values (Read only)")]
public Vector2 MoveInput;
public Vector2 LookInput;
public static bool IsInputEnabled { get; private set; } = true;
public static bool IsLookingEnabled { get; private set; } = true;
public static InputContext CurrentContext { get; private set; } = InputContext.Normal;
private InputActionMap _playerActionMap;
private InputAction _moveAction;
private InputAction _lookAction;
private InputAction _jumpAction;
private InputAction _interactAction;
private InputAction _diapasonAction;
private InputAction _fireAction;
private InputAction _exitAction;
private PlayerMovement _playerMovement;
private PlayerLook _playerLook;
private CameraHeadBob _headBob;
protected override void OnAwake()
{
IsInputEnabled = true;
PlayerInput playerInput = GetComponent<PlayerInput>();
playerInput.actions.Enable();
_playerActionMap = playerInput.actions.FindActionMap("Player");
_moveAction = _playerActionMap.FindAction("Move");
_lookAction = _playerActionMap.FindAction("Look");
_jumpAction = _playerActionMap.FindAction("Jump");
_interactAction = _playerActionMap.FindAction("Interact");
_diapasonAction = _playerActionMap.FindAction("Diapason");
_fireAction = _playerActionMap.FindAction("Fire");
_exitAction = _playerActionMap.FindAction("Exit");
_playerMovement = GetComponent<PlayerMovement>();
_playerLook = GetComponent<PlayerLook>();
_headBob = GetComponentInChildren<CameraHeadBob>();
}
/// <summary>
/// Sets the input context, controlling which inputs and player components are active.
/// </summary>
public static void SetContext(InputContext context)
{
if (Instance == null) return;
CurrentContext = context;
switch (context)
{
case InputContext.Normal:
Instance._playerActionMap.Enable();
Instance.SetComponentsEnabled(true, true, true);
IsInputEnabled = true;
IsLookingEnabled = true;
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
break;
case InputContext.Dialogue:
Instance._playerActionMap.Disable();
Instance._lookAction.Enable();
Instance._interactAction.Enable();
if (Instance._diapasonAction != null)
Instance._diapasonAction.Enable();
Instance.SetComponentsEnabled(false, false, false);
IsInputEnabled = false;
IsLookingEnabled = false;
break;
case InputContext.Listening:
Instance._playerActionMap.Disable();
Instance._lookAction.Enable();
if (Instance._diapasonAction != null)
Instance._diapasonAction.Enable();
if (Instance._fireAction != null)
Instance._fireAction.Enable();
if (Instance._exitAction != null)
Instance._exitAction.Enable();
Instance.SetComponentsEnabled(false, false, true);
IsInputEnabled = false;
IsLookingEnabled = false;
break;
}
}
/// <summary>
/// Enables or disables player movement components.
/// </summary>
private void SetComponentsEnabled(bool movement, bool look, bool headBob)
{
if (_playerMovement != null)
_playerMovement.enabled = movement;
if (_playerLook != null)
_playerLook.enabled = look;
if (_headBob != null)
_headBob.enabled = headBob;
}
/// <summary>
/// Globally toggles player input on/off. When disabled, only look and interact actions remain active.
/// Used during dialogues, inspections, or UI interactions.
/// LEGACY: Use SetContext(InputContext.Dialogue) instead.
/// </summary>
public static void ToggleInput(bool enable)
{
SetContext(enable ? InputContext.Normal : InputContext.Dialogue);
}
public void OnMove(InputAction.CallbackContext context)
{
MoveInput = context.ReadValue<Vector2>();
}
public void Look(InputAction.CallbackContext context)
{
LookInput = context.ReadValue<Vector2>();
}
public void OnJump(InputAction.CallbackContext context)
{
}
public void OnInteract(InputAction.CallbackContext context)
{
}
public void OnDiapason(InputAction.CallbackContext context)
{
}
public void OnFire(InputAction.CallbackContext context)
{
}
public void OnExit(InputAction.CallbackContext context)
{
}
/// <summary>
/// Provides public access to input actions for other systems.
/// </summary>
public static InputAction GetMoveAction() => Instance?._moveAction;
public static InputAction GetLookAction() => Instance?._lookAction;
public static InputAction GetJumpAction() => Instance?._jumpAction;
public static InputAction GetInteractAction() => Instance?._interactAction;
public static InputAction GetDiapasonAction() => Instance?._diapasonAction;
public static InputAction GetFireAction() => Instance?._fireAction;
public static InputAction GetExitAction() => Instance?._exitAction;
}
}
@@ -1,13 +0,0 @@
fileFormatVersion: 2
guid: 860eff61af1c75243b697035c7b7fc83
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences:
- JumpAction: {fileID: -2099379676528639254, guid: 052faaac586de48259a63d0c4782560b, type: 3}
- InteractAction: {fileID: 1781555164194001046, guid: 052faaac586de48259a63d0c4782560b, type: 3}
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -4,10 +4,11 @@ using System.Collections.Generic;
using System.Linq;
using Unheimlich.Data.DetectiveBoard;
using Unheimlich.Managers;
using Unheimlich.Core;
namespace Unheimlich.UI.DetectiveBoard
{
public class DetectiveBoardCanvas : MonoBehaviour
public class DetectiveBoardCanvas : Singleton<DetectiveBoardCanvas>
{
[Header("References")]
[SerializeField] private Canvas _canvas;
@@ -60,7 +61,7 @@ namespace Unheimlich.UI.DetectiveBoard
ToggleNodeEliminated(node);
}
private void Awake()
protected override void OnAwake()
{
if (_canvas == null)
_canvas = GetComponent<Canvas>();