修复一堆bug 加入粒子系统代替原击打特效 焕新长音符逻辑 修复多重计分 更新判定管理和音符生成器 搞了一个免费包
This commit is contained in:
@@ -1,12 +1,19 @@
|
||||
using UnityEngine;
|
||||
using TMPro;
|
||||
using System.Collections; // 保持对 Coroutine 的支持
|
||||
using System.Collections.Generic; // For List
|
||||
|
||||
public class AnimationController : MonoBehaviour
|
||||
{
|
||||
public GameObject redEffect; // 红色击打动画对象
|
||||
public GameObject greenEffect; // 绿色击打动画对象
|
||||
public GameObject yellowEffect; // 黄色击打动画对象
|
||||
public GameObject purpleEffect; // 紫色击打动画对象
|
||||
public GameObject blueEffect; // 蓝色击打动画对象
|
||||
[HideInInspector] public bool isGlobalController = false;
|
||||
|
||||
public static AnimationController Global;
|
||||
|
||||
public GameObject redEffect; // 红色击打动画对象
|
||||
public GameObject greenEffect; // 绿色击打动画对象
|
||||
public GameObject yellowEffect; // 黄色击打动画对象
|
||||
public GameObject purpleEffect; // 紫色击打动画对象
|
||||
public GameObject blueEffect; // 蓝色击打动画对象
|
||||
|
||||
private Animator redAnimator;
|
||||
private Animator greenAnimator;
|
||||
@@ -14,42 +21,434 @@ public class AnimationController : MonoBehaviour
|
||||
private Animator purpleAnimator;
|
||||
private Animator blueAnimator;
|
||||
|
||||
// Scene object used as particle source (must be assigned in scene or found automatically)
|
||||
public GameObject hit_particular_object;
|
||||
public GameObject hit_ring_object;
|
||||
|
||||
[Header("轨道颜色 (Hex Color Codes)")]
|
||||
// F86F64 (浅红色/珊瑚色)
|
||||
public string redColorHex = "#F86F64";
|
||||
// 40EC90 (亮绿色)
|
||||
public string greenColorHex = "#40EC90";
|
||||
// F7CB6C (金黄色)
|
||||
public string yellowColorHex = "#F7CB6C";
|
||||
// F888E7 (亮粉色/洋红色)
|
||||
public string purpleColorHex = "#F888E7";
|
||||
// 4FACFE (天蓝色)
|
||||
public string blueColorHex = "#4FACFE";
|
||||
[Header("Hold 粒子配置")]
|
||||
public float holdParticleInterval = 0.2f;
|
||||
|
||||
private Coroutine holdParticleCoroutine;
|
||||
private bool holdActive = false;
|
||||
private string currentHoldColor;
|
||||
private bool isHolding = false;
|
||||
|
||||
// Track active particle instances for immediate cleanup
|
||||
private List<GameObject> activeParticles = new List<GameObject>();
|
||||
|
||||
[Header("判定飘字prefabs")]
|
||||
public GameObject perfect_judge_prefab;
|
||||
public GameObject great_judge_prefab;
|
||||
public GameObject good_judge_prefab;
|
||||
public GameObject miss_judge_prefab;
|
||||
[Header("轨道关联的判定文字 (Legacy Text)")]
|
||||
// 请在 Inspector 中将对应的 UI Text 拖入
|
||||
public TextMeshProUGUI red_track_judgementText;
|
||||
public TextMeshProUGUI green_track_judgementText;
|
||||
public TextMeshProUGUI yellow_track_judgementText;
|
||||
public TextMeshProUGUI purple_track_judgementText;
|
||||
public TextMeshProUGUI blue_track_judgementText;
|
||||
|
||||
public void StartHoldParticles(string color)
|
||||
{
|
||||
// Prefer the Global controller if available so Start/Stop always affect the same instance
|
||||
if (AnimationController.Global != null && AnimationController.Global != this)
|
||||
{
|
||||
AnimationController.Global.InternalStartHoldParticles(color);
|
||||
return;
|
||||
}
|
||||
|
||||
InternalStartHoldParticles(color);
|
||||
}
|
||||
|
||||
public void StopHoldParticles()
|
||||
{
|
||||
if (AnimationController.Global != null && AnimationController.Global != this)
|
||||
{
|
||||
AnimationController.Global.InternalStopHoldParticles();
|
||||
return;
|
||||
}
|
||||
|
||||
InternalStopHoldParticles();
|
||||
}
|
||||
|
||||
// Internal implementations operate on this instance
|
||||
private void InternalStartHoldParticles(string color)
|
||||
{
|
||||
if (holdActive) return;
|
||||
|
||||
holdActive = true;
|
||||
currentHoldColor = color;
|
||||
|
||||
if (holdParticleCoroutine != null)
|
||||
{
|
||||
StopCoroutine(holdParticleCoroutine);
|
||||
}
|
||||
|
||||
holdParticleCoroutine = StartCoroutine(HoldParticleRoutine(color));
|
||||
}
|
||||
|
||||
private void InternalStopHoldParticles()
|
||||
{
|
||||
holdActive = false;
|
||||
|
||||
if (holdParticleCoroutine != null)
|
||||
{
|
||||
try { StopCoroutine(holdParticleCoroutine); } catch { }
|
||||
holdParticleCoroutine = null;
|
||||
}
|
||||
|
||||
// Immediately destroy all active particle instances
|
||||
foreach (var p in activeParticles)
|
||||
{
|
||||
if (p != null)
|
||||
{
|
||||
Destroy(p);
|
||||
}
|
||||
}
|
||||
activeParticles.Clear();
|
||||
}
|
||||
|
||||
private IEnumerator HoldParticleRoutine(string color)
|
||||
{
|
||||
while (holdActive)
|
||||
{
|
||||
yield return new WaitForSeconds(Mathf.Max(0.01f, holdParticleInterval));
|
||||
if (!holdActive) break;
|
||||
PlayDestroyAnimation(color);
|
||||
}
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// 获取每个动画对象的 Animator 组件
|
||||
redAnimator = redEffect.GetComponent<Animator>();
|
||||
greenAnimator = greenEffect.GetComponent<Animator>();
|
||||
yellowAnimator = yellowEffect.GetComponent<Animator>();
|
||||
purpleAnimator = purpleEffect.GetComponent<Animator>();
|
||||
blueAnimator = blueEffect.GetComponent<Animator>();
|
||||
if (isGlobalController)
|
||||
{
|
||||
if (Global != null && Global != this)
|
||||
{
|
||||
Debug.LogWarning("[AnimationController] Duplicate global detected, destroying self");
|
||||
Destroy(this);
|
||||
return;
|
||||
}
|
||||
|
||||
Global = this;
|
||||
DontDestroyOnLoad(gameObject);
|
||||
Debug.Log("[AnimationController] Global AnimationController registered");
|
||||
}
|
||||
|
||||
// Animator cache
|
||||
redAnimator = redEffect != null ? redEffect.GetComponent<Animator>() : null;
|
||||
greenAnimator = greenEffect != null ? greenEffect.GetComponent<Animator>() : null;
|
||||
yellowAnimator = yellowEffect != null ? yellowEffect.GetComponent<Animator>() : null;
|
||||
purpleAnimator = purpleEffect != null ? purpleEffect.GetComponent<Animator>() : null;
|
||||
blueAnimator = blueEffect != null ? blueEffect.GetComponent<Animator>() : null;
|
||||
|
||||
// If the scene object reference was not assigned on this instance (common when this script is on a prefab),
|
||||
// try to locate a scene object automatically so runtime instances can still use a shared particle source.
|
||||
if (hit_particular_object == null)
|
||||
{
|
||||
// First try a tag-based lookup. Designer should assign the scene particle source a tag "HitParticleSource".
|
||||
try
|
||||
{
|
||||
var byTag = GameObject.FindWithTag("HitParticleSource");
|
||||
if (byTag != null)
|
||||
{
|
||||
hit_particular_object = byTag;
|
||||
Debug.Log("AnimationController: assigned hit_particular_object via tag 'HitParticleSource'.");
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
// Next try common names if tag not used
|
||||
if (hit_particular_object == null)
|
||||
{
|
||||
var byName = GameObject.Find("HitParticleSource") ?? GameObject.Find("hit_particular_object") ?? GameObject.Find("Hit_Particle_Source");
|
||||
if (byName != null)
|
||||
{
|
||||
hit_particular_object = byName;
|
||||
Debug.Log("AnimationController: assigned hit_particular_object via GameObject.Find by name.");
|
||||
}
|
||||
}
|
||||
|
||||
if (hit_particular_object == null)
|
||||
{
|
||||
Debug.LogWarning("AnimationController: hit_particular_object not assigned in Inspector and automatic lookup failed.\n" +
|
||||
"You can either assign a scene object to 'hit_particular_object' on the prefab instance in the scene,\n" +
|
||||
"or tag the scene particle source with 'HitParticleSource', or place a GameObject named 'HitParticleSource' in the scene.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void PlayDestroyAnimation(string color)
|
||||
{
|
||||
// Only use the scene object as the template. No prefab fallback.
|
||||
if (hit_particular_object == null)
|
||||
{
|
||||
Debug.LogError("hit_particular_object is null. Assign a scene particle source to AnimationController.hit_particular_object or tag a GameObject 'HitParticleSource'.");
|
||||
// As a fallback, trigger the Animator-based effects
|
||||
TriggerAnimatorEffect(color);
|
||||
return;
|
||||
}
|
||||
|
||||
GameObject source = hit_particular_object;
|
||||
|
||||
// choose spawn position: prefer per-color effect transform if available
|
||||
Vector3 spawnPos = transform.position;
|
||||
Transform parentTransform = null;
|
||||
switch (color)
|
||||
{
|
||||
case "red":
|
||||
// 重置之前的触发器
|
||||
redAnimator.ResetTrigger("PlayRedDestroy");
|
||||
redAnimator.SetTrigger("PlayRedDestroy");
|
||||
break;
|
||||
case "green":
|
||||
greenAnimator.ResetTrigger("PlayGreenDestroy");
|
||||
greenAnimator.SetTrigger("PlayGreenDestroy");
|
||||
break;
|
||||
case "yellow":
|
||||
yellowAnimator.ResetTrigger("PlayYellowDestroy");
|
||||
yellowAnimator.SetTrigger("PlayYellowDestroy");
|
||||
break;
|
||||
case "purple":
|
||||
purpleAnimator.ResetTrigger("PlayPurpleDestroy");
|
||||
purpleAnimator.SetTrigger("PlayPurpleDestroy");
|
||||
break;
|
||||
case "blue":
|
||||
blueAnimator.ResetTrigger("PlayBlueDestroy");
|
||||
blueAnimator.SetTrigger("PlayBlueDestroy");
|
||||
break;
|
||||
case "red": if (redEffect != null) { spawnPos = redEffect.transform.position; parentTransform = redEffect.transform; } break;
|
||||
case "green": if (greenEffect != null) { spawnPos = greenEffect.transform.position; parentTransform = greenEffect.transform; } break;
|
||||
case "yellow": if (yellowEffect != null) { spawnPos = yellowEffect.transform.position; parentTransform = yellowEffect.transform; } break;
|
||||
case "purple": if (purpleEffect != null) { spawnPos = purpleEffect.transform.position; parentTransform = purpleEffect.transform; } break;
|
||||
case "blue": if (blueEffect != null) { spawnPos = blueEffect.transform.position; parentTransform = blueEffect.transform; } break;
|
||||
}
|
||||
|
||||
// 获取并解析颜色
|
||||
Color trackColor;
|
||||
string hexCode = GetHexCodeForColor(color);
|
||||
|
||||
// 尝试解析 16 进制颜色代码。如果失败,使用白色作为默认值。
|
||||
if (!ColorUtility.TryParseHtmlString(hexCode, out trackColor))
|
||||
{
|
||||
trackColor = Color.white;
|
||||
Debug.LogWarning($"Failed to parse hex color for '{color}' ({hexCode}). Using white.");
|
||||
}
|
||||
|
||||
SpawnJudgePrefabByTrackText(color);
|
||||
// Instantiate a copy of the scene object
|
||||
GameObject particleInstance = Instantiate(source, spawnPos, Quaternion.identity);
|
||||
if (particleInstance == null)
|
||||
{
|
||||
Debug.LogError("Failed to instantiate hit_particular_object");
|
||||
TriggerAnimatorEffect(color);
|
||||
return;
|
||||
}
|
||||
|
||||
particleInstance.SetActive(true);
|
||||
|
||||
// Find all ParticleSystems on the instance (root + children) and set their startColor
|
||||
var systems = particleInstance.GetComponentsInChildren<ParticleSystem>(true);
|
||||
if (systems != null && systems.Length > 0)
|
||||
{
|
||||
float maxLifetime = 0f;
|
||||
foreach (var ps in systems)
|
||||
{
|
||||
var main = ps.main;
|
||||
// Set start color (works for most setups)
|
||||
main.startColor = trackColor;
|
||||
// Play the system
|
||||
ps.Play();
|
||||
// determine lifetime (use constantMax for safety)
|
||||
var lifetime = main.startLifetime;
|
||||
float life = lifetime.constantMax;
|
||||
if (life <= 0f) life = lifetime.constant; // fallback
|
||||
if (life > maxLifetime) maxLifetime = life;
|
||||
}
|
||||
|
||||
// Optionally parent the instance under the effect object so it moves with UI/slot
|
||||
if (parentTransform != null)
|
||||
{
|
||||
particleInstance.transform.SetParent(parentTransform, true);
|
||||
}
|
||||
|
||||
// Destroy after max lifetime + small buffer
|
||||
Destroy(particleInstance, Mathf.Max(0.5f, maxLifetime + 0.1f));
|
||||
|
||||
// Track for immediate cleanup
|
||||
activeParticles.Add(particleInstance);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("No ParticleSystem components found on hit_particular_object instance");
|
||||
Destroy(particleInstance);
|
||||
TriggerAnimatorEffect(color);
|
||||
}
|
||||
|
||||
// NEW: instantiate and play ring effect (overlay) if assigned
|
||||
if (hit_ring_object != null)
|
||||
{
|
||||
GameObject ringInstance = Instantiate(hit_ring_object, spawnPos, Quaternion.identity);
|
||||
if (ringInstance != null)
|
||||
{
|
||||
Debug.Log($"AnimationController: instantiated hit_ring_object '{ringInstance.name}' at {spawnPos}. parentTransform={(parentTransform != null ? parentTransform.name : "null")} ");
|
||||
|
||||
// Parent before activation to avoid transform surprises when Simulation Space = Local
|
||||
if (parentTransform != null)
|
||||
{
|
||||
ringInstance.transform.SetParent(parentTransform, false);
|
||||
// keep world position at spawnPos
|
||||
ringInstance.transform.position = spawnPos;
|
||||
Debug.Log("AnimationController: ringInstance parent set to " + parentTransform.name);
|
||||
}
|
||||
|
||||
// Activate after parenting
|
||||
ringInstance.SetActive(true);
|
||||
|
||||
var ringSystems = ringInstance.GetComponentsInChildren<ParticleSystem>(true);
|
||||
Debug.Log("AnimationController: ringSystems count=" + (ringSystems != null ? ringSystems.Length : 0));
|
||||
if (ringSystems != null && ringSystems.Length > 0)
|
||||
{
|
||||
float ringMaxLife = 0f;
|
||||
foreach (var rps in ringSystems)
|
||||
{
|
||||
var rmain = rps.main;
|
||||
// log important runtime properties for debugging
|
||||
Debug.Log($"ring PS: {rps.gameObject.name} simulationSpace={rmain.simulationSpace} startLifetime={rmain.startLifetime.constant} startColor={rmain.startColor.color}");
|
||||
|
||||
// reuse same track color
|
||||
rmain.startColor = trackColor;
|
||||
rps.Play();
|
||||
var rlifetime = rmain.startLifetime;
|
||||
float rlife = rlifetime.constantMax;
|
||||
if (rlife <= 0f) rlife = rlifetime.constant;
|
||||
if (rlife > ringMaxLife) ringMaxLife = rlife;
|
||||
|
||||
// Also log emission rate if available
|
||||
try
|
||||
{
|
||||
var emission = rps.emission;
|
||||
var rate = emission.rateOverTime;
|
||||
Debug.Log($"ring PS emission rateOverTime.constant (approx) = {rate.constant}");
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
Destroy(ringInstance, Mathf.Max(0.5f, ringMaxLife + 0.1f));
|
||||
|
||||
// Track for immediate cleanup
|
||||
activeParticles.Add(ringInstance);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("hit_ring_object has no ParticleSystem components");
|
||||
Destroy(ringInstance);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("Failed to instantiate hit_ring_object");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
// 用于根据颜色名称获取对应的 16 进制代码
|
||||
private string GetHexCodeForColor(string color)
|
||||
{
|
||||
switch (color)
|
||||
{
|
||||
case "red": return redColorHex;
|
||||
case "green": return greenColorHex;
|
||||
case "yellow": return yellowColorHex;
|
||||
case "purple": return purpleColorHex;
|
||||
case "blue": return blueColorHex;
|
||||
default: return "#FFFFFF"; // 默认返回白色
|
||||
}
|
||||
}
|
||||
|
||||
private void TriggerAnimatorEffect(string color)
|
||||
{
|
||||
// Fallback to original Animator triggers if particle source missing
|
||||
switch (color)
|
||||
{
|
||||
case "red":
|
||||
if (redAnimator != null)
|
||||
{
|
||||
redAnimator.ResetTrigger("PlayRedDestroy");
|
||||
redAnimator.SetTrigger("PlayRedDestroy");
|
||||
}
|
||||
break;
|
||||
case "green":
|
||||
if (greenAnimator != null)
|
||||
{
|
||||
greenAnimator.ResetTrigger("PlayGreenDestroy");
|
||||
greenAnimator.SetTrigger("PlayGreenDestroy");
|
||||
}
|
||||
break;
|
||||
case "yellow":
|
||||
if (yellowAnimator != null)
|
||||
{
|
||||
yellowAnimator.ResetTrigger("PlayYellowDestroy");
|
||||
yellowAnimator.SetTrigger("PlayYellowDestroy");
|
||||
}
|
||||
break;
|
||||
case "purple":
|
||||
if (purpleAnimator != null)
|
||||
{
|
||||
purpleAnimator.ResetTrigger("PlayPurpleDestroy");
|
||||
purpleAnimator.SetTrigger("PlayPurpleDestroy");
|
||||
}
|
||||
break;
|
||||
case "blue":
|
||||
if (blueAnimator != null)
|
||||
{
|
||||
blueAnimator.ResetTrigger("PlayBlueDestroy");
|
||||
blueAnimator.SetTrigger("PlayBlueDestroy");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 根据传入的颜色频道,定位到对应的 UI Text 并读取其内容
|
||||
/// </summary>
|
||||
private void SpawnJudgePrefabByTrackText(string color)
|
||||
{
|
||||
TextMeshProUGUI targetText = null;
|
||||
Transform spawnPoint = null;
|
||||
|
||||
// 1. 匹配对应的文本组件和位置
|
||||
switch (color)
|
||||
{
|
||||
case "red": targetText = red_track_judgementText; spawnPoint = redEffect != null ? redEffect.transform : null; break;
|
||||
case "green": targetText = green_track_judgementText; spawnPoint = greenEffect != null ? greenEffect.transform : null; break;
|
||||
case "yellow": targetText = yellow_track_judgementText; spawnPoint = yellowEffect != null ? yellowEffect.transform : null; break;
|
||||
case "purple": targetText = purple_track_judgementText; spawnPoint = purpleEffect != null ? purpleEffect.transform : null; break;
|
||||
case "blue": targetText = blue_track_judgementText; spawnPoint = blueEffect != null ? blueEffect.transform : null; break;
|
||||
}
|
||||
if (targetText != null && spawnPoint != null)
|
||||
{
|
||||
// 打印调试信息,看程序到底读到了什么文字
|
||||
Debug.LogError($"轨道 {color} 当前读到的文字是: [{targetText.text}]");
|
||||
}
|
||||
|
||||
// 2. 如果找到了文本且文本不为空,则执行喷出逻辑
|
||||
if (targetText != null && spawnPoint != null && !string.IsNullOrEmpty(targetText.text))
|
||||
{
|
||||
DoExecutePrefabSpawn(targetText.text, spawnPoint);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 最终执行实例化的函数
|
||||
/// </summary>
|
||||
private void DoExecutePrefabSpawn(string judgeResult, Transform spawnTransform)
|
||||
{
|
||||
GameObject prefabToUse = null;
|
||||
|
||||
// 字符串匹配(需确保与 InputManager 中传入的字符串一致)
|
||||
if (judgeResult.Contains("Perfect")) prefabToUse = perfect_judge_prefab;
|
||||
else if (judgeResult.Contains("Great")) prefabToUse = great_judge_prefab;
|
||||
else if (judgeResult.Contains("Good")) prefabToUse = good_judge_prefab;
|
||||
else if (judgeResult.Contains("Miss")) prefabToUse = miss_judge_prefab;
|
||||
|
||||
if (prefabToUse != null)
|
||||
{
|
||||
// 在对应特效点生成判定 Prefab
|
||||
GameObject instance = Instantiate(prefabToUse, spawnTransform.position, Quaternion.identity);
|
||||
|
||||
// 自动销毁,防止堆积
|
||||
Destroy(instance, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user