2修
This commit is contained in:
@@ -67,6 +67,13 @@ public class AnimationController : MonoBehaviour
|
||||
public bool[] ChildInitialActive;
|
||||
}
|
||||
|
||||
private struct PendingFxReturn
|
||||
{
|
||||
public GameObject Instance;
|
||||
public int RentToken;
|
||||
public float ReturnTime;
|
||||
}
|
||||
|
||||
private readonly Dictionary<GameObject, Queue<GameObject>> fxPools =
|
||||
new Dictionary<GameObject, Queue<GameObject>>();
|
||||
private readonly Dictionary<GameObject, FxCacheEntry> fxCache =
|
||||
@@ -83,6 +90,7 @@ public class AnimationController : MonoBehaviour
|
||||
// instance that was already returned early (StopHoldParticles) and re-rented.
|
||||
private int fxRentCounter;
|
||||
private readonly Dictionary<GameObject, int> fxRentId = new Dictionary<GameObject, int>();
|
||||
private readonly List<PendingFxReturn> pendingFxReturns = new List<PendingFxReturn>(64);
|
||||
private Transform fxPoolRoot;
|
||||
private string hitRingPathUnderParticleSource;
|
||||
|
||||
@@ -349,6 +357,41 @@ public class AnimationController : MonoBehaviour
|
||||
ReturnFxInstance(instance);
|
||||
}
|
||||
|
||||
private void ScheduleFxReturn(GameObject instance, int rentToken, float delay)
|
||||
{
|
||||
if (instance == null) return;
|
||||
|
||||
pendingFxReturns.Add(new PendingFxReturn
|
||||
{
|
||||
Instance = instance,
|
||||
RentToken = rentToken,
|
||||
ReturnTime = Time.unscaledTime + Mathf.Max(0f, delay)
|
||||
});
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (pendingFxReturns.Count == 0) return;
|
||||
|
||||
float now = Time.unscaledTime;
|
||||
for (int i = pendingFxReturns.Count - 1; i >= 0; i--)
|
||||
{
|
||||
PendingFxReturn pending = pendingFxReturns[i];
|
||||
if (pending.Instance == null)
|
||||
{
|
||||
pendingFxReturns.RemoveAt(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (now < pending.ReturnTime) continue;
|
||||
|
||||
pendingFxReturns.RemoveAt(i);
|
||||
if (CurrentRentToken(pending.Instance) != pending.RentToken) continue;
|
||||
|
||||
ReturnFxInstance(pending.Instance);
|
||||
}
|
||||
}
|
||||
|
||||
private void ReturnAllRentedFx()
|
||||
{
|
||||
var snapshot = new List<GameObject>(rentedFx);
|
||||
@@ -364,6 +407,7 @@ public class AnimationController : MonoBehaviour
|
||||
{
|
||||
holdActive = false;
|
||||
holdParticleCoroutine = null;
|
||||
pendingFxReturns.Clear();
|
||||
ReturnAllRentedFx();
|
||||
}
|
||||
|
||||
@@ -409,6 +453,10 @@ public class AnimationController : MonoBehaviour
|
||||
main.startColor = tint;
|
||||
try { ps.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear); } catch { }
|
||||
try { ps.Clear(true); } catch { }
|
||||
// Force pooled FX back to time 0 before replaying, matching a fresh Instantiate
|
||||
// without paying allocation/destruction cost on every hit.
|
||||
try { ps.Simulate(0f, false, true); } catch { }
|
||||
try { ps.Clear(true); } catch { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -736,85 +784,48 @@ public class AnimationController : MonoBehaviour
|
||||
// 判定字(PERFECT/GREAT 等)统一且仅由 Animation_GenerateJudgementSituationPrefab 负责生成,
|
||||
// 本方法只负责打击粒子 + ring,不碰判定字。
|
||||
|
||||
// 【彻底去池化】前几轮所有"粒子变多/变少、ring 不激活、复用状态错乱"都源于对象池复用时
|
||||
// 子节点激活态/粒子播放态无法可靠复位。改回最朴素可靠的 Instantiate + 定时 Destroy:
|
||||
// 每次判定 new 一个全新干净实例(与 prefab 设计态完全一致),播放完销毁。无复用=无脏状态。
|
||||
// 这正是加池化之前本就正常工作的行为。
|
||||
|
||||
// 粒子本体:以场景源为模板实例化一份全新拷贝。
|
||||
SpawnOneShotFx(source, spawnPos, parentTransform, trackColor, true);
|
||||
// 粒子本体:走对象池,避免高频判定时 Instantiate/Destroy 造成移动端掉帧。
|
||||
SpawnOneShotFx(source, spawnPos, parentTransform, trackColor, true, trackAsHoldParticle);
|
||||
|
||||
// Ring(叠加环):tap / hold 头尾 / hold 持续 tick 都按同一频率生成一次性实例。
|
||||
// 实例会按自身生命周期 Destroy,不会修改或启动场景里的模板对象。
|
||||
// 实例会按自身生命周期回池,不会修改或启动场景里的模板对象。
|
||||
if (hit_ring_object != null)
|
||||
{
|
||||
SpawnOneShotFx(hit_ring_object, spawnPos, parentTransform, trackColor, true);
|
||||
SpawnOneShotFx(hit_ring_object, spawnPos, parentTransform, trackColor, true, trackAsHoldParticle);
|
||||
}
|
||||
}
|
||||
|
||||
// 实例化一个一次性特效(粒子/ring),播放后按自身生命周期 Destroy。无对象池、无复用。
|
||||
// applyTint=true 时把粒子 startColor 染成轨道色(粒子本体);ring 保留自身配色(false)。
|
||||
private void SpawnOneShotFx(GameObject template, Vector3 spawnPos, Transform parentTransform, Color tint, bool applyTint)
|
||||
// 播放一个一次性特效(粒子/ring),播放后按自身生命周期回池。
|
||||
// applyTint=true 时把粒子 startColor 染成轨道色;当前调用保持旧行为,粒子和 ring 都染色。
|
||||
private void SpawnOneShotFx(GameObject template, Vector3 spawnPos, Transform parentTransform, Color tint, bool applyTint, bool trackAsHoldParticle)
|
||||
{
|
||||
if (template == null) return;
|
||||
|
||||
GameObject instance = Instantiate(template);
|
||||
GameObject instance = RentFxInstance(template, out FxCacheEntry entry, out int rentToken);
|
||||
if (instance == null) return;
|
||||
|
||||
// 去除粒子本体克隆里"自带的嵌套 ring":hit_ring_object 本身是 hit_particular_object 的子物体,
|
||||
// Instantiate(粒子本体) 会连它一起克隆(原色,未染色) → 与我另外单独实例化的染色 ring 叠成双层。
|
||||
// 先 SetActive(false) 立即让它停播,并从下面的系统列表中排除,再销毁;只保留单独实例化的染色 ring。
|
||||
Transform nestedRing = null;
|
||||
if (template == hit_particular_object && !string.IsNullOrEmpty(hitRingPathUnderParticleSource))
|
||||
{
|
||||
nestedRing = instance.transform.Find(hitRingPathUnderParticleSource);
|
||||
if (nestedRing != null)
|
||||
{
|
||||
nestedRing.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
// 定位:跟随分轨效果对象(worldPositionStays=false 保留模板设计的本地朝向,如 ring 的 -90°X)。
|
||||
instance.transform.SetParent(parentTransform, false);
|
||||
instance.transform.position = spawnPos;
|
||||
instance.transform.localRotation = template.transform.localRotation;
|
||||
instance.transform.localRotation = entry != null ? entry.LocalRotation : template.transform.localRotation;
|
||||
instance.SetActive(true);
|
||||
|
||||
// 排除嵌套 ring 的粒子系统,避免把那层原色 ring 播出来。
|
||||
var systems = FilterParticleSystems(instance.GetComponentsInChildren<ParticleSystem>(true), nestedRing);
|
||||
if (nestedRing != null) Destroy(nestedRing.gameObject);
|
||||
float lifetime = 0.5f;
|
||||
if (systems != null && systems.Length > 0)
|
||||
if (applyTint)
|
||||
{
|
||||
lifetime = ComputeFxLifetime(systems, null);
|
||||
// 关键修复(开局粒子多、之后变少的衰减):模板 hit_particular_object 是活动场景对象,
|
||||
// Instantiate 会连同模板粒子系统"当前缓冲里的活粒子"一起克隆——歌曲早期模板残留/预热粒子多
|
||||
// → 克隆多;随模板粒子消亡 → 克隆少。故必须把克隆继承的模拟状态彻底重置为"时间0、零粒子",
|
||||
// 再从头播放,保证每次一致、与模板当时状态无关。
|
||||
foreach (var ps in systems)
|
||||
{
|
||||
if (ps == null) continue;
|
||||
var main = ps.main;
|
||||
if (applyTint) main.startColor = tint;
|
||||
try { ps.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear); } catch { }
|
||||
try { ps.Clear(true); } catch { }
|
||||
// Simulate(0, withChildren:false, restart:true) 强制该系统回到时间 0 的干净态,
|
||||
// 抹掉 Instantiate 继承的任何已模拟粒子(单靠 Clear 有时不彻底)。
|
||||
try { ps.Simulate(0f, false, true); } catch { }
|
||||
try { ps.Clear(true); } catch { }
|
||||
}
|
||||
// 全部重置后,只对根系统 Play(true) 级联播放,从零开始正常发射。
|
||||
var rootPs = instance.GetComponent<ParticleSystem>();
|
||||
if (rootPs == null) rootPs = systems[0];
|
||||
try { rootPs.Play(true); } catch { }
|
||||
RestartFxPlayback(entry, tint);
|
||||
}
|
||||
else
|
||||
{
|
||||
var animators = instance.GetComponentsInChildren<Animator>(true);
|
||||
lifetime = ComputeFxLifetime(null, animators);
|
||||
RestartFxPlayback(entry, Color.white);
|
||||
}
|
||||
|
||||
Destroy(instance, Mathf.Max(0.5f, lifetime + 0.1f));
|
||||
if (trackAsHoldParticle)
|
||||
{
|
||||
activeHoldParticles.Add(instance);
|
||||
}
|
||||
|
||||
float lifetime = entry != null ? entry.Lifetime : 0.5f;
|
||||
ScheduleFxReturn(instance, rentToken, Mathf.Max(0.5f, lifetime + 0.1f));
|
||||
}
|
||||
|
||||
// Documentation text normalized.
|
||||
|
||||
Reference in New Issue
Block a user