using UnityEngine; using System.Collections; public class Animation_GenerateJudgementSituationPrefab : MonoBehaviour { private sealed class PooledJudgeTag : MonoBehaviour { public GameObject prefabSource; } public static Animation_GenerateJudgementSituationPrefab Instance; [Header("Inspector")] public GameObject redEffect; public GameObject greenEffect; public GameObject yellowEffect; public GameObject purpleEffect; public GameObject blueEffect; [Header("Inspector")] public GameObject perfect_judge_prefab; public GameObject great_judge_prefab; public GameObject good_judge_prefab; public GameObject miss_judge_prefab; [Header("Inspector")] public float baseScale = 1.0f; public bool useRandomScale = true; public float minScaleMultiplier = 0.8f; public float maxScaleMultiplier = 1.2f; [Header("Inspector")] public float jumpForce = 5.0f; // Documentation text normalized. public float gravity = -16f; // Documentation text normalized. [Header("Inspector")] public float fadeInTime = 0.1f; // Documentation text normalized. public float fadeOutStartTime = 0.6f; // Documentation text normalized. public float fadeOutDuration = 0.35f; // Documentation text normalized. // Cache spawn transforms to avoid accessing destroyed GameObject references. private Transform redSpawn; private Transform greenSpawn; private Transform yellowSpawn; private Transform purpleSpawn; private Transform blueSpawn; // Track last scene to detect scene changes private int lastSceneIndex = -1; private Coroutine prewarmJudgeCoroutine; private readonly System.Collections.Generic.Dictionary> judgePools = new System.Collections.Generic.Dictionary>(); private void Awake() { if (Instance == null) { Instance = this; // Documentation text normalized. // Documentation text normalized. } else if (Instance != this) { Destroy(gameObject); return; } CacheSpawnPoints(); lastSceneIndex = UnityEngine.SceneManagement.SceneManager.GetActiveScene().buildIndex; } private void Start() { // Ensure spawn points are cached after all scene objects are initialized CacheSpawnPointsIfNeeded(); } private void OnEnable() { // Detect scene reload and refresh cache int currentSceneIndex = UnityEngine.SceneManagement.SceneManager.GetActiveScene().buildIndex; if (currentSceneIndex != lastSceneIndex) { if (JudgeManager.IsDebugEnabled) Debug.Log($"[Animation_Generate] Scene changed detected (from {lastSceneIndex} to {currentSceneIndex}), refreshing spawn point cache"); lastSceneIndex = currentSceneIndex; CacheSpawnPoints(); } else { CacheSpawnPointsIfNeeded(); } } private void CacheSpawnPointsIfNeeded() { // Only recache if any of the references appear to be invalid (destroyed) if (redSpawn == null || greenSpawn == null || yellowSpawn == null || purpleSpawn == null || blueSpawn == null) { // Check if the effect GameObjects are still valid bool anyInvalid = (redEffect == null || redEffect.transform == null) || (greenEffect == null || greenEffect.transform == null) || (yellowEffect == null || yellowEffect.transform == null) || (purpleEffect == null || purpleEffect.transform == null) || (blueEffect == null || blueEffect.transform == null); if (anyInvalid || redSpawn == null) { CacheSpawnPoints(); } } } private void CacheSpawnPoints() { // Important: check Unity "fake null" before accessing .transform redSpawn = redEffect != null ? redEffect.transform : null; greenSpawn = greenEffect != null ? greenEffect.transform : null; yellowSpawn = yellowEffect != null ? yellowEffect.transform : null; purpleSpawn = purpleEffect != null ? purpleEffect.transform : null; blueSpawn = blueEffect != null ? blueEffect.transform : null; if (JudgeManager.IsDebugEnabled) { Debug.Log($"[Animation_Generate] Cached spawn points: red={redSpawn!=null}, green={greenSpawn!=null}, yellow={yellowSpawn!=null}, purple={purpleSpawn!=null}, blue={blueSpawn!=null}"); } } public void SpawnJudgePrefab(string color, string judgeResult) { if (string.IsNullOrEmpty(color)) { Debug.LogError("[Animation_Generate] SpawnJudgePrefab called with empty color!"); return; } if (string.IsNullOrEmpty(judgeResult)) { Debug.LogError("[Animation_Generate] SpawnJudgePrefab called with empty judgeResult!"); return; } Transform spawnPoint = GetSpawnPoint(color); if (spawnPoint == null) { Debug.LogError($"[Animation_Generate] Could not find spawn point for color: {color}"); return; } GameObject prefabToUse = GetJudgePrefab(judgeResult); if (prefabToUse == null) { Debug.LogError($"[Animation_Generate] Could not find judge prefab for result: {judgeResult}. Available: perfect={perfect_judge_prefab != null}, great={great_judge_prefab != null}, good={good_judge_prefab != null}, miss={miss_judge_prefab != null}"); return; } if (JudgeManager.IsDebugEnabled) Debug.Log($"[Animation_Generate] Spawning judge prefab: color={color}, result={judgeResult}, prefab={prefabToUse.name}"); GameObject instance = GetPooledJudge(prefabToUse, spawnPoint); if (instance == null) return; instance.transform.localPosition = Vector3.zero; // Documentation text normalized. float finalScale = baseScale; if (useRandomScale) finalScale *= Random.Range(minScaleMultiplier, maxScaleMultiplier); instance.transform.localScale = new Vector3(finalScale, finalScale, 1f); // Documentation text normalized. StartCoroutine(AnimateSprite(instance)); } public void PrewarmJudgePrefabs(int perPrefab = 1) { if (!Application.isPlaying) return; if (prewarmJudgeCoroutine != null) { StopCoroutine(prewarmJudgeCoroutine); } prewarmJudgeCoroutine = StartCoroutine(PrewarmJudgePrefabsRoutine(Mathf.Max(1, perPrefab))); } public IEnumerator PrewarmJudgePrefabsRoutine(int perPrefab = 1) { GameObject[] prefabs = { perfect_judge_prefab, great_judge_prefab, good_judge_prefab, miss_judge_prefab }; int count = Mathf.Max(1, perPrefab); foreach (GameObject prefab in prefabs) { if (prefab == null) continue; for (int i = 0; i < count; i++) { GameObject instance = null; try { instance = CreatePooledJudge(prefab); ReturnJudgeToPool(instance); } catch { } yield return null; } } prewarmJudgeCoroutine = null; } private IEnumerator AnimateSprite(GameObject obj) { if (obj == null) yield break; SpriteRenderer sr = obj.GetComponent(); float elapsed = 0f; float vVelocity = jumpForce; Vector3 currentLocalPos = Vector3.zero; // Documentation text normalized. float totalLifeTime = fadeOutStartTime + fadeOutDuration; // Documentation text normalized. if (sr != null) { Color c = sr.color; c.a = 0f; sr.color = c; } // Documentation text normalized. while (elapsed < totalLifeTime) { if (obj == null) yield break; float dt = Time.deltaTime; elapsed += dt; // Documentation text normalized. vVelocity += gravity * dt; currentLocalPos.y += vVelocity * dt; obj.transform.localPosition = currentLocalPos; // Documentation text normalized. if (sr != null) { float alpha; // Documentation text normalized. if (elapsed < fadeInTime) { alpha = Mathf.InverseLerp(0f, fadeInTime, elapsed); } // Documentation text normalized. else if (elapsed > fadeOutStartTime) { // Documentation text normalized. alpha = Mathf.InverseLerp(totalLifeTime, fadeOutStartTime, elapsed); } // Documentation text normalized. else { alpha = 1f; } Color c = sr.color; c.a = Mathf.Clamp01(alpha); sr.color = c; } yield return null; } // Documentation text normalized. if (obj != null && sr != null) { Color c = sr.color; c.a = 0f; sr.color = c; } // Documentation text normalized. if (obj != null) ReturnJudgeToPool(obj); } private GameObject GetPooledJudge(GameObject prefab, Transform parent) { if (prefab == null || parent == null) return null; if (!judgePools.TryGetValue(prefab, out var pool)) { pool = new System.Collections.Generic.Stack(); judgePools[prefab] = pool; } GameObject instance = null; while (pool.Count > 0 && instance == null) { instance = pool.Pop(); } if (instance == null) { instance = CreatePooledJudge(prefab); } if (instance == null) return null; instance.transform.SetParent(parent, false); instance.SetActive(true); return instance; } private GameObject CreatePooledJudge(GameObject prefab) { if (prefab == null) return null; GameObject instance = Instantiate(prefab, transform); var tag = instance.GetComponent(); if (tag == null) tag = instance.AddComponent(); tag.prefabSource = prefab; instance.SetActive(false); return instance; } private void ReturnJudgeToPool(GameObject instance) { if (instance == null) return; var tag = instance.GetComponent(); if (tag == null || tag.prefabSource == null) { Destroy(instance); return; } if (!judgePools.TryGetValue(tag.prefabSource, out var pool)) { pool = new System.Collections.Generic.Stack(); judgePools[tag.prefabSource] = pool; } instance.transform.SetParent(transform, false); instance.transform.localPosition = new Vector3(99999f, 99999f, 0f); instance.transform.localRotation = Quaternion.identity; instance.SetActive(false); pool.Push(instance); } private Transform GetSpawnPoint(string color) { if (string.IsNullOrEmpty(color)) return null; // Documentation text normalized. Transform point = null; switch (color.ToLower()) { case "red": point = redSpawn; break; case "green": point = greenSpawn; break; case "yellow": point = yellowSpawn; break; case "purple": point = purpleSpawn; break; case "blue": point = blueSpawn; break; default: Debug.LogWarning($"[Animation_Generate] Unknown color: {color}"); return null; } if (point == null) { Debug.LogError($"[Animation_Generate] Spawn point for color {color} is null. Check if effects are assigned in inspector. Make sure the effect object is active in the scene."); } return point; } private GameObject GetJudgePrefab(string result) { if (string.IsNullOrEmpty(result)) return null; GameObject prefab = null; switch (result.ToLower()) { case "perfect": prefab = perfect_judge_prefab; break; case "great": prefab = great_judge_prefab; break; case "good": prefab = good_judge_prefab; break; case "miss": prefab = miss_judge_prefab; break; default: Debug.LogWarning($"[Animation_Generate] Unknown judge result: {result}"); return null; } if (prefab == null) { Debug.LogError($"[Animation_Generate] Judge prefab for '{result}' is not assigned in inspector! Check Animation_GenerateJudgementSituationPrefab component."); } return prefab; } }