VfxComponent.cs 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. using System;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using XGame.Framework;
  5. using XGame.Framework.Asyncs;
  6. using XGame.Framework.Components;
  7. using XGame.Framework.Interfaces;
  8. using XGame.Framework.Map;
  9. using XGame.Framework.Time;
  10. namespace FL.Battle.Components
  11. {
  12. /// <summary>
  13. /// 特效播放上下文
  14. /// </summary>
  15. public interface IVfxContext
  16. {
  17. IMapAssetModule Asset { get; }
  18. ITimeModule Time { get; }
  19. /// <summary>
  20. /// 世界节点,不是则返回空
  21. /// 只有世界节点才能播放VfxFollowType.World的特效
  22. /// 每个地图只能有一个世界节点
  23. /// </summary>
  24. Transform WorldTr { get; }
  25. Transform CenterTr { get; }
  26. /// <summary>
  27. /// 施法点,没有则返回空
  28. /// </summary>
  29. Transform CastTr { get; }
  30. }
  31. /// <summary>
  32. /// 特效播放组件
  33. /// TODO 世界跟随类型需要将特效转到地图播放,以防止角色死亡时特效播放被打断
  34. /// </summary>
  35. public class VfxComponent : Component<IVfxContext>
  36. {
  37. class VfxPlayingDto : IReference
  38. {
  39. public IAsync loadAsync;
  40. public ITimer timer;
  41. public GameObject vfxGo;
  42. void IReference.Clear()
  43. {
  44. loadAsync = null;
  45. timer = null;
  46. vfxGo = null;
  47. }
  48. }
  49. private Dictionary<VfxPlayingDto, VfxArgs> _playingVfxMap = new Dictionary<VfxPlayingDto, VfxArgs>();
  50. protected override void OnEnable(object intent)
  51. {
  52. if (Context.WorldTr != null)
  53. {
  54. EventSingle.Instance.AddListener(EventDefine.VfxComponentWorldVfx, OnWorldVfx);
  55. }
  56. }
  57. protected override void OnDisable()
  58. {
  59. if (Context.WorldTr != null)
  60. {
  61. EventSingle.Instance.RemoveListener(EventDefine.VfxComponentWorldVfx, OnWorldVfx);
  62. }
  63. }
  64. protected override void OnDispose()
  65. {
  66. Stop();
  67. }
  68. public void Play(VfxArgs args)
  69. {
  70. Assert.IsTrue(args != null, "特效播放参数不能为空.");
  71. Assert.IsFalse(string.IsNullOrEmpty(args.vfxName), "特效名字不能为空.");
  72. Assert.IsTrue(args.duration > 0, $"特效播放时长必须大于零. Name:{args.vfxName}");
  73. if (args.followType == EVfxFollowType.World && Context.WorldTr == null)
  74. {
  75. EventSingle.Instance.Notify(EventDefine.VfxComponentWorldVfx, args);
  76. return;
  77. }
  78. var dto = AcquireDto(args);
  79. void DelayPlay()
  80. {
  81. var loadAsync = Context.Asset.LoadAsync<GameObject>(args.vfxName);
  82. dto.loadAsync = loadAsync;
  83. loadAsync.On(_ =>
  84. {
  85. dto.loadAsync = null;
  86. var vfxGo = loadAsync.Result;
  87. if (vfxGo == null)
  88. {
  89. RecycleDto(dto);
  90. args.onFinish.SafeInvoke();
  91. RecycleArgs(args);
  92. return;
  93. }
  94. dto.vfxGo = vfxGo;
  95. SetParent(vfxGo.transform, args.followType);
  96. vfxGo.SetActive(true);
  97. args.action?.Start(vfxGo.transform);
  98. dto.timer = Context.Time.AddDelayTimer(args.duration, () =>
  99. {
  100. dto.timer = null;
  101. dto.vfxGo = null;
  102. //回收GameObject
  103. Context.Asset.Recycle(vfxGo);
  104. //回收dto
  105. RecycleDto(dto);
  106. args.onFinish.SafeInvoke();
  107. var next = args.next;
  108. if (next != null)
  109. {
  110. args.next = null;
  111. //播放下一个
  112. Play(next);
  113. }
  114. RecycleArgs(args);
  115. });
  116. });
  117. }
  118. if (args.delay > 0)
  119. {
  120. dto.timer = Context.Time.AddDelayTimer(args.delay, () =>
  121. {
  122. dto.timer = null;
  123. DelayPlay();
  124. });
  125. }
  126. else
  127. {
  128. DelayPlay();
  129. }
  130. }
  131. public void Stop()
  132. {
  133. foreach (var item in _playingVfxMap)
  134. {
  135. RecycleDto(item.Key, false);
  136. RecycleArgs(item.Value);
  137. }
  138. _playingVfxMap.Clear();
  139. }
  140. private void SetParent(Transform vfxTr, EVfxFollowType followType)
  141. {
  142. Transform parent;
  143. switch (followType)
  144. {
  145. case EVfxFollowType.World:
  146. parent = Context.WorldTr;
  147. break;
  148. case EVfxFollowType.CastPoint:
  149. parent = Context.CastTr != null ? Context.CastTr : Context.CenterTr;
  150. break;
  151. default:
  152. parent = Context.CenterTr;
  153. break;
  154. }
  155. vfxTr.SetParent(parent, false);
  156. if (followType != EVfxFollowType.World)
  157. {
  158. vfxTr.localPosition = Vector3.zero;
  159. }
  160. //TODO 技能特效需要缩小尺寸
  161. vfxTr.localScale = Vector3.one * 0.7f;
  162. }
  163. //public VfxArgs AcquireArgs()
  164. //{
  165. // return ObjectPool.Acquire<VfxArgs>();
  166. //}
  167. public void RecycleArgs(VfxArgs args)
  168. {
  169. if (args == null)
  170. return;
  171. if (args.next != null)
  172. {
  173. RecycleArgs(args.next);
  174. }
  175. if (args.action != null)
  176. {
  177. ObjectPool.Recycle(args.action);
  178. args.action = null;
  179. }
  180. ObjectPool.Recycle(args);
  181. }
  182. private VfxPlayingDto AcquireDto(VfxArgs args)
  183. {
  184. var dto = ObjectPool.Acquire<VfxPlayingDto>();
  185. _playingVfxMap.Add(dto, args);
  186. return dto;
  187. }
  188. private void RecycleDto(VfxPlayingDto dto, bool removeMap = true)
  189. {
  190. dto.loadAsync?.RemoveAll();
  191. dto.timer?.Cancel();
  192. if (dto.vfxGo != null)
  193. {
  194. Context.Asset.Recycle(dto.vfxGo);
  195. }
  196. if (removeMap)
  197. {
  198. _playingVfxMap.Remove(dto);
  199. }
  200. ObjectPool.Recycle(dto);
  201. }
  202. #region 事件监听
  203. private void OnWorldVfx(int eventId, object args)
  204. {
  205. var vfxArgs = args as VfxArgs;
  206. Play(vfxArgs);
  207. }
  208. #endregion
  209. }
  210. }