382 lines
16 KiB
C#
382 lines
16 KiB
C#
using System.Collections;
|
|
using AibisDream.Utility;
|
|
using DG.Tweening;
|
|
using UnityEngine;
|
|
|
|
namespace AibisDream
|
|
{
|
|
internal sealed class KitePresentation
|
|
{
|
|
private const string KiteSpritePicName = "D2S风筝1_风筝层";
|
|
private const string KiteBasePicName = "D2S风筝1_底图";
|
|
private const float KiteMaxHeight = 13f;
|
|
|
|
private readonly SpriteRenderer _referenceRenderer;
|
|
private readonly Vector3 _baseLocalPosition;
|
|
private readonly Quaternion _baseLocalRotation;
|
|
private readonly Vector3 _baseLocalScale;
|
|
private readonly KiteWindSettings _windSettings;
|
|
private readonly KiteCameraSettings _cameraSettings;
|
|
private readonly KiteFocusSettings _focusSettings;
|
|
|
|
private Material _focusMaterial;
|
|
private Transform _cameraNode;
|
|
private Transform _rootPivot;
|
|
private Transform _swayPivot;
|
|
private SpriteRenderer _kiteRenderer;
|
|
private SpriteRenderer _baseRenderer;
|
|
private SpriteMask _mask;
|
|
private KiteRigDriver _driver;
|
|
private Sequence _accentSequence;
|
|
private bool _kiteSpriteLoaded;
|
|
private bool _baseSpriteLoaded;
|
|
|
|
public bool IsShowing => _cameraNode != null && _cameraNode.gameObject.activeSelf;
|
|
public float TargetHeight => _driver != null ? _driver.TargetHeight : 0f;
|
|
public int CurrentWind => _driver != null ? _driver.CurrentWind : 0;
|
|
public float Turbulence => _driver != null ? _driver.Turbulence : 0f;
|
|
|
|
public KitePresentation(
|
|
SpriteRenderer referenceRenderer,
|
|
Material focusMaterial,
|
|
KiteWindSettings windSettings,
|
|
KiteCameraSettings cameraSettings,
|
|
KiteFocusSettings focusSettings)
|
|
{
|
|
_referenceRenderer = referenceRenderer;
|
|
_focusMaterial = focusMaterial;
|
|
_windSettings = windSettings ?? new KiteWindSettings();
|
|
_cameraSettings = cameraSettings ?? new KiteCameraSettings();
|
|
_focusSettings = focusSettings ?? new KiteFocusSettings();
|
|
if (_referenceRenderer == null) return;
|
|
var transform = _referenceRenderer.transform;
|
|
_baseLocalPosition = transform.localPosition;
|
|
_baseLocalRotation = transform.localRotation;
|
|
_baseLocalScale = transform.localScale;
|
|
}
|
|
|
|
public IEnumerator Show(float height, int wind, float duration = 0.35f)
|
|
{
|
|
EnsureRig();
|
|
if (_kiteRenderer == null || _driver == null) yield break;
|
|
height = Mathf.Clamp(height, 0f, KiteMaxHeight);
|
|
if (IsShowing)
|
|
{
|
|
if (_driver.IsRunning)
|
|
{
|
|
SetState(height, wind, duration);
|
|
}
|
|
else
|
|
{
|
|
_kiteRenderer.transform.localPosition = Vector3.zero;
|
|
_kiteRenderer.transform.localRotation = Quaternion.identity;
|
|
_kiteRenderer.SetAlpha(KiteRigDriver.Alpha(height));
|
|
_driver.Wake(height, wind);
|
|
}
|
|
yield break;
|
|
}
|
|
|
|
KillTweens();
|
|
_cameraNode.gameObject.SetActive(true);
|
|
_baseRenderer.gameObject.SetActive(true);
|
|
|
|
if (!_kiteSpriteLoaded)
|
|
{
|
|
var loaded = false;
|
|
yield return Day2SleepSpriteLoader.LoadInto(
|
|
_kiteRenderer,
|
|
KiteSpritePicName,
|
|
value => loaded = value);
|
|
_kiteSpriteLoaded = loaded;
|
|
EnsureFocusMaterial();
|
|
if (_focusMaterial != null)
|
|
_kiteRenderer.material = _focusMaterial;
|
|
}
|
|
|
|
if (!_baseSpriteLoaded)
|
|
{
|
|
var loaded = false;
|
|
yield return Day2SleepSpriteLoader.LoadInto(
|
|
_baseRenderer,
|
|
KiteBasePicName,
|
|
value => loaded = value);
|
|
_baseSpriteLoaded = loaded;
|
|
}
|
|
|
|
if (!_kiteSpriteLoaded || !_baseSpriteLoaded)
|
|
{
|
|
ClearImmediate();
|
|
yield break;
|
|
}
|
|
|
|
_mask.sprite = _baseRenderer.sprite;
|
|
_mask.frontSortingLayerID = _kiteRenderer.sortingLayerID;
|
|
_mask.backSortingLayerID = _kiteRenderer.sortingLayerID;
|
|
_mask.frontSortingOrder = _referenceRenderer.sortingOrder + 2;
|
|
_mask.backSortingOrder = _referenceRenderer.sortingOrder;
|
|
_mask.enabled = true;
|
|
_driver.Wake(height, wind);
|
|
_baseRenderer.SetAlpha(1f);
|
|
_kiteRenderer.SetAlpha(KiteRigDriver.Alpha(height));
|
|
}
|
|
|
|
public void SetState(float height, int wind, float duration = 0.9f)
|
|
{
|
|
if (!IsShowing || _driver == null) return;
|
|
_driver.SetHeight(Mathf.Clamp(height, 0f, KiteMaxHeight), duration);
|
|
_driver.SetWind(wind);
|
|
}
|
|
|
|
public void SetWind(int wind, float turbulence = -1f)
|
|
{
|
|
if (!IsShowing || _driver == null) return;
|
|
var value = turbulence >= 0f ? Mathf.Clamp01(turbulence) : 0f;
|
|
_driver.SetWind(wind, value);
|
|
}
|
|
|
|
public void PlayAccent(string profile)
|
|
{
|
|
if (!IsShowing || _kiteRenderer == null) return;
|
|
_accentSequence?.Kill();
|
|
_accentSequence = null;
|
|
var transform = _kiteRenderer.transform;
|
|
var sequence = DOTween.Sequence();
|
|
|
|
switch (NormalizeProfile(profile))
|
|
{
|
|
case "lift":
|
|
sequence.Append(transform.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));
|
|
sequence.Join(transform.DOLocalRotate(new Vector3(2f, -1.5f, -2f), 0.62f)
|
|
.SetEase(Ease.InOutSine));
|
|
break;
|
|
case "sink":
|
|
sequence.Append(transform.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));
|
|
sequence.Join(transform.DOLocalRotate(new Vector3(-2f, 1.5f, 2f), 0.68f)
|
|
.SetEase(Ease.InOutSine));
|
|
break;
|
|
case "taut":
|
|
sequence.Append(transform.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));
|
|
sequence.Join(transform.DOLocalRotate(new Vector3(0f, -2f, -3f), 0.62f)
|
|
.SetEase(Ease.OutSine));
|
|
sequence.AppendInterval(0.32f);
|
|
break;
|
|
case "drop":
|
|
sequence.Append(transform.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));
|
|
sequence.Join(transform.DOLocalRotate(new Vector3(-2.5f, 1f, 3f), 0.86f)
|
|
.SetEase(Ease.InOutSine));
|
|
break;
|
|
case "wrench":
|
|
sequence.Append(transform.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));
|
|
sequence.Join(transform.DOLocalRotate(new Vector3(1.5f, -3f, -6f), 0.92f)
|
|
.SetEase(Ease.InOutSine));
|
|
sequence.AppendInterval(0.42f);
|
|
break;
|
|
case "flutter":
|
|
var side = Random.value < 0.5f ? -1f : 1f;
|
|
sequence.Append(transform.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));
|
|
sequence.Join(transform.DOLocalRotate(
|
|
new Vector3(1.5f, -2.5f * side, -3f * side), 0.9f)
|
|
.SetEase(Ease.InOutSine));
|
|
sequence.AppendInterval(0.22f);
|
|
break;
|
|
default:
|
|
sequence.Kill();
|
|
return;
|
|
}
|
|
|
|
sequence.Append(transform.DOLocalMove(Vector3.zero, 0.82f).SetEase(Ease.InOutSine));
|
|
sequence.Join(transform.DOLocalRotate(Vector3.zero, 0.82f).SetEase(Ease.InOutSine));
|
|
_accentSequence = sequence;
|
|
}
|
|
|
|
public IEnumerator FallOutOfFrame(float duration = 1.1f)
|
|
{
|
|
if (!IsShowing || _kiteRenderer == null || _driver == null) yield break;
|
|
|
|
_accentSequence?.Kill();
|
|
_accentSequence = null;
|
|
_driver.ResetAndSleep();
|
|
|
|
var transform = _kiteRenderer.transform;
|
|
float frameHeight = _baseRenderer != null && _baseRenderer.sprite != null
|
|
? _baseRenderer.sprite.bounds.size.y
|
|
: 6f;
|
|
float fallDistance = Mathf.Max(frameHeight * 1.1f, 3f);
|
|
Vector3 target = transform.localPosition + Vector3.down * fallDistance;
|
|
float fallDuration = Mathf.Max(0.05f, duration);
|
|
|
|
var sequence = DOTween.Sequence();
|
|
sequence.Append(transform.DOLocalMove(target, fallDuration)
|
|
.SetEase(Ease.InQuad));
|
|
sequence.Join(transform.DOLocalRotate(new Vector3(-4f, 2f, 8f), fallDuration)
|
|
.SetEase(Ease.InSine));
|
|
_accentSequence = sequence;
|
|
|
|
yield return sequence.WaitForCompletion();
|
|
if (_accentSequence != sequence) yield break;
|
|
|
|
_kiteRenderer.SetAlpha(0f);
|
|
_accentSequence = null;
|
|
}
|
|
|
|
public IEnumerator Hide(float duration = 1f)
|
|
{
|
|
ClearImmediate();
|
|
yield break;
|
|
}
|
|
|
|
public void ClearImmediate()
|
|
{
|
|
KillTweens();
|
|
if (_kiteRenderer != null)
|
|
{
|
|
_kiteRenderer.SetAlpha(0f);
|
|
_kiteRenderer.transform.localPosition = Vector3.zero;
|
|
_kiteRenderer.transform.localRotation = Quaternion.identity;
|
|
}
|
|
if (_baseRenderer != null)
|
|
{
|
|
_baseRenderer.SetAlpha(0f);
|
|
_baseRenderer.gameObject.SetActive(false);
|
|
}
|
|
if (_cameraNode != null)
|
|
_cameraNode.gameObject.SetActive(false);
|
|
if (_mask != null)
|
|
_mask.enabled = false;
|
|
}
|
|
|
|
private void EnsureRig()
|
|
{
|
|
if (_rootPivot != null || _referenceRenderer == null) return;
|
|
var parent = _referenceRenderer.transform.parent;
|
|
var cameraObject = new GameObject("Kite Camera");
|
|
cameraObject.transform.SetParent(parent);
|
|
cameraObject.transform.localPosition = _baseLocalPosition;
|
|
cameraObject.transform.localRotation = Quaternion.identity;
|
|
cameraObject.transform.localScale = Vector3.one;
|
|
|
|
var rootObject = new GameObject("Kite Rig");
|
|
rootObject.transform.SetParent(cameraObject.transform, false);
|
|
rootObject.transform.localRotation = _baseLocalRotation;
|
|
rootObject.transform.localScale = _baseLocalScale;
|
|
var swayObject = new GameObject("Kite Sway");
|
|
swayObject.transform.SetParent(rootObject.transform, false);
|
|
var accentObject = new GameObject("Kite Accent");
|
|
accentObject.transform.SetParent(swayObject.transform, false);
|
|
|
|
var baseObject = new GameObject("Kite Base");
|
|
baseObject.transform.SetParent(parent);
|
|
baseObject.transform.localPosition = _baseLocalPosition;
|
|
baseObject.transform.localRotation = _baseLocalRotation;
|
|
baseObject.transform.localScale = _baseLocalScale;
|
|
var maskObject = new GameObject("Kite Mask");
|
|
maskObject.transform.SetParent(parent);
|
|
maskObject.transform.localPosition = _baseLocalPosition;
|
|
maskObject.transform.localRotation = _baseLocalRotation;
|
|
maskObject.transform.localScale = _baseLocalScale;
|
|
|
|
_cameraNode = cameraObject.transform;
|
|
_rootPivot = rootObject.transform;
|
|
_swayPivot = swayObject.transform;
|
|
_kiteRenderer = accentObject.AddComponent<SpriteRenderer>();
|
|
CopyRendererSettings(_kiteRenderer, 1, SpriteMaskInteraction.VisibleInsideMask);
|
|
_baseRenderer = baseObject.AddComponent<SpriteRenderer>();
|
|
CopyRendererSettings(_baseRenderer, 0, SpriteMaskInteraction.None);
|
|
_mask = maskObject.AddComponent<SpriteMask>();
|
|
_mask.alphaCutoff = 0.01f;
|
|
_mask.isCustomRangeActive = true;
|
|
_mask.enabled = false;
|
|
|
|
EnsureFocusMaterial();
|
|
_driver = cameraObject.AddComponent<KiteRigDriver>();
|
|
_driver.Configure(
|
|
_cameraNode,
|
|
_rootPivot,
|
|
_swayPivot,
|
|
_kiteRenderer,
|
|
_baseLocalPosition,
|
|
_baseLocalRotation,
|
|
_baseLocalScale,
|
|
_windSettings,
|
|
_cameraSettings,
|
|
_focusSettings,
|
|
_focusMaterial);
|
|
_driver.enabled = false;
|
|
cameraObject.SetActive(false);
|
|
baseObject.SetActive(false);
|
|
_kiteRenderer.SetAlpha(0f);
|
|
_baseRenderer.SetAlpha(0f);
|
|
}
|
|
|
|
private void CopyRendererSettings(
|
|
SpriteRenderer target,
|
|
int sortingOrderOffset,
|
|
SpriteMaskInteraction maskInteraction)
|
|
{
|
|
target.sharedMaterial = _referenceRenderer.sharedMaterial;
|
|
target.sortingLayerID = _referenceRenderer.sortingLayerID;
|
|
target.sortingOrder = _referenceRenderer.sortingOrder + sortingOrderOffset;
|
|
target.drawMode = _referenceRenderer.drawMode;
|
|
target.size = _referenceRenderer.size;
|
|
target.flipX = _referenceRenderer.flipX;
|
|
target.flipY = _referenceRenderer.flipY;
|
|
target.maskInteraction = maskInteraction;
|
|
}
|
|
|
|
private void EnsureFocusMaterial()
|
|
{
|
|
if (_focusMaterial != null) return;
|
|
var shader = Shader.Find("AIBIS/KiteFocusSprite");
|
|
if (shader == null)
|
|
{
|
|
Debug.LogWarning("[KitePresentation] 找不到 AIBIS/KiteFocusSprite,风筝虚焦不可用。");
|
|
return;
|
|
}
|
|
_focusMaterial = new Material(shader) { name = "KiteFocusSprite (Runtime)" };
|
|
}
|
|
|
|
private void KillTweens()
|
|
{
|
|
_accentSequence?.Kill();
|
|
_accentSequence = null;
|
|
_driver?.ResetAndSleep();
|
|
}
|
|
|
|
private static string NormalizeProfile(string profile)
|
|
{
|
|
return string.IsNullOrEmpty(profile) ? string.Empty : profile.Trim().ToLowerInvariant();
|
|
}
|
|
}
|
|
}
|