WIP : Props placement, lighting and optimisation test
This commit is contained in:
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user