Progress : Implementation of the infinite staircase in the blocking
This commit is contained in:
-1845
File diff suppressed because it is too large
Load Diff
-3233
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -3823,7 +3823,7 @@ GameObject:
|
||||
m_Component:
|
||||
- component: {fileID: 3115487469603733110}
|
||||
m_Layer: 0
|
||||
m_Name: Stairs
|
||||
m_Name: StairsBase
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
+7358
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b278a20a2783def489d009364c988771
|
||||
guid: 32380164e79cd91468f9be51e78b6848
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
+5769
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3159084da89126c4f8f26519f0c6f75e
|
||||
guid: de8a9dd38c057f244a9a07ef0d93672a
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
+9
-456
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -4,70 +4,49 @@ using Unheimlich.Features.Player;
|
||||
|
||||
namespace Unheimlich.World.Environment
|
||||
{
|
||||
/// <summary>
|
||||
/// Infinite parallax staircase — tower approach.
|
||||
///
|
||||
/// When the player enters the trigger zone from below:
|
||||
/// - A full tower of (_maxIterations + 1) segments is spawned instantly above ground.
|
||||
/// - The tower scrolls at a speed proportional to how far the player's Y deviates
|
||||
/// from the trigger center — keeping the player locked near that height.
|
||||
/// If the player stops moving, scroll speed drops to zero.
|
||||
/// - When the topmost segment reaches ground level the stock is exhausted: the tower
|
||||
/// decelerates, extras are destroyed, and the system resets.
|
||||
///
|
||||
/// Entering from above mirrors the logic (tower ascends, stock is below ground).
|
||||
/// Re-triggerable indefinitely.
|
||||
///
|
||||
/// Setup:
|
||||
/// 1. Add this component to the InfinityStair root.
|
||||
/// 2. Add <see cref="StaircaseTriggerZone"/> to the Trigger child.
|
||||
/// 3. Assign <see cref="_stairPrefab"/>.
|
||||
/// </summary>
|
||||
public class InfiniteStaircaseController : MonoBehaviour
|
||||
{
|
||||
// ─── Prefab ──────────────────────────────────────────────────────────
|
||||
// ─── Prefabs ─────────────────────────────────────────────────────────
|
||||
|
||||
[Header("Stair Prefab")]
|
||||
[Tooltip("One staircase segment prefab used to build the tower.")]
|
||||
[Header("Stair Prefabs")]
|
||||
[Tooltip("Segment standard utilisé pour remplir la tour.")]
|
||||
[SerializeField] private GameObject _stairPrefab;
|
||||
|
||||
[Tooltip("If true, the first child with renderers is reused as slot 0 rather than instantiated from the prefab.")]
|
||||
[Tooltip("Segment toujours placé tout en HAUT de la tour (slot +maxIterationsUp).")]
|
||||
[SerializeField] private GameObject _stairTopPrefab;
|
||||
|
||||
[Tooltip("Segment toujours placé tout en BAS de la tour (slot -maxIterationsDown).")]
|
||||
[SerializeField] private GameObject _stairBottomPrefab;
|
||||
|
||||
[Tooltip("Si true, le premier enfant avec renderers est réutilisé comme slot 0.")]
|
||||
[SerializeField] private bool _useExistingChildAsBase = true;
|
||||
|
||||
// ─── Iterations ──────────────────────────────────────────────────────
|
||||
|
||||
[Header("Iterations")]
|
||||
[Tooltip("Number of full segment heights the player can traverse. Tower = (maxIterations + 1) segments.")]
|
||||
[SerializeField] private int _maxIterations = 4;
|
||||
[Tooltip("Nombre de segments au-dessus du sol (montée).")]
|
||||
[SerializeField] private int _maxIterationsUp = 4;
|
||||
|
||||
[Tooltip("Nombre de segments en-dessous du sol (descente).")]
|
||||
[SerializeField] private int _maxIterationsDown = 4;
|
||||
|
||||
// ─── Segment Bounds ──────────────────────────────────────────────────
|
||||
|
||||
[Header("Segment Bounds — Auto-Detection")]
|
||||
[Tooltip("Renderer of the central column mesh in the stair asset. When assigned, segment height is read from its world bounds.")]
|
||||
[SerializeField] private Renderer _centralColumnRenderer;
|
||||
|
||||
[Tooltip("Fallback segment height (m) used when no central column renderer is assigned.")]
|
||||
[SerializeField] private float _segmentHeight = 4.82f;
|
||||
|
||||
[Tooltip("Y offset added to the segment height for fine-tuning snapping.")]
|
||||
[SerializeField] private float _segmentHeightOffset = 0f;
|
||||
|
||||
// ─── Scroll Control ───────────────────────────────────────────────────
|
||||
|
||||
[Header("Scroll Speed Control")]
|
||||
[Tooltip("Proportional gain: scroll speed (m/s) per metre of player deviation from the trigger center.")]
|
||||
[SerializeField] private float _correctionGain = 4f;
|
||||
|
||||
[Tooltip("Maximum tower scroll speed in m/s.")]
|
||||
[SerializeField] private float _maxScrollSpeed = 8f;
|
||||
|
||||
[Tooltip("SmoothDamp time for scroll speed transitions — prevents sudden jolts.")]
|
||||
[SerializeField] private float _scrollSmoothTime = 0.1f;
|
||||
|
||||
// ─── Rotation Offset ─────────────────────────────────────────────────
|
||||
|
||||
[Header("Rotation Offset")]
|
||||
[Tooltip("Y rotation applied to every spawned segment around its geometric bounds center (not its pivot).")]
|
||||
[SerializeField] private float _segmentRotationOffsetY = 0f;
|
||||
|
||||
// ─── Debug ───────────────────────────────────────────────────────────
|
||||
@@ -81,23 +60,10 @@ namespace Unheimlich.World.Environment
|
||||
private StairState _currentState = StairState.Idle;
|
||||
|
||||
private Transform _playerTransform;
|
||||
|
||||
/// <summary>Single parent moved to scroll the entire tower at once.</summary>
|
||||
private Transform _towerRoot;
|
||||
|
||||
/// <summary>World Y of the trigger collider center, cached in Awake.</summary>
|
||||
private float _triggerCenterWorldY;
|
||||
|
||||
/// <summary>True when the player entered from below (going up).</summary>
|
||||
private bool _enteredFromBelow;
|
||||
|
||||
/// <summary>
|
||||
/// towerRoot.localPosition.y threshold signalling stock exhaustion.
|
||||
/// Negative when going up, positive when going down.
|
||||
/// </summary>
|
||||
private float _limitLocalY;
|
||||
|
||||
/// <summary>Current scroll speed (signed). Positive means the tower moves down.</summary>
|
||||
private float _scrollSpeed;
|
||||
private float _scrollSpeedVelocity;
|
||||
|
||||
@@ -106,7 +72,10 @@ namespace Unheimlich.World.Environment
|
||||
// ─── Properties ──────────────────────────────────────────────────────
|
||||
|
||||
private float EffectiveSegmentHeight => _segmentHeight + _segmentHeightOffset;
|
||||
private float MaxTowerDisplacement => _maxIterations * EffectiveSegmentHeight;
|
||||
|
||||
/// <summary>Déplacement max dépend de la direction.</summary>
|
||||
private float MaxTowerDisplacementUp => _maxIterationsUp * EffectiveSegmentHeight;
|
||||
private float MaxTowerDisplacementDown => _maxIterationsDown * EffectiveSegmentHeight;
|
||||
|
||||
// ─── Lifecycle ───────────────────────────────────────────────────────
|
||||
|
||||
@@ -136,10 +105,6 @@ namespace Unheimlich.World.Environment
|
||||
|
||||
// ─── Trigger Events ───────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Called by <see cref="StaircaseTriggerZone"/> when the player enters the collider.
|
||||
/// Detects entry direction, builds the tower, and starts the effect.
|
||||
/// </summary>
|
||||
public void OnPlayerEnteredTrigger()
|
||||
{
|
||||
if (_currentState != StairState.Idle && _currentState != StairState.Done) return;
|
||||
@@ -155,16 +120,10 @@ namespace Unheimlich.World.Environment
|
||||
_currentState = StairState.Active;
|
||||
}
|
||||
|
||||
/// <summary>Called by <see cref="StaircaseTriggerZone"/> when the player exits. No action needed — the limit check drives the stop.</summary>
|
||||
public void OnPlayerExitedTrigger() { }
|
||||
|
||||
// ─── Tower Construction ───────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Spawns the full tower under a single _towerRoot Transform.
|
||||
/// UP: slots 0..+N above ground. Limit = -N*h (descend to exhaust stock).
|
||||
/// DOWN: slots 0..-N below ground. Limit = +N*h (ascend to exhaust stock).
|
||||
/// </summary>
|
||||
private void BuildTower()
|
||||
{
|
||||
DestroyTower();
|
||||
@@ -177,15 +136,19 @@ namespace Unheimlich.World.Environment
|
||||
|
||||
if (_enteredFromBelow)
|
||||
{
|
||||
for (int i = 0; i <= _maxIterations; i++)
|
||||
// Slot 0 → +maxIterationsUp
|
||||
for (int i = 0; i <= _maxIterationsUp; i++)
|
||||
SpawnSegmentAtSlot(i);
|
||||
_limitLocalY = -MaxTowerDisplacement;
|
||||
|
||||
_limitLocalY = -MaxTowerDisplacementUp;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i >= -_maxIterations; i--)
|
||||
// Slot 0 → -maxIterationsDown
|
||||
for (int i = 0; i >= -_maxIterationsDown; i--)
|
||||
SpawnSegmentAtSlot(i);
|
||||
_limitLocalY = MaxTowerDisplacement;
|
||||
|
||||
_limitLocalY = MaxTowerDisplacementDown;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,6 +162,13 @@ namespace Unheimlich.World.Environment
|
||||
_segmentsBySlot.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Choisit le bon prefab selon le slot :
|
||||
/// - slot == +maxIterationsUp → _stairTopPrefab
|
||||
/// - slot == -maxIterationsDown → _stairBottomPrefab
|
||||
/// - tout le reste → _stairPrefab
|
||||
/// Le slot 0 peut aussi réutiliser l'enfant existant si l'option est activée.
|
||||
/// </summary>
|
||||
private void SpawnSegmentAtSlot(int slotIndex)
|
||||
{
|
||||
if (_segmentsBySlot.ContainsKey(slotIndex)) return;
|
||||
@@ -206,7 +176,7 @@ namespace Unheimlich.World.Environment
|
||||
float localY = slotIndex * EffectiveSegmentHeight;
|
||||
GameObject segment;
|
||||
|
||||
// Slot 0: try to reuse the existing scene child to avoid a visible pop.
|
||||
// ── Slot 0 : réutilisation de l'enfant existant ──────────────────
|
||||
if (slotIndex == 0 && _useExistingChildAsBase)
|
||||
{
|
||||
Transform existing = FindExistingStairChild();
|
||||
@@ -222,24 +192,36 @@ namespace Unheimlich.World.Environment
|
||||
}
|
||||
}
|
||||
|
||||
if (_stairPrefab == null) return;
|
||||
// ── Choix du prefab ───────────────────────────────────────────────
|
||||
GameObject prefabToUse = PickPrefabForSlot(slotIndex);
|
||||
if (prefabToUse == null) return;
|
||||
|
||||
segment = Instantiate(_stairPrefab, _towerRoot);
|
||||
segment = Instantiate(prefabToUse, _towerRoot);
|
||||
segment.transform.localPosition = new Vector3(0f, localY, 0f);
|
||||
segment.transform.localRotation = Quaternion.identity;
|
||||
ApplyRotationOffsetFromCenter(segment);
|
||||
_segmentsBySlot[slotIndex] = segment;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renvoie le prefab approprié pour un slot donné.
|
||||
/// Top = slot +maxIterationsUp (sommet absolu de la tour montante)
|
||||
/// Bottom = slot -maxIterationsDown (fond absolu de la tour descendante)
|
||||
/// Sinon = prefab standard.
|
||||
/// </summary>
|
||||
private GameObject PickPrefabForSlot(int slotIndex)
|
||||
{
|
||||
if (slotIndex == _maxIterationsUp && _stairTopPrefab != null)
|
||||
return _stairTopPrefab;
|
||||
|
||||
if (slotIndex == -_maxIterationsDown && _stairBottomPrefab != null)
|
||||
return _stairBottomPrefab;
|
||||
|
||||
return _stairPrefab;
|
||||
}
|
||||
|
||||
// ─── Scroll ───────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Proportional controller: error = player.y - triggerCenter.y.
|
||||
/// Positive error (player above center) → tower moves down → positive speed.
|
||||
/// Zero error (player at center) → speed converges to zero → tower stops.
|
||||
/// Speed is clamped so it can only push the tower in the intended direction,
|
||||
/// preventing the initial negative error from making the tower snap upward.
|
||||
/// </summary>
|
||||
private void UpdateScrollSpeed()
|
||||
{
|
||||
if (_playerTransform == null) return;
|
||||
@@ -265,7 +247,6 @@ namespace Unheimlich.World.Environment
|
||||
Vector3 pos = _towerRoot.localPosition;
|
||||
pos.y -= _scrollSpeed * Time.deltaTime;
|
||||
|
||||
// Clamp to the stock limit — never overshoot the last segment.
|
||||
pos.y = _enteredFromBelow
|
||||
? Mathf.Max(pos.y, _limitLocalY)
|
||||
: Mathf.Min(pos.y, _limitLocalY);
|
||||
@@ -278,16 +259,13 @@ namespace Unheimlich.World.Environment
|
||||
if (_towerRoot == null) return;
|
||||
|
||||
bool reached = _enteredFromBelow
|
||||
? _towerRoot.localPosition.y <= _limitLocalY // going up, tower descended enough
|
||||
: _towerRoot.localPosition.y >= _limitLocalY; // going down, tower ascended enough
|
||||
? _towerRoot.localPosition.y <= _limitLocalY
|
||||
: _towerRoot.localPosition.y >= _limitLocalY;
|
||||
|
||||
if (reached)
|
||||
_currentState = StairState.Stopping;
|
||||
_currentState = StairState.Stopping;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Smoothly brings the tower to rest before triggering cleanup.
|
||||
/// </summary>
|
||||
private void DecelerateAndFinish()
|
||||
{
|
||||
_scrollSpeed = Mathf.SmoothDamp(_scrollSpeed, 0f, ref _scrollSpeedVelocity, _scrollSmoothTime);
|
||||
@@ -299,15 +277,13 @@ namespace Unheimlich.World.Environment
|
||||
|
||||
// ─── Cleanup ─────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Extracts the segment now at ground level, destroys the rest, and resets state.
|
||||
/// </summary>
|
||||
private void FinishAndCleanup()
|
||||
{
|
||||
_currentState = StairState.Done;
|
||||
if (_towerRoot == null) return;
|
||||
|
||||
int keepSlot = _enteredFromBelow ? _maxIterations : -_maxIterations;
|
||||
// Le segment qui doit rester visible après la fin = l'extrémité atteinte
|
||||
int keepSlot = _enteredFromBelow ? _maxIterationsUp : -_maxIterationsDown;
|
||||
_segmentsBySlot.TryGetValue(keepSlot, out GameObject keepSegment);
|
||||
|
||||
if (keepSegment != null)
|
||||
@@ -324,7 +300,6 @@ namespace Unheimlich.World.Environment
|
||||
_scrollSpeed = 0f;
|
||||
_scrollSpeedVelocity = 0f;
|
||||
_playerTransform = null;
|
||||
|
||||
}
|
||||
|
||||
// ─── Helpers ─────────────────────────────────────────────────────────
|
||||
@@ -347,7 +322,6 @@ namespace Unheimlich.World.Environment
|
||||
Collider col = zone.GetComponent<Collider>();
|
||||
if (col != null)
|
||||
_triggerCenterWorldY = col.bounds.center.y;
|
||||
|
||||
}
|
||||
|
||||
private void EnsurePlayerCached()
|
||||
@@ -357,9 +331,6 @@ namespace Unheimlich.World.Environment
|
||||
_playerTransform = PlayerController.Instance.transform;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the first direct child of InfinityStair (excluding _towerRoot) that has renderers.
|
||||
/// </summary>
|
||||
private Transform FindExistingStairChild()
|
||||
{
|
||||
foreach (Transform child in transform)
|
||||
@@ -399,16 +370,42 @@ namespace Unheimlich.World.Environment
|
||||
float h = EffectiveSegmentHeight;
|
||||
Vector3 o = transform.position;
|
||||
|
||||
// Tour montante — vert
|
||||
Gizmos.color = new Color(0f, 1f, 0.5f, 0.35f);
|
||||
for (int i = 1; i <= _maxIterations; i++)
|
||||
for (int i = 1; i <= _maxIterationsUp; i++)
|
||||
Gizmos.DrawWireCube(new Vector3(o.x, o.y + i * h + h * 0.5f, o.z), new Vector3(1.8f, h * 0.95f, 1.8f));
|
||||
|
||||
// Tour descendante — orange
|
||||
Gizmos.color = new Color(1f, 0.6f, 0f, 0.25f);
|
||||
for (int i = 1; i <= _maxIterations; i++)
|
||||
for (int i = 1; i <= _maxIterationsDown; i++)
|
||||
Gizmos.DrawWireCube(new Vector3(o.x, o.y - i * h + h * 0.5f, o.z), new Vector3(1.8f, h * 0.95f, 1.8f));
|
||||
|
||||
// Ligne du centre du trigger — cyan
|
||||
Gizmos.color = Color.cyan;
|
||||
Gizmos.DrawLine(new Vector3(o.x - 2f, _triggerCenterWorldY, o.z), new Vector3(o.x + 2f, _triggerCenterWorldY, o.z));
|
||||
Gizmos.DrawLine(
|
||||
new Vector3(o.x - 2f, _triggerCenterWorldY, o.z),
|
||||
new Vector3(o.x + 2f, _triggerCenterWorldY, o.z)
|
||||
);
|
||||
|
||||
// Slot top — magenta
|
||||
if (_stairTopPrefab != null)
|
||||
{
|
||||
Gizmos.color = Color.magenta;
|
||||
Gizmos.DrawWireCube(
|
||||
new Vector3(o.x, o.y + _maxIterationsUp * h + h * 0.5f, o.z),
|
||||
new Vector3(2f, h, 2f)
|
||||
);
|
||||
}
|
||||
|
||||
// Slot bottom — rouge
|
||||
if (_stairBottomPrefab != null)
|
||||
{
|
||||
Gizmos.color = Color.red;
|
||||
Gizmos.DrawWireCube(
|
||||
new Vector3(o.x, o.y - _maxIterationsDown * h + h * 0.5f, o.z),
|
||||
new Vector3(2f, h, 2f)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -76,10 +76,15 @@
|
||||
"key": "editor.showEditorNotifications",
|
||||
"value": "{\"m_Value\":false}"
|
||||
},
|
||||
{
|
||||
"type": "System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
|
||||
"key": "editor.extrudeEdgesAsGroup",
|
||||
"value": "{\"m_Value\":true}"
|
||||
},
|
||||
{
|
||||
"type": "UnityEngine.ProBuilder.SelectMode, Unity.ProBuilder, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null",
|
||||
"key": "s_SelectMode",
|
||||
"value": "{\"m_Value\":4}"
|
||||
"value": "{\"m_Value\":2}"
|
||||
},
|
||||
{
|
||||
"type": "System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
|
||||
|
||||
Reference in New Issue
Block a user