Merge branch '7.1春细节优化' into 'develop'
fix(eye): 修复视口遮罩不可见并避免遮挡系统 UI See merge request aibis-dream/aibis-dream!720
This commit is contained in:
Binary file not shown.
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
+1029
-842
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;
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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>须低于 Persistence 中 UI Canvas(1) 与 Dialog Canvas(3)。</summary>
|
||||
/// <summary>Persistence:本遮罩=0,UI Canvas=1,Dialog 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);
|
||||
|
||||
@@ -210,7 +210,6 @@ position: -1467,-2207
|
||||
<<set $eyeTargetTriggerCount = $eyeTargetTriggerCount + 1>>
|
||||
me:佩佩,看到那张海报了么? #line:005434c
|
||||
<<set_EyeColorState EyeManager "CanImagineColor">>
|
||||
<<set_Eyetarget EyeManager "poster">>
|
||||
peipei: 那是,啊……<br>那是我装上UF之后画的的第一张广告画。<br>“护肤机油”。 #line:0d47b90
|
||||
me:原来这是UF画的。我就说…<br>好像从那个时候开始,公司的美术都变得…怪怪的。 #line:06dd03c
|
||||
peipei: 那是我的错……<br>我不习惯UF,本来想用笔来画的… #line:05f750d
|
||||
@@ -231,8 +230,6 @@ position: -1467,-2207
|
||||
<<CollectionClue>>
|
||||
//<<jump EyeCheckEnd>>
|
||||
<<endif>>
|
||||
<<set_EyeColorState EyeManager "CanImagineColor">>
|
||||
<<EyeEffect_FadeIn EyeManager>>
|
||||
<<jump EyeCheckEnd>>
|
||||
===
|
||||
|
||||
@@ -268,8 +265,6 @@ peipei: 我愧疚了好久,那时的我真的以为小草可以长成一棵树
|
||||
<<CollectionClue>>
|
||||
//<<jump EyeCheckEnd>>
|
||||
<<endif>>
|
||||
<<set_EyeColorState EyeManager "CanImagineColor">>
|
||||
<<EyeEffect_FadeIn EyeManager>>
|
||||
<<jump EyeCheckEnd>>
|
||||
|
||||
===
|
||||
|
||||
@@ -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/`(仅 Editor,MenuItem 与 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 Camera,Canvas 须在 **UI 层 (5)** 以匹配 UI Camera culling mask。
|
||||
|
||||
## 变更记录
|
||||
|
||||
| 日期 | 说明 |
|
||||
|---|---|
|
||||
| 2026-07-01 | 初稿:overlay debug 后整理 Agent 自动化计划 |
|
||||
Reference in New Issue
Block a user