From 2a0c34fd7ad721b6fd2cec69aa21b11963555478 Mon Sep 17 00:00:00 2001 From: Ding Yuntian <1491671119@qq.com> Date: Wed, 24 Jun 2026 22:43:08 +0800 Subject: [PATCH] =?UTF-8?q?refactor(actor-kit):=20Actor=20=E4=BD=93?= =?UTF-8?q?=E7=B3=BB=E8=BF=81=E7=A7=BB=E4=B8=BA=20DTO=20=E5=BF=AB=E7=85=A7?= =?UTF-8?q?=E4=B8=8E=E5=BC=82=E6=AD=A5=E6=81=A2=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Framework/OtherKit/BubbleSlotKit.cs | 4 +- .../Providers/ActorSnapshotProvider.cs | 11 +- .../SceneManagement/ActorKit/AbsActor.cs | 156 ++++++++++-------- .../SceneManagement/ActorKit/ActorAnima.cs | 88 +++++++--- .../SceneManagement/ActorKit/ActorAnimaEx.cs | 130 +++++++-------- .../SceneManagement/ActorKit/ActorManager.cs | 63 ++----- .../SceneManagement/ActorKit/SpriteActor.cs | 46 ++++-- 7 files changed, 279 insertions(+), 219 deletions(-) diff --git a/Assets/Scripts/Framework/OtherKit/BubbleSlotKit.cs b/Assets/Scripts/Framework/OtherKit/BubbleSlotKit.cs index b2ec388e5..33e43f8cc 100644 --- a/Assets/Scripts/Framework/OtherKit/BubbleSlotKit.cs +++ b/Assets/Scripts/Framework/OtherKit/BubbleSlotKit.cs @@ -1,10 +1,10 @@ -using System.Collections.Generic; +using System.Collections.Generic; using AibisDream.Kit; using UnityEngine; namespace AibisDream.Framework { - public class BubbleSlotKit : Singleton + public class BubbleSlotKit : SingletonBase { private readonly Dictionary _slotDict = new(); diff --git a/Assets/Scripts/SaveSystem/Providers/ActorSnapshotProvider.cs b/Assets/Scripts/SaveSystem/Providers/ActorSnapshotProvider.cs index 40ab832ce..3b20e124d 100644 --- a/Assets/Scripts/SaveSystem/Providers/ActorSnapshotProvider.cs +++ b/Assets/Scripts/SaveSystem/Providers/ActorSnapshotProvider.cs @@ -1,3 +1,4 @@ +using System.Collections; using UnityEngine; namespace AibisDream.SaveSystem @@ -6,7 +7,7 @@ namespace AibisDream.SaveSystem /// sections["actor"]:场景中各角色显隐、槽位、动画状态、透明度等(ActorManager)。 /// RestoreOrder=40,依赖 scene 与 env 已就绪。 /// - public class ActorSnapshotProvider : ISyncSnapshotProvider + public class ActorSnapshotProvider : IAsyncSnapshotProvider { public string SaveId => SnapshotProviderIds.Actor; public int RestoreOrder => 40; @@ -22,21 +23,21 @@ namespace AibisDream.SaveSystem return ActorManager.Instance.CaptureSnapshot(); } - public void Restore(object dto, SnapshotRestoreContext context) + public IEnumerator RestoreAsync(object dto, SnapshotRestoreContext context) { if (dto is not ActorSnapshotDto actors) { context.Warn("[ActorSnapshotProvider] DTO 类型不匹配,跳过还原。"); - return; + yield break; } if (ActorManager.Instance == null) { context.Warn("[ActorSnapshotProvider] ActorManager 未初始化,跳过还原。"); - return; + yield break; } - ActorManager.Instance.RestoreSnapshot(actors); + yield return ActorManager.Instance.RestoreSnapshotAsync(actors); } } } diff --git a/Assets/Scripts/SceneManagement/ActorKit/AbsActor.cs b/Assets/Scripts/SceneManagement/ActorKit/AbsActor.cs index d38d3f1b2..7aa83a843 100644 --- a/Assets/Scripts/SceneManagement/ActorKit/AbsActor.cs +++ b/Assets/Scripts/SceneManagement/ActorKit/AbsActor.cs @@ -1,11 +1,10 @@ using System; using System.Collections; using AibisDream.Framework; +using AibisDream.SaveSystem; using AibisDream.Utility; using DG.Tweening; -using Newtonsoft.Json; using UnityEngine; -using UnityEngine.Events; using Object = UnityEngine.Object; namespace AibisDream @@ -16,74 +15,109 @@ namespace AibisDream /// public abstract class AbsActor : MonoBehaviour { - public ActorData actorData; - public string ActorName => actorData.actorName; - public string SlotName => actorData.slotName; + protected string _actorName; + protected string _slotName; + protected ActorType _actorType; + protected float _alpha; protected SpriteRenderer spriteRenderer; - public void Init(string actorName, ActorSlot slot) + public string ActorName => _actorName; + public string SlotName => _slotName; + public ActorType ActorType => _actorType; + + public void Init(string actorName, ActorSlot slot, ActorType actorType) { - actorData = new ActorData + _actorName = actorName; + _slotName = slot.slotName; + _actorType = actorType; + _alpha = 0; + + InitInternal(); + SetSlot(slot); + } + + public void InitFromEntry(ActorEntrySnapshotDto entry, ActorSlot slot) + { + _actorName = entry.actorName; + _slotName = entry.slotName; + _alpha = entry.alpha; + + if (!Enum.TryParse(entry.actorType, out _actorType)) { - actorName = actorName, - slotName = slot.slotName, - alpha = 0 - }; - - Init(); + _actorType = ActorType.Anima; + } + + InitInternal(); SetSlot(slot); } - public void Init(ActorData data, ActorSlot slot) - { - actorData = data; - Init(); - SetSlot(slot); - } - - protected virtual void Init() + protected virtual void InitInternal() { gameObject.name = ActorName; spriteRenderer = GetComponent(); spriteRenderer.SetAlpha(0); } + public abstract ActorEntrySnapshotDto CaptureEntry(); + + public abstract IEnumerator ApplyEntryAsync(ActorEntrySnapshotDto entry); + + protected void FillBaseEntry(ActorEntrySnapshotDto entry) + { + entry.actorName = _actorName; + entry.slotName = _slotName; + entry.actorType = _actorType.ToString(); + entry.alpha = _alpha; + } + + protected void ApplyAlphaFromEntry(ActorEntrySnapshotDto entry) + { + if (entry.alpha <= 0.01f) + { + Hide(); + } + else + { + SetAlpha(Mathf.RoundToInt(entry.alpha)); + } + } + public void Show() { - actorData.alpha = 1; + _alpha = 1; spriteRenderer.SetAlpha(1); } public void Hide() { - actorData.alpha = 0; + _alpha = 0; spriteRenderer.SetAlpha(0); } - + public void SetSlot(ActorSlot slot) { transform.position = slot.Position; spriteRenderer.sortingOrder = slot.sortingOrder; spriteRenderer.sortingLayerID = slot.sortingLayer; - - actorData.slotName = slot.slotName; + + _slotName = slot.slotName; } - + public IEnumerator FadeInAsync(float duration) { - actorData.alpha = 1; + _alpha = 1; yield return spriteRenderer.DOFade(1, duration).WaitForCompletion(); } public IEnumerator FadeOutAsync(float duration) { - actorData.alpha = 0; + _alpha = 0; yield return spriteRenderer.DOFade(0, duration).WaitForCompletion(); } public void SetAlpha(int alpha) { - actorData.alpha = alpha; + _alpha = alpha; spriteRenderer.SetAlpha(alpha); } @@ -96,11 +130,10 @@ namespace AibisDream } public virtual void SetTalkingState(bool isTalking) {} - + public virtual void SetFaceState(string faceName) {} } - // 工厂类 public class ActorFactory { private readonly Transform _actorRoot; @@ -115,41 +148,43 @@ namespace AibisDream _spriteActorPrefab = ResourceKit.LoadAssetSync(ConstRef.SpriteActorPrefabName); _actorAnimaExPrefab = ResourceKit.LoadAssetSync(ConstRef.ActorAnimaExPrefabName); } - + public AbsActor CreateActor(string actorName, ActorSlot slot, ActorType actorType) { - var actor = actorType switch - { - ActorType.Sprite => Object.Instantiate(_spriteActorPrefab, _actorRoot).GetComponent(), - ActorType.Anima => Object.Instantiate(_actorAnimaPrefab, _actorRoot).GetComponent(), - ActorType.AnimaEx => Object.Instantiate(_actorAnimaExPrefab, _actorRoot).GetComponent(), - _ => null - }; - + var actor = InstantiateByType(actorType); if (actor) { - actor.Init(actorName, slot); + actor.Init(actorName, slot, actorType); } - + return actor; } - - public AbsActor CreateActor(ActorData actorData, ActorSlot slot) + + public AbsActor CreateFromEntry(ActorEntrySnapshotDto entry, ActorSlot slot) { - var actor = actorData.actorType switch + if (!Enum.TryParse(entry.actorType, out var actorType)) + { + actorType = ActorType.Anima; + } + + var actor = InstantiateByType(actorType); + if (actor) + { + actor.InitFromEntry(entry, slot); + } + + return actor; + } + + private AbsActor InstantiateByType(ActorType actorType) + { + return actorType switch { ActorType.Sprite => Object.Instantiate(_spriteActorPrefab, _actorRoot).GetComponent(), ActorType.Anima => Object.Instantiate(_actorAnimaPrefab, _actorRoot).GetComponent(), ActorType.AnimaEx => Object.Instantiate(_actorAnimaExPrefab, _actorRoot).GetComponent(), _ => null }; - - if (actor) - { - actor.Init(actorData, slot); - } - - return actor; } } @@ -159,17 +194,4 @@ namespace AibisDream Anima, AnimaEx } - - [Serializable] - public class ActorData - { - [JsonIgnore] public UnityEvent onSave = new(); - - public string actorName; - public string slotName; - public ActorType actorType; - public string stateName; - public float alpha; - public float faceParam; - } -} \ No newline at end of file +} diff --git a/Assets/Scripts/SceneManagement/ActorKit/ActorAnima.cs b/Assets/Scripts/SceneManagement/ActorKit/ActorAnima.cs index a4b14c1d7..a8dcb9e25 100644 --- a/Assets/Scripts/SceneManagement/ActorKit/ActorAnima.cs +++ b/Assets/Scripts/SceneManagement/ActorKit/ActorAnima.cs @@ -1,6 +1,7 @@ using System.Collections; using System.Collections.Generic; using AibisDream.Framework; +using AibisDream.SaveSystem; using UnityEngine; namespace AibisDream @@ -10,34 +11,23 @@ namespace AibisDream private const string HashPrefix = "#hash."; private Animator _animator; - private Dictionary _stateNameDict; + private Dictionary _hashToStateName; - protected override void Init() + protected override void InitInternal() { _animator = GetComponent(); - // 设置基本信息 - base.Init(); - // 获取Controller - actorData.actorType = ActorType.Anima; + base.InitInternal(); + _actorType = ActorType.Anima; + var controller = ResourceKit.LoadAssetSync($"Animation/{ActorName}"); _animator.runtimeAnimatorController = controller; - - // 保存事件 - actorData.onSave.AddListener(() => - { - actorData.stateName = - HashPrefix + _animator.GetCurrentAnimatorStateInfo(0).shortNameHash; - }); - - // 加载初始状态 - if (!string.IsNullOrWhiteSpace(actorData.stateName)) - { - ChangeState(actorData.stateName); - } + _hashToStateName = AnimatorKit.BuildStateHashToNameMap(controller); } public override void ChangeState(string animaName) { + if (string.IsNullOrEmpty(animaName)) return; + if (animaName.StartsWith(HashPrefix)) { var hash = int.Parse(animaName[HashPrefix.Length..]); @@ -51,12 +41,23 @@ namespace AibisDream public override IEnumerator ChangeStateAsync(string animaName) { + if (string.IsNullOrEmpty(animaName)) + { + yield break; + } + + if (animaName.StartsWith(HashPrefix)) + { + var hash = int.Parse(animaName[HashPrefix.Length..]); + _animator.Play(hash); + yield return null; + yield break; + } + _animator.Play(animaName); - // 等待一帧确保动画状态切换完成 yield return null; - // 等待直到动画状态名称匹配 while (!_animator.GetCurrentAnimatorStateInfo(0).IsName(animaName)) { yield return null; @@ -66,9 +67,48 @@ namespace AibisDream yield return new WaitForSeconds(info.length); } - private void OnDestroy() + public override ActorEntrySnapshotDto CaptureEntry() { - actorData.onSave.RemoveAllListeners(); + var entry = new ActorEntrySnapshotDto(); + FillBaseEntry(entry); + + var info = _animator.GetCurrentAnimatorStateInfo(0); + entry.stateNormalizedTime = info.normalizedTime; + + if (_hashToStateName != null + && _hashToStateName.TryGetValue(info.shortNameHash, out var name)) + { + entry.stateName = name; + } + else + { + entry.stateName = HashPrefix + info.shortNameHash; + } + + return entry; + } + + public override IEnumerator ApplyEntryAsync(ActorEntrySnapshotDto entry) + { + ApplyAlphaFromEntry(entry); + + if (string.IsNullOrEmpty(entry.stateName)) + { + yield return null; + yield break; + } + + if (entry.stateName.StartsWith(HashPrefix)) + { + var hash = int.Parse(entry.stateName[HashPrefix.Length..]); + _animator.Play(hash, 0, entry.stateNormalizedTime); + } + else + { + _animator.Play(entry.stateName, 0, entry.stateNormalizedTime); + } + + yield return null; } } -} \ No newline at end of file +} diff --git a/Assets/Scripts/SceneManagement/ActorKit/ActorAnimaEx.cs b/Assets/Scripts/SceneManagement/ActorKit/ActorAnimaEx.cs index 1cb8e54ac..1a68db5f9 100644 --- a/Assets/Scripts/SceneManagement/ActorKit/ActorAnimaEx.cs +++ b/Assets/Scripts/SceneManagement/ActorKit/ActorAnimaEx.cs @@ -1,66 +1,49 @@ -using AibisDream.Framework; using System.Collections; using System.Collections.Generic; +using AibisDream.Framework; +using AibisDream.SaveSystem; using UnityEngine; namespace AibisDream { public class ActorAnimaEx : AbsActor { + private const string IsTalkingParam = "Is Talking"; + private Animator _animator; private string _currentStateName; + private string _currentFaceName; private Dictionary _blendTreeDict; - protected override void Init() + protected override void InitInternal() { _animator = GetComponent(); - // 设置基本信息 - base.Init(); - actorData.actorType = ActorType.AnimaEx; + base.InitInternal(); + _actorType = ActorType.AnimaEx; + var controller = ResourceKit.LoadAssetSync($"Animation/{ActorName}"); _animator.runtimeAnimatorController = controller; ParseBlendTreeInfos(); - - // 保存事件 - actorData.onSave.AddListener(() => - { - actorData.stateName = _currentStateName; - actorData.faceParam = _animator.GetFloat("Face Param"); - }); - - // 加载初始状态 - if (!string.IsNullOrWhiteSpace(actorData.stateName)) - { - ChangeState(actorData.stateName); - } - if (actorData.faceParam >= 0) - { - _animator.SetFloat("Face Param", actorData.faceParam); - } } - + private void ParseBlendTreeInfos() { _blendTreeDict = new Dictionary(); - // 解析混合树 var configText = ResourceKit.LoadAssetSync($"AnimationConfig/{ActorName}"); - if (configText != null) - { - var blendTreeConfig = AnimatorKit.ParseBlendTreeConfigFromJson(configText.text); - foreach (var info in blendTreeConfig.blendTrees) - { - _blendTreeDict[info.name] = info; - } + if (configText == null) return; - _currentStateName = blendTreeConfig.defaultStateName; + var blendTreeConfig = AnimatorKit.ParseBlendTreeConfigFromJson(configText.text); + if (blendTreeConfig?.blendTrees == null) return; + + foreach (var info in blendTreeConfig.blendTrees) + { + _blendTreeDict[info.name] = info; } + + _currentStateName = blendTreeConfig.defaultStateName; } - /// - /// 切换站姿 - /// - /// public override void ChangeState(string stateName) { StartCoroutine(ChangeStateAsync(stateName)); @@ -68,58 +51,75 @@ namespace AibisDream public override IEnumerator ChangeStateAsync(string stateName) { - // 记录当前状态 _currentStateName = stateName; - // 切换站姿 _animator.SetTrigger(stateName); - // 等待动作切换完成 while (!_animator.GetCurrentAnimatorStateInfo(0).IsName(stateName)) { yield return null; } - // var info = _animator.GetCurrentAnimatorStateInfo(0); - // yield return new WaitForSeconds(info.length); - _animator.ResetTrigger(stateName); - - Debug.Log($"当前状态{_currentStateName}"); } public override void SetTalkingState(bool isTalking) { - _animator.SetFloat("Is Talking", isTalking ? 1 : 0); + _animator.SetFloat(IsTalkingParam, isTalking ? 1 : 0); } public override void SetFaceState(string faceName) { - // 先读取当前混合树信息 - if (_blendTreeDict.TryGetValue(_currentStateName, out var blendTreeInfo)) + _currentFaceName = faceName; + + if (!_blendTreeDict.TryGetValue(_currentStateName, out var blendTreeInfo)) { - Debug.Log($"当前状态{_currentStateName}的混合树信息存在"); - if (blendTreeInfo.type == "1D") + Debug.LogWarning($"当前状态{_currentStateName}的混合树信息不存在"); + return; + } + + if (blendTreeInfo.type == "1D") + { + var clipInfo = blendTreeInfo.GetClipInfo(faceName); + if (clipInfo != null) { - var clipInfo = blendTreeInfo.GetClipInfo(faceName); - if (clipInfo != null) - { - _animator.SetFloat(blendTreeInfo.blendParameter, clipInfo.threshold); - } - } - else - { - var clipInfo = blendTreeInfo.GetClipInfo(faceName); - if (clipInfo != null) - { - _animator.SetFloat(blendTreeInfo.blendParameter, clipInfo.position.x); - // 不设置Y轴参数 - } + _animator.SetFloat(blendTreeInfo.blendParameter, clipInfo.threshold); } } else { - Debug.LogWarning($"当前状态{_currentStateName}的混合树信息不存在"); + var clipInfo = blendTreeInfo.GetClipInfo(faceName); + if (clipInfo != null) + { + _animator.SetFloat(blendTreeInfo.blendParameter, clipInfo.position.x); + } } } + + public override ActorEntrySnapshotDto CaptureEntry() + { + var entry = new ActorEntrySnapshotDto(); + FillBaseEntry(entry); + entry.stateName = _currentStateName; + entry.faceName = _currentFaceName; + entry.isTalking = _animator.GetFloat(IsTalkingParam) > 0.5f; + return entry; + } + + public override IEnumerator ApplyEntryAsync(ActorEntrySnapshotDto entry) + { + ApplyAlphaFromEntry(entry); + + if (!string.IsNullOrEmpty(entry.stateName)) + { + yield return ChangeStateAsync(entry.stateName); + } + + if (!string.IsNullOrEmpty(entry.faceName)) + { + SetFaceState(entry.faceName); + } + + SetTalkingState(entry.isTalking); + } } -} \ No newline at end of file +} diff --git a/Assets/Scripts/SceneManagement/ActorKit/ActorManager.cs b/Assets/Scripts/SceneManagement/ActorKit/ActorManager.cs index 805264f0d..056eae8c7 100644 --- a/Assets/Scripts/SceneManagement/ActorKit/ActorManager.cs +++ b/Assets/Scripts/SceneManagement/ActorKit/ActorManager.cs @@ -71,74 +71,45 @@ namespace AibisDream var dto = new ActorSnapshotDto(); foreach (var actor in _actorDict.Values) { - var data = actor.actorData; - dto.actors.Add(new ActorEntrySnapshotDto - { - actorName = data.actorName, - slotName = data.slotName, - actorType = data.actorType.ToString(), - stateName = data.stateName, - alpha = data.alpha, - faceParam = data.faceParam - }); + dto.actors.Add(actor.CaptureEntry()); } return dto; } - public void RestoreSnapshot(ActorSnapshotDto dto) + public IEnumerator RestoreSnapshotAsync(ActorSnapshotDto dto) { - if (dto?.actors == null) return; - DestroyEditorTemp(); + if (dto?.actors == null) + { + yield break; + } + foreach (var entry in dto.actors) { - if (!Enum.TryParse(entry.actorType, out var actorType)) + if (!_slotDict.TryGetValue(entry.slotName, out var slot)) { - actorType = ActorType.Anima; - } - - var actorData = new ActorData - { - actorName = entry.actorName, - slotName = entry.slotName, - actorType = actorType, - stateName = entry.stateName, - alpha = entry.alpha, - faceParam = entry.faceParam - }; - - if (!_slotDict.TryGetValue(actorData.slotName, out var slot)) - { - Debug.LogWarning($"slot {actorData.slotName} 不存在"); + Debug.LogWarning($"slot {entry.slotName} 不存在"); continue; } - var actor = _actorFactory.CreateActor(actorData, slot); + var actor = _actorFactory.CreateFromEntry(entry, slot); if (actor == null) continue; - _actorDict.Add(actorData.actorName, actor); - ApplyActorVisualState(actor, actorData); + UpsertActor(entry.actorName, actor); + yield return actor.ApplyEntryAsync(entry); } - } - private static void ApplyActorVisualState(AbsActor actor, ActorData data) + private void UpsertActor(string actorName, AbsActor actor) { - if (data.alpha <= 0.01f) + if (_actorDict.TryGetValue(actorName, out var existing) && existing != null) { - actor.Hide(); - } - else - { - actor.SetAlpha(Mathf.RoundToInt(data.alpha)); + Destroy(existing.gameObject); } - if (!string.IsNullOrEmpty(data.stateName)) - { - actor.ChangeState(data.stateName); - } + _actorDict[actorName] = actor; } #if UNITY_EDITOR @@ -165,7 +136,7 @@ namespace AibisDream { var slot = _slotDict[slotName]; var actor = _actorFactory.CreateActor(actorName, slot, actorType); - _actorDict.Add(actorName, actor); + UpsertActor(actorName, actor); return actor; } diff --git a/Assets/Scripts/SceneManagement/ActorKit/SpriteActor.cs b/Assets/Scripts/SceneManagement/ActorKit/SpriteActor.cs index 171118304..9b1976819 100644 --- a/Assets/Scripts/SceneManagement/ActorKit/SpriteActor.cs +++ b/Assets/Scripts/SceneManagement/ActorKit/SpriteActor.cs @@ -1,23 +1,49 @@ -using AibisDream.Framework; -using AibisDream.Utility; +using System.Collections; +using AibisDream.Framework; +using AibisDream.SaveSystem; using UnityEngine; namespace AibisDream { public class SpriteActor : AbsActor { - protected override void Init() + private string _stateName = "Idle"; + + protected override void InitInternal() { - base.Init(); - actorData.actorType = ActorType.Sprite; - // 赋值 - ChangeState(string.IsNullOrWhiteSpace(actorData.stateName) ? "Idle" : actorData.stateName); + base.InitInternal(); + _actorType = ActorType.Sprite; + ChangeState("Idle"); } public override void ChangeState(string stateName) { - spriteRenderer.sprite = ResourceKit.LoadAssetSync($"Sprite/{ActorName}[{stateName}]"); - actorData.stateName = stateName; + _stateName = string.IsNullOrWhiteSpace(stateName) ? "Idle" : stateName; + spriteRenderer.sprite = ResourceKit.LoadAssetSync($"Sprite/{ActorName}[{_stateName}]"); + } + + public override ActorEntrySnapshotDto CaptureEntry() + { + var entry = new ActorEntrySnapshotDto(); + FillBaseEntry(entry); + entry.stateName = _stateName; + return entry; + } + + public override IEnumerator ApplyEntryAsync(ActorEntrySnapshotDto entry) + { + ApplyAlphaFromEntry(entry); + + if (!string.IsNullOrEmpty(entry.stateName)) + { + ChangeState(entry.stateName); + } + else + { + ChangeState("Idle"); + } + + yield break; } } -} \ No newline at end of file +}