using System; using System.Collections; using System.Collections.Generic; using System.Linq; using AibisDream.Kit; using AibisDream.SaveSystem; using UnityEngine; using Yarn.Unity; namespace AibisDream { public class ActorManager : Singleton { #region 索引 private Transform _actorRoot; private Dictionary _slotDict; private Dictionary _actorDict; private ActorFactory _actorFactory; #endregion public override void OnSingletonInit() { InitRefs(); DestroyEditorTemp(); InitDefaultActor(); } public void DestroyEditorTemp() { foreach (Transform child in _actorRoot) { #if UNITY_EDITOR if (!Application.isPlaying) { DestroyImmediate(child.gameObject); continue; } #endif Destroy(child.gameObject); } _actorDict.Clear(); } private void InitDefaultActor() { foreach (var slot in _slotDict.Values.Where(slot => slot.hasDefaultActor)) { if (slot.defaultActorType == ActorType.FrameAnimation) { StartCoroutine(InitActorAsync( slot.defaultActorName, slot.slotName, slot.defaultActorType)); } else { InitActor(slot.defaultActorName, slot.slotName, slot.defaultActorType); } } } private void InitRefs() { _actorRoot = transform.Find("ActorRoot"); _slotDict = new Dictionary(); foreach (var child in transform.Find("Slots").GetComponentsInChildren()) { _slotDict[child.slotName] = child; } _actorDict = new Dictionary(); _actorFactory = new ActorFactory(_actorRoot); } public ActorSnapshotDto CaptureSnapshot() { var dto = new ActorSnapshotDto(); foreach (var actor in _actorDict.Values) { dto.actors.Add(actor.CaptureEntry()); } return dto; } public IEnumerator RestoreSnapshotAsync(ActorSnapshotDto dto) { DestroyEditorTemp(); if (dto?.actors == null) { yield break; } foreach (var entry in dto.actors) { if (!_slotDict.TryGetValue(entry.slotName, out var slot)) { Debug.LogWarning($"slot {entry.slotName} 不存在"); continue; } AbsActor actor = null; yield return _actorFactory.CreateFromEntryAsync( entry, slot, created => actor = created); if (actor == null) continue; UpsertActor(entry.actorName, actor); yield return actor.ApplyEntryAsync(entry); } } private void UpsertActor(string actorName, AbsActor actor) { if (_actorDict.TryGetValue(actorName, out var existing) && existing != null) { Destroy(existing.gameObject); } _actorDict[actorName] = actor; } #if UNITY_EDITOR public void InitAllDefault() { InitRefs(); DestroyEditorTemp(); foreach (var slot in _slotDict.Values) { if (!string.IsNullOrEmpty(slot.defaultActorName)) { if (slot.defaultActorType == ActorType.FrameAnimation) { if (Application.isPlaying) { StartCoroutine(InitActorAsync( slot.defaultActorName, slot.slotName, slot.defaultActorType, actor => actor?.Show())); } else { Debug.LogWarning("FrameAnimation 默认角色只能在 Play Mode 中异步加载。"); } } else { var actor = InitActor(slot.defaultActorName, slot.slotName, slot.defaultActorType); actor?.Show(); } } else { Debug.Log($"slot {slot.slotName} 没有默认角色"); } } } #endif public AbsActor InitActor(string actorName, string slotName, ActorType actorType) { if (actorType == ActorType.FrameAnimation) { Debug.LogError( $"FrameAnimation 角色 '{actorName}' 必须通过 InitActorAsync 初始化。"); return null; } var slot = _slotDict[slotName]; var actor = _actorFactory.CreateActor(actorName, slot, actorType); UpsertActor(actorName, actor); return actor; } public IEnumerator InitActorAsync( string actorName, string slotName, ActorType actorType, Action onCompleted = null) { if (!_slotDict.TryGetValue(slotName, out var slot)) { Debug.LogError( $"[ActorManager] 初始化角色 '{actorName}' 失败:slot '{slotName}' 不存在。"); onCompleted?.Invoke(null); yield break; } AbsActor actor = null; yield return _actorFactory.CreateActorAsync( actorName, slot, actorType, created => actor = created); if (actor == null) { onCompleted?.Invoke(null); yield break; } UpsertActor(actorName, actor); onCompleted?.Invoke(actor); } public bool TryFindActor(string actorOrSlot, out AbsActor actor) { if (_actorDict.TryGetValue(actorOrSlot, out actor)) { return true; } actor = _actorDict.Values.FirstOrDefault(item => item.SlotName == actorOrSlot); return actor != null; } public void ChangeSlot(string actorOrSlot, string targetSlot) { if (!_slotDict.TryGetValue(targetSlot, out var targetSlotObj)) return; if (!TryFindActor(actorOrSlot, out var actor)) return; actor.SetSlot(targetSlotObj); } } public static class ActorYarnCommand { private const string ClinicSlotName = "clinic"; [YarnCommand("init_actor")] public static IEnumerator InitActor(string actorName, string slotName = ClinicSlotName, string actorType = "Anima") { if (Enum.TryParse(actorType, out var actorTypeEnum)) { yield return ActorManager.Instance.InitActorAsync(actorName, slotName, actorTypeEnum); } else { Debug.Log($"Actor类型{actorType}有问题"); } } [YarnCommand("change_actor_state")] public static void PlayAnima(string animaName, string slotOrActorName = ClinicSlotName) { if (ActorManager.Instance.TryFindActor(slotOrActorName, out var actor)) { actor.ChangeState(animaName); } } [YarnCommand("change_actor_state_async")] public static IEnumerator PlayAnimaAsync(string animaName, string slotOrActorName = ClinicSlotName) { if (ActorManager.Instance.TryFindActor(slotOrActorName, out var actor)) { yield return actor.ChangeStateAsync(animaName); } } [YarnCommand("fade_in_actor")] public static IEnumerator ActorFadeIn(string actorName = ClinicSlotName, float duration = 1) { if (ActorManager.Instance.TryFindActor(actorName, out var actor)) { yield return actor.FadeInAsync(duration); } } [YarnCommand("fade_out_actor")] public static IEnumerator ActorFadeOut(string actorName = ClinicSlotName, float duration = 1) { if (ActorManager.Instance.TryFindActor(actorName, out var actor)) { yield return actor.FadeOutAsync(duration); } } [YarnCommand("change_actor_slot")] public static void ChangeSlot(string actorOrSlot, string targetSlot) { ActorManager.Instance.ChangeSlot(actorOrSlot, targetSlot); } [YarnCommand("show_actor")] public static void ShowActor(string actorName = ClinicSlotName) { if (ActorManager.Instance.TryFindActor(actorName, out var actor)) { actor.Show(); } } [YarnCommand("hide_actor")] public static void HideActor(string actorName = ClinicSlotName) { if (ActorManager.Instance.TryFindActor(actorName, out var actor)) { actor.Hide(); } } [YarnCommand("set_actor_face")] public static void SetFaceState(string faceName, string actorName = ClinicSlotName) { if (ActorManager.Instance.TryFindActor(actorName, out var actor)) { actor.SetFaceState(faceName); } } /// /// 持续振动角色立绘。默认作用于诊室槽位,强度单位为世界坐标。 /// 用法:<<start_actor_shake>> 或 <<start_actor_shake 火山 0.04 0.25 18>> /// [YarnCommand("start_actor_shake")] public static void StartActorShake( string actorName = ClinicSlotName, float strength = 0.04f, float cycleDuration = 0.25f, int vibrato = 18) { if (ActorManager.Instance.TryFindActor(actorName, out var actor)) { actor.StartShake(strength, cycleDuration, vibrato); } } /// /// 停止角色立绘振动并复位。默认作用于诊室槽位。 /// [YarnCommand("stop_actor_shake")] public static void StopActorShake(string actorName = ClinicSlotName) { if (ActorManager.Instance.TryFindActor(actorName, out var actor)) { actor.StopShake(); } } } }