1886 lines
64 KiB
C#
1886 lines
64 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using Games.AnimationModule;
|
||
using Games.Events;
|
||
using Games.GlobeDefine;
|
||
using Games.LogicObj;
|
||
using Games.Scene;
|
||
using GCGame.Table;
|
||
using Module.Log;
|
||
using UnityEngine;
|
||
using UnityEngine.Events;
|
||
using Object = UnityEngine.Object;
|
||
|
||
/// <inheritdoc />
|
||
/// <summary>
|
||
/// 最基本的特效管理器
|
||
/// </summary>
|
||
public abstract class BaseEffectLogic : MonoBehaviour
|
||
{
|
||
/// <summary>
|
||
/// 最小加载容忍时间,加载时间超过该值会忽略特效
|
||
/// </summary>
|
||
public const float minLoadTolerance = 0.5f;
|
||
|
||
/// <summary>
|
||
/// 最小加载容忍时间比例,加载时间超过特效总时长比例会忽略特效
|
||
/// </summary>
|
||
public const float minLoadRatio = 0.3f;
|
||
|
||
private bool _enable;
|
||
public List<BaseEffectReference> ActiveList { get; private set; }
|
||
public List<DelayEffectReference> DelayList { get; private set; }
|
||
|
||
public bool IsInited { get; private set; }
|
||
|
||
/// <summary>
|
||
/// 获得特效后,如果是需要延迟出现的情况,特效将会延迟出现
|
||
/// </summary>
|
||
/// <param name="effect">特效物体</param>
|
||
/// <param name="loadData">加载数据</param>
|
||
public void OnCreateEffect(Transform effect, EffectLoadData loadData)
|
||
{
|
||
// 加载超过容忍时间后,直接忽略当前特效
|
||
var loadDelay = Time.time - loadData.startTime;
|
||
var effectDelay = Mathf.Max(0f, loadData.data.DelayTime + loadData.delayModifier);
|
||
var tolerance = effectDelay + Mathf.Max(minLoadTolerance, minLoadRatio * loadData.data.Duration);
|
||
if (loadDelay > tolerance)
|
||
{
|
||
IndependentEffectManager.PushEffect(loadData.data, effect);
|
||
}
|
||
else
|
||
{
|
||
// 从加载开始所计算的延迟时间
|
||
effectDelay = Mathf.Max(0f, effectDelay - loadDelay);
|
||
if (effectDelay > 0)
|
||
{
|
||
effect.gameObject.SetActive(false);
|
||
DelayList.Add(new DelayEffectReference(loadData, effect, Time.time + effectDelay));
|
||
}
|
||
else
|
||
{
|
||
ActiveEffect(effect, loadData);
|
||
}
|
||
}
|
||
}
|
||
|
||
public virtual Transform GetBindPoint(string pointName, out Vector3 offset)
|
||
{
|
||
// 故意保留的强制报错,实际只有角色的需要拥有获取绑定点的功能,其他的只保留一个编译接口
|
||
throw new InvalidOperationException(string.Format("{0}未实现获取绑定点的功能,不应该使用绑定类型特效!", GetType()));
|
||
}
|
||
|
||
public virtual Obj_Character GetOwnerCharacter()
|
||
{
|
||
// 故意保留的强制报错,实际只有角色才能获得拥有者,其他的只保留一个编译接口
|
||
throw new InvalidOperationException(string.Format("{0}未实现获取拥有者角色的功能,不应该使用神像类型特效!", GetType()));
|
||
}
|
||
|
||
private void ActiveEffect(Transform effect, EffectLoadData loadData)
|
||
{
|
||
CommonUtility.SetLayersRecursively(effect, loadData.layer);
|
||
var activeResult = ActiveEffectInherited(effect, loadData);
|
||
if (loadData.playCallback != null)
|
||
loadData.playCallback(activeResult ? effect.gameObject : null, loadData.playParameter);
|
||
}
|
||
|
||
protected bool ActiveEffectInherited(Transform effect, EffectLoadData loadData)
|
||
{
|
||
var result = false;
|
||
var customData = (CustomEffectLoadData) loadData.customData;
|
||
switch (customData.EffectType)
|
||
{
|
||
case CommonEffectType.Bound:
|
||
{
|
||
var duration = loadData.data.Duration;
|
||
if (duration < 0)
|
||
duration = float.PositiveInfinity;
|
||
var activeEffectData = new CommonEffectReference(effect, loadData.data, loadData.handle,
|
||
Time.time + duration);
|
||
Vector3 offset;
|
||
var bindPoint = GetBindPoint(loadData.data.ParentName, out offset);
|
||
activeEffectData.SetBindPoint(bindPoint,
|
||
offset + new Vector3(loadData.data.OffsetX, loadData.data.OffsetY, loadData.data.OffsetZ));
|
||
ActiveList.Add(activeEffectData);
|
||
// Hack:给预警圈的特别处理
|
||
SetWarningData(effect, loadData.data);
|
||
result = true;
|
||
}
|
||
break;
|
||
case CommonEffectType.Position:
|
||
{
|
||
var effectData = (NormalEffectCustomData) customData;
|
||
var duration = effectData.duration > 0f ? effectData.duration : loadData.data.Duration;
|
||
if (duration < 0)
|
||
duration = float.PositiveInfinity;
|
||
var activeEffectData =
|
||
new CommonEffectReference(effect, loadData.data, loadData.handle, Time.time + duration);
|
||
effect.position = effectData.position;
|
||
// NoRotation不需要旋转
|
||
var rotation = loadData.data.RotationType == EffectRotationType.noRotation
|
||
? Quaternion.identity
|
||
: effectData.rotation;
|
||
if (loadData.data.Yaw > 0)
|
||
rotation = rotation * Quaternion.Euler(0f, loadData.data.Yaw, 0f);
|
||
effect.rotation = rotation;
|
||
effect.localScale = loadData.data.Scale > 0 ? Vector3.one * loadData.data.Scale : Vector3.one;
|
||
ActiveList.Add(activeEffectData);
|
||
// Hack:给预警圈的特别处理
|
||
SetWarningData(effect, loadData.data);
|
||
result = true;
|
||
}
|
||
break;
|
||
case CommonEffectType.Bullet:
|
||
{
|
||
var effectData = (BulletCustomData) customData;
|
||
var source = Singleton<ObjManager>.Instance.FindObjCharacterInScene(effectData.sourceId);
|
||
if (source != null)
|
||
{
|
||
var activeBulletData = new BulletEffectReference(effect, effectData.targetId, source,
|
||
effectData.bulletData, loadData.data, loadData.handle);
|
||
effect.position = source.transform.position +
|
||
source.transform.rotation * activeBulletData.GetStartOffset();
|
||
var delay = Mathf.Max(0f, Time.time - loadData.startTime - loadData.data.DelayTime);
|
||
// 模拟子弹加载期间的位移
|
||
if (activeBulletData.InitBullet(delay))
|
||
{
|
||
ActiveList.Add(activeBulletData);
|
||
result = true;
|
||
}
|
||
}
|
||
|
||
if (!result)
|
||
IndependentEffectManager.PushEffect(loadData.data, effect);
|
||
}
|
||
break;
|
||
case CommonEffectType.BulletToPos:
|
||
{
|
||
var effectData = (BulletToPosData) customData;
|
||
var activeBulletData = new BulletToPosReference(effect, effectData.startPos, effectData.endPos,
|
||
effectData.bulletData, loadData.data, loadData.handle);
|
||
ActiveList.Add(activeBulletData);
|
||
result = true;
|
||
}
|
||
break;
|
||
case CommonEffectType.Chain:
|
||
{
|
||
var effectData = (ChainCustomData) customData;
|
||
var activeChainData = new ChainEffectReference(effect, effectData, loadData.data, loadData.handle);
|
||
ActiveList.Add(activeChainData);
|
||
result = true;
|
||
}
|
||
break;
|
||
case CommonEffectType.Stamp:
|
||
{
|
||
var effectData = (StampCustomData) customData;
|
||
var activeEffectData =
|
||
new StampEffectReference(effect, loadData.data, effectData.stampCount, loadData.handle);
|
||
Vector3 offset;
|
||
var bindPoint = GetBindPoint(loadData.data.ParentName, out offset);
|
||
activeEffectData.SetBindPoint(bindPoint,
|
||
offset + new Vector3(loadData.data.OffsetX, loadData.data.OffsetY, loadData.data.OffsetZ));
|
||
ActiveList.Add(activeEffectData);
|
||
result = true;
|
||
}
|
||
break;
|
||
case CommonEffectType.WuChangLink:
|
||
{
|
||
var effectData = (WuChangLinkData) customData;
|
||
var activeLinkRef = new WuChangLinkReference(effect, effectData, loadData.data, loadData.handle);
|
||
ActiveList.Add(activeLinkRef);
|
||
result = true;
|
||
}
|
||
break;
|
||
case CommonEffectType.WuChangAirport:
|
||
{
|
||
var effectData = (WuChangAirportData) customData;
|
||
var airportReference = new WuChangAirportReference(effect, effectData, loadData.data, loadData.handle);
|
||
ActiveList.Add(airportReference);
|
||
result = true;
|
||
}
|
||
break;
|
||
case CommonEffectType.Meteor:
|
||
{
|
||
var effectData = (MeteorEffectData) customData;
|
||
var meteorRef = new MeteorEffectReference(effect, effectData, loadData.data, loadData.handle);
|
||
ActiveList.Add(meteorRef);
|
||
result = true;
|
||
}
|
||
break;
|
||
case CommonEffectType.Avatar:
|
||
{
|
||
var character = GetOwnerCharacter();
|
||
if (character != null)
|
||
{
|
||
var duration = loadData.data.Duration;
|
||
if (duration < 0)
|
||
duration = float.PositiveInfinity;
|
||
var activeEffectData = new AvatarEffectReference(effect, loadData.data, loadData.handle,
|
||
Time.time + duration);
|
||
activeEffectData.Init(character);
|
||
ActiveList.Add(activeEffectData);
|
||
result = true;
|
||
}
|
||
}
|
||
break;
|
||
default:
|
||
LogModule.ErrorLog(string.Format("未处理类型为{0}的特效!", customData.EffectType));
|
||
break;
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
protected void Init()
|
||
{
|
||
IsInited = true;
|
||
ActiveList = new List<BaseEffectReference>();
|
||
DelayList = new List<DelayEffectReference>();
|
||
TryAddEvent();
|
||
}
|
||
|
||
protected virtual void OnEnable()
|
||
{
|
||
_enable = true;
|
||
TryAddEvent();
|
||
}
|
||
|
||
protected virtual void OnDisable()
|
||
{
|
||
_enable = false;
|
||
TryAddEvent();
|
||
}
|
||
|
||
private void SetWarningData(Transform effect, Tab_Effect data)
|
||
{
|
||
// 特殊处理技能警告指示器 - 暂时简单做法是用普通加载流程,然后只在配置位置时配置效果
|
||
var effectType = (EffectLogic.EffectType) data.Type;
|
||
switch (effectType)
|
||
{
|
||
case EffectLogic.EffectType.TYPE_CIRCLE:
|
||
effect.GetComponent<WarningCircle>().SetEffectData(data);
|
||
break;
|
||
case EffectLogic.EffectType.TYPE_RECTANGLE:
|
||
effect.GetComponent<WarningRect>().SetEffectData(data);
|
||
break;
|
||
case EffectLogic.EffectType.TYPE_SECTOR:
|
||
effect.GetComponent<WarningSector>().SetEffectData(data);
|
||
break;
|
||
}
|
||
}
|
||
|
||
private void OnDestroy()
|
||
{
|
||
if (GameManager.gameManager != null && GameManager.gameManager.effectPool != null)
|
||
GameManager.gameManager.effectPool.DestroyEffectLogic(this);
|
||
}
|
||
|
||
private void TryAddEvent()
|
||
{
|
||
if (IsInited && _enable)
|
||
EventDispatcher.Instance.Add(Games.Events.EventId.PostMainCameraMove, AfterCameraMovement);
|
||
else
|
||
EventDispatcher.Instance.Remove(Games.Events.EventId.PostMainCameraMove,
|
||
AfterCameraMovement);
|
||
}
|
||
|
||
protected virtual void AfterCameraMovement(object args)
|
||
{
|
||
for (var i = DelayList.Count - 1; i >= 0; i--)
|
||
try
|
||
{
|
||
if (DelayList[i].effect == null)
|
||
{
|
||
#if UNITY_EDITOR
|
||
LogModule.ErrorLog(string.Format("一个于{1}的特效{0}被使用不正常的方式删除!", DelayList[i].loadData.data.EffectID,
|
||
transform.GetHierarchyName()));
|
||
#endif
|
||
DelayList.RemoveAt(i);
|
||
}
|
||
// 处于延迟发布的状态
|
||
else if (DelayList[i].CheckActive())
|
||
{
|
||
var delayReference = DelayList[i];
|
||
DelayList.RemoveAt(i);
|
||
// 可能某个主角模型在Delay阶段被析构,导致绑定的特效被析构
|
||
if (delayReference.effect != null)
|
||
{
|
||
delayReference.effect.gameObject.SetActive(true);
|
||
ActiveEffect(delayReference.effect, delayReference.loadData);
|
||
}
|
||
}
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
LogModule.ErrorLog(e.ToString());
|
||
}
|
||
|
||
for (var i = ActiveList.Count - 1; i >= 0; i--)
|
||
try
|
||
{
|
||
// 处于延迟回收状态
|
||
if (ActiveList[i].effect == null)
|
||
{
|
||
#if UNITY_EDITOR
|
||
LogModule.ErrorLog(string.Format("一个于{1}的特效{0}被使用不正常的方式删除!", ActiveList[i].data.EffectID,
|
||
transform.GetHierarchyName()));
|
||
#endif
|
||
ActiveList.RemoveAt(i);
|
||
}
|
||
else if (ActiveList[i].DelayRecovery)
|
||
{
|
||
if (!ActiveList[i].UpdateForDelayRecovery())
|
||
RemoveEffectAt(i);
|
||
}
|
||
else
|
||
{
|
||
if (!ActiveList[i].Update())
|
||
if (!ActiveList[i].CheckDelayRecovery())
|
||
RemoveEffectAt(i);
|
||
}
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
LogModule.ErrorLog(e.ToString());
|
||
}
|
||
}
|
||
|
||
public void RemoveEffectByHandle(int handleId)
|
||
{
|
||
if (handleId != 0)
|
||
{
|
||
IndependentEffectManager.CancelLoadByHandleId(handleId);
|
||
for (var i = ActiveList.Count - 1; i >= 0; i--)
|
||
try
|
||
{
|
||
if (ActiveList[i].effectHandle == handleId)
|
||
RemoveEffectAt(i);
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
LogModule.ErrorLog(e.ToString());
|
||
}
|
||
|
||
for (var i = DelayList.Count - 1; i >= 0; i--)
|
||
try
|
||
{
|
||
if (DelayList[i].loadData.handle == handleId)
|
||
RemoveDelayAt(i);
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
LogModule.ErrorLog(e.ToString());
|
||
}
|
||
}
|
||
}
|
||
|
||
public void RemoveEffectByEffectId(int effectId)
|
||
{
|
||
IndependentEffectManager.CancelLoad(a => a.data.EffectID == effectId);
|
||
for (var i = ActiveList.Count - 1; i >= 0; i--)
|
||
try
|
||
{
|
||
if (ActiveList[i].data.EffectID == effectId)
|
||
RemoveEffectAt(i);
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
LogModule.ErrorLog(e.ToString());
|
||
}
|
||
|
||
for (var i = DelayList.Count - 1; i >= 0; i--)
|
||
try
|
||
{
|
||
if (DelayList[i].loadData.data.EffectID == effectId)
|
||
RemoveDelayAt(i);
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
LogModule.ErrorLog(e.ToString());
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清除全部播放中的特效
|
||
/// </summary>
|
||
public void CleanEffect()
|
||
{
|
||
// 清除加载中的特效
|
||
IndependentEffectManager.CleanEffectLogic(this);
|
||
}
|
||
|
||
protected void RemoveEffectAt(int index)
|
||
{
|
||
var item = ActiveList[index];
|
||
ActiveList.RemoveAt(index);
|
||
IndependentEffectManager.PushEffect(item);
|
||
}
|
||
|
||
protected void RemoveDelayAt(int index)
|
||
{
|
||
var delayReference = DelayList[index];
|
||
DelayList.RemoveAt(index);
|
||
IndependentEffectManager.PushEffect(delayReference.loadData.data, delayReference.effect);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从延迟列表获得HandleId符合条件的物体
|
||
/// </summary>
|
||
// 特殊处理HandleId为0的情况 - 0视为不可用id
|
||
public DelayEffectReference GetDelayByHandle(int? handleId)
|
||
{
|
||
if (handleId == null)
|
||
return null;
|
||
return DelayList.Find(a => a.loadData.handle == handleId);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从激活列表获得HandleId符合条件的物体
|
||
/// </summary>
|
||
// 延迟回收的特效不允许获得
|
||
public BaseEffectReference GetReferenceByHandle(int? handleId)
|
||
{
|
||
return handleId == null ? null : ActiveList.Find(a => a.effectHandle == handleId && !a.DelayRecovery);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从激活列表获得EffectId符合条件的物体
|
||
/// </summary>
|
||
// 延迟回收的特效不允许获得
|
||
public BaseEffectReference GetReferenceByEffectId(int effectId)
|
||
{
|
||
var result = ActiveList.Find(a => a.data.EffectID == effectId && !a.DelayRecovery);
|
||
return result;
|
||
}
|
||
|
||
public static bool ValidEffectByCount(Tab_Effect data, GameDefine_Globe.OBJ_TYPE objType)
|
||
{
|
||
var result = true;
|
||
if (objType == GameDefine_Globe.OBJ_TYPE.OBJ_OTHER_PLAYER || //其他玩家
|
||
objType == GameDefine_Globe.OBJ_TYPE.OBJ_NPC || //NPC
|
||
objType == GameDefine_Globe.OBJ_TYPE.OBJ_FELLOW || //伙伴
|
||
objType == GameDefine_Globe.OBJ_TYPE.OBJ_ZOMBIE_PLAYER || //僵尸玩家
|
||
objType == GameDefine_Globe.OBJ_TYPE.OBJ_DROP_ITEM)//掉落包
|
||
{
|
||
result = false;
|
||
if (data.CountLimit >= EffectLimitLevel.unlimit)
|
||
result = true;
|
||
// 原始逻辑,一般认为unlimit的是非技能特效
|
||
else if (PlayerPreferenceData.SystemSkillEffectEnable)
|
||
{
|
||
var limit = data.CountLimit < EffectLimitLevel.important
|
||
? PlayerPreferenceData.effectCountLimit.commonCount
|
||
: PlayerPreferenceData.effectCountLimit.importantCount;
|
||
if (limit > GameManager.gameManager.effectPool.inUseCount)
|
||
result = true;
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
}
|
||
|
||
public class DelayEffectReference
|
||
{
|
||
public readonly Transform effect;
|
||
public readonly EffectLoadData loadData;
|
||
public readonly float startTime;
|
||
|
||
public DelayEffectReference(EffectLoadData loadData, Transform effect, float startTime)
|
||
{
|
||
this.loadData = loadData;
|
||
this.effect = effect;
|
||
this.startTime = startTime;
|
||
}
|
||
|
||
public bool CheckActive()
|
||
{
|
||
return Time.time > startTime;
|
||
}
|
||
}
|
||
|
||
public abstract class BaseEffectReference
|
||
{
|
||
public readonly Tab_Effect data;
|
||
public readonly Transform effect;
|
||
public readonly int? effectHandle;
|
||
|
||
protected BaseEffectReference(Tab_Effect data, Transform effect, int? effectHandle)
|
||
{
|
||
this.data = data;
|
||
this.effect = effect;
|
||
this.effectHandle = effectHandle;
|
||
}
|
||
|
||
// 特效是否需要延迟回收 - 有拖尾等特效效果等待效果结束
|
||
public bool DelayRecovery { get; private set; }
|
||
|
||
// 特效延迟回收时间
|
||
public float DelayRecoveryTime { get; private set; }
|
||
|
||
/// <summary>
|
||
/// 更新特效状态
|
||
/// </summary>
|
||
/// <returns>特效是否继续存在</returns>
|
||
public virtual bool Update()
|
||
{
|
||
return effect != null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查特效是否需要延迟回收
|
||
/// </summary>
|
||
public bool CheckDelayRecovery()
|
||
{
|
||
var cleaner = effect.GetComponent<IParticleCleaner>();
|
||
if (cleaner == null)
|
||
{
|
||
LogModule.ErrorLog(string.Format("特效{0}上没有ParticleCleaner!", effect.GetHierarchyName()));
|
||
}
|
||
else if (cleaner.DelayRecoveryTime > 0f)
|
||
{
|
||
DelayRecovery = true;
|
||
DelayRecoveryTime = Time.time + cleaner.DelayRecoveryTime;
|
||
cleaner.StartDelayRecovery();
|
||
}
|
||
|
||
return DelayRecovery;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查特效是否延迟回收尚未结束
|
||
/// </summary>
|
||
public virtual bool UpdateForDelayRecovery()
|
||
{
|
||
return Time.time < DelayRecoveryTime;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 特效播放结束时统一处理
|
||
/// </summary>
|
||
public virtual void EndEffect()
|
||
{
|
||
}
|
||
}
|
||
|
||
public delegate void PlayEffectDelegate(GameObject effectObj, object param);
|
||
|
||
#region 特效物品通用管理池
|
||
|
||
public class EffectPool : AutoLoadPool<Transform, EffectLoadData, EffectLoadTask>
|
||
{
|
||
public EffectPool(Transform root, UnityAction<Transform> createAction) : base(root, true, createAction)
|
||
{
|
||
}
|
||
|
||
public static string GetBundleName(string assetPath)
|
||
{
|
||
return LoadAssetBundle.FixBundleName(LoadAssetBundle.BUNDLE_PATH_EFFECT + assetPath);
|
||
}
|
||
|
||
public void PreloadEffect(Tab_Effect data)
|
||
{
|
||
var bundleName = GetBundleName(data.Path);
|
||
Preload(bundleName, data.Path);
|
||
}
|
||
|
||
public void PullEffect(EffectLoadData taskStarter)
|
||
{
|
||
var bundleName = GetBundleName(taskStarter.data.Path);
|
||
PullItem(taskStarter, bundleName, taskStarter.data.Path);
|
||
}
|
||
|
||
public void PushEffect(BaseEffectReference effect)
|
||
{
|
||
PushEffect(effect.data, effect.effect);
|
||
}
|
||
|
||
public void PushEffect(Tab_Effect data, Transform effect)
|
||
{
|
||
var bundleName = GetBundleName(data.Path);
|
||
if (data.IsOnlyDeactive)
|
||
{
|
||
PushItem(bundleName, data.Path, effect);
|
||
}
|
||
else
|
||
{
|
||
// 不退回物体,但是要求减少使用记录
|
||
ReduceCount(bundleName, data.Path);
|
||
Object.Destroy(effect.gameObject);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 按照特效句柄取消一个特效加载任务
|
||
/// </summary>
|
||
/// <param name="handleId">特效句柄</param>
|
||
// 注:主要用于特效在完成加载前,服务器要求停止播放的情况
|
||
public void CancelLoadByHandleId(int handleId)
|
||
{
|
||
//if (!Killed)
|
||
for (var i = loadTaskList.Count - 1; i >= 0; i--)
|
||
loadTaskList[i].CancelByHandle(handleId);
|
||
}
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 全局唯一的特效管理池,统一处理所有特效GameObject的回收和再利用
|
||
/// </summary>
|
||
public class CentralEffectPool : EffectPool
|
||
{
|
||
public CentralEffectPool(Transform root) : base(root, EnsureParticleCleaner)
|
||
{
|
||
}
|
||
|
||
private static void EnsureParticleCleaner(Transform child)
|
||
{
|
||
// 如果没有自定义粒子回收器,就添加通用回收器
|
||
var cleaner = child.GetComponent<IParticleCleaner>();
|
||
if (cleaner == null)
|
||
child.gameObject.AddComponent<ParticleCleaner>();
|
||
}
|
||
|
||
/// <summary>
|
||
/// EffectLogic被销毁时,通知池子取消加载注册
|
||
/// </summary>
|
||
public void CleanEffectLogic(BaseEffectLogic effectLogic)
|
||
{
|
||
// if (!Killed)
|
||
// {
|
||
// 移除对加载事件的监听
|
||
for (var i = 0; i < loadTaskList.Count; i++)
|
||
try
|
||
{
|
||
loadTaskList[i].RemoveListener(a => a.callback == effectLogic.OnCreateEffect);
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
LogModule.ErrorLog(e.ToString());
|
||
}
|
||
|
||
// 回收使用中的特效
|
||
for (var i = 0; i < effectLogic.ActiveList.Count; i++)
|
||
if (effectLogic.ActiveList[i].effect != null)
|
||
PushEffect(effectLogic.ActiveList[i]);
|
||
for (var i = 0; i < effectLogic.DelayList.Count; i++)
|
||
if (effectLogic.DelayList[i].effect != null)
|
||
{
|
||
var delayItem = effectLogic.DelayList[i];
|
||
PushEffect(delayItem.loadData.data, delayItem.effect);
|
||
}
|
||
effectLogic.ActiveList.Clear();
|
||
effectLogic.DelayList.Clear();
|
||
}
|
||
|
||
public void DestroyEffectLogic(BaseEffectLogic effectLogic)
|
||
{
|
||
if (!GameManager.applicationQuit)
|
||
{
|
||
// 移除对加载事件的监听
|
||
for (var i = 0; i < loadTaskList.Count; i++)
|
||
try
|
||
{
|
||
loadTaskList[i].RemoveListener(a => a.callback == effectLogic.OnCreateEffect);
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
LogModule.ErrorLog(e.ToString());
|
||
}
|
||
|
||
// 回收会导致报错,因此跳过回收流程
|
||
for (var i = 0; i < effectLogic.ActiveList.Count; i++)
|
||
if (effectLogic.ActiveList[i].effect != null)
|
||
{
|
||
var assetName = effectLogic.ActiveList[i].data.Path;
|
||
var bundleName = GetBundleName(assetName);
|
||
ReduceCount(bundleName, assetName);
|
||
}
|
||
|
||
for (var i = 0; i < effectLogic.DelayList.Count; i++)
|
||
if (effectLogic.DelayList[i].effect != null)
|
||
{
|
||
var assetName = effectLogic.DelayList[i].loadData.data.Path;
|
||
var bundleName = GetBundleName(assetName);
|
||
ReduceCount(bundleName, assetName);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 主角技能特效预加载
|
||
// 预加载方式为在这个地方添加一次引用防止成为Unused
|
||
|
||
#region MainCharacter Preload Effect
|
||
|
||
// 注:其他角色的技能表信息是无法获得的,因此只能预加载主角特效
|
||
private readonly List<string> _holdEffects = new List<string>();
|
||
|
||
public void SetMainPlayerPreload()
|
||
{
|
||
var preloadList = new HashSet<string>();
|
||
var playerDataPool = GameManager.gameManager.PlayerDataPool;
|
||
foreach (var ownSkill in playerDataPool.OwnSkillInfo)
|
||
{
|
||
if (ownSkill.SkillExTable != null)
|
||
{
|
||
var preload = ownSkill.SkillExTable.PreloadEffect;
|
||
if (!string.IsNullOrEmpty(preload) && !"-1".Equals(preload) && !"0".Equals(preload))
|
||
{
|
||
var preloads = preload.Split(';');
|
||
for (var i = 0; i < preloads.Length; i++)
|
||
{
|
||
int effectId;
|
||
if (int.TryParse(preloads[i], out effectId))
|
||
{
|
||
if (effectId > GlobeVar.INVALID_ID)
|
||
{
|
||
var effectData = TableManager.GetEffectByID(effectId, 0);
|
||
if (effectData != null)
|
||
preloadList.Add(effectData.Path);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
SetHoldPreload(preloadList);
|
||
}
|
||
|
||
public void SetHoldPreload(ICollection<string> effects)
|
||
{
|
||
for (var i = _holdEffects.Count - 1; i >= 0; i--)
|
||
{
|
||
if (!effects.Contains(_holdEffects[i]))
|
||
{
|
||
var assetName = _holdEffects[i];
|
||
var bundleName = GetBundleName(assetName);
|
||
ReduceCount(bundleName, assetName, true);
|
||
_holdEffects.RemoveAt(i);
|
||
}
|
||
}
|
||
foreach (var effect in effects)
|
||
if (!_holdEffects.Contains(effect))
|
||
{
|
||
_holdEffects.Add(effect);
|
||
var assetName = effect;
|
||
var bundleName = GetBundleName(assetName);
|
||
// 如果已经有池,就直接添加数量
|
||
if (!Preload(bundleName, assetName))
|
||
AddCount(bundleName, assetName, true);
|
||
}
|
||
}
|
||
|
||
// 只有Kill会清空Pool;Kill后也不会重用池,因此不需要额外调用这个。
|
||
public void CleanHoldPreload()
|
||
{
|
||
for (var i = 0; i < _holdEffects.Count; i++)
|
||
{
|
||
var assetName = _holdEffects[i];
|
||
var bundleName = GetBundleName(assetName);
|
||
ReduceCount(bundleName, assetName, true);
|
||
}
|
||
_holdEffects.Clear();
|
||
}
|
||
|
||
// 底层锁死Pool只在资源加载完成才构造,因此只能监听加载完成后,再执行Hold;
|
||
// 修改底层风险过高,因此采用加载完成加Token的流程
|
||
protected override void OnLoadTaskFinished(EffectLoadTask loadTask)
|
||
{
|
||
base.OnLoadTaskFinished(loadTask);
|
||
if (_holdEffects.Contains(loadTask.PrefabName))
|
||
AddCount(loadTask.BundleName, loadTask.PrefabName, true);
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
|
||
public class EffectLoadTask : BaseLoadTask<Transform, EffectLoadData, EffectLoadTask>
|
||
{
|
||
/// <summary>
|
||
/// 按照特效句柄Id取消一个加载需求
|
||
/// </summary>
|
||
/// <param name="handle">特效句柄Id</param>
|
||
/// <returns>是否加载任务已空</returns>
|
||
public void CancelByHandle(int handle)
|
||
{
|
||
for (var i = taskListeners.Count - 1; i >= 0; i--)
|
||
if (taskListeners[i].handle == handle)
|
||
taskListeners.RemoveAt(i);
|
||
}
|
||
}
|
||
|
||
public class EffectLoadData : BaseLoadData<Transform, EffectLoadData>
|
||
{
|
||
public readonly Tab_Effect data;
|
||
|
||
/// <summary>
|
||
/// 特效播放延迟的修正数值
|
||
/// </summary>
|
||
public readonly float delayModifier;
|
||
|
||
public readonly int? handle;
|
||
public readonly int layer;
|
||
public readonly PlayEffectDelegate playCallback;
|
||
public readonly object playParameter;
|
||
public readonly float startTime;
|
||
public object customData;
|
||
|
||
public EffectLoadData(BaseEffectLogic effectLogic, int layer, Tab_Effect data, int? handle,
|
||
PlayEffectDelegate playCallback, object playParameter, object customData, float delayModifier = 0f) : base(
|
||
effectLogic.transform, effectLogic.OnCreateEffect)
|
||
{
|
||
this.layer = layer;
|
||
this.data = data;
|
||
this.handle = handle;
|
||
this.playCallback = playCallback;
|
||
this.playParameter = playParameter;
|
||
this.customData = customData;
|
||
this.delayModifier = delayModifier;
|
||
startTime = Time.time;
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 特效物品自定义参数
|
||
|
||
public class BulletCustomData : CustomEffectLoadData
|
||
{
|
||
public readonly Tab_Bullet bulletData;
|
||
public readonly int sourceId;
|
||
public readonly int targetId;
|
||
|
||
public BulletCustomData(Tab_Bullet bulletData, int sourceId, int targetId)
|
||
{
|
||
this.bulletData = bulletData;
|
||
this.sourceId = sourceId;
|
||
this.targetId = targetId;
|
||
}
|
||
|
||
public override CommonEffectType EffectType
|
||
{
|
||
get { return CommonEffectType.Bullet; }
|
||
}
|
||
}
|
||
|
||
public class BulletToPosData : CustomEffectLoadData
|
||
{
|
||
public readonly Tab_Bullet bulletData;
|
||
public readonly Vector3 endPos;
|
||
public readonly Vector3 startPos;
|
||
|
||
public BulletToPosData(Tab_Bullet bulletData, Vector3 startPos, Vector3 endPos)
|
||
{
|
||
this.bulletData = bulletData;
|
||
this.startPos = startPos;
|
||
this.endPos = endPos;
|
||
}
|
||
|
||
public override CommonEffectType EffectType
|
||
{
|
||
get { return CommonEffectType.BulletToPos; }
|
||
}
|
||
}
|
||
|
||
public class NormalEffectCustomData : CustomEffectLoadData
|
||
{
|
||
public readonly float duration;
|
||
public readonly Vector3 position;
|
||
public readonly Quaternion rotation;
|
||
|
||
public NormalEffectCustomData(Vector3 position, Quaternion rotation, float duration = -1f)
|
||
{
|
||
this.position = position;
|
||
this.rotation = rotation;
|
||
this.duration = duration;
|
||
}
|
||
|
||
public override CommonEffectType EffectType
|
||
{
|
||
get { return CommonEffectType.Position; }
|
||
}
|
||
}
|
||
|
||
public class ChainCustomData : CustomEffectLoadData
|
||
{
|
||
public readonly float duration;
|
||
|
||
public readonly int sourceId;
|
||
public readonly float speed;
|
||
|
||
public ChainCustomData(int sourceId, int targetId, float duration, float speed)
|
||
{
|
||
this.sourceId = sourceId;
|
||
TargetId = targetId;
|
||
this.duration = duration;
|
||
this.speed = speed;
|
||
}
|
||
|
||
public override CommonEffectType EffectType
|
||
{
|
||
get { return CommonEffectType.Chain; }
|
||
}
|
||
|
||
public int TargetId { get; private set; }
|
||
|
||
public void ResetTarget(int targetId)
|
||
{
|
||
TargetId = targetId;
|
||
}
|
||
}
|
||
|
||
public class BoundCustomData : CustomEffectLoadData
|
||
{
|
||
public override CommonEffectType EffectType
|
||
{
|
||
get { return CommonEffectType.Bound; }
|
||
}
|
||
}
|
||
|
||
public class StampCustomData : CustomEffectLoadData
|
||
{
|
||
public readonly int stampCount;
|
||
|
||
public StampCustomData(int stampCount)
|
||
{
|
||
this.stampCount = stampCount;
|
||
}
|
||
|
||
public override CommonEffectType EffectType
|
||
{
|
||
get { return CommonEffectType.Stamp; }
|
||
}
|
||
}
|
||
|
||
public class WuChangLinkData : CustomEffectLoadData
|
||
{
|
||
public readonly int sourceId;
|
||
public readonly int targetId;
|
||
|
||
public WuChangLinkData(int sourceId, int targetId)
|
||
{
|
||
this.sourceId = sourceId;
|
||
this.targetId = targetId;
|
||
}
|
||
|
||
public override CommonEffectType EffectType
|
||
{
|
||
get { return CommonEffectType.WuChangLink; }
|
||
}
|
||
}
|
||
|
||
public class WuChangAirportData : CustomEffectLoadData
|
||
{
|
||
public readonly int count;
|
||
public readonly int sourceId;
|
||
|
||
public WuChangAirportData(int sourceId, int count)
|
||
{
|
||
this.sourceId = sourceId;
|
||
this.count = count;
|
||
}
|
||
|
||
public override CommonEffectType EffectType
|
||
{
|
||
get { return CommonEffectType.WuChangAirport; }
|
||
}
|
||
}
|
||
|
||
public class MeteorEffectData : CustomEffectLoadData
|
||
{
|
||
public readonly Vector3 fallPosition;
|
||
public readonly float fallTime;
|
||
public readonly Vector3 holdPosition;
|
||
public readonly float holdTime;
|
||
public readonly Vector3 rollPosition;
|
||
public readonly float rollTime;
|
||
|
||
public MeteorEffectData(float offsetY, float holdTime, float fallTime, float rollTime, Vector2 holdPos,
|
||
Vector2 fallPos, Vector2 rollPos)
|
||
{
|
||
this.holdTime = holdTime;
|
||
this.fallTime = fallTime + this.holdTime;
|
||
this.rollTime = rollTime + this.fallTime;
|
||
holdPosition = ActiveScene.GetTerrainPosition(holdPos.InsertY());
|
||
fallPosition = ActiveScene.GetTerrainPosition(fallPos.InsertY());
|
||
rollPosition = ActiveScene.GetTerrainPosition(rollPos.InsertY());
|
||
holdPosition.y += offsetY;
|
||
}
|
||
|
||
public override CommonEffectType EffectType
|
||
{
|
||
get { return CommonEffectType.Meteor; }
|
||
}
|
||
}
|
||
|
||
public class AvatarEffectData : CustomEffectLoadData
|
||
{
|
||
public override CommonEffectType EffectType
|
||
{
|
||
get { return CommonEffectType.Avatar; }
|
||
}
|
||
}
|
||
|
||
public abstract class CustomEffectLoadData
|
||
{
|
||
public abstract CommonEffectType EffectType { get; }
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 特效物品引用参数
|
||
|
||
/// <summary>
|
||
/// 标准绑定位置的特效基类
|
||
/// </summary>
|
||
public abstract class BoundEffectReferenceBase : BaseEffectReference
|
||
{
|
||
private readonly Quaternion _localRotation;
|
||
|
||
public BoundEffectReferenceBase(Transform effect, Tab_Effect data, int? effectHandle) : base(data, effect,
|
||
effectHandle)
|
||
{
|
||
_localRotation = data.Yaw > 0 ? Quaternion.Euler(0f, data.Yaw, 0f) : Quaternion.identity;
|
||
}
|
||
|
||
// 特效绑定点
|
||
public Transform BindPoint { get; private set; }
|
||
|
||
// 特效绑定偏移值
|
||
public Vector3 Offset { get; private set; }
|
||
|
||
public void SetBindPoint(Transform bindPoint, Vector3 offset)
|
||
{
|
||
BindPoint = bindPoint;
|
||
Offset = offset;
|
||
effect.SetParent(bindPoint, false);
|
||
effect.transform.localPosition = offset;
|
||
effect.transform.localRotation = _localRotation;
|
||
var scale = data.Scale > 0 ? data.Scale * Vector3.one : Vector3.one;
|
||
effect.transform.localScale = scale;
|
||
}
|
||
|
||
public override bool Update()
|
||
{
|
||
if (base.Update())
|
||
{
|
||
SetRotation();
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
private void SetRotation()
|
||
{
|
||
switch (data.RotationType)
|
||
{
|
||
case EffectRotationType.noRotation:
|
||
// 需要校正LocalPosition到世界类型
|
||
effect.rotation = _localRotation;
|
||
break;
|
||
case EffectRotationType.faceCamera:
|
||
// 暂时使用UiManager上面那个安全接口
|
||
var mainCamera = UIManager.Instance().GetWorldCamera();
|
||
if (mainCamera != null)
|
||
{
|
||
var rotation = Quaternion.LookRotation(mainCamera.transform.position - effect.position);
|
||
rotation = rotation * _localRotation;
|
||
effect.rotation = rotation;
|
||
}
|
||
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 载入中特效数据,用于存放动态加载的特效实例
|
||
/// </summary>
|
||
public class CommonEffectReference : BoundEffectReferenceBase
|
||
{
|
||
private readonly float _endTime;
|
||
|
||
public CommonEffectReference(Transform effect, Tab_Effect data, int? effectHandle, float endTime) : base(effect,
|
||
data,
|
||
effectHandle)
|
||
{
|
||
_endTime = endTime;
|
||
}
|
||
|
||
public override bool Update()
|
||
{
|
||
if (Time.time < _endTime)
|
||
return base.Update();
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 子弹数据记录
|
||
/// </summary>
|
||
public class BulletEffectReference : BaseEffectReference
|
||
{
|
||
private readonly Quaternion _localRotation;
|
||
private readonly BulletSimulator _simulator;
|
||
|
||
// 用于在Target析构后,重新恢复Target
|
||
private readonly int _targetId;
|
||
|
||
public readonly Tab_Bullet bulletData;
|
||
|
||
// 抛物线子弹最大偏移位置
|
||
private Vector3 _maxPathOffset;
|
||
|
||
private Vector3 _targetOffset;
|
||
private Transform _targetTransform;
|
||
|
||
public BulletEffectReference(Transform effect, int targetId, Obj source, Tab_Bullet bulletData, Tab_Effect data,
|
||
int? effectHandle) : base(data, effect, effectHandle)
|
||
{
|
||
this.bulletData = bulletData;
|
||
_localRotation = data.Yaw > 0 ? Quaternion.Euler(0f, data.Yaw, 0f) : Quaternion.identity;
|
||
_targetId = targetId;
|
||
// 提前推送目标无法获得时的攻击位置
|
||
_simulator = new BulletSimulator
|
||
{
|
||
targetPoint = source.transform.position + source.transform.rotation * GetStartOffset() +
|
||
source.transform.forward * bulletData.MinDistance
|
||
};
|
||
}
|
||
|
||
public Vector3 GetStartOffset()
|
||
{
|
||
return new Vector3(bulletData.StartPosX, bulletData.StartPosY, bulletData.StartPosZ);
|
||
}
|
||
|
||
public override bool Update()
|
||
{
|
||
var valid = base.Update() && MoveBullet();
|
||
// 子弹消散特效
|
||
if (!valid)
|
||
if (bulletData.EndEffectId > 0 && bulletData.EndEffectId != bulletData.EffectId)
|
||
if (IndependentEffectManager.Instance != null)
|
||
IndependentEffectManager.Instance.ShowEffect(bulletData.EndEffectId, effect.gameObject.layer, GameDefine_Globe.OBJ_TYPE.OBJ,
|
||
effect.position, Quaternion.Euler(0f, effect.eulerAngles.y, 0f));
|
||
return valid;
|
||
}
|
||
|
||
public bool InitBullet(float delay)
|
||
{
|
||
ResetAttackPoint();
|
||
_simulator.duration = (_simulator.targetPoint - effect.position).RemoveY().magnitude / bulletData.Speed;
|
||
var result = delay < _simulator.duration;
|
||
if (result)
|
||
{
|
||
_simulator.startTime = Time.time - delay;
|
||
_maxPathOffset = 0.5f * new Vector3(bulletData.BulletDirectionX, bulletData.BulletDirectionY, 0f) *
|
||
_simulator.duration;
|
||
_simulator.logicPoint = effect.position;
|
||
var scale = data.Scale > 0 ? data.Scale * Vector3.one : Vector3.one;
|
||
effect.localScale = scale;
|
||
result = MoveBullet();
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 移动子弹位置
|
||
/// </summary>
|
||
/// <returns>子弹尚未飞行到目标位置</returns>
|
||
private bool MoveBullet()
|
||
{
|
||
var result = true;
|
||
ResetAttackPoint();
|
||
// 计算子弹飞行时间百分比
|
||
var direction = _simulator.UpdateBullet().normalized;
|
||
if (_simulator.lastRatio >= 1f)
|
||
result = false;
|
||
var logicPoint = _simulator.logicPoint;
|
||
if (_maxPathOffset == Vector3.zero || direction == Vector3.zero)
|
||
{
|
||
effect.position = logicPoint;
|
||
}
|
||
else
|
||
{
|
||
var x = Vector3.Cross(Vector3.up, direction).normalized;
|
||
effect.position = logicPoint + (_maxPathOffset.x * x + _maxPathOffset.y * Vector3.up) *
|
||
(1f - Mathf.Abs(_simulator.lastRatio * 2 - 1f).ToSquare());
|
||
}
|
||
|
||
// 调节子弹面对方向
|
||
if (direction != Vector3.zero)
|
||
{
|
||
var rotationType = data.RotationType;
|
||
// 如果没有摄像机就统一回退到最基础方式
|
||
var cameraTrans = SceneLogic.CameraController != null && SceneLogic.CameraController.MainCamera != null
|
||
? SceneLogic.CameraController.MainCamera.transform
|
||
: null;
|
||
if (cameraTrans == null)
|
||
rotationType = EffectRotationType.byEffectType;
|
||
switch (rotationType)
|
||
{
|
||
case EffectRotationType.faceCamera:
|
||
// 忽略这个Null警告,这个位置不可能是Null
|
||
var y = cameraTrans.position - effect.position;
|
||
var x = Vector3.Cross(y, direction);
|
||
y = Vector3.Cross(direction, x);
|
||
if (y == Vector3.zero)
|
||
y = Vector3.up;
|
||
effect.rotation = Quaternion.LookRotation(direction, y) * _localRotation;
|
||
break;
|
||
case EffectRotationType.byEffectType:
|
||
effect.rotation = Quaternion.LookRotation(direction, Vector3.up) * _localRotation;
|
||
break;
|
||
}
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获得子弹攻击目标位置
|
||
/// </summary>
|
||
private void ResetAttackPoint()
|
||
{
|
||
// 试图恢复目标绑定
|
||
if (_targetId != 0 && _targetTransform == null)
|
||
{
|
||
var targetObj = Singleton<ObjManager>.Instance.FindObjCharacterInScene(_targetId);
|
||
if (targetObj != null)
|
||
if (targetObj.ObjEffectLogic == null)
|
||
{
|
||
_targetTransform = targetObj.transform;
|
||
_targetOffset = Vector3.zero;
|
||
}
|
||
else
|
||
{
|
||
_targetTransform = targetObj.ObjEffectLogic.GetBindPoint(bulletData.AttackPoint, out _targetOffset);
|
||
}
|
||
}
|
||
|
||
// 如果目标没有丢失,使用目标校正攻击位置
|
||
if (_targetTransform != null)
|
||
_simulator.targetPoint = _targetTransform.rotation * _targetOffset + _targetTransform.position;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 对位置飞行子弹引用记录
|
||
/// </summary>
|
||
public class BulletToPosReference : BaseEffectReference
|
||
{
|
||
private readonly float _duration;
|
||
private readonly Vector3 _endPos;
|
||
private readonly Quaternion _localRotation;
|
||
private readonly Vector3 _offsetDir;
|
||
private readonly Vector3 _startPos;
|
||
private readonly float _startTime;
|
||
|
||
public readonly Tab_Bullet bulletData;
|
||
|
||
public BulletToPosReference(Transform effect, Vector3 startPos, Vector3 endPos, Tab_Bullet bulletData,
|
||
Tab_Effect data,
|
||
int? effectHandle) : base(data, effect, effectHandle)
|
||
{
|
||
this.bulletData = bulletData;
|
||
_startPos = startPos;
|
||
_endPos = endPos;
|
||
_startTime = Time.time;
|
||
_localRotation = data.Yaw > 0 ? Quaternion.Euler(0f, data.Yaw, 0f) : Quaternion.identity;
|
||
var delta = endPos - startPos;
|
||
delta.y = 0f;
|
||
if (delta == Vector3.zero)
|
||
{
|
||
_startTime = float.NegativeInfinity;
|
||
_duration = 0f;
|
||
effect.position = startPos;
|
||
}
|
||
else
|
||
{
|
||
_duration = Mathf.Min(60f, delta.magnitude / bulletData.Speed);
|
||
var rotation = Quaternion.LookRotation(delta, Vector3.up) * _localRotation;
|
||
_offsetDir = rotation * new Vector3(bulletData.BulletDirectionX, bulletData.BulletDirectionY, 0f);
|
||
MoveBullet();
|
||
}
|
||
}
|
||
|
||
public override bool Update()
|
||
{
|
||
var valid = base.Update() && MoveBullet();
|
||
// 子弹消散特效
|
||
if (!valid)
|
||
if (bulletData.EndEffectId > 0 && bulletData.EndEffectId != bulletData.EffectId)
|
||
if (IndependentEffectManager.Instance != null)
|
||
IndependentEffectManager.Instance.ShowEffect(bulletData.EndEffectId, effect.gameObject.layer, GameDefine_Globe.OBJ_TYPE.OBJ,
|
||
effect.position, Quaternion.Euler(0f, effect.eulerAngles.y, 0f));
|
||
return valid;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 移动子弹位置
|
||
/// </summary>
|
||
/// <returns>子弹尚未飞行到目标位置</returns>
|
||
private bool MoveBullet()
|
||
{
|
||
var result = Time.time < _startTime + _duration;
|
||
if (result)
|
||
{
|
||
var time = Time.time - _startTime;
|
||
var ratio = time / _duration;
|
||
var direction = (_endPos - _startPos).normalized;
|
||
if (_offsetDir == Vector3.zero)
|
||
{
|
||
effect.position = Vector3.Lerp(_startPos, _endPos, ratio);
|
||
}
|
||
else
|
||
{
|
||
var currentOffsetDir = _offsetDir * (1f - ratio * 2f);
|
||
effect.position = Vector3.Lerp(_startPos, _endPos, ratio) +
|
||
(_offsetDir + currentOffsetDir) * 0.5f * bulletData.Speed * time;
|
||
direction += currentOffsetDir;
|
||
}
|
||
var rotationType = data.RotationType;
|
||
// 如果没有摄像机就统一回退到最基础方式
|
||
var cameraTrans = SceneLogic.CameraController != null && SceneLogic.CameraController.MainCamera != null
|
||
? SceneLogic.CameraController.MainCamera.transform
|
||
: null;
|
||
if (cameraTrans == null)
|
||
rotationType = EffectRotationType.byEffectType;
|
||
switch (rotationType)
|
||
{
|
||
case EffectRotationType.faceCamera:
|
||
// 忽略这个Null警告,这个位置不可能是Null
|
||
var y = cameraTrans.position - effect.position;
|
||
var x = Vector3.Cross(y, direction);
|
||
y = Vector3.Cross(direction, x);
|
||
if (y == Vector3.zero)
|
||
y = Vector3.up;
|
||
effect.rotation = Quaternion.LookRotation(direction, y) * _localRotation;
|
||
break;
|
||
case EffectRotationType.byEffectType:
|
||
effect.rotation = Quaternion.LookRotation(direction, Vector3.up) * _localRotation;
|
||
break;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
effect.position = _endPos;
|
||
// 特殊处理结束不消失的流程
|
||
if (bulletData.EffectId > 0 && bulletData.EffectId == bulletData.EndEffectId &&
|
||
_startTime + data.Duration > Time.time)
|
||
result = true;
|
||
}
|
||
return result;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 锁链特效引用记录
|
||
/// </summary>
|
||
public class ChainEffectReference : BaseEffectReference
|
||
{
|
||
// 用于在Source或者Target析构后,重新恢复效果
|
||
private readonly LinkFxController _controller;
|
||
|
||
// 如果锁链拥有消散时间和飞行速度,需要做子弹效果
|
||
private BulletSimulator _bulletSimulator;
|
||
|
||
private ChainCustomData _chainData;
|
||
|
||
private float _endTime;
|
||
|
||
private Obj_Character _source;
|
||
private Obj_Character _target;
|
||
|
||
public ChainEffectReference(Transform effect, ChainCustomData chainData, Tab_Effect data, int? effectHandle) : base(
|
||
data, effect, effectHandle)
|
||
{
|
||
_chainData = chainData;
|
||
_controller = effect.GetComponent<LinkFxController>();
|
||
if (_controller == null)
|
||
LogModule.ErrorLog(string.Format("特效物体{0}上不存在锁链控制器", data.Path));
|
||
ResetEndTime();
|
||
}
|
||
|
||
public override bool Update()
|
||
{
|
||
var result = base.Update() && Time.time < _endTime;
|
||
if (result && _controller != null)
|
||
{
|
||
if (_source == null || _source.ServerID != _chainData.sourceId)
|
||
_source = ObjManager.Instance.FindObjCharacterInScene(_chainData.sourceId);
|
||
if (_target == null || _target.ServerID != _chainData.TargetId)
|
||
_target = ObjManager.Instance.FindObjCharacterInScene(_chainData.TargetId);
|
||
// 需要动态初始化子弹的情况
|
||
if (_bulletSimulator == null && _source != null && _target != null)
|
||
{
|
||
var sourcePoint = GetChainPoint(_source, data.ParentName);
|
||
var targetPoint = GetChainPoint(_target, EffectLogic.centerName);
|
||
_bulletSimulator = new BulletSimulator(sourcePoint,
|
||
targetPoint,
|
||
_chainData.speed > 0f ? Vector3.Distance(sourcePoint, targetPoint) / _chainData.speed : 0f);
|
||
}
|
||
|
||
if (_bulletSimulator != null)
|
||
{
|
||
// 如果目标存在,刷新目标位置
|
||
if (_target != null)
|
||
_bulletSimulator.targetPoint = GetChainPoint(_target, EffectLogic.centerName);
|
||
_bulletSimulator.UpdateBullet();
|
||
if (_source != null)
|
||
{
|
||
var sourcePoint = GetChainPoint(_source, data.ParentName);
|
||
_controller.sourcePoint = sourcePoint;
|
||
}
|
||
|
||
_controller.targetPoint = _bulletSimulator.logicPoint;
|
||
_controller.TryUpdateLink();
|
||
}
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
public void ResetParameters(ChainCustomData chainData)
|
||
{
|
||
// 检查,如果Src或者Target有改变,就重置锁链
|
||
// 否则仅仅修改参数
|
||
if (chainData.sourceId != _chainData.sourceId
|
||
|| chainData.TargetId != _chainData.TargetId)
|
||
{
|
||
_source = null;
|
||
_target = null;
|
||
_bulletSimulator = null;
|
||
ResetEndTime();
|
||
}
|
||
|
||
_chainData = chainData;
|
||
Update();
|
||
}
|
||
|
||
private void ResetEndTime()
|
||
{
|
||
_endTime = _chainData.duration > 0 ? Time.time + _chainData.duration : float.PositiveInfinity;
|
||
if (_controller != null)
|
||
_controller.ResetEndTime(_endTime);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 恢复锁链攻击目标
|
||
/// </summary>
|
||
private Vector3 GetChainPoint(Obj_Character target, string bindName)
|
||
{
|
||
if (target.ObjEffectLogic != null)
|
||
return target.ObjEffectLogic.GetBindPointPosition(bindName);
|
||
return target.Position;
|
||
}
|
||
}
|
||
|
||
public class StampEffectReference : BoundEffectReferenceBase
|
||
{
|
||
private readonly MarkSfxController _controller;
|
||
|
||
public StampEffectReference(Transform effect, Tab_Effect data, int initCount, int? effectHandle) : base(effect,
|
||
data, effectHandle)
|
||
{
|
||
_controller = effect.GetComponent<MarkSfxController>();
|
||
if (_controller == null)
|
||
LogModule.ErrorLog(string.Format("特效物体{0}上不存在印记控制器", data.Path));
|
||
else
|
||
_controller.TargetCount = initCount;
|
||
}
|
||
|
||
public void SetCurrentCount(int count)
|
||
{
|
||
_controller.TargetCount = count;
|
||
}
|
||
|
||
public void Detonate()
|
||
{
|
||
_controller.Detonate(true);
|
||
}
|
||
|
||
public override bool Update()
|
||
{
|
||
var result = base.Update();
|
||
if (result)
|
||
result = !_controller.IsDetonated;
|
||
return result;
|
||
}
|
||
}
|
||
|
||
public class WuChangLinkReference : BaseEffectReference
|
||
{
|
||
// 用于在Source或者Target析构后,重新恢复效果
|
||
private readonly WuChangLinkController _controller;
|
||
|
||
public readonly WuChangLinkData linkData;
|
||
private bool _isBreak;
|
||
|
||
public WuChangLinkReference(Transform effect, WuChangLinkData linkData, Tab_Effect data, int? effectHandle) : base(
|
||
data, effect, effectHandle)
|
||
{
|
||
this.linkData = linkData;
|
||
_controller = effect.GetComponent<WuChangLinkController>();
|
||
if (_controller == null)
|
||
LogModule.ErrorLog(string.Format("特效物体{0}上不存在锁链控制器", data.Path));
|
||
else
|
||
_controller.Init(data);
|
||
}
|
||
|
||
public override bool Update()
|
||
{
|
||
var result = base.Update();
|
||
if (result && _controller != null)
|
||
{
|
||
var sourceObj = ObjManager.Instance.FindObjInScene(linkData.sourceId);
|
||
var targetObj = ObjManager.Instance.FindObjInScene(linkData.targetId);
|
||
if (sourceObj != null && targetObj != null)
|
||
{
|
||
var sourcePos = sourceObj.ObjEffectLogic == null
|
||
? sourceObj.Position
|
||
: sourceObj.ObjEffectLogic.GetBindPointPosition(EffectLogic.centerName);
|
||
var targetPos = targetObj.ObjEffectLogic == null
|
||
? targetObj.Position
|
||
: targetObj.ObjEffectLogic.GetBindPointPosition(EffectLogic.centerName);
|
||
_controller.gameObject.SetActive(true);
|
||
result = _controller.SetLinkPosition(sourcePos, targetPos, _isBreak);
|
||
}
|
||
else
|
||
{
|
||
_controller.gameObject.SetActive(false);
|
||
result = false;
|
||
}
|
||
|
||
// 如果没有断开,则不回收;否则等待断开动画结束
|
||
result = result || !_isBreak;
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
public void BreakLink()
|
||
{
|
||
_isBreak = true;
|
||
}
|
||
}
|
||
|
||
public class WuChangAirportReference : BaseEffectReference
|
||
{
|
||
// 用于在Source或者Target析构后,重新恢复效果
|
||
private readonly WuChangGhostAuraController _controller;
|
||
|
||
public readonly WuChangAirportData airportData;
|
||
private bool _active;
|
||
|
||
public WuChangAirportReference(Transform effect, WuChangAirportData airportData, Tab_Effect data,
|
||
int? effectHandle) : base(
|
||
data, effect, effectHandle)
|
||
{
|
||
this.airportData = airportData;
|
||
_active = true;
|
||
_controller = effect.GetComponent<WuChangGhostAuraController>();
|
||
if (_controller == null)
|
||
LogModule.ErrorLog(string.Format("特效物体{0}上不存在幽灵机场控制器", data.Path));
|
||
else
|
||
_controller.Init(airportData.count, data);
|
||
}
|
||
|
||
public override bool Update()
|
||
{
|
||
var result = base.Update();
|
||
if (result && _controller != null)
|
||
{
|
||
var sourceObj = ObjManager.Instance.FindObjInScene(airportData.sourceId);
|
||
_active = sourceObj != null;
|
||
_controller.gameObject.SetActive(_active);
|
||
if (_active)
|
||
_controller.ownerPos = sourceObj.ObjEffectLogic == null
|
||
? sourceObj.Position
|
||
: sourceObj.ObjEffectLogic.GetBindPointPosition(data.ParentName);
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
public void AttackTarget(Transform target, Vector3 offset, float impactTime)
|
||
{
|
||
if (_active)
|
||
_controller.AttackTarget(target, offset, impactTime);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 陨石轨迹模拟器
|
||
/// </summary>
|
||
// 陨石实际为一个三段式的子弹,三阶段都是匀速移动流程
|
||
public class MeteorEffectReference : BaseEffectReference
|
||
{
|
||
private readonly MeteorEffectController _meteorController;
|
||
|
||
private readonly MeteorEffectData _meteorData;
|
||
private readonly BulletSimulator _simulator;
|
||
private readonly float _startTime;
|
||
private int _groundEffectCount;
|
||
private MeteorState _meteorState;
|
||
|
||
public MeteorEffectReference(Transform effect, MeteorEffectData meteorData, Tab_Effect data,
|
||
int? effectHandle) : base(data, effect, effectHandle)
|
||
{
|
||
_groundEffectCount = 0;
|
||
_meteorData = meteorData;
|
||
_startTime = Time.unscaledTime;
|
||
_meteorState = MeteorState.Wait;
|
||
var z = _meteorData.fallPosition - _meteorData.holdPosition;
|
||
var x = Vector3.Cross(Vector3.up, z);
|
||
var y = Vector3.Cross(z, x);
|
||
if (y == Vector3.zero)
|
||
y = Vector3.up;
|
||
effect.rotation = Quaternion.LookRotation(z, y);
|
||
_meteorController = effect.GetComponent<MeteorEffectController>();
|
||
var collider = effect.GetComponentInChildren<Collider>();
|
||
if (collider != null)
|
||
collider.gameObject.layer = ActiveScene.obstacleLayer;
|
||
if (_meteorController != null)
|
||
Move();
|
||
}
|
||
|
||
public override bool Update()
|
||
{
|
||
// 只通过句柄删除
|
||
var valid = base.Update() && _meteorController != null;
|
||
if (valid)
|
||
Move();
|
||
return valid;
|
||
}
|
||
|
||
private void Move()
|
||
{
|
||
MeteorState nextState;
|
||
if (Time.unscaledTime < _startTime + _meteorData.holdTime)
|
||
nextState = MeteorState.Wait;
|
||
else if (Time.unscaledTime < _startTime + _meteorData.fallTime)
|
||
nextState = MeteorState.Fall;
|
||
else if (Time.unscaledTime < _startTime + _meteorData.rollTime)
|
||
nextState = MeteorState.Roll;
|
||
else
|
||
nextState = MeteorState.End;
|
||
if (_meteorState != nextState)
|
||
{
|
||
_meteorState = nextState;
|
||
//if (_meteorState == MeteorState.End)
|
||
//{
|
||
// var distance = Vector3.Distance(_meteorData.rollPosition, _meteorData.fallPosition);
|
||
// var pitch = distance * _distanceToPitch;
|
||
// effect.rotation = _startQuaternion * Quaternion.Euler(pitch, 0f, 0f);
|
||
//}
|
||
if (_meteorState == MeteorState.Roll)
|
||
_meteorController.SetOnGround(true);
|
||
}
|
||
|
||
switch (_meteorState)
|
||
{
|
||
case MeteorState.Wait:
|
||
{
|
||
effect.position = _meteorData.holdPosition;
|
||
}
|
||
break;
|
||
case MeteorState.Fall:
|
||
{
|
||
var ratio = (Time.unscaledTime - _startTime - _meteorData.holdTime) /
|
||
(_meteorData.fallTime - _meteorData.holdTime);
|
||
effect.position = Vector3.Lerp(_meteorData.holdPosition,
|
||
_meteorData.fallPosition + Vector3.up * _meteorController.radius, ratio);
|
||
}
|
||
break;
|
||
case MeteorState.Roll:
|
||
{
|
||
var ratio = (Time.unscaledTime - _startTime - _meteorData.fallTime) /
|
||
(_meteorData.rollTime - _meteorData.fallTime);
|
||
var effectPos = Vector3.Lerp(_meteorData.fallPosition, _meteorData.rollPosition, ratio);
|
||
var distance = Vector3.Distance(effect.position, _meteorData.fallPosition);
|
||
_meteorController.SetRotationByDistance(distance);
|
||
effect.position = effectPos + _meteorController.radius * Vector3.up;
|
||
|
||
CreateGroundEffect(distance);
|
||
}
|
||
break;
|
||
default:
|
||
effect.position = _meteorData.rollPosition + _meteorController.radius * Vector3.up;
|
||
break;
|
||
}
|
||
}
|
||
|
||
private void CreateGroundEffect(float distance)
|
||
{
|
||
if (distance > _groundEffectCount)
|
||
{
|
||
// 制造第一个地面特效
|
||
var groundEffectId = data.GetParamValuebyIndex(1);
|
||
if (groundEffectId >= 0)
|
||
{
|
||
var time = Time.unscaledDeltaTime - _startTime - _meteorData.fallTime;
|
||
var groundData = CommonUtility.TryGetTable(groundEffectId, a => TableManager.GetEffectByID(a, 0));
|
||
if (groundData != null && groundData.Duration > time)
|
||
IndependentEffectManager.Instance.ShowEffect(groundEffectId, effect.gameObject.layer, GameDefine_Globe.OBJ_TYPE.OBJ,
|
||
effect.position - _meteorController.radius * Vector3.up,
|
||
Quaternion.identity, groundData.Duration - time);
|
||
}
|
||
|
||
_groundEffectCount = Mathf.FloorToInt(distance) + 1;
|
||
}
|
||
}
|
||
|
||
private enum MeteorState
|
||
{
|
||
Wait, // 下落前
|
||
Fall, // 下落中
|
||
Roll, // 落地后
|
||
End // 停止滚动后
|
||
}
|
||
}
|
||
|
||
// 背后神像效果
|
||
public class AvatarEffectReference : CommonEffectReference
|
||
{
|
||
private readonly AnimationLogic _effectAnim;
|
||
private AnimationLogic _characterAnim;
|
||
|
||
public AvatarEffectReference(Transform effect, Tab_Effect data, int? effectHandle, float endTime) : base(effect,
|
||
data, effectHandle, endTime)
|
||
{
|
||
_effectAnim = effect.gameObject.EnsureComponent<AnimationLogic>();
|
||
_effectAnim.InitAnimLogicData(effect.GetComponentInChildren<Animator>(), null);
|
||
_effectAnim.enabled = true;
|
||
}
|
||
|
||
public void Init(Obj_Character objCharacter)
|
||
{
|
||
// 注:Obj本身必然会在特效执行前执行事件绑定,因此特效监听到的时机必然在Obj执行过模型初始化
|
||
objCharacter.ModelNode.onModelCreate += OnCharacterModelCreate;
|
||
objCharacter.ModelNode.onModelDestroy += OnCharacterModelRemove;
|
||
if (objCharacter.ModelNode.model != null)
|
||
OnCharacterModelCreate(objCharacter.ModelNode.model, null);
|
||
effect.SetParent(objCharacter.ObjTransform);
|
||
effect.localPosition = new Vector3(data.OffsetX, data.OffsetY, data.OffsetZ);
|
||
effect.localScale = Vector3.one * (data.Scale > 0 ? data.Scale : 1f);
|
||
effect.localRotation = Quaternion.identity;
|
||
}
|
||
|
||
private void OnCharacterModelCreate(ObjPartRoot partRoot, object args)
|
||
{
|
||
_characterAnim = partRoot.GetComponent<AnimationLogic>();
|
||
if (_characterAnim != null)
|
||
_characterAnim.onPlayAnim += OnCharacterPlayAnim;
|
||
}
|
||
|
||
private void OnCharacterModelRemove(ObjPartRoot partRoot)
|
||
{
|
||
if (_characterAnim != null)
|
||
{
|
||
_characterAnim.onPlayAnim -= OnCharacterPlayAnim;
|
||
_characterAnim = null;
|
||
}
|
||
}
|
||
|
||
public override void EndEffect()
|
||
{
|
||
base.EndEffect();
|
||
if (_characterAnim != null)
|
||
_characterAnim.onPlayAnim -= OnCharacterPlayAnim;
|
||
}
|
||
|
||
private void OnCharacterPlayAnim(Tab_Animation animData, string transitionName)
|
||
{
|
||
if (_effectAnim != null)
|
||
_effectAnim.Play(animData);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 模拟子弹运动轨迹的模拟器
|
||
/// </summary>
|
||
public class BulletSimulator
|
||
{
|
||
// 子弹预期命中目标时间
|
||
public float duration;
|
||
|
||
// 子弹在上一帧的行走时间记录
|
||
public float lastRatio;
|
||
|
||
// 子弹逻辑位置记录 - 抛物线子弹逻辑位置和显示位置不一致
|
||
public Vector3 logicPoint;
|
||
|
||
// 子弹飞行开始时间
|
||
public float startTime;
|
||
|
||
// 子弹目标位置
|
||
public Vector3 targetPoint;
|
||
|
||
public BulletSimulator()
|
||
{
|
||
}
|
||
|
||
public BulletSimulator(Vector3 startPoint, Vector3 targetPoint, float duration, float startTime = -1f)
|
||
{
|
||
this.startTime = startTime < 0f ? Time.time : startTime;
|
||
this.duration = duration;
|
||
logicPoint = startPoint;
|
||
this.targetPoint = targetPoint;
|
||
lastRatio = 0f;
|
||
}
|
||
|
||
public Vector3 UpdateBullet()
|
||
{
|
||
// 计算子弹飞行时间百分比
|
||
var ratio = duration > 0f ? (Time.time - startTime) / duration : 1f;
|
||
var lastPoint = logicPoint;
|
||
var direction = Vector3.zero;
|
||
if (ratio >= 1f)
|
||
{
|
||
logicPoint = targetPoint;
|
||
if (lastRatio < 1f)
|
||
direction = logicPoint - lastPoint;
|
||
}
|
||
else
|
||
{
|
||
var deltaRatio = (ratio - lastRatio) / (1f - lastRatio);
|
||
logicPoint = Vector3.Lerp(logicPoint, targetPoint, deltaRatio);
|
||
direction = logicPoint - lastPoint;
|
||
}
|
||
|
||
lastRatio = ratio;
|
||
return direction;
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
/// <summary>
|
||
/// 特效逻辑类型
|
||
/// </summary>
|
||
public enum CommonEffectType
|
||
{
|
||
Bound, // 绑定在节点上面的类型
|
||
Position, // 按照位置特效播放的特效类型
|
||
Bullet, // 子弹类型
|
||
Chain, // 闪电链类型特效
|
||
Stamp, // 印记类型特效
|
||
WuChangLink, // 无常锁链特效
|
||
WuChangAirport, // 无常幽灵机场特效
|
||
Meteor, // 陨石类型特效
|
||
Avatar, // 背后神象效果
|
||
BulletToPos // 对位置攻击的子弹
|
||
}
|
||
|
||
/// <summary>
|
||
/// 特效旋转策略类型
|
||
/// </summary>
|
||
public static class EffectRotationType
|
||
{
|
||
public const int noRotation = 0;
|
||
public const int faceCamera = 1;
|
||
public const int byEffectType = 2;
|
||
} |