Merge branch '7.21春优化' into 'develop'
7.21春优化 See merge request aibis-dream/aibis-dream!762
This commit is contained in:
@@ -8,12 +8,18 @@ namespace AibisDream.SystemEditor.Tests
|
||||
public sealed class PresentationSnapshotContractTests
|
||||
{
|
||||
[Test]
|
||||
public void PlayToolProvider_UsesStableIdAndRestoreBeforeScreen()
|
||||
public void PresentationProviders_UseStableIdsAndRestoreInDependencyOrder()
|
||||
{
|
||||
var showcase = new ShowcaseSnapshotProvider();
|
||||
var day2Sleep = new Day2SleepPresentationSnapshotProvider();
|
||||
var playTool = new PlayToolSnapshotProvider();
|
||||
var screen = new ScreenSnapshotProvider();
|
||||
|
||||
Assert.That(showcase.SaveId, Is.EqualTo("showcase"));
|
||||
Assert.That(day2Sleep.SaveId, Is.EqualTo("day2SleepPresentation"));
|
||||
Assert.That(playTool.SaveId, Is.EqualTo("playTool"));
|
||||
Assert.That(showcase.RestoreOrder, Is.LessThan(day2Sleep.RestoreOrder));
|
||||
Assert.That(day2Sleep.RestoreOrder, Is.LessThan(playTool.RestoreOrder));
|
||||
Assert.That(playTool.RestoreOrder, Is.LessThan(screen.RestoreOrder));
|
||||
}
|
||||
|
||||
@@ -41,6 +47,42 @@ namespace AibisDream.SystemEditor.Tests
|
||||
Assert.That(restored.schemaVersion, Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ShowcaseAndDay2SleepSections_RoundTripSemanticState()
|
||||
{
|
||||
var source = new SaveSnapshot();
|
||||
source.sections[SnapshotProviderIds.Showcase] = new ShowcaseSnapshotDto
|
||||
{
|
||||
displayMode = ShowcaseDisplayMode.Large.ToString(),
|
||||
picName = "D2S星图",
|
||||
isBackgroundVisible = true
|
||||
};
|
||||
source.sections[SnapshotProviderIds.Day2SleepPresentation] =
|
||||
new Day2SleepPresentationSnapshotDto
|
||||
{
|
||||
mode = Day2SleepPresentationMode.LargeEffects.ToString(),
|
||||
largeBlurAmount = 0.72f,
|
||||
largeBlurSize = 0.015f,
|
||||
largeScaleMultiplier = 1.1f,
|
||||
largeAlpha = 0.4f
|
||||
};
|
||||
|
||||
var json = JsonConvert.SerializeObject(source);
|
||||
var restored = JsonConvert.DeserializeObject<SaveSnapshot>(json);
|
||||
var showcase = ((JObject)restored.sections[SnapshotProviderIds.Showcase])
|
||||
.ToObject<ShowcaseSnapshotDto>();
|
||||
var day2Sleep = ((JObject)restored.sections[SnapshotProviderIds.Day2SleepPresentation])
|
||||
.ToObject<Day2SleepPresentationSnapshotDto>();
|
||||
|
||||
Assert.That(showcase.displayMode, Is.EqualTo("Large"));
|
||||
Assert.That(showcase.picName, Is.EqualTo("D2S星图"));
|
||||
Assert.That(showcase.isBackgroundVisible, Is.True);
|
||||
Assert.That(day2Sleep.mode, Is.EqualTo("LargeEffects"));
|
||||
Assert.That(day2Sleep.largeBlurAmount, Is.EqualTo(0.72f));
|
||||
Assert.That(day2Sleep.largeScaleMultiplier, Is.EqualTo(1.1f));
|
||||
Assert.That(day2Sleep.largeAlpha, Is.EqualTo(0.4f));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OldSnapshotWithoutPresentationSections_RemainsCompatible()
|
||||
{
|
||||
@@ -48,6 +90,8 @@ namespace AibisDream.SystemEditor.Tests
|
||||
"{\"schemaVersion\":1,\"sections\":{}}");
|
||||
|
||||
Assert.That(restored.schemaVersion, Is.EqualTo(SaveSnapshotSchema.CurrentVersion));
|
||||
Assert.That(restored.sections.ContainsKey(SnapshotProviderIds.Showcase), Is.False);
|
||||
Assert.That(restored.sections.ContainsKey(SnapshotProviderIds.Day2SleepPresentation), Is.False);
|
||||
Assert.That(restored.sections.ContainsKey(SnapshotProviderIds.PlayTool), Is.False);
|
||||
}
|
||||
}
|
||||
|
||||
+17
-1
@@ -1188,6 +1188,7 @@ GameObject:
|
||||
m_Component:
|
||||
- component: {fileID: 861686055}
|
||||
- component: {fileID: 861686057}
|
||||
- component: {fileID: 861686058}
|
||||
m_Layer: 0
|
||||
m_Name: Timeline Kit
|
||||
m_TagString: Untagged
|
||||
@@ -1232,7 +1233,22 @@ MonoBehaviour:
|
||||
_spriteRenderer: {fileID: 218986884}
|
||||
_backgroundSpriteRenderer: {fileID: 969796818}
|
||||
_largeSpriteRenderer: {fileID: 762853994}
|
||||
_kiteFocusMaterial: {fileID: 2100000, guid: 78265ded39f017d45a2afc117effab89, type: 2}
|
||||
--- !u!114 &861686058
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 861686054}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 9f771105a4b347ffbe2dc7da5acba320, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
_showcase: {fileID: 861686057}
|
||||
_smallRenderer: {fileID: 218986884}
|
||||
_largeRenderer: {fileID: 762853994}
|
||||
_focusMaterial: {fileID: 2100000, guid: 78265ded39f017d45a2afc117effab89, type: 2}
|
||||
_kiteWindSettings:
|
||||
calmFloatAmp: 0.53
|
||||
calmFloatFreq: 0.065
|
||||
|
||||
@@ -415,77 +415,6 @@ namespace AibisDream
|
||||
yield return SpriteShowcase.Instance.ShowObj(picName, duration);
|
||||
}
|
||||
|
||||
[YarnCommand("show_sprite_motion")]
|
||||
public static IEnumerator ShowSpriteMotion(string picName, string motionProfile, float duration = 0.35f)
|
||||
{
|
||||
var showcase = SpriteShowcase.Instance;
|
||||
if (showcase == null) yield break;
|
||||
yield return showcase.ShowObjWithMotion(picName, motionProfile, duration);
|
||||
}
|
||||
|
||||
[YarnCommand("sprite_motion")]
|
||||
public static IEnumerator SpriteMotion(string motionProfile, float leadDuration = 0.18f)
|
||||
{
|
||||
var showcase = SpriteShowcase.Instance;
|
||||
if (showcase == null) yield break;
|
||||
|
||||
showcase.PlayCurrentMotion(motionProfile);
|
||||
if (leadDuration > 0f)
|
||||
{
|
||||
yield return new WaitForSeconds(leadDuration);
|
||||
}
|
||||
}
|
||||
|
||||
[YarnCommand("kite_show")]
|
||||
public static IEnumerator KiteShow(float height = 0, int wind = 0, float duration = 0.35f)
|
||||
{
|
||||
var showcase = SpriteShowcase.Instance;
|
||||
if (showcase == null) yield break;
|
||||
yield return showcase.ShowKite(height, wind, duration);
|
||||
}
|
||||
|
||||
[YarnCommand("kite_state")]
|
||||
public static IEnumerator KiteState(float height, int wind, float duration = 0.9f, float leadDuration = 0.35f)
|
||||
{
|
||||
var showcase = SpriteShowcase.Instance;
|
||||
if (showcase == null) yield break;
|
||||
|
||||
showcase.SetKiteState(height, wind, duration);
|
||||
if (leadDuration > 0f)
|
||||
{
|
||||
yield return new WaitForSeconds(Mathf.Min(leadDuration, duration));
|
||||
}
|
||||
}
|
||||
|
||||
[YarnCommand("kite_wind")]
|
||||
public static void KiteWind(int wind, float turbulence = -1f)
|
||||
{
|
||||
var showcase = SpriteShowcase.Instance;
|
||||
if (showcase == null) return;
|
||||
showcase.SetKiteWind(wind, turbulence);
|
||||
}
|
||||
|
||||
[YarnCommand("kite_accent")]
|
||||
public static IEnumerator KiteAccent(string profile, float leadDuration = 0.18f)
|
||||
{
|
||||
var showcase = SpriteShowcase.Instance;
|
||||
if (showcase == null) yield break;
|
||||
|
||||
showcase.PlayKiteAccent(profile);
|
||||
if (leadDuration > 0f)
|
||||
{
|
||||
yield return new WaitForSeconds(leadDuration);
|
||||
}
|
||||
}
|
||||
|
||||
[YarnCommand("kite_hide")]
|
||||
public static IEnumerator KiteHide(float duration = 1)
|
||||
{
|
||||
var showcase = SpriteShowcase.Instance;
|
||||
if (showcase == null) yield break;
|
||||
yield return showcase.HideKite(duration);
|
||||
}
|
||||
|
||||
[YarnCommand("hide_sprite")]
|
||||
public static IEnumerator HideSprite(float duration = 1)
|
||||
{
|
||||
@@ -522,47 +451,6 @@ namespace AibisDream
|
||||
yield return SpriteShowcase.Instance.ShowLargeObj(picName, duration);
|
||||
}
|
||||
|
||||
[YarnCommand("show_large_sprite_animation")]
|
||||
public static IEnumerator ShowLargeSpriteAnimation(
|
||||
string picNamePrefix,
|
||||
int frameCount,
|
||||
float secondsPerFrame = 0.2f,
|
||||
float duration = 1f)
|
||||
{
|
||||
yield return SpriteShowcase.Instance.ShowLargeSpriteAnimation(
|
||||
picNamePrefix,
|
||||
frameCount,
|
||||
secondsPerFrame,
|
||||
duration);
|
||||
}
|
||||
|
||||
[YarnCommand("set_large_sprite_blur")]
|
||||
public static void SetLargeSpriteBlur(float amount, float blurSize = 0.012f)
|
||||
{
|
||||
SpriteShowcase.Instance.SetLargeSpriteBlur(amount, blurSize);
|
||||
}
|
||||
|
||||
[YarnCommand("tween_large_sprite_blur")]
|
||||
public static IEnumerator TweenLargeSpriteBlur(
|
||||
float targetAmount,
|
||||
float duration,
|
||||
float blurSize = 0.012f)
|
||||
{
|
||||
yield return SpriteShowcase.Instance.TweenLargeSpriteBlur(targetAmount, duration, blurSize);
|
||||
}
|
||||
|
||||
[YarnCommand("tween_large_sprite_blur_async")]
|
||||
public static void TweenLargeSpriteBlurAsync(
|
||||
float targetAmount,
|
||||
float duration,
|
||||
float blurSize = 0.012f)
|
||||
{
|
||||
if (DialogController.Instance == null) return;
|
||||
|
||||
DialogController.Instance.StartCoroutine(
|
||||
SpriteShowcase.Instance.TweenLargeSpriteBlur(targetAmount, duration, blurSize));
|
||||
}
|
||||
|
||||
[YarnCommand("hide_large_sprite")]
|
||||
public static IEnumerator HideLargeSprite(float duration = 1)
|
||||
{
|
||||
|
||||
@@ -708,6 +708,7 @@ namespace AibisDream
|
||||
{
|
||||
ScreenEffectManager.Instance?.ResetSaturation();
|
||||
UIManager.Instance.GetPanel<PlayToolPanel>()?.ClearPresentationEffects();
|
||||
Day2SleepPresentationController.Instance?.ClearPresentationEffects();
|
||||
SpriteShowcase.Instance?.ClearPresentationEffects();
|
||||
CameraKit.Instance?.ResetCamera();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
using System.Collections;
|
||||
|
||||
namespace AibisDream.SaveSystem
|
||||
{
|
||||
/// <summary>sections["day2SleepPresentation"]:D2 Sleep 动态表现语义终态。</summary>
|
||||
public sealed class Day2SleepPresentationSnapshotProvider : IAsyncSnapshotProvider
|
||||
{
|
||||
public string SaveId => SnapshotProviderIds.Day2SleepPresentation;
|
||||
public int RestoreOrder => 70;
|
||||
|
||||
public object Capture()
|
||||
{
|
||||
var controller = Day2SleepPresentationController.Instance;
|
||||
return controller != null && controller.HasPersistableState
|
||||
? controller.CaptureSnapshot()
|
||||
: null;
|
||||
}
|
||||
|
||||
public IEnumerator RestoreAsync(object dto, SnapshotRestoreContext context)
|
||||
{
|
||||
if (dto is not Day2SleepPresentationSnapshotDto snapshot)
|
||||
{
|
||||
context.Warn("[Day2SleepPresentationSnapshotProvider] DTO 类型不匹配,跳过还原。");
|
||||
yield break;
|
||||
}
|
||||
var controller = Day2SleepPresentationController.Instance;
|
||||
if (controller == null)
|
||||
{
|
||||
context.Warn("[Day2SleepPresentationSnapshotProvider] 控制器未初始化,跳过还原。");
|
||||
yield break;
|
||||
}
|
||||
yield return controller.RestoreSnapshotAsync(snapshot, context.Warn);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8f80937eaaba42da9baa4a2d8e73ff57
|
||||
@@ -3,11 +3,11 @@ using AibisDream.UI;
|
||||
|
||||
namespace AibisDream.SaveSystem
|
||||
{
|
||||
/// <summary>sections["playTool"]:物品图与全屏图。RestoreOrder=70。</summary>
|
||||
/// <summary>sections["playTool"]:物品图与全屏图。RestoreOrder=71。</summary>
|
||||
public sealed class PlayToolSnapshotProvider : IAsyncSnapshotProvider
|
||||
{
|
||||
public string SaveId => SnapshotProviderIds.PlayTool;
|
||||
public int RestoreOrder => 70;
|
||||
public int RestoreOrder => 71;
|
||||
|
||||
public object Capture()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
using System.Collections;
|
||||
|
||||
namespace AibisDream.SaveSystem
|
||||
{
|
||||
/// <summary>sections["showcase"]:梦境通用静态图片;先于场景专属表现恢复。</summary>
|
||||
public sealed class ShowcaseSnapshotProvider : IAsyncSnapshotProvider
|
||||
{
|
||||
public string SaveId => SnapshotProviderIds.Showcase;
|
||||
public int RestoreOrder => 69;
|
||||
|
||||
public object Capture()
|
||||
{
|
||||
return SpriteShowcase.Instance != null
|
||||
? SpriteShowcase.Instance.CaptureSnapshot()
|
||||
: null;
|
||||
}
|
||||
|
||||
public IEnumerator RestoreAsync(object dto, SnapshotRestoreContext context)
|
||||
{
|
||||
if (dto is not ShowcaseSnapshotDto snapshot)
|
||||
{
|
||||
context.Warn("[ShowcaseSnapshotProvider] DTO 类型不匹配,跳过还原。");
|
||||
yield break;
|
||||
}
|
||||
var showcase = SpriteShowcase.Instance;
|
||||
if (showcase == null)
|
||||
{
|
||||
context.Warn("[ShowcaseSnapshotProvider] SpriteShowcase 未初始化,跳过还原。");
|
||||
yield break;
|
||||
}
|
||||
yield return showcase.RestoreSnapshotAsync(snapshot, context.Warn);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 83cccdfaca2a42a89ed515c16c6adfad
|
||||
@@ -1,4 +1,4 @@
|
||||
# SaveSystem 存档快照层
|
||||
# SaveSystem 存档快照层
|
||||
|
||||
> 当前代码包含 P1 快照层、P2 槽位/落盘基础版、P3 可存点判定基础版、P4 读档编排基础版。设计文档见 [`Docs/存档系统设计方案.md`](../../../Docs/存档系统设计方案.md)(**§4.1 / D6** 描述读档编排模型)。
|
||||
|
||||
@@ -101,7 +101,8 @@ SaveSystem/
|
||||
├── FixSnapshotProvider.cs order 65
|
||||
├── FixPanelSnapshotProvider.cs order 66
|
||||
├── ShowcaseSnapshotProvider.cs order 69
|
||||
├── PlayToolSnapshotProvider.cs order 70
|
||||
├── Day2SleepPresentationSnapshotProvider.cs order 70
|
||||
├── PlayToolSnapshotProvider.cs order 71
|
||||
└── ScreenSnapshotProvider.cs order 80
|
||||
```
|
||||
|
||||
@@ -135,7 +136,7 @@ Phase 0 Yarn 变量(sync)
|
||||
↓
|
||||
Phase 1 场景加载(Barrier:LoadSceneAsync)← 框架直管,不走 Provider
|
||||
↓
|
||||
Phase 2 env / actor / audio / timeline / fix / showcase / playTool / screen(Provider 按 RestoreOrder)
|
||||
Phase 2 env / actor / audio / timeline / fix / showcase / day2SleepPresentation / playTool / screen(Provider 按 RestoreOrder)
|
||||
└─ timeline:`Stopped` = untouched(从未 Evaluate),读档仅还原 `isActive`,不 Reset/Evaluate
|
||||
↓
|
||||
Phase 2′ 可选 Barrier(如 Timeline Addressable 须显式等待)
|
||||
@@ -202,7 +203,7 @@ Fix 场景各 State 的 `Enter()` 通常包含相机过渡、Timeline 播放、F
|
||||
| `bodyModule` | `BodyModuleSnapshotDto` | 67 | 插线模块物理态 |
|
||||
| `eye` | `EyeSnapshotDto` | 68 | Eye 叙事阶段 |
|
||||
|
||||
**还原顺序**:punchTape → fix(cue)→ fixPanel → bodyModule → eye → showcase → playTool → screen。`fixPanel` 须在 `fix` 之后,以覆盖 Cue `EnterImmediate` 中的 `ResetPlug`。
|
||||
**还原顺序**:punchTape → fix(cue)→ fixPanel → bodyModule → eye → showcase → day2SleepPresentation → playTool → screen。`fixPanel` 须在 `fix` 之后,以覆盖 Cue `EnterImmediate` 中的 `ResetPlug`;D2 动态表现须在通用 Showcase 图片之后恢复,才能叠加虚焦、缩放或场景专属动画。
|
||||
|
||||
**维修场景门控**(`FixSceneSnapshotHelper`):上述 section 仅在 `FixSystemCenter.Instance != null` 时 Capture/Restore。
|
||||
|
||||
|
||||
@@ -190,6 +190,53 @@ namespace AibisDream.SaveSystem
|
||||
public string fullScreenPicName;
|
||||
}
|
||||
|
||||
public enum ShowcaseDisplayMode
|
||||
{
|
||||
None,
|
||||
Small,
|
||||
Large
|
||||
}
|
||||
|
||||
/// <summary>sections["showcase"]:梦境通用静态图片与背景终态。</summary>
|
||||
[Serializable]
|
||||
public class ShowcaseSnapshotDto
|
||||
{
|
||||
public string displayMode;
|
||||
public string picName;
|
||||
public bool isBackgroundVisible;
|
||||
}
|
||||
|
||||
public enum Day2SleepPresentationMode
|
||||
{
|
||||
None,
|
||||
SmallMotion,
|
||||
Kite,
|
||||
LargeEffects
|
||||
}
|
||||
|
||||
/// <summary>sections["day2SleepPresentation"]:D2 Sleep 动态表现语义终态。</summary>
|
||||
[Serializable]
|
||||
public class Day2SleepPresentationSnapshotDto
|
||||
{
|
||||
public string mode;
|
||||
|
||||
public string picName;
|
||||
public string motionProfile;
|
||||
|
||||
public float kiteHeight;
|
||||
public int kiteWind;
|
||||
public float kiteTurbulence;
|
||||
|
||||
public bool largeAnimationActive;
|
||||
public string largeAnimationPrefix;
|
||||
public int largeAnimationFrameCount;
|
||||
public float largeAnimationSecondsPerFrame;
|
||||
public float largeBlurAmount;
|
||||
public float largeBlurSize;
|
||||
public float largeScaleMultiplier = 1f;
|
||||
public float largeAlpha = 1f;
|
||||
}
|
||||
|
||||
/// <summary>sections["subway"]:地铁场景宏观状态(SubwayCenter / OutSideState)。</summary>
|
||||
[Serializable]
|
||||
public class SubwaySnapshotDto
|
||||
|
||||
@@ -16,6 +16,8 @@ namespace AibisDream.SaveSystem
|
||||
public const string Timeline = "timeline";
|
||||
public const string Screen = "screen";
|
||||
public const string PlayTool = "playTool";
|
||||
public const string Showcase = "showcase";
|
||||
public const string Day2SleepPresentation = "day2SleepPresentation";
|
||||
public const string Subway = "subway";
|
||||
public const string PunchTape = "punchTape";
|
||||
public const string Fix = "fix";
|
||||
@@ -30,7 +32,7 @@ namespace AibisDream.SaveSystem
|
||||
/// </summary>
|
||||
public static readonly string[] RequiredForCapture =
|
||||
{
|
||||
Environment, Actor, Audio, Timeline, Subway, Fix, PlayTool, Screen
|
||||
Environment, Actor, Audio, Timeline, Subway, Fix, Showcase, PlayTool, Screen
|
||||
};
|
||||
}
|
||||
|
||||
@@ -60,6 +62,8 @@ namespace AibisDream.SaveSystem
|
||||
Register(new FixPanelSnapshotProvider());
|
||||
Register(new BodyModuleSnapshotProvider());
|
||||
Register(new EyeSnapshotProvider());
|
||||
Register(new ShowcaseSnapshotProvider());
|
||||
Register(new Day2SleepPresentationSnapshotProvider());
|
||||
Register(new PlayToolSnapshotProvider());
|
||||
Register(new ScreenSnapshotProvider());
|
||||
}
|
||||
|
||||
@@ -257,6 +257,8 @@ namespace AibisDream.SaveSystem
|
||||
SnapshotProviderIds.FixPanel => jobj.ToObject<FixPanelSnapshotDto>(),
|
||||
SnapshotProviderIds.BodyModule => jobj.ToObject<BodyModuleSnapshotDto>(),
|
||||
SnapshotProviderIds.Eye => jobj.ToObject<EyeSnapshotDto>(),
|
||||
SnapshotProviderIds.Showcase => jobj.ToObject<ShowcaseSnapshotDto>(),
|
||||
SnapshotProviderIds.Day2SleepPresentation => jobj.ToObject<Day2SleepPresentationSnapshotDto>(),
|
||||
SnapshotProviderIds.PlayTool => jobj.ToObject<PlayToolSnapshotDto>(),
|
||||
SnapshotProviderIds.Screen => jobj.ToObject<ScreenSnapshotDto>(),
|
||||
_ => jobj
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cb05144af0734df89dd21ed3c9db9f13
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,269 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using AibisDream.Kit;
|
||||
using AibisDream.SaveSystem;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
/// <summary>D2 Sleep 专属动态表现门面;普通静态图片仍由 SpriteShowcase 管理。</summary>
|
||||
public sealed class Day2SleepPresentationController : Singleton<Day2SleepPresentationController>
|
||||
{
|
||||
[Header("通用展示引用")]
|
||||
[SerializeField] private SpriteShowcase _showcase;
|
||||
[SerializeField] private SpriteRenderer _smallRenderer;
|
||||
[SerializeField] private SpriteRenderer _largeRenderer;
|
||||
|
||||
[Header("风筝 / 对焦材质")]
|
||||
[SerializeField] private Material _focusMaterial;
|
||||
[SerializeField] private KiteWindSettings _kiteWindSettings = new();
|
||||
[SerializeField] private KiteCameraSettings _kiteCameraSettings = new();
|
||||
[SerializeField] private KiteFocusSettings _kiteFocusSettings = new();
|
||||
|
||||
private ReelMotionPresenter _reel;
|
||||
private LargeSpriteEffectsPresenter _largeEffects;
|
||||
private KitePresentation _kite;
|
||||
private Day2SleepPresentationMode _mode;
|
||||
|
||||
public bool HasPersistableState => _mode != Day2SleepPresentationMode.None;
|
||||
|
||||
public override void OnSingletonInit()
|
||||
{
|
||||
_showcase ??= SpriteShowcase.Instance;
|
||||
_reel = new ReelMotionPresenter(_smallRenderer);
|
||||
_largeEffects = new LargeSpriteEffectsPresenter(this, _largeRenderer, _focusMaterial);
|
||||
_kite = new KitePresentation(
|
||||
_smallRenderer,
|
||||
_focusMaterial,
|
||||
_kiteWindSettings,
|
||||
_kiteCameraSettings,
|
||||
_kiteFocusSettings);
|
||||
if (_showcase != null)
|
||||
_showcase.PresentationChanging += HandleGenericPresentationChanging;
|
||||
ClearPresentationEffects();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
ClearPresentationEffects();
|
||||
}
|
||||
|
||||
public override void OnSingletonDestroy()
|
||||
{
|
||||
if (_showcase != null)
|
||||
_showcase.PresentationChanging -= HandleGenericPresentationChanging;
|
||||
ClearPresentationEffects();
|
||||
base.OnSingletonDestroy();
|
||||
}
|
||||
|
||||
public IEnumerator ShowSpriteMotion(string picName, string motionProfile, float duration = 0.35f)
|
||||
{
|
||||
yield return PrepareExclusiveMode();
|
||||
yield return _reel.Show(picName, motionProfile);
|
||||
if (_reel.IsShowing)
|
||||
_mode = Day2SleepPresentationMode.SmallMotion;
|
||||
}
|
||||
|
||||
public void PlayCurrentMotion(string motionProfile)
|
||||
{
|
||||
if (_mode != Day2SleepPresentationMode.SmallMotion) return;
|
||||
_reel.PlayCurrentMotion(motionProfile);
|
||||
}
|
||||
|
||||
public IEnumerator ShowKite(float height, int wind, float duration = 0.35f)
|
||||
{
|
||||
if (_mode != Day2SleepPresentationMode.Kite)
|
||||
yield return PrepareExclusiveMode();
|
||||
yield return _kite.Show(height, wind, duration);
|
||||
if (_kite.IsShowing)
|
||||
_mode = Day2SleepPresentationMode.Kite;
|
||||
}
|
||||
|
||||
public void SetKiteState(float height, int wind, float duration = 0.9f)
|
||||
{
|
||||
_kite.SetState(height, wind, duration);
|
||||
}
|
||||
|
||||
public void SetKiteWind(int wind, float turbulence = -1f)
|
||||
{
|
||||
_kite.SetWind(wind, turbulence);
|
||||
}
|
||||
|
||||
public void PlayKiteAccent(string profile)
|
||||
{
|
||||
_kite.PlayAccent(profile);
|
||||
}
|
||||
|
||||
public IEnumerator HideKite(float duration = 1f)
|
||||
{
|
||||
yield return _kite.Hide(duration);
|
||||
if (_mode == Day2SleepPresentationMode.Kite)
|
||||
_mode = Day2SleepPresentationMode.None;
|
||||
}
|
||||
|
||||
public IEnumerator ShowLargeSpriteAnimation(
|
||||
string picNamePrefix,
|
||||
int frameCount,
|
||||
float secondsPerFrame = 0.2f,
|
||||
float duration = 1f)
|
||||
{
|
||||
yield return PrepareExclusiveMode();
|
||||
yield return _largeEffects.ShowAnimation(
|
||||
picNamePrefix,
|
||||
frameCount,
|
||||
secondsPerFrame,
|
||||
duration);
|
||||
if (_largeEffects.IsAnimationPlaying)
|
||||
_mode = Day2SleepPresentationMode.LargeEffects;
|
||||
}
|
||||
|
||||
public void SetLargeSpriteBlur(float amount, float blurSize = 0.012f)
|
||||
{
|
||||
_largeEffects.SetBlur(amount, blurSize);
|
||||
MarkLargeEffectsActive();
|
||||
}
|
||||
|
||||
public IEnumerator TweenLargeSpriteBlur(
|
||||
float targetAmount,
|
||||
float duration,
|
||||
float blurSize = 0.012f)
|
||||
{
|
||||
MarkLargeEffectsActive();
|
||||
yield return _largeEffects.TweenBlur(targetAmount, duration, blurSize);
|
||||
}
|
||||
|
||||
public IEnumerator AnimateLargePresentation(float scale, float alpha, float duration)
|
||||
{
|
||||
MarkLargeEffectsActive();
|
||||
yield return _largeEffects.AnimatePresentation(scale, alpha, duration);
|
||||
}
|
||||
|
||||
public void SetLargePresentationImmediate(float scale, float alpha)
|
||||
{
|
||||
_largeEffects.ApplyPresentationImmediate(scale, alpha);
|
||||
MarkLargeEffectsActive();
|
||||
}
|
||||
|
||||
public void ResetLargePresentationImmediate()
|
||||
{
|
||||
_largeEffects.ResetPresentationImmediate();
|
||||
}
|
||||
|
||||
public void StopLargePresentationAnimation()
|
||||
{
|
||||
_largeEffects.StopPresentationAnimation();
|
||||
}
|
||||
|
||||
public void HideLargePresentationImmediate()
|
||||
{
|
||||
_largeEffects.HidePresentationImmediate();
|
||||
if (_mode == Day2SleepPresentationMode.LargeEffects)
|
||||
_mode = Day2SleepPresentationMode.None;
|
||||
}
|
||||
|
||||
public void SetLargeSpriteImmediate(Sprite sprite)
|
||||
{
|
||||
_largeEffects.SetSpriteImmediate(sprite);
|
||||
}
|
||||
|
||||
public void ClearPresentationEffects()
|
||||
{
|
||||
_reel?.ClearImmediate();
|
||||
_kite?.ClearImmediate();
|
||||
_largeEffects?.ResetEffectsImmediate();
|
||||
_mode = Day2SleepPresentationMode.None;
|
||||
}
|
||||
|
||||
public Day2SleepPresentationSnapshotDto CaptureSnapshot()
|
||||
{
|
||||
var snapshot = new Day2SleepPresentationSnapshotDto
|
||||
{
|
||||
mode = _mode.ToString()
|
||||
};
|
||||
|
||||
switch (_mode)
|
||||
{
|
||||
case Day2SleepPresentationMode.SmallMotion:
|
||||
snapshot.picName = _reel.CurrentPicName;
|
||||
snapshot.motionProfile = _reel.CurrentMotionProfile;
|
||||
break;
|
||||
case Day2SleepPresentationMode.Kite:
|
||||
snapshot.kiteHeight = _kite.TargetHeight;
|
||||
snapshot.kiteWind = _kite.CurrentWind;
|
||||
snapshot.kiteTurbulence = _kite.Turbulence;
|
||||
break;
|
||||
case Day2SleepPresentationMode.LargeEffects:
|
||||
snapshot.largeAnimationActive = _largeEffects.IsAnimationPlaying;
|
||||
snapshot.largeAnimationPrefix = _largeEffects.AnimationPrefix;
|
||||
snapshot.largeAnimationFrameCount = _largeEffects.AnimationFrameCount;
|
||||
snapshot.largeAnimationSecondsPerFrame = _largeEffects.AnimationSecondsPerFrame;
|
||||
snapshot.largeBlurAmount = _largeEffects.TargetBlurAmount;
|
||||
snapshot.largeBlurSize = _largeEffects.TargetBlurSize;
|
||||
snapshot.largeScaleMultiplier = _largeEffects.TargetScaleMultiplier;
|
||||
snapshot.largeAlpha = _largeEffects.TargetAlpha;
|
||||
break;
|
||||
}
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
public IEnumerator RestoreSnapshotAsync(
|
||||
Day2SleepPresentationSnapshotDto snapshot,
|
||||
Action<string> warning = null)
|
||||
{
|
||||
ClearPresentationEffects();
|
||||
if (snapshot == null) yield break;
|
||||
if (!Enum.TryParse(snapshot.mode, true, out Day2SleepPresentationMode mode))
|
||||
{
|
||||
warning?.Invoke($"[Day2SleepPresentation] Unknown mode: {snapshot.mode}");
|
||||
yield break;
|
||||
}
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case Day2SleepPresentationMode.SmallMotion:
|
||||
if (!string.IsNullOrWhiteSpace(snapshot.picName))
|
||||
yield return ShowSpriteMotion(snapshot.picName, snapshot.motionProfile, 0f);
|
||||
break;
|
||||
case Day2SleepPresentationMode.Kite:
|
||||
yield return ShowKite(snapshot.kiteHeight, snapshot.kiteWind, 0f);
|
||||
SetKiteWind(snapshot.kiteWind, snapshot.kiteTurbulence);
|
||||
break;
|
||||
case Day2SleepPresentationMode.LargeEffects:
|
||||
if (snapshot.largeAnimationActive
|
||||
&& !string.IsNullOrWhiteSpace(snapshot.largeAnimationPrefix)
|
||||
&& snapshot.largeAnimationFrameCount > 0)
|
||||
{
|
||||
yield return ShowLargeSpriteAnimation(
|
||||
snapshot.largeAnimationPrefix,
|
||||
snapshot.largeAnimationFrameCount,
|
||||
snapshot.largeAnimationSecondsPerFrame,
|
||||
0f);
|
||||
}
|
||||
_largeEffects.SetBlur(snapshot.largeBlurAmount, snapshot.largeBlurSize);
|
||||
_largeEffects.ApplyPresentationImmediate(
|
||||
snapshot.largeScaleMultiplier <= 0f ? 1f : snapshot.largeScaleMultiplier,
|
||||
snapshot.largeAlpha);
|
||||
_mode = Day2SleepPresentationMode.LargeEffects;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator PrepareExclusiveMode()
|
||||
{
|
||||
if (_showcase != null)
|
||||
yield return _showcase.HideAll(0f);
|
||||
ClearPresentationEffects();
|
||||
}
|
||||
|
||||
private void HandleGenericPresentationChanging()
|
||||
{
|
||||
ClearPresentationEffects();
|
||||
}
|
||||
|
||||
private void MarkLargeEffectsActive()
|
||||
{
|
||||
if (_largeRenderer != null && _largeRenderer.gameObject.activeSelf)
|
||||
_mode = Day2SleepPresentationMode.LargeEffects;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9f771105a4b347ffbe2dc7da5acba320
|
||||
@@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using AibisDream.Framework;
|
||||
using UnityEngine;
|
||||
using UnityEngine.ResourceManagement.AsyncOperations;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
internal static class Day2SleepSpriteLoader
|
||||
{
|
||||
private const string DreamSpritePath = "Sprite/梦境/{0}.png";
|
||||
|
||||
public static string NormalizeName(string picName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(picName)) return picName;
|
||||
return picName.EndsWith(".png", StringComparison.OrdinalIgnoreCase)
|
||||
? picName.Substring(0, picName.Length - 4)
|
||||
: picName;
|
||||
}
|
||||
|
||||
public static IEnumerator LoadInto(
|
||||
SpriteRenderer target,
|
||||
string picName,
|
||||
Action<bool> completed = null,
|
||||
Action<string> warning = null)
|
||||
{
|
||||
if (target == null)
|
||||
{
|
||||
completed?.Invoke(false);
|
||||
yield break;
|
||||
}
|
||||
|
||||
var key = string.Format(DreamSpritePath, NormalizeName(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);
|
||||
yield break;
|
||||
}
|
||||
|
||||
var message = $"[Day2SleepPresentation] Failed to load sprite: {key}";
|
||||
Debug.LogError(message);
|
||||
warning?.Invoke(message);
|
||||
completed?.Invoke(false);
|
||||
}
|
||||
|
||||
public static IEnumerator Load(
|
||||
string picName,
|
||||
Action<Sprite> completed,
|
||||
Action<string> warning = null)
|
||||
{
|
||||
var key = string.Format(DreamSpritePath, NormalizeName(picName));
|
||||
var handle = ResourceSystem.LoadAsync<Sprite>(key);
|
||||
yield return handle;
|
||||
if (handle.Status == AsyncOperationStatus.Succeeded && handle.Result != null)
|
||||
{
|
||||
completed?.Invoke(handle.Result);
|
||||
yield break;
|
||||
}
|
||||
|
||||
var message = $"[Day2SleepPresentation] Failed to load sprite: {key}";
|
||||
Debug.LogError(message);
|
||||
warning?.Invoke(message);
|
||||
completed?.Invoke(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 41ba6bf8f06f44a0870d6c72a2ebeb39
|
||||
@@ -0,0 +1,131 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using Yarn.Unity;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
public static class Day2SleepYarnCommand
|
||||
{
|
||||
private static Day2SleepPresentationController Resolve()
|
||||
{
|
||||
if (Day2SleepPresentationController.Instance != null)
|
||||
return Day2SleepPresentationController.Instance;
|
||||
var controller = Object.FindObjectOfType<Day2SleepPresentationController>(true);
|
||||
if (controller == null)
|
||||
Debug.LogError("[Day2SleepYarnCommand] 当前场景缺少 Day2SleepPresentationController。");
|
||||
return controller;
|
||||
}
|
||||
|
||||
[YarnCommand("show_sprite_motion")]
|
||||
public static IEnumerator ShowSpriteMotion(
|
||||
string picName,
|
||||
string motionProfile,
|
||||
float duration = 0.35f)
|
||||
{
|
||||
var controller = Resolve();
|
||||
if (controller == null) yield break;
|
||||
yield return controller.ShowSpriteMotion(picName, motionProfile, duration);
|
||||
}
|
||||
|
||||
[YarnCommand("sprite_motion")]
|
||||
public static IEnumerator SpriteMotion(string motionProfile, float leadDuration = 0.18f)
|
||||
{
|
||||
var controller = Resolve();
|
||||
if (controller == null) yield break;
|
||||
controller.PlayCurrentMotion(motionProfile);
|
||||
if (leadDuration > 0f)
|
||||
yield return new WaitForSeconds(leadDuration);
|
||||
}
|
||||
|
||||
[YarnCommand("kite_show")]
|
||||
public static IEnumerator KiteShow(float height = 0f, int wind = 0, float duration = 0.35f)
|
||||
{
|
||||
var controller = Resolve();
|
||||
if (controller == null) yield break;
|
||||
yield return controller.ShowKite(height, wind, duration);
|
||||
}
|
||||
|
||||
[YarnCommand("kite_state")]
|
||||
public static IEnumerator KiteState(
|
||||
float height,
|
||||
int wind,
|
||||
float duration = 0.9f,
|
||||
float leadDuration = 0.35f)
|
||||
{
|
||||
var controller = Resolve();
|
||||
if (controller == null) yield break;
|
||||
controller.SetKiteState(height, wind, duration);
|
||||
if (leadDuration > 0f)
|
||||
yield return new WaitForSeconds(Mathf.Min(leadDuration, duration));
|
||||
}
|
||||
|
||||
[YarnCommand("kite_wind")]
|
||||
public static void KiteWind(int wind, float turbulence = -1f)
|
||||
{
|
||||
Resolve()?.SetKiteWind(wind, turbulence);
|
||||
}
|
||||
|
||||
[YarnCommand("kite_accent")]
|
||||
public static IEnumerator KiteAccent(string profile, float leadDuration = 0.18f)
|
||||
{
|
||||
var controller = Resolve();
|
||||
if (controller == null) yield break;
|
||||
controller.PlayKiteAccent(profile);
|
||||
if (leadDuration > 0f)
|
||||
yield return new WaitForSeconds(leadDuration);
|
||||
}
|
||||
|
||||
[YarnCommand("kite_hide")]
|
||||
public static IEnumerator KiteHide(float duration = 1f)
|
||||
{
|
||||
var controller = Resolve();
|
||||
if (controller == null) yield break;
|
||||
yield return controller.HideKite(duration);
|
||||
}
|
||||
|
||||
[YarnCommand("show_large_sprite_animation")]
|
||||
public static IEnumerator ShowLargeSpriteAnimation(
|
||||
string picNamePrefix,
|
||||
int frameCount,
|
||||
float secondsPerFrame = 0.2f,
|
||||
float duration = 1f)
|
||||
{
|
||||
var controller = Resolve();
|
||||
if (controller == null) yield break;
|
||||
yield return controller.ShowLargeSpriteAnimation(
|
||||
picNamePrefix,
|
||||
frameCount,
|
||||
secondsPerFrame,
|
||||
duration);
|
||||
}
|
||||
|
||||
[YarnCommand("set_large_sprite_blur")]
|
||||
public static void SetLargeSpriteBlur(float amount, float blurSize = 0.012f)
|
||||
{
|
||||
Resolve()?.SetLargeSpriteBlur(amount, blurSize);
|
||||
}
|
||||
|
||||
[YarnCommand("tween_large_sprite_blur")]
|
||||
public static IEnumerator TweenLargeSpriteBlur(
|
||||
float targetAmount,
|
||||
float duration,
|
||||
float blurSize = 0.012f)
|
||||
{
|
||||
var controller = Resolve();
|
||||
if (controller == null) yield break;
|
||||
yield return controller.TweenLargeSpriteBlur(targetAmount, duration, blurSize);
|
||||
}
|
||||
|
||||
[YarnCommand("tween_large_sprite_blur_async")]
|
||||
public static void TweenLargeSpriteBlurAsync(
|
||||
float targetAmount,
|
||||
float duration,
|
||||
float blurSize = 0.012f)
|
||||
{
|
||||
var controller = Resolve();
|
||||
if (controller == null) return;
|
||||
controller.StartCoroutine(
|
||||
controller.TweenLargeSpriteBlur(targetAmount, duration, blurSize));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 60df707002da4e30b73897e04b63d911
|
||||
@@ -0,0 +1,341 @@
|
||||
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)
|
||||
{
|
||||
SetState(height, wind, duration);
|
||||
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 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6ade30c6ea7b492e8ee1ee2b32141673
|
||||
+2
@@ -265,6 +265,8 @@ namespace AibisDream
|
||||
|
||||
public float TailSway { get; private set; }
|
||||
public float CurrentHeight => _height;
|
||||
public float TargetHeight => _heightTarget;
|
||||
public int CurrentWind => _wind;
|
||||
public float Turbulence => _turbulence;
|
||||
public bool IsRunning => _running;
|
||||
|
||||
@@ -0,0 +1,293 @@
|
||||
using System.Collections;
|
||||
using AibisDream.Utility;
|
||||
using DG.Tweening;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
internal sealed class LargeSpriteEffectsPresenter
|
||||
{
|
||||
private static readonly int BlurAmountId = Shader.PropertyToID("_BlurAmount");
|
||||
private static readonly int BlurSizeId = Shader.PropertyToID("_BlurSize");
|
||||
private static readonly int UseSoftFocusId = Shader.PropertyToID("_UseSoftFocus");
|
||||
|
||||
private readonly MonoBehaviour _host;
|
||||
private readonly SpriteRenderer _renderer;
|
||||
private readonly Material _focusMaterial;
|
||||
private readonly Vector3 _baseLocalPosition;
|
||||
private readonly Quaternion _baseLocalRotation;
|
||||
private readonly Vector3 _baseLocalScale;
|
||||
private readonly float _baseAlpha;
|
||||
private readonly Material _baseMaterial;
|
||||
|
||||
private MaterialPropertyBlock _propertyBlock;
|
||||
private Coroutine _animationCoroutine;
|
||||
private Sequence _presentationSequence;
|
||||
private int _blurTweenVersion;
|
||||
|
||||
public bool IsAnimationPlaying => _animationCoroutine != null;
|
||||
public string AnimationPrefix { get; private set; }
|
||||
public int AnimationFrameCount { get; private set; }
|
||||
public float AnimationSecondsPerFrame { get; private set; } = 0.2f;
|
||||
public float TargetBlurAmount { get; private set; }
|
||||
public float TargetBlurSize { get; private set; } = 0.012f;
|
||||
public float TargetScaleMultiplier { get; private set; } = 1f;
|
||||
public float TargetAlpha { get; private set; } = 1f;
|
||||
|
||||
public LargeSpriteEffectsPresenter(
|
||||
MonoBehaviour host,
|
||||
SpriteRenderer renderer,
|
||||
Material focusMaterial)
|
||||
{
|
||||
_host = host;
|
||||
_renderer = renderer;
|
||||
_focusMaterial = focusMaterial;
|
||||
if (_renderer == null) return;
|
||||
var transform = _renderer.transform;
|
||||
_baseLocalPosition = transform.localPosition;
|
||||
_baseLocalRotation = transform.localRotation;
|
||||
_baseLocalScale = transform.localScale;
|
||||
// SpriteShowcase 在自己的 Awake 中会先隐藏 Renderer;D2 的稳定基准仍然是完全可见。
|
||||
_baseAlpha = 1f;
|
||||
_baseMaterial = _renderer.sharedMaterial;
|
||||
}
|
||||
|
||||
public IEnumerator ShowAnimation(
|
||||
string picNamePrefix,
|
||||
int frameCount,
|
||||
float secondsPerFrame = 0.2f,
|
||||
float duration = 1f)
|
||||
{
|
||||
if (_renderer == null || _host == null || frameCount <= 0) yield break;
|
||||
ResetEffectsImmediate();
|
||||
|
||||
var frames = new Sprite[frameCount];
|
||||
var normalizedPrefix = Day2SleepSpriteLoader.NormalizeName(picNamePrefix);
|
||||
for (var index = 0; index < frameCount; index++)
|
||||
{
|
||||
Sprite loadedFrame = null;
|
||||
yield return Day2SleepSpriteLoader.Load(
|
||||
$"{normalizedPrefix}{index + 1}",
|
||||
value => loadedFrame = value);
|
||||
if (loadedFrame == null)
|
||||
{
|
||||
ResetEffectsImmediate();
|
||||
yield break;
|
||||
}
|
||||
frames[index] = loadedFrame;
|
||||
}
|
||||
|
||||
_renderer.gameObject.SetActive(true);
|
||||
_renderer.sprite = frames[0];
|
||||
_renderer.SetAlpha(0f);
|
||||
AnimationPrefix = normalizedPrefix;
|
||||
AnimationFrameCount = frameCount;
|
||||
AnimationSecondsPerFrame = Mathf.Max(0.01f, secondsPerFrame);
|
||||
_animationCoroutine = _host.StartCoroutine(LoopAnimation(frames, AnimationSecondsPerFrame));
|
||||
|
||||
if (duration <= 0f)
|
||||
_renderer.SetAlpha(1f);
|
||||
else
|
||||
yield return _renderer.DOFade(1f, duration).WaitForCompletion();
|
||||
TargetAlpha = 1f;
|
||||
}
|
||||
|
||||
public void SetBlur(float amount, float blurSize = 0.012f)
|
||||
{
|
||||
_blurTweenVersion++;
|
||||
TargetBlurAmount = Mathf.Clamp01(amount);
|
||||
TargetBlurSize = Mathf.Max(0f, blurSize);
|
||||
ApplyBlur(TargetBlurAmount, TargetBlurSize);
|
||||
}
|
||||
|
||||
public IEnumerator TweenBlur(float targetAmount, float duration, float blurSize = 0.012f)
|
||||
{
|
||||
if (_renderer == null) yield break;
|
||||
var tweenVersion = ++_blurTweenVersion;
|
||||
var startAmount = ReadBlurAmount();
|
||||
TargetBlurAmount = Mathf.Clamp01(targetAmount);
|
||||
TargetBlurSize = Mathf.Max(0f, blurSize);
|
||||
duration = Mathf.Max(0f, duration);
|
||||
if (duration <= 0f)
|
||||
{
|
||||
ApplyBlur(TargetBlurAmount, TargetBlurSize);
|
||||
yield break;
|
||||
}
|
||||
|
||||
var elapsed = 0f;
|
||||
while (elapsed < duration)
|
||||
{
|
||||
if (tweenVersion != _blurTweenVersion) yield break;
|
||||
elapsed += Time.deltaTime;
|
||||
var progress = Mathf.Clamp01(elapsed / duration);
|
||||
progress = progress * progress * (3f - 2f * progress);
|
||||
ApplyBlur(Mathf.Lerp(startAmount, TargetBlurAmount, progress), TargetBlurSize);
|
||||
yield return null;
|
||||
}
|
||||
if (tweenVersion != _blurTweenVersion) yield break;
|
||||
ApplyBlur(TargetBlurAmount, TargetBlurSize);
|
||||
}
|
||||
|
||||
public IEnumerator AnimatePresentation(float scaleMultiplier, float targetAlpha, float duration)
|
||||
{
|
||||
if (_renderer == null) yield break;
|
||||
KillPresentationTweens();
|
||||
_renderer.gameObject.SetActive(true);
|
||||
TargetScaleMultiplier = Mathf.Max(0.01f, scaleMultiplier);
|
||||
TargetAlpha = Mathf.Clamp01(targetAlpha);
|
||||
duration = Mathf.Max(0f, duration);
|
||||
var targetScale = ScaleFor(TargetScaleMultiplier);
|
||||
if (duration <= 0f)
|
||||
{
|
||||
ApplyPresentationImmediate(TargetScaleMultiplier, TargetAlpha);
|
||||
yield break;
|
||||
}
|
||||
|
||||
_presentationSequence = DOTween.Sequence();
|
||||
_presentationSequence.Join(
|
||||
_renderer.transform.DOScale(targetScale, duration).SetEase(Ease.InOutSine));
|
||||
_presentationSequence.Join(
|
||||
_renderer.transform.DOLocalMove(_baseLocalPosition, duration).SetEase(Ease.InOutSine));
|
||||
_presentationSequence.Join(
|
||||
_renderer.DOFade(TargetAlpha, duration).SetEase(Ease.InOutSine));
|
||||
yield return _presentationSequence.WaitForCompletion();
|
||||
_presentationSequence = null;
|
||||
}
|
||||
|
||||
public void ApplyPresentationImmediate(float scaleMultiplier, float alpha)
|
||||
{
|
||||
if (_renderer == null) return;
|
||||
KillPresentationTweens();
|
||||
TargetScaleMultiplier = Mathf.Max(0.01f, scaleMultiplier);
|
||||
TargetAlpha = Mathf.Clamp01(alpha);
|
||||
var transform = _renderer.transform;
|
||||
transform.localPosition = _baseLocalPosition;
|
||||
transform.localRotation = _baseLocalRotation;
|
||||
transform.localScale = ScaleFor(TargetScaleMultiplier);
|
||||
_renderer.SetAlpha(TargetAlpha);
|
||||
}
|
||||
|
||||
public void SetSpriteImmediate(Sprite sprite)
|
||||
{
|
||||
if (_renderer != null && sprite != null)
|
||||
_renderer.sprite = sprite;
|
||||
}
|
||||
|
||||
public void StopPresentationAnimation()
|
||||
{
|
||||
KillPresentationTweens();
|
||||
}
|
||||
|
||||
public void ResetPresentationImmediate()
|
||||
{
|
||||
if (_renderer == null) return;
|
||||
KillPresentationTweens();
|
||||
TargetScaleMultiplier = 1f;
|
||||
TargetAlpha = _baseAlpha;
|
||||
var transform = _renderer.transform;
|
||||
transform.localPosition = _baseLocalPosition;
|
||||
transform.localRotation = _baseLocalRotation;
|
||||
transform.localScale = _baseLocalScale;
|
||||
_renderer.SetAlpha(_baseAlpha);
|
||||
}
|
||||
|
||||
public void HidePresentationImmediate()
|
||||
{
|
||||
if (_renderer == null) return;
|
||||
StopAnimation(hideRenderer: true);
|
||||
ApplyBlur(0f, 0f);
|
||||
ResetPresentationImmediate();
|
||||
_renderer.SetAlpha(0f);
|
||||
_renderer.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
public void ResetEffectsImmediate()
|
||||
{
|
||||
_blurTweenVersion++;
|
||||
var animationWasPlaying = IsAnimationPlaying;
|
||||
StopAnimation(hideRenderer: animationWasPlaying);
|
||||
TargetBlurAmount = 0f;
|
||||
TargetBlurSize = 0.012f;
|
||||
ApplyBlur(0f, 0f);
|
||||
ResetPresentationImmediate();
|
||||
}
|
||||
|
||||
private IEnumerator LoopAnimation(Sprite[] frames, float secondsPerFrame)
|
||||
{
|
||||
var index = 0;
|
||||
var wait = new WaitForSeconds(Mathf.Max(0.01f, secondsPerFrame));
|
||||
while (frames.Length > 0)
|
||||
{
|
||||
if (_renderer != null)
|
||||
_renderer.sprite = frames[index];
|
||||
index = (index + 1) % frames.Length;
|
||||
yield return wait;
|
||||
}
|
||||
}
|
||||
|
||||
private void StopAnimation(bool hideRenderer)
|
||||
{
|
||||
if (_animationCoroutine != null && _host != null)
|
||||
{
|
||||
_host.StopCoroutine(_animationCoroutine);
|
||||
_animationCoroutine = null;
|
||||
}
|
||||
AnimationPrefix = null;
|
||||
AnimationFrameCount = 0;
|
||||
if (hideRenderer && _renderer != null)
|
||||
{
|
||||
_renderer.SetAlpha(0f);
|
||||
_renderer.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
private float ReadBlurAmount()
|
||||
{
|
||||
if (_renderer == null || _renderer.sharedMaterial != _focusMaterial) return 0f;
|
||||
_propertyBlock ??= new MaterialPropertyBlock();
|
||||
_renderer.GetPropertyBlock(_propertyBlock);
|
||||
return _propertyBlock.GetFloat(BlurAmountId);
|
||||
}
|
||||
|
||||
private void ApplyBlur(float amount, float blurSize)
|
||||
{
|
||||
if (_renderer == null) return;
|
||||
amount = Mathf.Clamp01(amount);
|
||||
if (amount <= 0.001f)
|
||||
{
|
||||
_renderer.SetPropertyBlock(null);
|
||||
_renderer.sharedMaterial = _baseMaterial;
|
||||
return;
|
||||
}
|
||||
if (_focusMaterial == null)
|
||||
{
|
||||
Debug.LogWarning("[LargeSpriteEffectsPresenter] 未配置全屏图片对焦材质。");
|
||||
return;
|
||||
}
|
||||
_renderer.sharedMaterial = _focusMaterial;
|
||||
_propertyBlock ??= new MaterialPropertyBlock();
|
||||
_renderer.GetPropertyBlock(_propertyBlock);
|
||||
_propertyBlock.SetFloat(BlurAmountId, amount);
|
||||
_propertyBlock.SetFloat(BlurSizeId, Mathf.Max(0f, blurSize));
|
||||
_propertyBlock.SetFloat(UseSoftFocusId, 1f);
|
||||
_renderer.SetPropertyBlock(_propertyBlock);
|
||||
}
|
||||
|
||||
private Vector3 ScaleFor(float multiplier)
|
||||
{
|
||||
return new Vector3(
|
||||
_baseLocalScale.x * multiplier,
|
||||
_baseLocalScale.y * multiplier,
|
||||
_baseLocalScale.z);
|
||||
}
|
||||
|
||||
private void KillPresentationTweens()
|
||||
{
|
||||
_presentationSequence?.Kill();
|
||||
_presentationSequence = null;
|
||||
if (_renderer == null) return;
|
||||
DOTween.Kill(_renderer);
|
||||
DOTween.Kill(_renderer.transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2a1c4fea897e46bf94ed561eaa210ceb
|
||||
@@ -0,0 +1,341 @@
|
||||
using System.Collections;
|
||||
using AibisDream.Utility;
|
||||
using DG.Tweening;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AibisDream
|
||||
{
|
||||
internal sealed class ReelMotionPresenter
|
||||
{
|
||||
private const string BaseSuffix = "_底图";
|
||||
private const string MotionSuffix = "_风筝层";
|
||||
private const float MotionSpeedMultiplier = 2.3f;
|
||||
private const float MotionAmplitude = 0.22f;
|
||||
|
||||
private readonly SpriteRenderer _baseRenderer;
|
||||
private readonly Vector3 _baseLocalPosition;
|
||||
private readonly Quaternion _baseLocalRotation;
|
||||
private readonly Vector3 _baseLocalScale;
|
||||
|
||||
private SpriteRenderer _motionRenderer;
|
||||
private SpriteMask _motionMask;
|
||||
private Sequence _motionSequence;
|
||||
private Vector3 _motionBaseOffset;
|
||||
|
||||
public bool IsShowing => _baseRenderer != null && _baseRenderer.gameObject.activeSelf;
|
||||
public string CurrentPicName { get; private set; }
|
||||
public string CurrentMotionProfile { get; private set; }
|
||||
|
||||
public ReelMotionPresenter(SpriteRenderer baseRenderer)
|
||||
{
|
||||
_baseRenderer = baseRenderer;
|
||||
if (_baseRenderer == null) return;
|
||||
var transform = _baseRenderer.transform;
|
||||
_baseLocalPosition = transform.localPosition;
|
||||
_baseLocalRotation = transform.localRotation;
|
||||
_baseLocalScale = transform.localScale;
|
||||
}
|
||||
|
||||
public IEnumerator Show(string picName, string motionProfile)
|
||||
{
|
||||
ClearImmediate();
|
||||
if (_baseRenderer == null) yield break;
|
||||
|
||||
var normalizedName = Day2SleepSpriteLoader.NormalizeName(picName);
|
||||
if (!IsReelPic(normalizedName))
|
||||
{
|
||||
Debug.LogWarning($"[ReelMotionPresenter] Unsupported reel sprite: {picName}");
|
||||
yield break;
|
||||
}
|
||||
|
||||
_motionBaseOffset = new Vector3(0f, -0.32f, 0f);
|
||||
_baseRenderer.gameObject.SetActive(true);
|
||||
var baseLoaded = false;
|
||||
yield return Day2SleepSpriteLoader.LoadInto(
|
||||
_baseRenderer,
|
||||
normalizedName + BaseSuffix,
|
||||
value => baseLoaded = value);
|
||||
if (!baseLoaded)
|
||||
{
|
||||
ClearImmediate();
|
||||
yield break;
|
||||
}
|
||||
|
||||
EnsureMotionRenderer();
|
||||
_motionRenderer.gameObject.SetActive(true);
|
||||
CopyRendererSettings(_motionRenderer, 1, SpriteMaskInteraction.VisibleInsideMask);
|
||||
ConfigureMotionMask();
|
||||
ResetTransforms();
|
||||
|
||||
var motionLoaded = false;
|
||||
yield return Day2SleepSpriteLoader.LoadInto(
|
||||
_motionRenderer,
|
||||
normalizedName + MotionSuffix,
|
||||
value => motionLoaded = value);
|
||||
if (!motionLoaded)
|
||||
{
|
||||
ClearImmediate();
|
||||
yield break;
|
||||
}
|
||||
|
||||
_baseRenderer.SetAlpha(1f);
|
||||
_motionRenderer.SetAlpha(1f);
|
||||
CurrentPicName = normalizedName;
|
||||
PlayMotion(motionProfile);
|
||||
}
|
||||
|
||||
public void PlayCurrentMotion(string motionProfile)
|
||||
{
|
||||
if (!IsShowing) return;
|
||||
PlayMotion(motionProfile);
|
||||
}
|
||||
|
||||
public void ClearImmediate()
|
||||
{
|
||||
KillTweenState();
|
||||
ResetTransforms();
|
||||
CurrentPicName = null;
|
||||
CurrentMotionProfile = null;
|
||||
|
||||
if (_baseRenderer != null)
|
||||
{
|
||||
_baseRenderer.DOKill();
|
||||
_baseRenderer.SetAlpha(0f);
|
||||
_baseRenderer.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
if (_motionRenderer != null)
|
||||
{
|
||||
_motionRenderer.DOKill();
|
||||
_motionRenderer.SetAlpha(0f);
|
||||
_motionRenderer.gameObject.SetActive(false);
|
||||
_motionRenderer.maskInteraction = SpriteMaskInteraction.None;
|
||||
}
|
||||
|
||||
if (_motionMask != null)
|
||||
_motionMask.enabled = false;
|
||||
}
|
||||
|
||||
private void EnsureMotionRenderer()
|
||||
{
|
||||
if (_motionRenderer != null || _baseRenderer == null) return;
|
||||
var motionObject = new GameObject($"{_baseRenderer.name} Motion");
|
||||
motionObject.transform.SetParent(_baseRenderer.transform.parent);
|
||||
_motionRenderer = motionObject.AddComponent<SpriteRenderer>();
|
||||
CopyRendererSettings(_motionRenderer, 1, SpriteMaskInteraction.VisibleInsideMask);
|
||||
_motionRenderer.gameObject.SetActive(false);
|
||||
_motionRenderer.SetAlpha(0f);
|
||||
}
|
||||
|
||||
private void EnsureMotionMask()
|
||||
{
|
||||
if (_motionMask != null || _baseRenderer == null) return;
|
||||
var maskObject = new GameObject($"{_baseRenderer.name} Motion Mask");
|
||||
maskObject.transform.SetParent(_baseRenderer.transform.parent);
|
||||
_motionMask = maskObject.AddComponent<SpriteMask>();
|
||||
_motionMask.alphaCutoff = 0.01f;
|
||||
_motionMask.isCustomRangeActive = true;
|
||||
_motionMask.enabled = false;
|
||||
}
|
||||
|
||||
private void CopyRendererSettings(
|
||||
SpriteRenderer target,
|
||||
int sortingOrderOffset,
|
||||
SpriteMaskInteraction maskInteraction)
|
||||
{
|
||||
if (target == null || _baseRenderer == null) return;
|
||||
target.sharedMaterial = _baseRenderer.sharedMaterial;
|
||||
target.sortingLayerID = _baseRenderer.sortingLayerID;
|
||||
target.sortingOrder = _baseRenderer.sortingOrder + sortingOrderOffset;
|
||||
target.drawMode = _baseRenderer.drawMode;
|
||||
target.size = _baseRenderer.size;
|
||||
target.flipX = _baseRenderer.flipX;
|
||||
target.flipY = _baseRenderer.flipY;
|
||||
target.maskInteraction = maskInteraction;
|
||||
}
|
||||
|
||||
private void ConfigureMotionMask()
|
||||
{
|
||||
EnsureMotionMask();
|
||||
if (_motionMask == null) return;
|
||||
_motionMask.sprite = _baseRenderer.sprite;
|
||||
_motionMask.frontSortingLayerID = _baseRenderer.sortingLayerID;
|
||||
_motionMask.backSortingLayerID = _baseRenderer.sortingLayerID;
|
||||
_motionMask.frontSortingOrder = _baseRenderer.sortingOrder + 2;
|
||||
_motionMask.backSortingOrder = _baseRenderer.sortingOrder;
|
||||
var transform = _motionMask.transform;
|
||||
transform.localPosition = _baseLocalPosition;
|
||||
transform.localRotation = _baseLocalRotation;
|
||||
transform.localScale = _baseLocalScale;
|
||||
_motionMask.enabled = true;
|
||||
}
|
||||
|
||||
private void ResetTransforms()
|
||||
{
|
||||
if (_baseRenderer == null) return;
|
||||
var baseTransform = _baseRenderer.transform;
|
||||
baseTransform.localPosition = _baseLocalPosition;
|
||||
baseTransform.localRotation = _baseLocalRotation;
|
||||
baseTransform.localScale = _baseLocalScale;
|
||||
if (_motionRenderer == null) return;
|
||||
var motionTransform = _motionRenderer.transform;
|
||||
motionTransform.localPosition = _baseLocalPosition + _motionBaseOffset;
|
||||
motionTransform.localRotation = _baseLocalRotation;
|
||||
motionTransform.localScale = _baseLocalScale;
|
||||
}
|
||||
|
||||
private static bool IsReelPic(string picName)
|
||||
{
|
||||
return picName == "D2S轱辘" || picName == "D2S轱辘没线";
|
||||
}
|
||||
|
||||
private static string NormalizeProfile(string motionProfile)
|
||||
{
|
||||
return string.IsNullOrEmpty(motionProfile)
|
||||
? string.Empty
|
||||
: motionProfile.Trim().ToLowerInvariant();
|
||||
}
|
||||
|
||||
private Vector3 BaseOffset(float x, float y)
|
||||
{
|
||||
return _baseLocalPosition + _motionBaseOffset + new Vector3(x, y, 0f);
|
||||
}
|
||||
|
||||
private void PlayMotion(string motionProfile)
|
||||
{
|
||||
KillTweenState();
|
||||
var profile = NormalizeProfile(motionProfile);
|
||||
CurrentMotionProfile = profile;
|
||||
if (string.IsNullOrEmpty(profile) || profile == "none") return;
|
||||
if (profile != "reel_tension")
|
||||
{
|
||||
Debug.LogWarning($"[ReelMotionPresenter] Unknown motion profile: {motionProfile}");
|
||||
return;
|
||||
}
|
||||
|
||||
var target = _motionRenderer != null && _motionRenderer.gameObject.activeSelf
|
||||
? _motionRenderer.transform
|
||||
: _baseRenderer.transform;
|
||||
var sequence = DOTween.Sequence();
|
||||
sequence.Append(target.DOLocalMove(
|
||||
BaseOffset(0.13f * MotionAmplitude, -0.32f * MotionAmplitude), 0.38f)
|
||||
.SetEase(Ease.OutCubic));
|
||||
sequence.Join(target.DOLocalRotate(
|
||||
new Vector3(0f, 0f, -2.8f * MotionAmplitude), 0.38f)
|
||||
.SetEase(Ease.OutCubic));
|
||||
sequence.OnComplete(() => PlayReelHold(target));
|
||||
sequence.timeScale = MotionSpeedMultiplier;
|
||||
_motionSequence = sequence;
|
||||
}
|
||||
|
||||
private void PlayReelHold(Transform transform, float strain = 0f)
|
||||
{
|
||||
_motionSequence?.Kill();
|
||||
var amp = MotionAmplitude;
|
||||
var deepX = strain * 0.03f;
|
||||
var deepY = strain * 0.07f;
|
||||
var speed = Mathf.Lerp(1f, 0.78f, strain);
|
||||
var rotBias = strain * 0.35f;
|
||||
var sequence = DOTween.Sequence();
|
||||
|
||||
var strainDuration = Random.Range(0.55f, 0.9f) * speed;
|
||||
sequence.Append(transform.DOLocalMove(
|
||||
BaseOffset((Random.Range(0.17f, 0.23f) + deepX) * amp,
|
||||
(Random.Range(-0.42f, -0.34f) - deepY) * amp), strainDuration)
|
||||
.SetEase(Ease.InOutSine));
|
||||
sequence.Join(transform.DOLocalRotate(
|
||||
new Vector3(0f, 0f, (Random.Range(-3.6f, -2.4f) - rotBias) * amp), strainDuration)
|
||||
.SetEase(Ease.InOutSine));
|
||||
|
||||
if (Random.value < 0.4f + strain * 0.25f)
|
||||
{
|
||||
var yankDuration = Random.Range(0.18f, 0.28f) * speed;
|
||||
sequence.Append(transform.DOLocalMove(
|
||||
BaseOffset((Random.Range(0.25f, 0.34f) + deepX) * amp,
|
||||
(Random.Range(-0.62f, -0.5f) - deepY) * amp), yankDuration)
|
||||
.SetEase(Ease.OutCubic));
|
||||
sequence.Join(transform.DOLocalRotate(
|
||||
new Vector3(0f, 0f, (Random.Range(-4.8f, -3.4f) - rotBias) * amp), yankDuration)
|
||||
.SetEase(Ease.OutCubic));
|
||||
sequence.AppendInterval(Random.Range(0.1f, 0.18f) * speed);
|
||||
var biteDuration = Random.Range(0.2f, 0.32f) * speed;
|
||||
sequence.Append(transform.DOLocalMove(
|
||||
BaseOffset((Random.Range(0.19f, 0.27f) + deepX) * amp,
|
||||
(Random.Range(-0.48f, -0.4f) - deepY) * amp), biteDuration)
|
||||
.SetEase(Ease.OutCubic));
|
||||
sequence.Join(transform.DOLocalRotate(
|
||||
new Vector3(0f, 0f, (Random.Range(-3.4f, -2.2f) - rotBias) * amp), biteDuration)
|
||||
.SetEase(Ease.OutCubic));
|
||||
}
|
||||
|
||||
var recoverDuration = Random.Range(0.28f, 0.42f) * speed;
|
||||
sequence.Append(transform.DOLocalMove(
|
||||
BaseOffset((Random.Range(0.12f, 0.16f) + deepX) * amp,
|
||||
(Random.Range(-0.32f, -0.26f) - deepY * 0.55f) * amp), recoverDuration)
|
||||
.SetEase(Ease.OutCubic));
|
||||
sequence.Join(transform.DOLocalRotate(
|
||||
new Vector3(0f, 0f, (Random.Range(-2.3f, -1.3f) - rotBias * 0.7f) * amp), recoverDuration)
|
||||
.SetEase(Ease.OutCubic));
|
||||
|
||||
var slipDuration = Random.Range(0.16f, 0.26f) * speed;
|
||||
sequence.Append(transform.DOLocalMove(
|
||||
BaseOffset((Random.Range(0.2f, 0.27f) + deepX) * amp,
|
||||
(Random.Range(-0.5f, -0.4f) - deepY) * amp), slipDuration)
|
||||
.SetEase(Ease.OutQuad));
|
||||
sequence.Join(transform.DOLocalRotate(
|
||||
new Vector3(0f, 0f, (Random.Range(-4.1f, -2.8f) - rotBias) * amp), slipDuration)
|
||||
.SetEase(Ease.OutQuad));
|
||||
|
||||
if (Random.value < 0.3f)
|
||||
{
|
||||
var dipDuration = Random.Range(0.14f, 0.22f) * speed;
|
||||
sequence.Append(transform.DOLocalMove(
|
||||
BaseOffset((Random.Range(0.21f, 0.28f) + deepX) * amp,
|
||||
(Random.Range(-0.56f, -0.47f) - deepY) * amp), dipDuration)
|
||||
.SetEase(Ease.OutQuad));
|
||||
sequence.Join(transform.DOLocalRotate(
|
||||
new Vector3(0f, 0f, (Random.Range(-4.4f, -3f) - rotBias) * amp), dipDuration)
|
||||
.SetEase(Ease.OutQuad));
|
||||
var braceDuration = Random.Range(0.18f, 0.28f) * speed;
|
||||
sequence.Append(transform.DOLocalMove(
|
||||
BaseOffset((Random.Range(0.16f, 0.22f) + deepX) * amp,
|
||||
(Random.Range(-0.4f, -0.33f) - deepY) * amp), braceDuration)
|
||||
.SetEase(Ease.OutCubic));
|
||||
sequence.Join(transform.DOLocalRotate(
|
||||
new Vector3(0f, 0f, (Random.Range(-3f, -1.8f) - rotBias) * amp), braceDuration)
|
||||
.SetEase(Ease.OutCubic));
|
||||
}
|
||||
|
||||
var pullDuration = Random.Range(0.4f, 0.58f) * speed;
|
||||
sequence.Append(transform.DOLocalMove(
|
||||
BaseOffset((Random.Range(0.13f, 0.18f) + deepX) * amp,
|
||||
(Random.Range(-0.35f, -0.28f) - deepY * 0.75f) * amp), pullDuration)
|
||||
.SetEase(Ease.InOutSine));
|
||||
sequence.Join(transform.DOLocalRotate(
|
||||
new Vector3(0f, 0f, (Random.Range(-2.8f, -1.6f) - rotBias) * amp), pullDuration)
|
||||
.SetEase(Ease.InOutSine));
|
||||
|
||||
if (Random.value < 0.25f)
|
||||
{
|
||||
var sagDuration = Random.Range(0.2f, 0.32f) * speed;
|
||||
sequence.Append(transform.DOLocalMove(
|
||||
BaseOffset((Random.Range(0.18f, 0.24f) + deepX) * amp,
|
||||
(Random.Range(-0.46f, -0.38f) - deepY) * amp), sagDuration)
|
||||
.SetEase(Ease.InOutSine));
|
||||
sequence.Join(transform.DOLocalRotate(
|
||||
new Vector3(0f, 0f, (Random.Range(-3.5f, -2.2f) - rotBias) * amp), sagDuration)
|
||||
.SetEase(Ease.InOutSine));
|
||||
}
|
||||
|
||||
sequence.OnComplete(() => PlayReelHold(transform, Mathf.Min(1f, strain + 0.12f)));
|
||||
sequence.timeScale = MotionSpeedMultiplier;
|
||||
_motionSequence = sequence;
|
||||
}
|
||||
|
||||
private void KillTweenState()
|
||||
{
|
||||
_motionSequence?.Kill();
|
||||
_motionSequence = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e5bccd7819704be4afad76507faac330
|
||||
@@ -182,9 +182,7 @@ namespace AibisDream
|
||||
ResetCodeSeaImmediate();
|
||||
ResetTerminalImmediate();
|
||||
|
||||
var showcase = SpriteShowcase.Instance;
|
||||
if (showcase != null)
|
||||
showcase.SetLargePresentationImmediate(1f, 1f);
|
||||
Day2SleepPresentationController.Instance?.SetLargePresentationImmediate(1f, 1f);
|
||||
|
||||
if (showSea && glassSea != null)
|
||||
{
|
||||
@@ -263,16 +261,16 @@ namespace AibisDream
|
||||
/// </summary>
|
||||
public IEnumerator TweenListenCloseup(float scale, float alpha, float duration)
|
||||
{
|
||||
var showcase = SpriteShowcase.Instance;
|
||||
if (showcase == null)
|
||||
var presentation = Day2SleepPresentationController.Instance;
|
||||
if (presentation == null)
|
||||
yield break;
|
||||
|
||||
yield return showcase.AnimateLargePresentation(scale, alpha, duration);
|
||||
yield return presentation.AnimateLargePresentation(scale, alpha, duration);
|
||||
}
|
||||
|
||||
public void SetListenCloseup(float scale, float alpha)
|
||||
{
|
||||
SpriteShowcase.Instance?.SetLargePresentationImmediate(scale, alpha);
|
||||
Day2SleepPresentationController.Instance?.SetLargePresentationImmediate(scale, alpha);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -334,7 +332,7 @@ namespace AibisDream
|
||||
: null;
|
||||
panel?.FadeOutSync(0f);
|
||||
|
||||
SpriteShowcase.Instance?.ResetLargePresentationImmediate();
|
||||
Day2SleepPresentationController.Instance?.ResetLargePresentationImmediate();
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -516,7 +514,7 @@ namespace AibisDream
|
||||
DOTween.Kill(_doorShellTransform);
|
||||
DOTween.Kill(flashOverlay);
|
||||
DOTween.Kill(sceneBlackOverlay);
|
||||
SpriteShowcase.Instance?.StopLargePresentationAnimation();
|
||||
Day2SleepPresentationController.Instance?.StopLargePresentationAnimation();
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -568,11 +566,7 @@ namespace AibisDream
|
||||
if (sceneBlackOverlay != null)
|
||||
sceneBlackOverlay.SetAlpha(0);
|
||||
|
||||
var showcase = SpriteShowcase.Instance;
|
||||
if (showcase != null)
|
||||
{
|
||||
showcase.HideLargePresentationImmediate();
|
||||
}
|
||||
Day2SleepPresentationController.Instance?.HideLargePresentationImmediate();
|
||||
|
||||
_presentationPhase = DoorPresentationPhase.Hidden;
|
||||
}
|
||||
@@ -864,7 +858,7 @@ namespace AibisDream
|
||||
}
|
||||
}
|
||||
|
||||
SpriteShowcase.Instance?.SetLargeSpriteImmediate(targetSprite);
|
||||
Day2SleepPresentationController.Instance?.SetLargeSpriteImmediate(targetSprite);
|
||||
}
|
||||
|
||||
private void SetBackgroundVisible(bool visible)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -895,7 +895,7 @@ girl: 往前走——但是别忘了我——! #line:0b267e5
|
||||
<<wait 2>>
|
||||
|
||||
|
||||
<<show_large_sprite "D2S枯树-Large">>
|
||||
<<show_large_sprite "D2S枯树-Large" 0>>
|
||||
【关门的声音】 #line:01c4bb5
|
||||
// <<fade_out 0>>
|
||||
<<bloom_exposure_out "BloomEffect" 2.2 0.6>>
|
||||
@@ -953,7 +953,11 @@ position: -4,927
|
||||
<<hide_dream_door>>
|
||||
<<show_large_sprite "D2S门">>
|
||||
<<else>>
|
||||
<<show_large_sprite "门框特写">>
|
||||
<<show_large_sprite "门框特写" 0>>
|
||||
<<show_door_listen_view false 0>>
|
||||
<<set_door_closeup 1 1>>
|
||||
<<set_volume_weight "BloomEffect" 0.4>>
|
||||
<<set_volume_brightness "BloomEffect" 0>>
|
||||
<<endif>>
|
||||
|
||||
<<if $consciousness == 0>>
|
||||
@@ -967,59 +971,72 @@ position: -4,927
|
||||
<<endonce>>
|
||||
|
||||
->观察
|
||||
<<set $doorCheck = true>>
|
||||
<<jump 门观察>>
|
||||
->回头
|
||||
<<set $doorCheck = false>>
|
||||
<<jump UntitledGreybox>>
|
||||
<<jump 门>>
|
||||
===
|
||||
|
||||
|
||||
title: 门观察
|
||||
color: purple
|
||||
cluster: Day2_Door
|
||||
position: 118,927
|
||||
---
|
||||
<<set $doorCheck = true>>
|
||||
<<show_large_sprite "门框特写" 0>>
|
||||
<<show_door_listen_view false 0>>
|
||||
<<set_door_closeup 1 1>>
|
||||
<<set_volume_weight "BloomEffect" 0.4>>
|
||||
<<set_volume_brightness "BloomEffect" 0>>
|
||||
门看起来很旧,表面的漆已经开裂、剥落,形成一道道纹路。 #line:06fa27b
|
||||
你走近门边,透过毛玻璃想要看清另一边。
|
||||
???: …喂——!… #line:0bfb27b
|
||||
你好像听到门的那边有人在呼唤你。 #line:029c773
|
||||
<i>你知道穿过这扇门你就无法再回来了。</i> #line:05ac743
|
||||
<<show_large_sprite "门把手特写" 0>>
|
||||
->推门 #line:02467b3
|
||||
<<show_large_sprite "门框特写" 0>>
|
||||
<<show_door_listen_view false 0>>
|
||||
<<set_door_closeup 1 1>>
|
||||
<<set_volume_weight "BloomEffect" 0.4>>
|
||||
<<set_volume_brightness "BloomEffect" 0>>
|
||||
门看起来很旧,表面的漆已经开裂、剥落,形成一道道纹路。 #line:06fa27b
|
||||
你走近门边,透过毛玻璃想要看清另一边。
|
||||
???: …喂——!… #line:0bfb27b
|
||||
你好像听到门的那边有人在呼唤你。 #line:029c773
|
||||
<i>你知道穿过这扇门你就无法再回来了。</i> #line:05ac743
|
||||
<<show_large_sprite "门把手特写" 0>>
|
||||
->推门 #line:02467b3
|
||||
<<show_large_sprite "门框特写" 0>>
|
||||
门的那边传来了一种窸窸窣窣的声音。 #line:03e96a2
|
||||
你有一种奇怪的感觉,仿佛你是听着这种声音长大的。 #line:08b0cf3
|
||||
// <<fade_in 0.5>>
|
||||
<<hide_dialog>>
|
||||
// 第一段:门近景推进,同时 Bloom 和曝光升至全白。
|
||||
<<tween_door_closeup_async 1.1 0 2.2>>
|
||||
<<tween_volume_weight_async "BloomEffect" 1 2.8>>
|
||||
<<tween_volume_brightness_async "BloomEffect" 1 2.8>>
|
||||
<<wait 2.8>>
|
||||
// 全白时切到无门的全屏金色海浪,再逐步恢复曝光。
|
||||
<<hide_large_sprite 0>>
|
||||
<<show_door_glass_sea_fullscreen>>
|
||||
<<tween_volume_brightness_async "BloomEffect" 0 1.5>>
|
||||
<<wait 1>>
|
||||
<<tween_volume_weight_async "BloomEffect" 0.02 2.5>>
|
||||
<<wait 3>>
|
||||
这真的只是一场梦吗…… #line:06faee0
|
||||
<<wait 2>>
|
||||
// 第二段:再次过曝;全白时恢复门框和门后海浪。
|
||||
<<tween_volume_weight_async "BloomEffect" 1 2.2>>
|
||||
<<tween_volume_brightness_async "BloomEffect" 1 2.2>>
|
||||
<<wait 2>>
|
||||
<<show_large_sprite "门框特写" 0>>
|
||||
<<show_door_listen_view true 0>>
|
||||
<<tween_volume_weight_async "BloomEffect" 0.02 2>>
|
||||
<<tween_volume_brightness_async "BloomEffect" 0 2>>
|
||||
<<wait 2>>
|
||||
me: … #line:00a452a
|
||||
海…真的存在吗? #line:09ec716
|
||||
->存在 #line:0fafc3e
|
||||
<<set $seaExist="存在…">>
|
||||
->不存在 #line:00cc442
|
||||
<<set $seaExist="不存在。">>
|
||||
->不知道 #line:0ff6de6
|
||||
<<set $seaExist="我不知道…也许?">>
|
||||
<<jump end>>
|
||||
->返回 #line:02cb5f1
|
||||
<<set $doorCheck = false>>
|
||||
你退了回来,远离这扇门。空气都似乎变得稀薄了。 #line:07400da
|
||||
门的那边传来了一种窸窸窣窣的声音。 #line:03e96a2
|
||||
你有一种奇怪的感觉,仿佛你是听着这种声音长大的。 #line:08b0cf3
|
||||
// <<fade_in 0.5>>
|
||||
<<hide_dialog>>
|
||||
// 第一段:门近景推进,同时 Bloom 和曝光升至全白。
|
||||
<<tween_door_closeup_async 1.1 0 2.2>>
|
||||
<<tween_volume_weight_async "BloomEffect" 1 2.8>>
|
||||
<<tween_volume_brightness_async "BloomEffect" 1 2.8>>
|
||||
<<wait 2.8>>
|
||||
// 全白时切到无门的全屏金色海浪,再逐步恢复曝光。
|
||||
<<hide_large_sprite 0>>
|
||||
<<show_door_glass_sea_fullscreen>>
|
||||
<<tween_volume_brightness_async "BloomEffect" 0 1.5>>
|
||||
<<wait 1>>
|
||||
<<tween_volume_weight_async "BloomEffect" 0.02 2.5>>
|
||||
<<wait 3>>
|
||||
这真的只是一场梦吗…… #line:06faee0
|
||||
<<wait 2>>
|
||||
// 第二段:再次过曝;全白时恢复门框和门后海浪。
|
||||
<<tween_volume_weight_async "BloomEffect" 1 2.2>>
|
||||
<<tween_volume_brightness_async "BloomEffect" 1 2.2>>
|
||||
<<wait 2>>
|
||||
<<show_large_sprite "门框特写" 0>>
|
||||
<<show_door_listen_view true 0>>
|
||||
<<tween_volume_weight_async "BloomEffect" 0.02 2>>
|
||||
<<tween_volume_brightness_async "BloomEffect" 0 2>>
|
||||
<<wait 2>>
|
||||
me: … #line:00a452a
|
||||
海…真的存在吗? #line:09ec716
|
||||
->存在 #line:0fafc3e
|
||||
<<set $seaExist="存在…">>
|
||||
->不存在 #line:00cc442
|
||||
<<set $seaExist="不存在。">>
|
||||
->不知道 #line:0ff6de6
|
||||
<<set $seaExist="我不知道…也许?">>
|
||||
<<jump end>>
|
||||
->返回 #line:02cb5f1
|
||||
你松开门把手,退回一步,重新打量着这扇门。 #line:07400da
|
||||
<<jump 门观察>>
|
||||
->回头 #line:0998afe
|
||||
<<set $doorCheck = false>>
|
||||
<<jump UntitledGreybox>>
|
||||
@@ -1094,11 +1111,8 @@ position: -4,927
|
||||
//->后退
|
||||
// <<jump 记忆空间>>
|
||||
|
||||
|
||||
<<jump 门>>
|
||||
===
|
||||
|
||||
|
||||
title: 靠近黑暗
|
||||
color: blue
|
||||
cluster: Day2_Door
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
# D2 Sleep 表现系统拆分与存档重构方案
|
||||
|
||||
## 目标
|
||||
|
||||
将 D2 Sleep 新增的轱辘、风筝、全屏序列帧、虚焦和门近景控制从通用 `SpriteShowcase` 中剥离,同时为通用图片与 D2 专属表现分别建立可扩展的语义快照。
|
||||
|
||||
本次重构保持 Yarn 命令名、参数默认值、Addressable key、场景视觉参数和剧情文本不变。
|
||||
|
||||
## 职责边界
|
||||
|
||||
### SpriteShowcase
|
||||
|
||||
只保留跨章节可复用的能力:
|
||||
|
||||
- 普通小图显示、隐藏。
|
||||
- 普通大图显示、隐藏。
|
||||
- 背景淡入淡出。
|
||||
- 通用即时清理。
|
||||
- 通用图片终态的捕获与恢复。
|
||||
- 在图片切换、隐藏或清理前触发 `PresentationChanging`,通知场景专属表现清除叠加层。
|
||||
|
||||
`SpriteShowcase` 不再持有 D2 配置、运行时节点、遮罩、序列帧协程、虚焦材质或 D2 Tween。
|
||||
|
||||
### Day2SleepPresentationController
|
||||
|
||||
D2 Sleep 场景级统一入口,负责:
|
||||
|
||||
- 普通小图、轱辘、风筝和大图特效的互斥。
|
||||
- 监听 `SpriteShowcase.PresentationChanging` 并停止全部 D2 表现。
|
||||
- 为 Yarn 和 `DreamDoorSystem` 提供稳定 API。
|
||||
- 捕获、恢复 D2 专属表现的语义终态。
|
||||
|
||||
内部 Presenter:
|
||||
|
||||
| 类型 | 职责 |
|
||||
| --- | --- |
|
||||
| `ReelMotionPresenter` | 轱辘底图/运动层、SpriteMask、`reel_tension` 常驻拉锯循环 |
|
||||
| `KitePresentation` | 风筝相机节点、底图、遮罩、sway/accent 层、资源加载与显隐 |
|
||||
| `KiteRigDriver` | 风筝连续运动、风况、高度、镜头补偿与对焦算法 |
|
||||
| `LargeSpriteEffectsPresenter` | 全屏序列帧、虚焦、大图缩放/透明度及 Tween |
|
||||
|
||||
## Yarn 与系统调用
|
||||
|
||||
D2 专属命令移动到 `Day2SleepYarnCommand`:
|
||||
|
||||
- `show_sprite_motion`
|
||||
- `sprite_motion`
|
||||
- `kite_show`
|
||||
- `kite_state`
|
||||
- `kite_wind`
|
||||
- `kite_accent`
|
||||
- `kite_hide`
|
||||
- `show_large_sprite_animation`
|
||||
- `set_large_sprite_blur`
|
||||
- `tween_large_sprite_blur`
|
||||
- `tween_large_sprite_blur_async`
|
||||
|
||||
普通 `show_sprite`、`show_large_sprite`、`hide_*` 和 Showcase 背景命令仍由 `BaseYarnCommand` 调用 `SpriteShowcase`。
|
||||
|
||||
`DreamDoorSystem` 的大图换图、近景缩放和透明度控制调用 `Day2SleepPresentationController`。Bloom、曝光、海浪和终端逻辑仍属于 `DreamDoorSystem`。
|
||||
|
||||
## 存档模型
|
||||
|
||||
存档只记录可重建的语义终态,不保存 Coroutine、Tween、运行时 GameObject、SpriteMask、材质实例或随机运动瞬时相位。
|
||||
|
||||
### showcase section
|
||||
|
||||
稳定 id:`showcase`,RestoreOrder:69。
|
||||
|
||||
`ShowcaseSnapshotDto`:
|
||||
|
||||
- `displayMode`:`None` / `Small` / `Large`。
|
||||
- `picName`:逻辑图片名,不保存加载后的 `Sprite`。
|
||||
- `isBackgroundVisible`:背景遮罩终态。
|
||||
|
||||
恢复时通过 `ResourceSystem` 重新加载图片,并以 0 秒时长应用终态。
|
||||
|
||||
### day2SleepPresentation section
|
||||
|
||||
稳定 id:`day2SleepPresentation`,RestoreOrder:70。
|
||||
|
||||
`Day2SleepPresentationSnapshotDto` 按模式保存:
|
||||
|
||||
| 模式 | 保存内容 |
|
||||
| --- | --- |
|
||||
| `SmallMotion` | 图片名、motion profile |
|
||||
| `Kite` | 目标高度、风向、乱度 |
|
||||
| `LargeEffects` | 序列帧前缀/帧数/帧间隔、虚焦参数、缩放倍率、透明度 |
|
||||
|
||||
`kite_accent` 属于一次性顿挫,不进入快照;轱辘和风筝的随机相位在恢复后从对应语义状态重新开始。
|
||||
|
||||
若当前没有 D2 专属状态,Provider 返回 `null`,不写入该 section。通用大图叠加了 D2 虚焦或门近景效果时,会同时写入 `showcase` 与 `day2SleepPresentation`:先恢复底图,再恢复特效。
|
||||
|
||||
### 恢复顺序
|
||||
|
||||
相关表现 Provider 顺序为:
|
||||
|
||||
1. `showcase`(69):恢复普通底图和背景。
|
||||
2. `day2SleepPresentation`(70):恢复 D2 动态层或大图特效。
|
||||
3. `playTool`(71):恢复物品展示 UI。
|
||||
4. `screen`(80):恢复饱和度和全屏遮罩。
|
||||
|
||||
旧存档没有新增 section 时直接跳过,不提升 `schemaVersion`。旧 `showcase` JSON 中的额外字段由 Json.NET 忽略。
|
||||
|
||||
## 场景迁移
|
||||
|
||||
`梦境` 场景保留原 `SpriteShowcase` 组件及脚本 GUID,只留下三个 Renderer 引用。新增 `Day2SleepPresentationController`,迁移原对焦材质和三组风筝设置。轱辘、风筝和遮罩节点仍在运行时动态创建。
|
||||
|
||||
## 生命周期清理
|
||||
|
||||
- 普通图片切换前:`PresentationChanging` 清理 D2 表现。
|
||||
- D2 模式切换前:控制器清理普通图片与上一种 D2 表现。
|
||||
- 返回菜单、切章和读档前:`GameManager` 分别清理 D2 控制器与 `SpriteShowcase`。
|
||||
- 组件禁用或销毁:停止 Coroutine/Tween、隐藏运行时节点、关闭 SpriteMask,并恢复大图材质属性、缩放和透明度。
|
||||
|
||||
## 验证范围
|
||||
|
||||
- Unity 编译和现有 EditMode 测试通过。
|
||||
- FP/Fiction Day1 普通小图、大图和背景淡入淡出无回退。
|
||||
- D2 Sleep 轱辘、风筝、全屏序列帧、星图虚焦和 DreamDoor 近景表现保持一致。
|
||||
- 普通图片命令能中断并清理任意 D2 动效。
|
||||
- 通用小图/大图、风筝、轱辘、序列帧和大图特效可按快照恢复。
|
||||
- 返回菜单、切章、读档和重复进入梦境后没有残留动态节点、材质属性或 DOTween。
|
||||
|
||||
## 不在本次范围
|
||||
|
||||
- 不保存动画的逐帧进度、Tween 中间值或随机种子。
|
||||
- 不重构 Bloom、曝光和 `DreamDoorSystem` 自身状态机。
|
||||
- 不修改 Yarn 剧情内容或 `#line:` 标签。
|
||||
- 不修改现有资源名、Addressable key 和视觉参数。
|
||||
+3
-3
@@ -6,7 +6,7 @@
|
||||
|
||||
放风筝段落原来用 `D2S风筝1/2/3` 三张切图按高度阈值硬切,跨阈值时尺寸跳变,且节点顶部的常驻动效会覆盖选项里的一次性反馈。现已改为**单张风筝图 + 连续参数驱动**,不再使用切图。
|
||||
|
||||
实现在 [`SpriteShowcase.cs`](../Assets/Scripts/SceneManagement/SpriteShowcase.cs) 的“风筝 Rig”区域(`#region 风筝 Rig`),命令注册在 [`BaseYarnCommand.cs`](../Assets/Scripts/Dialog%20System/BaseYarnCommand.cs)。
|
||||
场景入口在 [`Day2SleepPresentationController.cs`](../Assets/Scripts/SceneManagement/Day2Sleep/Day2SleepPresentationController.cs),风筝节点与资源由 [`KitePresentation.cs`](../Assets/Scripts/SceneManagement/Day2Sleep/KitePresentation.cs) 管理,连续运动仍由 [`KiteRigDriver.cs`](../Assets/Scripts/SceneManagement/Day2Sleep/KiteRigDriver.cs) 驱动。命令统一注册在 [`Day2SleepYarnCommand.cs`](../Assets/Scripts/SceneManagement/Day2Sleep/Day2SleepYarnCommand.cs)。
|
||||
|
||||
### Rig 结构
|
||||
|
||||
@@ -65,7 +65,7 @@ Kite Mask(SpriteMask,以底图为形状,风筝层被裁在天空范围内
|
||||
| `flutter` | 左右交替的几下小拉扯,力度递减 | 保持不动、原地挣扎 |
|
||||
| `wrench` | 被阵风猛地拽向一侧,停顿后回正 | 强乱流、角色被风筝拖拽 |
|
||||
|
||||
轱辘特写(`D2S轱辘`)不属于这套 Rig,仍用旧的 `<<show_sprite_motion "D2S轱辘" "reel_tension" duration>>`,动效在 `SpriteShowcase.PlaySmallMotion` / `PlayReelHold` 里维护:先被猛地拽进受力姿态(只进一次),之后绷在那里持续高频颤抖,偶尔再被拽深一点——是“全力拉住”的发力感,不会松回原位。
|
||||
轱辘特写(`D2S轱辘`)不属于这套 Rig,仍使用 `<<show_sprite_motion "D2S轱辘" "reel_tension" duration>>`;分层、遮罩和常驻拉锯循环由 [`ReelMotionPresenter.cs`](../Assets/Scripts/SceneManagement/Day2Sleep/ReelMotionPresenter.cs) 维护:先被猛地拽进受力姿态(只进一次),之后绷在那里持续拉锯,偶尔再被拽深一点——是“全力拉住”的发力感,不会松回原位。
|
||||
|
||||
## 典型用法模式
|
||||
|
||||
@@ -109,7 +109,7 @@ Kite Mask(SpriteMask,以底图为形状,风筝层被裁在天空范围内
|
||||
|
||||
### 4. 离场:`kite_hide` 或让互斥逻辑自动处理
|
||||
|
||||
风筝 Rig 已经接入 `ShowObj` / `ShowObjWithMotion` / `ShowLargeObj` / `HideObj` / `HideAll` 的互斥逻辑,切到大图或普通小图时会自动隐藏,不必每次手动 `kite_hide`。只有明确要求“风筝消失但暂不显示别的图”时才需要显式调用。
|
||||
`Day2SleepPresentationController` 会监听 `SpriteShowcase.PresentationChanging`。执行普通 `show_sprite`、`show_large_sprite`、`hide_*` 或通用清理时,会自动停止风筝、轱辘、序列帧与 Tween,因此不必每次手动 `kite_hide`。只有明确要求“风筝消失但暂不显示别的图”时才需要显式调用。
|
||||
|
||||
## 常见坑
|
||||
|
||||
|
||||
Reference in New Issue
Block a user