Files
bansonic_beta_main/Assets/scripts/gamePlay_gameplay/trackFractureController.cs
T

534 lines
18 KiB
C#

using System.Collections;
using UnityEngine;
public class trackFractureController : MonoBehaviour
{
private const int TrackCount = 5;
private static readonly int FadeId = Shader.PropertyToID("_Fade");
[System.Serializable]
private class TrackSpriteFadeEntry
{
public SpriteRenderer spriteRendererA;
public SpriteRenderer spriteRendererB;
}
[Header("Tracks")]
[SerializeField] private GameObject[] trackObjects = new GameObject[TrackCount];
[SerializeField] private Material[] dissolveMaterials = new Material[TrackCount];
[SerializeField] private TrackSpriteFadeEntry[] trackSpriteFadeEntries = new TrackSpriteFadeEntry[TrackCount];
[SerializeField] private bool autoFetchFractureAndDrift = true;
[SerializeField] private bool autoApplyDissolveMaterialOnStart = true;
[Tooltip("If true, each track gets its own runtime CLONE of its dissolve material. The clone REPLACES the " +
"material in the renderer slot, so you can no longer drive _Fade through the original material asset " +
"reference. Only needed when several tracks share one material asset. If false (default), the assigned " +
"dissolve material is used directly on the renderer, so controlling that material's _Fade (via this " +
"controller or externally) drives the dissolve.")]
[SerializeField] private bool cloneDissolveMaterialAtRuntime = false;
[Header("Fracture Fade")]
[Tooltip("float1: seconds to wait AFTER a track fractures before its dissolve _Fade starts changing.")]
[SerializeField] private float fractureFadeDelay = 0.2f;
[Tooltip("Seconds over which the linked SpriteRenderer pair fades from alpha 1 to 0 after fracture.")]
[SerializeField] private float spriteRendererFadeDuration = 0.2f;
[Tooltip("float2: seconds over which the dissolve _Fade is driven from 1 to 0 once the fade begins. " +
"When this completes, the track's fragments are reclaimed asynchronously (collected one-by-one).")]
[SerializeField] private float fractureFadeDuration = 1f;
[Tooltip("The _Fade value written when the fracture dissolve begins.")]
[SerializeField] private float fractureFadeStartValue = 0f;
[Tooltip("The _Fade value written when the fracture dissolve completes.")]
[SerializeField] private float fractureFadeEndValue = 1f;
[Header("Start Z Move")]
[SerializeField] private bool playStartZMove = true;
[SerializeField] private float startLocalZ = 0f;
[SerializeField] private float endLocalZ = 0.286f;
[SerializeField] private float moveDuration = 0.35f;
[Header("Debug")]
[SerializeField] private bool enableDebugKeyTrigger = true;
[SerializeField] private bool debugLogs = true;
private readonly FractureAndDrift[] fractureDrivers = new FractureAndDrift[TrackCount];
private readonly Renderer[] trackRenderers = new Renderer[TrackCount];
private readonly Material[] runtimeDissolveMaterials = new Material[TrackCount];
// Original _Fade of each ASSIGNED material asset, captured before we touch it, so we can restore it
// on teardown. Needed only when NOT cloning: we drive the asset directly, and Unity persists those
// writes back to the .mat, so the value would otherwise stick after exiting play mode. NaN = not captured.
private readonly float[] originalFadeValues = new float[TrackCount];
private Coroutine startMoveRoutine;
private readonly Coroutine[] fadeRoutines = new Coroutine[TrackCount];
private void Awake()
{
CacheTrackReferences();
}
private void Start()
{
CacheTrackReferences();
ApplyRuntimeDissolveMaterials();
SetAllTrackLocalZ(startLocalZ);
if (playStartZMove)
{
startMoveRoutine = StartCoroutine(AnimateTracksToEndZ());
}
}
private void Update()
{
if (!enableDebugKeyTrigger)
{
return;
}
if (Input.GetKeyDown(KeyCode.Alpha1) || Input.GetKeyDown(KeyCode.Keypad1))
{
if (debugLogs) Debug.Log("[trackFractureController] Debug key 1 pressed.", this);
TriggerTrackFracture(0);
}
else if (Input.GetKeyDown(KeyCode.Alpha2) || Input.GetKeyDown(KeyCode.Keypad2))
{
if (debugLogs) Debug.Log("[trackFractureController] Debug key 2 pressed.", this);
TriggerTrackFracture(1);
}
else if (Input.GetKeyDown(KeyCode.Alpha3) || Input.GetKeyDown(KeyCode.Keypad3))
{
if (debugLogs) Debug.Log("[trackFractureController] Debug key 3 pressed.", this);
TriggerTrackFracture(2);
}
else if (Input.GetKeyDown(KeyCode.Alpha4) || Input.GetKeyDown(KeyCode.Keypad4))
{
if (debugLogs) Debug.Log("[trackFractureController] Debug key 4 pressed.", this);
TriggerTrackFracture(3);
}
else if (Input.GetKeyDown(KeyCode.Alpha5) || Input.GetKeyDown(KeyCode.Keypad5))
{
if (debugLogs) Debug.Log("[trackFractureController] Debug key 5 pressed.", this);
TriggerTrackFracture(4);
}
}
private void OnValidate()
{
EnsureArraySizes();
if (!Application.isPlaying)
{
CacheTrackReferences();
}
}
private void OnDestroy()
{
// When not cloning, we drove the assigned .mat asset directly and Unity persists those writes.
// Restore each asset's original _Fade so the value doesn't stick after play mode ends.
RestoreOriginalFadeValues();
for (int i = 0; i < runtimeDissolveMaterials.Length; i++)
{
if (runtimeDissolveMaterials[i] != null)
{
Destroy(runtimeDissolveMaterials[i]);
runtimeDissolveMaterials[i] = null;
}
}
}
private void RestoreOriginalFadeValues()
{
for (int i = 0; i < TrackCount; i++)
{
// Only restore assets we drove directly (clones are thrown away, no restore needed).
if (runtimeDissolveMaterials[i] != null)
{
continue;
}
Material sourceMaterial = dissolveMaterials[i];
if (sourceMaterial == null || float.IsNaN(originalFadeValues[i]) || !sourceMaterial.HasProperty(FadeId))
{
continue;
}
sourceMaterial.SetFloat(FadeId, originalFadeValues[i]);
originalFadeValues[i] = float.NaN;
}
}
public void TriggerTrackFractureByNumber(int trackNumber)
{
TriggerTrackFracture(trackNumber - 1);
}
public void TriggerTrackFracture(int trackIndex)
{
if (!IsValidTrackIndex(trackIndex))
{
Debug.LogWarning($"[trackFractureController] Invalid track index: {trackIndex}", this);
return;
}
FractureAndDrift fractureDriver = fractureDrivers[trackIndex];
if (fractureDriver == null)
{
Debug.LogWarning($"[trackFractureController] Missing FractureAndDrift on track {trackIndex + 1}.", this);
return;
}
fractureDriver.Shatter();
StartFractureFade(trackIndex);
}
private void StartFractureFade(int trackIndex)
{
Material targetMaterial = GetFadeTargetMaterial(trackIndex);
if (targetMaterial == null || !targetMaterial.HasProperty(FadeId))
{
return;
}
if (fadeRoutines[trackIndex] != null)
{
StopCoroutine(fadeRoutines[trackIndex]);
}
fadeRoutines[trackIndex] = StartCoroutine(FadeTrackOut(trackIndex, targetMaterial));
}
private IEnumerator FadeTrackOut(int trackIndex, Material targetMaterial)
{
TrackSpriteFadeEntry spriteFadeEntry = GetSpriteFadeEntry(trackIndex);
SetSpriteFadeEntryAlpha(spriteFadeEntry, 1f);
if (spriteRendererFadeDuration > 0f)
{
float spriteFadeElapsed = 0f;
while (spriteFadeElapsed < spriteRendererFadeDuration)
{
spriteFadeElapsed += Time.deltaTime;
float t = Mathf.Clamp01(spriteFadeElapsed / spriteRendererFadeDuration);
SetSpriteFadeEntryAlpha(spriteFadeEntry, Mathf.Lerp(1f, 0f, t));
yield return null;
}
}
else
{
SetSpriteFadeEntryAlpha(spriteFadeEntry, 0f);
}
SetSpriteFadeEntryAlpha(spriteFadeEntry, 0f);
// float1: hold before the dissolve begins. Fragments have already started drifting during this time.
if (fractureFadeDelay > 0f)
{
yield return GameplayClock.WaitForSeconds(fractureFadeDelay);
}
// float2: drive _Fade from the configured start value to the configured end value over this duration.
float duration = Mathf.Max(0.0001f, fractureFadeDuration);
float startFade = Mathf.Clamp01(fractureFadeStartValue);
float endFade = Mathf.Clamp01(fractureFadeEndValue);
float elapsed = 0f;
targetMaterial.SetFloat(FadeId, startFade);
while (elapsed < duration)
{
elapsed += Time.deltaTime;
float t = Mathf.Clamp01(elapsed / duration);
targetMaterial.SetFloat(FadeId, Mathf.Lerp(startFade, endFade, t));
yield return null;
}
targetMaterial.SetFloat(FadeId, endFade);
fadeRoutines[trackIndex] = null;
// Fade finished: asynchronously reclaim (collect) this track's fragments rather than destroying
// them all at once.
FractureAndDrift fractureDriver = fractureDrivers[trackIndex];
if (fractureDriver != null)
{
fractureDriver.ReclaimFragments();
}
}
public void SetTrackFadeByNumber(int trackNumber, float fadeValue)
{
SetTrackFade(trackNumber - 1, fadeValue);
}
public void SetTrackFade(int trackIndex, float fadeValue)
{
if (!IsValidTrackIndex(trackIndex))
{
Debug.LogWarning($"[trackFractureController] Invalid track index: {trackIndex}", this);
return;
}
Material targetMaterial = GetFadeTargetMaterial(trackIndex);
if (targetMaterial == null)
{
Debug.LogWarning($"[trackFractureController] Missing dissolve material on track {trackIndex + 1}.", this);
return;
}
if (!targetMaterial.HasProperty(FadeId))
{
Debug.LogWarning($"[trackFractureController] Material on track {trackIndex + 1} has no _Fade property.", targetMaterial);
return;
}
targetMaterial.SetFloat(FadeId, Mathf.Clamp01(fadeValue));
}
public float GetTrackFade(int trackIndex)
{
if (!IsValidTrackIndex(trackIndex))
{
return 0f;
}
Material targetMaterial = GetFadeTargetMaterial(trackIndex);
if (targetMaterial == null || !targetMaterial.HasProperty(FadeId))
{
return 0f;
}
return targetMaterial.GetFloat(FadeId);
}
public void SetAllTrackFade(float fadeValue)
{
for (int i = 0; i < TrackCount; i++)
{
SetTrackFade(i, fadeValue);
}
}
public void RestartStartZMove()
{
SetAllTrackLocalZ(startLocalZ);
if (startMoveRoutine != null)
{
StopCoroutine(startMoveRoutine);
}
startMoveRoutine = StartCoroutine(AnimateTracksToEndZ());
}
public void TriggerTrack1Fracture() => TriggerTrackFracture(0);
public void TriggerTrack2Fracture() => TriggerTrackFracture(1);
public void TriggerTrack3Fracture() => TriggerTrackFracture(2);
public void TriggerTrack4Fracture() => TriggerTrackFracture(3);
public void TriggerTrack5Fracture() => TriggerTrackFracture(4);
private void CacheTrackReferences()
{
EnsureArraySizes();
for (int i = 0; i < TrackCount; i++)
{
GameObject trackObject = trackObjects[i];
fractureDrivers[i] = null;
trackRenderers[i] = null;
if (trackObject == null)
{
continue;
}
if (autoFetchFractureAndDrift)
{
fractureDrivers[i] = trackObject.GetComponent<FractureAndDrift>();
if (fractureDrivers[i] == null)
{
fractureDrivers[i] = trackObject.GetComponentInChildren<FractureAndDrift>(true);
}
}
trackRenderers[i] = trackObject.GetComponent<Renderer>();
if (trackRenderers[i] == null)
{
trackRenderers[i] = trackObject.GetComponentInChildren<Renderer>(true);
}
}
}
private void ApplyRuntimeDissolveMaterials()
{
for (int i = 0; i < TrackCount; i++)
{
if (runtimeDissolveMaterials[i] != null)
{
Destroy(runtimeDissolveMaterials[i]);
runtimeDissolveMaterials[i] = null;
}
originalFadeValues[i] = float.NaN;
Material sourceMaterial = dissolveMaterials[i];
Renderer trackRenderer = trackRenderers[i];
if (sourceMaterial == null)
{
continue;
}
// The material actually used on the renderer AND driven for fade. When cloning is off this is the
// assigned asset itself, so external control of that asset's _Fade drives the dissolve directly.
Material appliedMaterial = sourceMaterial;
if (cloneDissolveMaterialAtRuntime)
{
appliedMaterial = new Material(sourceMaterial) { name = sourceMaterial.name + "_Runtime" };
runtimeDissolveMaterials[i] = appliedMaterial;
}
else if (sourceMaterial.HasProperty(FadeId))
{
// Not cloning: we mutate the asset directly, so remember its original _Fade to restore later.
originalFadeValues[i] = sourceMaterial.GetFloat(FadeId);
}
if (autoApplyDissolveMaterialOnStart && trackRenderer != null)
{
Material[] materials = trackRenderer.sharedMaterials;
if (materials == null || materials.Length == 0)
{
trackRenderer.sharedMaterial = appliedMaterial;
}
else
{
bool replaced = false;
for (int j = 0; j < materials.Length; j++)
{
if (materials[j] == sourceMaterial)
{
materials[j] = appliedMaterial;
replaced = true;
}
}
if (!replaced)
{
materials[0] = appliedMaterial;
}
trackRenderer.sharedMaterials = materials;
}
}
}
}
private IEnumerator AnimateTracksToEndZ()
{
float duration = Mathf.Max(0.0001f, moveDuration);
float elapsed = 0f;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
float t = Mathf.Clamp01(elapsed / duration);
float z = Mathf.Lerp(startLocalZ, endLocalZ, t);
SetAllTrackLocalZ(z);
yield return null;
}
SetAllTrackLocalZ(endLocalZ);
startMoveRoutine = null;
}
private void SetAllTrackLocalZ(float zValue)
{
for (int i = 0; i < TrackCount; i++)
{
GameObject trackObject = trackObjects[i];
if (trackObject == null)
{
continue;
}
Transform targetTransform = trackObject.transform;
Vector3 localPosition = targetTransform.localPosition;
localPosition.z = zValue;
targetTransform.localPosition = localPosition;
}
}
private Material GetFadeTargetMaterial(int trackIndex)
{
if (!IsValidTrackIndex(trackIndex))
{
return null;
}
// When cloning is on, drive the runtime clone. Otherwise drive the assigned asset directly - which is
// also the material now used on the renderer, so controlling that asset's _Fade drives the dissolve.
return runtimeDissolveMaterials[trackIndex] != null
? runtimeDissolveMaterials[trackIndex]
: dissolveMaterials[trackIndex];
}
private void EnsureArraySizes()
{
if (trackObjects == null || trackObjects.Length != TrackCount)
{
System.Array.Resize(ref trackObjects, TrackCount);
}
if (dissolveMaterials == null || dissolveMaterials.Length != TrackCount)
{
System.Array.Resize(ref dissolveMaterials, TrackCount);
}
if (trackSpriteFadeEntries == null || trackSpriteFadeEntries.Length != TrackCount)
{
System.Array.Resize(ref trackSpriteFadeEntries, TrackCount);
}
}
private static bool IsValidTrackIndex(int trackIndex)
{
return trackIndex >= 0 && trackIndex < TrackCount;
}
private TrackSpriteFadeEntry GetSpriteFadeEntry(int trackIndex)
{
if (!IsValidTrackIndex(trackIndex) || trackSpriteFadeEntries == null || trackIndex >= trackSpriteFadeEntries.Length)
{
return null;
}
return trackSpriteFadeEntries[trackIndex];
}
private static void SetSpriteFadeEntryAlpha(TrackSpriteFadeEntry entry, float alpha)
{
if (entry == null)
{
return;
}
SetSpriteRendererAlpha(entry.spriteRendererA, alpha);
SetSpriteRendererAlpha(entry.spriteRendererB, alpha);
}
private static void SetSpriteRendererAlpha(SpriteRenderer spriteRenderer, float alpha)
{
if (spriteRenderer == null)
{
return;
}
Color color = spriteRenderer.color;
color.a = Mathf.Clamp01(alpha);
spriteRenderer.color = color;
}
}