chore(merge): 合并 develop 并整合 Day2 梦境剧情与演出
This commit is contained in:
@@ -5,6 +5,7 @@ using AibisDream.SaveSystem;
|
||||
using AibisDream.Utility;
|
||||
using DG.Tweening;
|
||||
using UnityEngine;
|
||||
using UnityEngine.ResourceManagement.AsyncOperations;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace AibisDream
|
||||
@@ -27,6 +28,7 @@ namespace AibisDream
|
||||
public string ActorName => _actorName;
|
||||
public string SlotName => _slotName;
|
||||
public ActorType ActorType => _actorType;
|
||||
public virtual bool IsReady => true;
|
||||
|
||||
public void Init(string actorName, ActorSlot slot, ActorType actorType)
|
||||
{
|
||||
@@ -61,6 +63,11 @@ namespace AibisDream
|
||||
spriteRenderer.SetAlpha(0);
|
||||
}
|
||||
|
||||
public virtual IEnumerator InitializeAsync()
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
public abstract ActorEntrySnapshotDto CaptureEntry();
|
||||
|
||||
public abstract IEnumerator ApplyEntryAsync(ActorEntrySnapshotDto entry);
|
||||
@@ -226,6 +233,102 @@ namespace AibisDream
|
||||
return actor;
|
||||
}
|
||||
|
||||
public IEnumerator CreateActorAsync(
|
||||
string actorName,
|
||||
ActorSlot slot,
|
||||
ActorType actorType,
|
||||
Action<AbsActor> onCompleted)
|
||||
{
|
||||
if (actorType != ActorType.FrameAnimation)
|
||||
{
|
||||
onCompleted?.Invoke(CreateActor(actorName, slot, actorType));
|
||||
yield break;
|
||||
}
|
||||
|
||||
yield return CreateFrameAnimationActorAsync(
|
||||
actorName,
|
||||
slot,
|
||||
actorType,
|
||||
null,
|
||||
onCompleted);
|
||||
}
|
||||
|
||||
public IEnumerator CreateFromEntryAsync(
|
||||
ActorEntrySnapshotDto entry,
|
||||
ActorSlot slot,
|
||||
Action<AbsActor> onCompleted)
|
||||
{
|
||||
if (!Enum.TryParse<ActorType>(entry.actorType, out var actorType))
|
||||
{
|
||||
actorType = ActorType.Anima;
|
||||
}
|
||||
|
||||
if (actorType != ActorType.FrameAnimation)
|
||||
{
|
||||
onCompleted?.Invoke(CreateFromEntry(entry, slot));
|
||||
yield break;
|
||||
}
|
||||
|
||||
yield return CreateFrameAnimationActorAsync(
|
||||
entry.actorName,
|
||||
slot,
|
||||
actorType,
|
||||
entry,
|
||||
onCompleted);
|
||||
}
|
||||
|
||||
private IEnumerator CreateFrameAnimationActorAsync(
|
||||
string actorName,
|
||||
ActorSlot slot,
|
||||
ActorType actorType,
|
||||
ActorEntrySnapshotDto entry,
|
||||
Action<AbsActor> onCompleted)
|
||||
{
|
||||
var prefabKey = ConstRef.FrameAnimationActorPrefabName;
|
||||
var handle = ResourceSystem.LoadPersistentAsync<GameObject>(prefabKey);
|
||||
yield return handle;
|
||||
|
||||
if (handle.Status != AsyncOperationStatus.Succeeded || handle.Result == null)
|
||||
{
|
||||
Debug.LogError(
|
||||
$"[ActorFactory] 角色 '{actorName}' 的帧动画 Prefab 加载失败,key='{prefabKey}'。");
|
||||
onCompleted?.Invoke(null);
|
||||
yield break;
|
||||
}
|
||||
|
||||
var instance = Object.Instantiate(handle.Result, _actorRoot);
|
||||
var actor = instance.GetComponent<AbsActor>();
|
||||
if (actor == null)
|
||||
{
|
||||
Debug.LogError(
|
||||
$"[ActorFactory] Prefab '{prefabKey}' 缺少 AbsActor 组件,角色='{actorName}'。");
|
||||
Object.Destroy(instance);
|
||||
onCompleted?.Invoke(null);
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (entry == null)
|
||||
{
|
||||
actor.Init(actorName, slot, actorType);
|
||||
}
|
||||
else
|
||||
{
|
||||
actor.InitFromEntry(entry, slot);
|
||||
}
|
||||
|
||||
yield return actor.InitializeAsync();
|
||||
if (!actor.IsReady)
|
||||
{
|
||||
Debug.LogError(
|
||||
$"[ActorFactory] 帧动画角色 '{actorName}' 初始化失败,Prefab='{prefabKey}'。");
|
||||
Object.Destroy(actor.gameObject);
|
||||
onCompleted?.Invoke(null);
|
||||
yield break;
|
||||
}
|
||||
|
||||
onCompleted?.Invoke(actor);
|
||||
}
|
||||
|
||||
private AbsActor InstantiateByType(ActorType actorType)
|
||||
{
|
||||
return actorType switch
|
||||
@@ -242,6 +345,7 @@ namespace AibisDream
|
||||
{
|
||||
Sprite,
|
||||
Anima,
|
||||
AnimaEx
|
||||
AnimaEx,
|
||||
FrameAnimation = 3
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,17 @@ namespace AibisDream
|
||||
{
|
||||
foreach (var slot in _slotDict.Values.Where(slot => slot.hasDefaultActor))
|
||||
{
|
||||
InitActor(slot.defaultActorName, slot.slotName, slot.defaultActorType);
|
||||
if (slot.defaultActorType == ActorType.FrameAnimation)
|
||||
{
|
||||
StartCoroutine(InitActorAsync(
|
||||
slot.defaultActorName,
|
||||
slot.slotName,
|
||||
slot.defaultActorType));
|
||||
}
|
||||
else
|
||||
{
|
||||
InitActor(slot.defaultActorName, slot.slotName, slot.defaultActorType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +104,11 @@ namespace AibisDream
|
||||
continue;
|
||||
}
|
||||
|
||||
var actor = _actorFactory.CreateFromEntry(entry, slot);
|
||||
AbsActor actor = null;
|
||||
yield return _actorFactory.CreateFromEntryAsync(
|
||||
entry,
|
||||
slot,
|
||||
created => actor = created);
|
||||
if (actor == null) continue;
|
||||
|
||||
UpsertActor(entry.actorName, actor);
|
||||
@@ -121,8 +135,26 @@ namespace AibisDream
|
||||
{
|
||||
if (!string.IsNullOrEmpty(slot.defaultActorName))
|
||||
{
|
||||
var actor = InitActor(slot.defaultActorName, slot.slotName, slot.defaultActorType);
|
||||
actor.Show();
|
||||
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
|
||||
{
|
||||
@@ -134,6 +166,13 @@ namespace AibisDream
|
||||
|
||||
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);
|
||||
@@ -141,6 +180,36 @@ namespace AibisDream
|
||||
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))
|
||||
@@ -165,11 +234,11 @@ namespace AibisDream
|
||||
private const string ClinicSlotName = "clinic";
|
||||
|
||||
[YarnCommand("init_actor")]
|
||||
public static void InitActor(string actorName, string slotName = ClinicSlotName, string actorType = "Anima")
|
||||
public static IEnumerator InitActor(string actorName, string slotName = ClinicSlotName, string actorType = "Anima")
|
||||
{
|
||||
if (Enum.TryParse<ActorType>(actorType, out var actorTypeEnum))
|
||||
{
|
||||
ActorManager.Instance.InitActor(actorName, slotName, actorTypeEnum);
|
||||
yield return ActorManager.Instance.InitActorAsync(actorName, slotName, actorTypeEnum);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using AibisDream.FrameAnimation;
|
||||
using AibisDream.Framework;
|
||||
using AibisDream.SaveSystem;
|
||||
using AibisDream.Utility;
|
||||
using UnityEngine;
|
||||
using UnityEngine.ResourceManagement.AsyncOperations;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
/// <summary>
|
||||
/// 由 FrameAnimationGraph 驱动的 ActorManager 角色实现。
|
||||
/// Graph 使用 FrameAnimation/{ActorName} 的场景级 Addressable key。
|
||||
/// </summary>
|
||||
public sealed class FrameAnimationActor : AbsActor
|
||||
{
|
||||
private FrameAnimationPlayer _player;
|
||||
private string _currentStateName = string.Empty;
|
||||
private bool _isReady;
|
||||
|
||||
public override bool IsReady => _isReady;
|
||||
|
||||
protected override void InitInternal()
|
||||
{
|
||||
_player = GetComponent<FrameAnimationPlayer>();
|
||||
base.InitInternal();
|
||||
_actorType = ActorType.FrameAnimation;
|
||||
_isReady = false;
|
||||
}
|
||||
|
||||
public override IEnumerator InitializeAsync()
|
||||
{
|
||||
if (_player == null)
|
||||
{
|
||||
Debug.LogError(
|
||||
$"[FrameAnimationActor] 角色 '{ActorName}' 缺少 FrameAnimationPlayer。",
|
||||
this);
|
||||
yield break;
|
||||
}
|
||||
|
||||
var graphKey = ConstRef.FrameAnimationGraphPrefix + ActorName;
|
||||
var handle = ResourceSystem.LoadSceneAsync<FrameAnimationGraph>(graphKey);
|
||||
if (!handle.IsValid())
|
||||
{
|
||||
Debug.LogError(
|
||||
$"[FrameAnimationActor] 角色 '{ActorName}' 无法开始加载 Graph,key='{graphKey}'。",
|
||||
this);
|
||||
yield break;
|
||||
}
|
||||
|
||||
yield return handle;
|
||||
if (handle.Status != AsyncOperationStatus.Succeeded || handle.Result == null)
|
||||
{
|
||||
Debug.LogError(
|
||||
$"[FrameAnimationActor] 角色 '{ActorName}' 的 Graph 加载失败,key='{graphKey}'。",
|
||||
this);
|
||||
yield break;
|
||||
}
|
||||
|
||||
var validation = FrameAnimationGraphValidator.Validate(handle.Result);
|
||||
if (validation.HasErrors)
|
||||
{
|
||||
var errors = string.Join(
|
||||
" | ",
|
||||
validation.Issues
|
||||
.Where(issue => issue.Severity == FrameAnimationValidationSeverity.Error)
|
||||
.Select(issue => $"{issue.Code}: {issue.Message}"));
|
||||
Debug.LogError(
|
||||
$"[FrameAnimationActor] 角色 '{ActorName}' 的 Graph 校验失败,key='{graphKey}':{errors}",
|
||||
this);
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (!_player.TryBindGraph(handle.Result, out var bindError))
|
||||
{
|
||||
Debug.LogError(
|
||||
$"[FrameAnimationActor] 角色 '{ActorName}' 绑定 Graph 失败,key='{graphKey}', error={bindError}",
|
||||
this);
|
||||
yield break;
|
||||
}
|
||||
|
||||
_isReady = true;
|
||||
}
|
||||
|
||||
public override void ChangeState(string stateName)
|
||||
{
|
||||
if (!TryPlay(stateName, false, out var handle))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
handle.RegisterCompleted(result => LogFailedResult(stateName, result));
|
||||
}
|
||||
|
||||
public override IEnumerator ChangeStateAsync(string stateName)
|
||||
{
|
||||
if (!TryPlay(stateName, false, out var handle))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
yield return handle;
|
||||
LogFailedResult(stateName, handle.Result);
|
||||
}
|
||||
|
||||
public override ActorEntrySnapshotDto CaptureEntry()
|
||||
{
|
||||
var entry = new ActorEntrySnapshotDto();
|
||||
FillBaseEntry(entry);
|
||||
entry.stateName = _currentStateName;
|
||||
return entry;
|
||||
}
|
||||
|
||||
public override IEnumerator ApplyEntryAsync(ActorEntrySnapshotDto entry)
|
||||
{
|
||||
ApplyAlphaFromEntry(entry);
|
||||
if (string.IsNullOrWhiteSpace(entry.stateName))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (!TryPlay(entry.stateName, true, out var handle))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
// 非循环终态会同步完成;Loop 会继续播放,读档 Barrier 不等待它。
|
||||
if (handle.IsCompleted)
|
||||
{
|
||||
LogFailedResult(entry.stateName, handle.Result);
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryPlay(
|
||||
string stateName,
|
||||
bool restoreTerminalState,
|
||||
out FrameAnimationPlaybackHandle handle)
|
||||
{
|
||||
handle = null;
|
||||
if (!_isReady || _player == null)
|
||||
{
|
||||
Debug.LogError(
|
||||
$"[FrameAnimationActor] 角色 '{ActorName}' 尚未完成初始化,playable='{stateName}'。",
|
||||
this);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(stateName))
|
||||
{
|
||||
Debug.LogWarning(
|
||||
$"[FrameAnimationActor] 角色 '{ActorName}' 收到空 playable id。",
|
||||
this);
|
||||
return false;
|
||||
}
|
||||
|
||||
handle = restoreTerminalState
|
||||
? _player.RestoreTerminalState(stateName)
|
||||
: _player.Play(stateName);
|
||||
if (handle.IsCompleted &&
|
||||
handle.Result.Reason == FrameAnimationCompletionReason.Failed)
|
||||
{
|
||||
LogFailedResult(stateName, handle.Result);
|
||||
return false;
|
||||
}
|
||||
|
||||
_currentStateName = stateName;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void LogFailedResult(
|
||||
string stateName,
|
||||
FrameAnimationPlaybackResult result)
|
||||
{
|
||||
if (result.Reason != FrameAnimationCompletionReason.Failed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var graphKey = ConstRef.FrameAnimationGraphPrefix + ActorName;
|
||||
Debug.LogError(
|
||||
$"[FrameAnimationActor] 角色 '{ActorName}' 播放失败,Graph='{graphKey}', " +
|
||||
$"playable='{stateName}', error={result.Error}",
|
||||
this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f2b56f5f150d4fc486d9c68615bd57dc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,115 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
public sealed class SceneReadinessResult
|
||||
{
|
||||
public bool Success { get; internal set; }
|
||||
public bool Cancelled { get; internal set; }
|
||||
public string Error { get; internal set; }
|
||||
}
|
||||
|
||||
public static class SceneReadiness
|
||||
{
|
||||
public const float DefaultTimeoutSeconds = 15f;
|
||||
|
||||
public static IEnumerator WaitUntilReady(
|
||||
Scene scene,
|
||||
Func<bool> isCancellationRequested,
|
||||
Action<SceneReadinessResult> completed,
|
||||
float timeoutSeconds = DefaultTimeoutSeconds)
|
||||
{
|
||||
var result = new SceneReadinessResult();
|
||||
|
||||
// 等待场景内 Awake/Start 完成,再收集当前场景自己的 Gate。
|
||||
yield return null;
|
||||
|
||||
if (isCancellationRequested?.Invoke() == true)
|
||||
{
|
||||
result.Cancelled = true;
|
||||
result.Error = "Scene readiness was cancelled.";
|
||||
completed?.Invoke(result);
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (!scene.IsValid() || !scene.isLoaded)
|
||||
{
|
||||
result.Error = "Loaded scene is invalid or not loaded.";
|
||||
completed?.Invoke(result);
|
||||
yield break;
|
||||
}
|
||||
|
||||
var gates = CollectGates(scene);
|
||||
if (gates.Count == 0)
|
||||
{
|
||||
result.Success = true;
|
||||
completed?.Invoke(result);
|
||||
yield break;
|
||||
}
|
||||
|
||||
var deadline = Time.unscaledTime + Mathf.Max(0f, timeoutSeconds);
|
||||
while (true)
|
||||
{
|
||||
if (isCancellationRequested?.Invoke() == true)
|
||||
{
|
||||
result.Cancelled = true;
|
||||
result.Error = "Scene readiness was cancelled.";
|
||||
break;
|
||||
}
|
||||
|
||||
var allReady = true;
|
||||
foreach (var gate in gates)
|
||||
{
|
||||
if (gate is UnityEngine.Object unityObject && unityObject == null)
|
||||
{
|
||||
result.Error = "A scene dialogue gate was destroyed while waiting.";
|
||||
completed?.Invoke(result);
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (!gate.IsDialogueReady)
|
||||
{
|
||||
allReady = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (allReady)
|
||||
{
|
||||
result.Success = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (Time.unscaledTime >= deadline)
|
||||
{
|
||||
result.Error = $"Scene readiness timed out after {timeoutSeconds:0.#} seconds.";
|
||||
break;
|
||||
}
|
||||
|
||||
yield return null;
|
||||
}
|
||||
|
||||
completed?.Invoke(result);
|
||||
}
|
||||
|
||||
private static List<ISceneDialogueGate> CollectGates(Scene scene)
|
||||
{
|
||||
var gates = new List<ISceneDialogueGate>();
|
||||
foreach (var root in scene.GetRootGameObjects())
|
||||
{
|
||||
foreach (var behaviour in root.GetComponentsInChildren<MonoBehaviour>(true))
|
||||
{
|
||||
if (behaviour is ISceneDialogueGate gate)
|
||||
{
|
||||
gates.Add(gate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return gates;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aa605d25316143c690f4e165e48ed832
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections;
|
||||
using AibisDream.Framework;
|
||||
using AibisDream.Kit;
|
||||
using AibisDream.SaveSystem;
|
||||
using AibisDream.Utility;
|
||||
using DG.Tweening;
|
||||
using UnityEngine;
|
||||
@@ -28,6 +29,8 @@ namespace AibisDream
|
||||
private Vector3 _smallBaseLocalScale;
|
||||
private Vector3 _smallMotionBaseOffset;
|
||||
private string _currentSmallPicName;
|
||||
private string _currentLargePicName;
|
||||
private bool _currentSmallUsesSplitLayers;
|
||||
|
||||
private DG.Tweening.Sequence _largePresentationSequence;
|
||||
private Vector3 _largeBaseLocalPosition;
|
||||
@@ -183,15 +186,27 @@ namespace AibisDream
|
||||
: picName;
|
||||
}
|
||||
|
||||
private IEnumerator LoadDreamSpriteInto(SpriteRenderer target, string picName)
|
||||
private IEnumerator LoadDreamSpriteInto(
|
||||
SpriteRenderer target,
|
||||
string picName,
|
||||
Action<bool> completed = null,
|
||||
Action<string> warning = null)
|
||||
{
|
||||
var key = string.Format(DreamSpritePath, NormalizeDreamPicName(picName));
|
||||
var handle = ResourceSystem.LoadAsync<Sprite>(key);
|
||||
yield return handle;
|
||||
if (handle.Status == AsyncOperationStatus.Succeeded && handle.Result != null)
|
||||
{
|
||||
target.sprite = handle.Result;
|
||||
completed?.Invoke(true);
|
||||
}
|
||||
else
|
||||
Debug.LogError($"[SpriteShowcase] Failed to load sprite: {key}");
|
||||
{
|
||||
var message = $"[SpriteShowcase] Failed to load sprite: {key}";
|
||||
Debug.LogError(message);
|
||||
warning?.Invoke(message);
|
||||
completed?.Invoke(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureSmallMotionRenderer()
|
||||
@@ -464,6 +479,72 @@ namespace AibisDream
|
||||
PlaySmallMotion(motionProfile);
|
||||
}
|
||||
|
||||
private void ClearSmallImmediate()
|
||||
{
|
||||
KillSmallTweenState();
|
||||
ResetSmallTransform();
|
||||
|
||||
if (_spriteRenderer != null)
|
||||
{
|
||||
_spriteRenderer.DOKill();
|
||||
_spriteRenderer.SetAlpha(0f);
|
||||
_spriteRenderer.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
_currentSmallPicName = null;
|
||||
_currentSmallUsesSplitLayers = false;
|
||||
|
||||
if (_smallMotionRenderer != null)
|
||||
{
|
||||
_smallMotionRenderer.DOKill();
|
||||
_smallMotionRenderer.SetAlpha(0f);
|
||||
_smallMotionRenderer.gameObject.SetActive(false);
|
||||
_smallMotionRenderer.maskInteraction = SpriteMaskInteraction.None;
|
||||
}
|
||||
|
||||
if (_smallMotionMask != null)
|
||||
{
|
||||
_smallMotionMask.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearLargeImmediate()
|
||||
{
|
||||
if (_largeSpriteRenderer != null)
|
||||
{
|
||||
_largeSpriteRenderer.DOKill();
|
||||
_largeSpriteRenderer.SetAlpha(0f);
|
||||
_largeSpriteRenderer.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
_currentLargePicName = null;
|
||||
}
|
||||
|
||||
private void ClearKiteImmediate()
|
||||
{
|
||||
KillKiteTweens();
|
||||
if (_kiteRenderer != null)
|
||||
{
|
||||
_kiteRenderer.SetAlpha(0f);
|
||||
}
|
||||
|
||||
if (_kiteBaseRenderer != null)
|
||||
{
|
||||
_kiteBaseRenderer.SetAlpha(0f);
|
||||
_kiteBaseRenderer.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
if (_kiteCameraNode != null)
|
||||
{
|
||||
_kiteCameraNode.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
if (_kiteMask != null)
|
||||
{
|
||||
_kiteMask.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 显示物体(小图)
|
||||
/// </summary>
|
||||
@@ -501,8 +582,16 @@ namespace AibisDream
|
||||
|
||||
_spriteRenderer.gameObject.SetActive(true);
|
||||
|
||||
yield return LoadDreamSpriteInto(_spriteRenderer, picName);
|
||||
var loaded = false;
|
||||
yield return LoadDreamSpriteInto(_spriteRenderer, picName, value => loaded = value);
|
||||
if (!loaded)
|
||||
{
|
||||
ClearSmallImmediate();
|
||||
yield break;
|
||||
}
|
||||
|
||||
_currentSmallPicName = NormalizeDreamPicName(picName);
|
||||
_currentSmallUsesSplitLayers = false;
|
||||
|
||||
yield return _spriteRenderer.DOFade(1, duration).WaitForCompletion();
|
||||
}
|
||||
@@ -523,7 +612,8 @@ namespace AibisDream
|
||||
|
||||
var normalizedName = NormalizeDreamPicName(picName);
|
||||
var useSplitMotion = TryGetSplitMotionPicNames(picName, out var basePicName, out var motionPicName);
|
||||
var isSamePic = string.Equals(_currentSmallPicName, normalizedName, StringComparison.OrdinalIgnoreCase);
|
||||
var isSamePic = string.Equals(_currentSmallPicName, normalizedName, StringComparison.OrdinalIgnoreCase)
|
||||
&& _currentSmallUsesSplitLayers == useSplitMotion;
|
||||
_smallMotionBaseOffset = normalizedName == "D2S轱辘" ? new Vector3(0f, -0.32f, 0f) : Vector3.zero;
|
||||
|
||||
KillSmallTweenState();
|
||||
@@ -533,7 +623,17 @@ namespace AibisDream
|
||||
|
||||
if (!isSamePic || _spriteRenderer.sprite == null)
|
||||
{
|
||||
yield return LoadDreamSpriteInto(_spriteRenderer, useSplitMotion ? basePicName : picName);
|
||||
var baseLoaded = false;
|
||||
yield return LoadDreamSpriteInto(
|
||||
_spriteRenderer,
|
||||
useSplitMotion ? basePicName : picName,
|
||||
value => baseLoaded = value);
|
||||
if (!baseLoaded)
|
||||
{
|
||||
ClearSmallImmediate();
|
||||
yield break;
|
||||
}
|
||||
|
||||
_currentSmallPicName = normalizedName;
|
||||
}
|
||||
_spriteRenderer.SetAlpha(1f);
|
||||
@@ -547,7 +647,16 @@ namespace AibisDream
|
||||
ResetSmallTransform(resetMotionLayer: !isSamePic);
|
||||
if (!isSamePic || _smallMotionRenderer.sprite == null)
|
||||
{
|
||||
yield return LoadDreamSpriteInto(_smallMotionRenderer, motionPicName);
|
||||
var motionLoaded = false;
|
||||
yield return LoadDreamSpriteInto(
|
||||
_smallMotionRenderer,
|
||||
motionPicName,
|
||||
value => motionLoaded = value);
|
||||
if (!motionLoaded)
|
||||
{
|
||||
ClearSmallImmediate();
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
_smallMotionRenderer.SetAlpha(1f);
|
||||
}
|
||||
@@ -563,6 +672,8 @@ namespace AibisDream
|
||||
}
|
||||
}
|
||||
|
||||
_currentSmallPicName = normalizedName;
|
||||
_currentSmallUsesSplitLayers = useSplitMotion;
|
||||
PlaySmallMotion(motionProfile);
|
||||
}
|
||||
|
||||
@@ -591,8 +702,15 @@ namespace AibisDream
|
||||
_largeSpriteRenderer.gameObject.SetActive(true);
|
||||
_largeSpriteRenderer.SetAlpha(0f);
|
||||
|
||||
yield return LoadDreamSpriteInto(_largeSpriteRenderer, picName);
|
||||
var loaded = false;
|
||||
yield return LoadDreamSpriteInto(_largeSpriteRenderer, picName, value => loaded = value);
|
||||
if (!loaded)
|
||||
{
|
||||
ClearLargeImmediate();
|
||||
yield break;
|
||||
}
|
||||
|
||||
_currentLargePicName = NormalizeDreamPicName(picName);
|
||||
if (duration <= 0f)
|
||||
{
|
||||
_largeSpriteRenderer.SetAlpha(1f);
|
||||
@@ -656,6 +774,7 @@ namespace AibisDream
|
||||
|
||||
_spriteRenderer.gameObject.SetActive(false);
|
||||
_currentSmallPicName = null;
|
||||
_currentSmallUsesSplitLayers = false;
|
||||
|
||||
if (_smallMotionRenderer != null)
|
||||
{
|
||||
@@ -691,6 +810,7 @@ namespace AibisDream
|
||||
}
|
||||
|
||||
_largeSpriteRenderer.gameObject.SetActive(false);
|
||||
_currentLargePicName = null;
|
||||
ResetLargePresentationImmediate();
|
||||
}
|
||||
|
||||
@@ -708,6 +828,185 @@ namespace AibisDream
|
||||
yield return HideLargeObj(duration);
|
||||
}
|
||||
|
||||
public ShowcaseSnapshotDto CaptureSnapshot()
|
||||
{
|
||||
if (IsKiteShowing)
|
||||
{
|
||||
return HiddenShowcaseSnapshot();
|
||||
}
|
||||
|
||||
if (IsSmallShowing && !string.IsNullOrEmpty(_currentSmallPicName))
|
||||
{
|
||||
return new ShowcaseSnapshotDto
|
||||
{
|
||||
displayMode = nameof(ShowcaseDisplayMode.Small),
|
||||
picName = _currentSmallPicName,
|
||||
usesSplitLayers = _currentSmallUsesSplitLayers
|
||||
};
|
||||
}
|
||||
|
||||
if (IsLargeShowing && !string.IsNullOrEmpty(_currentLargePicName))
|
||||
{
|
||||
return new ShowcaseSnapshotDto
|
||||
{
|
||||
displayMode = nameof(ShowcaseDisplayMode.Large),
|
||||
picName = _currentLargePicName
|
||||
};
|
||||
}
|
||||
|
||||
return HiddenShowcaseSnapshot();
|
||||
}
|
||||
|
||||
private static ShowcaseSnapshotDto HiddenShowcaseSnapshot()
|
||||
{
|
||||
return new ShowcaseSnapshotDto
|
||||
{
|
||||
displayMode = nameof(ShowcaseDisplayMode.None)
|
||||
};
|
||||
}
|
||||
|
||||
public IEnumerator RestoreSnapshotAsync(ShowcaseSnapshotDto snapshot, Action<string> warning = null)
|
||||
{
|
||||
ClearPresentationEffects();
|
||||
if (snapshot == null) yield break;
|
||||
|
||||
if (!Enum.TryParse(snapshot.displayMode, true, out ShowcaseDisplayMode mode))
|
||||
{
|
||||
warning?.Invoke($"[SpriteShowcase] Unknown display mode: {snapshot.displayMode}");
|
||||
yield break;
|
||||
}
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case ShowcaseDisplayMode.None:
|
||||
yield break;
|
||||
|
||||
case ShowcaseDisplayMode.Small:
|
||||
if (string.IsNullOrWhiteSpace(snapshot.picName))
|
||||
{
|
||||
warning?.Invoke("[SpriteShowcase] Small snapshot is missing picName.");
|
||||
yield break;
|
||||
}
|
||||
|
||||
yield return RestoreSmallImmediate(snapshot.picName, snapshot.usesSplitLayers, warning);
|
||||
yield break;
|
||||
|
||||
case ShowcaseDisplayMode.Large:
|
||||
if (string.IsNullOrWhiteSpace(snapshot.picName))
|
||||
{
|
||||
warning?.Invoke("[SpriteShowcase] Large snapshot is missing picName.");
|
||||
yield break;
|
||||
}
|
||||
|
||||
yield return RestoreLargeImmediate(snapshot.picName, warning);
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator RestoreSmallImmediate(string picName, bool usesSplitLayers, Action<string> warning)
|
||||
{
|
||||
if (_spriteRenderer == null) yield break;
|
||||
|
||||
var normalizedName = NormalizeDreamPicName(picName);
|
||||
_smallMotionBaseOffset = normalizedName == "D2S轱辘" ? new Vector3(0f, -0.32f, 0f) : Vector3.zero;
|
||||
_spriteRenderer.gameObject.SetActive(true);
|
||||
|
||||
if (usesSplitLayers)
|
||||
{
|
||||
if (!TryGetSplitMotionPicNames(picName, out var basePicName, out var motionPicName))
|
||||
{
|
||||
warning?.Invoke($"[SpriteShowcase] Split-layer snapshot is unsupported: {picName}");
|
||||
ClearSmallImmediate();
|
||||
yield break;
|
||||
}
|
||||
|
||||
var baseLoaded = false;
|
||||
yield return LoadDreamSpriteInto(
|
||||
_spriteRenderer,
|
||||
basePicName,
|
||||
value => baseLoaded = value,
|
||||
warning);
|
||||
if (!baseLoaded)
|
||||
{
|
||||
ClearSmallImmediate();
|
||||
yield break;
|
||||
}
|
||||
|
||||
EnsureSmallMotionRenderer();
|
||||
_smallMotionRenderer.gameObject.SetActive(true);
|
||||
CopySmallRendererSettings(_smallMotionRenderer, 1, SpriteMaskInteraction.VisibleInsideMask);
|
||||
ConfigureSmallMotionMask();
|
||||
ResetSmallTransform();
|
||||
|
||||
var motionLoaded = false;
|
||||
yield return LoadDreamSpriteInto(
|
||||
_smallMotionRenderer,
|
||||
motionPicName,
|
||||
value => motionLoaded = value,
|
||||
warning);
|
||||
if (!motionLoaded)
|
||||
{
|
||||
ClearSmallImmediate();
|
||||
yield break;
|
||||
}
|
||||
|
||||
_smallMotionRenderer.SetAlpha(1f);
|
||||
}
|
||||
else
|
||||
{
|
||||
var loaded = false;
|
||||
yield return LoadDreamSpriteInto(
|
||||
_spriteRenderer,
|
||||
picName,
|
||||
value => loaded = value,
|
||||
warning);
|
||||
if (!loaded)
|
||||
{
|
||||
ClearSmallImmediate();
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
_spriteRenderer.SetAlpha(1f);
|
||||
_currentSmallPicName = normalizedName;
|
||||
_currentSmallUsesSplitLayers = usesSplitLayers;
|
||||
}
|
||||
|
||||
private IEnumerator RestoreLargeImmediate(string picName, Action<string> warning)
|
||||
{
|
||||
if (_largeSpriteRenderer == null) yield break;
|
||||
|
||||
_largeSpriteRenderer.gameObject.SetActive(true);
|
||||
var loaded = false;
|
||||
yield return LoadDreamSpriteInto(
|
||||
_largeSpriteRenderer,
|
||||
picName,
|
||||
value => loaded = value,
|
||||
warning);
|
||||
if (!loaded)
|
||||
{
|
||||
ClearLargeImmediate();
|
||||
yield break;
|
||||
}
|
||||
|
||||
_largeSpriteRenderer.SetAlpha(1f);
|
||||
_currentLargePicName = NormalizeDreamPicName(picName);
|
||||
}
|
||||
|
||||
public void ClearPresentationEffects()
|
||||
{
|
||||
ClearSmallImmediate();
|
||||
ClearLargeImmediate();
|
||||
ClearKiteImmediate();
|
||||
|
||||
if (_backgroundSpriteRenderer != null)
|
||||
{
|
||||
_backgroundSpriteRenderer.DOKill();
|
||||
_backgroundSpriteRenderer.SetAlpha(0f);
|
||||
_backgroundSpriteRenderer.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
#region 风筝 Rig
|
||||
|
||||
// Kite Camera(虚拟镜头完全补偿)→ Rig(高度+漂移)→ Sway(鱼式流体摆动)→ Accent(DOTween 顿挫)
|
||||
|
||||
Reference in New Issue
Block a user