备份,准备做双端

This commit is contained in:
2026-07-30 23:15:58 +08:00
parent add675e45d
commit ec4ec53545
88 changed files with 4050 additions and 872 deletions
+127 -39
View File
@@ -15,6 +15,80 @@ public class GfxController : MonoBehaviour
// 复用的属性块:命中特效透明度覆写用,避免每次命中访问 r.materials 克隆材质。
private MaterialPropertyBlock _fxPropertyBlock;
// ---- 特效对象池:按 prefab 归类复用实例,避免每次命中/弹道 Instantiate+Destroy 造成的 GC。----
private readonly System.Collections.Generic.Dictionary<GameObject, System.Collections.Generic.Stack<GameObject>> _fxPools
= new System.Collections.Generic.Dictionary<GameObject, System.Collections.Generic.Stack<GameObject>>();
private Transform _fxPoolRoot;
/// <summary>从池中取出一个 prefab 实例(无则实例化),并挂上 GfxPooledItem 追踪。</summary>
private GameObject AcquireFx(GameObject prefab)
{
if (prefab == null) return null;
GameObject go = null;
if (_fxPools.TryGetValue(prefab, out var stack))
{
while (stack.Count > 0)
{
var candidate = stack.Pop();
if (candidate != null) { go = candidate; break; }
}
}
GfxPooledItem item;
if (go == null)
{
go = Instantiate(prefab);
item = go.GetComponent<GfxPooledItem>();
if (item == null) item = go.AddComponent<GfxPooledItem>();
item.prefabKey = prefab;
item.owner = this;
item.CaptureOriginals();
}
else
{
item = go.GetComponent<GfxPooledItem>();
item.ResetToOriginal();
go.SetActive(true);
}
item.released = false;
return go;
}
/// <summary>把特效实例还原并压回对应 prefab 的池中。</summary>
public void ReleaseFx(GameObject go)
{
if (go == null) return;
var item = go.GetComponent<GfxPooledItem>();
if (item == null) { Destroy(go); return; }
if (item.released) return;
item.released = true;
if (_fxPoolRoot == null)
{
var root = new GameObject("_FxPool");
root.transform.SetParent(transform, false);
_fxPoolRoot = root.transform;
}
go.SetActive(false);
go.transform.SetParent(_fxPoolRoot, false);
if (!_fxPools.TryGetValue(item.prefabKey, out var stack))
{
stack = new System.Collections.Generic.Stack<GameObject>();
_fxPools[item.prefabKey] = stack;
}
stack.Push(go);
}
/// <summary>延迟 delay 秒后回收(用于非粒子/动画类特效,替代 Destroy(fx, delay))。</summary>
private IEnumerator ReleaseFxDelayed(GameObject go, float delay)
{
yield return new WaitForSeconds(delay);
ReleaseFx(go);
}
[Header("Combatant Objects")]
public GameObject enemyMoveObject;
public GameObject ally01MoveObject;
@@ -287,9 +361,10 @@ public class GfxController : MonoBehaviour
// 将缩放应用到挂载点物体(visualParent)本身
visualParent.transform.localScale = Vector3.one * fxScale;
// 作为子物体生成
fx = Instantiate(fxPrefab, visualParent.transform);
// 作为子物体生成(从池取出后挂到挂载点下)
fx = AcquireFx(fxPrefab);
fx.transform.SetParent(visualParent.transform, false);
// 确保特效在 UI 层级中显示在最前方
fx.transform.SetAsLastSibling();
@@ -329,11 +404,15 @@ public class GfxController : MonoBehaviour
else
{
Vector3 spawnPos = customWorldPos.HasValue ? customWorldPos.Value : (target.transform.position + offsetVector);
fx = Instantiate(fxPrefab, spawnPos, Quaternion.identity);
fx = AcquireFx(fxPrefab);
fx.transform.SetParent(null, false);
fx.transform.position = spawnPos;
fx.transform.rotation = Quaternion.identity;
fx.transform.localScale = Vector3.one * fxScale;
}
// 针对粒子系统的特殊处理
// 针对粒子系统的特殊处理。这里采集一次粒子数组,后面 ApplyTransparencyAndRender
// 复用它,避免对同一 fx 再做一次 GetComponentsInChildren<ParticleSystem>() 分配。
ParticleSystem[] allParticles = fx.GetComponentsInChildren<ParticleSystem>();
foreach (var ps in allParticles)
{
@@ -342,8 +421,8 @@ public class GfxController : MonoBehaviour
main.scalingMode = ParticleSystemScalingMode.Hierarchy;
}
// 应用透明度并处理渲染
ApplyTransparencyAndRender(fx, transparency, isEnemy ? enemyFxSortingOrder : allyFxSortingOrder);
// 应用透明度并处理渲染(复用上面已采集的粒子数组)
ApplyTransparencyAndRender(fx, transparency, isEnemy ? enemyFxSortingOrder : allyFxSortingOrder, allParticles);
if (allParticles.Length > 0)
{
@@ -352,18 +431,18 @@ public class GfxController : MonoBehaviour
// 确保粒子在生成时立即播放
ps.Play(true);
// 设置停止动作,如果是根节点粒子,设置自动销毁
// 设置停止动作根节点粒子停止时回调,用于回收到对象池(替代 Destroy)
if (ps.gameObject == fx)
{
var m = ps.main;
m.stopAction = ParticleSystemStopAction.Destroy;
m.stopAction = ParticleSystemStopAction.Callback;
}
}
}
else
{
// 如果不是粒子系统(例如动画 Prefab),在固定时间后销毁(默认 2 秒)
Destroy(fx, 2.0f);
// 如果不是粒子系统(例如动画 Prefab),在固定时间后回收(默认 2 秒)
StartCoroutine(ReleaseFxDelayed(fx, 2.0f));
}
}
@@ -390,11 +469,11 @@ public class GfxController : MonoBehaviour
Transform startTransform = visualSource != null ? visualSource.transform : source.transform;
Transform endTransform = visualTarget != null ? visualTarget.transform : target.transform;
// 在源物体位置实例化
GameObject projectile = Instantiate(projectileFX, startTransform.position, Quaternion.identity);
// --- 核心修正:挂载到 GfxController (即当前脚本所在的 Canvas 层级物体) ---
projectile.transform.SetParent(this.transform, true);
// 在源物体位置实例化(从池取出)
GameObject projectile = AcquireFx(projectileFX);
projectile.transform.SetParent(this.transform, false);
projectile.transform.position = startTransform.position;
projectile.transform.rotation = Quaternion.identity;
projectile.transform.localScale = Vector3.one * projectileScale;
// 应用透明度
@@ -475,8 +554,10 @@ public class GfxController : MonoBehaviour
Transform startTransform = visualSource != null ? visualSource.transform : source.transform;
Transform endTransform = visualTarget != null ? visualTarget.transform : target.transform;
GameObject projectile = Instantiate(prefab, startTransform.position, Quaternion.identity);
projectile.transform.SetParent(this.transform, true);
GameObject projectile = AcquireFx(prefab);
projectile.transform.SetParent(this.transform, false);
projectile.transform.position = startTransform.position;
projectile.transform.rotation = Quaternion.identity;
projectile.transform.localScale = Vector3.one * allySkillProjectileScale;
// 应用层级(复用 projectileTransparency
@@ -572,19 +653,19 @@ public class GfxController : MonoBehaviour
// 到达终点,触发回调并传回终点坐标
onComplete?.Invoke(lastEndPos);
// 到达后销毁
// 到达后回收
if (projectile != null)
{
// 如果有粒子系统,先停止发射让残余拖尾自然消失,或者直接销毁
// 如果有粒子系统,先停止发射让残余拖尾自然消失,再延迟回收
ParticleSystem[] ps = projectile.GetComponentsInChildren<ParticleSystem>();
if (ps.Length > 0)
{
foreach (var p in ps) p.Stop(true, ParticleSystemStopBehavior.StopEmitting);
Destroy(projectile, 1.0f); // 给一点时间让残余粒子消失
StartCoroutine(ReleaseFxDelayed(projectile, 1.0f)); // 给一点时间让残余粒子消失
}
else
{
Destroy(projectile);
ReleaseFx(projectile);
}
}
}
@@ -631,18 +712,18 @@ public class GfxController : MonoBehaviour
// 到达终点,触发回调并传回终点坐标
onComplete?.Invoke(lastEndPos);
// 到达后销毁
// 到达后回收
if (projectile != null)
{
ParticleSystem[] ps = projectile.GetComponentsInChildren<ParticleSystem>();
if (ps.Length > 0)
{
foreach (var p in ps) p.Stop(true, ParticleSystemStopBehavior.StopEmitting);
Destroy(projectile, 1.0f); // 给一点时间让残余粒子消失
StartCoroutine(ReleaseFxDelayed(projectile, 1.0f)); // 给一点时间让残余粒子消失
}
else
{
Destroy(projectile);
ReleaseFx(projectile);
}
}
}
@@ -682,7 +763,7 @@ public class GfxController : MonoBehaviour
/// <summary>
/// 为特效物体及其子物体中的渲染器应用透明度和层级
/// </summary>
private void ApplyTransparencyAndRender(GameObject fx, float transparency, int sortingOrder)
private void ApplyTransparencyAndRender(GameObject fx, float transparency, int sortingOrder, ParticleSystem[] cachedParticles = null)
{
// 1. 处理所有普通渲染器 (MeshRenderer, SpriteRenderer 等)
Renderer[] renderers = fx.GetComponentsInChildren<Renderer>();
@@ -717,8 +798,8 @@ public class GfxController : MonoBehaviour
}
}
// 2. 处理粒子系统颜色 (考虑不同模式)
ParticleSystem[] particles = fx.GetComponentsInChildren<ParticleSystem>();
// 2. 处理粒子系统颜色 (考虑不同模式)。若调用方已采集粒子数组则复用,避免重复分配。
ParticleSystem[] particles = cachedParticles ?? fx.GetComponentsInChildren<ParticleSystem>();
foreach (var ps in particles)
{
var main = ps.main;
@@ -883,9 +964,11 @@ public class GfxController : MonoBehaviour
return false;
}
// 生成弹道
GameObject projectile = Instantiate(projectilePrefab, enemyObject.transform.position, Quaternion.identity);
projectile.transform.SetParent(enemyObject.transform, true);
// 生成弹道(从池取出)
GameObject projectile = AcquireFx(projectilePrefab);
projectile.transform.SetParent(enemyObject.transform, false);
projectile.transform.position = enemyObject.transform.position;
projectile.transform.rotation = Quaternion.identity;
projectile.transform.localScale = Vector3.one * enemyAttackProjectileScale;
// 应用层级
@@ -934,7 +1017,7 @@ public class GfxController : MonoBehaviour
// 停止粒子系统
ParticleSystem[] ps = projectile.GetComponentsInChildren<ParticleSystem>();
foreach (var p in ps) { var m = p.main; m.loop = false; }
Destroy(projectile, 0.5f);
StartCoroutine(ReleaseFxDelayed(projectile, 0.5f));
}
// 播放 Hit 特效、闪红,并执行实际扣血
@@ -1036,11 +1119,12 @@ public class GfxController : MonoBehaviour
if (moveObj == null) return;
SmoothShake ss = moveObj.GetComponent<SmoothShake>();
if (ss == null) ss = moveObj.AddComponent<SmoothShake>();
bool freshShaker = false;
if (ss == null) { ss = moveObj.AddComponent<SmoothShake>(); freshShaker = true; }
// 修复:确保 Shaker 实例已初始化,避免 NullReferenceException
if (ss.positionShake == null) ss.positionShake = new Shaker();
if (ss.rotationShake == null) ss.rotationShake = new Shaker();
if (ss.positionShake == null) { ss.positionShake = new Shaker(); freshShaker = true; }
if (ss.rotationShake == null) { ss.rotationShake = new Shaker(); freshShaker = true; }
// 选择震动参数
Vector3 amplitude = isEnemy ? hitShakeAmplitude : allyHitShakeAmplitude;
@@ -1072,10 +1156,14 @@ public class GfxController : MonoBehaviour
if (ss.timeSettings.fadeOutCurve == null || ss.timeSettings.fadeOutCurve.length == 0)
ss.timeSettings.fadeOutCurve = AnimationCurve.Linear(0, 1, 1, 0);
// 初始化内部数组(插件在 AddComponent 时 Awake 已经运行,由于当时字段为空,需要重新同步数组)
ss.shakers = new Shaker[] { ss.positionShake, ss.rotationShake };
ss.sum = new Vector3[2];
// 初始化内部数组(插件在 AddComponent 时 Awake 已经运行,由于当时字段为空,需要重新同步数组)
// 仅在 Shaker 实例首次创建/变更时重建这两个数组;之后每次命中复用,避免每击两次数组分配。
if (freshShaker || ss.shakers == null || ss.shakers.Length != 2 || ss.sum == null || ss.sum.Length != 2)
{
ss.shakers = new Shaker[] { ss.positionShake, ss.rotationShake };
ss.sum = new Vector3[2];
}
// 启动震动
ss.StartShake();
}