399 lines
13 KiB
C#
399 lines
13 KiB
C#
using OctoCore;
|
|
using OctoCore.Components;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using Unity.VisualScripting;
|
|
using UnityEngine;
|
|
using UnityEngine.Events;
|
|
using UnityEngine.InputSystem;
|
|
using UnityEngine.Rendering.Universal;
|
|
using static UnityEngine.Rendering.DebugUI;
|
|
|
|
public class SpiralStaircase : MonoBehaviour
|
|
{
|
|
[System.Serializable]
|
|
public class DoorsPair
|
|
{
|
|
public Transform Left;
|
|
public Transform Right;
|
|
}
|
|
|
|
[System.Serializable]
|
|
public class State
|
|
{
|
|
public bool PlayerInside;
|
|
public int Quarters;
|
|
public float TotalAngle;
|
|
[Range(0, 1)] public float Progression;
|
|
[Range(0, 1)] public float TotalProgression;
|
|
}
|
|
|
|
[System.Serializable]
|
|
public class References
|
|
{
|
|
public UniversalRendererData RendererData;
|
|
public GameObject Stairs;
|
|
public MeshFilter StairSource;
|
|
public GameObject WallTop;
|
|
public GameObject WallMiddle;
|
|
public DoorsPair[] Doors;
|
|
public GameObject[] Inside;
|
|
public GameObject[] Outside;
|
|
}
|
|
|
|
[System.Serializable]
|
|
public class Settings
|
|
{
|
|
//public float Offset = -12.25f;
|
|
//public float StartAngle = 450;
|
|
//public float EndAngle = 720;
|
|
public float TotalProgressionOffset = 180f;
|
|
[Tooltip("Must be pair")]
|
|
[Min(7)] public int HalfTurns = 7;
|
|
[Tooltip("Middle wall lamps only render their shadows while the player is inside. From the theatre they are hidden behind the walls, but URP still renders their shadow maps (6 faces per point light) whenever they are in the camera frustum.")]
|
|
public bool MiddleLampShadowsOnlyInside = true;
|
|
[Tooltip("Middle wall lamps farther than their range plus this distance from the camera don't render shadows (hidden by the spiral). 0 = no distance limit.")]
|
|
[Min(0)] public float MiddleLampShadowDistance = 6f;
|
|
}
|
|
|
|
public enum ProgressionTriggerDirection
|
|
{
|
|
Up,
|
|
Down,
|
|
Both
|
|
}
|
|
|
|
[System.Serializable]
|
|
public class HallucinationTrigger
|
|
{
|
|
[Range(0, 1)] public float Progression;
|
|
public ProgressionTriggerDirection Direction = ProgressionTriggerDirection.Up;
|
|
public UnityEvent OnReached;
|
|
}
|
|
|
|
[System.Serializable]
|
|
public class Events
|
|
{
|
|
public UnityEvent<bool> OnInside;
|
|
public UnityEvent OnTopReached;
|
|
public UnityEvent OnBottomReached;
|
|
public HallucinationTrigger[] HallucinationTriggers;
|
|
}
|
|
|
|
[SerializeField, OctoReadOnly] private State _state;
|
|
[SerializeField] private References _references;
|
|
[SerializeField] private Settings _settings;
|
|
[SerializeField] private Events _events;
|
|
|
|
private List<IEnumerator> _doorsCoroutine;
|
|
private readonly List<(Light light, LightShadows shadows)> _middleShadowLights = new List<(Light, LightShadows)>();
|
|
|
|
private float _entryAngle = 0f;
|
|
private float _stairHeight;
|
|
private float _startAngle;
|
|
private float _endAngle;
|
|
private bool _hasPreviousTotalProgression;
|
|
|
|
void Awake()
|
|
{
|
|
_doorsCoroutine = new List<IEnumerator>();
|
|
for (int i = 0; i < _references.Doors.Length; i++)
|
|
_doorsCoroutine.Add(null);
|
|
|
|
_stairHeight = 1.75f;// _references.StairSource.mesh.bounds.size.y;
|
|
_startAngle = 180 * 2.5f;
|
|
_endAngle = Mathf.Max(_startAngle + 180, (_settings.HalfTurns - 2.5f) * 180);
|
|
|
|
for (int i = 1; i < _settings.HalfTurns; i++)
|
|
{
|
|
GameObject obj = Instantiate(_references.StairSource.gameObject, _references.StairSource.transform.parent);
|
|
obj.transform.localPosition = _references.StairSource.transform.localPosition + Vector3.up * _stairHeight * i;
|
|
obj.transform.localRotation = _references.StairSource.transform.localRotation * Quaternion.Euler(0, i * -180, 0);
|
|
}
|
|
|
|
_references.WallTop.transform.localPosition = Vector3.up * _settings.HalfTurns * _stairHeight;
|
|
|
|
float wallSegmentHeight = _stairHeight * 2f;
|
|
|
|
int lastIndex = _settings.HalfTurns / 2 - 3;
|
|
|
|
CollectShadowLights(_references.WallMiddle);
|
|
|
|
for (int i = 1; i < _settings.HalfTurns / 2 - 2; i++)
|
|
{
|
|
GameObject obj = Instantiate(
|
|
_references.WallMiddle,
|
|
_references.WallMiddle.transform.parent
|
|
);
|
|
|
|
CollectShadowLights(obj);
|
|
|
|
obj.transform.localPosition =
|
|
_references.WallMiddle.transform.localPosition +
|
|
Vector3.up * wallSegmentHeight * i;
|
|
|
|
if (i == lastIndex)
|
|
{
|
|
Vector3 scale = obj.transform.localScale;
|
|
scale.y = 3f;
|
|
obj.transform.localScale = scale;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Start()
|
|
{
|
|
ManageInside();
|
|
_events.OnBottomReached?.Invoke();
|
|
}
|
|
|
|
void OnDisable()
|
|
{
|
|
_state.PlayerInside = false;
|
|
_hasPreviousTotalProgression = false;
|
|
ManageInside();
|
|
}
|
|
|
|
void Update()
|
|
{
|
|
if (_state.PlayerInside)
|
|
{
|
|
Count();
|
|
SetRtpc();
|
|
}
|
|
|
|
UpdateMiddleLampShadows();
|
|
}
|
|
|
|
void OnTriggerEnter(Collider col)
|
|
{
|
|
if (OctoPlayer.Instance && OctoPlayer.Instance.gameObject == col.gameObject)
|
|
{
|
|
if (!_state.PlayerInside)
|
|
{
|
|
_entryAngle = _state.TotalAngle - _settings.TotalProgressionOffset;
|
|
_state.PlayerInside = true;
|
|
_hasPreviousTotalProgression = false;
|
|
ManageInside();
|
|
}
|
|
}
|
|
}
|
|
|
|
void OnTriggerExit(Collider col)
|
|
{
|
|
if (OctoPlayer.Instance && OctoPlayer.Instance.gameObject == col.gameObject)
|
|
{
|
|
if (_state.PlayerInside)
|
|
{
|
|
_state.PlayerInside = false;
|
|
_hasPreviousTotalProgression = false;
|
|
ManageInside();
|
|
}
|
|
}
|
|
}
|
|
|
|
public void OpendDoors(bool open) { for (int i = 0; i < _references.Doors.Length; i++) OpendDoor(i, open); }
|
|
public void OpendDoor(int id) { OpendDoor(id, true); }
|
|
public void CloseDoor(int id) { OpendDoor(id, false); }
|
|
public void SetDoorOpened(int id) { OpendDoor(id, true, false); }
|
|
public void SetDoorClosed(int id) { OpendDoor(id, false, false); }
|
|
|
|
private void OpendDoor(int id, bool open, bool animate = true)
|
|
{
|
|
if (_doorsCoroutine[id] != null) StopCoroutine(_doorsCoroutine[id]);
|
|
StartCoroutine(_doorsCoroutine[id] = OpenDoorCoroutine(id, open, animate));
|
|
}
|
|
|
|
IEnumerator OpenDoorCoroutine(int id, bool open, bool animate)
|
|
{
|
|
const float duration = .6f;
|
|
|
|
DoorsPair door = _references.Doors[id];
|
|
|
|
Quaternion originRotL = door.Left.transform.localRotation;
|
|
Quaternion originRotR = door.Right.transform.localRotation;
|
|
Quaternion targetRotL = Quaternion.Euler(0, open ? 0 : 13.1f, 0);
|
|
Quaternion targetRotR = Quaternion.Euler(0, open ? 0 : -13.1f, 0);
|
|
|
|
if (animate)
|
|
{
|
|
for (float t = 0; t < duration; t += Time.deltaTime)
|
|
{
|
|
float lerp = .5f - Mathf.Cos(t / duration * Mathf.PI) * .5f;
|
|
|
|
door.Left.transform.localRotation = Quaternion.Lerp(originRotL, targetRotL, lerp);
|
|
door.Right.transform.localRotation = Quaternion.Lerp(originRotR, targetRotR, lerp);
|
|
|
|
yield return new WaitForEndOfFrame();
|
|
}
|
|
}
|
|
|
|
door.Left.transform.localRotation = targetRotL;
|
|
door.Right.transform.localRotation = targetRotR;
|
|
|
|
_doorsCoroutine[id] = null;
|
|
}
|
|
|
|
public void SimulateTop()
|
|
{
|
|
_state.Progression = 0;
|
|
_state.PlayerInside = true;
|
|
_state.Quarters = (int)_endAngle / 90;
|
|
_hasPreviousTotalProgression = false;
|
|
Count();
|
|
ManageInside();
|
|
}
|
|
|
|
public void SimulateBottom()
|
|
{
|
|
_state.Progression = 1;
|
|
_state.PlayerInside = true;
|
|
_state.Quarters = 0;
|
|
_hasPreviousTotalProgression = false;
|
|
Count();
|
|
ManageInside();
|
|
}
|
|
|
|
private void Count()
|
|
{
|
|
if (!OctoPlayer.Instance) return;
|
|
|
|
Vector2[] steps = new Vector2[4] { Vector2.up, Vector2.left, Vector2.down, Vector2.right };
|
|
Vector2 nextStep = steps[_state.Quarters % steps.Length];
|
|
|
|
Vector3 relativePos3D = transform.InverseTransformPoint(OctoPlayer.Instance.transform.position);
|
|
Vector2 relativePos2D = new Vector2(relativePos3D.x, relativePos3D.z);
|
|
|
|
float quarterAngle = Vector2.Angle(nextStep, relativePos2D) * Mathf.Sign(Vector3.Cross(nextStep, relativePos2D).z);
|
|
|
|
_state.TotalAngle = Mathf.Max(0, _state.Quarters * 90 + quarterAngle);
|
|
|
|
if (quarterAngle > 90 && quarterAngle < 100)
|
|
{
|
|
_state.Quarters++;
|
|
}
|
|
else if (quarterAngle < 0 && _state.Quarters > 0)
|
|
{
|
|
_state.Quarters--;
|
|
}
|
|
|
|
float progression = Mathf.Clamp01(Mathf.InverseLerp(_startAngle, _endAngle, _state.TotalAngle));
|
|
|
|
if (progression >= .5f && _state.Progression < .5f)
|
|
{
|
|
_events.OnTopReached?.Invoke();
|
|
}
|
|
else if (progression < .5f && _state.Progression >= .5f)
|
|
{
|
|
_events.OnBottomReached?.Invoke();
|
|
}
|
|
|
|
_state.Progression = progression;
|
|
|
|
float previousTotalProgression = _state.TotalProgression;
|
|
|
|
_state.TotalProgression = Mathf.Clamp01(
|
|
Mathf.InverseLerp(_entryAngle, _endAngle + _settings.TotalProgressionOffset, _state.TotalAngle)
|
|
);
|
|
|
|
if (_hasPreviousTotalProgression)
|
|
{
|
|
CheckHallucinationTriggers(previousTotalProgression, _state.TotalProgression);
|
|
}
|
|
else
|
|
{
|
|
_hasPreviousTotalProgression = true;
|
|
}
|
|
|
|
Vector3 targetPosition = new Vector3(0, _state.Progression * _settings.HalfTurns * -_stairHeight, 0);
|
|
Vector3 offset = targetPosition - _references.Stairs.transform.localPosition;
|
|
|
|
_references.Stairs.transform.localPosition = targetPosition;
|
|
|
|
if (OctoPlayer.Instance)
|
|
OctoPlayer.Instance.Move(offset);
|
|
}
|
|
|
|
private void CheckHallucinationTriggers(float previousTotalProgression, float currentTotalProgression)
|
|
{
|
|
if (_events.HallucinationTriggers == null) return;
|
|
|
|
foreach (var trigger in _events.HallucinationTriggers)
|
|
{
|
|
if (trigger == null) continue;
|
|
|
|
bool crossedUp =
|
|
previousTotalProgression < trigger.Progression &&
|
|
currentTotalProgression >= trigger.Progression;
|
|
|
|
bool crossedDown =
|
|
previousTotalProgression > trigger.Progression &&
|
|
currentTotalProgression <= trigger.Progression;
|
|
|
|
bool shouldTrigger =
|
|
(trigger.Direction == ProgressionTriggerDirection.Up && crossedUp) ||
|
|
(trigger.Direction == ProgressionTriggerDirection.Down && crossedDown) ||
|
|
(trigger.Direction == ProgressionTriggerDirection.Both && (crossedUp || crossedDown));
|
|
|
|
if (shouldTrigger)
|
|
{
|
|
trigger.OnReached?.Invoke();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void CollectShadowLights(GameObject root)
|
|
{
|
|
foreach (Light light in root.GetComponentsInChildren<Light>(true))
|
|
if (light.shadows != LightShadows.None)
|
|
_middleShadowLights.Add((light, light.shadows));
|
|
}
|
|
|
|
private void UpdateMiddleLampShadows()
|
|
{
|
|
bool active = _state.PlayerInside || !_settings.MiddleLampShadowsOnlyInside;
|
|
Camera cam = active && _settings.MiddleLampShadowDistance > 0 ? Camera.main : null;
|
|
Vector3 eye = cam ? cam.transform.position : Vector3.zero;
|
|
|
|
foreach (var (light, shadows) in _middleShadowLights)
|
|
{
|
|
if (!light) continue;
|
|
bool on = active && (!cam || Vector3.Distance(eye, light.transform.position) < light.range + _settings.MiddleLampShadowDistance);
|
|
LightShadows target = on ? shadows : LightShadows.None;
|
|
if (light.shadows != target) light.shadows = target;
|
|
}
|
|
}
|
|
|
|
private void ManageInside()
|
|
{
|
|
foreach (GameObject obj in _references.Inside) obj.SetActive(_state.PlayerInside);
|
|
foreach (GameObject obj in _references.Outside) obj.SetActive(!_state.PlayerInside);
|
|
|
|
UpdateMiddleLampShadows();
|
|
|
|
_events.OnInside?.Invoke(_state.PlayerInside);
|
|
|
|
/*Shader.SetGlobalFloat("_MaskEnabled", _state.PlayerInside ? 0 : 1);
|
|
|
|
foreach (var feature in _references.RendererData.rendererFeatures)
|
|
{
|
|
// Stencil Mask
|
|
// VisibleThroughMask
|
|
// HiddenThroughMask
|
|
|
|
if (feature.name == "HiddenThroughMask")
|
|
{
|
|
feature.SetActive(!_state.PlayerInside);
|
|
break;
|
|
}
|
|
}*/
|
|
}
|
|
|
|
//--Sound--------------
|
|
private void SetRtpc()
|
|
{
|
|
float rtpcValue = _state.TotalProgression;
|
|
AKRESULT aKRESULT = AkSoundEngine.SetRTPCValue("BinauralFREQ_Rtpc", rtpcValue);
|
|
}
|
|
} |