Fix : House of leaves compiler bug

This commit is contained in:
2026-09-22 13:24:09 +02:00
parent 519fb0155a
commit e95980d88c
2 changed files with 248 additions and 76 deletions
@@ -8,11 +8,13 @@ namespace Unheimlich.World.HouseOfLeaves
/// <summary> /// <summary>
/// Orchestrates the House of Leaves "endless corridor" illusion: /// Orchestrates the House of Leaves "endless corridor" illusion:
/// - The exit is slowly pushed away as the player walks, so the corridor /// - The exit is slowly pushed away as the player walks, so the corridor
/// never seems to actually run out (progress feels real, but is deliberately /// never seems to actually run out.
/// much slower than the player's own walking speed - no speed/FOV trickery involved). /// - A darkness/vignette effect kicks in if the player turns back.
/// - A darkness/vignette effect kicks in if the player turns back towards /// - Exposes a normalized Progression value [0,1] for visual effects
/// where they came from, discouraging backtracking. /// such as sand, walls, shaders, etc.
/// No fixed corridor length is required - this works indefinitely. ///
/// The corridor itself remains infinite. Progression only represents
/// visual advancement over a configurable distance.
/// </summary> /// </summary>
public class HouseOfLeavesController : MonoBehaviour public class HouseOfLeavesController : MonoBehaviour
{ {
@@ -22,25 +24,51 @@ namespace Unheimlich.World.HouseOfLeaves
[Header("Endless Corridor (Receding Exit)")] [Header("Endless Corridor (Receding Exit)")]
[Tooltip("The transform representing the visual end of the corridor (exit door, light at the end, etc). " + [Tooltip(
"It will be slowly pushed further away as the player walks, to create the illusion of an endless corridor.")] "The transform representing the visual end of the corridor " +
[SerializeField] private Transform _exit; "(exit door, light at the end, etc).")]
[SerializeField]
private Transform _exit;
[Tooltip("Fraction of each meter walked that pushes the exit further away. " + [Tooltip(
"0 = exit never recedes (normal corridor). Close to 1 = exit recedes almost as fast as the player walks " + "Fraction of each meter walked that pushes the exit further away. " +
"(barely any felt progress). Keep this comfortably below 1 so the player still senses real progress, just a lot slower than expected.")] "0 = exit never recedes. Close to 1 = exit recedes almost as fast " +
[SerializeField, Range(0f, 0.95f)] private float _recedeFactor = 0.4f; "as the player walks.")]
[SerializeField, Range(0f, 0.95f)]
private float _recedeFactor = 0.4f;
[Tooltip(
"How slowly the exit catches up to its receded target, in seconds.")]
[SerializeField, Min(0.01f)]
private float _recedeSmoothTime = 2f;
[Header("Visual Progression")]
[Tooltip(
"Distance in meters required for visual effects to reach " +
"Progression = 1. The corridor itself remains infinite.")]
[SerializeField, Min(0.01f)]
private float _progressionDistance = 50f;
[Tooltip("How slowly the exit's position catches up to its receded target, in seconds. " +
"Higher values make the shift smoother and harder to consciously notice.")]
[SerializeField] private float _recedeSmoothTime = 2f;
[Header("Darkness (Turning Back)")] [Header("Darkness (Turning Back)")]
[SerializeField] private Volume _postProcessVolume;
[SerializeField] private float _turningBackAngle = 100f; [SerializeField]
[SerializeField] [Range(0f, 1f)] private float _maxVignetteIntensity = 0.6f; private Volume _postProcessVolume;
[SerializeField] private float _maxExposureReduction = -3f;
[SerializeField] private float _darknessLerpSpeed = 2f; [SerializeField]
private float _turningBackAngle = 100f;
[SerializeField, Range(0f, 1f)]
private float _maxVignetteIntensity = 0.6f;
[SerializeField]
private float _maxExposureReduction = -3f;
[SerializeField]
private float _darknessLerpSpeed = 2f;
// ───────────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────────
// Private Fields // Private Fields
@@ -50,36 +78,45 @@ namespace Unheimlich.World.HouseOfLeaves
private ColorAdjustments _colorAdjustments; private ColorAdjustments _colorAdjustments;
private Vector3 _lastPlayerPosition; private Vector3 _lastPlayerPosition;
private float _distanceTravelled; // Unbounded - there is no fixed corridor length anymore.
// Unbounded distance travelled through the corridor.
private float _distanceTravelled;
private Vector3 _lightForwardDirection; private Vector3 _lightForwardDirection;
private Vector3 _exitRestPosition; // The exit's original position, used as the recede baseline. private Vector3 _exitRestPosition;
private bool _isInitialized; private bool _isInitialized;
// ───────────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────────
// Unity Messages // Unity Messages
// ───────────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────────
private void Start() private void Start()
{ {
if (!TryResolvePlayerReferences()) return; if (!TryResolvePlayerReferences())
return;
InitializeLightDirection(); InitializeLightDirection();
InitializePostProcess(); InitializePostProcess();
InitializeExit(); InitializeExit();
_lastPlayerPosition = PlayerController.Instance.transform.position; _lastPlayerPosition = PlayerController.Instance.transform.position;
_isInitialized = true; _isInitialized = true;
} }
private void Update() private void Update()
{ {
if (!_isInitialized) return; if (!_isInitialized)
return;
TrackDistance(); TrackDistance();
TickExitRecede(); TickExitRecede();
TickDarkness(); TickDarkness();
} }
// ───────────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────────
// Initialization // Initialization
// ───────────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────────
@@ -88,7 +125,10 @@ namespace Unheimlich.World.HouseOfLeaves
{ {
if (PlayerController.Instance == null) if (PlayerController.Instance == null)
{ {
Debug.LogError("[HouseOfLeavesController] PlayerController.Instance not found."); Debug.LogError(
"[HouseOfLeavesController] PlayerController.Instance not found."
);
return false; return false;
} }
@@ -96,14 +136,16 @@ namespace Unheimlich.World.HouseOfLeaves
} }
/// <summary> /// <summary>
/// The corridor's reference direction is simply wherever the player is facing when this /// Uses the player's initial facing direction as the corridor's forward axis.
/// controller starts - no manual light/reference transform needed. This becomes both the
/// "forward" axis used to detect turning back, and the axis the exit recedes along.
/// </summary> /// </summary>
private void InitializeLightDirection() private void InitializeLightDirection()
{ {
_lightForwardDirection = PlayerController.Instance.transform.forward; _lightForwardDirection =
PlayerController.Instance.transform.forward;
_lightForwardDirection.y = 0f; _lightForwardDirection.y = 0f;
if (_lightForwardDirection.sqrMagnitude > 0f)
_lightForwardDirection.Normalize(); _lightForwardDirection.Normalize();
} }
@@ -111,7 +153,11 @@ namespace Unheimlich.World.HouseOfLeaves
{ {
if (_postProcessVolume == null) if (_postProcessVolume == null)
{ {
Debug.LogWarning("[HouseOfLeavesController] No URP Volume assigned — darkness effect disabled."); Debug.LogWarning(
"[HouseOfLeavesController] No URP Volume assigned — " +
"darkness effect disabled."
);
return; return;
} }
@@ -123,116 +169,220 @@ namespace Unheimlich.World.HouseOfLeaves
{ {
if (_exit == null) if (_exit == null)
{ {
Debug.LogWarning("[HouseOfLeavesController] No Exit transform assigned — endless-corridor recede disabled."); Debug.LogWarning(
"[HouseOfLeavesController] No Exit transform assigned — " +
"endless-corridor recede disabled."
);
return; return;
} }
_exitRestPosition = _exit.position; _exitRestPosition = _exit.position;
} }
// ───────────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────────
// Distance Tracking // Distance Tracking
// ───────────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────────
private void TrackDistance() private void TrackDistance()
{ {
Vector3 currentPosition = PlayerController.Instance.transform.position; Vector3 currentPosition =
Vector3 delta = currentPosition - _lastPlayerPosition; PlayerController.Instance.transform.position;
Vector3 delta =
currentPosition - _lastPlayerPosition;
delta.y = 0f; delta.y = 0f;
// No clamp here on purpose: the corridor has no defined length, so this can grow forever. // Signed distance along the corridor direction.
_distanceTravelled += Vector3.Dot(delta, _lightForwardDirection); _distanceTravelled +=
Vector3.Dot(delta, _lightForwardDirection);
// Prevent the visual progression from becoming negative if
// the player walks backwards past the starting point.
_distanceTravelled =
Mathf.Max(0f, _distanceTravelled);
_lastPlayerPosition = currentPosition; _lastPlayerPosition = currentPosition;
} }
// ───────────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────────
// Tick Methods // Tick Methods
// ───────────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────────
/// <summary>
/// Pushes the exit further away along the corridor direction, proportionally to how far
/// the player has walked. Because _recedeFactor is less than 1, the exit still slowly
/// gets closer overall - just much slower than the player's actual walking speed - which
/// is what sells the "this corridor never seems to end" feeling without ever touching
/// the player's movement speed or FOV.
/// </summary>
private void TickExitRecede() private void TickExitRecede()
{ {
if (_exit == null) return; if (_exit == null)
return;
float recedeDistance = _distanceTravelled * _recedeFactor; float recedeDistance =
Vector3 targetPosition = _exitRestPosition + _lightForwardDirection * recedeDistance; _distanceTravelled * _recedeFactor;
// Smoothly drift towards the target rather than snapping, so the shift is never perceptible Vector3 targetPosition =
// as a sudden jump - it should read as "the corridor is just long", not as a visible trick. _exitRestPosition +
float t = Time.deltaTime / Mathf.Max(_recedeSmoothTime, 0.01f); _lightForwardDirection * recedeDistance;
_exit.position = Vector3.Lerp(_exit.position, targetPosition, t);
float t =
Time.deltaTime /
Mathf.Max(_recedeSmoothTime, 0.01f);
_exit.position =
Vector3.Lerp(
_exit.position,
targetPosition,
t
);
} }
private void TickDarkness() private void TickDarkness()
{ {
if (_vignette == null && _colorAdjustments == null) return; if (_vignette == null &&
_colorAdjustments == null)
{
return;
}
Vector3 playerForward =
PlayerController.Instance.transform.forward;
Vector3 playerForward = PlayerController.Instance.transform.forward;
playerForward.y = 0f; playerForward.y = 0f;
if (playerForward.sqrMagnitude > 0f)
playerForward.Normalize(); playerForward.Normalize();
float dot = Vector3.Dot(playerForward, _lightForwardDirection); float dot =
float angleDegrees = Mathf.Acos(Mathf.Clamp(dot, -1f, 1f)) * Mathf.Rad2Deg; Vector3.Dot(
playerForward,
float darknessT = Mathf.Clamp01( _lightForwardDirection
(angleDegrees - _turningBackAngle) / (180f - _turningBackAngle)
); );
float lerpDelta = Time.deltaTime * _darknessLerpSpeed; float angleDegrees =
Mathf.Acos(
Mathf.Clamp(dot, -1f, 1f)
) * Mathf.Rad2Deg;
float darknessT =
Mathf.Clamp01(
(angleDegrees - _turningBackAngle) /
Mathf.Max(
180f - _turningBackAngle,
0.01f
)
);
float lerpDelta =
Time.deltaTime * _darknessLerpSpeed;
if (_vignette != null) if (_vignette != null)
_vignette.intensity.value = Mathf.Lerp(_vignette.intensity.value, _maxVignetteIntensity * darknessT, lerpDelta); {
_vignette.intensity.value =
Mathf.Lerp(
_vignette.intensity.value,
_maxVignetteIntensity * darknessT,
lerpDelta
);
}
if (_colorAdjustments != null) if (_colorAdjustments != null)
_colorAdjustments.postExposure.value = Mathf.Lerp(_colorAdjustments.postExposure.value, _maxExposureReduction * darknessT, lerpDelta); {
_colorAdjustments.postExposure.value =
Mathf.Lerp(
_colorAdjustments.postExposure.value,
_maxExposureReduction * darknessT,
lerpDelta
);
} }
}
// ───────────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────────
// Public API // Public API
// ───────────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────────
/// <summary>Total distance the player has walked along the corridor direction (unbounded).</summary> /// <summary>
public float DistanceTravelled => _distanceTravelled; /// Total distance travelled along the corridor direction.
/// </summary>
public float DistanceTravelled =>
_distanceTravelled;
/// <summary>
/// Normalized visual progression.
///
/// 0 = start of the corridor.
/// 1 = progression distance has been reached.
///
/// This value is clamped to [0,1] even though DistanceTravelled
/// can continue increasing indefinitely.
/// </summary>
public float Progression =>
Mathf.Clamp01(
_distanceTravelled /
Mathf.Max(_progressionDistance, 0.01f)
);
/// <summary>
/// Resets corridor progression and the receding exit.
/// </summary>
public void ResetProgression() public void ResetProgression()
{ {
_distanceTravelled = 0f; _distanceTravelled = 0f;
_lastPlayerPosition = PlayerController.Instance.transform.position;
if (PlayerController.Instance != null)
{
_lastPlayerPosition =
PlayerController.Instance.transform.position;
}
if (_exit != null) if (_exit != null)
_exit.position = _exitRestPosition; {
_exit.position =
_exitRestPosition;
}
} }
/// <summary>
/// Overrides the corridor forward direction.
/// </summary>
public void SetLightDirection(Vector3 worldDirection) public void SetLightDirection(Vector3 worldDirection)
{ {
_lightForwardDirection = worldDirection; worldDirection.y = 0f;
_lightForwardDirection.y = 0f;
_lightForwardDirection.Normalize(); if (worldDirection.sqrMagnitude <= 0f)
return;
_lightForwardDirection =
worldDirection.normalized;
} }
// ───────────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────────
// Editor // Editor
// ───────────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────────
#if UNITY_EDITOR #if UNITY_EDITOR
private void OnDrawGizmosSelected() private void OnDrawGizmosSelected()
{ {
Gizmos.color = Color.yellow; Gizmos.color = Color.yellow;
Gizmos.DrawRay(transform.position + Vector3.up, _lightForwardDirection * 4f);
Gizmos.DrawRay(
transform.position + Vector3.up,
_lightForwardDirection * 4f
);
if (_exit != null) if (_exit != null)
{ {
Gizmos.color = Color.cyan; Gizmos.color = Color.cyan;
Gizmos.DrawWireSphere(_exit.position, 0.5f);
Gizmos.DrawWireSphere(
_exit.position,
0.5f
);
} }
} }
#endif #endif
} }
} }
+22
View File
@@ -0,0 +1,22 @@
<Solution>
<Project Path="AK.Wwise.Unity.MonoBehaviour.csproj" />
<Project Path="OctoCore.Runtime.csproj" />
<Project Path="Assembly-CSharp-Editor.csproj" />
<Project Path="AK.Wwise.Unity.API.csproj" />
<Project Path="AK.Wwise.Unity.MonoBehaviour.Editor.csproj" />
<Project Path="AK.Wwise.Unity.API.Editor.csproj" />
<Project Path="AK.Wwise.Unity.ProjectDatabase.csproj" />
<Project Path="AK.Wwise.Unity.Timeline.csproj" />
<Project Path="Assembly-CSharp.csproj" />
<Project Path="OctoCore.Editor.csproj" />
<Project Path="AK.Wwise.Unity.API.WwiseTypes.csproj" />
<Project Path="AK.Wwise.Unity.OpenXR.csproj" />
<Project Path="MeshBakerEditor.csproj" />
<Project Path="AK.Wwise.Unity.Logging.csproj" />
<Project Path="AK.Wwise.Unity.Timeline.Editor.csproj" />
<Project Path="MeshBakerCore.csproj" />
<Project Path="AK.Wwise.Unity.AddressableInstaller.csproj" />
<Project Path="AK.Wwise.Unity.Utilities.csproj" />
<Project Path="Ak.Wwise.Api.WAAPI.csproj" />
<Project Path="AK.Wwise.Unity.AddressableUpdater.csproj" />
</Solution>