diff --git a/Assets/Scripts/OCES/AssetsManagement/AssetBundleAssetProvider.cs b/Assets/Scripts/OCES/AssetsManagement/AssetBundleAssetProvider.cs index 6b8618b..68f2bcd 100644 --- a/Assets/Scripts/OCES/AssetsManagement/AssetBundleAssetProvider.cs +++ b/Assets/Scripts/OCES/AssetsManagement/AssetBundleAssetProvider.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using UnityEngine; namespace OCES @@ -20,9 +21,17 @@ namespace OCES { return this.m_assetBundle.LoadAsset(path); } - public ResourceRequest LoadAsync(string path) where T : UnityEngine.Object + + public void LoadAsync(string path, MonoBehaviour coroutineHost, Action onComplete) where T : UnityEngine.Object { - return this.m_assetBundle.LoadAssetAsync(path); + coroutineHost.StartCoroutine(LoadAsyncCoroutine(path, onComplete)); + } + + IEnumerator LoadAsyncCoroutine(string path, Action onComplete) where T : UnityEngine.Object + { + AssetBundleRequest request = this.m_assetBundle.LoadAssetAsync(path); + yield return request; + onComplete?.Invoke(request.asset as T); } } } diff --git a/Assets/Scripts/OCES/AssetsManagement/IAssetProvider.cs b/Assets/Scripts/OCES/AssetsManagement/IAssetProvider.cs index 57dba63..0f6321a 100644 --- a/Assets/Scripts/OCES/AssetsManagement/IAssetProvider.cs +++ b/Assets/Scripts/OCES/AssetsManagement/IAssetProvider.cs @@ -6,6 +6,6 @@ namespace OCES public interface IAssetProvider { T Load(string path) where T : UnityEngine.Object; - ResourceRequest LoadAsync(string path) where T : UnityEngine.Object; + void LoadAsync(string path, MonoBehaviour coroutineHost, Action onComplete) where T : UnityEngine.Object; } } diff --git a/Assets/Scripts/OCES/AssetsManagement/ResourceLoader.cs b/Assets/Scripts/OCES/AssetsManagement/ResourceLoader.cs index 8623286..77d00e2 100644 --- a/Assets/Scripts/OCES/AssetsManagement/ResourceLoader.cs +++ b/Assets/Scripts/OCES/AssetsManagement/ResourceLoader.cs @@ -2,7 +2,6 @@ using System; using System.Collections; using System.Collections.Generic; using JetBrains.Annotations; -using OCES.Audio; using UnityEngine; using Object = UnityEngine.Object; @@ -57,34 +56,40 @@ namespace OCES } this.m_pendingCallbacks[path] = new List> { obj => onComplete?.Invoke(obj as T) }; - ResourceRequest newRequest; + if (this.m_assetProvider is not null) { - newRequest = this.m_assetProvider.LoadAsync(path); + this.m_assetProvider.LoadAsync(path, this, asset => + { + OnAsyncAssetLoaded(path, asset as Object); + }); } else { - newRequest = Resources.LoadAsync(path); Debug.LogWarning($"[ResourceLoader] No IAssetProvider set, falling back to Resources.Load for '{path}'"); + StartCoroutine(FallbackLoadAsyncCoroutine(path)); } - - StartCoroutine(HandleAsyncLoadCompletion(path, newRequest)); } - IEnumerator HandleAsyncLoadCompletion(string path, ResourceRequest request) + IEnumerator FallbackLoadAsyncCoroutine(string path) where T : Object { + ResourceRequest request = Resources.LoadAsync(path); yield return request; + OnAsyncAssetLoaded(path, request.asset); + } - if (request.asset) + void OnAsyncAssetLoaded(string path, Object asset) + { + if (asset) { - this.m_cachedObjects[path] = request.asset; + this.m_cachedObjects[path] = asset; } if (this.m_pendingCallbacks.Remove(path, out List> callbacks)) { foreach (Action callback in callbacks) { - callback?.Invoke(request.asset); + callback?.Invoke(asset); } } } @@ -104,4 +109,4 @@ namespace OCES } } -} \ No newline at end of file +} diff --git a/Assets/Scripts/OCES/AssetsManagement/ResourcesAssetProvider.cs b/Assets/Scripts/OCES/AssetsManagement/ResourcesAssetProvider.cs index af7f1b0..39fb562 100644 --- a/Assets/Scripts/OCES/AssetsManagement/ResourcesAssetProvider.cs +++ b/Assets/Scripts/OCES/AssetsManagement/ResourcesAssetProvider.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using UnityEngine; namespace OCES @@ -9,9 +10,17 @@ namespace OCES { return Resources.Load(path); } - public ResourceRequest LoadAsync(string path) where T : UnityEngine.Object + + public void LoadAsync(string path, MonoBehaviour coroutineHost, Action onComplete) where T : UnityEngine.Object { - return Resources.LoadAsync(path); + coroutineHost.StartCoroutine(LoadAsyncCoroutine(path, onComplete)); + } + + IEnumerator LoadAsyncCoroutine(string path, Action onComplete) where T : UnityEngine.Object + { + ResourceRequest request = Resources.LoadAsync(path); + yield return request; + onComplete?.Invoke(request.asset as T); } } } diff --git a/Assets/Scripts/OCES/AssetsManagement/StreamingAssetProvider.cs b/Assets/Scripts/OCES/AssetsManagement/StreamingAssetProvider.cs new file mode 100644 index 0000000..0260454 --- /dev/null +++ b/Assets/Scripts/OCES/AssetsManagement/StreamingAssetProvider.cs @@ -0,0 +1,193 @@ +using System; +using UnityEngine; +using Object = UnityEngine.Object; + +#if UNITY_STANDALONE || UNITY_EDITOR +using System.Collections; +using System.IO; +using UnityEngine.Networking; +#endif + +namespace OCES +{ +#if UNITY_STANDALONE || UNITY_EDITOR + /// + /// 从 StreamingAssets 加载资源的 IAssetProvider 实现。 + /// 仅支持桌面平台(Windows / macOS / Linux)及 Editor。 + /// 无内置缓存——每次调用都从磁盘读取,缓存由上层 ResourceLoader 负责。 + /// 目前仅支持 AudioClip(.wav 格式)。 + /// + public class StreamingAssetProvider : IAssetProvider + { + public T Load(string path) where T : Object + { + if (typeof(T) != typeof(AudioClip)) + { + Debug.LogError($"[StreamingAssetProvider] Load<{typeof(T).Name}> 不支持,仅支持 AudioClip"); + return null; + } + + string fullPath = Path.Combine(Application.streamingAssetsPath, path); + if (!File.Exists(fullPath)) + { + Debug.LogError($"[StreamingAssetProvider] 文件 {fullPath} 不存在。"); + return null; + } + + byte[] wavBytes = File.ReadAllBytes(fullPath); + AudioClip clip = ParseWav(wavBytes, Path.GetFileNameWithoutExtension(path)); + return clip as T; + } + + public void LoadAsync(string path, MonoBehaviour coroutineHost, Action onComplete) where T : Object + { + coroutineHost.StartCoroutine(LoadAsyncCoroutine(path, onComplete)); + } + + IEnumerator LoadAsyncCoroutine(string path, Action onComplete) where T : Object + { + if (typeof(T) != typeof(AudioClip)) + { + Debug.LogError($"[StreamingAssetProvider] LoadAsync<{typeof(T).Name}> 不支持,仅支持 AudioClip"); + onComplete?.Invoke(null); + yield break; + } + + string fullPath = Path.Combine(Application.streamingAssetsPath, path); + string url = "file://" + fullPath; + + using UnityWebRequest request = UnityWebRequestMultimedia.GetAudioClip(url, AudioType.WAV); + yield return request.SendWebRequest(); + + if (request.result == UnityWebRequest.Result.Success) + { + AudioClip clip = DownloadHandlerAudioClip.GetContent(request); + onComplete?.Invoke(clip as T); + } + else + { + Debug.LogError($"[StreamingAssetProvider] 加载失败: {url}, 错误: {request.error}"); + onComplete?.Invoke(null); + } + } + + // ───────────────────────────────────────────── + // WAV 解析(PCM 16-bit / 8-bit,单声道 / 立体声) + // ───────────────────────────────────────────── + + static AudioClip ParseWav(byte[] wavBytes, string clipName) + { + int position = 0; + + // RIFF header + if (wavBytes.Length < 44 || + wavBytes[0] != 'R' || wavBytes[1] != 'I' || wavBytes[2] != 'F' || wavBytes[3] != 'F') + { + Debug.LogError("[StreamingAssetProvider] 无效的 WAV 文件:缺少 RIFF 头"); + return null; + } + position += 4; // "RIFF" + position += 4; // file size (unused) + if (wavBytes[8] != 'W' || wavBytes[9] != 'A' || wavBytes[10] != 'V' || wavBytes[11] != 'E') + { + Debug.LogError("[StreamingAssetProvider] 无效的 WAV 文件:缺少 WAVE 标识"); + return null; + } + position += 4; // "WAVE" + + int channels = 1; + int sampleRate = 44100; + int bitsPerSample = 16; + byte[] pcmData = null; + + // 遍历 chunk + while (position < wavBytes.Length - 8) + { + string chunkId = System.Text.Encoding.ASCII.GetString(wavBytes, position, 4); + position += 4; + int chunkSize = BitConverter.ToInt32(wavBytes, position); + position += 4; + + if (chunkId == "fmt ") + { + int audioFormat = BitConverter.ToInt16(wavBytes, position); + if (audioFormat != 1) // PCM = 1 + { + Debug.LogError($"[StreamingAssetProvider] 不支持的音频格式: {audioFormat},仅支持 PCM"); + return null; + } + channels = BitConverter.ToInt16(wavBytes, position + 2); + sampleRate = BitConverter.ToInt32(wavBytes, position + 4); + bitsPerSample = BitConverter.ToInt16(wavBytes, position + 14); + + if (channels < 1 || channels > 2) + { + Debug.LogError($"[StreamingAssetProvider] 不支持的声道数: {channels}"); + return null; + } + } + else if (chunkId == "data") + { + pcmData = new byte[chunkSize]; + Array.Copy(wavBytes, position, pcmData, 0, chunkSize); + } + + position += chunkSize; + } + + if (pcmData == null || pcmData.Length == 0) + { + Debug.LogError("[StreamingAssetProvider] WAV 文件中未找到 data chunk"); + return null; + } + + // PCM → float 样本 + int sampleCount = pcmData.Length / (bitsPerSample / 8); + float[] samples = new float[sampleCount]; + + if (bitsPerSample == 16) + { + for (int i = 0; i < sampleCount; i++) + { + short sample = BitConverter.ToInt16(pcmData, i * 2); + samples[i] = sample / 32768f; + } + } + else if (bitsPerSample == 8) + { + for (int i = 0; i < sampleCount; i++) + { + samples[i] = (pcmData[i] - 128f) / 128f; + } + } + else + { + Debug.LogError($"[StreamingAssetProvider] 不支持的位深度: {bitsPerSample}"); + return null; + } + + AudioClip clip = AudioClip.Create(clipName, sampleCount / channels, channels, sampleRate, false); + clip.SetData(samples, 0); + return clip; + } + } +#else + /// + /// 非桌面平台的桩实现——StreamingAssetProvider 仅支持桌面平台。 + /// + public class StreamingAssetProvider : IAssetProvider + { + public T Load(string path) where T : Object + { + Debug.LogError("[StreamingAssetProvider] 仅支持桌面平台 (Windows/macOS/Linux)"); + return null; + } + + public void LoadAsync(string path, MonoBehaviour coroutineHost, Action onComplete) where T : Object + { + Debug.LogError("[StreamingAssetProvider] 仅支持桌面平台 (Windows/macOS/Linux)"); + onComplete?.Invoke(null); + } + } +#endif +} diff --git a/Assets/Scripts/OCES/AssetsManagement/StreamingAssetProvider.cs.meta b/Assets/Scripts/OCES/AssetsManagement/StreamingAssetProvider.cs.meta new file mode 100644 index 0000000..f77a6e6 --- /dev/null +++ b/Assets/Scripts/OCES/AssetsManagement/StreamingAssetProvider.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 7b8b3ffa721341f8ac66707c91bccb28 +timeCreated: 1779096845 \ No newline at end of file diff --git a/Assets/Scripts/OCES/Audio/HandWritten/LongAudio/LongAudioContainerPlayer.cs b/Assets/Scripts/OCES/Audio/HandWritten/LongAudio/LongAudioContainerPlayer.cs index e9c34fc..801026f 100644 --- a/Assets/Scripts/OCES/Audio/HandWritten/LongAudio/LongAudioContainerPlayer.cs +++ b/Assets/Scripts/OCES/Audio/HandWritten/LongAudio/LongAudioContainerPlayer.cs @@ -340,12 +340,8 @@ namespace OCES.Audio return null; } - // AudioClip clip = Resources.Load($"Audios/{segment.Name}"); - AudioClip clip = null; - AudioSystem.Instance.ResourceLoader.LoadAsync($"{AudioExtendSettings.Instance.audioResourcePath}/{segment.Name}", loadedClip => - { - clip = loadedClip; - }); + AudioClip clip = AudioSystem.Instance.ResourceLoader.LoadSync( + $"{AudioExtendSettings.Instance.audioResourcePath}/{segment.Name}"); if (!clip) { Debug.LogError($"[LongAudioContainerPlayer] 音频文件未找到: {segment.Name}");