Merge branch 'develop' into feature/全文捉虫

This commit is contained in:
Ovid
2026-07-02 02:40:42 +08:00
18 changed files with 1813 additions and 1116 deletions
@@ -214,7 +214,67 @@ ScriptedImporter:
characterGroups: []
pivot: {x: 0, y: 0}
boneReadOnly: 0
platformSettings: []
platformSettings:
- name: DefaultTexturePlatform
overridden: 0
ignorePlatformSupport: 0
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
forceMaximumCompressionQuality_BC6H_BC7: 0
crunchedCompression: 0
allowsAlphaSplitting: 0
androidETC2FallbackOverride: 0
- name: Standalone
overridden: 0
ignorePlatformSupport: 0
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
forceMaximumCompressionQuality_BC6H_BC7: 0
crunchedCompression: 0
allowsAlphaSplitting: 0
androidETC2FallbackOverride: 0
- name: Server
overridden: 0
ignorePlatformSupport: 0
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
forceMaximumCompressionQuality_BC6H_BC7: 0
crunchedCompression: 0
allowsAlphaSplitting: 0
androidETC2FallbackOverride: 0
- name: Android
overridden: 0
ignorePlatformSupport: 0
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
forceMaximumCompressionQuality_BC6H_BC7: 0
crunchedCompression: 0
allowsAlphaSplitting: 0
androidETC2FallbackOverride: 0
- name: WebGL
overridden: 0
ignorePlatformSupport: 0
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
forceMaximumCompressionQuality_BC6H_BC7: 0
crunchedCompression: 0
allowsAlphaSplitting: 0
androidETC2FallbackOverride: 0
mosaicLayers: 1
characterMode: 1
documentPivot: {x: 0, y: 0}
@@ -233,146 +293,6 @@ ScriptedImporter:
resliceFromLayer: 0
mosaicPSDLayers: []
rigPSDLayers:
- name: "UF\u89C6\u89D2\u4E0B\u751F\u6210\u7684\u56FE\u50CF\u5143\u7D20"
spriteName:
isGroup: 1
parentIndex: -1
spriteID: e941588a141b56f479db3454d668ebef
layerID: 637
mosaicPosition: {x: 0, y: 0}
flatten: 0
isImported: 0
isVisible: 0
- name: "\u56FE\u5C42 5"
spriteName:
isGroup: 0
parentIndex: 0
spriteID: d895094d526880e4684305bc55024019
layerID: 666
mosaicPosition: {x: 0, y: 0}
flatten: 0
isImported: 0
isVisible: 1
- name: "\u6D77\u4E2D\u5927\u8C61"
spriteName:
isGroup: 0
parentIndex: 0
spriteID: 8c2a75dae64ffd749bd065803bcdc2dc
layerID: 646
mosaicPosition: {x: 0, y: 0}
flatten: 0
isImported: 0
isVisible: 1
- name: "\u690D\u7269\u53D8\u6210\u7684\u753B\u7B14\u7B14\u7B52"
spriteName:
isGroup: 0
parentIndex: 0
spriteID: 5f1bf878f8b636d4aa7198c430cd8ddb
layerID: 651
mosaicPosition: {x: 0, y: 0}
flatten: 0
isImported: 0
isVisible: 0
- name: "\u5915\u9633\u5C71\u4E18"
spriteName:
isGroup: 1
parentIndex: 0
spriteID: 40d012f90d12d054b8b4b095c5d17097
layerID: 659
mosaicPosition: {x: 0, y: 0}
flatten: 0
isImported: 0
isVisible: 0
- name: "\u56FE\u5C42 1 \u62F7\u8D1D"
spriteName:
isGroup: 0
parentIndex: 4
spriteID: 6e6a26c6b33e3f046a0fb07c7a0be1d2
layerID: 655
mosaicPosition: {x: 0, y: 0}
flatten: 0
isImported: 0
isVisible: 0
- name: "\u8272\u76F8/\u9971\u548C\u5EA6 1"
spriteName:
isGroup: 0
parentIndex: 4
spriteID: e00cb4f5c86d7af42810b4b13c2b9311
layerID: 665
mosaicPosition: {x: 0, y: 0}
flatten: 0
isImported: 0
isVisible: 1
- name: "\u8272\u9636 1"
spriteName:
isGroup: 0
parentIndex: 4
spriteID: d4750bcb749c6be489a6433b8733ffb3
layerID: 662
mosaicPosition: {x: 0, y: 0}
flatten: 0
isImported: 0
isVisible: 1
- name: "\u56FE\u5C42 1 \u62F7\u8D1D 2"
spriteName:
isGroup: 0
parentIndex: 4
spriteID: dd619b01f886515448f78aeb6d501809
layerID: 656
mosaicPosition: {x: 0, y: 0}
flatten: 0
isImported: 0
isVisible: 1
- name: "\u5915\u9633\u5C71\u4E18"
spriteName:
isGroup: 1
parentIndex: 0
spriteID: 9713048476e1591449391ce052b71542
layerID: 657
mosaicPosition: {x: 0, y: 0}
flatten: 0
isImported: 0
isVisible: 0
- name: "\u56FE\u5C42 4"
spriteName:
isGroup: 0
parentIndex: 9
spriteID: 22f2788988d685542a68985f6c194113
layerID: 664
mosaicPosition: {x: 0, y: 0}
flatten: 0
isImported: 0
isVisible: 0
- name: "\u56FE\u5C42 3"
spriteName:
isGroup: 0
parentIndex: 9
spriteID: db1382cf3f3423d42b9ac9c9b9ecc7fc
layerID: 661
mosaicPosition: {x: 0, y: 0}
flatten: 0
isImported: 0
isVisible: 0
- name: "\u56FE\u5C42 2"
spriteName:
isGroup: 0
parentIndex: 9
spriteID: 8a4910856da3a3e459c28726ad02e802
layerID: 654
mosaicPosition: {x: 0, y: 0}
flatten: 0
isImported: 0
isVisible: 0
- name: "\u56FE\u5C42 1"
spriteName:
isGroup: 0
parentIndex: 9
spriteID: 4f403b854fa76234fabfccf65b54804a
layerID: 653
mosaicPosition: {x: 0, y: 0}
flatten: 0
isImported: 0
isVisible: 0
- name: "\u7A97\u5B50"
spriteName: "\u7A97\u5B50"
isGroup: 0
Binary file not shown.
@@ -48,7 +48,7 @@ TextureImporter:
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spritePixelsToUnits: 50
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
Binary file not shown.
@@ -48,7 +48,7 @@ TextureImporter:
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spritePixelsToUnits: 50
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
+41 -14
View File
@@ -133,6 +133,29 @@ Shader "AIBIS/EyeUnifiedSprite"
return saturate(mul(mat, color));
}
fixed4 sampleMainTex(float2 uv)
{
return tex2D(_MainTex, uv);
}
fixed4 gaussianBlurSample(float2 uv, float stepSize)
{
float angle = 1.937315; // ~111 deg, matches MemoryTuningSprite
float2 stepDir = float2(sin(angle), cos(angle));
float2 stepUv = stepDir * stepSize;
fixed4 col = sampleMainTex(uv) * 0.18;
col += sampleMainTex(uv + stepUv * 1.0) * 0.15;
col += sampleMainTex(uv - stepUv * 1.0) * 0.15;
col += sampleMainTex(uv + stepUv * 2.0) * 0.12;
col += sampleMainTex(uv - stepUv * 2.0) * 0.12;
col += sampleMainTex(uv + stepUv * 3.0) * 0.09;
col += sampleMainTex(uv - stepUv * 3.0) * 0.09;
col += sampleMainTex(uv + stepUv * 4.0) * 0.05;
col += sampleMainTex(uv - stepUv * 4.0) * 0.05;
return col;
}
fixed4 sampleSprite(float2 uv, float2 screenUv)
{
float2 distortedUv = uv;
@@ -141,23 +164,21 @@ Shader "AIBIS/EyeUnifiedSprite"
float ripple = sin((length(centered) * 42.0) - (_Time.y * 8.0));
distortedUv += centered * ripple * _RoundWaveStrength * 0.018;
float glitchRow = floor(screenUv.y * 240.0);
float tear = (hash21(float2(glitchRow, floor(_Time.y * 18.0))) - 0.5) * _GlitchAmount * 0.04;
float glitchRow = floor(screenUv.y * 360.0);
float tear = (hash21(float2(glitchRow, floor(_Time.y * 20.0))) - 0.5) * _GlitchAmount * 0.012;
distortedUv.x += tear;
float noise = hash21(floor(uv * 42.0) + floor(_Time.y * 16.0));
distortedUv += (noise - 0.5) * _DistortAmount;
float2 blur = _MainTex_TexelSize.xy + _BlurSize;
fixed4 col = tex2D(_MainTex, distortedUv);
fixed4 blurCol = col;
blurCol += tex2D(_MainTex, distortedUv + float2(blur.x, 0));
blurCol += tex2D(_MainTex, distortedUv - float2(blur.x, 0));
blurCol += tex2D(_MainTex, distortedUv + float2(0, blur.y));
blurCol += tex2D(_MainTex, distortedUv - float2(0, blur.y));
blurCol *= 0.2;
// Match legacy AllIn1 scale (~offset * fade * 0.005); avoid MemoryTuning's 0.014 base.
float2 texel = _MainTex_TexelSize.xy;
float stepSize = length(texel) + _BlurSize;
fixed4 col = sampleMainTex(distortedUv);
fixed4 blurCol = gaussianBlurSample(distortedUv, stepSize);
return lerp(col, blurCol, _BlurAmount);
float blurStrength = pow(saturate(_BlurAmount), 0.75);
return lerp(col, blurCol, blurStrength);
}
fixed4 frag(v2f i) : SV_Target
@@ -172,10 +193,16 @@ Shader "AIBIS/EyeUnifiedSprite"
float directional = dot(uv - 0.5, revealDir) + 0.5;
float dissolveNoise = (hash21(floor(uv * 96.0)) - 0.5) * _RevealNoise;
float revealMask = smoothstep(_ColorReveal - _RevealWidth, _ColorReveal + _RevealWidth, directional + dissolveNoise);
// Sweep only applies while the target is still desaturated; full color when _GrayAmount reaches 0.
float grayMix = saturate(max(_GrayAmount, (1.0 - revealMask) * _GrayAmount));
// Idle (_GrayAmount=1) or fully colored (_GrayAmount=0): uniform mix; sweep only mid-transition.
float inSweep = step(0.001, 1.0 - _GrayAmount) * step(0.001, _GrayAmount);
float sweepFactor = lerp(1.0, (1.0 - revealMask), inSweep);
float grayMix = saturate(_GrayAmount * sweepFactor);
col.rgb = lerp(col.rgb, gray.xxx, grayMix);
float sweepEdge = 1.0 - abs(directional + dissolveNoise - _ColorReveal);
float edgeGlow = smoothstep(_RevealWidth * 0.15, _RevealWidth * 0.55, sweepEdge) * inSweep * _GrayAmount * (1.0 - revealMask);
col.rgb += float3(1.0, 0.92, 0.78) * edgeGlow * 0.35;
col.rgb = lerp(col.rgb, _WrongColor.rgb * max(gray, 0.08), _WrongColorAmount * _WrongColor.a);
col.rgb = hueShift(col.rgb, _HsvShift);
@@ -197,7 +224,7 @@ Shader "AIBIS/EyeUnifiedSprite"
float ascii = asciiShape * asciiBrightness;
col.rgb = lerp(col.rgb, ascii.xxx * lerp(float3(1, 1, 1), col.rgb, 0.35), _AsciiAmount);
float chromaOffset = _GlitchAmount * 0.006;
float chromaOffset = _GlitchAmount * 0.004;
if (chromaOffset > 0.0001)
{
float red = tex2D(_MainTex, uv + float2(chromaOffset, 0)).r;
File diff suppressed because it is too large Load Diff
@@ -1,5 +1,6 @@
using AibisDream.Framework;
using DG.Tweening;
using System.Collections;
using TMPro;
using UnityEngine;
using UnityEngine.Serialization;
@@ -83,11 +84,11 @@ namespace AibisDream
initialPosition = PanTransform.position;
}
public void MoveFocusTo(Transform target, float duration, Vector2 minBounds, Vector2 maxBounds, Vector2 viewportSize)
public Tween MoveFocusTo(Transform target, float duration, Vector2 minBounds, Vector2 maxBounds, Vector2 viewportSize)
{
if (target == null)
{
return;
return null;
}
Vector3 focusPosition = ClampFocusPosition(target.position, minBounds, maxBounds, viewportSize);
@@ -115,10 +116,26 @@ namespace AibisDream
if (duration <= 0f)
{
PanTransform.position = initialPosition + currentOffset;
return;
focusTween = null;
return null;
}
focusTween = PanTransform.DOMove(initialPosition + currentOffset, duration).SetEase(Ease.InOutSine);
return focusTween;
}
public IEnumerator WaitForFocusMove(float fallbackDuration = 0f)
{
if (focusTween != null && focusTween.IsActive())
{
yield return focusTween.WaitForCompletion();
yield break;
}
if (fallbackDuration > 0f)
{
yield return new WaitForSeconds(fallbackDuration);
}
}
private void Start()
@@ -265,10 +282,11 @@ namespace AibisDream
indicatorRectTransform.position = boxCenter;
indicatorRectTransform.sizeDelta = new Vector2(boxSize.x, boxSize.y);
Color indicatorColor = currentTarget.GetIndicatorColor();
targetNameText.text = currentTarget.name + " " +
"R" + currentTarget._color.r.ToString("F1") +
" G" + currentTarget._color.g.ToString("F1") +
" B" + currentTarget._color.b.ToString("F1");
"R" + indicatorColor.r.ToString("F1") +
" G" + indicatorColor.g.ToString("F1") +
" B" + indicatorColor.b.ToString("F1");
targetIndicator.SetActive(true);
}
@@ -18,7 +18,7 @@ namespace AibisDream.FixSystem
private const float DefaultFadeDuration = 1f;
private const float SearchStartDelay = 1f;
private const float ProjectorMoveDuration = 2f;
private const float MaxClarityDistortion = 0.58f;
private const float MaxClarityDistortion = 1f;
private const float MaxFrequencyBlur = 0.6f;
private const float MinPixelDensity = 18f;
private const float MaxPixelDensity = 96f;
+165 -24
View File
@@ -52,6 +52,119 @@ namespace AibisDream
public float GetEffectTransitionDuration() => visualSettings.GetDurationForState(_currentState);
private bool ShouldPreBlinkForFocusedEffect()
{
return _currentState is EyeColorState.CanImagineColor
or EyeColorState.CannotImagineColor
or EyeColorState.CanSeeColor;
}
private IEnumerator WaitForFocusMovement(float moveDuration)
{
if (!EnsureMouseFollowAndZoom())
{
yield break;
}
yield return mouseFollowAndZoom.WaitForFocusMove(moveDuration);
}
private IEnumerator RunFocusedEffectWithPreBlink(EyeTarget target)
{
if (EnsureMouseFollowAndZoom())
{
yield return mouseFollowAndZoom.WaitForFocusMove();
}
float duration = GetEffectTransitionDuration();
EyeViewportOverlay overlay = EnsureViewportOverlay();
if (overlay != null && Application.isPlaying && ShouldPreBlinkForFocusedEffect())
{
if (_currentState == EyeColorState.CanImagineColor)
{
yield return overlay.PlayPreEffectBlinkThen(
2,
visualSettings,
() => eyeStateEffect?.ApplyFocusedEffect(target, this),
visualSettings.imagineEffectAtBlinkOpen,
visualSettings.imagineBlinkGap);
}
else
{
yield return overlay.PlayPreEffectBlinkThen(
1,
visualSettings,
() => eyeStateEffect?.ApplyFocusedEffect(target, this),
visualSettings.focusEffectAtBlinkOpen);
}
}
else
{
eyeStateEffect?.ApplyFocusedEffect(target, this);
}
yield return new WaitForSeconds(duration);
}
private IEnumerator RunBlurOutWithPreBlink(EyeTarget target, float duration)
{
EyeViewportOverlay overlay = EnsureViewportOverlay();
if (overlay != null && Application.isPlaying)
{
yield return overlay.PlayPreEffectBlinkThen(
1,
visualSettings,
() => target.BlurOut(duration),
visualSettings.focusEffectAtBlinkOpen);
}
else
{
target.BlurOut(duration);
}
yield return new WaitForSeconds(duration);
}
private IEnumerator RunUnfocusedEffectWithPreBlink(EyeTarget target)
{
if (target == null || eyeStateEffect == null)
{
yield break;
}
float duration = GetEffectTransitionDuration();
EyeViewportOverlay overlay = EnsureViewportOverlay();
if (overlay != null && Application.isPlaying && ShouldPreBlinkForFocusedEffect())
{
if (_currentState == EyeColorState.CanImagineColor)
{
yield return overlay.PlayPreEffectBlinkThen(
2,
visualSettings,
() => eyeStateEffect.ApplyUnfocusedEffect(target, this),
visualSettings.imagineEffectAtBlinkOpen,
visualSettings.imagineBlinkGap);
}
else
{
yield return overlay.PlayPreEffectBlinkThen(
1,
visualSettings,
() => eyeStateEffect.ApplyUnfocusedEffect(target, this),
visualSettings.focusEffectAtBlinkOpen);
}
}
else
{
eyeStateEffect.ApplyUnfocusedEffect(target, this);
}
yield return new WaitForSeconds(duration);
}
private void OnValidate()
{
ApplyVisualSettingsNow(
@@ -183,6 +296,11 @@ namespace AibisDream
return;
}
if (duration > 0.01f)
{
AudioManager.Instance.PlaySfx("event:/ActionFB/visualturn");
}
mouseFollowAndZoom.MoveFocusTo(
focusTransform,
duration,
@@ -266,6 +384,14 @@ namespace AibisDream
private void Start()
{
ApplyVisualSettingsToTargets();
if (targets != null)
{
foreach (EyeTarget target in targets)
{
target?.Init();
}
}
SetEyeColorState(_currentState);
EnsureMouseFollowAndZoom();
}
@@ -289,7 +415,7 @@ namespace AibisDream
[YarnCommand("EyeBlink")]
public void EyeBlink(float duration = 0f)
{
EnsureViewportOverlay()?.PlayBlink(duration > 0f ? duration : null);
EnsureViewportOverlay()?.PlayBlink(duration > 0f ? duration : null, restartAutoBlinkAfter: true);
}
[YarnCommand("set_Eyetarget")]
@@ -309,30 +435,33 @@ namespace AibisDream
}
YarnVariableStorage.Instance.SetValue("$currentEyeTarget", target.name);
StartCoroutine(EyeEffect_FadeOut());
FocusOnTarget(target, 1f);
if (currentTarget != null)
{
currentTarget.BlurIn(visualSettings.targetSwitchDuration);
}
if (currentTarget == target)
{
DialogController.Instance.StartDialogNode("重复看同一个目标");
currentTarget = target;
mouseFollowAndZoom.SetIndicatorTarget(currentTarget);
currentTarget.BlurOut(visualSettings.targetSwitchDuration);
yield return new WaitForSeconds(visualSettings.targetSwitchDuration);
if (startDialogue)
{
DialogController.Instance.StartDialogNode("重复看同一个目标");
yield return RunBlurOutWithPreBlink(currentTarget, visualSettings.targetSwitchDuration);
}
yield break;
}
EyeTarget leavingTarget = currentTarget;
if (leavingTarget != null)
{
StartCoroutine(EyeEffect_FadeOut());
}
const float focusMoveDuration = 1f;
FocusOnTarget(target, focusMoveDuration);
yield return WaitForFocusMovement(focusMoveDuration);
currentTarget = target;
mouseFollowAndZoom.SetIndicatorTarget(currentTarget);
currentTarget.BlurOut(visualSettings.targetSwitchDuration);
yield return new WaitForSeconds(visualSettings.targetSwitchDuration);
yield return RunBlurOutWithPreBlink(currentTarget, visualSettings.targetSwitchDuration);
if (startDialogue)
{
@@ -354,25 +483,37 @@ namespace AibisDream
[YarnCommand("EyeEffect_FadeIn")]
public IEnumerator EyeEffect_FadeIn()
{
if (currentTarget != null)
if (currentTarget == null)
{
AudioManager.Instance.PlaySfx("event:/ActionFB/color_imagine");
eyeStateEffect?.ApplyFocusedEffect(currentTarget, this);
yield break;
}
yield return new WaitForSeconds(GetEffectTransitionDuration());
if (_currentState == EyeColorState.CanImagineColor
&& currentTarget.IsAtFocusedColorState(visualSettings))
{
yield break;
}
AudioManager.Instance.PlaySfx("event:/ActionFB/color_imagine");
yield return RunFocusedEffectWithPreBlink(currentTarget);
}
[YarnCommand("EyeEffect_FadeOut")]
public IEnumerator EyeEffect_FadeOut()
{
if (currentTarget != null)
if (currentTarget == null)
{
AudioManager.Instance.PlaySfx("event:/ActionFB/color_imagine");
eyeStateEffect?.ApplyUnfocusedEffect(currentTarget, this);
yield break;
}
yield return new WaitForSeconds(GetEffectTransitionDuration());
if (_currentState == EyeColorState.CanImagineColor
&& currentTarget.IsAtUnfocusedColorState(visualSettings))
{
yield break;
}
AudioManager.Instance.PlaySfx("event:/ActionFB/color_imagine");
yield return RunUnfocusedEffectWithPreBlink(currentTarget);
}
[YarnCommand("EyeEffect_Init")]
+36 -16
View File
@@ -20,8 +20,23 @@ public class EyeTarget : MonoBehaviour
[SerializeField] private Shader unifiedSpriteShader;
[SerializeField] private EyeTargetVisual unifiedVisual;
[Header("Indicator RGB (imagined color for debug overlay)")]
public Color _color;
public Color GetIndicatorColor()
{
return new Color(
RoundIndicatorChannel(_color.r),
RoundIndicatorChannel(_color.g),
RoundIndicatorChannel(_color.b),
_color.a);
}
private static float RoundIndicatorChannel(float value)
{
return Mathf.Round(value * 10f) / 10f;
}
private EyeVisualSettings visualSettings = new();
private Material wrongColorMaterial;
@@ -80,6 +95,7 @@ public class EyeTarget : MonoBehaviour
public void Init()
{
unifiedVisual?.ClearTransientEffects(visualSettings);
SetToMinColorReveal();
SetToMaxBlur();
SetToNoWrongColor();
@@ -111,6 +127,20 @@ public class EyeTarget : MonoBehaviour
unifiedVisual?.SetColorReveal(visualSettings.colorRevealMax);
}
public bool IsAtFocusedColorState(EyeVisualSettings settings = null)
{
EyeVisualSettings tuning = settings ?? visualSettings;
return unifiedVisual != null
&& unifiedVisual.IsAtColorReveal(tuning.colorRevealMax);
}
public bool IsAtUnfocusedColorState(EyeVisualSettings settings = null)
{
EyeVisualSettings tuning = settings ?? visualSettings;
return unifiedVisual != null
&& unifiedVisual.IsAtColorReveal(tuning.colorRevealMin);
}
public void SetToMaxBlur()
{
unifiedVisual?.SetBlur(visualSettings.idleBlurAmount);
@@ -135,16 +165,14 @@ public class EyeTarget : MonoBehaviour
public void FadeOut(float duration)
{
unifiedVisual?.TweenColorReveal(visualSettings.colorRevealMin, duration)?.SetEase(Ease.InSine);
unifiedVisual?.TweenBlur(visualSettings.idleBlurAmount, duration)?.SetEase(Ease.InSine);
unifiedVisual?.PulseDistortion(visualSettings.colorPulseDistortion, duration);
unifiedVisual?.KillActiveTweens();
unifiedVisual?.PlayColorSweepTransition(visualSettings, duration, colorIn: false);
}
public void ColorIn(float duration)
{
unifiedVisual?.TweenColorReveal(visualSettings.colorRevealMax, duration)?.SetEase(Ease.InOutSine);
unifiedVisual?.TweenBlur(visualSettings.focusedBlurAmount, duration)?.SetEase(Ease.InOutSine);
unifiedVisual?.PulseDistortion(visualSettings.colorPulseDistortion, duration);
unifiedVisual?.KillActiveTweens();
unifiedVisual?.PlayColorSweepTransition(visualSettings, duration, colorIn: true);
}
public void WrongColorIn(float duration)
@@ -161,16 +189,13 @@ public class EyeTarget : MonoBehaviour
public void BlurIn(float duration)
{
AudioManager.Instance.PlaySfx("event:/ActionFB/visualturn");
var tween = unifiedVisual?.TweenBlur(visualSettings.idleBlurAmount, duration);
tween?.SetEase(Ease.InOutSine).OnComplete(StopVisualTurnSfx);
tween?.SetEase(Ease.InOutSine);
}
public void BlurOut(float duration)
{
AudioManager.Instance.PlaySfx("event:/ActionFB/visualturn");
var tween = unifiedVisual?.TweenBlur(visualSettings.focusedBlurAmount, duration);
tween?.SetEase(Ease.InOutSine).OnComplete(StopVisualTurnSfx);
unifiedVisual?.TweenBlurFocusSettle(visualSettings, duration);
}
public void ColorBack()
@@ -421,9 +446,4 @@ public class EyeTarget : MonoBehaviour
renderer.enabled = false;
}
}
private static void StopVisualTurnSfx()
{
AudioManager.Instance.StopSfx("event:/ActionFB/visualturn");
}
}
@@ -115,6 +115,22 @@ namespace AibisDream
{
imageSpriteRenderer.DOKill();
}
ClearTransientEffects(null);
}
public void ClearTransientEffects(EyeVisualSettings settings)
{
if (!EnsureMaterial())
{
return;
}
SetFloat(GlitchAmountId, 0f);
SetFloat(DistortAmountId, 0f);
SetFloat(ScanlineAmountId, 0f);
SetFloatRaw(HsvShiftId, 0f);
SetFloatRaw(RevealNoiseId, settings != null ? settings.revealNoise : 0f);
}
public void ResetVisual(float reveal)
@@ -151,6 +167,21 @@ namespace AibisDream
SetFloat(GrayAmountId, 1f - value);
}
public float GetColorReveal()
{
if (!EnsureMaterial() || !material.HasProperty(ColorRevealId))
{
return 0f;
}
return material.GetFloat(ColorRevealId);
}
public bool IsAtColorReveal(float target, float epsilon = 0.02f)
{
return Mathf.Abs(GetColorReveal() - target) <= epsilon;
}
public Tween TweenColorReveal(float value, float duration)
{
if (!EnsureMaterial())
@@ -176,7 +207,69 @@ namespace AibisDream
public Tween TweenBlur(float value, float duration)
{
return TweenFloat(BlurAmountId, value, duration);
if (!EnsureMaterial())
{
return null;
}
Tween tween = material.DOFloat(value, BlurAmountId, duration);
tween.OnUpdate(SyncBlurSizeToMaterial);
tween.OnComplete(SyncBlurSizeToMaterial);
return tween;
}
/// <summary>
/// One focus transition: sharpen, brief defocus, settle. Total wall time equals <paramref name="duration"/>.
/// </summary>
public Tween TweenBlurFocusSettle(EyeVisualSettings settings, float duration)
{
if (!EnsureMaterial() || settings == null || duration <= 0f)
{
return null;
}
float sharpBlur = settings.focusedBlurAmount;
float bumpBlur = Mathf.Clamp(
sharpBlur + settings.focusSettleDefocusBump,
sharpBlur,
settings.idleBlurAmount);
Vector3 ratios = settings.focusSettlePhaseRatios;
float ratioSum = ratios.x + ratios.y + ratios.z;
if (ratioSum <= 0.001f)
{
ratios = new Vector3(0.5f, 0.22f, 0.28f);
ratioSum = ratios.x + ratios.y + ratios.z;
}
float primaryDuration = duration * (ratios.x / ratioSum);
float defocusDuration = duration * (ratios.y / ratioSum);
float refocusDuration = duration * (ratios.z / ratioSum);
Sequence sequence = DOTween.Sequence();
Tween primaryFocus = TweenBlur(sharpBlur, primaryDuration);
if (primaryFocus != null)
{
primaryFocus.SetEase(Ease.InOutSine);
sequence.Append(primaryFocus);
}
Tween microDefocus = TweenBlur(bumpBlur, defocusDuration);
if (microDefocus != null)
{
microDefocus.SetEase(Ease.InOutSine);
sequence.Append(microDefocus);
}
Tween refocus = TweenBlur(sharpBlur, refocusDuration);
if (refocus != null)
{
refocus.SetEase(Ease.InOutSine);
sequence.Append(refocus);
}
return sequence;
}
public void SetWrongColor(float value)
@@ -189,16 +282,53 @@ namespace AibisDream
return TweenFloat(WrongColorAmountId, value, duration);
}
public Tween PulseDistortion(float amount, float duration)
public Tween PlayColorSweepTransition(EyeVisualSettings settings, float duration, bool colorIn)
{
if (!EnsureMaterial())
if (!EnsureMaterial() || settings == null || duration <= 0f)
{
return null;
}
return material.DOFloat(amount, DistortAmountId, Mathf.Max(0.01f, duration * 0.5f))
.SetEase(Ease.InOutSine)
.SetLoops(2, LoopType.Yoyo);
duration = Mathf.Max(0.01f, duration);
float chromaPeak = settings.colorSweepChromaPeak;
float ramp = Mathf.Clamp(settings.colorSweepChromaRamp, 0.1f, 0.6f);
float rampDuration = duration * ramp;
float settleDuration = duration - rampDuration;
float targetReveal = colorIn ? settings.colorRevealMax : settings.colorRevealMin;
float targetBlur = colorIn ? settings.focusedBlurAmount : settings.idleBlurAmount;
Ease sweepEase = colorIn ? Ease.InOutSine : Ease.InSine;
ClearTransientEffects(null);
SetFloatRaw(RevealNoiseId, 0f);
Sequence sequence = DOTween.Sequence();
Tween colorTween = TweenColorReveal(targetReveal, duration);
if (colorTween != null)
{
colorTween.SetEase(sweepEase);
sequence.Join(colorTween);
}
Tween blurTween = TweenBlur(targetBlur, duration);
if (blurTween != null)
{
blurTween.SetEase(sweepEase);
sequence.Join(blurTween);
}
if (chromaPeak > 0.001f)
{
Sequence chromaEnvelope = DOTween.Sequence();
chromaEnvelope.Append(material.DOFloat(chromaPeak, GlitchAmountId, rampDuration)
.SetEase(Ease.OutSine));
chromaEnvelope.Append(material.DOFloat(0f, GlitchAmountId, settleDuration)
.SetEase(Ease.InSine));
sequence.Join(chromaEnvelope);
}
sequence.OnComplete(() => ClearTransientEffects(settings));
return sequence;
}
public void SetImageAlpha(float alpha)
@@ -20,7 +20,7 @@ namespace AibisDream
private static readonly int EdgeSoftnessId = Shader.PropertyToID("_EdgeSoftness");
private static readonly int BlinkOpenId = Shader.PropertyToID("_BlinkOpen");
/// <summary>须低于 PersistenceUI Canvas(1) 与 Dialog Canvas(3)。</summary>
/// <summary>Persistence:本遮罩=0UI Canvas=1Dialog Canvas=3。</summary>
[SerializeField] private int sortingOrder;
private Canvas overlayCanvas;
@@ -45,7 +45,10 @@ namespace AibisDream
}
PrepareHierarchyIfNeeded();
SetOverlayActive(false);
if (!moduleActive && !editorPreviewActive)
{
SetOverlayActive(false);
}
}
private void OnDestroy()
@@ -143,7 +146,7 @@ namespace AibisDream
}
}
public Tween PlayBlink(float? totalDuration = null)
public Tween PlayBlink(float? totalDuration = null, bool restartAutoBlinkAfter = false)
{
if (instanceMaterial == null || activeSettings == null)
{
@@ -164,11 +167,109 @@ namespace AibisDream
blinkTween = DOTween.Sequence()
.Append(CreateBlinkTween(blinkOpen, 0f, closeDuration, Ease.InQuad))
.Append(CreateBlinkTween(0f, 1f, openDuration, Ease.OutQuad))
.OnComplete(() => SetBlinkOpen(1f));
.OnComplete(() =>
{
SetBlinkOpen(1f);
if (restartAutoBlinkAfter)
{
RestartAutoBlinkCooldown(activeSettings);
}
});
return blinkTween;
}
/// <summary>
/// 先眨眼,再在最后一次眨眼的开眼阶段触发效果(聚焦 1 次 / 想象 2 次)。
/// </summary>
public IEnumerator PlayPreEffectBlinkThen(
int blinkCount,
EyeVisualSettings settings,
System.Action onEffectStart,
float effectAtOpenRatio,
float betweenBlinkGap = 0f)
{
if (!ShouldPlayTransitionBlink(settings))
{
onEffectStart?.Invoke();
yield break;
}
StopAutoBlink();
bool effectStarted = false;
blinkCount = Mathf.Max(1, blinkCount);
effectAtOpenRatio = Mathf.Clamp01(effectAtOpenRatio);
for (int i = 0; i < blinkCount; i++)
{
bool isLastBlink = i == blinkCount - 1;
Tween blink = PlayBlink();
if (blink == null)
{
if (isLastBlink && !effectStarted)
{
effectStarted = true;
onEffectStart?.Invoke();
}
continue;
}
if (isLastBlink)
{
float effectDelay = settings.blinkCloseDuration + settings.blinkOpenDuration * effectAtOpenRatio;
yield return new WaitForSeconds(effectDelay);
if (!effectStarted)
{
effectStarted = true;
onEffectStart?.Invoke();
}
yield return blink.WaitForCompletion();
}
else
{
yield return blink.WaitForCompletion();
if (betweenBlinkGap > 0f)
{
yield return new WaitForSeconds(betweenBlinkGap);
}
}
}
if (!effectStarted)
{
onEffectStart?.Invoke();
}
RestartAutoBlinkCooldown(settings);
}
private bool ShouldPlayTransitionBlink(EyeVisualSettings settings)
{
return Application.isPlaying
&& moduleActive
&& !editorPreviewActive
&& settings != null
&& settings.enableViewportOverlay;
}
private void RestartAutoBlinkCooldown(EyeVisualSettings settings)
{
if (settings == null || !settings.autoBlink || !Application.isPlaying)
{
return;
}
if (!(moduleActive || editorPreviewActive))
{
return;
}
StartAutoBlink(settings);
}
private void ShowOverlay(EyeVisualSettings settings)
{
if (settings == null || !settings.enableViewportOverlay)
@@ -178,6 +279,7 @@ namespace AibisDream
}
PrepareHierarchyIfNeeded();
ApplyCanvasRenderSettings();
activeSettings = settings;
ApplySettings(settings);
if (!editorPreviewActive)
@@ -279,11 +381,6 @@ namespace AibisDream
private void PrepareHierarchyIfNeeded()
{
if (hierarchyReady && overlayCanvas != null)
{
return;
}
if (Application.isPlaying && !lifecycleReady)
{
ScheduleDeferredPrepare();
@@ -292,6 +389,7 @@ namespace AibisDream
EnsureOverlayHierarchy();
hierarchyReady = overlayCanvas != null;
ApplyCanvasRenderSettings();
}
private void ScheduleDeferredPrepare()
@@ -316,6 +414,8 @@ namespace AibisDream
PushViewportParams(activeSettings);
SetBlinkOpen(blinkOpen);
}
ApplyCanvasRenderSettings();
}
private void EnsureOverlayHierarchy()
@@ -382,6 +482,21 @@ namespace AibisDream
overlayCanvas.planeDistance = OverlayPlaneDistance;
overlayCanvas.sortingLayerName = OverlaySortingLayer;
overlayCanvas.sortingOrder = sortingOrder;
SetOverlayUILayer(overlayCanvas.gameObject);
}
private static void SetOverlayUILayer(GameObject root)
{
int uiLayer = LayerMask.NameToLayer("UI");
if (uiLayer < 0)
{
uiLayer = 5;
}
foreach (Transform node in root.GetComponentsInChildren<Transform>(true))
{
node.gameObject.layer = uiLayer;
}
}
private static Camera ResolveUICamera()
@@ -391,7 +506,7 @@ namespace AibisDream
return UIManager.Instance.UICamera;
}
var uiManager = FindObjectOfType<UIManager>();
var uiManager = Object.FindObjectOfType<UIManager>();
if (uiManager != null && uiManager.UICamera != null)
{
return uiManager.UICamera;
@@ -42,9 +42,23 @@ namespace AibisDream
[Range(0f, 1f)] public float revealNoise = 0.08f;
[Header("Distortion Pulse")]
[Tooltip("Peak _DistortAmount during ColorIn / FadeOut.")]
[Range(0f, 0.08f)] public float colorPulseDistortion = 0.035f;
[Header("Color Sweep (ColorIn / FadeOut)")]
[Tooltip("Light RGB fringe during color sweep; 0 = sweep only.")]
[Range(0f, 1f)] public float colorSweepChromaPeak = 0.35f;
[Tooltip("Sweep duration fraction to ramp chroma fringe to peak.")]
[Range(0.1f, 0.6f)] public float colorSweepChromaRamp = 0.4f;
[HideInInspector] public float imagineGlitchPeak = 0.35f;
[HideInInspector] public float imagineGlitchRamp = 0.4f;
[HideInInspector] public float colorPulseDistortion = 0.06f;
[Header("Focus Settle (BlurOut)")]
[Tooltip("Mid-phase micro-defocus added on top of focused blur.")]
[Range(0f, 0.5f)] public float focusSettleDefocusBump = 0.12f;
[Tooltip("Single BlurOut timing split (focus | micro-defocus | refocus). Sum = 1; total seconds unchanged.")]
public Vector3 focusSettlePhaseRatios = new(0.5f, 0.22f, 0.28f);
[Header("Wrong Color (Eye Disorder)")]
public Color wrongColor = new(1f, 0.16f, 0.08f, 1f);
@@ -77,6 +91,16 @@ namespace AibisDream
public float blinkCloseDuration = 0.09f;
public float blinkOpenDuration = 0.14f;
[Header("Transition Blink")]
[Tooltip("聚焦:单次眨眼开眼到该进度时触发变清晰(0=刚睁眼,1=完全睁开)。")]
[Range(0f, 1f)] public float focusEffectAtBlinkOpen = 0.85f;
[Tooltip("想象:两次眨眼之间的间隔(秒)。")]
public float imagineBlinkGap = 0.04f;
[Tooltip("想象:第二次眨眼开眼到该进度时触发上色。")]
[Range(0f, 1f)] public float imagineEffectAtBlinkOpen = 1f;
public float GetDurationForState(EyeSystem.EyeColorState state) => state switch
{
EyeSystem.EyeColorState.CanImagineColor => imagineColorDuration,
@@ -11,9 +11,9 @@ Shader "AibisDream/MemoryTuningSprite"
_PixelMin ("Pixel Min", Range(8,64)) = 18
_PixelMax ("Pixel Max", Range(32,256)) = 128
_BlurStep ("Blur Step", Range(0,0.01)) = 0.0025
_DistortStrength ("Distort Strength", Range(0,0.05)) = 0.018
_DistortStrength ("Distort Strength", Range(0,0.08)) = 0.035
_ChromaticStrength ("Chromatic Strength", Range(0,0.03)) = 0.008
_FadeDistortStrength ("Fade Distort Strength", Range(0,0.2)) = 0.08
_FadeDistortStrength ("Fade Distort Strength", Range(0,0.2)) = 0.12
_PixelateFade ("Pixelate Fade", Range(0,1)) = 0
_PixelatePixelDensity ("Pixelate Pixel Density", Range(6,160)) = 96
_GaussianBlurFade ("Gaussian Blur Fade", Range(0,1)) = 0
@@ -123,10 +123,10 @@ Shader "AibisDream/MemoryTuningSprite"
float angle = radians(130.0);
float2 dir = float2(cos(angle), sin(angle));
float2 side = float2(-sin(angle), cos(angle));
float2 edge = smoothstep(0.0, 0.12, uv) * smoothstep(0.0, 0.12, 1.0 - uv);
float2 edge = smoothstep(0.0, 0.07, uv) * smoothstep(0.0, 0.07, 1.0 - uv);
float edgeMask = edge.x * edge.y;
float wave = sin(dot(uv - 0.5, dir) * 27.2 + _Time.y * 2.15);
float volFxStrength = lerp(0.006, 0.026, saturate(_UVDistortFade)) * strength;
float volFxStrength = lerp(_DistortStrength * 0.55, _DistortStrength, saturate(_UVDistortFade)) * strength;
return uv + side * wave * volFxStrength * edgeMask;
}
@@ -173,7 +173,7 @@ Shader "AibisDream/MemoryTuningSprite"
half frequency = saturate(_FrequencyTuning);
half pixelate = pow(max(frequency, saturate(_PixelateFade)), 0.75);
half blur = pow(max(frequency, saturate(_GaussianBlurFade)), 0.75);
half distortion = pow(max(clarity, saturate(_UVDistortFade / 0.58)), 0.75);
half distortion = pow(max(clarity, saturate(_UVDistortFade)), 0.85);
float2 uv = input.uv;
half fade = saturate((_DirectionalDistortionFade + 15.0) / 15.0);
@@ -22,44 +22,44 @@ position: -1469,-2536
<<if visited("FirstDialogue")==false>>
<<if $colorChecked == false>>
me: …… #line:0b1977c
me: 怎么是…<waitfor=0.2>黑白的? #line:067ed32
me: 怎么是…黑白的? #line:067ed32
me: …难道是检查仪器的问题?<br>让我确认一下…… #line:01eca08
<<hide_dialog>>
<<wait 1>>
peipei: 医生…不用确认了。<br>这是正常的。 #line:0ca0fb3
me: …正常? #line:06b3757
peipei: 已经有段时间了……<br>应该是装UF的时候开始的吧。 #line:07dfaf3
peipei: 但这不是什么大问题。<waitfor=0.2>美工都有这种情况。 #line:057cc94
peipei: 但这不是什么大问题。美工都有这种情况。 #line:057cc94
<<hide_dialog>>
<<wait 1>>
me: 也就是说可能是UF…<waitfor=0.1>导致了美工机体失去色觉?<br>而且…所有的美工机体都是这样吗? #line:0eb070a
me: 也就是说可能是UF…导致了美工机体失去色觉?<br>而且…所有的美工机体都是这样吗? #line:0eb070a
<<else>>
me: 果然…<waitfor=0.2>没有<color=\#FFB200>色觉晶圆</color><waitfor=0.1>它就看不见色彩。 #line:00cc4aa
me: 果然…没有色觉晶圆,它就看不见色彩。 #line:00cc4aa
<<hide_dialog>>
<<wait 1>>
peipei: 已经有段时间了……<waitfor=0.3><br>应该是装UF的时候开始的吧。 #line:0348dfe
me: 那就对上了。<waitfor=0.2><br>你说的那个什么…“改造的一部分”,<waitfor=0.1><br>在给你装上UF的时候,<waitfor=0.2>它们拿走了<color=\#FFB200>色觉晶圆</color>。 #line:06339b7
peipei: 医生。<waitfor=0.1><br>这不是什么大问题。<waitfor=0.3>美工都有这种情况。 #line:07867a3
peipei: 已经有段时间了……应该是装UF的时候开始的吧。 #line:0348dfe
me: 那就对上了.<br>你说的那个什么…“改造的一部分”,<br>在给你装上UF的时候,它们拿走了色觉晶圆。 #line:06339b7
peipei: 医生。<br>这不是什么大问题。美工都有这种情况。 #line:07867a3
<<hide_dialog>>
<<wait 1>>
me: *都*<br>…所有的美工机体都是这样吗? #line:0b34b6c
<<endif>>
peipei: 对,所有美工都这样。<br>只是大家都已经适应了,我还…太慢。 #line:00b1a31
me: 等会等会…<waitfor=0.2>我有点糊涂了…… #line:0c7a5aa
me: 是我对美术的理解有问题吗?<br>…如果一个美工机体都看不见色彩…<waitfor=0.2><br>上边怎么能指望它能画出美宣? #line:0aa834e
me: 等会等会…我有点糊涂了…… #line:0c7a5aa
me: 是我对美术的理解有问题吗?<br>…如果一个美工机体都看不见色彩…上边怎么能指望它能画出美宣? #line:0aa834e
<<hide_dialog>>
<<wait 1>>
peipei: 医生,<waitfor=0.1>你和我一样都理解错啦。<br>公司已经做过指示:<waitfor=0.2>效率是美术的第一要务。 #line:0fa2130
peipei: 我确实…<waitfor=0.2>做什么都很慢。<waitfor=0.2>我需要这个模块。<br>反正UF会自动给作品上色,<waitfor=0.1>所以我自己也就不用看见颜色啦。 #line:0cfcb3a
peipei: 医生,你和我一样都理解错啦。<br>公司已经做过指示:效率是美术的第一要务。 #line:0fa2130
peipei: 我确实…做什么都很慢。我需要这个模块。<br>反正UF会自动给作品上色,所以我自己也就不用看见颜色啦。 #line:0cfcb3a
<<hide_dialog>>
<<wait 0.5>>
me: 明白了,<waitfor=0.2>美术确实不是我擅长的领域。 #line:0d46f3c
me: 明白了,美术确实不是我擅长的领域。 #line:0d46f3c
<<if $gameStage == "视觉检查">>
<<hide_dialog>>
<<wait 1>>
me: 不过…这看起来和UF的报错没有什么关系啊?<br>它说这里有“异常成像”…… #line:0748233
me: 这说明,我应该看到些不正确的图像什么的…<br>可除了画面是黑白的,看起来没有什么“异常成像”啊。 #line:0bc98b8
me: 也许我漏了什么?<waitfor=0.3>最好再继续检查一下…… #line:06f4b9b
me: 也许我漏了什么?最好再继续检查一下…… #line:06f4b9b
<<set $peipeiReadyToShow = true>>
<<endif>>
<<endif>>
@@ -78,17 +78,17 @@ position: -1046,-2637
<<if $eyeDeepThenUf == true>>
<<hide_dialog>>
<<wait 0.5>>
me: 除了画面是黑白的,<waitfor=0.2>看起来没有看漏什么异常成像”的东西啊… #line:0e1811a
me: 除了画面是黑白的,看起来没有看漏什么异常成像”的东西啊… #line:0e1811a
me: 奇怪了。 #line:024c966
<<else>>
me: 依然没有…看起来成像都很正常。<br>难道我搞错了…… #line:0622bc8
<<endif>>
<<hide_dialog>>
<<wait 0.5>>
peipei: 医生…<waitfor=0.2>我想我最好还是主动给你看这个…… #line:0925b34
peipei: 医生…我想我最好还是主动给你看这个…… #line:0925b34
me: 什么…? #line:02bed63
peipei: 对不起…我不是故意要瞒着的。<br>只是我还以为,这不是什么大问题。 #line:0acfaf1
peipei: 我也不知道这是什么原理,<waitfor=0.2>但是只要我…… #line:0df4bf6
peipei: 我也不知道这是什么原理,但是只要我…… #line:0df4bf6
<<set_Eyetarget EyeManager "sky" false>>
peipei: 看到窗外的蓝天了吗? #line:0fb595e
<<set_EyeColorState EyeManager "CanImagineColor">>
@@ -103,26 +103,26 @@ peipei: 我也不知道。<br>我就是…记得这些颜色。 #line:0f78c41
<<hide_dialog>>
<<wait 0.5>>
me: 记得? #line:085d5bf
peipei: 医生,<waitfor=0.1>你应该也有关于天空的珍贵记忆吧。<br>你还记得在那段记忆里,<waitfor=0.2>天空是什么颜色的吗? #line:01c6f45
peipei: 你一定是记得的,<waitfor=0.2>我也是。<br>天空的颜色<waitfor=0.1>不是那么容易被忘记的。 #line:0d5b0cc
peipei: 医生,你应该也有关于天空的珍贵记忆吧。<br>你还记得在那段记忆里,天空是什么颜色的吗? #line:01c6f45
peipei: 你一定是记得的,我也是。<br>天空的颜色不是那么容易被忘记的。 #line:0d5b0cc
<<hide_dialog>>
<<wait 0.5>>
me: 等会…<waitfor=0.2>有一个报错弹窗传过来了。 #line:03a29d7
me: 等会…有一个报错弹窗传过来了。 #line:03a29d7
// <<OpenSingleButtonWindow WindowManager 非法进程检测 正在向视觉非法写入色彩数据,是否打印索引?>>
<<wait 0.5>>
<<PrintPunchTapes 1>>
<<generate_PunchTape "l10n.天空" EyeView liuying1-3>>
<<wait 1>>
me: 记忆…<waitfor=0.3>向视觉发送了数据?<waitfor=0.3>然后生成了这些颜色? #line:09d653a
me: 记忆…向视觉发送了数据?然后生成了这些颜色? #line:09d653a
peipei: 什么…数据?<br>医生,我不明白。 #line:04d0ffd
me: 就好像有个什么东西在用记忆作为颜料,<waitfor=0.2>为你所看到的东西上色。 #line:0d36711
me: 就好像有个什么东西在用记忆作为颜料,为你所看到的东西上色。 #line:0d36711
->放入收藏夹 #line:0292cab
<<hide_dialog>>
<<CollectionClue>>
<<complete_task "line:01fada9">>
Task: 继续收集异常色彩 #line:0a397f7
me: 嗯……<waitfor=0.3>我可能有线索了。<waitfor=0.2>需要对你做一个色觉检测。 #line:09cb45e
me: 我需要让你再观察几个东西,<waitfor=0.2>你只要重复刚才做的事情就好了。 #line:0911cc5
<<complete_task "#line:043e26d">>
Task: 观察更多异常色彩 #line:0a397f7
me: 嗯……我可能有线索了。需要对你做一个色觉检测。 #line:09cb45e
me: 我需要让你再观察几个东西,你只要重复刚才做的事情就好了。 #line:0911cc5
peipei: 好的,医生。 #line:0a00775
<<set $eyeTargetTriggerCount = $eyeTargetTriggerCount + 1>>
<<set $eyecolorKnown=true>>
@@ -188,12 +188,12 @@ position: -1468,-2364
---
<<if visited("Eye_poster")==false>>
me: 佩佩,试试看那个电脑。 #line:0c1aff7
peipei: 好新奇的电脑…<waitfor=0.2>我从来没有见过呢。 #line:083ae3e
peipei: 好新奇的电脑…我从来没有见过呢。 #line:083ae3e
<<hide_dialog>>
<<wait 0.5>>
<<endif>>
<<wait 0.5>>
me: 这个没有生成颜色?<waitfor=0.2>奇怪…… #line:06e6a8e
me:这个没有生成颜色?奇怪…… #line:06e6a8e
<<jump EyeCheckEnd>>
===
@@ -208,16 +208,15 @@ position: -1467,-2207
---
<<if visited("Eye_poster")==false>>
<<set $eyeTargetTriggerCount = $eyeTargetTriggerCount + 1>>
me: 佩佩,<waitfor=0.1>看到那张海报了么? #line:005434c
me:佩佩,看到那张海报了么? #line:005434c
<<set_EyeColorState EyeManager "CanImagineColor">>
<<set_Eyetarget EyeManager "poster">>
peipei: 那是,<waitfor=0.1>啊……<br>那是我装上UF之后画的第一张广告画。<br>“护肤机油”。 #line:0d47b90
me: 原来这是UF画的。<waitfor=0.2>我就说…<br>好像从那个时候开始,<waitfor=0.1>公司的美术都变得…怪怪的。 #line:06dd03c
peipei: 那是,啊……<br>那是我装上UF之后画的的第一张广告画。<br>“护肤机油”。 #line:0d47b90
me:原来这是UF画的。我就说…<br>好像从那个时候开始,公司的美术都变得…怪怪的。 #line:06dd03c
peipei: 那是我的错……<br>我不习惯UF,本来想用笔来画的… #line:05f750d
peipei: 主管最后才发现,<waitfor=0.2>把我痛骂了一顿,<waitfor=0.2>让我加班改用UF重新画了一张。 #line:0a75b41
peipei: 主管最后才发现,把我痛骂了一顿,让我加班改用UF重新画了一张。 #line:0a75b41
me: 不是说*效率优先*么?重新画…不是更慢吗? #line:0732b8a
peipei: 我也是这么说的,可主管说机体必须按照标准化流程工作。<br>当时的我,就是怎么也搞不懂…… #line:0424be1
peipei: 把它气得脸都涨红了,<waitfor=0.2>红得像…<waitfor=0.2>太阳一样。 #line:0866c17
peipei: 把它气得脸都涨红了,红得像…太阳一样。 #line:0866c17
<<wait 0.5>>
me: 这倒是很奇特的比喻。 #line:0fa00a2
<<EyeEffect_FadeIn EyeManager>>
@@ -231,8 +230,6 @@ position: -1467,-2207
<<CollectionClue>>
//<<jump EyeCheckEnd>>
<<endif>>
<<set_EyeColorState EyeManager "CanImagineColor">>
<<EyeEffect_FadeIn EyeManager>>
<<jump EyeCheckEnd>>
===
@@ -247,16 +244,16 @@ position: -1463,-2056
---
<<if visited("Eye_plant")==false>>
<<set $eyeTargetTriggerCount = $eyeTargetTriggerCount + 1>>
me: 佩佩,<waitfor=0.1>看到那个绿萝了么? #line:0d8dc23
me:佩佩,看到那个绿萝了么? #line:0d8dc23
peipei: 医生,你的绿萝长得很不错喔! #line:0520d90
//植物出现颜色;其中一片叶子枯萎了,但也显示出了绿色
peipei: 小时候我养过一盆绿萝。<waitfor=0.2>爸爸说等我慢慢长大,<waitfor=0.2><br>这盆绿萝就会跟我一起长大变成绿萝树! #line:0aad1a2
peipei: 小时候我养过一盆绿萝。爸爸说等我慢慢长大,这盆绿萝就会跟我一起长大变成绿萝树! #line:0aad1a2
<<set_EyeColorState EyeManager "CanImagineColor">>
<<EyeEffect_FadeIn EyeManager>>
peipei: 可惜…… #line:00bbb13
///<<set_EyeColorState EyeManager "CanImagineColor">>枯萎的叶子变成了正确的黄色
peipei: 我还是把它养死了。 #line:0116b4c
peipei: 我愧疚了好久,<waitfor=0.2>那时的我真的以为<waitfor=0.1>小草可以长成一棵树。 #line:03ff48d
peipei: 我愧疚了好久,那时的我真的以为小草可以长成一棵树。 #line:03ff48d
<<hide_dialog>>
<<wait 0.5>>
// <<OpenSingleButtonWindow WindowManager 非法进程检测 正在向视觉非法写入色彩数据,是否打印索引?>>
@@ -268,8 +265,6 @@ peipei: 我愧疚了好久,<waitfor=0.2>那时的我真的以为<waitfor=0.1>
<<CollectionClue>>
//<<jump EyeCheckEnd>>
<<endif>>
<<set_EyeColorState EyeManager "CanImagineColor">>
<<EyeEffect_FadeIn EyeManager>>
<<jump EyeCheckEnd>>
===
+120
View File
@@ -0,0 +1,120 @@
# AgentBridge 计划草案
> 状态:草案,待讨论后实施。
> 目的:让 Cursor Agent / Unity MCP 能自动到达可测状态、读取结构化运行时状态、做回归验证,减少手动 Play + 复现。
## 背景
当前 Agent 已可通过 **Unity MCP** 做:
- Play / Stop、加载场景、查 GameObject / 组件
- 读 Console、截图
- 触发 Editor MenuItem`execute_menu_item`
难以自动完成的部分:
- 完整叙事流程(Persistence → 维修场景 → Yarn → 进模块)
- UI 交互(Esc 开设置、点 log 面板等)
- 缺乏结构化状态输出,debug 依赖临时埋点
本项目已有大量可复用入口:`FixSystemCenter``EyeSystem``DialogController``UIManager`、各 `*YarnCommand``PeipeiFixCues`(如 `EyeCue.EnterImmediate`)等。AgentBridge 只做薄包装,不重写业务逻辑。
## 目标
1. **一键到可测状态** — 场景预设,而非暴露底层细节
2. **状态可查询** — JSON / Log,便于 Agent 自动验证
3. **Editor / Dev 专用**`#if UNITY_EDITOR || DEVELOPMENT_BUILD`,不进 Release
4. **菜单路径稳定** — 供 MCP `execute_menu_item` 与文档引用
## 阶段 1:最小可用(建议优先)
| 项 | 内容 | 调用方式 |
|---|---|---|
| `AgentBridge.cs` | 静态薄包装 | — |
| 场景预设 | `EnterPeipeiEyeModule()``EyeCue.EnterImmediate` | `Tools/Agent/Enter Peipei Eye Module` |
| UI 操作 | `ToggleSettingPanel()``ToggleMainPanel()` | `Tools/Agent/Toggle Setting Panel` 等 |
| 状态查询 | `DumpEyeOverlayState()` → 输出 layer / sortingOrder / active | `Tools/Agent/Dump Eye Overlay State` |
| 文档 | 本文件 + 菜单清单维护 | Agent / `.cursor/rules` 引用 |
### 建议 Editor 菜单(第一批)
```
Tools/Agent/Enter Peipei Eye Module
Tools/Agent/Toggle Setting Panel
Tools/Agent/Toggle Main Panel
Tools/Agent/Dump Eye Overlay State
```
### 代码位置(待拍板)
- 方案 A`Assets/Scripts/Framework/AgentBridge/`Runtime + `#if` 守卫)
- 方案 B`Assets/Editor/Agent/`(仅 EditorMenuItem 与 Bridge 同目录)
## 阶段 2:常用模块覆盖(按需扩展)
按实际 debug 频率扩展场景预设:
| 模块 | 预设示例 | 复用 API |
|---|---|---|
| FixSystem | `TransitionTo(Eye / UF / Memory)``ResetCablePanel` | `FixSystemCenter` |
| Eye | `EyeOverlayOn/Off``SetEyeTarget(name)` | `EyeSystem` |
| Dialog | `StartDialogNode(nodeName)` | `DialogController` |
| Save | `LoadTestSave("peipei_eye")` | `SaveSystem` / 固定测试档 |
| Camera | `SwitchCamera(EyeDeep)` 等 | `CameraKit` |
每个预设 = 一个 MenuItem + 一个 `AgentBridge` 方法 +(可选)一个 `GetXxxState()`
## 阶段 3:可验证 + 防回归
| 项 | 用途 |
|---|---|
| `GetXxxState()` 返回 JSON | Agent 读 Console / 文件验证,少依赖截图 |
| PlayMode Tests | 如 overlay UI 层、sortingOrder、Dialog 不被挡 |
| MCP `run_tests` | UI / 渲染相关改动后自动跑 |
### 示例:Eye Overlay 状态结构
```json
{
"overlayActive": true,
"canvasLayer": 5,
"sortingOrder": 0,
"sortingLayer": "StartUI",
"worldCamera": "UI Camera",
"layerVisibleToCamera": true
}
```
## 设计原则
1. **只包装,不重写** — 调用现有 public API
2. **场景预设 > 原子命令** — 优先「一键到眼动模块」
3. **状态可查询** — 每个常 debug 模块至少一个 `GetState()` / `Dump*()`
4. **不进入 Release** — 编译条件守卫 + 不进 Addressables
5. **菜单路径稳定** — 变更需同步更新本文档
## 待讨论问题
1. **代码放哪**`Framework/AgentBridge` 还是 `Editor/Agent`
2. **测试存档**:是否需要固定 PlayMode 存档 slot,还是每次冷启动场景?
3. **第一批优先级**:眼动 / 插线 / 对话 / 记忆 — 哪个最常需要 Agent 介入?
4. **是否写入 `.cursor/rules`**:固定 MenuItem 清单,让 Agent 默认走 Bridge 流程?
5. **与现有 Editor 工具关系**:如 `EyeSystemEditor` 预览 — 合并还是并存?
## 参考:Persistence UI 层级
Eye 视口遮罩修复后的约定(`EyeViewportOverlay`):
| Canvas | sortingOrder | 说明 |
|---|---|---|
| EyeViewportOverlay | 0 | 遮罩,不挡系统 UI |
| UI Canvas | 1 | 设置、MainPanel、PlayTool 等 |
| Dialog Canvas | 3 | 对话 |
Overlay 使用 `ScreenSpaceCamera` + UI CameraCanvas 须在 **UI 层 (5)** 以匹配 UI Camera culling mask。
## 变更记录
| 日期 | 说明 |
|---|---|
| 2026-07-01 | 初稿:overlay debug 后整理 Agent 自动化计划 |