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; private const float DefaultFadeDuration = 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 MinPixelDensity = 18f; private const float MaxPixelDensity = 96f; private const string DirectionalDistortionFadeProperty = "_DirectionalDistortionFade"; private static readonly int EnablePixelateId = Shader.PropertyToID("_EnablePixelate"); 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 EnableGaussianBlurId = Shader.PropertyToID("_EnableGaussianBlur"); private static readonly int GaussianBlurFadeId = Shader.PropertyToID("_GaussianBlurFade"); private static readonly int GaussianBlurOffsetId = Shader.PropertyToID("_GaussianBlurOffset"); private static readonly int EnableUvDistortId = Shader.PropertyToID("_EnableUVDistort"); 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; private Material runtimeMemoryMaterial; private Material runtimeNoColorMemoryMaterial; 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(); PunchTapeManager.Instance.OpenFavoritesPanel(); playBackground.gameObject.SetActive(false); playBackground.alpha = 0; StartStandbyOverlay(); } public void OnExit() { // 停止拉条音频 if (isPlayingFaderSound) { AudioManager.Instance.StopSfx("event:/ActionFB/mem_fader"); isPlayingFaderSound = false; } PunchTapeManager.Instance.CloseFavoritesPanel(); CloseMemoryView(); // 离开视图时的逻辑 Debug.Log("MemoryView exited."); if (frequencySlider != null) { frequencySlider.SetInteractable(false); frequencySlider.LockSlider(); } if (claritySlider != null) { claritySlider.SetInteractable(false); claritySlider.LockSlider(); } } 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(!useSpriteShaderTuning); frequencySlider.OnTuningChanged += ApplyFrequencyTuning; } if (claritySlider != null) { claritySlider.OnTuningChanged -= ApplyClarityTuning; claritySlider.Init(memoryClarityVolume); claritySlider.SetVolumeOutputEnabled(!useSpriteShaderTuning); 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); if (claritySlider != null) claritySlider.SetInteractable(false); ResetPresentationState(); } private void ResetPresentationState() { if (fadeMaterial != null) SetFloatIfExists(DirectionalDistortionFadeId, FadeMaterialStartValue); ResetSpriteTuning(); SetMemoryColor(Color.black); SetVolumeWeight(memoryProcessVolume, 0f); _activePunchTape = null; StartStandbyOverlay(); playBackground.gameObject.SetActive(false); playBackground.alpha = 0; } [YarnCommand("MemoryFinished")] public IEnumerator MemoryFinished() { yield return playBackground.DOFade(0f, DefaultFadeDuration).WaitForCompletion(); playBackground.gameObject.SetActive(false); PunchTapeManager.Instance.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, FadeMaterialStartValue); DG.Tweening.Sequence sequence = DOTween.Sequence(); AppendMaterialFloatTween(sequence, fadeMaterial, 0f, DirectionalDistortionFadeProperty, 3f); if (runtimeNoColorMemoryMaterial != null && runtimeNoColorMemoryMaterial != fadeMaterial) AppendMaterialFloatTween(sequence, runtimeNoColorMemoryMaterial, 0f, DirectionalDistortionFadeProperty, 3f); Tween tween = sequence; yield return tween.WaitForCompletion(); } [YarnCommand("PlayMemory")] public IEnumerator PlayMemory(bool useYarn = false, string memName = "") { SetMemoryColor(Color.white); PunchTapeManager.Instance.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; } } playBackground.gameObject.SetActive(true); playBackground.alpha = 0; AudioManager.Instance.PlaySfx("event:/ActionFB/mem_play"); yield return playBackground.DOFade(1f, DefaultFadeDuration).WaitForCompletion(); if (!useYarn) { DialogController.Instance.StartDialogNode( LocalizationKit.GetL10NParamKey(punchTapeSlot.punchTape.ClueName)); } } 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); PunchTapeManager.Instance.CloseFavoritesPanel(); if (punchTape == null) { Debug.Log("no punchTape"); StartStandbyOverlay(); yield break; } _activePunchTape = punchTape; if (isProcessing) { Debug.LogWarning("Already processing a memory. Please wait until the process is finished."); yield break; } StopStandbyOverlay(); ShowSearchOverlay(); var searchMemoryKey = string.Format(MemorySpritePath, _activePunchTape.MemoryIndex); 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 (!useSpriteShaderTuning || fadeMaterial == null) return; bool active = strength > 0.001f; SetFloatIfExists(FrequencyTuningId, strength); SetFloatIfExists(EnablePixelateId, active ? 1f : 0f); SetFloatIfExists(PixelateFadeId, strength); SetFloatIfExists(PixelatePixelDensityId, Mathf.Lerp(MaxPixelDensity, MinPixelDensity, strength)); SetFloatIfExists(EnableGaussianBlurId, active ? 1f : 0f); SetFloatIfExists(GaussianBlurFadeId, strength * MaxFrequencyBlur); SetFloatIfExists(GaussianBlurOffsetId, Mathf.Lerp(0.05f, 0.35f, strength)); } private void ApplyClarityTuning(float strength) { if (!useSpriteShaderTuning || fadeMaterial == null) return; bool active = strength > 0.001f; SetFloatIfExists(ClarityTuningId, strength); SetFloatIfExists(EnableUvDistortId, active ? 1f : 0f); SetFloatIfExists(UvDistortFadeId, strength * MaxClarityDistortion); } private void ResetSpriteTuning() { if (!useSpriteShaderTuning || 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() { if (!useSpriteShaderTuning || !HasMemoryGraphic()) return; var baseMaterial = GetColorMemoryMaterial(); if (baseMaterial == null) baseMaterial = fadeMaterial; var shader = baseMaterial != null ? baseMaterial.shader : null; if (shader == null || shader.name != "AibisDream/MemoryTuningSprite") shader = Shader.Find("AibisDream/MemoryTuningSprite"); if (shader == null) { Debug.LogWarning("[MemoryProcess] 未找到 AibisDream/MemoryTuningSprite,调频将回退到当前材质表现。", 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); } 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; } } }