New file |
| | |
| | | using Cysharp.Threading.Tasks; |
| | | using System; |
| | | using System.Collections; |
| | | using System.Collections.Generic; |
| | | using DG.Tweening; |
| | | using Spine; |
| | | using Spine.Unity; |
| | | using UnityEngine; |
| | | |
| | | public class SkeletonIllusionShadow : MonoBehaviour |
| | | { |
| | | // 记录所有残影对象,便于统一销毁 |
| | | private readonly List<GameObject> illusionShadows = new List<GameObject>(); |
| | | private SkeletonAnimation skeletonAnimation; |
| | | |
| | | private bool createSwitch = false; |
| | | |
| | | private int curFrame = 0; |
| | | private int frameInteral = 2; |
| | | |
| | | public void SetSkeletonAnimation(SkeletonAnimation _skeletonAnimation) |
| | | { |
| | | skeletonAnimation = _skeletonAnimation; |
| | | } |
| | | |
| | | public void Show(bool v) |
| | | { |
| | | createSwitch = v; |
| | | curFrame = 0; |
| | | |
| | | if (!v) |
| | | { |
| | | // 清理所有残影对象 |
| | | for (int i = illusionShadows.Count - 1; i >= 0; i--) |
| | | { |
| | | var go = illusionShadows[i]; |
| | | if (go != null) |
| | | { |
| | | DOTween.Kill(go, complete: false); |
| | | if (Application.isPlaying) |
| | | Destroy(go); |
| | | else |
| | | DestroyImmediate(go); |
| | | } |
| | | } |
| | | illusionShadows.Clear(); |
| | | |
| | | } |
| | | } |
| | | |
| | | |
| | | public void Run() |
| | | { |
| | | if (createSwitch) |
| | | { |
| | | // 0.1秒创建一个残像 |
| | | curFrame++; |
| | | if (curFrame >= frameInteral) |
| | | { |
| | | curFrame -= frameInteral; |
| | | CreateIllusionShadow(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private void CreateIllusionShadow() |
| | | { |
| | | var src = skeletonAnimation.transform; |
| | | |
| | | // 保存源的世界变换 |
| | | Vector3 worldPos = src.position; |
| | | Quaternion worldRot = src.rotation; |
| | | Vector3 worldScale = src.lossyScale; |
| | | |
| | | GameObject objTest = new GameObject("IllusionShadow"); |
| | | illusionShadows.Add(objTest); |
| | | |
| | | objTest.transform.SetParent(null, false); |
| | | |
| | | objTest.transform.position = worldPos; |
| | | objTest.transform.rotation = worldRot; |
| | | objTest.transform.localScale = worldScale; // 当 parent == null 时 localScale == worldScale |
| | | |
| | | |
| | | objTest.layer = LayerMask.NameToLayer("UI"); |
| | | SkeletonAnimation sa = SkeletonAnimation.AddToGameObject(objTest, skeletonAnimation.skeletonDataAsset, false); |
| | | TrackEntry trackEntry = skeletonAnimation.AnimationState.GetCurrent(0); |
| | | TrackEntry playedEntry = sa.AnimationState.SetAnimation(0, trackEntry.Animation.Name, trackEntry.Loop); |
| | | playedEntry.TrackTime = trackEntry.TrackTime; |
| | | |
| | | sa.timeScale = 0; |
| | | sa.AnimationState.TimeScale = 0; |
| | | playedEntry.TimeScale = 0; |
| | | |
| | | RendererAdjuster parent = skeletonAnimation.GetComponentInParent<RendererAdjuster>(); |
| | | objTest.AddMissingComponent<RendererAdjuster>().SetSortingOrder(parent.sortingOrder); |
| | | |
| | | sa.skeleton.A = skeletonAnimation.skeleton.A; |
| | | // 使用DoTween做alpha淡出,Tween与objTest绑定,便于统一Kill |
| | | DOTween.To(() => sa.skeleton.A, x => { sa.skeleton.A = x; sa.LateUpdate(); }, 0f, 0.5f) |
| | | .SetTarget(objTest); |
| | | |
| | | // 安全销毁,移除引用 |
| | | _ = DestroyIllusionShadowAfterAsync(objTest, 0.5f); |
| | | |
| | | } |
| | | |
| | | // 用UniTask安全延迟销毁残影对象,多重安全检查 |
| | | private async UniTaskVoid DestroyIllusionShadowAfterAsync(GameObject go, float delay) |
| | | { |
| | | try |
| | | { |
| | | await UniTask.Delay((int)(delay * 1000), cancellationToken: this.GetCancellationTokenOnDestroy()); |
| | | } |
| | | catch (OperationCanceledException) |
| | | { |
| | | // 对象已销毁,无需处理 |
| | | return; |
| | | } |
| | | if (go == null) return; |
| | | DOTween.Kill(go, complete: false); |
| | | illusionShadows.Remove(go); |
| | | if (Application.isPlaying) |
| | | Destroy(go); |
| | | else |
| | | DestroyImmediate(go); |
| | | } |
| | | } |