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; } 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 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 _doorsCoroutine; private float _entryAngle = 0f; private float _stairHeight; private float _startAngle; private float _endAngle; private bool _hasPreviousTotalProgression; void Awake() { _doorsCoroutine = new List(); 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; for (int i = 1; i < _settings.HalfTurns / 2 - 2; i++) { GameObject obj = Instantiate( _references.WallMiddle, _references.WallMiddle.transform.parent ); 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(); } } 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 ManageInside() { foreach (GameObject obj in _references.Inside) obj.SetActive(_state.PlayerInside); foreach (GameObject obj in _references.Outside) obj.SetActive(!_state.PlayerInside); _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); } }