From ea95111f2acac489dc046bac31c6e16471fdbf3b Mon Sep 17 00:00:00 2001
From: bottlefish <781230111@qq.com>
Date: Thu, 16 Jul 2026 17:52:34 +0800
Subject: [PATCH] =?UTF-8?q?feat(dream):=20=E5=AE=8C=E5=96=84=E6=A2=A6?=
=?UTF-8?q?=E5=A2=83=E9=97=A8=E4=B8=8E=E6=98=9F=E7=A9=BA=E8=81=9A=E7=84=A6?=
=?UTF-8?q?=E6=BC=94=E5=87=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.gitattributes | 3 +-
.../Scene_Dream/Materials/DreamSeaMemory.mat | 43 +++
.../Materials/DreamSeaMemory.mat.meta | 8 +
.../Materials/DreamSeaMemory.renderTexture | 40 +++
.../DreamSeaMemory.renderTexture.meta | 8 +
.../Materials/DreamSeaMemory.shader | 120 +++++++
.../Materials/DreamSeaMemory.shader.meta | 9 +
.../animation/D2S_SeaMemory_Duo.mp4 | 3 +
.../animation/D2S_SeaMemory_Duo.mp4.meta | 27 ++
.../animation/D2S_SeaMemory_Duo_Poster.png | 3 +
.../D2S_SeaMemory_Duo_Poster.png.meta | 114 +++++++
Assets/Render/KiteFocusSprite.shader | 41 +++
Assets/Scenes/梦境.unity | 63 ++--
.../SceneManagement/DreamDoorSystem.cs | 308 +++++++++++++++++-
.../TimelineKit/StarfieldDefocusController.cs | 184 ++++++++++-
.../Scripts/UI/Components/DreamTerminalUI.cs | 16 +-
.../Yarn/FP/FP_Day1_sleep/FP_Day1_sleep.yarn | 11 +-
.../Yarn/FP/FP_Day2_sleep/FP_Day2_sleep.yarn | 4 +
18 files changed, 951 insertions(+), 54 deletions(-)
create mode 100644 Assets/GameContent/Scene_Dream/Materials/DreamSeaMemory.mat
create mode 100644 Assets/GameContent/Scene_Dream/Materials/DreamSeaMemory.mat.meta
create mode 100644 Assets/GameContent/Scene_Dream/Materials/DreamSeaMemory.renderTexture
create mode 100644 Assets/GameContent/Scene_Dream/Materials/DreamSeaMemory.renderTexture.meta
create mode 100644 Assets/GameContent/Scene_Dream/Materials/DreamSeaMemory.shader
create mode 100644 Assets/GameContent/Scene_Dream/Materials/DreamSeaMemory.shader.meta
create mode 100644 Assets/GameContent/Scene_Dream/animation/D2S_SeaMemory_Duo.mp4
create mode 100644 Assets/GameContent/Scene_Dream/animation/D2S_SeaMemory_Duo.mp4.meta
create mode 100644 Assets/GameContent/Scene_Dream/animation/D2S_SeaMemory_Duo_Poster.png
create mode 100644 Assets/GameContent/Scene_Dream/animation/D2S_SeaMemory_Duo_Poster.png.meta
diff --git a/.gitattributes b/.gitattributes
index aab49955f..d8c37fec9 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -75,7 +75,8 @@
*.psd filter=lfs diff=lfs merge=lfs -text
*.psb filter=lfs diff=lfs merge=lfs -text
*.bank filter=lfs diff=lfs merge=lfs -text
+*.mp4 filter=lfs diff=lfs merge=lfs -text
# DOTween editor images - exclude from LFS (stored as regular binaries, Unity re-import causes false changes)
Assets/Plugins/Demigiant/DOTween/Editor/Imgs/*.png -filter -diff -merge
-Assets/Plugins/Demigiant/DOTween/Editor/Imgs/*.jpg -filter -diff -merge
\ No newline at end of file
+Assets/Plugins/Demigiant/DOTween/Editor/Imgs/*.jpg -filter -diff -merge
diff --git a/Assets/GameContent/Scene_Dream/Materials/DreamSeaMemory.mat b/Assets/GameContent/Scene_Dream/Materials/DreamSeaMemory.mat
new file mode 100644
index 000000000..8c3972b3d
--- /dev/null
+++ b/Assets/GameContent/Scene_Dream/Materials/DreamSeaMemory.mat
@@ -0,0 +1,43 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!21 &2100000
+Material:
+ serializedVersion: 8
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_Name: DreamSeaMemory
+ m_Shader: {fileID: 4800000, guid: a8d86af0aff94e1794fa50e65ac676d5, type: 3}
+ m_Parent: {fileID: 0}
+ m_ModifiedSerializedProperties: 0
+ m_ValidKeywords: []
+ m_InvalidKeywords: []
+ m_LightmapFlags: 4
+ m_EnableInstancingVariants: 0
+ m_DoubleSidedGI: 0
+ m_CustomRenderQueue: -1
+ stringTagMap: {}
+ disabledShaderPasses: []
+ m_LockedProperties:
+ m_SavedProperties:
+ serializedVersion: 3
+ m_TexEnvs:
+ - _MainTex:
+ m_Texture: {fileID: 0}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ - _VideoTex:
+ m_Texture: {fileID: 8400000, guid: d873791053c443c1a79eedd22a687780, type: 2}
+ m_Scale: {x: 1, y: 1}
+ m_Offset: {x: 0, y: 0}
+ m_Ints: []
+ m_Floats:
+ - _Contrast: 1
+ - _DitherStrength: 0.002
+ - _Levels: 10
+ - _UseVideo: 0
+ m_Colors:
+ - _Color: {r: 1, g: 1, b: 1, a: 1}
+ - _PixelGrid: {r: 360, g: 640, b: 0, a: 0}
+ m_BuildTextureStacks: []
diff --git a/Assets/GameContent/Scene_Dream/Materials/DreamSeaMemory.mat.meta b/Assets/GameContent/Scene_Dream/Materials/DreamSeaMemory.mat.meta
new file mode 100644
index 000000000..7eb2ac177
--- /dev/null
+++ b/Assets/GameContent/Scene_Dream/Materials/DreamSeaMemory.mat.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: e04544198a7d4f88ba592bf1feebbca2
+NativeFormatImporter:
+ externalObjects: {}
+ mainObjectFileID: 2100000
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/GameContent/Scene_Dream/Materials/DreamSeaMemory.renderTexture b/Assets/GameContent/Scene_Dream/Materials/DreamSeaMemory.renderTexture
new file mode 100644
index 000000000..fbc220f35
--- /dev/null
+++ b/Assets/GameContent/Scene_Dream/Materials/DreamSeaMemory.renderTexture
@@ -0,0 +1,40 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!84 &8400000
+RenderTexture:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_Name: DreamSeaMemory
+ m_ImageContentsHash:
+ serializedVersion: 2
+ Hash: 00000000000000000000000000000000
+ m_ForcedFallbackFormat: 4
+ m_DownscaleFallback: 0
+ m_IsAlphaChannelOptional: 0
+ serializedVersion: 5
+ m_Width: 720
+ m_Height: 1280
+ m_AntiAliasing: 1
+ m_MipCount: -1
+ m_DepthStencilFormat: 0
+ m_ColorFormat: 4
+ m_MipMap: 0
+ m_GenerateMips: 0
+ m_SRGB: 1
+ m_UseDynamicScale: 0
+ m_BindMS: 0
+ m_EnableCompatibleFormat: 1
+ m_EnableRandomWrite: 0
+ m_TextureSettings:
+ serializedVersion: 2
+ m_FilterMode: 1
+ m_Aniso: 0
+ m_MipBias: 0
+ m_WrapU: 1
+ m_WrapV: 1
+ m_WrapW: 1
+ m_Dimension: 2
+ m_VolumeDepth: 1
+ m_ShadowSamplingMode: 2
diff --git a/Assets/GameContent/Scene_Dream/Materials/DreamSeaMemory.renderTexture.meta b/Assets/GameContent/Scene_Dream/Materials/DreamSeaMemory.renderTexture.meta
new file mode 100644
index 000000000..7c3ec219d
--- /dev/null
+++ b/Assets/GameContent/Scene_Dream/Materials/DreamSeaMemory.renderTexture.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: d873791053c443c1a79eedd22a687780
+NativeFormatImporter:
+ externalObjects: {}
+ mainObjectFileID: 8400000
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/GameContent/Scene_Dream/Materials/DreamSeaMemory.shader b/Assets/GameContent/Scene_Dream/Materials/DreamSeaMemory.shader
new file mode 100644
index 000000000..3aee99195
--- /dev/null
+++ b/Assets/GameContent/Scene_Dream/Materials/DreamSeaMemory.shader
@@ -0,0 +1,120 @@
+Shader "AibisDream/DreamSeaMemory"
+{
+ Properties
+ {
+ [PerRendererData] _MainTex ("Poster Texture", 2D) = "white" {}
+ _VideoTex ("Video Texture", 2D) = "black" {}
+ _UseVideo ("Use Prepared Video", Range(0, 1)) = 0
+ _Color ("Tint", Color) = (1,1,1,1)
+ _PixelGrid ("Virtual Pixel Grid", Vector) = (240,426,0,0)
+ _Contrast ("Contrast", Range(0.5, 2)) = 1
+ _Levels ("Luminance Levels", Range(2, 16)) = 10
+ _DitherStrength ("Dither Strength", Range(0, 0.1)) = 0.002
+ }
+
+ SubShader
+ {
+ Tags
+ {
+ "Queue"="Transparent"
+ "IgnoreProjector"="True"
+ "RenderType"="Transparent"
+ "PreviewType"="Plane"
+ "CanUseSpriteAtlas"="True"
+ }
+
+ Cull Off
+ Lighting Off
+ ZWrite Off
+ Blend SrcAlpha OneMinusSrcAlpha
+
+ Pass
+ {
+ CGPROGRAM
+ #pragma vertex vert
+ #pragma fragment frag
+ #pragma target 3.0
+
+ #include "UnityCG.cginc"
+
+ struct appdata
+ {
+ float4 vertex : POSITION;
+ float4 color : COLOR;
+ float2 uv : TEXCOORD0;
+ };
+
+ struct v2f
+ {
+ float4 vertex : SV_POSITION;
+ fixed4 color : COLOR;
+ float2 uv : TEXCOORD0;
+ };
+
+ sampler2D _MainTex;
+ sampler2D _VideoTex;
+ fixed4 _Color;
+ float _UseVideo;
+ float4 _PixelGrid;
+ float _Contrast;
+ float _Levels;
+ float _DitherStrength;
+
+ v2f vert(appdata v)
+ {
+ v2f o;
+ o.vertex = UnityObjectToClipPos(v.vertex);
+ o.uv = v.uv;
+ o.color = v.color * _Color;
+ return o;
+ }
+
+ float Bayer4x4(float2 pixel)
+ {
+ int x = ((int)pixel.x) & 3;
+ int y = ((int)pixel.y) & 3;
+ int index = x + y * 4;
+
+ if (index == 0) return 0.0 / 16.0;
+ if (index == 1) return 8.0 / 16.0;
+ if (index == 2) return 2.0 / 16.0;
+ if (index == 3) return 10.0 / 16.0;
+ if (index == 4) return 12.0 / 16.0;
+ if (index == 5) return 4.0 / 16.0;
+ if (index == 6) return 14.0 / 16.0;
+ if (index == 7) return 6.0 / 16.0;
+ if (index == 8) return 3.0 / 16.0;
+ if (index == 9) return 11.0 / 16.0;
+ if (index == 10) return 1.0 / 16.0;
+ if (index == 11) return 9.0 / 16.0;
+ if (index == 12) return 15.0 / 16.0;
+ if (index == 13) return 7.0 / 16.0;
+ if (index == 14) return 13.0 / 16.0;
+ return 5.0 / 16.0;
+ }
+
+ fixed4 frag(v2f i) : SV_Target
+ {
+ float2 grid = max(_PixelGrid.xy, float2(1.0, 1.0));
+ float2 pixel = floor(i.uv * grid);
+ float2 sampleUv = (pixel + 0.5) / grid;
+
+ fixed4 poster = tex2D(_MainTex, sampleUv);
+ fixed4 video = tex2D(_VideoTex, sampleUv);
+ float3 source = lerp(poster.rgb, video.rgb, saturate(_UseVideo));
+
+ float luminance = dot(source, float3(0.299, 0.587, 0.114));
+ luminance = saturate((luminance - 0.5) * _Contrast + 0.5);
+ luminance = saturate(luminance + (Bayer4x4(pixel) - 0.5) * _DitherStrength);
+
+ float levels = max(2.0, floor(_Levels + 0.5));
+ luminance = floor(luminance * (levels - 1.0) + 0.5) / (levels - 1.0);
+
+ return fixed4(luminance.xxx * i.color.rgb, poster.a * i.color.a);
+ }
+ ENDCG
+ }
+ }
+
+ Fallback "Sprites/Default"
+}
diff --git a/Assets/GameContent/Scene_Dream/Materials/DreamSeaMemory.shader.meta b/Assets/GameContent/Scene_Dream/Materials/DreamSeaMemory.shader.meta
new file mode 100644
index 000000000..3751d3071
--- /dev/null
+++ b/Assets/GameContent/Scene_Dream/Materials/DreamSeaMemory.shader.meta
@@ -0,0 +1,9 @@
+fileFormatVersion: 2
+guid: a8d86af0aff94e1794fa50e65ac676d5
+ShaderImporter:
+ externalObjects: {}
+ defaultTextures: []
+ nonModifiableTextures: []
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/GameContent/Scene_Dream/animation/D2S_SeaMemory_Duo.mp4 b/Assets/GameContent/Scene_Dream/animation/D2S_SeaMemory_Duo.mp4
new file mode 100644
index 000000000..bec1dae7d
--- /dev/null
+++ b/Assets/GameContent/Scene_Dream/animation/D2S_SeaMemory_Duo.mp4
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:7d33a3e1f2477f161f5ba770ccda3bab33c1dd4c404d0c689d873a04894f3bae
+size 2799606
diff --git a/Assets/GameContent/Scene_Dream/animation/D2S_SeaMemory_Duo.mp4.meta b/Assets/GameContent/Scene_Dream/animation/D2S_SeaMemory_Duo.mp4.meta
new file mode 100644
index 000000000..de2b8b706
--- /dev/null
+++ b/Assets/GameContent/Scene_Dream/animation/D2S_SeaMemory_Duo.mp4.meta
@@ -0,0 +1,27 @@
+fileFormatVersion: 2
+guid: 9ed84eb452114d46b39fa43322d186ec
+VideoClipImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ frameRange: 0
+ startFrame: -1
+ endFrame: -1
+ colorSpace: 0
+ deinterlace: 0
+ encodeAlpha: 0
+ flipVertical: 0
+ flipHorizontal: 0
+ importAudio: 0
+ targetSettings:
+ 0:
+ enableTranscoding: 0
+ codec: 0
+ resizeFormat: 0
+ aspectRatio: 0
+ customWidth: 1
+ customHeight: 1
+ bitrateMode: 2
+ spatialQuality: 2
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/GameContent/Scene_Dream/animation/D2S_SeaMemory_Duo_Poster.png b/Assets/GameContent/Scene_Dream/animation/D2S_SeaMemory_Duo_Poster.png
new file mode 100644
index 000000000..2e89428af
--- /dev/null
+++ b/Assets/GameContent/Scene_Dream/animation/D2S_SeaMemory_Duo_Poster.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:827a16ba66367829cf916d8547faa3e817cd3ff782cda2005001e91c4e6b6047
+size 170641
diff --git a/Assets/GameContent/Scene_Dream/animation/D2S_SeaMemory_Duo_Poster.png.meta b/Assets/GameContent/Scene_Dream/animation/D2S_SeaMemory_Duo_Poster.png.meta
new file mode 100644
index 000000000..9e64c317c
--- /dev/null
+++ b/Assets/GameContent/Scene_Dream/animation/D2S_SeaMemory_Duo_Poster.png.meta
@@ -0,0 +1,114 @@
+fileFormatVersion: 2
+guid: 0d38a05e2cc64f5bb1958d3bdf78925d
+TextureImporter:
+ internalIDToNameTable: []
+ externalObjects: {}
+ serializedVersion: 12
+ mipmaps:
+ mipMapMode: 0
+ enableMipMap: 0
+ sRGBTexture: 1
+ linearTexture: 0
+ fadeOut: 0
+ borderMipMap: 0
+ mipMapsPreserveCoverage: 0
+ alphaTestReferenceValue: 0.5
+ mipMapFadeDistanceStart: 1
+ mipMapFadeDistanceEnd: 3
+ bumpmap:
+ convertToNormalMap: 0
+ externalNormalMap: 0
+ heightScale: 0.25
+ normalMapFilter: 0
+ flipGreenChannel: 0
+ isReadable: 0
+ streamingMipmaps: 0
+ streamingMipmapsPriority: 0
+ vTOnly: 0
+ ignoreMipmapLimit: 0
+ grayScaleToAlpha: 0
+ generateCubemap: 6
+ cubemapConvolution: 0
+ seamlessCubemap: 0
+ textureFormat: 1
+ maxTextureSize: 2048
+ textureSettings:
+ serializedVersion: 2
+ filterMode: 1
+ aniso: 1
+ mipBias: 0
+ wrapU: 1
+ wrapV: 1
+ wrapW: 1
+ nPOTScale: 0
+ lightmap: 0
+ compressionQuality: 50
+ spriteMode: 1
+ spriteExtrude: 1
+ spriteMeshType: 1
+ alignment: 0
+ spritePivot: {x: 0.5, y: 0.5}
+ spritePixelsToUnits: 100
+ spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+ spriteGenerateFallbackPhysicsShape: 0
+ alphaUsage: 1
+ alphaIsTransparency: 1
+ spriteTessellationDetail: -1
+ textureType: 8
+ textureShape: 1
+ singleChannelComponent: 0
+ flipbookRows: 1
+ flipbookColumns: 1
+ maxTextureSizeSet: 0
+ compressionQualitySet: 0
+ textureFormatSet: 0
+ ignorePngGamma: 0
+ applyGammaDecoding: 0
+ swizzle: 50462976
+ cookieLightType: 0
+ platformSettings:
+ - serializedVersion: 3
+ buildTarget: DefaultTexturePlatform
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 3
+ buildTarget: Standalone
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ spriteSheet:
+ serializedVersion: 2
+ sprites: []
+ outline: []
+ physicsShape: []
+ bones: []
+ spriteID: 5e97eb03825dee720800000000000000
+ internalID: 0
+ vertices: []
+ indices:
+ edges: []
+ weights: []
+ secondaryTextures: []
+ nameFileIdTable: {}
+ mipmapLimitGroupName:
+ pSDRemoveMatte: 0
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Render/KiteFocusSprite.shader b/Assets/Render/KiteFocusSprite.shader
index 3f623f71b..fa0175d49 100644
--- a/Assets/Render/KiteFocusSprite.shader
+++ b/Assets/Render/KiteFocusSprite.shader
@@ -8,6 +8,7 @@ Shader "AIBIS/KiteFocusSprite"
[Header(Focus Blur)]
_BlurAmount ("Blur Amount", Range(0, 1)) = 0
_BlurSize ("Blur Size", Range(0, 0.05)) = 0.012
+ [HideInInspector] _UseSoftFocus ("Use Soft Focus", Float) = 0
_DefocusBloom ("Defocus Highlight Bloom", Range(0, 1)) = 0.32
_HaloStrength ("Large Circle Halo", Range(0, 0.6)) = 0.22
_ChromaticFringe ("Lens Color Fringe", Range(0, 0.6)) = 0.14
@@ -75,6 +76,7 @@ Shader "AIBIS/KiteFocusSprite"
fixed4 _Color;
float _BlurAmount;
float _BlurSize;
+ float _UseSoftFocus;
float _DefocusBloom;
float _HaloStrength;
float _ChromaticFringe;
@@ -150,6 +152,33 @@ Shader "AIBIS/KiteFocusSprite"
return result * 0.125;
}
+ half4 SoftFocusBlur(float2 uv, float radius)
+ {
+ float nearRadius = radius * 0.48;
+ half4 result = samplePremultiplied(uv) * 0.20;
+
+ result += samplePremultiplied(uv + float2( nearRadius, 0)) * 0.08;
+ result += samplePremultiplied(uv + float2(-nearRadius, 0)) * 0.08;
+ result += samplePremultiplied(uv + float2(0, nearRadius)) * 0.08;
+ result += samplePremultiplied(uv + float2(0, -nearRadius)) * 0.08;
+
+ result += samplePremultiplied(uv + float2( nearRadius, nearRadius)) * 0.055;
+ result += samplePremultiplied(uv + float2(-nearRadius, nearRadius)) * 0.055;
+ result += samplePremultiplied(uv + float2( nearRadius, -nearRadius)) * 0.055;
+ result += samplePremultiplied(uv + float2(-nearRadius, -nearRadius)) * 0.055;
+
+ result += samplePremultiplied(uv + float2( radius, 0)) * 0.04;
+ result += samplePremultiplied(uv + float2(-radius, 0)) * 0.04;
+ result += samplePremultiplied(uv + float2(0, radius)) * 0.04;
+ result += samplePremultiplied(uv + float2(0, -radius)) * 0.04;
+
+ result += samplePremultiplied(uv + float2( radius, radius)) * 0.025;
+ result += samplePremultiplied(uv + float2(-radius, radius)) * 0.025;
+ result += samplePremultiplied(uv + float2( radius, -radius)) * 0.025;
+ result += samplePremultiplied(uv + float2(-radius, -radius)) * 0.025;
+ return result;
+ }
+
fixed4 frag(v2f i) : SV_Target
{
float blurAmt = saturate(_BlurAmount);
@@ -164,6 +193,18 @@ Shader "AIBIS/KiteFocusSprite"
float2 texel = max(abs(_MainTex_TexelSize.xy), float2(1e-5, 1e-5));
float radius = (length(texel) * 0.5 + _BlurSize) * (0.28 + blurAmt * 1.72);
+
+ if (_UseSoftFocus > 0.5)
+ {
+ half4 softBlurred = SoftFocusBlur(i.uv, radius);
+ half4 softSharp = sharp;
+ softSharp.rgb *= softSharp.a;
+ half4 softColor = lerp(softSharp, softBlurred, smoothstep(0.0, 1.0, blurAmt));
+ softColor.rgb *= i.color.rgb * i.color.a;
+ softColor.a *= i.color.a;
+ return softColor;
+ }
+
half4 disc = ApertureDisc(i.uv, radius);
half4 halo = WideHalo(i.uv, radius * 1.85);
half4 blurred = lerp(disc, halo, saturate(_HaloStrength * blurAmt));
diff --git a/Assets/Scenes/梦境.unity b/Assets/Scenes/梦境.unity
index 3261017bf..52020e336 100644
--- a/Assets/Scenes/梦境.unity
+++ b/Assets/Scenes/梦境.unity
@@ -421,20 +421,20 @@ MonoBehaviour:
m_EditorClassIdentifier:
director: {fileID: 132823524}
starfieldRenderer: {fileID: 1420961919}
- initialBlur: 0.78
- blurSize: 0.009
+ initialBlur: 0.58
+ blurSize: 0.0045
firstAttemptStart: 0.45
- firstFocusDuration: 0.48
- firstNearFocusBlur: 0.38
- firstHoldDuration: 0.14
- firstSlipDuration: 0.38
- firstSlipBlur: 0.72
- secondAttemptStart: 1.72
- secondFocusDuration: 0.55
- secondNearFocusBlur: 0.28
- secondHoldDuration: 0.18
- secondSlipDuration: 0.46
- unresolvedBlur: 0.62
+ firstFocusDuration: 0.72
+ firstNearFocusBlur: 0.16
+ firstHoldDuration: 0.1
+ firstSlipDuration: 0.42
+ firstSlipBlur: 0.52
+ secondAttemptStart: 1.85
+ secondFocusDuration: 0.82
+ secondNearFocusBlur: 0.08
+ secondHoldDuration: 0.12
+ secondSlipDuration: 0.5
+ unresolvedBlur: 0.44
blurFadeStart: 4
blurFadeDuration: 3.2
--- !u!1 &218986882
@@ -663,6 +663,9 @@ MonoBehaviour:
frameRenderers:
- {fileID: 1847534534}
sceneBlackOverlay: {fileID: 1373573734}
+ glassSeaVideoClip: {fileID: 32900000, guid: 9ed84eb452114d46b39fa43322d186ec, type: 3}
+ glassSeaTargetTexture: {fileID: 8400000, guid: d873791053c443c1a79eedd22a687780, type: 2}
+ glassSeaPrepareTimeout: 5
terminalUI: {fileID: 1195418928}
doorShellTransform: {fileID: 0}
terminalShapeReference: {fileID: 1847534534}
@@ -1510,15 +1513,27 @@ MonoBehaviour:
typewriterCharDelay: 0.025
bootSpeedMultiplier: 4
bootSequence:
- - text: '>> SYSTEM: Reality_Engine.sys stopped.'
+ - text: 'root@sys:~$ dream.render --stop --session 0742'
style: 3
- delayAfter: 0.2
- - text: '>> SYSTEM: Rendering underlying code...'
+ delayAfter: 0.15
+ - text: '[ OK ] Dream renderer stopped.'
+ style: 0
+ delayAfter: 0.25
+ - text: '[ RAW ] Untitled_Greybox_0 exposed.'
+ style: 0
+ delayAfter: 0.35
+ - text: 'root@sys:~$ trace --tail /log/dream_0742.rec'
style: 3
- delayAfter: 0.2
- - text: '>> LOADING: Ocean_Simulation.sh ... OK'
+ delayAfter: 0.15
+ - text: '[0005] audio.play("sea_ambient", source=door.rear)'
style: 3
- delayAfter: 0.8
+ delayAfter: 0.1
+ - text: '[0006] memory.project("glass_sea")'
+ style: 3
+ delayAfter: 0.1
+ - text: '[ WARN ] memory source unresolved; overflow detected.'
+ style: 1
+ delayAfter: 0.4
- text: "root@sys:~$ print_string(\"\u6D77\u662F\u5B58\u5728\u7684\u5417?\")"
style: 2
delayAfter: 0.5
@@ -1791,7 +1806,7 @@ SpriteRenderer:
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- - {fileID: 2100000, guid: a97c105638bdf8b4a8650670310a4cd3, type: 2}
+ - {fileID: 2100000, guid: 78265ded39f017d45a2afc117effab89, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
@@ -2602,7 +2617,7 @@ SpriteRenderer:
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- - {fileID: 1583441590}
+ - {fileID: 2100000, guid: e04544198a7d4f88ba592bf1feebbca2, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
@@ -2623,7 +2638,7 @@ SpriteRenderer:
m_SortingLayerID: 844151739
m_SortingLayer: 1
m_SortingOrder: 5
- m_Sprite: {fileID: 21300000, guid: 8e0b68f1e994fc24b80e5a480c61602b, type: 3}
+ m_Sprite: {fileID: 21300000, guid: 0d38a05e2cc64f5bb1958d3bdf78925d, type: 3}
m_Color: {r: 1, g: 1, b: 1, a: 0}
m_FlipX: 0
m_FlipY: 0
@@ -2643,8 +2658,8 @@ Transform:
m_GameObject: {fileID: 1961593256904873106}
serializedVersion: 2
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
- m_LocalPosition: {x: -0.07, y: -0.03, z: 0}
- m_LocalScale: {x: 0.7074555, y: 0.62522215, z: 0.8706811}
+ m_LocalPosition: {x: 0.02, y: 0.03, z: 0}
+ m_LocalScale: {x: 0.636667, y: 0.615, z: 0.8706811}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 450371655}
diff --git a/Assets/Scripts/SceneManagement/DreamDoorSystem.cs b/Assets/Scripts/SceneManagement/DreamDoorSystem.cs
index 233cf778a..beda35eec 100644
--- a/Assets/Scripts/SceneManagement/DreamDoorSystem.cs
+++ b/Assets/Scripts/SceneManagement/DreamDoorSystem.cs
@@ -4,6 +4,7 @@ using AibisDream.UI;
using AibisDream.Utility;
using DG.Tweening;
using UnityEngine;
+using UnityEngine.Video;
namespace AibisDream
{
@@ -22,6 +23,11 @@ namespace AibisDream
[SerializeField] private Renderer[] frameRenderers;
[SerializeField] private SpriteRenderer sceneBlackOverlay;
+ [Header("Door Sea Memory")]
+ [SerializeField] private VideoClip glassSeaVideoClip;
+ [SerializeField] private RenderTexture glassSeaTargetTexture;
+ [SerializeField, Min(0.5f)] private float glassSeaPrepareTimeout = 5f;
+
[Header("Terminal(挂在 Door 子节点上即可,无需 Canvas)")]
[SerializeField] private DreamTerminalUI terminalUI;
@@ -56,6 +62,14 @@ namespace AibisDream
private Vector3 _terminalUiOriginalLocalPosition;
private Transform _doorShellTransform;
private Vector3 _doorShellOriginalScale;
+ private VideoPlayer _glassSeaVideoPlayer;
+ private Material _glassSeaMaterial;
+ private bool _glassSeaVideoPrepared;
+ private bool _glassSeaVideoFailed;
+ private bool _glassSeaFrameReady;
+ private DoorPresentationPhase _presentationPhase = DoorPresentationPhase.Hidden;
+
+ private static readonly int UseVideoId = Shader.PropertyToID("_UseVideo");
private enum FrameHidePhase
{
@@ -64,6 +78,15 @@ namespace AibisDream
OnTerminal
}
+ private enum DoorPresentationPhase
+ {
+ Hidden,
+ Door,
+ GlassSea,
+ CodeSea,
+ Terminal
+ }
+
public override void OnSingletonInit()
{
if (doorRoot != null)
@@ -75,10 +98,14 @@ namespace AibisDream
if (glassSea != null)
{
+ _glassSeaMaterial = glassSea.material;
+ SetGlassSeaVideoVisible(false);
glassSea.SetAlpha(0);
glassSea.gameObject.SetActive(false);
}
+ InitializeGlassSeaVideo();
+
if (codeSea != null)
{
codeSea.SetAlpha(0);
@@ -108,19 +135,33 @@ namespace AibisDream
_terminalUiOriginalScale = _terminalUiTransform.localScale;
_terminalUiOriginalLocalPosition = _terminalUiTransform.localPosition;
}
+
+ _presentationPhase = DoorPresentationPhase.Hidden;
+ }
+
+ public override void OnSingletonDestroy()
+ {
+ if (_glassSeaVideoPlayer != null)
+ {
+ _glassSeaVideoPlayer.prepareCompleted -= OnGlassSeaVideoPrepared;
+ _glassSeaVideoPlayer.frameReady -= OnGlassSeaVideoFrameReady;
+ _glassSeaVideoPlayer.errorReceived -= OnGlassSeaVideoError;
+ }
+
+ base.OnSingletonDestroy();
}
///
- /// 显示整个门(仅 background + Frame,GlassSea/CodeSea 保持隐藏直到各自命令调用)
+ /// 显示普通门。该命令是一个完整状态切换,会清掉之前残留的海景、代码海浪和终端。
///
public IEnumerator ShowDoor(float duration = 0f)
{
- if (doorRoot != null)
- doorRoot.gameObject.SetActive(true);
-
- SetBackgroundVisible(true);
- SetFrameVisible(true);
- ResetDoorShellScale();
+ StopAllEffects();
+ EnsureDoorBaseVisible();
+ ResetGlassSeaImmediate(stopVideo: true);
+ ResetCodeSeaImmediate();
+ ResetTerminalImmediate();
+ _presentationPhase = DoorPresentationPhase.Door;
if (duration > 0f)
yield return new WaitForSeconds(duration);
@@ -139,18 +180,48 @@ namespace AibisDream
public IEnumerator ShowGlassSea(float duration = 2f)
{
+ if (glassSea == null)
+ yield break;
+
+ StopAllEffects();
+ EnsureDoorBaseVisible();
+ ResetCodeSeaImmediate();
+ ResetTerminalImmediate();
+
+ if (_presentationPhase != DoorPresentationPhase.GlassSea || !glassSea.gameObject.activeSelf)
+ glassSea.SetAlpha(0f);
+
glassSea.gameObject.SetActive(true);
- yield return glassSea.DOFade(1f, duration)
+ _presentationPhase = DoorPresentationPhase.GlassSea;
+ yield return EnsureGlassSeaVideoPlaying();
+ yield return glassSea.DOFade(1f, Mathf.Max(0f, duration))
.SetEase(Ease.InOutSine)
.WaitForCompletion();
}
public IEnumerator HideGlassSea(float duration = 1f)
{
- yield return glassSea.DOFade(0f, duration)
- .SetEase(Ease.InOutSine)
- .WaitForCompletion();
+ if (glassSea == null)
+ {
+ PauseGlassSeaVideo();
+ yield break;
+ }
+
+ DOTween.Kill(glassSea);
+
+ if (glassSea.gameObject.activeSelf)
+ {
+ yield return glassSea.DOFade(0f, Mathf.Max(0f, duration))
+ .SetEase(Ease.InOutSine)
+ .WaitForCompletion();
+ }
+
+ glassSea.SetAlpha(0f);
glassSea.gameObject.SetActive(false);
+ PauseGlassSeaVideo();
+
+ if (_presentationPhase == DoorPresentationPhase.GlassSea)
+ _presentationPhase = DoorPresentationPhase.Door;
}
#endregion
@@ -159,21 +230,39 @@ namespace AibisDream
public IEnumerator ShowCodeSea(float duration = 2f)
{
+ if (codeSea == null)
+ yield break;
+
+ StopAllEffects();
+ EnsureDoorBaseVisible();
+ ResetTerminalImmediate();
+
+ codeSea.color = _codeSeaOriginalColor;
+ if (_codeSeaTransform != null)
+ _codeSeaTransform.localScale = _codeSeaOriginalScale;
+ codeSea.SetAlpha(0f);
codeSea.gameObject.SetActive(true);
+ _presentationPhase = DoorPresentationPhase.CodeSea;
+
if (frameHidePhase == FrameHidePhase.OnCodeSea)
SetFrameVisible(false);
var seq = DOTween.Sequence();
- seq.Append(codeSea.DOFade(1f, duration).SetEase(Ease.InOutSine));
+ seq.Append(codeSea.DOFade(1f, Mathf.Max(0f, duration)).SetEase(Ease.InOutSine));
if (glassSea != null && glassSea.gameObject.activeSelf)
{
- seq.Join(glassSea.DOFade(0f, duration * 0.7f).SetEase(Ease.InSine));
+ seq.Join(glassSea.DOFade(0f, Mathf.Max(0f, duration) * 0.7f).SetEase(Ease.InSine));
}
yield return seq.WaitForCompletion();
if (glassSea != null)
+ {
+ glassSea.SetAlpha(0f);
glassSea.gameObject.SetActive(false);
+ }
+
+ StopGlassSeaVideo();
}
#endregion
@@ -182,6 +271,7 @@ namespace AibisDream
public IEnumerator ShowTerminal()
{
+ _presentationPhase = DoorPresentationPhase.Terminal;
yield return AnimateDoorToTerminal();
if (terminalUI != null)
{
@@ -214,6 +304,8 @@ namespace AibisDream
if (glassSea != null)
glassSea.gameObject.SetActive(false);
+ StopGlassSeaVideo();
+
if (codeSea != null)
codeSea.gameObject.SetActive(false);
@@ -288,6 +380,7 @@ namespace AibisDream
public void StopAllEffects()
{
+ DOTween.Kill(glassSea);
DOTween.Kill(_codeSeaTransform);
DOTween.Kill(codeSea);
DOTween.Kill(_terminalUiTransform);
@@ -314,6 +407,8 @@ namespace AibisDream
glassSea.gameObject.SetActive(false);
}
+ StopGlassSeaVideo();
+
if (codeSea != null)
{
codeSea.color = _codeSeaOriginalColor;
@@ -340,6 +435,193 @@ namespace AibisDream
if (sceneBlackOverlay != null)
sceneBlackOverlay.SetAlpha(0);
+
+ _presentationPhase = DoorPresentationPhase.Hidden;
+ }
+
+ #endregion
+
+ #region Presentation State
+
+ private void EnsureDoorBaseVisible()
+ {
+ if (doorRoot != null)
+ doorRoot.gameObject.SetActive(true);
+
+ SetBackgroundVisible(true);
+ SetFrameVisible(true);
+ ResetDoorShellScale();
+ }
+
+ private void ResetGlassSeaImmediate(bool stopVideo)
+ {
+ DOTween.Kill(glassSea);
+
+ if (glassSea != null)
+ {
+ glassSea.SetAlpha(0f);
+ glassSea.gameObject.SetActive(false);
+ }
+
+ if (stopVideo)
+ StopGlassSeaVideo();
+ else
+ PauseGlassSeaVideo();
+ }
+
+ private void ResetCodeSeaImmediate()
+ {
+ DOTween.Kill(_codeSeaTransform);
+ DOTween.Kill(codeSea);
+
+ if (codeSea == null)
+ return;
+
+ codeSea.color = _codeSeaOriginalColor;
+ codeSea.SetAlpha(0f);
+ codeSea.gameObject.SetActive(false);
+
+ if (_codeSeaTransform != null)
+ _codeSeaTransform.localScale = _codeSeaOriginalScale;
+ }
+
+ private void ResetTerminalImmediate()
+ {
+ DOTween.Kill(_terminalUiTransform);
+
+ if (terminalUI != null)
+ {
+ terminalUI.Alpha = 0f;
+ terminalUI.ResetVisualState();
+ }
+
+ if (_terminalUiTransform != null)
+ {
+ _terminalUiTransform.localPosition = _terminalUiOriginalLocalPosition;
+ _terminalUiTransform.localScale = _terminalUiOriginalScale;
+ }
+
+ if (flashOverlay != null)
+ flashOverlay.SetAlpha(0f);
+
+ if (sceneBlackOverlay != null)
+ sceneBlackOverlay.SetAlpha(0f);
+ }
+
+ #endregion
+
+ #region GlassSea Video
+
+ private void InitializeGlassSeaVideo()
+ {
+ if (glassSeaVideoClip == null || glassSeaTargetTexture == null)
+ {
+ SetGlassSeaVideoVisible(false);
+ return;
+ }
+
+ _glassSeaVideoPlayer = GetComponent();
+ if (_glassSeaVideoPlayer == null)
+ _glassSeaVideoPlayer = gameObject.AddComponent();
+
+ _glassSeaVideoPlayer.playOnAwake = false;
+ _glassSeaVideoPlayer.source = VideoSource.VideoClip;
+ _glassSeaVideoPlayer.clip = glassSeaVideoClip;
+ _glassSeaVideoPlayer.renderMode = VideoRenderMode.RenderTexture;
+ _glassSeaVideoPlayer.targetTexture = glassSeaTargetTexture;
+ _glassSeaVideoPlayer.audioOutputMode = VideoAudioOutputMode.None;
+ _glassSeaVideoPlayer.isLooping = true;
+ _glassSeaVideoPlayer.skipOnDrop = true;
+ _glassSeaVideoPlayer.waitForFirstFrame = true;
+ _glassSeaVideoPlayer.sendFrameReadyEvents = true;
+
+ _glassSeaVideoPlayer.prepareCompleted += OnGlassSeaVideoPrepared;
+ _glassSeaVideoPlayer.frameReady += OnGlassSeaVideoFrameReady;
+ _glassSeaVideoPlayer.errorReceived += OnGlassSeaVideoError;
+
+ _glassSeaVideoPrepared = false;
+ _glassSeaVideoFailed = false;
+ _glassSeaFrameReady = false;
+ SetGlassSeaVideoVisible(false);
+ _glassSeaVideoPlayer.Prepare();
+ }
+
+ private IEnumerator EnsureGlassSeaVideoPlaying()
+ {
+ if (_glassSeaVideoPlayer == null || _glassSeaVideoFailed)
+ {
+ SetGlassSeaVideoVisible(false);
+ yield break;
+ }
+
+ if (!_glassSeaVideoPrepared && !_glassSeaVideoPlayer.isPrepared)
+ {
+ float deadline = Time.realtimeSinceStartup + glassSeaPrepareTimeout;
+ while (!_glassSeaVideoPrepared && !_glassSeaVideoFailed && Time.realtimeSinceStartup < deadline)
+ yield return null;
+
+ if (!_glassSeaVideoPrepared && !_glassSeaVideoPlayer.isPrepared)
+ {
+ _glassSeaVideoFailed = true;
+ _glassSeaVideoPlayer.Stop();
+ SetGlassSeaVideoVisible(false);
+ Debug.LogWarning("[DreamDoorSystem] 海景视频准备超时,回退到静态海报。", this);
+ yield break;
+ }
+ }
+
+ _glassSeaVideoPrepared = true;
+ if (!_glassSeaVideoPlayer.isPlaying)
+ _glassSeaVideoPlayer.Play();
+
+ if (!_glassSeaFrameReady)
+ {
+ float firstFrameDeadline = Time.realtimeSinceStartup + 1f;
+ while (!_glassSeaFrameReady && !_glassSeaVideoFailed && Time.realtimeSinceStartup < firstFrameDeadline)
+ yield return null;
+ }
+ }
+
+ private void PauseGlassSeaVideo()
+ {
+ if (_glassSeaVideoPlayer != null && _glassSeaVideoPlayer.isPlaying)
+ _glassSeaVideoPlayer.Pause();
+ }
+
+ private void StopGlassSeaVideo()
+ {
+ if (_glassSeaVideoPlayer != null)
+ _glassSeaVideoPlayer.Stop();
+
+ _glassSeaFrameReady = false;
+ SetGlassSeaVideoVisible(false);
+ }
+
+ private void OnGlassSeaVideoPrepared(VideoPlayer source)
+ {
+ _glassSeaVideoPrepared = true;
+ _glassSeaVideoFailed = false;
+ }
+
+ private void OnGlassSeaVideoFrameReady(VideoPlayer source, long frameIndex)
+ {
+ _glassSeaFrameReady = true;
+ SetGlassSeaVideoVisible(true);
+ }
+
+ private void OnGlassSeaVideoError(VideoPlayer source, string message)
+ {
+ _glassSeaVideoFailed = true;
+ _glassSeaVideoPrepared = false;
+ _glassSeaFrameReady = false;
+ SetGlassSeaVideoVisible(false);
+ Debug.LogWarning($"[DreamDoorSystem] 海景视频播放失败,回退到静态海报:{message}", this);
+ }
+
+ private void SetGlassSeaVideoVisible(bool visible)
+ {
+ if (_glassSeaMaterial != null && _glassSeaMaterial.HasProperty(UseVideoId))
+ _glassSeaMaterial.SetFloat(UseVideoId, visible ? 1f : 0f);
}
#endregion
diff --git a/Assets/Scripts/SceneManagement/TimelineKit/StarfieldDefocusController.cs b/Assets/Scripts/SceneManagement/TimelineKit/StarfieldDefocusController.cs
index e11595444..747efc7e9 100644
--- a/Assets/Scripts/SceneManagement/TimelineKit/StarfieldDefocusController.cs
+++ b/Assets/Scripts/SceneManagement/TimelineKit/StarfieldDefocusController.cs
@@ -1,5 +1,7 @@
+using System.Collections;
using UnityEngine;
using UnityEngine.Playables;
+using Yarn.Unity;
namespace AibisDream
{
@@ -12,6 +14,7 @@ namespace AibisDream
{
private static readonly int BlurAmountId = Shader.PropertyToID("_BlurAmount");
private static readonly int BlurSizeId = Shader.PropertyToID("_BlurSize");
+ private static readonly int UseSoftFocusId = Shader.PropertyToID("_UseSoftFocus");
[Header("References")]
[SerializeField] private PlayableDirector director;
@@ -60,6 +63,114 @@ namespace AibisDream
private MaterialPropertyBlock propertyBlock;
+ [YarnCommand("starfield_prepare")]
+ public static void PrepareStarfieldCommand()
+ {
+ var controller = FindFirstObjectByType();
+ if (controller == null)
+ {
+ Debug.LogWarning("[StarfieldDefocusController] No active controller found for starfield_prepare.");
+ return;
+ }
+
+ controller.PrepareStarfield();
+ }
+
+ [YarnCommand("starfield_focus")]
+ public static IEnumerator PlayFocusAttemptCommand(int attempt)
+ {
+ var controller = FindFirstObjectByType();
+ if (controller == null)
+ {
+ Debug.LogWarning("[StarfieldDefocusController] No active controller found for starfield_focus.");
+ yield break;
+ }
+
+ yield return controller.PlayFocusAttempt(attempt);
+ }
+
+ [YarnCommand("starfield_descend")]
+ public static IEnumerator DescendToSeaCommand()
+ {
+ var controller = FindFirstObjectByType();
+ if (controller == null)
+ {
+ Debug.LogWarning("[StarfieldDefocusController] No active controller found for starfield_descend.");
+ yield break;
+ }
+
+ yield return controller.DescendToSea();
+ }
+
+ private void PrepareStarfield()
+ {
+ if (!TryValidateReferences())
+ {
+ return;
+ }
+
+ director.Stop();
+ SetTimelineTime(0f);
+ }
+
+ private IEnumerator PlayFocusAttempt(int attempt)
+ {
+ if (!TryValidateReferences())
+ {
+ yield break;
+ }
+
+ float startTime;
+ float endTime;
+ switch (attempt)
+ {
+ case 1:
+ startTime = firstAttemptStart;
+ endTime = firstAttemptStart + firstFocusDuration + firstHoldDuration + firstSlipDuration;
+ break;
+ case 2:
+ startTime = secondAttemptStart;
+ endTime = secondAttemptStart + secondFocusDuration + secondHoldDuration + secondSlipDuration;
+ break;
+ default:
+ Debug.LogWarning($"[StarfieldDefocusController] Unsupported focus attempt '{attempt}'. Expected 1 or 2.", this);
+ yield break;
+ }
+
+ director.Pause();
+ SetTimelineTime(startTime);
+
+ float duration = Mathf.Max(0.01f, endTime - startTime);
+ float elapsed = 0f;
+ while (elapsed < duration)
+ {
+ elapsed += Time.deltaTime;
+ SetTimelineTime(Mathf.Lerp(startTime, endTime, Mathf.Clamp01(elapsed / duration)));
+ yield return null;
+ }
+
+ SetTimelineTime(endTime);
+ }
+
+ private IEnumerator DescendToSea()
+ {
+ if (!TryValidateReferences())
+ {
+ yield break;
+ }
+
+ director.Pause();
+ SetTimelineTime(blurFadeStart);
+ director.Play();
+
+ while (director.state == PlayState.Playing)
+ {
+ yield return null;
+ }
+
+ ApplyAtTime((float)director.time);
+ }
+
private void Reset()
{
director = GetComponent();
@@ -87,6 +198,45 @@ namespace AibisDream
ApplyAtTime((float)director.time);
}
+ private bool TryValidateReferences()
+ {
+ if (director == null)
+ {
+ Debug.LogWarning("[StarfieldDefocusController] PlayableDirector is not assigned.", this);
+ return false;
+ }
+
+ if (director.playableAsset == null)
+ {
+ Debug.LogWarning("[StarfieldDefocusController] PlayableDirector has no Timeline asset.", this);
+ return false;
+ }
+
+ if (starfieldRenderer == null)
+ {
+ Debug.LogWarning("[StarfieldDefocusController] Starfield SpriteRenderer is not assigned.", this);
+ return false;
+ }
+
+ var material = starfieldRenderer.sharedMaterial;
+ if (material == null || !material.HasProperty(BlurAmountId) || !material.HasProperty(BlurSizeId))
+ {
+ Debug.LogWarning(
+ "[StarfieldDefocusController] Starfield material must support _BlurAmount and _BlurSize.",
+ starfieldRenderer);
+ return false;
+ }
+
+ return true;
+ }
+
+ private void SetTimelineTime(float time)
+ {
+ director.time = Mathf.Clamp(time, 0f, (float)director.duration);
+ director.Evaluate();
+ ApplyAtTime((float)director.time);
+ }
+
private void ApplyAtTime(float time)
{
if (starfieldRenderer == null)
@@ -98,6 +248,7 @@ namespace AibisDream
starfieldRenderer.GetPropertyBlock(propertyBlock);
propertyBlock.SetFloat(BlurAmountId, EvaluateBlur(time));
propertyBlock.SetFloat(BlurSizeId, blurSize);
+ propertyBlock.SetFloat(UseSoftFocusId, 1f);
starfieldRenderer.SetPropertyBlock(propertyBlock);
}
@@ -118,7 +269,10 @@ namespace AibisDream
if (time < firstFocusEnd)
{
- return SmoothLerp(initialBlur, firstNearFocusBlur, InverseLerp(firstAttemptStart, firstFocusEnd, time));
+ return EvaluateFocusTurn(
+ initialBlur,
+ firstNearFocusBlur,
+ InverseLerp(firstAttemptStart, firstFocusEnd, time));
}
if (time < firstHoldEnd)
@@ -138,7 +292,10 @@ namespace AibisDream
if (time < secondFocusEnd)
{
- return SmoothLerp(firstSlipBlur, secondNearFocusBlur, InverseLerp(secondAttemptStart, secondFocusEnd, time));
+ return EvaluateFocusTurn(
+ firstSlipBlur,
+ secondNearFocusBlur,
+ InverseLerp(secondAttemptStart, secondFocusEnd, time));
}
if (time < secondHoldEnd)
@@ -159,6 +316,29 @@ namespace AibisDream
return SmoothLerp(unresolvedBlur, 0f, InverseLerp(blurFadeStart, blurFadeStart + blurFadeDuration, time));
}
+ ///
+ /// Simulates turning a focus ring toward focus, passing the focal plane,
+ /// then reversing back. The following slip phase still lets the image
+ /// drift out of focus, keeping both attempts deliberately unsuccessful.
+ ///
+ private static float EvaluateFocusTurn(float fromBlur, float nearFocusBlur, float t)
+ {
+ t = Mathf.Clamp01(t);
+ float overshootBlur = Mathf.Lerp(nearFocusBlur, fromBlur, 0.68f);
+
+ if (t < 0.44f)
+ {
+ return SmoothLerp(fromBlur, nearFocusBlur, t / 0.44f);
+ }
+
+ if (t < 0.72f)
+ {
+ return SmoothLerp(nearFocusBlur, overshootBlur, (t - 0.44f) / 0.28f);
+ }
+
+ return SmoothLerp(overshootBlur, nearFocusBlur, (t - 0.72f) / 0.28f);
+ }
+
private static float InverseLerp(float from, float to, float value)
{
return Mathf.Clamp01((value - from) / Mathf.Max(0.0001f, to - from));
diff --git a/Assets/Scripts/UI/Components/DreamTerminalUI.cs b/Assets/Scripts/UI/Components/DreamTerminalUI.cs
index c5bacd596..e2f7ba2eb 100644
--- a/Assets/Scripts/UI/Components/DreamTerminalUI.cs
+++ b/Assets/Scripts/UI/Components/DreamTerminalUI.cs
@@ -98,14 +98,14 @@ namespace AibisDream.UI
[Header("启动日志")]
[SerializeField] private List bootSequence = new()
{
- new BootLogEntry { text = "root@sys:~$ ./boot --test", style = LogStyle.Dim, delayAfter = 0.2f },
- new BootLogEntry { text = "[ OK ] Test passed.", style = LogStyle.Normal, delayAfter = 0.2f },
- new BootLogEntry { text = "root@sys:~$ render --target ocean && aplay sea.wav", style = LogStyle.Dim, delayAfter = 0.2f },
- new BootLogEntry { text = "[ OK ] Ocean rendered. Audio stream started.", style = LogStyle.Normal, delayAfter = 0.2f },
- new BootLogEntry { text = "root@sys:~$ render --stop", style = LogStyle.Dim, delayAfter = 0.2f },
- new BootLogEntry { text = "[ OK ] Render pipeline closed.", style = LogStyle.Normal, delayAfter = 0.2f },
- new BootLogEntry { text = "root@sys:~$ showcode", style = LogStyle.Alert, delayAfter = 0.2f },
- new BootLogEntry { text = "root@sys:~$ print_string(\"海是存在的吗?\")", style = LogStyle.Highlight, delayAfter = 0.2f },
+ new BootLogEntry { text = "root@sys:~$ dream.render --stop --session 0742", style = LogStyle.Dim, delayAfter = 0.15f },
+ new BootLogEntry { text = "[ OK ] Dream renderer stopped.", style = LogStyle.Normal, delayAfter = 0.25f },
+ new BootLogEntry { text = "[ RAW ] Untitled_Greybox_0 exposed.", style = LogStyle.Normal, delayAfter = 0.35f },
+ new BootLogEntry { text = "root@sys:~$ trace --tail /log/dream_0742.rec", style = LogStyle.Dim, delayAfter = 0.15f },
+ new BootLogEntry { text = "[0005] audio.play(\"sea_ambient\", source=door.rear)", style = LogStyle.Dim, delayAfter = 0.1f },
+ new BootLogEntry { text = "[0006] memory.project(\"glass_sea\")", style = LogStyle.Dim, delayAfter = 0.1f },
+ new BootLogEntry { text = "[ WARN ] memory source unresolved; overflow detected.", style = LogStyle.Alert, delayAfter = 0.4f },
+ new BootLogEntry { text = "root@sys:~$ print_string(\"海是存在的吗?\")", style = LogStyle.Highlight, delayAfter = 0.5f },
};
#endregion
diff --git a/Assets/Yarn/FP/FP_Day1_sleep/FP_Day1_sleep.yarn b/Assets/Yarn/FP/FP_Day1_sleep/FP_Day1_sleep.yarn
index bb12b1d38..e42fbf6ea 100644
--- a/Assets/Yarn/FP/FP_Day1_sleep/FP_Day1_sleep.yarn
+++ b/Assets/Yarn/FP/FP_Day1_sleep/FP_Day1_sleep.yarn
@@ -345,7 +345,7 @@ title: 女孩_已完成
position: 743,-1403
tags: detour
---
-//girl: 女孩一边哼歌一边继续涂画着,
没有听到你的声音。 #line:01ef1ed
+girl: 女孩一边哼歌一边继续涂画着,
没有听到你的声音。 #line:01ef1ed
===
title: 天台
@@ -606,17 +606,17 @@ tags: linear
<>
<>
-【星空是模糊的,很难看清】
+<>
girl: 怎么还是看不清?
-【调试镜头】
+<>
jj: 这都漏雨了。它太旧了,应该很难修好了。
girl: 不好!肯定是还有什么地方没弄好…
-【调试镜头】
+<>
jj: 走吧,我们在这里也玩够了。
@@ -634,10 +634,9 @@ girl: 你刚才还不是这么说的。
me: 姐姐……
-【镜头变得模糊,下摇】
+<>
<>
-<>
<>
<>
diff --git a/Assets/Yarn/FP/FP_Day2_sleep/FP_Day2_sleep.yarn b/Assets/Yarn/FP/FP_Day2_sleep/FP_Day2_sleep.yarn
index 668324c73..24dfd4dd4 100644
--- a/Assets/Yarn/FP/FP_Day2_sleep/FP_Day2_sleep.yarn
+++ b/Assets/Yarn/FP/FP_Day2_sleep/FP_Day2_sleep.yarn
@@ -822,6 +822,7 @@ position: -4,927
<>
<>
<>
+ <>
<>
<>
一扇紧闭的手术室门。让人感觉很熟悉。 #line:0ba4b43
@@ -886,6 +887,8 @@ position: -4,927
<>
<>
一种莫名的亲切感。 #line:0dfe59e
+ <>
+ <>
->靠近黑暗<> #line:02cb5f1
// 黑暗中闪烁着无数微弱的绿色光点。那是无数段代码。 #line:0dc92eb
// 其中一段正静静地漂浮在你的面前。 #line:07400da
@@ -981,6 +984,7 @@ me: 海… #line:00a7bf5
<>
<>
<>
+<>
<>
<>
<>