using System.Collections; using System.Collections.Generic; using UnityEngine; using DG.Tweening; using Yarn.Unity; using AibisDream.Framework; using AibisDream.Kit; 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 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 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; 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 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); } private void Start() { PrepareRuntimeMemoryMaterial(); frequencySlider.OnFound += CheckAllFound; claritySlider.OnFound += CheckAllFound; Init(); } private void OnDestroy() { if (runtimeMemoryMaterial != null) Destroy(runtimeMemoryMaterial); if (runtimeNoColorMemoryMaterial != null) Destroy(runtimeNoColorMemoryMaterial); 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) { 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(); memoryFinishedSpriteRenderer.color = Color.black; SetVolumeWeight(memoryProcessVolume, 0f); _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; 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 = "") { memoryFinishedSpriteRenderer.color = 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; 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); } SetVolumeWeight(memoryProcessVolume, 1f); 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; SetVolumeWeight(memoryProcessVolume, 0f); 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); } 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 || 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 = CreateRuntimeMemoryMaterial(fadeMaterial, shader, "Color", 0f, 1f); memoryFinishedSpriteRenderer.sharedMaterial = runtimeMemoryMaterial; fadeMaterial = runtimeMemoryMaterial; if (memoryFinishedImageNoColor != null && memoryFinishedImageNoColor.sharedMaterial != null) { runtimeNoColorMemoryMaterial = CreateRuntimeMemoryMaterial( memoryFinishedImageNoColor.sharedMaterial, shader, "NoColor", 1f, 1.8f); memoryFinishedImageNoColor.sharedMaterial = runtimeNoColorMemoryMaterial; } ResetSpriteTuning(); } private static Material CreateRuntimeMemoryMaterial( Material source, Shader shader, string suffix, float grayscale, float contrast) { var material = new Material(source) { shader = shader, name = $"{source.name}_RuntimeMemoryTuning_{suffix}" }; SetFloatIfExists(material, MemoryGrayscaleId, grayscale); SetFloatIfExists(material, MemoryContrastId, contrast); return material; } public bool IsProcessing() { return isProcessing; } } }