using System.Collections; using System.Collections.Generic; using UnityEngine; using DG.Tweening; using Yarn.Unity; using AibisDream.Framework; using Cinemachine; using UnityEngine.Rendering; using UnityEngine.UI; using AibisDream.Kit; using UnityEngine.ResourceManagement.AsyncOperations; 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 = 0.58f; private const float MaxFrequencyBlur = 0.6f; private const float MinPixelDensity = 18f; private const float MaxPixelDensity = 96f; 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 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 EnableSaturationId = Shader.PropertyToID("_EnableSaturation"); private static readonly int SaturationId = Shader.PropertyToID("_Saturation"); [SerializeField] private BubbleSlotGroup bubbleSlotGroup; public BubbleSlotGroup BubbleSlotGroup => bubbleSlotGroup; [SerializeField] private GameObject memoryView; [SerializeField] private GameObject projector; [SerializeField] private SpriteRenderer memoryFinishedSpriteRenderer; // 用于显示记忆处理结束的表现 [SerializeField] private SpriteRenderer memoryFinishedImageNoColor; // 用于显示记忆处理结束的表现 [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 GameObject memoryGroup; [SerializeField] private CinemachineVirtualCamera memoryCamera; private bool isProcessing = false; // 新增变量来追踪是否正在处理记忆 private bool isPlayingFaderSound = false; // 追踪音效是否正在播放 private PunchTape _activePunchTape; [SerializeField] private MemoryPunchTapeSlot punchTapeSlot; [SerializeField] private Material fadeMaterial; private Material runtimeMemoryMaterial; private readonly List runtimeRenderTextures = new(); public void OpenMemoryView() { memoryGroup.SetActive(true); memoryView.gameObject.SetActive(true); EnsureMemoryRenderTargets(); StartCoroutine(PrimeMemoryRenderTargets()); } 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() { 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; } 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); if (memoryGroup != null) memoryGroup.SetActive(false); if (memoryCamera != null) CameraKit.Instance.RegisterCamera(CameraEnum.Memory, memoryCamera); } private void Start() { PrepareRuntimeMemoryMaterial(); frequencySlider.OnFound += CheckAllFound; claritySlider.OnFound += CheckAllFound; Init(); } private void OnDestroy() { if (runtimeMemoryMaterial != null) Destroy(runtimeMemoryMaterial); foreach (var renderTexture in runtimeRenderTextures) { if (renderTexture == null) continue; renderTexture.Release(); Destroy(renderTexture); } runtimeRenderTextures.Clear(); } private void EnsureMemoryRenderTargets() { if (memoryGroup == null || memoryView == null) return; var cameras = memoryGroup.GetComponentsInChildren(true); foreach (var renderCamera in cameras) { if (renderCamera == null || renderCamera.targetTexture == null) continue; renderCamera.allowHDR = false; renderCamera.allowMSAA = false; var originalTexture = renderCamera.targetTexture; var targetTexture = originalTexture; if (ShouldReplaceRenderTexture(originalTexture)) { targetTexture = CreateCompatibleRenderTexture(originalTexture); renderCamera.targetTexture = targetTexture; RebindMemoryRawImages(originalTexture, targetTexture); } if (!targetTexture.IsCreated()) { targetTexture.Create(); } } } private static bool ShouldReplaceRenderTexture(RenderTexture renderTexture) { if (renderTexture == null) return true; if (!SystemInfo.SupportsRenderTextureFormat(renderTexture.format)) return true; var maxSize = Mathf.Min(SystemInfo.maxTextureSize, 2048); return renderTexture.width > maxSize || renderTexture.height > maxSize; } private RenderTexture CreateCompatibleRenderTexture(RenderTexture source) { var maxSize = Mathf.Min(SystemInfo.maxTextureSize, 2048); var sourceWidth = source != null ? source.width : Screen.width; var sourceHeight = source != null ? source.height : Screen.height; var scale = Mathf.Min(1f, maxSize / (float)Mathf.Max(sourceWidth, sourceHeight)); var width = Mathf.Max(1, Mathf.RoundToInt(sourceWidth * scale)); var height = Mathf.Max(1, Mathf.RoundToInt(sourceHeight * scale)); var renderTexture = new RenderTexture(width, height, 24, RenderTextureFormat.ARGB32) { name = source != null ? $"{source.name}_RuntimeCompatible" : "Memory_RuntimeCompatible", antiAliasing = 1, useMipMap = false, autoGenerateMips = false }; renderTexture.Create(); runtimeRenderTextures.Add(renderTexture); return renderTexture; } private void RebindMemoryRawImages(Texture oldTexture, Texture newTexture) { var rawImages = memoryView.GetComponentsInChildren(true); foreach (var rawImage in rawImages) { if (rawImage != null && rawImage.texture == oldTexture) { rawImage.texture = newTexture; } } } private IEnumerator PrimeMemoryRenderTargets() { yield return null; ForceRenderMemoryTargets(); yield return new WaitForEndOfFrame(); ForceRenderMemoryTargets(); } private void ForceRenderMemoryTargets() { if (memoryGroup == null || !memoryGroup.activeInHierarchy) return; var cameras = memoryGroup.GetComponentsInChildren(true); foreach (var renderCamera in cameras) { if (renderCamera != null && renderCamera.enabled && renderCamera.targetTexture != null) { renderCamera.Render(); } } } private void CheckAllFound() { if (frequencySlider.IsFound && claritySlider.IsFound) OnSearchComplete(); } private float GetRandomNumber() { // 随机决定选择哪个区间 if (UnityEngine.Random.value < 0.5f) // 50% 概率选择第一个区间 { return UnityEngine.Random.Range(0.5f, 2f); } else // 50% 概率选择第二个区间 { return UnityEngine.Random.Range(4f, 5.5f); } } 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) { // 动态调整权重 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) fadeMaterial.SetFloat("_DirectionalDistortionFade", FadeMaterialStartValue); ResetSpriteTuning(); memoryFinishedSpriteRenderer.color = Color.black; memoryProcessVolume.weight = 0; _activePunchTape = null; 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; fadeMaterial.SetFloat("_DirectionalDistortionFade", FadeMaterialStartValue); Tween tween = fadeMaterial.DOFloat(0, "_DirectionalDistortionFade", 3); yield return tween.WaitForCompletion(); } [YarnCommand("PlayMemory")] public IEnumerator PlayMemory(bool useYarn = false, string memName = "") { memoryFinishedSpriteRenderer.color = Color.white; PunchTapeManager.Instance.CloseFavoritesPanel(); if (useYarn) { if (fadeMaterial != null) fadeMaterial.SetFloat("_DirectionalDistortionFade", FadeMaterialStartValue); var memoryKey = string.Format(MemorySpritePath, memName); var memoryHandle = ResourceSystem.LoadAsync(memoryKey); yield return memoryHandle; Sprite memSprite = memoryHandle.Status == AsyncOperationStatus.Succeeded ? memoryHandle.Result : null; memoryFinishedSpriteRenderer.sprite = memSprite; memoryFinishedImageNoColor.sprite = 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 = GetRandomNumber(); claritySlider.TargetValue = GetRandomNumber(); PunchTapeManager.Instance.CloseFavoritesPanel(); if (punchTape == null) { Debug.Log("no punchTape"); yield break; } _activePunchTape = punchTape; if (isProcessing) { Debug.LogWarning("Already processing a memory. Please wait until the process is finished."); yield break; } var searchMemoryKey = string.Format(MemorySpritePath, _activePunchTape.MemoryIndex); var searchMemoryHandle = ResourceSystem.LoadAsync(searchMemoryKey); yield return searchMemoryHandle; Sprite memorySprite = searchMemoryHandle.Status == AsyncOperationStatus.Succeeded ? searchMemoryHandle.Result : null; memoryFinishedSpriteRenderer.sprite = memorySprite; memoryFinishedImageNoColor.sprite = memorySprite; if (memorySprite == null) { Debug.LogError("Memory not found: " + searchMemoryKey); } memoryProcessVolume.weight = 1; AudioManager.Instance.PlaySfx("event:/Scriptal/mem_load"); yield return new WaitForSeconds(SearchStartDelay); AudioManager.Instance.SetSfxParam("event:/Amb/amb_mem_tuning", "is_mem_tuning", 1); memoryFinishedSpriteRenderer.color = Color.white; memoryProcessVolume.weight = 0; isProcessing = true; frequencySlider.PrepareForSearch(); claritySlider.PrepareForSearch(); 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); SetFloatIfExists(EnableSaturationId, active ? 1f : 0f); SetFloatIfExists(SaturationId, Mathf.Lerp(1f, 0.25f, strength)); } private void ResetSpriteTuning() { if (!useSpriteShaderTuning || fadeMaterial == null) return; ApplyFrequencyTuning(0f); ApplyClarityTuning(0f); } private void SetFloatIfExists(int propertyId, float value) { if (fadeMaterial.HasProperty(propertyId)) fadeMaterial.SetFloat(propertyId, value); } private void PrepareRuntimeMemoryMaterial() { if (!useSpriteShaderTuning || memoryFinishedSpriteRenderer == null || fadeMaterial == null) return; var shader = fadeMaterial.shader; if (shader == null || shader.name != "AibisDream/MemoryTuningSprite") shader = Shader.Find("AibisDream/MemoryTuningSprite"); if (shader == null) { Debug.LogWarning("[MemoryProcess] 未找到 AibisDream/MemoryTuningSprite,调频将回退到当前材质表现。", this); return; } runtimeMemoryMaterial = new Material(fadeMaterial) { shader = shader, name = $"{fadeMaterial.name}_RuntimeMemoryTuning" }; memoryFinishedSpriteRenderer.sharedMaterial = runtimeMemoryMaterial; fadeMaterial = runtimeMemoryMaterial; } public bool IsProcessing() { return isProcessing; } } }