From a294128dd6d4fbe67f2dae58849e0086b7674438 Mon Sep 17 00:00:00 2001 From: CL <62653664+Chen-Luan@users.noreply.github.com> Date: Mon, 13 Jul 2026 00:13:42 +0800 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20AudioWave=20=E9=9F=B3=E9=A2=91?= =?UTF-8?q?=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Assets/BundleRes/Scenes/ChartEditor.unity | 74 ++++++++ .../Gameplay/ChartEditor/View/EditAreaView.cs | 160 +++++++++++++++++- .../ViewModel/EditAreaViewModel.cs | 10 +- 3 files changed, 237 insertions(+), 7 deletions(-) diff --git a/Cyan-Stars/Assets/BundleRes/Scenes/ChartEditor.unity b/Cyan-Stars/Assets/BundleRes/Scenes/ChartEditor.unity index 7f2a5a7b7..afe73dde4 100644 --- a/Cyan-Stars/Assets/BundleRes/Scenes/ChartEditor.unity +++ b/Cyan-Stars/Assets/BundleRes/Scenes/ChartEditor.unity @@ -9881,6 +9881,7 @@ MonoBehaviour: notesFrameRect: {fileID: 200356153} scrollRect: {fileID: 1746494796} judgeLineRect: {fileID: 2134614510} + audioWaveRaoImage: {fileID: 702279848} --- !u!1 &549145901 GameObject: m_ObjectHideFlags: 0 @@ -13539,6 +13540,78 @@ MonoBehaviour: m_EditorClassIdentifier: m_Padding: {x: 0, y: 0, z: 0, w: 0} m_Softness: {x: 0, y: 0} +--- !u!1 &702279846 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 702279847} + - component: {fileID: 702279849} + - component: {fileID: 702279848} + m_Layer: 5 + m_Name: AudioWave + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &702279847 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 702279846} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1023559425} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0} +--- !u!114 &702279848 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 702279846} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 1344c3c82d62a2a41a3576d8abb8e3ea, type: 3} + m_Name: + m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.RawImage + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 0 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Texture: {fileID: 0} + m_UVRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 +--- !u!222 &702279849 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 702279846} + m_CullTransparentMesh: 1 --- !u!1001 &717315022 PrefabInstance: m_ObjectHideFlags: 0 @@ -19254,6 +19327,7 @@ RectTransform: m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: + - {fileID: 702279847} - {fileID: 1974464501} - {fileID: 200356153} m_Father: {fileID: 487915726} diff --git a/Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/View/EditAreaView.cs b/Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/View/EditAreaView.cs index 36ed3c9c6..06dd9703b 100644 --- a/Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/View/EditAreaView.cs +++ b/Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/View/EditAreaView.cs @@ -52,6 +52,11 @@ public class EditAreaView : BaseView, IPointerDownHandler [SerializeField] private RectTransform judgeLineRect = null!; + [SerializeField] + private RawImage audioWaveRaoImage = null!; + + + private readonly Color AudioWaveColor = new Color(1, 1, 1, 0.05f); private readonly CancellationTokenSource Cts = new CancellationTokenSource(); private static GameObjectPoolManager PoolManager => GameRoot.GameObjectPool; @@ -125,6 +130,25 @@ public override void Bind(EditAreaViewModel targetViewModel) }) .AddTo(this); + Observable.CombineLatest( + ViewModel.AudioClipHandler, + ViewModel.FirstMusicVersionItemOffset, + ViewModel.TotalBeats, + (handler, offset, _) => (handler, offset: offset ?? 0) + ) + .ThrottleLastFrame(1) // 避免同一帧多次刷新 + .Subscribe(async t => + { + if (t.handler != null && t.handler.IsDoing) + await t.handler; + + DrawAudioWave(t.handler?.Asset, t.offset); + }) + .AddTo(this); + + ViewModel.BeatZoom + .Subscribe(_ => RefreshFramesAnchor()) + .AddTo(this); // 1. 位置线逻辑 ViewModel.PosLineCount.Subscribe(UpdatePosLines).AddTo(this); @@ -137,9 +161,9 @@ public override void Bind(EditAreaViewModel targetViewModel) (_, _, _) => true ) .ThrottleLastFrame(1) // 避免同一帧多次刷新 - .Subscribe(_ => ForceRebuildBeatLines()).AddTo(this); + .Subscribe(_ => RebuildBeatLines()).AddTo(this); - // 3. 滚动时刷新节拍线和音符,如果没在播放音乐则一并更新时间轴时间 + // 3. 滚动时刷新节拍线和音符可见性,如果没在播放音乐则一并更新时间轴时间 scrollRect.onValueChanged.AsObservable() .Subscribe(_ => { @@ -197,7 +221,7 @@ private async Task CreatePosLine() #region BeatLines - private void ForceRebuildBeatLines() + private void RebuildBeatLines() { foreach (var kvp in ActiveBeatLines) if (kvp.Value != null) @@ -213,7 +237,6 @@ private async void UpdateBeatLinesVisibility() // 计算 Content 底部为 0,向上增加 // Viewport 可视区域在 Content 中的 Y 轴范围: float viewportHeight = viewportRect.rect.height; - float contentHeight = contentRect.rect.height; // 当 verticalNormalizedPosition = 0 时,显示底部 (0 ~ viewportHeight) float scrollY = Mathf.Max(0, -contentRect.anchoredPosition.y); @@ -299,7 +322,6 @@ private async void UpdateNotesVisibility() return; float viewportHeight = viewportRect.rect.height; - float contentHeight = contentRect.rect.height; float scrollY = Mathf.Max(0, -contentRect.anchoredPosition.y); @@ -458,6 +480,125 @@ private async Task CreateNoteObject(BaseChartNoteData note) #endregion + #region AudioWave + + private void DrawAudioWave(AudioClip? clip, int musicOffset) + { + RefreshFramesAnchor(); + + var rectTransform = (RectTransform)audioWaveRaoImage.transform; + + int uiWidth = Mathf.CeilToInt(rectTransform.rect.width); + int uiHeight = Mathf.CeilToInt(rectTransform.rect.height); + + // 防止宽高度异常导致报错 + if (uiWidth <= 0 || uiHeight <= 0) return; + + // 限制最大纹理尺寸 + int maxTexSize = Mathf.Min(SystemInfo.maxTextureSize, 8192); + int texWidth = Mathf.Clamp(uiWidth, 1, maxTexSize); + int texHeight = Mathf.Clamp(uiHeight, 1, maxTexSize); + + Texture2D texture = new Texture2D(texWidth, texHeight, TextureFormat.RGBA32, false); + + // 计算 UI 实际尺寸 与 纹理贴图 之间的缩放比例 + float scaleX = (float)uiWidth / texWidth; + float scaleY = (float)uiHeight / texHeight; + + // 背景填充透明色 + Color[] pixels = new Color[texWidth * texHeight]; + for (int i = 0; i < pixels.Length; i++) + pixels[i] = Color.clear; + + // 波形纵轴需与时间轴布局保持一致:AudioWave 铺满 Content, + // 且节拍线/音符均以 “判定线高度 + 拍数 * 每拍像素” 的方式从 Content 底部排布, + // 因此像素 -> 拍 -> (经 Bpm 组换算)时间,才能与时间轴对齐。 + var bpmGroup = ViewModel.BpmGroup; + double majorBeatDist = ViewModel.GetMajorBeatLineDistance(); + + // 无音频、缩放异常或 Bpm 组不合法时,仅绘制透明背景 + if (clip != null + && majorBeatDist > 0 + && bpmGroup.Count > 0 + && BpmGroupHelper.Validate(bpmGroup) == BpmGroupHelper.BpmValidationStatus.Valid) + { + // 获取数据 + float[] fullSamples = new float[clip.samples * clip.channels]; + clip.GetData(fullSamples, 0); + + float offsetSeconds = musicOffset / 1000f; + int centerTexX = texWidth / 2; + + // 搜索峰值,并按峰值拉伸填充像素 + for (int y = 0; y < texHeight; y++) + { + // 1. 计算当前像素行 (y) 映射到 Content 上的高度范围, + // 先换算为拍,再通过 Bpm 组求出对应的音频起止时间 + float uiYStart = y * scaleY; + float uiYEnd = (y + 1) * scaleY; + + double judgeLineY = judgeLineRect.anchoredPosition.y; + double beatStart = (uiYStart - judgeLineY) / majorBeatDist; + double beatEnd = (uiYEnd - judgeLineY) / majorBeatDist; + + float startTime = (BpmGroupHelper.CalculateTime(bpmGroup, beatStart) / 1000f) - offsetSeconds; + float endTime = (BpmGroupHelper.CalculateTime(bpmGroup, beatEnd) / 1000f) - offsetSeconds; + + // 2. 转换为 AudioClip 的 sample 索引 + int startSample = Mathf.FloorToInt(startTime * clip.frequency); + int endSample = Mathf.CeilToInt(endTime * clip.frequency); + + startSample = Mathf.Clamp(startSample, 0, clip.samples - 1); + endSample = Mathf.Clamp(endSample, 0, clip.samples - 1); + + float maxPeak = 0f; + + // 3. 搜索当前时间片段内的峰值振幅 + if (startSample < endSample) + { + int startIdx = startSample * clip.channels; + int endIdx = endSample * clip.channels; + endIdx = Mathf.Min(endIdx, fullSamples.Length); + + for (int i = startIdx; i < endIdx; i++) + { + float absVal = Mathf.Abs(fullSamples[i]); + if (absVal > maxPeak) + { + maxPeak = absVal; + } + } + } + + // 4. 按峰值拉伸并填充像素 + // 先算出 UI 上的波形宽度,再除以 scaleX 压缩为 Texture 里的像素宽度 + float uiWaveWidth = maxPeak * (uiWidth / 2f) * 0.5f; // 只填充 50% + int texWaveWidth = Mathf.FloorToInt(uiWaveWidth / scaleX); + + int startX = Mathf.Clamp(centerTexX - texWaveWidth, 0, texWidth - 1); + int endX = Mathf.Clamp(centerTexX + texWaveWidth, 0, texWidth - 1); + + // 写入一维颜色数组 + for (int x = startX; x <= endX; x++) + { + pixels[y * texWidth + x] = AudioWaveColor; + } + } + } + + // 绘制材质 + texture.SetPixels(pixels); + texture.Apply(); // 应用像素更改提交到 GPU + + // 释放旧的 Texture + if (audioWaveRaoImage.texture != null) + Destroy(audioWaveRaoImage.texture); + + audioWaveRaoImage.texture = texture; + } + + #endregion + #region Input public void OnPointerDown(PointerEventData eventData) @@ -516,6 +657,15 @@ private void Update() ViewModel.OnSpaceDown(); } + private void RefreshFramesAnchor() + { + // TODO: 目前仅供 AudioWave 使用,后续可考虑重构接入 BeatLinesFrame 和 NoteLinesFrame 以优化缩放时卡顿 + var rectTransform = (RectTransform)audioWaveRaoImage.transform; + double judgeLineY = judgeLineRect.anchoredPosition.y; + rectTransform.anchorMin = new Vector2(0, (float)(judgeLineY / contentRect.rect.height)); + rectTransform.anchorMax = new Vector2(1, (contentRect.rect.height - viewportRect.rect.height + (float)judgeLineY) / contentRect.rect.height); + } + protected void OnDestroy() { Cts.Cancel(); diff --git a/Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/ViewModel/EditAreaViewModel.cs b/Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/ViewModel/EditAreaViewModel.cs index ce26e08a1..f781962f5 100644 --- a/Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/ViewModel/EditAreaViewModel.cs +++ b/Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/ViewModel/EditAreaViewModel.cs @@ -5,11 +5,13 @@ using System; using System.Collections.Generic; using System.Diagnostics.Contracts; +using CatAsset.Runtime; using CyanStars.Chart; using CyanStars.Gameplay.ChartEditor.Command; using CyanStars.Gameplay.ChartEditor.Model; using ObservableCollections; using R3; +using UnityEngine; namespace CyanStars.Gameplay.ChartEditor.ViewModel { @@ -37,10 +39,14 @@ public class EditAreaViewModel : BaseViewModel public ReadOnlyReactiveProperty PosMagnetState => Model.PosMagnet; public ReadOnlyReactiveProperty PosAccuracy => Model.PosAccuracy; public ReadOnlyReactiveProperty IsCompactNoteButtonArea => Model.IsCompactNoteButtonArea; + public ReadOnlyReactiveProperty?> AudioClipHandler => Model.AudioClipHandler; public readonly ReadOnlyReactiveProperty CanPutNote; + public readonly ReadOnlyReactiveProperty FirstMusicVersionItemOffset; + public IList BpmGroup => Model.ChartPackData.CurrentValue.BpmGroup; + public EditAreaViewModel(ChartEditorModel model) : base(model) @@ -123,7 +129,7 @@ public EditAreaViewModel(ChartEditorModel model) .ToReadOnlyReactiveProperty() .AddTo(base.Disposables); - ReadOnlyReactiveProperty firstMusicVersionItemOffset = + FirstMusicVersionItemOffset = Model.ChartPackData.CurrentValue.MusicVersions .ObserveChanged() .Prepend((CollectionChangedEvent)default) @@ -150,7 +156,7 @@ public EditAreaViewModel(ChartEditorModel model) ) .Prepend(Unit.Default) .CombineLatest( - firstMusicVersionItemOffset, + FirstMusicVersionItemOffset, Model.AudioClipHandler, (_, offset, handler) => { From e97d473226a204e89cb2c062f7b2c3a5acf6fafc Mon Sep 17 00:00:00 2001 From: CL <62653664+Chen-Luan@users.noreply.github.com> Date: Mon, 13 Jul 2026 19:54:16 +0800 Subject: [PATCH 2/4] =?UTF-8?q?refactor:=20=E9=83=A8=E5=88=86=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Gameplay/ChartEditor/View/EditAreaView.cs | 158 +++++++++--------- .../ViewModel/EditAreaViewModel.cs | 1 - 2 files changed, 77 insertions(+), 82 deletions(-) diff --git a/Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/View/EditAreaView.cs b/Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/View/EditAreaView.cs index 06dd9703b..ba926ead4 100644 --- a/Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/View/EditAreaView.cs +++ b/Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/View/EditAreaView.cs @@ -56,8 +56,11 @@ public class EditAreaView : BaseView, IPointerDownHandler private RawImage audioWaveRaoImage = null!; + private const float AudioWaveSamplesPerSecond = 10; + const int AudioWaveTextureWidthPixel = 32; private readonly Color AudioWaveColor = new Color(1, 1, 1, 0.05f); + private readonly CancellationTokenSource Cts = new CancellationTokenSource(); private static GameObjectPoolManager PoolManager => GameRoot.GameObjectPool; @@ -486,114 +489,106 @@ private void DrawAudioWave(AudioClip? clip, int musicOffset) { RefreshFramesAnchor(); + // 计算材质高度和缩放比例 var rectTransform = (RectTransform)audioWaveRaoImage.transform; - - int uiWidth = Mathf.CeilToInt(rectTransform.rect.width); int uiHeight = Mathf.CeilToInt(rectTransform.rect.height); - // 防止宽高度异常导致报错 - if (uiWidth <= 0 || uiHeight <= 0) return; + if (uiHeight <= 0) + return; - // 限制最大纹理尺寸 int maxTexSize = Mathf.Min(SystemInfo.maxTextureSize, 8192); - int texWidth = Mathf.Clamp(uiWidth, 1, maxTexSize); int texHeight = Mathf.Clamp(uiHeight, 1, maxTexSize); - - Texture2D texture = new Texture2D(texWidth, texHeight, TextureFormat.RGBA32, false); - - // 计算 UI 实际尺寸 与 纹理贴图 之间的缩放比例 - float scaleX = (float)uiWidth / texWidth; float scaleY = (float)uiHeight / texHeight; + Texture2D texture = new Texture2D(AudioWaveTextureWidthPixel, texHeight, TextureFormat.Alpha8, false); // 背景填充透明色 - Color[] pixels = new Color[texWidth * texHeight]; + Color[] pixels = new Color[AudioWaveTextureWidthPixel * texHeight]; for (int i = 0; i < pixels.Length; i++) pixels[i] = Color.clear; - // 波形纵轴需与时间轴布局保持一致:AudioWave 铺满 Content, - // 且节拍线/音符均以 “判定线高度 + 拍数 * 每拍像素” 的方式从 Content 底部排布, - // 因此像素 -> 拍 -> (经 Bpm 组换算)时间,才能与时间轴对齐。 - var bpmGroup = ViewModel.BpmGroup; - double majorBeatDist = ViewModel.GetMajorBeatLineDistance(); - - // 无音频、缩放异常或 Bpm 组不合法时,仅绘制透明背景 - if (clip != null - && majorBeatDist > 0 - && bpmGroup.Count > 0 - && BpmGroupHelper.Validate(bpmGroup) == BpmGroupHelper.BpmValidationStatus.Valid) + // 无音频时仅绘制透明背景 + if (clip != null) { - // 获取数据 + // 获取总采样 float[] fullSamples = new float[clip.samples * clip.channels]; clip.GetData(fullSamples, 0); - float offsetSeconds = musicOffset / 1000f; - int centerTexX = texWidth / 2; + // 计算降采样次数 + float musicOffsetS = musicOffset / 1000f; + float timelineTotalTimeS = Mathf.Max(0, clip.length + musicOffsetS); + int samplesCount = (int)Math.Ceiling(timelineTotalTimeS * AudioWaveSamplesPerSecond); + float[] samples = new float[samplesCount]; - // 搜索峰值,并按峰值拉伸填充像素 - for (int y = 0; y < texHeight; y++) + // 从总采样中降采样 + for (int i = 0; i < samplesCount; i++) { - // 1. 计算当前像素行 (y) 映射到 Content 上的高度范围, - // 先换算为拍,再通过 Bpm 组求出对应的音频起止时间 - float uiYStart = y * scaleY; - float uiYEnd = (y + 1) * scaleY; - - double judgeLineY = judgeLineRect.anchoredPosition.y; - double beatStart = (uiYStart - judgeLineY) / majorBeatDist; - double beatEnd = (uiYEnd - judgeLineY) / majorBeatDist; - - float startTime = (BpmGroupHelper.CalculateTime(bpmGroup, beatStart) / 1000f) - offsetSeconds; - float endTime = (BpmGroupHelper.CalculateTime(bpmGroup, beatEnd) / 1000f) - offsetSeconds; + // 计算当前降采样点在时间轴上的起止时间 + float t0 = (float)i / samplesCount * timelineTotalTimeS; + float t1 = (float)(i + 1) / samplesCount * timelineTotalTimeS; - // 2. 转换为 AudioClip 的 sample 索引 - int startSample = Mathf.FloorToInt(startTime * clip.frequency); - int endSample = Mathf.CeilToInt(endTime * clip.frequency); + // 转换为音频片段本地的起止时间 + float localT0 = t0 - musicOffsetS; + float localT1 = t1 - musicOffsetS; - startSample = Mathf.Clamp(startSample, 0, clip.samples - 1); - endSample = Mathf.Clamp(endSample, 0, clip.samples - 1); + // 转换为对应的采样帧索引 + int frameStart = Mathf.Clamp(Mathf.FloorToInt(localT0 * clip.frequency), 0, clip.samples); + int frameEnd = Mathf.Clamp(Mathf.FloorToInt(localT1 * clip.frequency), 0, clip.samples); - float maxPeak = 0f; - - // 3. 搜索当前时间片段内的峰值振幅 - if (startSample < endSample) + // 如果有有效的重叠区间 + if (frameStart < frameEnd) { - int startIdx = startSample * clip.channels; - int endIdx = endSample * clip.channels; - endIdx = Mathf.Min(endIdx, fullSamples.Length); - - for (int i = startIdx; i < endIdx; i++) + float maxAmplitude = 0f; + for (int f = frameStart; f < frameEnd; f++) { - float absVal = Mathf.Abs(fullSamples[i]); - if (absVal > maxPeak) + int baseIndex = f * clip.channels; + for (int ch = 0; ch < clip.channels; ch++) { - maxPeak = absVal; + float amplitude = Mathf.Abs(fullSamples[baseIndex + ch]); + if (amplitude > maxAmplitude) + { + maxAmplitude = amplitude; + } } } + samples[i] = maxAmplitude; } + else + { + // 无重叠区间 + samples[i] = 0f; + } + } - // 4. 按峰值拉伸并填充像素 - // 先算出 UI 上的波形宽度,再除以 scaleX 压缩为 Texture 里的像素宽度 - float uiWaveWidth = maxPeak * (uiWidth / 2f) * 0.5f; // 只填充 50% - int texWaveWidth = Mathf.FloorToInt(uiWaveWidth / scaleX); + // 计算波形中心点和最大半宽 + float centerX = (AudioWaveTextureWidthPixel - 1) / 2f; + float maxHalfWidth = AudioWaveTextureWidthPixel / 2f; + + // 填充波形像素 + for (int y = 0; y < texHeight; y++) + { + // 将当前的纹理 Y 坐标归一化,并映射到 samples 数组对应的索引 + float t = texHeight > 1 ? (float)y / (texHeight - 1) : 0f; + int sampleIndex = Mathf.Clamp(Mathf.RoundToInt(t * (samplesCount - 1)), 0, samplesCount - 1); + float amplitude = samples[sampleIndex]; - int startX = Mathf.Clamp(centerTexX - texWaveWidth, 0, texWidth - 1); - int endX = Mathf.Clamp(centerTexX + texWaveWidth, 0, texWidth - 1); + // 根据振幅计算当前行波形的半宽 + float halfWidth = amplitude * maxHalfWidth; - // 写入一维颜色数组 - for (int x = startX; x <= endX; x++) + for (int x = 0; x < AudioWaveTextureWidthPixel; x++) { - pixels[y * texWidth + x] = AudioWaveColor; + // 计算当前像素到中心线的距离 + float distanceToCenter = Mathf.Abs(x - centerX); + + if (distanceToCenter <= halfWidth) + { + pixels[y * AudioWaveTextureWidthPixel + x] = AudioWaveColor; + } } } } - // 绘制材质 texture.SetPixels(pixels); - texture.Apply(); // 应用像素更改提交到 GPU - - // 释放旧的 Texture - if (audioWaveRaoImage.texture != null) - Destroy(audioWaveRaoImage.texture); - + texture.Apply(); audioWaveRaoImage.texture = texture; } @@ -642,6 +637,15 @@ out Beat beat #endregion + private void RefreshFramesAnchor() + { + // TODO: 目前仅供 AudioWave 使用,后续可考虑重构接入 BeatLinesFrame 和 NoteLinesFrame 以优化缩放时卡顿 + var rectTransform = (RectTransform)audioWaveRaoImage.transform; + double judgeLineY = judgeLineRect.anchoredPosition.y; + rectTransform.anchorMin = new Vector2(0, (float)(judgeLineY / contentRect.rect.height)); + rectTransform.anchorMax = new Vector2(1, (contentRect.rect.height - viewportRect.rect.height + (float)judgeLineY) / contentRect.rect.height); + } + private void Update() { if (!Input.GetKeyDown(KeyCode.Space)) @@ -657,15 +661,6 @@ private void Update() ViewModel.OnSpaceDown(); } - private void RefreshFramesAnchor() - { - // TODO: 目前仅供 AudioWave 使用,后续可考虑重构接入 BeatLinesFrame 和 NoteLinesFrame 以优化缩放时卡顿 - var rectTransform = (RectTransform)audioWaveRaoImage.transform; - double judgeLineY = judgeLineRect.anchoredPosition.y; - rectTransform.anchorMin = new Vector2(0, (float)(judgeLineY / contentRect.rect.height)); - rectTransform.anchorMax = new Vector2(1, (contentRect.rect.height - viewportRect.rect.height + (float)judgeLineY) / contentRect.rect.height); - } - protected void OnDestroy() { Cts.Cancel(); @@ -691,4 +686,5 @@ protected void OnDestroy() ActiveNotes.Clear(); } } + } diff --git a/Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/ViewModel/EditAreaViewModel.cs b/Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/ViewModel/EditAreaViewModel.cs index f781962f5..a447c831e 100644 --- a/Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/ViewModel/EditAreaViewModel.cs +++ b/Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/ViewModel/EditAreaViewModel.cs @@ -45,7 +45,6 @@ public class EditAreaViewModel : BaseViewModel public readonly ReadOnlyReactiveProperty CanPutNote; public readonly ReadOnlyReactiveProperty FirstMusicVersionItemOffset; - public IList BpmGroup => Model.ChartPackData.CurrentValue.BpmGroup; public EditAreaViewModel(ChartEditorModel model) From 26fa7f96fb2aa844bc2486157c6a7ee61ac03168 Mon Sep 17 00:00:00 2001 From: CL <62653664+Chen-Luan@users.noreply.github.com> Date: Mon, 13 Jul 2026 22:24:09 +0800 Subject: [PATCH 3/4] =?UTF-8?q?refactor:=20=E8=AE=A9=20AI=20=E5=86=99?= =?UTF-8?q?=E4=BA=86=20Burst?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Assets/BundleRes/Scenes/ChartEditor.unity | 6 +- Cyan-Stars/Assets/Scripts/CyanStars.asmdef | 1 + .../ChartEditor/AudioWaveformGenerator.cs | 250 ++++++++++++++++++ .../AudioWaveformGenerator.cs.meta | 3 + .../Gameplay/ChartEditor/View/EditAreaView.cs | 140 +++------- 5 files changed, 296 insertions(+), 104 deletions(-) create mode 100644 Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/AudioWaveformGenerator.cs create mode 100644 Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/AudioWaveformGenerator.cs.meta diff --git a/Cyan-Stars/Assets/BundleRes/Scenes/ChartEditor.unity b/Cyan-Stars/Assets/BundleRes/Scenes/ChartEditor.unity index afe73dde4..7729fe058 100644 --- a/Cyan-Stars/Assets/BundleRes/Scenes/ChartEditor.unity +++ b/Cyan-Stars/Assets/BundleRes/Scenes/ChartEditor.unity @@ -9881,7 +9881,7 @@ MonoBehaviour: notesFrameRect: {fileID: 200356153} scrollRect: {fileID: 1746494796} judgeLineRect: {fileID: 2134614510} - audioWaveRaoImage: {fileID: 702279848} + audioWaveRawImage: {fileID: 702279848} --- !u!1 &549145901 GameObject: m_ObjectHideFlags: 0 @@ -13572,8 +13572,8 @@ RectTransform: m_Children: [] m_Father: {fileID: 1023559425} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} - m_AnchorMin: {x: 0, y: 0} - m_AnchorMax: {x: 1, y: 1} + m_AnchorMin: {x: 0.25, y: 0} + m_AnchorMax: {x: 0.75, y: 1} m_AnchoredPosition: {x: 0, y: 0} m_SizeDelta: {x: 0, y: 0} m_Pivot: {x: 0.5, y: 0} diff --git a/Cyan-Stars/Assets/Scripts/CyanStars.asmdef b/Cyan-Stars/Assets/Scripts/CyanStars.asmdef index 0ef18680a..1e59ebb63 100644 --- a/Cyan-Stars/Assets/Scripts/CyanStars.asmdef +++ b/Cyan-Stars/Assets/Scripts/CyanStars.asmdef @@ -3,6 +3,7 @@ "rootNamespace": "CyanStars", "references": [ "GUID:a6b40c9ae77eca3438197e7d0aafc4d6", + "GUID:2665a8d13d1b3f18800f46e256720795", "GUID:d8b63aba1907145bea998dd612889d6b", "GUID:df380645f10b7bc4b97d4f5eb6303d95", "GUID:15fc0a57446b3144c949da3e2b9737a9", diff --git a/Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/AudioWaveformGenerator.cs b/Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/AudioWaveformGenerator.cs new file mode 100644 index 000000000..3c9065908 --- /dev/null +++ b/Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/AudioWaveformGenerator.cs @@ -0,0 +1,250 @@ +#nullable enable + +using System; +using System.Threading; +using System.Threading.Tasks; +using Unity.Burst; +using Unity.Collections; +using Unity.Jobs; +using Unity.Mathematics; +using UnityEngine; + +namespace CyanStars.Gameplay.ChartEditor.View +{ + public class AudioWaveformGenerator : IDisposable + { + private const int AudioWaveTextureWidthPixel = 32; + private const float AudioWaveSamplesPerSecond = 10f; + private readonly Color32 AudioWaveColor = new Color32(255, 255, 255, 13); + private readonly Color32 ClearColor = new Color32(0, 0, 0, 0); + + // 缓存的预计算数据 + private NativeArray precomputedSamples; + private int cachedMusicOffset; + private AudioClip? cachedClip; + private Texture2D? currentTexture; + + public void Dispose() + { + if (precomputedSamples.IsCreated) + { + precomputedSamples.Dispose(); + } + if (currentTexture != null) + { + UnityEngine.Object.Destroy(currentTexture); + } + } + + /// + /// 预计算音频数据 + /// + public async Task PrecomputeAsync(AudioClip? clip, int musicOffset, CancellationToken token) + { + if (clip == cachedClip && musicOffset == cachedMusicOffset) + return; // 数据未变,无需重新预计算 + + cachedClip = clip; + cachedMusicOffset = musicOffset; + + if (precomputedSamples.IsCreated) + precomputedSamples.Dispose(); + + if (clip == null) + return; + + int clipSamples = clip.samples; + int clipChannels = clip.channels; + int clipFrequency = clip.frequency; + float clipLength = clip.length; + + float[] fullSamples = new float[clipSamples * clipChannels]; + clip.GetData(fullSamples, 0); + + // 异步执行降采样计算 + await Task.Run(() => + { + float musicOffsetS = musicOffset / 1000f; + float timelineTotalTimeS = math.max(0, clipLength + musicOffsetS); + int samplesCount = (int)math.ceil(timelineTotalTimeS * AudioWaveSamplesPerSecond); + + NativeArray rawSamples = new NativeArray(fullSamples, Allocator.Persistent); + NativeArray downsampled = new NativeArray(samplesCount, Allocator.Persistent); + + try + { + var downsampleJob = new DownsampleAudioJob + { + FullSamples = rawSamples, + Channels = clipChannels, + Frequency = clipFrequency, + ClipSamples = clipSamples, + MusicOffsetS = musicOffsetS, + TimelineTotalTimeS = timelineTotalTimeS, + SamplesCount = samplesCount, + Result = downsampled + }; + + downsampleJob.Schedule(samplesCount, 64).Complete(); + + // 找出最大值 + float maxAmp = 0.0001f; + for (int i = 0; i < samplesCount; i++) + { + if (downsampled[i] > maxAmp) maxAmp = downsampled[i]; + } + + // 归一化并缓存 + precomputedSamples = new NativeArray(samplesCount, Allocator.Persistent); + for (int i = 0; i < samplesCount; i++) + { + precomputedSamples[i] = downsampled[i] / maxAmp; + } + } + finally + { + rawSamples.Dispose(); + downsampled.Dispose(); + } + }, token); + } + + /// + /// 异步生成纹理 + /// + public async Task GenerateTextureAsync(int uiHeight, CancellationToken token) + { + if (!precomputedSamples.IsCreated || precomputedSamples.Length == 0 || uiHeight <= 0) + return null; + + int maxTexSize = math.min(SystemInfo.maxTextureSize, 8192); + int texHeight = math.clamp(uiHeight, 1, maxTexSize); + int totalPixels = AudioWaveTextureWidthPixel * texHeight; + + // 创建接收像素数据的 NativeArray + NativeArray pixels = new NativeArray(totalPixels, Allocator.Persistent); + + try + { + var drawJob = new DrawWaveformTextureJob + { + Samples = precomputedSamples, + TexWidth = AudioWaveTextureWidthPixel, + TexHeight = texHeight, + WaveColor = AudioWaveColor, + BgColor = ClearColor, + Pixels = pixels + }; + + await Task.Run(() => + { + drawJob.Schedule(totalPixels, 64).Complete(); + }, token); + + if (token.IsCancellationRequested) return null; + + if (currentTexture != null && (currentTexture.width != AudioWaveTextureWidthPixel || currentTexture.height != texHeight)) + { + UnityEngine.Object.Destroy(currentTexture); + currentTexture = null; + } + + if (currentTexture == null) + { + currentTexture = new Texture2D(AudioWaveTextureWidthPixel, texHeight, TextureFormat.RGBA32, false); + } + + currentTexture.SetPixelData(pixels, 0); + currentTexture.Apply(); + + return currentTexture; + } + finally + { + pixels.Dispose(); + } + } + + #region Burst Jobs + + [BurstCompile] + private struct DownsampleAudioJob : IJobParallelFor + { + [ReadOnly] public NativeArray FullSamples; + public int Channels; + public int Frequency; + public int ClipSamples; + public float MusicOffsetS; + public float TimelineTotalTimeS; + public int SamplesCount; + + [WriteOnly] public NativeArray Result; + + public void Execute(int i) + { + float t0 = (float)i / SamplesCount * TimelineTotalTimeS; + float t1 = (float)(i + 1) / SamplesCount * TimelineTotalTimeS; + + float localT0 = t0 - MusicOffsetS; + float localT1 = t1 - MusicOffsetS; + + int frameStart = math.clamp((int)(localT0 * Frequency), 0, ClipSamples); + int frameEnd = math.clamp((int)(localT1 * Frequency), 0, ClipSamples); + + float maxAmplitude = 0f; + if (frameStart < frameEnd) + { + for (int f = frameStart; f < frameEnd; f++) + { + int baseIndex = f * Channels; + for (int ch = 0; ch < Channels; ch++) + { + float amplitude = math.abs(FullSamples[baseIndex + ch]); + if (amplitude > maxAmplitude) + { + maxAmplitude = amplitude; + } + } + } + } + Result[i] = maxAmplitude; + } + } + + [BurstCompile] + private struct DrawWaveformTextureJob : IJobParallelFor + { + [ReadOnly] public NativeArray Samples; + public int TexWidth; + public int TexHeight; + public Color32 WaveColor; + public Color32 BgColor; + + [WriteOnly] public NativeArray Pixels; + + // index 代表当前的像素索引 (0 到 totalPixels-1) + public void Execute(int index) + { + // 将一维索引转为 2D 的 y(行) 和 x(列) + int y = index / TexWidth; + int x = index % TexWidth; + + // 按照当前所在的行 (y) 计算采样的振幅 + float t = TexHeight > 1 ? (float)y / (TexHeight - 1) : 0f; + int sampleIndex = math.clamp((int)math.round(t * (Samples.Length - 1)), 0, Samples.Length - 1); + float amplitude = Samples[sampleIndex]; + + // 判断当前像素 (x) 是否在振幅宽度范围内 + float centerX = (TexWidth - 1) / 2f; + float maxHalfWidth = TexWidth / 2f; + float halfWidth = amplitude * maxHalfWidth; + float dist = math.abs(x - centerX); + + Pixels[index] = (dist <= halfWidth) ? WaveColor : BgColor; + } + } + + #endregion + + } +} diff --git a/Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/AudioWaveformGenerator.cs.meta b/Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/AudioWaveformGenerator.cs.meta new file mode 100644 index 000000000..3e7694199 --- /dev/null +++ b/Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/AudioWaveformGenerator.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 9d92b1bd62854003947cc69733250492 +timeCreated: 1783944383 \ No newline at end of file diff --git a/Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/View/EditAreaView.cs b/Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/View/EditAreaView.cs index ba926ead4..ec34db326 100644 --- a/Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/View/EditAreaView.cs +++ b/Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/View/EditAreaView.cs @@ -53,17 +53,17 @@ public class EditAreaView : BaseView, IPointerDownHandler private RectTransform judgeLineRect = null!; [SerializeField] - private RawImage audioWaveRaoImage = null!; + private RawImage audioWaveRawImage = null!; - private const float AudioWaveSamplesPerSecond = 10; - const int AudioWaveTextureWidthPixel = 32; - private readonly Color AudioWaveColor = new Color(1, 1, 1, 0.05f); + private readonly AudioWaveformGenerator WaveformGenerator = new AudioWaveformGenerator(); + private CancellationTokenSource? waveCts; private readonly CancellationTokenSource Cts = new CancellationTokenSource(); private static GameObjectPoolManager PoolManager => GameRoot.GameObjectPool; + // 管理当前激活的节拍线:Key=节拍索引(含细分拍),Value=节拍线物体实例 // 开始加载时会将 item 对应的 Value 设为 null 占位,加载完成后覆写为 gameObject private readonly Dictionary ActiveBeatLines = new Dictionary(); @@ -139,13 +139,13 @@ public override void Bind(EditAreaViewModel targetViewModel) ViewModel.TotalBeats, (handler, offset, _) => (handler, offset: offset ?? 0) ) - .ThrottleLastFrame(1) // 避免同一帧多次刷新 + .ThrottleLastFrame(1) .Subscribe(async t => { if (t.handler != null && t.handler.IsDoing) await t.handler; - DrawAudioWave(t.handler?.Asset, t.offset); + await RequestDrawAudioWaveAsync(t.handler?.Asset, t.offset); }) .AddTo(this); @@ -485,111 +485,45 @@ private async Task CreateNoteObject(BaseChartNoteData note) #region AudioWave - private void DrawAudioWave(AudioClip? clip, int musicOffset) + private async Task RequestDrawAudioWaveAsync(AudioClip? clip, int musicOffset) { RefreshFramesAnchor(); - // 计算材质高度和缩放比例 - var rectTransform = (RectTransform)audioWaveRaoImage.transform; + var rectTransform = (RectTransform)audioWaveRawImage.transform; int uiHeight = Mathf.CeilToInt(rectTransform.rect.height); - if (uiHeight <= 0) - return; - - int maxTexSize = Mathf.Min(SystemInfo.maxTextureSize, 8192); - int texHeight = Mathf.Clamp(uiHeight, 1, maxTexSize); - float scaleY = (float)uiHeight / texHeight; - Texture2D texture = new Texture2D(AudioWaveTextureWidthPixel, texHeight, TextureFormat.Alpha8, false); + if (uiHeight <= 0) return; - // 背景填充透明色 - Color[] pixels = new Color[AudioWaveTextureWidthPixel * texHeight]; - for (int i = 0; i < pixels.Length; i++) - pixels[i] = Color.clear; + // 取消上一次未完成的绘制任务 + waveCts?.Cancel(); + waveCts?.Dispose(); + waveCts = new CancellationTokenSource(); + var token = waveCts.Token; - // 无音频时仅绘制透明背景 - if (clip != null) + try { - // 获取总采样 - float[] fullSamples = new float[clip.samples * clip.channels]; - clip.GetData(fullSamples, 0); - - // 计算降采样次数 - float musicOffsetS = musicOffset / 1000f; - float timelineTotalTimeS = Mathf.Max(0, clip.length + musicOffsetS); - int samplesCount = (int)Math.Ceiling(timelineTotalTimeS * AudioWaveSamplesPerSecond); - float[] samples = new float[samplesCount]; - - // 从总采样中降采样 - for (int i = 0; i < samplesCount; i++) - { - // 计算当前降采样点在时间轴上的起止时间 - float t0 = (float)i / samplesCount * timelineTotalTimeS; - float t1 = (float)(i + 1) / samplesCount * timelineTotalTimeS; - - // 转换为音频片段本地的起止时间 - float localT0 = t0 - musicOffsetS; - float localT1 = t1 - musicOffsetS; - - // 转换为对应的采样帧索引 - int frameStart = Mathf.Clamp(Mathf.FloorToInt(localT0 * clip.frequency), 0, clip.samples); - int frameEnd = Mathf.Clamp(Mathf.FloorToInt(localT1 * clip.frequency), 0, clip.samples); - - // 如果有有效的重叠区间 - if (frameStart < frameEnd) - { - float maxAmplitude = 0f; - for (int f = frameStart; f < frameEnd; f++) - { - int baseIndex = f * clip.channels; - for (int ch = 0; ch < clip.channels; ch++) - { - float amplitude = Mathf.Abs(fullSamples[baseIndex + ch]); - if (amplitude > maxAmplitude) - { - maxAmplitude = amplitude; - } - } - } - samples[i] = maxAmplitude; - } - else - { - // 无重叠区间 - samples[i] = 0f; - } - } - - // 计算波形中心点和最大半宽 - float centerX = (AudioWaveTextureWidthPixel - 1) / 2f; - float maxHalfWidth = AudioWaveTextureWidthPixel / 2f; + // 预计算 + await WaveformGenerator.PrecomputeAsync(clip, musicOffset, token); - // 填充波形像素 - for (int y = 0; y < texHeight; y++) - { - // 将当前的纹理 Y 坐标归一化,并映射到 samples 数组对应的索引 - float t = texHeight > 1 ? (float)y / (texHeight - 1) : 0f; - int sampleIndex = Mathf.Clamp(Mathf.RoundToInt(t * (samplesCount - 1)), 0, samplesCount - 1); - float amplitude = samples[sampleIndex]; + if (token.IsCancellationRequested) + return; - // 根据振幅计算当前行波形的半宽 - float halfWidth = amplitude * maxHalfWidth; + // 生成纹理 + Texture2D? waveTex = await WaveformGenerator.GenerateTextureAsync(uiHeight, token); - for (int x = 0; x < AudioWaveTextureWidthPixel; x++) - { - // 计算当前像素到中心线的距离 - float distanceToCenter = Mathf.Abs(x - centerX); + if (token.IsCancellationRequested) + return; - if (distanceToCenter <= halfWidth) - { - pixels[y * AudioWaveTextureWidthPixel + x] = AudioWaveColor; - } - } - } + audioWaveRawImage.texture = waveTex != null ? waveTex : null; // 无音频处理 + } + catch (OperationCanceledException) + { + // 忽略取消异常 + } + catch (Exception ex) + { + Debug.LogError($"[AudioWaveform] 生成波形图报错: {ex}"); } - - texture.SetPixels(pixels); - texture.Apply(); - audioWaveRaoImage.texture = texture; } #endregion @@ -640,10 +574,10 @@ out Beat beat private void RefreshFramesAnchor() { // TODO: 目前仅供 AudioWave 使用,后续可考虑重构接入 BeatLinesFrame 和 NoteLinesFrame 以优化缩放时卡顿 - var rectTransform = (RectTransform)audioWaveRaoImage.transform; + var rectTransform = (RectTransform)audioWaveRawImage.transform; double judgeLineY = judgeLineRect.anchoredPosition.y; - rectTransform.anchorMin = new Vector2(0, (float)(judgeLineY / contentRect.rect.height)); - rectTransform.anchorMax = new Vector2(1, (contentRect.rect.height - viewportRect.rect.height + (float)judgeLineY) / contentRect.rect.height); + rectTransform.anchorMin = new Vector2(0.25f, (float)(judgeLineY / contentRect.rect.height)); + rectTransform.anchorMax = new Vector2(0.75f, (contentRect.rect.height - viewportRect.rect.height + (float)judgeLineY) / contentRect.rect.height); } private void Update() @@ -665,6 +599,8 @@ protected void OnDestroy() { Cts.Cancel(); Cts.Dispose(); + waveCts?.Cancel(); + waveCts?.Dispose(); // 清理节拍线 foreach (var kvp in ActiveBeatLines) @@ -683,6 +619,8 @@ protected void OnDestroy() } } + WaveformGenerator.Dispose(); + ActiveNotes.Clear(); } } From 428cbed8fc7b0083f2e162894581393be171d2b0 Mon Sep 17 00:00:00 2001 From: CL <62653664+Chen-Luan@users.noreply.github.com> Date: Mon, 13 Jul 2026 22:44:05 +0800 Subject: [PATCH 4/4] =?UTF-8?q?feat:=20=E5=B0=86=20AudioWave=20=E6=8E=A5?= =?UTF-8?q?=E5=85=A5=E5=88=B6=E8=B0=B1=E5=99=A8=E8=AE=BE=E7=BD=AE=E5=92=8C?= =?UTF-8?q?=20PlayerPrefs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Assets/BundleRes/Scenes/ChartEditor.unity | 410 +++++++++++++++++- .../ChartEditorPlayerPrefsManager.cs | 29 +- .../ChartEditor/Model/ChartEditorModel.cs | 3 +- .../View/ChartEditorSettingView.cs | 19 +- .../Gameplay/ChartEditor/View/EditAreaView.cs | 4 + .../ViewModel/ChartEditorSettingViewModel.cs | 6 +- .../ViewModel/EditAreaViewModel.cs | 1 + 7 files changed, 446 insertions(+), 26 deletions(-) diff --git a/Cyan-Stars/Assets/BundleRes/Scenes/ChartEditor.unity b/Cyan-Stars/Assets/BundleRes/Scenes/ChartEditor.unity index 7729fe058..8e4057d68 100644 --- a/Cyan-Stars/Assets/BundleRes/Scenes/ChartEditor.unity +++ b/Cyan-Stars/Assets/BundleRes/Scenes/ChartEditor.unity @@ -3834,6 +3834,164 @@ CanvasRenderer: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 208578990} m_CullTransparentMesh: 1 +--- !u!1 &234885784 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 234885785} + - component: {fileID: 234885788} + - component: {fileID: 234885787} + - component: {fileID: 234885786} + m_Layer: 5 + m_Name: Text (TMP) + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &234885785 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 234885784} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 542396988} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &234885786 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 234885784} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3} + m_Name: + m_EditorClassIdentifier: + m_IgnoreLayout: 0 + m_MinWidth: -1 + m_MinHeight: -1 + m_PreferredWidth: 598.233 + m_PreferredHeight: -1 + m_FlexibleWidth: -1 + m_FlexibleHeight: -1 + m_LayoutPriority: 1 +--- !u!114 &234885787 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 234885784} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_text: "\u663E\u793A\u97F3\u9891\u66F2\u7EBF" + m_isRightToLeft: 0 + m_fontAsset: {fileID: 11400000, guid: 5d14d235b5318bc49a0e892f337a7e87, type: 2} + m_sharedMaterial: {fileID: 5463881750006113116, guid: 5d14d235b5318bc49a0e892f337a7e87, type: 2} + m_fontSharedMaterials: [] + m_fontMaterial: {fileID: 0} + m_fontMaterials: [] + m_fontColor32: + serializedVersion: 2 + rgba: 4294967295 + m_fontColor: {r: 1, g: 1, b: 1, a: 1} + m_enableVertexGradient: 0 + m_colorMode: 3 + m_fontColorGradient: + topLeft: {r: 1, g: 1, b: 1, a: 1} + topRight: {r: 1, g: 1, b: 1, a: 1} + bottomLeft: {r: 1, g: 1, b: 1, a: 1} + bottomRight: {r: 1, g: 1, b: 1, a: 1} + m_fontColorGradientPreset: {fileID: 0} + m_spriteAsset: {fileID: 0} + m_tintAllSprites: 0 + m_StyleSheet: {fileID: 0} + m_TextStyleHashCode: -1183493901 + m_overrideHtmlColors: 0 + m_faceColor: + serializedVersion: 2 + rgba: 4294967295 + m_fontSize: 36 + m_fontSizeBase: 36 + m_fontWeight: 400 + m_enableAutoSizing: 0 + m_fontSizeMin: 18 + m_fontSizeMax: 72 + m_fontStyle: 0 + m_HorizontalAlignment: 1 + m_VerticalAlignment: 512 + m_textAlignment: 65535 + m_characterSpacing: 0 + m_characterHorizontalScale: 1 + m_wordSpacing: 0 + m_lineSpacing: 0 + m_lineSpacingMax: 0 + m_paragraphSpacing: 0 + m_charWidthMaxAdj: 0 + m_TextWrappingMode: 1 + m_wordWrappingRatios: 0.4 + m_overflowMode: 0 + m_linkedTextComponent: {fileID: 0} + parentLinkedComponent: {fileID: 0} + m_enableKerning: 1 + m_ActiveFontFeatures: 6e72656b + m_enableExtraPadding: 0 + checkPaddingRequired: 0 + m_isRichText: 1 + m_EmojiFallbackSupport: 1 + m_parseCtrlCharacters: 1 + m_isOrthographic: 1 + m_isCullingEnabled: 0 + m_horizontalMapping: 0 + m_verticalMapping: 0 + m_uvLineOffset: 0 + m_geometrySortingOrder: 0 + m_IsTextObjectScaleStatic: 0 + m_VertexBufferAutoSizeReduction: 0 + m_useMaxVisibleDescender: 1 + m_pageToDisplay: 1 + m_margin: {x: 0, y: 0, z: 0, w: 0} + m_isUsingLegacyAnimationComponent: 0 + m_isVolumetricText: 0 + m_hasFontAssetChanged: 0 + m_baseMaterial: {fileID: 0} + m_maskOffset: {x: 0, y: 0, z: 0, w: 0} +--- !u!222 &234885788 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 234885784} + m_CullTransparentMesh: 1 --- !u!1 &239559643 GameObject: m_ObjectHideFlags: 0 @@ -8050,10 +8208,10 @@ RectTransform: - {fileID: 978114220} m_Father: {fileID: 1514540015} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} - m_AnchorMin: {x: 0, y: 0} - m_AnchorMax: {x: 0, y: 0} - m_AnchoredPosition: {x: 0, y: 0} - m_SizeDelta: {x: 0, y: 602.50006} + m_AnchorMin: {x: 0, y: 1} + m_AnchorMax: {x: 0, y: 1} + m_AnchoredPosition: {x: 100, y: -717.50006} + m_SizeDelta: {x: 1205.0001, y: 0} m_Pivot: {x: 0, y: 0} --- !u!114 &442738496 MonoBehaviour: @@ -9683,6 +9841,70 @@ RectTransform: m_AnchoredPosition: {x: 0, y: 0} m_SizeDelta: {x: 0, y: 75} m_Pivot: {x: 0.5, y: 0.5} +--- !u!1 &542396987 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 542396988} + - component: {fileID: 542396989} + m_Layer: 5 + m_Name: "\u663E\u793A\u97F3\u9891\u66F2\u7EBFFrame" + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &542396988 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 542396987} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 234885785} + - {fileID: 798982892} + m_Father: {fileID: 1297060270} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 75} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &542396989 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 542396987} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 30649d3a9faa99c48a7b1166b86bf2a0, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Padding: + m_Left: 40 + m_Right: 40 + m_Top: 0 + m_Bottom: 0 + m_ChildAlignment: 0 + m_Spacing: 0 + m_ChildForceExpandWidth: 0 + m_ChildForceExpandHeight: 1 + m_ChildControlWidth: 1 + m_ChildControlHeight: 1 + m_ChildScaleWidth: 0 + m_ChildScaleHeight: 0 + m_ReverseArrangement: 0 --- !u!1 &543012495 GameObject: m_ObjectHideFlags: 0 @@ -13584,7 +13806,7 @@ MonoBehaviour: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 702279846} - m_Enabled: 1 + m_Enabled: 0 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 1344c3c82d62a2a41a3576d8abb8e3ea, type: 3} m_Name: @@ -15219,6 +15441,172 @@ RectTransform: m_CorrespondingSourceObject: {fileID: 8632055456003868757, guid: f11dfa8f2cee203408b50a343df0badb, type: 3} m_PrefabInstance: {fileID: 793223616} m_PrefabAsset: {fileID: 0} +--- !u!1001 &798982891 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + serializedVersion: 3 + m_TransformParent: {fileID: 542396988} + m_Modifications: + - target: {fileID: 1267847065536664933, guid: 90efbfb46b5e7f94aae8607cfb874781, type: 3} + propertyPath: m_SizeDelta.x + value: 48 + objectReference: {fileID: 0} + - target: {fileID: 1267847065536664933, guid: 90efbfb46b5e7f94aae8607cfb874781, type: 3} + propertyPath: m_SizeDelta.y + value: 48 + objectReference: {fileID: 0} + - target: {fileID: 2267842852582037851, guid: 90efbfb46b5e7f94aae8607cfb874781, type: 3} + propertyPath: m_fontSize + value: 41.05 + objectReference: {fileID: 0} + - target: {fileID: 2870093097978841429, guid: 90efbfb46b5e7f94aae8607cfb874781, type: 3} + propertyPath: m_Sprite + value: + objectReference: {fileID: 21300000, guid: 5aaa57c84d13ebd498cd8001c99c0e11, type: 3} + - target: {fileID: 4573623285734476500, guid: 90efbfb46b5e7f94aae8607cfb874781, type: 3} + propertyPath: rectTransformToChangeScale.Array.size + value: 3 + objectReference: {fileID: 0} + - target: {fileID: 4573623285734476500, guid: 90efbfb46b5e7f94aae8607cfb874781, type: 3} + propertyPath: 'rectTransformToChangeScale.Array.data[3]' + value: + objectReference: {fileID: 0} + - target: {fileID: 7757076495624774868, guid: 90efbfb46b5e7f94aae8607cfb874781, type: 3} + propertyPath: m_Pivot.x + value: 0.5 + objectReference: {fileID: 0} + - target: {fileID: 7757076495624774868, guid: 90efbfb46b5e7f94aae8607cfb874781, type: 3} + propertyPath: m_Pivot.y + value: 0.5 + objectReference: {fileID: 0} + - target: {fileID: 7757076495624774868, guid: 90efbfb46b5e7f94aae8607cfb874781, type: 3} + propertyPath: m_AnchorMax.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 7757076495624774868, guid: 90efbfb46b5e7f94aae8607cfb874781, type: 3} + propertyPath: m_AnchorMax.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 7757076495624774868, guid: 90efbfb46b5e7f94aae8607cfb874781, type: 3} + propertyPath: m_AnchorMin.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 7757076495624774868, guid: 90efbfb46b5e7f94aae8607cfb874781, type: 3} + propertyPath: m_AnchorMin.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 7757076495624774868, guid: 90efbfb46b5e7f94aae8607cfb874781, type: 3} + propertyPath: m_SizeDelta.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 7757076495624774868, guid: 90efbfb46b5e7f94aae8607cfb874781, type: 3} + propertyPath: m_SizeDelta.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 7757076495624774868, guid: 90efbfb46b5e7f94aae8607cfb874781, type: 3} + propertyPath: m_LocalPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 7757076495624774868, guid: 90efbfb46b5e7f94aae8607cfb874781, type: 3} + propertyPath: m_LocalPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 7757076495624774868, guid: 90efbfb46b5e7f94aae8607cfb874781, type: 3} + propertyPath: m_LocalPosition.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 7757076495624774868, guid: 90efbfb46b5e7f94aae8607cfb874781, type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 7757076495624774868, guid: 90efbfb46b5e7f94aae8607cfb874781, type: 3} + propertyPath: m_LocalRotation.x + value: -0 + objectReference: {fileID: 0} + - target: {fileID: 7757076495624774868, guid: 90efbfb46b5e7f94aae8607cfb874781, type: 3} + propertyPath: m_LocalRotation.y + value: -0 + objectReference: {fileID: 0} + - target: {fileID: 7757076495624774868, guid: 90efbfb46b5e7f94aae8607cfb874781, type: 3} + propertyPath: m_LocalRotation.z + value: -0 + objectReference: {fileID: 0} + - target: {fileID: 7757076495624774868, guid: 90efbfb46b5e7f94aae8607cfb874781, type: 3} + propertyPath: m_AnchoredPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 7757076495624774868, guid: 90efbfb46b5e7f94aae8607cfb874781, type: 3} + propertyPath: m_AnchoredPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 7757076495624774868, guid: 90efbfb46b5e7f94aae8607cfb874781, type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 7757076495624774868, guid: 90efbfb46b5e7f94aae8607cfb874781, type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 7757076495624774868, guid: 90efbfb46b5e7f94aae8607cfb874781, type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 7757076495624774869, guid: 90efbfb46b5e7f94aae8607cfb874781, type: 3} + propertyPath: m_Name + value: RadioButton + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_RemovedGameObjects: + - {fileID: 6806352148778909055, guid: 90efbfb46b5e7f94aae8607cfb874781, type: 3} + m_AddedGameObjects: [] + m_AddedComponents: + - targetCorrespondingSourceObject: {fileID: 7757076495624774869, guid: 90efbfb46b5e7f94aae8607cfb874781, type: 3} + insertIndex: -1 + addedObject: {fileID: 798982894} + m_SourcePrefab: {fileID: 100100000, guid: 90efbfb46b5e7f94aae8607cfb874781, type: 3} +--- !u!224 &798982892 stripped +RectTransform: + m_CorrespondingSourceObject: {fileID: 7757076495624774868, guid: 90efbfb46b5e7f94aae8607cfb874781, type: 3} + m_PrefabInstance: {fileID: 798982891} + m_PrefabAsset: {fileID: 0} +--- !u!1 &798982893 stripped +GameObject: + m_CorrespondingSourceObject: {fileID: 7757076495624774869, guid: 90efbfb46b5e7f94aae8607cfb874781, type: 3} + m_PrefabInstance: {fileID: 798982891} + m_PrefabAsset: {fileID: 0} +--- !u!114 &798982894 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 798982893} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3} + m_Name: + m_EditorClassIdentifier: + m_IgnoreLayout: 0 + m_MinWidth: -1 + m_MinHeight: -1 + m_PreferredWidth: 726.7668 + m_PreferredHeight: -1 + m_FlexibleWidth: -1 + m_FlexibleHeight: -1 + m_LayoutPriority: 1 +--- !u!114 &798982895 stripped +MonoBehaviour: + m_CorrespondingSourceObject: {fileID: 1811875860408279174, guid: 90efbfb46b5e7f94aae8607cfb874781, type: 3} + m_PrefabInstance: {fileID: 798982891} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 798982893} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d6424005847f4a129610fd8e1965bb41, type: 3} + m_Name: + m_EditorClassIdentifier: CyanStars::CyanStars.Utils.RadioButton.RadioButtonItem --- !u!1 &801457131 GameObject: m_ObjectHideFlags: 0 @@ -23695,6 +24083,10 @@ PrefabInstance: propertyPath: m_Name value: "\u5236\u8C31\u5668\u8BBE\u7F6E\u5F39\u7A97Canvas" objectReference: {fileID: 0} + - target: {fileID: 994941279456320301, guid: 21018f88cf8fd3d468929538bb4e0c4a, type: 3} + propertyPath: m_Enabled + value: 0 + objectReference: {fileID: 0} - target: {fileID: 994941279456320303, guid: 21018f88cf8fd3d468929538bb4e0c4a, type: 3} propertyPath: m_Pivot.x value: 0.5 @@ -23830,9 +24222,10 @@ MonoBehaviour: audioMixer: {fileID: 24100000, guid: c5a148793f573b34885fa0eb984dfa73, type: 2} musicVolumeField: {fileID: 590199864} noteVolumeField: {fileID: 2039431223} - isCompactNoteButtonAreaButton: {fileID: 1413580225} isMultiBpmItemButton: {fileID: 1320703400} isMultiMusicItemButton: {fileID: 434106518} + isCompactNoteButtonAreaButton: {fileID: 1413580225} + isShowingAudioWave: {fileID: 798982895} --- !u!1001 &1286130100 PrefabInstance: m_ObjectHideFlags: 0 @@ -24009,9 +24402,10 @@ RectTransform: m_Children: - {fileID: 1230908547} - {fileID: 1122245071} - - {fileID: 899722264} - {fileID: 1150858797} - {fileID: 784130505} + - {fileID: 899722264} + - {fileID: 542396988} m_Father: {fileID: 1300828993} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 1} @@ -33874,7 +34268,7 @@ MonoBehaviour: m_HandleRect: {fileID: 964606382} m_Direction: 2 m_Value: 1 - m_Size: 0.9704348 + m_Size: 0.8086957 m_NumberOfSteps: 0 m_OnValueChanged: m_PersistentCalls: diff --git a/Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/Management/ChartEditorPlayerPrefsManager.cs b/Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/Management/ChartEditorPlayerPrefsManager.cs index df9e7d39c..38f767ea4 100644 --- a/Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/Management/ChartEditorPlayerPrefsManager.cs +++ b/Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/Management/ChartEditorPlayerPrefsManager.cs @@ -19,9 +19,10 @@ public class ChartEditorPlayerPrefsManager : MonoBehaviour private const string MusicVolumePrefName = "ChartEditor_MusicVolume"; private const string NoteVolumePrefName = "ChartEditor_NoteVolume"; - private const string IsCompactNoteButtonAreaPrefName = "ChartEditor_IsCompactNoteButtonArea"; private const string IsMultiBpmItemPrefName = "ChartEditor_IsMultiBpmItem"; private const string IsMultiMusicItemPrefName = "ChartEditor_IsMultiMusicItem"; + private const string IsCompactNoteButtonAreaPrefName = "ChartEditor_IsCompactNoteButtonArea"; + private const string IsShowingAudioWavePrefName = "ChartEditor_IsShowingAudioWave"; private readonly CompositeDisposable Disposables = new CompositeDisposable(); @@ -89,12 +90,14 @@ private void InitChartEditorSettingPlayerPrefs(ChartEditorModel model) model.MusicVolume.Value = PlayerPrefs.GetInt(MusicVolumePrefName); if (PlayerPrefs.HasKey(BeatAccuracyPrefName)) model.BeatAccuracy.Value = PlayerPrefs.GetInt(BeatAccuracyPrefName); - if (PlayerPrefs.HasKey(IsCompactNoteButtonAreaPrefName)) - model.IsCompactNoteButtonArea.Value = PlayerPrefs.GetInt(IsCompactNoteButtonAreaPrefName) == 1; if (PlayerPrefs.HasKey(IsMultiBpmItemPrefName)) model.IsMultiBpmItemMode.Value = PlayerPrefs.GetInt(IsMultiBpmItemPrefName) == 1; if (PlayerPrefs.HasKey(IsMultiMusicItemPrefName)) model.IsMultiMusicItemMode.Value = PlayerPrefs.GetInt(IsMultiMusicItemPrefName) == 1; + if (PlayerPrefs.HasKey(IsCompactNoteButtonAreaPrefName)) + model.IsCompactNoteButtonArea.Value = PlayerPrefs.GetInt(IsCompactNoteButtonAreaPrefName) == 1; + if (PlayerPrefs.HasKey(IsShowingAudioWavePrefName)) + model.IsShowingAudioWave.Value = PlayerPrefs.GetInt(IsShowingAudioWavePrefName) == 1; model.MusicVolume .Skip(1) @@ -112,27 +115,35 @@ private void InitChartEditorSettingPlayerPrefs(ChartEditorModel model) PlayerPrefs.Save(); }) .AddTo(Disposables); - model.IsCompactNoteButtonArea + model.IsMultiBpmItemMode .Skip(1) .Subscribe(val => { - PlayerPrefs.SetInt(IsCompactNoteButtonAreaPrefName, val ? 1 : 0); + PlayerPrefs.SetInt(IsMultiBpmItemPrefName, val ? 1 : 0); PlayerPrefs.Save(); }) .AddTo(Disposables); - model.IsMultiBpmItemMode + model.IsMultiMusicItemMode .Skip(1) .Subscribe(val => { - PlayerPrefs.SetInt(IsMultiBpmItemPrefName, val ? 1 : 0); + PlayerPrefs.SetInt(IsMultiMusicItemPrefName, val ? 1 : 0); PlayerPrefs.Save(); }) .AddTo(Disposables); - model.IsMultiMusicItemMode + model.IsCompactNoteButtonArea .Skip(1) .Subscribe(val => { - PlayerPrefs.SetInt(IsMultiMusicItemPrefName, val ? 1 : 0); + PlayerPrefs.SetInt(IsCompactNoteButtonAreaPrefName, val ? 1 : 0); + PlayerPrefs.Save(); + }) + .AddTo(Disposables); + model.IsShowingAudioWave + .Skip(1) + .Subscribe(val => + { + PlayerPrefs.SetInt(IsShowingAudioWavePrefName, val ? 1 : 0); PlayerPrefs.Save(); }) .AddTo(Disposables); diff --git a/Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/Model/ChartEditorModel.cs b/Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/Model/ChartEditorModel.cs index 4c9f4d466..bb6f74ac7 100644 --- a/Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/Model/ChartEditorModel.cs +++ b/Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/Model/ChartEditorModel.cs @@ -81,9 +81,10 @@ public class ChartEditorModel // 制谱器设置 public readonly ReactiveProperty MusicVolume = new ReactiveProperty(100); public readonly ReactiveProperty NoteVolume = new ReactiveProperty(100); - public readonly ReactiveProperty IsCompactNoteButtonArea = new ReactiveProperty(false); public readonly ReactiveProperty IsMultiBpmItemMode = new ReactiveProperty(false); public readonly ReactiveProperty IsMultiMusicItemMode = new ReactiveProperty(false); + public readonly ReactiveProperty IsCompactNoteButtonArea = new ReactiveProperty(false); + public readonly ReactiveProperty IsShowingAudioWave = new ReactiveProperty(false); /// diff --git a/Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/View/ChartEditorSettingView.cs b/Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/View/ChartEditorSettingView.cs index d2e05bdc1..492e4ee3d 100644 --- a/Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/View/ChartEditorSettingView.cs +++ b/Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/View/ChartEditorSettingView.cs @@ -21,15 +21,18 @@ public class ChartEditorSettingView : BasePopupView [SerializeField] private TMP_InputField noteVolumeField = null!; - [SerializeField] - private RadioButtonItem isCompactNoteButtonAreaButton = null!; - [SerializeField] private RadioButtonItem isMultiBpmItemButton = null!; [SerializeField] private RadioButtonItem isMultiMusicItemButton = null!; + [SerializeField] + private RadioButtonItem isCompactNoteButtonAreaButton = null!; + + [SerializeField] + private RadioButtonItem isShowingAudioWave = null!; + // AudioMixer 中的变量名 private const string MusicVolumeName = "MusicVolume"; @@ -80,19 +83,23 @@ public override void Bind(ChartEditorSettingViewModel targetViewModel) }) .AddTo(this); - isCompactNoteButtonAreaButton.IsChecked = ViewModel.IsCompactNoteButtonArea.CurrentValue; isMultiBpmItemButton.IsChecked = ViewModel.IsMultiBpmItemMode.CurrentValue; isMultiMusicItemButton.IsChecked = ViewModel.IsMultiMusicItemMode.CurrentValue; - isCompactNoteButtonAreaButton.OnValueChanged.AddListener(ViewModel.SetCompactNoteButtonArea); + isCompactNoteButtonAreaButton.IsChecked = ViewModel.IsCompactNoteButtonArea.CurrentValue; + isShowingAudioWave.IsChecked = ViewModel.IsShowingAudioWave.CurrentValue; + isMultiBpmItemButton.OnValueChanged.AddListener(ViewModel.SetMultiBpmItemMode); isMultiMusicItemButton.OnValueChanged.AddListener(ViewModel.SetMultiMusicItemMode); + isCompactNoteButtonAreaButton.OnValueChanged.AddListener(ViewModel.SetCompactNoteButtonArea); + isShowingAudioWave.OnValueChanged.AddListener(ViewModel.SetShowingAudioWave); } protected override void OnDestroy() { - isCompactNoteButtonAreaButton.OnValueChanged.RemoveListener(ViewModel.SetCompactNoteButtonArea); isMultiBpmItemButton.OnValueChanged.RemoveListener(ViewModel.SetMultiBpmItemMode); isMultiMusicItemButton.OnValueChanged.RemoveListener(ViewModel.SetMultiMusicItemMode); + isCompactNoteButtonAreaButton.OnValueChanged.RemoveListener(ViewModel.SetCompactNoteButtonArea); + isShowingAudioWave.OnValueChanged.RemoveListener(ViewModel.SetShowingAudioWave); base.OnDestroy(); } diff --git a/Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/View/EditAreaView.cs b/Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/View/EditAreaView.cs index ec34db326..1b3c61821 100644 --- a/Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/View/EditAreaView.cs +++ b/Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/View/EditAreaView.cs @@ -80,6 +80,10 @@ public override void Bind(EditAreaViewModel targetViewModel) { base.Bind(targetViewModel); + ViewModel.IsShowingAudioWave + .Subscribe(isShowing => audioWaveRawImage.enabled = isShowing) + .AddTo(this); + ViewModel.IsTimelinePlaying .Subscribe(isPlaying => scrollRect.vertical = !isPlaying) // 正在播放时完全禁止拖动/滚动 scrollRect .AddTo(this); diff --git a/Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/ViewModel/ChartEditorSettingViewModel.cs b/Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/ViewModel/ChartEditorSettingViewModel.cs index 48900e5d8..322f0dca3 100644 --- a/Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/ViewModel/ChartEditorSettingViewModel.cs +++ b/Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/ViewModel/ChartEditorSettingViewModel.cs @@ -9,9 +9,10 @@ public class ChartEditorSettingViewModel : BaseViewModel { public ReadOnlyReactiveProperty MusicVolume => Model.MusicVolume; public ReadOnlyReactiveProperty NoteVolume => Model.NoteVolume; - public ReadOnlyReactiveProperty IsCompactNoteButtonArea => Model.IsCompactNoteButtonArea; public ReadOnlyReactiveProperty IsMultiBpmItemMode => Model.IsMultiBpmItemMode; public ReadOnlyReactiveProperty IsMultiMusicItemMode => Model.IsMultiMusicItemMode; + public ReadOnlyReactiveProperty IsCompactNoteButtonArea => Model.IsCompactNoteButtonArea; + public ReadOnlyReactiveProperty IsShowingAudioWave => Model.IsShowingAudioWave; public ChartEditorSettingViewModel(ChartEditorModel model) : base(model) { @@ -19,8 +20,9 @@ public ChartEditorSettingViewModel(ChartEditorModel model) : base(model) public void SetMusicVolume(int value) => Model.MusicVolume.Value = value; public void SetNoteVolume(int value) => Model.NoteVolume.Value = value; - public void SetCompactNoteButtonArea(bool value) => Model.IsCompactNoteButtonArea.Value = value; public void SetMultiBpmItemMode(bool value) => Model.IsMultiBpmItemMode.Value = value; public void SetMultiMusicItemMode(bool value) => Model.IsMultiMusicItemMode.Value = value; + public void SetCompactNoteButtonArea(bool value) => Model.IsCompactNoteButtonArea.Value = value; + public void SetShowingAudioWave(bool value) => Model.IsShowingAudioWave.Value = value; } } diff --git a/Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/ViewModel/EditAreaViewModel.cs b/Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/ViewModel/EditAreaViewModel.cs index a447c831e..1d1c19080 100644 --- a/Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/ViewModel/EditAreaViewModel.cs +++ b/Cyan-Stars/Assets/Scripts/Gameplay/ChartEditor/ViewModel/EditAreaViewModel.cs @@ -41,6 +41,7 @@ public class EditAreaViewModel : BaseViewModel public ReadOnlyReactiveProperty IsCompactNoteButtonArea => Model.IsCompactNoteButtonArea; public ReadOnlyReactiveProperty?> AudioClipHandler => Model.AudioClipHandler; + public ReadOnlyReactiveProperty IsShowingAudioWave => Model.IsShowingAudioWave; public readonly ReadOnlyReactiveProperty CanPutNote;