Files
Ipseity-Demo/Assets/_Content/Scripts/SpiralStaircase.cs
T
2026-07-07 14:25:54 +02:00

364 lines
11 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;
}
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 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;
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);
}
}