WIP : Props placement, lighting and optimisation test

This commit is contained in:
Ferlat Thibaud
2026-04-20 11:03:13 +02:00
parent f982a74462
commit 5739293a06
222 changed files with 26111 additions and 2493 deletions
@@ -9,63 +9,86 @@ namespace Unheimlich.World.CameraSystem
///
/// Layered in three passes:
/// 1. Continuous Perlin drift — organic idle tremor, scales with movement and stress.
/// 2. Micro-jolts — randomised snap/recoil spikes, more frequent under stress.
/// 2. Micro-jolts — randomised snap/recoil spikes, only fire when the camera
/// is steady (attenuated during active mouse look).
/// 3. Inertia swinging — the camera "lags" behind head rotation on all axes,
/// simulating the physical weight of a handheld camcorder.
///
/// Relies on its own dedicated GameObject (LostFootagePivot) so it never conflicts
/// with <see cref="CameraHeadBob"/> which owns the parent pivot's rotation.
/// Hierarchy expected:
/// OctoPlayerFP (mouse-look root)
/// └── CameraPivot (CameraHeadBob lives here)
/// └── LostFootagePivot ← this component
/// └── Camera Player
///
/// Mouse-look speed is sampled on OctoPlayerFP (grandparent) to avoid being
/// polluted by the continuous rotations CameraHeadBob writes on CameraPivot.
/// Movement blend (moveT) is smoothed to avoid noise amplitude spikes on
/// direction changes.
/// </summary>
public class LostFootageRig : MonoBehaviour
{
// ── Inspector — Noise ─────────────────────────────────────────────────────
[Header("Noise — idle")]
[SerializeField] private float _idleAmplitude = 0.25f;
[SerializeField] private float _idleNoiseSpeed = 0.6f;
[SerializeField] private float _idleAmplitude = 1f;
[SerializeField] private float _idleNoiseSpeed = 0.3f;
[Header("Noise — walk")]
[SerializeField] private float _walkAmplitude = 0.8f;
[SerializeField] private float _walkNoiseSpeed = 1.4f;
[SerializeField] private float _walkAmplitude = 3f;
[SerializeField] private float _walkNoiseSpeed = 0.4f;
[Header("Noise — run")]
[SerializeField] private float _runAmplitude = 1.8f;
[SerializeField] private float _runNoiseSpeed = 2.5f;
[SerializeField] private float _runAmplitude = 5f;
[SerializeField] private float _runNoiseSpeed = 0.6f;
[Header("Noise — stress scale")]
[SerializeField] private float _stressAmplitudeMul = 2.5f;
[SerializeField] private float _stressSpeedMul = 1.8f;
[SerializeField] private float _stressSpeedMul = 1f;
[Header("Noise — smoothing")]
[Tooltip("How fast the noise amplitude blends between idle/walk/run. Lower = smoother transitions.")]
[SerializeField] private float _moveTSmoothSpeed = 4f;
// ── Inspector — Micro-jolts ───────────────────────────────────────────────
[Header("Micro-jolts — idle")]
[SerializeField] private float _idleJoltAmplitude = 0.5f;
[SerializeField] private Vector2 _idleJoltIntervalRange = new Vector2(2f, 4f);
[SerializeField] private float _idleJoltAmplitude = 1.5f;
[SerializeField] private Vector2 _idleJoltIntervalRange = new Vector2(0.5f, 1.5f);
[Header("Micro-jolts — walk")]
[SerializeField] private float _walkJoltAmplitude = 1.2f;
[SerializeField] private Vector2 _walkJoltIntervalRange = new Vector2(0.8f, 2f);
[SerializeField] private float _walkJoltAmplitude = 3f;
[SerializeField] private Vector2 _walkJoltIntervalRange = new Vector2(2f, 4f);
[Header("Micro-jolts — run")]
[SerializeField] private float _runJoltAmplitude = 2.5f;
[SerializeField] private Vector2 _runJoltIntervalRange = new Vector2(0.2f, 0.8f);
[SerializeField] private float _runJoltAmplitude = 5f;
[SerializeField] private Vector2 _runJoltIntervalRange = new Vector2(1f, 2f);
[Header("Micro-jolts — shared")]
[SerializeField] private float _joltSnapFraction = 0.5f; // plus rapide que ton 0.25 original
[SerializeField] private float _stressJoltFreqMul = 5f;
[SerializeField] private float _stressJoltAmpMul = 3f;
[Tooltip("How fast the jolt snaps to its target (fraction of interval).")]
[SerializeField] private float _joltSnapFraction = 0.1f;
[Tooltip("Jolt interval is divided by this value when stress = 1.")]
[SerializeField] private float _stressJoltFreqMul = 2f;
[Tooltip("Jolt amplitude is multiplied by this value when stress = 1.")]
[SerializeField] private float _stressJoltAmpMul = 1.4f;
[Tooltip("Look speed (deg/s) above which jolts are fully suppressed. Sampled on OctoPlayerFP to bypass CameraHeadBob noise.")]
[SerializeField] private float _joltLookSuppressSpeed = 5f;
// ── Inspector — Inertia swing ─────────────────────────────────────────────
[Header("Inertia swing")]
[Tooltip("How strongly the camera lags behind movement. Higher = more swing.")]
[SerializeField] private float _inertiaStrength = 4f;
[Tooltip("How fast the inertia offset returns to zero. Lower = longer swing tail.")]
[SerializeField] private float _inertiaDamping = 6f;
[Tooltip("Maximum swing angle (degrees) on each axis to avoid excessive drift.")]
[SerializeField] private float _inertiaMaxAngle = 8f;
// ── Inspector — Fade ──────────────────────────────────────────────────────
[Header("Fade")]
[Tooltip("Duration (seconds) of the fade-in / fade-out when toggling the rig.")]
[SerializeField] private float _fadeDuration = 0.6f;
// ── Inspector — Debug ─────────────────────────────────────────────────────
[Header("Debug")]
@@ -81,12 +104,18 @@ namespace Unheimlich.World.CameraSystem
public bool IsActive { get; private set; }
private float _weight;
private float _noiseTimer;
private Vector3 _noiseOffset;
private Vector3 _joltRotation;
private Vector3 _inertiaOffset;
// Inertia — tracks angular velocity of the parent to derive swing offset.
private Quaternion _lastParentRotation;
private Vector3 _inertiaOffset;
// Smoothed movement blend — avoids amplitude spikes on direction changes.
private float _smoothMoveT;
// Mouse-look speed sampled on OctoPlayerFP (grandparent) — used to gate
// micro-jolts so they only fire when the player is not actively looking around.
private Quaternion _lastLookRootRotation;
private float _lookSpeed;
private Coroutine _fadeCoroutine;
private Coroutine _joltCoroutine;
@@ -104,9 +133,10 @@ namespace Unheimlich.World.CameraSystem
private void Start()
{
_lastParentRotation = transform.parent != null
? transform.parent.rotation
: transform.rotation;
Transform lookRoot = transform.parent?.parent;
_lastLookRootRotation = lookRoot != null
? lookRoot.localRotation
: Quaternion.identity;
}
private void Update()
@@ -116,7 +146,7 @@ namespace Unheimlich.World.CameraSystem
if (_weight <= 0f)
{
_UpdateInertia(); // keep tracking even when invisible
UpdateInertia();
return;
}
@@ -129,7 +159,7 @@ namespace Unheimlich.World.CameraSystem
if (Unheimlich.Features.Player.PlayerController.Instance?.CharController != null)
{
var vel = Unheimlich.Features.Player.PlayerController.Instance.CharController.velocity;
var vel = Unheimlich.Features.Player.PlayerController.Instance.CharController.velocity;
horizontalSpeed = new Vector3(vel.x, 0f, vel.z).magnitude;
isRunning = OctoCore.Components.OctoPlayer.Instance != null
&& OctoCore.Components.OctoPlayer.Instance.IsRunning;
@@ -138,22 +168,27 @@ namespace Unheimlich.World.CameraSystem
const float WalkSpeedMs = 10f / 3.6f;
const float RunSpeedMs = 20f / 3.6f;
float maxSpeed = isRunning ? RunSpeedMs : WalkSpeedMs;
float moveT = Mathf.Clamp01(horizontalSpeed / maxSpeed);
float maxSpeed = isRunning ? RunSpeedMs : WalkSpeedMs;
float rawMoveT = Mathf.Clamp01(horizontalSpeed / maxSpeed);
// Smooth moveT to avoid abrupt amplitude jumps on direction changes.
_smoothMoveT = Mathf.Lerp(_smoothMoveT, rawMoveT, Time.deltaTime * _moveTSmoothSpeed);
// ── 2. Interpolate noise parameters from movement state ───────────────
float targetAmp = isRunning
? Mathf.Lerp(_walkAmplitude, _runAmplitude, moveT)
: Mathf.Lerp(_idleAmplitude, _walkAmplitude, moveT);
float targetAmp = isRunning
? Mathf.Lerp(_walkAmplitude, _runAmplitude, _smoothMoveT)
: Mathf.Lerp(_idleAmplitude, _walkAmplitude, _smoothMoveT);
float targetSpeed = isRunning
? Mathf.Lerp(_walkNoiseSpeed, _runNoiseSpeed, moveT)
: Mathf.Lerp(_idleNoiseSpeed, _walkNoiseSpeed, moveT);
? Mathf.Lerp(_walkNoiseSpeed, _runNoiseSpeed, _smoothMoveT)
: Mathf.Lerp(_idleNoiseSpeed, _walkNoiseSpeed, _smoothMoveT);
float amp = (targetAmp + stress * _stressAmplitudeMul) * _weight;
float spd = (targetSpeed + stress * _stressSpeedMul);
float t = Time.time * spd;
float amp = (targetAmp + stress * _stressAmplitudeMul) * _weight;
float spd = (targetSpeed + stress * _stressSpeedMul);
_noiseTimer += Time.deltaTime * spd;
if (_noiseTimer > 1000f) _noiseTimer -= 1000f; // wrap to avoid float precision loss
float t = _noiseTimer;
// ── 3. Perlin drift ───────────────────────────────────────────────────
@@ -163,7 +198,7 @@ namespace Unheimlich.World.CameraSystem
// ── 4. Inertia swing ──────────────────────────────────────────────────
_UpdateInertia();
UpdateInertia();
Vector3 swing = _inertiaOffset * _weight;
// ── 5. Compose and apply ──────────────────────────────────────────────
@@ -205,30 +240,41 @@ namespace Unheimlich.World.CameraSystem
// ── Private helpers ───────────────────────────────────────────────────────
/// <summary>
/// Computes the inertia swing offset from the delta between the parent's
/// current and previous rotation, then damps it back toward zero.
/// Drives inertia swing from CharController velocity projected into local camera space,
/// avoiding the rotation-delta approach which caused spikes whenever CameraHeadBob
/// changed phase. Also samples OctoPlayerFP (grandparent) rotation delta to measure
/// true mouse-look speed for jolt gating.
/// </summary>
private void _UpdateInertia()
private void UpdateInertia()
{
Quaternion parentRot = transform.parent != null
? transform.parent.rotation
: Quaternion.identity;
// ── Inertia from CharController velocity ──────────────────────────────
Quaternion delta = parentRot * Quaternion.Inverse(_lastParentRotation);
delta.ToAngleAxis(out float angle, out Vector3 axis);
Vector3 velocity = Vector3.zero;
// Wrap angle to [-180, 180] to avoid 360° flips.
if (angle > 180f) angle -= 360f;
if (Unheimlich.Features.Player.PlayerController.Instance?.CharController != null)
velocity = Unheimlich.Features.Player.PlayerController.Instance.CharController.velocity;
// Angular velocity this frame → drive inertia offset.
Vector3 angularVel = axis * (angle / Mathf.Max(Time.deltaTime, 0.0001f));
_inertiaOffset -= angularVel * (_inertiaStrength * Time.deltaTime);
_inertiaOffset = Vector3.ClampMagnitude(_inertiaOffset, _inertiaMaxAngle);
// Project velocity into local camera space so the swing feels attached to the view.
Vector3 localVel = transform.parent != null
? transform.parent.InverseTransformDirection(velocity)
: velocity;
// Damp back toward zero.
_inertiaOffset = Vector3.Lerp(_inertiaOffset, Vector3.zero, _inertiaDamping * Time.deltaTime);
Vector3 targetInertia = new Vector3(-localVel.z, 0f, localVel.x) * (_inertiaStrength * 0.01f);
targetInertia = Vector3.ClampMagnitude(targetInertia, _inertiaMaxAngle);
_lastParentRotation = parentRot;
_inertiaOffset = Vector3.Lerp(_inertiaOffset, targetInertia, _inertiaDamping * Time.deltaTime);
// ── Mouse-look speed from OctoPlayerFP (grandparent) ─────────────────
Transform lookRoot = transform.parent?.parent;
Quaternion lookRot = lookRoot != null ? lookRoot.localRotation : Quaternion.identity;
Quaternion lookDelta = lookRot * Quaternion.Inverse(_lastLookRootRotation);
lookDelta.ToAngleAxis(out float lookAngle, out _);
if (lookAngle > 180f) lookAngle -= 360f;
_lookSpeed = Mathf.Abs(lookAngle) / Mathf.Max(Time.deltaTime, 0.0001f);
_lastLookRootRotation = lookRot;
}
private IEnumerator FadeCoroutine(bool fadeIn)
@@ -260,36 +306,25 @@ namespace Unheimlich.World.CameraSystem
{
float stress = Mathf.Clamp01(StressLevel);
// Resolve jolt parameters from movement state.
float horizontalSpeed = 0f;
bool isRunning = false;
// Use the same smoothed moveT as Update to keep jolt parameters consistent.
float joltAmp = isRunning()
? Mathf.Lerp(_walkJoltAmplitude, _runJoltAmplitude, _smoothMoveT)
: Mathf.Lerp(_idleJoltAmplitude, _walkJoltAmplitude, _smoothMoveT);
if (Unheimlich.Features.Player.PlayerController.Instance?.CharController != null)
{
var vel = Unheimlich.Features.Player.PlayerController.Instance.CharController.velocity;
horizontalSpeed = new Vector3(vel.x, 0f, vel.z).magnitude;
isRunning = OctoCore.Components.OctoPlayer.Instance != null
&& OctoCore.Components.OctoPlayer.Instance.IsRunning;
}
const float WalkSpeedMs = 10f / 3.6f;
float maxSpeed = isRunning ? 20f / 3.6f : WalkSpeedMs;
float moveT = Mathf.Clamp01(horizontalSpeed / maxSpeed);
float joltAmp = isRunning
? Mathf.Lerp(_walkJoltAmplitude, _runJoltAmplitude, moveT)
: Mathf.Lerp(_idleJoltAmplitude, _walkJoltAmplitude, moveT);
Vector2 intervalRange = isRunning
? Vector2.Lerp(_walkJoltIntervalRange, _runJoltIntervalRange, moveT)
: Vector2.Lerp(_idleJoltIntervalRange, _walkJoltIntervalRange, moveT);
Vector2 intervalRange = isRunning()
? Vector2.Lerp(_walkJoltIntervalRange, _runJoltIntervalRange, _smoothMoveT)
: Vector2.Lerp(_idleJoltIntervalRange, _walkJoltIntervalRange, _smoothMoveT);
float interval = Random.Range(intervalRange.x, intervalRange.y);
interval /= Mathf.Lerp(1f, _stressJoltFreqMul, stress);
yield return new WaitForSeconds(interval);
float ampScale = (1f + stress * _stressJoltAmpMul) * _weight;
// Jolts only fire when the camera is steady — steadyFactor is 1 when still,
// 0 when the player is actively looking around.
float steadyFactor = 1f - Mathf.Clamp01(_lookSpeed / _joltLookSuppressSpeed);
float ampScale = (1f + stress * _stressJoltAmpMul) * _weight * steadyFactor;
Vector3 target = new Vector3(
Random.Range(-1f, 1f) * joltAmp * ampScale,
Random.Range(-1f, 1f) * joltAmp * ampScale,
@@ -300,6 +335,13 @@ namespace Unheimlich.World.CameraSystem
}
}
/// <summary>Returns true if the player is currently running.</summary>
private bool isRunning()
{
return OctoCore.Components.OctoPlayer.Instance != null
&& OctoCore.Components.OctoPlayer.Instance.IsRunning;
}
/// <summary>
/// Snaps to <paramref name="target"/> in the first fraction of the interval,
/// then eases back to zero for the remainder — simulating a hand recoil.