Files

348 lines
11 KiB
C#

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<ActorManager>
{
#region 索引
private Transform _actorRoot;
private Dictionary<string, ActorSlot> _slotDict;
private Dictionary<string, AbsActor> _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<string, ActorSlot>();
foreach (var child in transform.Find("Slots").GetComponentsInChildren<ActorSlot>())
{
_slotDict[child.slotName] = child;
}
_actorDict = new Dictionary<string, AbsActor>();
_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<AbsActor> 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>(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);
}
}
/// <summary>
/// 持续振动角色立绘。默认作用于诊室槽位,强度单位为世界坐标。
/// 用法:&lt;&lt;start_actor_shake&gt;&gt; 或 &lt;&lt;start_actor_shake 火山 0.04 0.25 18&gt;&gt;
/// </summary>
[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);
}
}
/// <summary>
/// 停止角色立绘振动并复位。默认作用于诊室槽位。
/// </summary>
[YarnCommand("stop_actor_shake")]
public static void StopActorShake(string actorName = ClinicSlotName)
{
if (ActorManager.Instance.TryFindActor(actorName, out var actor))
{
actor.StopShake();
}
}
}
}