Complete reorga

This commit is contained in:
2026-03-09 17:55:23 +01:00
parent d9326e48cc
commit 10faf74405
703 changed files with 13040 additions and 1533 deletions
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 929c04453432c9844a8acb22428b5b91
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,30 @@
using UnityEngine;
namespace Unheimlich.Features.Player.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
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ebd95c7d81c32464c86497cd59c7ed2e
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,135 @@
using UnityEngine;
using UnityEngine.InputSystem;
using Unheimlich.Features.Player.Data;
using Unheimlich.Features.Dialogue;
using InputManager = Unheimlich.Core.Input.InputManager;
namespace Unheimlich.Features.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
@@ -0,0 +1,130 @@
using UnityEngine;
using Unheimlich.Core.Interaction;
using Unheimlich.World.CameraSystem;
namespace Unheimlich.Features.Player
{
/// <summary>
/// Simulates camera head bobbing motion during player movement and idle states.
/// </summary>
public class CameraHeadBob : MonoBehaviour
{
private float _timer = 0f;
private Vector3 _startLocalPos;
private HeadBobMode _overrideMode = HeadBobMode.Normal;
private bool _hasOverride = false;
private Coroutine _resetCoroutine;
void Start()
{
_startLocalPos = transform.localPosition;
}
void Update()
{
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(settings.resetSpeed);
return;
}
if (currentMode == HeadBobMode.Idle || (PlayerInspectionHandler.Instance != null && PlayerInspectionHandler.Instance.IsInspecting))
{
ApplyBob(settings.idleBobSpeed, settings.idleBobAmplitude);
return;
}
bool isMoving = PlayerController.Instance.IsMoving;
bool isRunning = PlayerController.Instance.IsRunning;
bool isGrounded = PlayerController.Instance.CharController.isGrounded;
if (isGrounded)
{
if (isMoving)
{
if (isRunning)
ApplyBob(settings.runBobSpeed, settings.runBobAmplitude);
else
ApplyBob(settings.walkBobSpeed, settings.walkBobAmplitude);
}
else
ApplyBob(settings.idleBobSpeed, settings.idleBobAmplitude);
}
else
ResetPosition(settings.resetSpeed);
}
public void SetMode(HeadBobMode mode)
{
_overrideMode = mode;
_hasOverride = true;
}
public void ClearOverride()
{
_hasOverride = false;
if (_resetCoroutine != null)
{
StopCoroutine(_resetCoroutine);
_resetCoroutine = null;
}
}
public void LerpToZero(float duration)
{
if (_resetCoroutine != null)
StopCoroutine(_resetCoroutine);
_resetCoroutine = StartCoroutine(LerpToZeroCoroutine(duration));
}
private System.Collections.IEnumerator LerpToZeroCoroutine(float duration)
{
Vector3 startPos = transform.localPosition;
float elapsed = 0f;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
float t = Mathf.Clamp01(elapsed / duration);
transform.localPosition = Vector3.Lerp(startPos, Vector3.zero, t);
yield return null;
}
transform.localPosition = Vector3.zero;
_resetCoroutine = null;
}
private void ApplyBob(float speed, float amplitude)
{
_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(float resetSpeed)
{
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,42 @@
using UnityEngine;
using UnityEngine.UI;
namespace Unheimlich.Features.Player
{
public class HandTouchFeedback : MonoBehaviour
{
[Header("Visual Settings")]
[SerializeField] private Image _feedbackImage;
[SerializeField] private Color _readyColor = Color.green;
[SerializeField] private float _pulseSpeed = 2f;
[SerializeField] private float _pulseIntensity = 0.3f;
private float _pulseTimer;
private Color _originalColor;
private void Awake()
{
if (_feedbackImage != null)
{
_originalColor = _feedbackImage.color;
}
}
private void OnEnable()
{
_pulseTimer = 0f;
}
private void Update()
{
if (_feedbackImage == null)
return;
_pulseTimer += Time.deltaTime * _pulseSpeed;
float pulse = (Mathf.Sin(_pulseTimer) + 1f) * 0.5f;
Color targetColor = Color.Lerp(_originalColor, _readyColor, pulse * _pulseIntensity);
_feedbackImage.color = targetColor;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 39b78978d656e894ea455342398ef8ba
@@ -0,0 +1,99 @@
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
using Unheimlich.Core;
namespace Unheimlich.Features.Player
{
public class ListeningVignetteEffect : Singleton<ListeningVignetteEffect>
{
[Header("References")]
[Tooltip("Dedicated Volume for listening mode (not shared with dialogue system)")]
[SerializeField] private Volume _listeningVolume;
[Header("Settings")]
[Tooltip("Target intensity when listening (0-1)")]
[SerializeField] [Range(0f, 1f)] private float _targetIntensity = 0.3f;
[Tooltip("Speed of vignette fade in/out")]
[SerializeField] private float _transitionSpeed = 2f;
private Vignette _vignette;
private bool _isEnabled;
private VolumeProfile _volumeProfile;
protected override void OnAwake()
{
if (_listeningVolume == null)
{
GameObject volumeObject = new GameObject("Listening Mode Volume");
volumeObject.transform.SetParent(transform);
_listeningVolume = volumeObject.AddComponent<Volume>();
_listeningVolume.isGlobal = true;
_listeningVolume.priority = 1;
_listeningVolume.weight = 0f;
}
if (_listeningVolume.profile == null)
{
_volumeProfile = ScriptableObject.CreateInstance<VolumeProfile>();
_listeningVolume.profile = _volumeProfile;
_vignette = _volumeProfile.Add<Vignette>();
_vignette.active = true;
_vignette.intensity.Override(0f);
_vignette.smoothness.Override(0.4f);
_vignette.rounded.Override(true);
}
else
{
if (!_listeningVolume.profile.TryGet(out _vignette))
{
_vignette = _listeningVolume.profile.Add<Vignette>();
_vignette.active = true;
_vignette.intensity.Override(0f);
_vignette.smoothness.Override(0.4f);
_vignette.rounded.Override(true);
}
}
_listeningVolume.weight = 0f;
}
private void Update()
{
if (_vignette == null || _listeningVolume == null)
return;
float targetWeight = _isEnabled ? 1f : 0f;
_listeningVolume.weight = Mathf.Lerp(_listeningVolume.weight, targetWeight, Time.deltaTime * _transitionSpeed);
if (_isEnabled)
_vignette.intensity.value = _targetIntensity;
}
public void EnableVignette(bool enable)
{
_isEnabled = enable;
}
public void SetIntensity(float intensity)
{
_targetIntensity = Mathf.Clamp01(intensity);
if (_vignette != null)
_vignette.intensity.value = _targetIntensity;
}
public void EnableVignetteWithIntensity(float intensity)
{
SetIntensity(intensity);
EnableVignette(true);
}
private void OnDestroy()
{
if (_volumeProfile != null)
Destroy(_volumeProfile);
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 878ebc270c13e5c49a3813b1848f6ea5
@@ -0,0 +1,116 @@
using UnityEngine;
using OctoCore.Components;
using Unheimlich.Core;
using Unheimlich.Features.Player.Data;
using Unheimlich.Features.Dialogue;
using Unheimlich.Core.Interaction;
using Unheimlich.Features.DetectiveBoard;
using Unheimlich.Features.Diapason;
using Unheimlich.Features.OctoArms;
using Unheimlich.World.CameraSystem;
using InputManager = Unheimlich.Core.Input.InputManager;
namespace Unheimlich.Features.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 DiapasonListeningHandlerV2 DiapasonHandler { get; private set; }
public DiapasonEquipmentManager DiapasonEquipment { get; private set; }
public PlayerInspectionHandler InspectionHandler { get; private set; }
public DetectiveBoardController DetectiveBoardController { get; private set; }
public OctoIkArmsImproved ArmsSystem { 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>();
DiapasonHandler = GetComponent<DiapasonListeningHandlerV2>();
DiapasonEquipment = GetComponent<DiapasonEquipmentManager>();
InspectionHandler = GetComponent<PlayerInspectionHandler>();
DetectiveBoardController = GetComponent<DetectiveBoardController>();
PlayerCamera = GetComponentInChildren<Camera>();
ArmsSystem = GetComponentInChildren<OctoIkArmsImproved>();
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