using System.Collections; using UnityEngine; using DG.Tweening; using Yarn.Unity; using AibisDream.Framework; using AibisDream.Kit; using UnityEngine.Rendering; using UnityEngine.UI; using UnityEngine.ResourceManagement.AsyncOperations; using UnityEngine.Serialization; namespace AibisDream.FixSystem { public class MemoryProcess : MonoBehaviour { private const string MemorySpritePath = "Sprite/Memory/{0}.png"; private const float FadeMaterialStartValue = -15f; // ColorFadeOut 专用:从溶解刚要开始的位置起 tween,去掉 -15→-8 的空转,让整段时长都是可见的褪色。 private const float ColorFadeStartValue = -8.5f; private const float ColorFadeDuration = 4f; private const float DefaultFadeDuration = 1f; private const float MemoryFinishSnowDuration = 0.1f; private const float SearchStartDelay = 1f; private const float ProjectorMoveDuration = 2f; private const float MaxClarityDistortion = 1f; private const float MaxFrequencyBlur = 0.6f; private const float MobileMaxFrequencyBlur = 0.45f; private const float MinPixelDensity = 18f; private const float MobileMinPixelDensity = 24f; private const float MaxPixelDensity = 96f; private const string DirectionalDistortionFadeProperty = "_DirectionalDistortionFade"; private static readonly int FrequencyTuningId = Shader.PropertyToID("_FrequencyTuning"); private static readonly int ClarityTuningId = Shader.PropertyToID("_ClarityTuning"); private static readonly int DirectionalDistortionFadeId = Shader.PropertyToID(DirectionalDistortionFadeProperty); private static readonly int PixelateFadeId = Shader.PropertyToID("_PixelateFade"); private static readonly int PixelatePixelDensityId = Shader.PropertyToID("_PixelatePixelDensity"); private static readonly int GaussianBlurFadeId = Shader.PropertyToID("_GaussianBlurFade"); private static readonly int GaussianBlurOffsetId = Shader.PropertyToID("_GaussianBlurOffset"); private static readonly int UvDistortFadeId = Shader.PropertyToID("_UVDistortFade"); private static readonly int MemoryGrayscaleId = Shader.PropertyToID("_MemoryGrayscale"); private static readonly int MemoryContrastId = Shader.PropertyToID("_MemoryContrast"); [SerializeField] private GameObject memoryView; [SerializeField] private GameObject projector; [SerializeField] private Image memoryFinishedImage; // UI彩色记忆层 [SerializeField] private Image memoryFinishedNoColorImage; // UI去色记忆层 [FormerlySerializedAs("memoryFinishedspRender")] [FormerlySerializedAs("memoryFinishedSpriteRenderer")] [HideInInspector] [SerializeField] private SpriteRenderer legacyMemoryFinishedSpriteRenderer; [FormerlySerializedAs("memoryFinishedImageNoColor")] [HideInInspector] [SerializeField] private SpriteRenderer legacyMemoryFinishedImageNoColor; [SerializeField] private Volume mainEffect; [SerializeField] private Volume memoryClarityVolume; [SerializeField] private Volume memoryFrequencyVolume; [SerializeField] private Volume memoryProcessVolume; [SerializeField] private MemorySlider frequencySlider; // 频率调节滑动条 [SerializeField] private MemorySlider claritySlider; // 清晰度调节滑动条 [SerializeField] private bool useSpriteShaderTuning = true; [SerializeField] private CanvasGroup playBackground; [SerializeField] private Graphic standbyOverlay; [SerializeField, Range(0f, 1f)] private float standbyOverlayMinAlpha = 0.04f; [SerializeField, Range(0f, 1f)] private float standbyOverlayMaxAlpha = 0.12f; [SerializeField, Range(0f, 1f)] private float searchOverlayAlpha = 0.85f; [SerializeField] private float standbyOverlayPulseDuration = 0.8f; [SerializeField] private float searchTargetMinDistanceFromStart = 2.5f; [SerializeField] private GameObject memoryGroup; private bool isProcessing = false; // 新增变量来追踪是否正在处理记忆 private bool isPlayingFaderSound = false; // 追踪音效是否正在播放 private PunchTape _activePunchTape; [SerializeField] private MemoryPunchTapeSlot punchTapeSlot; [SerializeField] private Material fadeMaterial; [SerializeField] private Shader memoryTuningShader; private Material runtimeMemoryMaterial; private Material runtimeNoColorMemoryMaterial; private bool spriteShaderTuningAvailable; private Tween overlayTween; public void OpenMemoryView() { memoryGroup.SetActive(true); memoryView.gameObject.SetActive(true); } public IEnumerator DropMemoryProjector() { DG.Tweening.Sequence zoomInSequence = DOTween.Sequence(); zoomInSequence.Append(projector.transform.DOLocalMoveY(-5, ProjectorMoveDuration).SetEase(Ease.OutElastic, 0.5f)); yield return zoomInSequence.WaitForCompletion(); } public void PullMemoryProjector() { DG.Tweening.Sequence zoomOutSequence = DOTween.Sequence(); zoomOutSequence.Append(projector.transform.DOLocalMoveY(0, ProjectorMoveDuration).SetEase(Ease.OutElastic, 0.5f)); } public void CloseMemoryView() { StopStandbyOverlay(); SetOverlayAlpha(0f); memoryView.gameObject.SetActive(false); memoryGroup.SetActive(false); } public void OnEnter() { OpenMemoryView(); PrepareMemoryView(); } public void OnShow() { Debug.Log("memoryView showed."); PrepareMemoryView(); } private void PrepareMemoryView() { EnsurePunchTapeSlotReference(); ResolvePunchTapeManager()?.OpenFavoritesPanel(); SetPlayBackgroundVisible(false, 0f); StartStandbyOverlay(); } public void OnExit() { // 停止拉条音频 if (isPlayingFaderSound) { AudioManager.Instance.StopSfx("event:/ActionFB/mem_fader"); isPlayingFaderSound = false; } ResolvePunchTapeManager()?.CloseFavoritesPanel(); CloseMemoryView(); // 离开视图时的逻辑 Debug.Log("MemoryView exited."); if (frequencySlider != null) { frequencySlider.SetInteractable(false); frequencySlider.LockSlider(); } if (claritySlider != null) { claritySlider.SetInteractable(false); claritySlider.LockSlider(); } } private PunchTapeManager ResolvePunchTapeManager() { var manager = PunchTapeManager.Resolve(); if (manager == null) { Debug.LogError( "[MemoryProcess] 未找到 PunchTapeManager。请确认当前 Fix 场景中存在 PunchTapeCanvas。", this); } return manager; } private void SetPlayBackgroundVisible(bool active, float alpha) { if (playBackground == null) { Debug.LogWarning("[MemoryProcess] playBackground 未配置。", this); return; } playBackground.gameObject.SetActive(active); playBackground.alpha = alpha; } private void Awake() { InitSystem(); } private void EnsurePunchTapeSlotReference() { if (punchTapeSlot != null) return; punchTapeSlot = MemoryPunchTapeSlot.ActiveInstance; if (punchTapeSlot == null) { Debug.LogWarning( "[MemoryProcess] 未配置 MemoryPunchTapeSlot:请在 Inspector 将 punchTapeSlot 赋值为场景中的插槽,或确保存在 MemoryPunchTapeSlot。", this); } } private void InitSystem() { FixSystemCenter.SystemDic.Register(this); DisableLegacyRenderTextureCameras(); if (memoryGroup != null) memoryGroup.SetActive(false); } private void Start() { PrepareRuntimeMemoryMaterial(); frequencySlider.OnFound += CheckAllFound; claritySlider.OnFound += CheckAllFound; Init(); } private void OnDestroy() { overlayTween?.Kill(); if (runtimeMemoryMaterial != null) Destroy(runtimeMemoryMaterial); if (runtimeNoColorMemoryMaterial != null) Destroy(runtimeNoColorMemoryMaterial); } private void DisableLegacyRenderTextureCameras() { if (memoryGroup == null) return; var cameras = memoryGroup.GetComponentsInChildren(true); foreach (var renderCamera in cameras) { if (renderCamera != null && renderCamera.targetTexture != null) renderCamera.enabled = false; } } private void CheckAllFound() { if (frequencySlider.IsFound && claritySlider.IsFound) OnSearchComplete(); } private float GetRandomSearchTarget(MemorySlider slider) { float start = slider != null ? slider.StartValue : 0f; float max = slider != null ? slider.MaxValue : 6f; float minDistance = Mathf.Max(0f, searchTargetMinDistanceFromStart); float nearBandMin = start + minDistance; float nearBandMax = start + (max - start) * 0.55f; float farBandMin = max - 1.5f; float farBandMax = max - 0.5f; if (nearBandMin >= nearBandMax) return UnityEngine.Random.Range(farBandMin, farBandMax); if (UnityEngine.Random.value < 0.5f) return UnityEngine.Random.Range(nearBandMin, nearBandMax); return UnityEngine.Random.Range(farBandMin, farBandMax); } private void OnSearchComplete() { Debug.Log("Memory search completed."); frequencySlider.SetInteractable(false); claritySlider.SetInteractable(false); // 停止拉条音频 if (isPlayingFaderSound) { AudioManager.Instance.StopSfx("event:/ActionFB/mem_fader"); isPlayingFaderSound = false; } AudioManager.Instance.SetSfxParam("event:/Amb/amb_mem_tuning", "is_mem_tuning", 0); StartCoroutine(PlayMemory()); } public void TweenWeight(Volume postProcessingVolume, float targetWeight, float duration) { if (postProcessingVolume == null) return; // 动态调整权重 DOTween.To(() => postProcessingVolume.weight, x => postProcessingVolume.weight = x, targetWeight, duration); } public void Init() { isProcessing = false; // 停止拉条音频 if (isPlayingFaderSound) { AudioManager.Instance.StopSfx("event:/ActionFB/mem_fader"); isPlayingFaderSound = false; } if (frequencySlider != null) { frequencySlider.OnTuningChanged -= ApplyFrequencyTuning; frequencySlider.Init(memoryFrequencyVolume); frequencySlider.SetVolumeOutputEnabled(!spriteShaderTuningAvailable); frequencySlider.OnTuningChanged += ApplyFrequencyTuning; } if (claritySlider != null) { claritySlider.OnTuningChanged -= ApplyClarityTuning; claritySlider.Init(memoryClarityVolume); claritySlider.SetVolumeOutputEnabled(!spriteShaderTuningAvailable); claritySlider.OnTuningChanged += ApplyClarityTuning; } ResetPresentationState(); } /// /// 单条记忆播放结束后的复位:不调用 / /// (避免收起 Timeline),仅禁用拖动并把拉杆视觉回到默认位置; /// 完整 仅在退出记忆模块时 执行。 /// private void ResetAfterMemoryFinished() { isProcessing = false; if (isPlayingFaderSound) { AudioManager.Instance.StopSfx("event:/ActionFB/mem_fader"); isPlayingFaderSound = false; } if (frequencySlider != null) { frequencySlider.SetInteractable(false); frequencySlider.ResetVisualToDefault(); } if (claritySlider != null) { claritySlider.SetInteractable(false); claritySlider.ResetVisualToDefault(); } ResetPresentationState(); } private void ResetPresentationState() { if (fadeMaterial != null) SetFloatIfExists(DirectionalDistortionFadeId, FadeMaterialStartValue); ResetSpriteTuning(); SetMemoryColor(Color.black); SetVolumeWeight(memoryProcessVolume, 0f); _activePunchTape = null; StartStandbyOverlay(); SetPlayBackgroundVisible(false, 0f); } [YarnCommand("MemoryFinished")] public IEnumerator MemoryFinished() { if (playBackground != null) { yield return playBackground.DOFade(0f, DefaultFadeDuration).WaitForCompletion(); playBackground.gameObject.SetActive(false); } // 短闪雪花再回待机,避免记忆画面硬切 SetMemoryColor(Color.black); ShowSearchOverlay(); yield return new WaitForSeconds(MemoryFinishSnowDuration); ResolvePunchTapeManager()?.OpenFavoritesPanel(); ResetAfterMemoryFinished(); EnsurePunchTapeSlotReference(); if (punchTapeSlot != null && punchTapeSlot.punchTape != null) { punchTapeSlot.punchTape.used = true; punchTapeSlot.ReleaseSlot(); } } [YarnCommand("ColorFadeOut")] public IEnumerator ColorFadeOut() { if (fadeMaterial == null) yield break; SetFloatIfExists(DirectionalDistortionFadeId, ColorFadeStartValue); DG.Tweening.Sequence sequence = DOTween.Sequence(); AppendMaterialFloatTween(sequence, fadeMaterial, 0f, DirectionalDistortionFadeProperty, ColorFadeDuration); if (runtimeNoColorMemoryMaterial != null && runtimeNoColorMemoryMaterial != fadeMaterial) AppendMaterialFloatTween(sequence, runtimeNoColorMemoryMaterial, 0f, DirectionalDistortionFadeProperty, ColorFadeDuration); Tween tween = sequence; yield return tween.WaitForCompletion(); } [YarnCommand("PlayMemory")] public IEnumerator PlayMemory(bool useYarn = false, string memName = "") { SetMemoryColor(Color.white); var punchTapeManager = ResolvePunchTapeManager(); punchTapeManager?.CloseFavoritesPanel(); if (useYarn) { if (fadeMaterial != null) SetFloatIfExists(DirectionalDistortionFadeId, FadeMaterialStartValue); var memoryKey = string.Format(MemorySpritePath, memName); var memoryHandle = ResourceSystem.LoadAsync(memoryKey); yield return memoryHandle; Sprite memSprite = memoryHandle.Status == AsyncOperationStatus.Succeeded ? memoryHandle.Result : null; SetMemorySprite(memSprite); if (memSprite == null) { Debug.LogError("Memory not found: " + memoryKey); } } else { EnsurePunchTapeSlotReference(); if (punchTapeSlot == null || punchTapeSlot.punchTape == null) { Debug.LogError("[MemoryProcess] PlayMemory:未配置插槽或打孔带数据缺失。", this); yield break; } } SetPlayBackgroundVisible(true, 0f); AudioManager.Instance.PlaySfx("event:/ActionFB/mem_play"); if (playBackground != null) yield return playBackground.DOFade(1f, DefaultFadeDuration).WaitForCompletion(); if (!useYarn) { if (punchTapeManager == null || !punchTapeManager.TryGetDefinition(punchTapeSlot.punchTape, out var definition)) yield break; DialogController.Instance.StartDialogNode(definition.DialogNode); } } public IEnumerator SearchMemory(PunchTape punchTape, bool isSkipDialogue = false) { if (isPlayingFaderSound) { AudioManager.Instance.StopSfx("event:/ActionFB/mem_fader"); isPlayingFaderSound = false; } frequencySlider.TargetValue = GetRandomSearchTarget(frequencySlider); claritySlider.TargetValue = GetRandomSearchTarget(claritySlider); var punchTapeManager = ResolvePunchTapeManager(); punchTapeManager?.CloseFavoritesPanel(); if (punchTape == null) { Debug.Log("no punchTape"); StartStandbyOverlay(); yield break; } _activePunchTape = punchTape; if (punchTapeManager == null || !punchTapeManager.TryGetDefinition(_activePunchTape, out var definition)) { StartStandbyOverlay(); yield break; } if (isProcessing) { Debug.LogWarning("Already processing a memory. Please wait until the process is finished."); yield break; } StopStandbyOverlay(); ShowSearchOverlay(); var searchMemoryKey = string.Format(MemorySpritePath, definition.MemoryResourceKey); var searchMemoryHandle = ResourceSystem.LoadAsync(searchMemoryKey); yield return searchMemoryHandle; Sprite memorySprite = searchMemoryHandle.Status == AsyncOperationStatus.Succeeded ? searchMemoryHandle.Result : null; SetMemorySprite(memorySprite); if (memorySprite == null) { Debug.LogError("Memory not found: " + searchMemoryKey); } AudioManager.Instance.PlaySfx("event:/Scriptal/mem_load"); yield return new WaitForSeconds(SearchStartDelay); AudioManager.Instance.SetSfxParam("event:/Amb/amb_mem_tuning", "is_mem_tuning", 1); SetMemoryColor(Color.white); isProcessing = true; frequencySlider.PrepareForSearch(); claritySlider.PrepareForSearch(); ClearSearchOverlay(); bool freqUnlocked = false; bool clarityUnlocked = false; frequencySlider.UnlockSlider(() => freqUnlocked = true); claritySlider.UnlockSlider(() => clarityUnlocked = true); yield return new WaitUntil(() => freqUnlocked && clarityUnlocked); frequencySlider.SetInteractable(true); claritySlider.SetInteractable(true); } private void ApplyFrequencyTuning(float strength) { if (!spriteShaderTuningAvailable || fadeMaterial == null) return; float blurCap = Application.isMobilePlatform ? MobileMaxFrequencyBlur : MaxFrequencyBlur; float minPixelDensity = Application.isMobilePlatform ? MobileMinPixelDensity : MinPixelDensity; SetFloatIfExists(FrequencyTuningId, strength); SetFloatIfExists(PixelateFadeId, strength); SetFloatIfExists(PixelatePixelDensityId, Mathf.Lerp(MaxPixelDensity, minPixelDensity, strength)); SetFloatIfExists(GaussianBlurFadeId, strength * blurCap); SetFloatIfExists(GaussianBlurOffsetId, Mathf.Lerp(0.05f, 0.35f, strength)); } private void ApplyClarityTuning(float strength) { if (!spriteShaderTuningAvailable || fadeMaterial == null) return; SetFloatIfExists(ClarityTuningId, strength); SetFloatIfExists(UvDistortFadeId, strength * MaxClarityDistortion); } private void ResetSpriteTuning() { if (!spriteShaderTuningAvailable || fadeMaterial == null) return; ApplyFrequencyTuning(0f); ApplyClarityTuning(0f); } private void SetFloatIfExists(int propertyId, float value) { SetFloatIfExists(fadeMaterial, propertyId, value); if (runtimeNoColorMemoryMaterial != null && runtimeNoColorMemoryMaterial != fadeMaterial) SetFloatIfExists(runtimeNoColorMemoryMaterial, propertyId, value); } private static void SetVolumeWeight(Volume volume, float weight) { if (volume != null) volume.weight = weight; } private static void SetFloatIfExists(Material material, int propertyId, float value) { if (material != null && material.HasProperty(propertyId)) material.SetFloat(propertyId, value); } private static void AppendMaterialFloatTween( DG.Tweening.Sequence sequence, Material material, float target, string property, float duration) { if (sequence != null && material != null && material.HasProperty(property)) sequence.Join(material.DOFloat(target, property, duration)); } private void PrepareRuntimeMemoryMaterial() { spriteShaderTuningAvailable = false; if (!useSpriteShaderTuning || !HasMemoryGraphic()) return; var baseMaterial = GetColorMemoryMaterial(); if (baseMaterial == null) baseMaterial = fadeMaterial; var shader = memoryTuningShader; if (shader == null && baseMaterial != null) shader = baseMaterial.shader; if (shader == null || shader.name != "AibisDream/MemoryTuningSprite") shader = Shader.Find("AibisDream/MemoryTuningSprite"); if (shader == null) { Debug.LogWarning("[MemoryProcess] 未找到 AibisDream/MemoryTuningSprite,调频将回退到 Volume 后处理。", this); return; } if (!shader.isSupported) { Debug.LogWarning("[MemoryProcess] MemoryTuningSprite 在当前平台不受支持,调频将回退到 Volume 后处理。", this); return; } runtimeMemoryMaterial = CreateRuntimeMemoryMaterial(baseMaterial, shader, "Color", 0f, 1f); SetColorMemoryMaterial(runtimeMemoryMaterial); fadeMaterial = runtimeMemoryMaterial; var noColorBaseMaterial = GetNoColorMemoryMaterial(); if (noColorBaseMaterial != null) { runtimeNoColorMemoryMaterial = CreateRuntimeMemoryMaterial( noColorBaseMaterial, shader, "NoColor", 1f, 1.8f); SetNoColorMemoryMaterial(runtimeNoColorMemoryMaterial); } spriteShaderTuningAvailable = true; ResetSpriteTuning(); } private static Material CreateRuntimeMemoryMaterial( Material source, Shader shader, string suffix, float grayscale, float contrast) { var material = source != null ? new Material(source) : new Material(shader); material.shader = shader; material.name = $"{(source != null ? source.name : shader.name)}_RuntimeMemoryTuning_{suffix}"; SetFloatIfExists(material, MemoryGrayscaleId, grayscale); SetFloatIfExists(material, MemoryContrastId, contrast); return material; } private bool HasMemoryGraphic() { return memoryFinishedImage != null || legacyMemoryFinishedSpriteRenderer != null; } private void StartStandbyOverlay() { if (standbyOverlay == null || isProcessing) return; overlayTween?.Kill(); SetOverlayAlpha(standbyOverlayMinAlpha); overlayTween = standbyOverlay .DOFade(standbyOverlayMaxAlpha, Mathf.Max(0.01f, standbyOverlayPulseDuration)) .SetLoops(-1, LoopType.Yoyo) .SetEase(Ease.InOutSine); } private void StopStandbyOverlay() { overlayTween?.Kill(); overlayTween = null; } private void ShowSearchOverlay() { StopStandbyOverlay(); SetOverlayAlpha(searchOverlayAlpha); } private void ClearSearchOverlay() { StopStandbyOverlay(); SetOverlayAlpha(0f); } private void SetOverlayAlpha(float alpha) { if (standbyOverlay == null) return; Color color = standbyOverlay.color; color.a = Mathf.Clamp01(alpha); standbyOverlay.color = color; } private void SetMemorySprite(Sprite sprite) { if (memoryFinishedImage != null) memoryFinishedImage.sprite = sprite; if (memoryFinishedNoColorImage != null) memoryFinishedNoColorImage.sprite = sprite; if (legacyMemoryFinishedSpriteRenderer != null) legacyMemoryFinishedSpriteRenderer.sprite = sprite; if (legacyMemoryFinishedImageNoColor != null) legacyMemoryFinishedImageNoColor.sprite = sprite; } private void SetMemoryColor(Color color) { if (memoryFinishedImage != null) memoryFinishedImage.color = color; if (legacyMemoryFinishedSpriteRenderer != null) legacyMemoryFinishedSpriteRenderer.color = color; } private Material GetColorMemoryMaterial() { if (memoryFinishedImage != null) return memoryFinishedImage.material; return legacyMemoryFinishedSpriteRenderer != null ? legacyMemoryFinishedSpriteRenderer.sharedMaterial : null; } private Material GetNoColorMemoryMaterial() { if (memoryFinishedNoColorImage != null) return memoryFinishedNoColorImage.material; return legacyMemoryFinishedImageNoColor != null ? legacyMemoryFinishedImageNoColor.sharedMaterial : null; } private void SetColorMemoryMaterial(Material material) { if (memoryFinishedImage != null) memoryFinishedImage.material = material; if (legacyMemoryFinishedSpriteRenderer != null) legacyMemoryFinishedSpriteRenderer.sharedMaterial = material; } private void SetNoColorMemoryMaterial(Material material) { if (memoryFinishedNoColorImage != null) memoryFinishedNoColorImage.material = material; if (legacyMemoryFinishedImageNoColor != null) legacyMemoryFinishedImageNoColor.sharedMaterial = material; } public bool IsProcessing() { return isProcessing; } } }