Files
aibis-dream/Assets/Scripts/SceneManagement/SpriteShowcase.cs
T

1542 lines
59 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections;
using AibisDream.Framework;
using AibisDream.Kit;
using AibisDream.SaveSystem;
using AibisDream.Utility;
using DG.Tweening;
using UnityEngine;
using UnityEngine.ResourceManagement.AsyncOperations;
namespace AibisDream
{
public class SpriteShowcase : Singleton<SpriteShowcase>
{
/// <summary>Addressable Key 模板,与 Scene_Dream 中梦境图 Address 一致。</summary>
private const string DreamSpritePath = "Sprite/梦境/{0}.png";
private const string KiteBaseSuffix = "_底图";
private const string KiteMotionSuffix = "_风筝层";
[SerializeField] private SpriteRenderer _spriteRenderer;
[SerializeField] private SpriteRenderer _backgroundSpriteRenderer;
[SerializeField] private SpriteRenderer _largeSpriteRenderer;
private SpriteRenderer _smallMotionRenderer;
private SpriteMask _smallMotionMask;
private DG.Tweening.Sequence _smallMotionSequence;
private Vector3 _smallBaseLocalPosition;
private Quaternion _smallBaseLocalRotation;
private Vector3 _smallBaseLocalScale;
private Vector3 _smallMotionBaseOffset;
private string _currentSmallPicName;
private string _currentLargePicName;
private bool _currentSmallUsesSplitLayers;
private DG.Tweening.Sequence _largePresentationSequence;
private Vector3 _largeBaseLocalPosition;
private Quaternion _largeBaseLocalRotation;
private Vector3 _largeBaseLocalScale;
private float _largeBaseAlpha;
private Material _largeBaseMaterial;
private MaterialPropertyBlock _largePropertyBlock;
private Coroutine _largeSpriteAnimationCoroutine;
private static readonly int BlurAmountId = Shader.PropertyToID("_BlurAmount");
private static readonly int BlurSizeId = Shader.PropertyToID("_BlurSize");
private static readonly int UseSoftFocusId = Shader.PropertyToID("_UseSoftFocus");
// 通过 activeSelf 判断当前状态
private bool IsSmallShowing => _spriteRenderer.gameObject.activeSelf;
private bool IsLargeShowing => _largeSpriteRenderer != null && _largeSpriteRenderer.gameObject.activeSelf;
public override void OnSingletonInit()
{
CaptureSmallBaseTransform();
CaptureLargeBaseTransform();
// 初始状态:隐藏
_spriteRenderer.gameObject.SetActive(false);
_spriteRenderer.SetAlpha(0);
// 大图初始状态:隐藏
if (_largeSpriteRenderer != null)
{
_largeSpriteRenderer.gameObject.SetActive(false);
_largeSpriteRenderer.SetAlpha(0);
}
}
private void OnDisable()
{
HideLargePresentationImmediate();
}
public override void OnSingletonDestroy()
{
HideLargePresentationImmediate();
base.OnSingletonDestroy();
}
private void CaptureSmallBaseTransform()
{
if (_spriteRenderer == null) return;
var tr = _spriteRenderer.transform;
_smallBaseLocalPosition = tr.localPosition;
_smallBaseLocalRotation = tr.localRotation;
_smallBaseLocalScale = tr.localScale;
}
private void CaptureLargeBaseTransform()
{
if (_largeSpriteRenderer == null) return;
var tr = _largeSpriteRenderer.transform;
_largeBaseLocalPosition = tr.localPosition;
_largeBaseLocalRotation = tr.localRotation;
_largeBaseLocalScale = tr.localScale;
_largeBaseAlpha = _largeSpriteRenderer.color.a;
_largeBaseMaterial = _largeSpriteRenderer.sharedMaterial;
}
private void StopLargeSpriteAnimation()
{
if (_largeSpriteAnimationCoroutine == null) return;
StopCoroutine(_largeSpriteAnimationCoroutine);
_largeSpriteAnimationCoroutine = null;
}
private IEnumerator LoopLargeSpriteAnimation(Sprite[] frames, float secondsPerFrame)
{
var frameIndex = 0;
var wait = new WaitForSeconds(Mathf.Max(0.01f, secondsPerFrame));
while (frames.Length > 0)
{
if (_largeSpriteRenderer != null)
{
_largeSpriteRenderer.sprite = frames[frameIndex];
}
frameIndex = (frameIndex + 1) % frames.Length;
yield return wait;
}
}
private void ApplyLargeSpriteBlur(float amount, float blurSize)
{
if (_largeSpriteRenderer == null) return;
amount = Mathf.Clamp01(amount);
if (amount <= 0.001f)
{
_largeSpriteRenderer.SetPropertyBlock(null);
_largeSpriteRenderer.sharedMaterial = _largeBaseMaterial;
return;
}
if (_kiteFocusMaterial == null)
{
Debug.LogWarning("[SpriteShowcase] 未配置全屏图片可用的对焦材质。", this);
return;
}
_largeSpriteRenderer.sharedMaterial = _kiteFocusMaterial;
_largePropertyBlock ??= new MaterialPropertyBlock();
_largeSpriteRenderer.GetPropertyBlock(_largePropertyBlock);
_largePropertyBlock.SetFloat(BlurAmountId, amount);
_largePropertyBlock.SetFloat(BlurSizeId, Mathf.Max(0f, blurSize));
_largePropertyBlock.SetFloat(UseSoftFocusId, 1f);
_largeSpriteRenderer.SetPropertyBlock(_largePropertyBlock);
}
public void SetLargeSpriteBlur(float amount, float blurSize = 0.012f)
{
ApplyLargeSpriteBlur(amount, blurSize);
}
public IEnumerator TweenLargeSpriteBlur(float targetAmount, float duration, float blurSize = 0.012f)
{
if (_largeSpriteRenderer == null) yield break;
_largePropertyBlock ??= new MaterialPropertyBlock();
_largeSpriteRenderer.GetPropertyBlock(_largePropertyBlock);
var startAmount = _largeSpriteRenderer.sharedMaterial == _kiteFocusMaterial
? _largePropertyBlock.GetFloat(BlurAmountId)
: 0f;
targetAmount = Mathf.Clamp01(targetAmount);
duration = Mathf.Max(0f, duration);
if (duration <= 0f)
{
ApplyLargeSpriteBlur(targetAmount, blurSize);
yield break;
}
var elapsed = 0f;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
var t = Mathf.Clamp01(elapsed / duration);
t = t * t * (3f - 2f * t);
ApplyLargeSpriteBlur(Mathf.Lerp(startAmount, targetAmount, t), blurSize);
yield return null;
}
ApplyLargeSpriteBlur(targetAmount, blurSize);
}
public IEnumerator AnimateLargePresentation(float scaleMultiplier, float targetAlpha, float duration)
{
if (_largeSpriteRenderer == null) yield break;
KillLargePresentationTweens();
_largeSpriteRenderer.gameObject.SetActive(true);
scaleMultiplier = Mathf.Max(0.01f, scaleMultiplier);
targetAlpha = Mathf.Clamp01(targetAlpha);
duration = Mathf.Max(0f, duration);
var targetScale = new Vector3(
_largeBaseLocalScale.x * scaleMultiplier,
_largeBaseLocalScale.y * scaleMultiplier,
_largeBaseLocalScale.z);
if (duration <= 0f)
{
_largeSpriteRenderer.transform.localPosition = _largeBaseLocalPosition;
_largeSpriteRenderer.transform.localRotation = _largeBaseLocalRotation;
_largeSpriteRenderer.transform.localScale = targetScale;
_largeSpriteRenderer.SetAlpha(targetAlpha);
yield break;
}
_largePresentationSequence = DOTween.Sequence();
_largePresentationSequence.Join(
_largeSpriteRenderer.transform.DOScale(targetScale, duration).SetEase(Ease.InOutSine));
_largePresentationSequence.Join(
_largeSpriteRenderer.transform.DOLocalMove(_largeBaseLocalPosition, duration).SetEase(Ease.InOutSine));
_largePresentationSequence.Join(
_largeSpriteRenderer.DOFade(targetAlpha, duration).SetEase(Ease.InOutSine));
yield return _largePresentationSequence.WaitForCompletion();
_largePresentationSequence = null;
}
public void SetLargePresentationImmediate(float scaleMultiplier, float alpha)
{
if (_largeSpriteRenderer == null) return;
KillLargePresentationTweens();
scaleMultiplier = Mathf.Max(0.01f, scaleMultiplier);
_largeSpriteRenderer.transform.localPosition = _largeBaseLocalPosition;
_largeSpriteRenderer.transform.localRotation = _largeBaseLocalRotation;
_largeSpriteRenderer.transform.localScale = new Vector3(
_largeBaseLocalScale.x * scaleMultiplier,
_largeBaseLocalScale.y * scaleMultiplier,
_largeBaseLocalScale.z);
_largeSpriteRenderer.SetAlpha(Mathf.Clamp01(alpha));
}
public void StopLargePresentationAnimation()
{
KillLargePresentationTweens();
}
public void ResetLargePresentationImmediate()
{
if (_largeSpriteRenderer == null) return;
KillLargePresentationTweens();
_largeSpriteRenderer.transform.localPosition = _largeBaseLocalPosition;
_largeSpriteRenderer.transform.localRotation = _largeBaseLocalRotation;
_largeSpriteRenderer.transform.localScale = _largeBaseLocalScale;
_largeSpriteRenderer.SetAlpha(_largeBaseAlpha);
}
public void HideLargePresentationImmediate()
{
if (_largeSpriteRenderer == null) return;
StopLargeSpriteAnimation();
ApplyLargeSpriteBlur(0f, 0f);
ResetLargePresentationImmediate();
_largeSpriteRenderer.gameObject.SetActive(false);
}
private void KillLargePresentationTweens()
{
_largePresentationSequence?.Kill();
_largePresentationSequence = null;
if (_largeSpriteRenderer == null) return;
DOTween.Kill(_largeSpriteRenderer);
DOTween.Kill(_largeSpriteRenderer.transform);
}
private static string NormalizeDreamPicName(string picName)
{
if (string.IsNullOrEmpty(picName)) return picName;
return picName.EndsWith(".png", StringComparison.OrdinalIgnoreCase)
? picName.Substring(0, picName.Length - 4)
: 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
{
var message = $"[SpriteShowcase] Failed to load sprite: {key}";
Debug.LogError(message);
warning?.Invoke(message);
completed?.Invoke(false);
}
}
private void EnsureSmallMotionRenderer()
{
if (_smallMotionRenderer != null || _spriteRenderer == null) return;
var motionObject = new GameObject($"{_spriteRenderer.name} Motion");
motionObject.transform.SetParent(_spriteRenderer.transform.parent);
_smallMotionRenderer = motionObject.AddComponent<SpriteRenderer>();
CopySmallRendererSettings(_smallMotionRenderer, 1, SpriteMaskInteraction.VisibleInsideMask);
_smallMotionRenderer.gameObject.SetActive(false);
_smallMotionRenderer.SetAlpha(0);
}
private void CopySmallRendererSettings(SpriteRenderer target, int sortingOrderOffset, SpriteMaskInteraction maskInteraction)
{
if (target == null || _spriteRenderer == null) return;
target.sharedMaterial = _spriteRenderer.sharedMaterial;
target.sortingLayerID = _spriteRenderer.sortingLayerID;
target.sortingOrder = _spriteRenderer.sortingOrder + sortingOrderOffset;
target.drawMode = _spriteRenderer.drawMode;
target.size = _spriteRenderer.size;
target.flipX = _spriteRenderer.flipX;
target.flipY = _spriteRenderer.flipY;
target.maskInteraction = maskInteraction;
}
private void EnsureSmallMotionMask()
{
if (_smallMotionMask != null || _spriteRenderer == null) return;
var maskObject = new GameObject($"{_spriteRenderer.name} Motion Mask");
maskObject.transform.SetParent(_spriteRenderer.transform.parent);
_smallMotionMask = maskObject.AddComponent<SpriteMask>();
_smallMotionMask.alphaCutoff = 0.01f;
_smallMotionMask.isCustomRangeActive = true;
_smallMotionMask.enabled = false;
}
private void ConfigureSmallMotionMask()
{
EnsureSmallMotionMask();
if (_smallMotionMask == null) return;
_smallMotionMask.sprite = _spriteRenderer.sprite;
_smallMotionMask.frontSortingLayerID = _spriteRenderer.sortingLayerID;
_smallMotionMask.backSortingLayerID = _spriteRenderer.sortingLayerID;
_smallMotionMask.frontSortingOrder = _spriteRenderer.sortingOrder + 2;
_smallMotionMask.backSortingOrder = _spriteRenderer.sortingOrder;
var maskTr = _smallMotionMask.transform;
maskTr.localPosition = _smallBaseLocalPosition;
maskTr.localRotation = _smallBaseLocalRotation;
maskTr.localScale = _smallBaseLocalScale;
_smallMotionMask.enabled = true;
}
private void KillSmallTweenState()
{
_smallMotionSequence?.Kill();
_smallMotionSequence = null;
}
private void ResetSmallTransform(bool resetMotionLayer = true)
{
if (_spriteRenderer == null) return;
var tr = _spriteRenderer.transform;
tr.localPosition = _smallBaseLocalPosition;
tr.localRotation = _smallBaseLocalRotation;
tr.localScale = _smallBaseLocalScale;
if (!resetMotionLayer || _smallMotionRenderer == null) return;
var motionTr = _smallMotionRenderer.transform;
motionTr.localPosition = _smallBaseLocalPosition + _smallMotionBaseOffset;
motionTr.localRotation = _smallBaseLocalRotation;
motionTr.localScale = _smallBaseLocalScale;
}
private static string NormalizeMotionProfile(string motionProfile)
{
return string.IsNullOrEmpty(motionProfile)
? string.Empty
: motionProfile.Trim().ToLowerInvariant();
}
private Vector3 BaseOffset(float x, float y)
{
return _smallBaseLocalPosition + _smallMotionBaseOffset + new Vector3(x, y, 0f);
}
private static bool TryGetSplitMotionPicNames(string picName, out string basePicName, out string motionPicName)
{
var normalized = NormalizeDreamPicName(picName);
// 风筝图已改由风筝 Rig(ShowKite 等)连续驱动,分层底图+遮罩通路仅保留给轱辘特写
if (normalized == "D2S轱辘")
{
basePicName = $"{normalized}{KiteBaseSuffix}";
motionPicName = $"{normalized}{KiteMotionSuffix}";
return true;
}
basePicName = picName;
motionPicName = null;
return false;
}
private void PlaySmallMotion(string motionProfile)
{
_smallMotionSequence?.Kill();
_smallMotionSequence = null;
var profile = NormalizeMotionProfile(motionProfile);
if (string.IsNullOrEmpty(profile) || profile == "none") return;
var target = _smallMotionRenderer != null && _smallMotionRenderer.gameObject.activeSelf
? _smallMotionRenderer
: _spriteRenderer;
var tr = target.transform;
var seq = DOTween.Sequence();
switch (profile)
{
// 轱辘:全力拉住——先被拽进受力姿态(只进一次),之后进入拔河式的角力拉锯。
// 拉锯循环见 PlayReelHold。
case "reel_tension":
seq.Append(tr.DOLocalMove(BaseOffset(0.13f, -0.32f), 0.22f).SetEase(Ease.OutCubic));
seq.Join(tr.DOLocalRotate(new Vector3(0f, 0f, -2.8f), 0.22f).SetEase(Ease.OutCubic));
seq.OnComplete(() => PlayReelHold(tr));
break;
default:
seq.Kill();
return;
}
_smallMotionSequence = seq;
}
/// <summary>
/// 轱辘“拔河角力”常驻循环:一场正在输掉的拔河。
/// strain 随回合递增(0→1):节奏越来越快、姿态被拽得越来越深、猛夺越来越频繁;
/// 往回挣不再是平滑回弹,而是“向下被拖住→抬回一点→又沉下去→再咬住”的三段拉锯。
/// 每回合参数随机,OnComplete 续下一回合;用纵向位移和停顿表达强拉,避免机械抖动。
/// </summary>
private void PlayReelHold(Transform tr, float strain = 0f)
{
_smallMotionSequence?.Kill();
// 姿态整体随 strain 加深;时长随 strain 收紧。旋转只做小角度,避免贴图边缘穿帮。
var deepX = strain * 0.06f;
var deepY = strain * 0.14f;
var speed = Mathf.Lerp(1f, 0.62f, strain);
var rotBias = strain * 0.8f;
var seq = DOTween.Sequence();
// 对面加力:被一点点拽深
var strainDur = UnityEngine.Random.Range(0.35f, 0.6f) * speed;
seq.Append(tr.DOLocalMove(
BaseOffset(UnityEngine.Random.Range(0.17f, 0.23f) + deepX,
UnityEngine.Random.Range(-0.42f, -0.34f) - deepY),
strainDur)
.SetEase(Ease.InOutSine));
seq.Join(tr.DOLocalRotate(
new Vector3(0f, 0f, UnityEngine.Random.Range(-3.6f, -2.4f) - rotBias), strainDur)
.SetEase(Ease.InOutSine));
// 猛夺一小截:概率与深度都随 strain 提升;夺完手上发麻——
// 不是连续 shake,而是被向下拽住、短暂停顿,再勉强抬回一点。
if (UnityEngine.Random.value < 0.55f + strain * 0.35f)
{
var yankDur = UnityEngine.Random.Range(0.1f, 0.16f) * speed;
seq.Append(tr.DOLocalMove(
BaseOffset(UnityEngine.Random.Range(0.25f, 0.34f) + deepX,
UnityEngine.Random.Range(-0.62f, -0.5f) - deepY),
yankDur)
.SetEase(Ease.OutCubic));
seq.Join(tr.DOLocalRotate(
new Vector3(0f, 0f, UnityEngine.Random.Range(-4.8f, -3.4f) - rotBias), yankDur)
.SetEase(Ease.OutCubic));
seq.AppendInterval(UnityEngine.Random.Range(0.05f, 0.11f) * speed);
var biteDur = UnityEngine.Random.Range(0.12f, 0.2f) * speed;
seq.Append(tr.DOLocalMove(
BaseOffset(UnityEngine.Random.Range(0.19f, 0.27f) + deepX,
UnityEngine.Random.Range(-0.48f, -0.4f) - deepY),
biteDur)
.SetEase(Ease.OutCubic));
seq.Join(tr.DOLocalRotate(
new Vector3(0f, 0f, UnityEngine.Random.Range(-3.4f, -2.2f) - rotBias), biteDur)
.SetEase(Ease.OutCubic));
}
// 往回挣(一):咬牙拉回一点
var recoverDur = UnityEngine.Random.Range(0.16f, 0.24f) * speed;
seq.Append(tr.DOLocalMove(
BaseOffset(UnityEngine.Random.Range(0.12f, 0.16f) + deepX,
UnityEngine.Random.Range(-0.32f, -0.26f) - deepY * 0.55f),
recoverDur)
.SetEase(Ease.OutCubic));
seq.Join(tr.DOLocalRotate(
new Vector3(0f, 0f, UnityEngine.Random.Range(-2.3f, -1.3f) - rotBias * 0.7f), recoverDur)
.SetEase(Ease.OutCubic));
// 滑走半截:手上又被抽走一段
var slipDur = UnityEngine.Random.Range(0.09f, 0.15f) * speed;
seq.Append(tr.DOLocalMove(
BaseOffset(UnityEngine.Random.Range(0.2f, 0.27f) + deepX,
UnityEngine.Random.Range(-0.5f, -0.4f) - deepY),
slipDur)
.SetEase(Ease.OutQuad));
seq.Join(tr.DOLocalRotate(
new Vector3(0f, 0f, UnityEngine.Random.Range(-4.1f, -2.8f) - rotBias), slipDur)
.SetEase(Ease.OutQuad));
// 四成回合再快速地向下沉半口气又咬住,上下拉锯的小碎步。
if (UnityEngine.Random.value < 0.45f)
{
var dipDur = UnityEngine.Random.Range(0.08f, 0.12f) * speed;
seq.Append(tr.DOLocalMove(
BaseOffset(UnityEngine.Random.Range(0.21f, 0.28f) + deepX,
UnityEngine.Random.Range(-0.56f, -0.47f) - deepY),
dipDur)
.SetEase(Ease.OutQuad));
seq.Join(tr.DOLocalRotate(
new Vector3(0f, 0f, UnityEngine.Random.Range(-4.4f, -3f) - rotBias), dipDur)
.SetEase(Ease.OutQuad));
var braceDur = UnityEngine.Random.Range(0.11f, 0.16f) * speed;
seq.Append(tr.DOLocalMove(
BaseOffset(UnityEngine.Random.Range(0.16f, 0.22f) + deepX,
UnityEngine.Random.Range(-0.4f, -0.33f) - deepY),
braceDur)
.SetEase(Ease.OutCubic));
seq.Join(tr.DOLocalRotate(
new Vector3(0f, 0f, UnityEngine.Random.Range(-3f, -1.8f) - rotBias), braceDur)
.SetEase(Ease.OutCubic));
}
// 往回挣(二):再咬住,但回不到最初的姿态
var pullDur = UnityEngine.Random.Range(0.25f, 0.38f) * speed;
seq.Append(tr.DOLocalMove(
BaseOffset(UnityEngine.Random.Range(0.13f, 0.18f) + deepX,
UnityEngine.Random.Range(-0.35f, -0.28f) - deepY * 0.75f),
pullDur)
.SetEase(Ease.InOutSine));
seq.Join(tr.DOLocalRotate(
new Vector3(0f, 0f, UnityEngine.Random.Range(-2.8f, -1.6f) - rotBias), pullDur)
.SetEase(Ease.InOutSine));
// 偶尔结尾再沉一下,像手腕被拖住,不做高频震动。
if (UnityEngine.Random.value < 0.35f)
{
var sagDur = UnityEngine.Random.Range(0.12f, 0.2f) * speed;
seq.Append(tr.DOLocalMove(
BaseOffset(UnityEngine.Random.Range(0.18f, 0.24f) + deepX,
UnityEngine.Random.Range(-0.46f, -0.38f) - deepY),
sagDur)
.SetEase(Ease.InOutSine));
seq.Join(tr.DOLocalRotate(
new Vector3(0f, 0f, UnityEngine.Random.Range(-3.5f, -2.2f) - rotBias), sagDur)
.SetEase(Ease.InOutSine));
}
seq.OnComplete(() => PlayReelHold(tr, Mathf.Min(1f, strain + 0.22f)));
_smallMotionSequence = seq;
}
public void PlayCurrentMotion(string motionProfile)
{
if (_spriteRenderer == null || !IsSmallShowing) return;
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()
{
StopLargeSpriteAnimation();
ApplyLargeSpriteBlur(0f, 0f);
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>
/// <param name="picName">图片名(无扩展名,与 Yarn 中一致;Addressable Key 由 <see cref="DreamSpritePath"/> 拼接)</param>
/// <param name="duration">淡入时长</param>
/// <returns>协程</returns>
public IEnumerator ShowObj(string picName, float duration = 1)
{
// 互斥:如果大图正在显示,先隐藏
if (IsLargeShowing)
{
yield return HideLargeObj(duration);
}
// 互斥:风筝 Rig 正在显示时先隐藏
if (IsKiteShowing)
{
yield return HideKite(Mathf.Min(duration, 0.25f));
}
KillSmallTweenState();
ResetSmallTransform();
if (_smallMotionRenderer != null)
{
_smallMotionRenderer.gameObject.SetActive(false);
_smallMotionRenderer.SetAlpha(0);
_smallMotionRenderer.maskInteraction = SpriteMaskInteraction.None;
}
if (_smallMotionMask != null)
{
_smallMotionMask.enabled = false;
}
_spriteRenderer.gameObject.SetActive(true);
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();
}
public IEnumerator ShowObjWithMotion(string picName, string motionProfile, float duration)
{
// 互斥:如果大图正在显示,先隐藏
if (IsLargeShowing)
{
yield return HideLargeObj(duration);
}
// 互斥:风筝 Rig 正在显示时先隐藏
if (IsKiteShowing)
{
yield return HideKite(Mathf.Min(duration, 0.25f));
}
var normalizedName = NormalizeDreamPicName(picName);
var useSplitMotion = TryGetSplitMotionPicNames(picName, out var basePicName, out var motionPicName);
var isSamePic = string.Equals(_currentSmallPicName, normalizedName, StringComparison.OrdinalIgnoreCase)
&& _currentSmallUsesSplitLayers == useSplitMotion;
_smallMotionBaseOffset = normalizedName == "D2S轱辘" ? new Vector3(0f, -0.32f, 0f) : Vector3.zero;
KillSmallTweenState();
ResetSmallTransform(resetMotionLayer: !isSamePic);
_spriteRenderer.gameObject.SetActive(true);
if (!isSamePic || _spriteRenderer.sprite == null)
{
var baseLoaded = false;
yield return LoadDreamSpriteInto(
_spriteRenderer,
useSplitMotion ? basePicName : picName,
value => baseLoaded = value);
if (!baseLoaded)
{
ClearSmallImmediate();
yield break;
}
_currentSmallPicName = normalizedName;
}
_spriteRenderer.SetAlpha(1f);
if (useSplitMotion)
{
EnsureSmallMotionRenderer();
_smallMotionRenderer.gameObject.SetActive(true);
CopySmallRendererSettings(_smallMotionRenderer, 1, SpriteMaskInteraction.VisibleInsideMask);
ConfigureSmallMotionMask();
ResetSmallTransform(resetMotionLayer: !isSamePic);
if (!isSamePic || _smallMotionRenderer.sprite == null)
{
var motionLoaded = false;
yield return LoadDreamSpriteInto(
_smallMotionRenderer,
motionPicName,
value => motionLoaded = value);
if (!motionLoaded)
{
ClearSmallImmediate();
yield break;
}
}
_smallMotionRenderer.SetAlpha(1f);
}
else if (_smallMotionRenderer != null)
{
_smallMotionRenderer.gameObject.SetActive(false);
_smallMotionRenderer.SetAlpha(0);
_smallMotionRenderer.maskInteraction = SpriteMaskInteraction.None;
if (_smallMotionMask != null)
{
_smallMotionMask.enabled = false;
}
}
_currentSmallPicName = normalizedName;
_currentSmallUsesSplitLayers = useSplitMotion;
PlaySmallMotion(motionProfile);
}
/// <summary>
/// 显示大图
/// </summary>
/// <param name="picName">图片名(无扩展名,与 Yarn 中一致;Addressable Key 由 <see cref="DreamSpritePath"/> 拼接)</param>
/// <param name="duration">淡入时长</param>
/// <returns>协程</returns>
public IEnumerator ShowLargeObj(string picName, float duration = 1)
{
StopLargeSpriteAnimation();
// 互斥:小图立刻关掉,避免风筝层与底图不同步淡出造成的怪 fade
if (IsSmallShowing)
{
yield return HideObj(0f);
}
if (IsKiteShowing)
{
yield return HideKite(0f);
}
if (_largeSpriteRenderer == null) yield break;
ResetLargePresentationImmediate();
_largeSpriteRenderer.gameObject.SetActive(true);
_largeSpriteRenderer.SetAlpha(0f);
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);
}
else
{
yield return _largeSpriteRenderer.DOFade(1, duration).WaitForCompletion();
}
}
/// <summary>
/// 按“前缀1.png … 前缀N.png”加载并循环播放全屏序列帧。
/// </summary>
public IEnumerator ShowLargeSpriteAnimation(
string picNamePrefix,
int frameCount,
float secondsPerFrame = 0.2f,
float duration = 1f)
{
if (_largeSpriteRenderer == null || frameCount <= 0) yield break;
StopLargeSpriteAnimation();
if (IsSmallShowing)
{
yield return HideObj(0f);
}
if (IsKiteShowing)
{
yield return HideKite(0f);
}
var frames = new Sprite[frameCount];
for (var i = 0; i < frameCount; i++)
{
var picName = $"{NormalizeDreamPicName(picNamePrefix)}{i + 1}";
var key = string.Format(DreamSpritePath, picName);
var handle = ResourceSystem.LoadAsync<Sprite>(key);
yield return handle;
if (handle.Status != AsyncOperationStatus.Succeeded || handle.Result == null)
{
Debug.LogError($"[SpriteShowcase] Failed to load animation frame: {key}");
ClearLargeImmediate();
yield break;
}
frames[i] = handle.Result;
}
ResetLargePresentationImmediate();
_largeSpriteRenderer.gameObject.SetActive(true);
_largeSpriteRenderer.sprite = frames[0];
_largeSpriteRenderer.SetAlpha(0f);
_currentLargePicName = $"{NormalizeDreamPicName(picNamePrefix)}1";
_largeSpriteAnimationCoroutine = StartCoroutine(
LoopLargeSpriteAnimation(frames, secondsPerFrame));
if (duration <= 0f)
{
_largeSpriteRenderer.SetAlpha(1f);
}
else
{
yield return _largeSpriteRenderer.DOFade(1f, duration).WaitForCompletion();
}
}
public void SetLargeSpriteImmediate(Sprite sprite)
{
if (_largeSpriteRenderer != null && sprite != null)
_largeSpriteRenderer.sprite = sprite;
}
/// <summary>
/// 隐藏物体(小图)
/// </summary>
/// <param name="duration">淡出时长</param>
/// <returns>协程</returns>
public IEnumerator HideObj(float duration = 1)
{
yield return HideObj(force: false, duration);
}
/// <summary>
/// 隐藏物体(小图)
/// </summary>
/// <param name="force">是否强制隐藏</param>
/// <param name="duration">淡出时长</param>
/// <returns>协程</returns>
public IEnumerator HideObj(bool force, float duration = 1)
{
// 风筝 Rig 视作小图的一种,hide_sprite 时一并隐藏
if (IsKiteShowing)
{
yield return HideKite(duration);
}
// force=false 时检查状态,未显示则直接返回
if (!force && !IsSmallShowing) yield break;
KillSmallTweenState();
ResetSmallTransform();
var motionActive = _smallMotionRenderer != null && _smallMotionRenderer.gameObject.activeSelf;
if (duration <= 0f)
{
_spriteRenderer.SetAlpha(0);
if (motionActive)
_smallMotionRenderer.SetAlpha(0);
}
else
{
var fade = _spriteRenderer.DOFade(0, duration);
if (motionActive)
_smallMotionRenderer.DOFade(0, duration);
yield return fade.WaitForCompletion();
}
_spriteRenderer.gameObject.SetActive(false);
_currentSmallPicName = null;
_currentSmallUsesSplitLayers = false;
if (_smallMotionRenderer != null)
{
_smallMotionRenderer.gameObject.SetActive(false);
_smallMotionRenderer.SetAlpha(0);
_smallMotionRenderer.maskInteraction = SpriteMaskInteraction.None;
}
if (_smallMotionMask != null)
{
_smallMotionMask.enabled = false;
}
}
/// <summary>
/// 隐藏大图
/// </summary>
/// <param name="force">是否强制隐藏</param>
/// <param name="duration">淡出时长</param>
/// <returns>协程</returns>
public IEnumerator HideLargeObj(float duration = 1)
{
if (_largeSpriteRenderer == null) yield break;
StopLargeSpriteAnimation();
KillLargePresentationTweens();
if (duration <= 0f)
{
_largeSpriteRenderer.SetAlpha(0f);
}
else
{
yield return _largeSpriteRenderer.DOFade(0, duration).WaitForCompletion();
}
_largeSpriteRenderer.gameObject.SetActive(false);
_currentLargePicName = null;
ApplyLargeSpriteBlur(0f, 0f);
ResetLargePresentationImmediate();
}
/// <summary>
/// 隐藏所有(小图和大图)
/// </summary>
/// <param name="duration">淡出时长</param>
/// <returns>协程</returns>
public IEnumerator HideAll(float duration = 1)
{
if (IsSmallShowing || IsKiteShowing)
yield return HideObj(duration);
if (IsLargeShowing)
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(鱼式流体摆动)→ AccentDOTween 顿挫)
// 底图 / Mask 保持静态,不进镜头节点。
private const string KiteSpritePicName = "D2S风筝1_风筝层";
private const string KiteBasePicName = "D2S风筝1_底图";
private const float KiteMaxHeight = 13f;
[Header("风筝 / 对焦材质")]
[SerializeField] private Material _kiteFocusMaterial;
[SerializeField] private KiteWindSettings _kiteWindSettings = new KiteWindSettings();
[SerializeField] private KiteCameraSettings _kiteCameraSettings = new KiteCameraSettings();
[SerializeField] private KiteFocusSettings _kiteFocusSettings = new KiteFocusSettings();
private Transform _kiteCameraNode;
private Transform _kiteRootPivot;
private Transform _kiteSwayPivot;
private SpriteRenderer _kiteRenderer;
private SpriteRenderer _kiteBaseRenderer;
private SpriteMask _kiteMask;
private KiteRigDriver _kiteDriver;
private DG.Tweening.Sequence _kiteAccentSequence;
private bool _kiteSpriteLoaded;
private bool _kiteBaseSpriteLoaded;
private bool IsKiteShowing =>
_kiteCameraNode != null && _kiteCameraNode.gameObject.activeSelf;
private void EnsureKiteRig()
{
if (_kiteRootPivot != null || _spriteRenderer == null) return;
var parent = _spriteRenderer.transform.parent;
var cameraObject = new GameObject("Kite Camera");
cameraObject.transform.SetParent(parent);
cameraObject.transform.localPosition = _smallBaseLocalPosition;
cameraObject.transform.localRotation = Quaternion.identity;
cameraObject.transform.localScale = Vector3.one;
var root = new GameObject("Kite Rig");
root.transform.SetParent(cameraObject.transform, false);
root.transform.localPosition = Vector3.zero;
root.transform.localRotation = _smallBaseLocalRotation;
root.transform.localScale = _smallBaseLocalScale;
var sway = new GameObject("Kite Sway");
sway.transform.SetParent(root.transform, false);
var accent = new GameObject("Kite Accent");
accent.transform.SetParent(sway.transform, false);
// 白色底图:独立静态节点,不随镜头/高度缩放
var baseObject = new GameObject("Kite Base");
baseObject.transform.SetParent(parent);
baseObject.transform.localPosition = _smallBaseLocalPosition;
baseObject.transform.localRotation = _smallBaseLocalRotation;
baseObject.transform.localScale = _smallBaseLocalScale;
// 遮罩:把风筝层限制在底图(天空)范围内
var maskObject = new GameObject("Kite Mask");
maskObject.transform.SetParent(parent);
maskObject.transform.localPosition = _smallBaseLocalPosition;
maskObject.transform.localRotation = _smallBaseLocalRotation;
maskObject.transform.localScale = _smallBaseLocalScale;
_kiteCameraNode = cameraObject.transform;
_kiteRootPivot = root.transform;
_kiteSwayPivot = sway.transform;
_kiteRenderer = accent.AddComponent<SpriteRenderer>();
CopySmallRendererSettings(_kiteRenderer, 1, SpriteMaskInteraction.VisibleInsideMask);
_kiteBaseRenderer = baseObject.AddComponent<SpriteRenderer>();
CopySmallRendererSettings(_kiteBaseRenderer, 0, SpriteMaskInteraction.None);
_kiteMask = maskObject.AddComponent<SpriteMask>();
_kiteMask.alphaCutoff = 0.01f;
_kiteMask.isCustomRangeActive = true;
_kiteMask.enabled = false;
EnsureKiteFocusMaterial();
_kiteDriver = cameraObject.AddComponent<KiteRigDriver>();
_kiteDriver.Configure(
_kiteCameraNode,
_kiteRootPivot,
_kiteSwayPivot,
_kiteRenderer,
_smallBaseLocalPosition,
_smallBaseLocalRotation,
_smallBaseLocalScale,
_kiteWindSettings,
_kiteCameraSettings,
_kiteFocusSettings,
_kiteFocusMaterial);
_kiteDriver.enabled = false;
cameraObject.SetActive(false);
baseObject.SetActive(false);
_kiteRenderer.SetAlpha(0);
_kiteBaseRenderer.SetAlpha(0);
}
private void EnsureKiteFocusMaterial()
{
if (_kiteFocusMaterial != null) return;
var shader = Shader.Find("AIBIS/KiteFocusSprite");
if (shader == null)
{
Debug.LogWarning("[SpriteShowcase] 找不到 AIBIS/KiteFocusSprite,风筝虚焦不可用。", this);
return;
}
_kiteFocusMaterial = new Material(shader) { name = "KiteFocusSprite (Runtime)" };
}
private void KillKiteTweens()
{
_kiteAccentSequence?.Kill();
_kiteAccentSequence = null;
_kiteDriver?.ResetAndSleep();
}
/// <summary>
/// 显示风筝 Rig。已显示时等价于 <see cref="SetKiteState"/>(幂等)。
/// </summary>
public IEnumerator ShowKite(float height, int wind, float duration = 0.35f)
{
if (IsLargeShowing)
{
yield return HideLargeObj(duration);
}
if (IsSmallShowing)
{
yield return HideObj(Mathf.Min(duration, 0.25f));
}
EnsureKiteRig();
if (_kiteRenderer == null || _kiteDriver == null) yield break;
height = Mathf.Clamp(height, 0f, KiteMaxHeight);
if (IsKiteShowing)
{
SetKiteState(height, wind, duration);
yield break;
}
KillKiteTweens();
_kiteCameraNode.gameObject.SetActive(true);
_kiteBaseRenderer.gameObject.SetActive(true);
if (!_kiteSpriteLoaded)
{
yield return LoadDreamSpriteInto(_kiteRenderer, KiteSpritePicName);
_kiteSpriteLoaded = _kiteRenderer.sprite != null;
// 贴图加载后重新赋 focus 材质(Load 可能换过 material
EnsureKiteFocusMaterial();
if (_kiteFocusMaterial != null)
{
_kiteRenderer.material = _kiteFocusMaterial;
}
}
if (!_kiteBaseSpriteLoaded)
{
yield return LoadDreamSpriteInto(_kiteBaseRenderer, KiteBasePicName);
_kiteBaseSpriteLoaded = _kiteBaseRenderer.sprite != null;
}
if (_kiteBaseSpriteLoaded)
{
_kiteMask.sprite = _kiteBaseRenderer.sprite;
_kiteMask.frontSortingLayerID = _kiteRenderer.sortingLayerID;
_kiteMask.backSortingLayerID = _kiteRenderer.sortingLayerID;
_kiteMask.frontSortingOrder = _spriteRenderer.sortingOrder + 2;
_kiteMask.backSortingOrder = _spriteRenderer.sortingOrder;
_kiteMask.enabled = true;
}
_kiteDriver.Wake(height, wind);
_kiteBaseRenderer.SetAlpha(1f);
_kiteRenderer.SetAlpha(KiteRigDriver.Alpha(height));
}
/// <summary>
/// 高度 SmoothDamp + 切换风向。乱度粘滞由 driver 保留。
/// </summary>
public void SetKiteState(float height, int wind, float duration = 0.9f)
{
if (!IsKiteShowing || _kiteDriver == null) return;
height = Mathf.Clamp(height, 0f, KiteMaxHeight);
_kiteDriver.SetHeight(height, duration);
_kiteDriver.SetWind(wind);
}
/// <summary>仅切换风况(选择前 telegraph 风向)。</summary>
/// <param name="turbulence">乱度 0~1;负数或省略表示平稳(清零)。设定后粘滞,kite_state 沿用。</param>
public void SetKiteWind(int wind, float turbulence = -1f)
{
if (!IsKiteShowing || _kiteDriver == null) return;
// 与旧语义对齐:省略/负数 → 平稳(乱度归零);显式传入 ≥0 则设定并粘滞
float t = turbulence >= 0f ? Mathf.Clamp01(turbulence) : 0f;
_kiteDriver.SetWind(wind, t);
}
/// <summary>
/// 一次性顿挫动效,叠加在 Accent 层上,结束回到原位。
/// 镜头采样合成位姿,会自然追着 accent 跑。
/// </summary>
public void PlayKiteAccent(string profile)
{
if (!IsKiteShowing) return;
_kiteAccentSequence?.Kill();
_kiteAccentSequence = null;
var tr = _kiteRenderer.transform;
var seq = DOTween.Sequence();
switch (NormalizeMotionProfile(profile))
{
case "lift":
seq.Append(tr.DOLocalPath(new[]
{
new Vector3(0.03f, 0.07f, 0f),
new Vector3(0.11f, 0.17f, 0f),
new Vector3(0.12f, 0.24f, 0f),
}, 0.75f, PathType.CatmullRom).SetEase(Ease.InOutSine));
seq.Join(tr.DOLocalRotate(new Vector3(2f, -1.5f, -2f), 0.62f).SetEase(Ease.InOutSine));
break;
case "sink":
seq.Append(tr.DOLocalPath(new[]
{
new Vector3(0.03f, -0.06f, 0f),
new Vector3(-0.02f, -0.17f, 0f),
new Vector3(-0.08f, -0.24f, 0f),
}, 0.82f, PathType.CatmullRom).SetEase(Ease.InOutSine));
seq.Join(tr.DOLocalRotate(new Vector3(-2f, 1.5f, 2f), 0.68f).SetEase(Ease.InOutSine));
break;
case "taut":
seq.Append(tr.DOLocalPath(new[]
{
new Vector3(0.08f, 0.01f, 0f),
new Vector3(0.2f, 0.07f, 0f),
new Vector3(0.26f, 0.05f, 0f),
}, 0.62f, PathType.CatmullRom).SetEase(Ease.OutSine));
seq.Join(tr.DOLocalRotate(new Vector3(0f, -2f, -3f), 0.62f).SetEase(Ease.OutSine));
seq.AppendInterval(0.32f);
break;
case "drop":
seq.Append(tr.DOLocalPath(new[]
{
new Vector3(0.06f, -0.08f, 0f),
new Vector3(0.08f, -0.24f, 0f),
new Vector3(0.01f, -0.36f, 0f),
}, 0.92f, PathType.CatmullRom).SetEase(Ease.InSine));
seq.Join(tr.DOLocalRotate(new Vector3(-2.5f, 1f, 3f), 0.86f).SetEase(Ease.InOutSine));
break;
case "wrench":
seq.Append(tr.DOLocalPath(new[]
{
new Vector3(-0.06f, 0.03f, 0f),
new Vector3(0.3f, 0.18f, 0f),
new Vector3(0.64f, 0.12f, 0f),
new Vector3(0.78f, 0.24f, 0f),
}, 1.15f, PathType.CatmullRom).SetEase(Ease.InOutSine));
seq.Join(tr.DOLocalRotate(new Vector3(1.5f, -3f, -6f), 0.92f).SetEase(Ease.InOutSine));
seq.AppendInterval(0.42f);
break;
case "flutter":
{
float side = UnityEngine.Random.value < 0.5f ? -1f : 1f;
seq.Append(tr.DOLocalPath(new[]
{
new Vector3(0.05f * side, 0.04f, 0f),
new Vector3(0.16f * side, 0.11f, 0f),
new Vector3(0.24f * side, 0.07f, 0f),
}, 1.05f, PathType.CatmullRom).SetEase(Ease.InOutSine));
seq.Join(tr.DOLocalRotate(new Vector3(1.5f, -2.5f * side, -3f * side), 0.9f)
.SetEase(Ease.InOutSine));
seq.AppendInterval(0.22f);
break;
}
default:
seq.Kill();
return;
}
seq.Append(tr.DOLocalMove(Vector3.zero, 0.82f).SetEase(Ease.InOutSine));
seq.Join(tr.DOLocalRotate(Vector3.zero, 0.82f).SetEase(Ease.InOutSine));
_kiteAccentSequence = seq;
}
/// <summary>
/// 隐藏风筝 Rig。与显示一致不做淡出,直接消失。
/// </summary>
public IEnumerator HideKite(float duration = 1)
{
if (!IsKiteShowing) yield break;
KillKiteTweens();
_kiteRenderer.SetAlpha(0);
_kiteBaseRenderer.SetAlpha(0);
_kiteCameraNode.gameObject.SetActive(false);
_kiteBaseRenderer.gameObject.SetActive(false);
if (_kiteMask != null)
{
_kiteMask.enabled = false;
}
}
#endregion
/// <summary>
/// 淡入(异步协程版本)
/// </summary>
/// <param name="duration">淡入时长</param>
/// <returns>协程</returns>
public IEnumerator FadeInAsync(float duration = 1)
{
if (_backgroundSpriteRenderer == null) yield break;
_backgroundSpriteRenderer.gameObject.SetActive(true);
yield return _backgroundSpriteRenderer.DOFade(1, duration).WaitForCompletion();
}
/// <summary>
/// 淡出(异步协程版本)
/// </summary>
/// <param name="duration">淡出时长</param>
/// <returns>协程</returns>
public IEnumerator FadeOutAsync(float duration = 1)
{
if (_backgroundSpriteRenderer == null) yield break;
_backgroundSpriteRenderer.gameObject.SetActive(true);
yield return _backgroundSpriteRenderer.DOFade(0, duration).WaitForCompletion();
_backgroundSpriteRenderer.gameObject.SetActive(false);
}
/// <summary>
/// 淡入(同步版本,启动协程)
/// </summary>
/// <param name="duration">淡入时长</param>
public void FadeInSync(float duration = 1)
{
StartCoroutine(FadeInAsync(duration));
}
/// <summary>
/// 淡出(同步版本,启动协程)
/// </summary>
/// <param name="duration">淡出时长</param>
public void FadeOutSync(float duration = 1)
{
StartCoroutine(FadeOutAsync(duration));
}
}
}