migrate changes from audio system

This commit is contained in:
2026-05-14 20:50:43 +08:00
parent 5457cb31a1
commit 32be8f5887
15 changed files with 236 additions and 30 deletions
+1
View File
@@ -842,6 +842,7 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: b1939bb20b5db46c1a5f7354ca0fba87, type: 3} m_Script: {fileID: 11500000, guid: b1939bb20b5db46c1a5f7354ca0fba87, type: 3}
m_Name: m_Name:
m_EditorClassIdentifier: m_EditorClassIdentifier:
hapticSettings: {fileID: 11400000, guid: 84578921d0f5c46718f1826c55c7856a, type: 2}
--- !u!4 &1279932963 --- !u!4 &1279932963
Transform: Transform:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
+1 -1
View File
@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 05675e28900a24c18b737120397c881a guid: 12705f55004c045a2ae82b5407a3cbbb
folderAsset: yes folderAsset: yes
DefaultImporter: DefaultImporter:
externalObjects: {} externalObjects: {}
@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 1a53a6b5d02fb40e4bd2de8e2c6ffa27 guid: 57a231d8d4f5a433caa1316cd9b055ae
MonoImporter: MonoImporter:
externalObjects: {} externalObjects: {}
serializedVersion: 2 serializedVersion: 2
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e681c729026b4fdf83b38f1824f4ab02
timeCreated: 1778761511
@@ -0,0 +1,62 @@
using UnityEditor;
using UnityEngine;
namespace OCES.Haptic.Editor
{
static class HapticSettingsProvider
{
const string k_settingPath = "Assets/Settings/HapticSettings.asset";
[SettingsProvider]
static SettingsProvider Creat()
{
return new SettingsProvider("Project/Haptic Settings", SettingsScope.Project)
{
label = "Haptic Settings",
guiHandler = _ =>
{
HapticSettings settings = AssetDatabase.LoadAssetAtPath<HapticSettings>(k_settingPath);
if (!settings)
{
EditorGUILayout.HelpBox(
$"未找到 AudioExtendSettings.asset\n期望路径: {k_settingPath}",
MessageType.Warning);
if (GUILayout.Button("创建默认配置"))
CreateDefaultAsset();
return;
}
SerializedObject serializedObject = new(settings);
SerializedProperty serializedProperty = serializedObject.GetIterator();
serializedProperty.NextVisible(true);
EditorGUI.BeginChangeCheck();
while (serializedProperty.NextVisible(false))
{
EditorGUILayout.PropertyField(serializedProperty, true);
}
if (EditorGUI.EndChangeCheck())
{
serializedObject.ApplyModifiedProperties();
EditorUtility.SetDirty(settings);
AssetDatabase.SaveAssets();
}
},
keywords = new[] { "Haptic", "Audio", "Vibration", "Vibrator", "Path" },
};
}
[MenuItem("Tools/Haptic/Create Haptic Settings Asset")]
static void CreateDefaultAsset()
{
if (!AssetDatabase.IsValidFolder("Assets/Settings"))
AssetDatabase.CreateFolder("Assets", "Settings");
HapticSettings asset = ScriptableObject.CreateInstance<HapticSettings>();
AssetDatabase.CreateAsset(asset, k_settingPath);
AssetDatabase.SaveAssets();
Debug.Log($"[HapticSettings] 已创建默认配置:{k_settingPath}");
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: fd277ab8102549be90d99031ceb8193a
timeCreated: 1778761525
@@ -31,12 +31,15 @@ namespace OCES.Haptic
{ {
if (bytes == null) if (bytes == null)
return false; return false;
using MemoryStream memoryStream = new(bytes); using (MemoryStream memoryStream = new(bytes))
using (BinaryReader br = new(memoryStream)) {
using (var br = new BinaryReader(memoryStream))
{ {
data.DeSerialize(br); data.DeSerialize(br);
br.Close();
} }
memoryStream.Close(); memoryStream.Close();
}
return true; return true;
} }
} }
@@ -0,0 +1,20 @@
using UnityEngine;
namespace OCES.Haptic
{
/// <summary>
/// 触感反馈系统扩展配置,集中管理所有路径和可调参数。
/// 资产位置:Assets/Resources/AudioExtendSettings.asset
/// 编辑入口:Project Settings > Audio Extend
/// </summary>
public class HapticSettings : ScriptableObject
{
[Tooltip("Resources 子目录:触感配置文件")]
public string hapticConfigPath = "HapticData/";
[Tooltip("Resources 子目录:触感资源文件(.haptic")]
public string hapticResourcePath = "Haptics/";
public static HapticSettings Instance { get; internal set; }
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: cf447f41468045bf9789408bd135ebeb
timeCreated: 1778750228
@@ -10,22 +10,19 @@ namespace OCES.Haptic
{ {
public static HapticSystem Instance {get; private set;} public static HapticSystem Instance {get; private set;}
[NonSerialized] [NonSerialized]
public bool IsHapticSupported = DeviceCapabilities.isVersionSupported; public bool IsHapticSupported;
[NonSerialized] [NonSerialized]
public bool IsMeetsAdvanceRequirements = DeviceCapabilities.meetsAdvancedRequirements; public bool IsMeetsAdvanceRequirements;
[SerializeField]
HapticSettings hapticSettings;
internal ResourceLoader ResourceLoader;
HapticObjectConfig m_hapticObjects; HapticObjectConfig m_hapticObjects;
const string k_hapticConfigPath = "HapticData/";
const string k_hapticResourcesPath = "Haptics/";
public void Play(uint hapticId, bool isDirectCall = true) public void Play(uint hapticId, bool isDirectCall = true)
{ {
if (this.m_hapticObjects is null)
{
Debug.LogError($"[Haptic System] Config not loaded!");
return;
}
HapticObject hapticObject = this.m_hapticObjects.QueryById(hapticId); HapticObject hapticObject = this.m_hapticObjects.QueryById(hapticId);
if (hapticObject != null) if (hapticObject != null)
{ {
@@ -58,7 +55,7 @@ namespace OCES.Haptic
} }
break; break;
case HapticType.Emphasis: case HapticType.Emphasis:
if (hapticObject.Amplitude <= 0f || hapticObject.Frequency <= 0f) if (hapticObject.Amplitude < 0f || hapticObject.Frequency < 0f)
{ {
Debug.LogWarning($"[Haptic System] Haptic {hapticObject.Id} have no amplitude or frequency." + Debug.LogWarning($"[Haptic System] Haptic {hapticObject.Id} have no amplitude or frequency." +
"Please check the datatable."); "Please check the datatable.");
@@ -67,7 +64,7 @@ namespace OCES.Haptic
HapticPatterns.PlayEmphasis(hapticObject.Amplitude, hapticObject.Frequency); HapticPatterns.PlayEmphasis(hapticObject.Amplitude, hapticObject.Frequency);
break; break;
case HapticType.Constant: case HapticType.Constant:
if (hapticObject.Amplitude <= 0f || hapticObject.Frequency <= 0f || hapticObject.Duration <= 0f) if (hapticObject.Amplitude < 0f || hapticObject.Frequency < 0f || hapticObject.Duration < 0f)
{ {
Debug.LogWarning($"[Haptic System] Haptic {hapticObject.Id} have no amplitude, frequency or duration." + Debug.LogWarning($"[Haptic System] Haptic {hapticObject.Id} have no amplitude, frequency or duration." +
"Please check the datatable."); "Please check the datatable.");
@@ -79,10 +76,10 @@ namespace OCES.Haptic
case HapticType.Advance: case HapticType.Advance:
if (Enum.TryParse(hapticObject.FallbackPreset, out HapticPatterns.PresetType fallbackPreset)) if (Enum.TryParse(hapticObject.FallbackPreset, out HapticPatterns.PresetType fallbackPreset))
{ {
HapticController.fallbackPreset = fallbackPreset;
HapticClip hapticClip = GetHapticClip(hapticObject); HapticClip hapticClip = GetHapticClip(hapticObject);
if (hapticClip) if (hapticClip)
{ {
HapticController.fallbackPreset = fallbackPreset;
HapticController.Stop(); HapticController.Stop();
HapticController.Play(hapticClip); HapticController.Play(hapticClip);
} }
@@ -109,6 +106,8 @@ namespace OCES.Haptic
void Awake() void Awake()
{ {
HapticSettings.Instance = this.hapticSettings;
if (Instance && Instance != this) if (Instance && Instance != this)
{ {
Destroy(gameObject); Destroy(gameObject);
@@ -117,31 +116,28 @@ namespace OCES.Haptic
Instance = this; Instance = this;
DontDestroyOnLoad(gameObject); DontDestroyOnLoad(gameObject);
this.m_hapticObjects = HapticConfigLoader.Load<HapticObjectConfig>(k_hapticConfigPath + "HapticObject"); this.ResourceLoader = gameObject.AddComponent<ResourceLoader>();
}
void OnDestroy() this.m_hapticObjects = HapticConfigLoader.Load<HapticObjectConfig>(HapticSettings.Instance.hapticConfigPath + "HapticObject");
{ this.IsHapticSupported = DeviceCapabilities.isVersionSupported;
if (Instance == this) Instance = null; this.IsMeetsAdvanceRequirements = DeviceCapabilities.meetsAdvancedRequirements;
} }
static HapticClip GetHapticClip(HapticObject hapticObject) static HapticClip GetHapticClip(HapticObject hapticObject)
{ {
return Resources.Load<HapticClip>(k_hapticResourcesPath + hapticObject.Payload); return Instance.ResourceLoader.LoadSync<HapticClip>(HapticSettings.Instance.hapticResourcePath + hapticObject.Payload);
//TODO: cache HapticClips into a Dictionary<string, HapticClip> or using AsyncLoad
} }
static class HapticConfigLoader static class HapticConfigLoader
{ {
internal static T Load<T>(string tableName) where T : IBinarySerializable, new() internal static T Load<T>(string tableName) where T : IBinarySerializable, new()
{ {
TextAsset bytes = Resources.Load<TextAsset>(tableName); TextAsset bytes = Instance.ResourceLoader.LoadSync<TextAsset>(tableName);
if (!bytes) if (!bytes)
{ {
Debug.LogError($"未找到表 {tableName}"); Debug.LogError($"未找到表 {tableName}");
return default; return default;
} }
IBinarySerializable data = new T(); IBinarySerializable data = new T();
bool readOk = FileManager.ReadBinaryDataFromBytes(bytes.bytes, ref data); bool readOk = FileManager.ReadBinaryDataFromBytes(bytes.bytes, ref data);
if (readOk) if (readOk)
+80
View File
@@ -0,0 +1,80 @@
using System;
using System.Collections;
using System.Collections.Generic;
using JetBrains.Annotations;
using UnityEngine;
using Object = UnityEngine.Object;
namespace OCES
{
public class ResourceLoader : MonoBehaviour
{
readonly Dictionary<string, Object> m_cachedObjects = new();
readonly Dictionary<string, List<Action<Object>>> m_pendingCallbacks = new();
[CanBeNull]
internal T LoadSync<T>(string path) where T : Object
{
if (this.m_cachedObjects.TryGetValue(path, out Object cachedObject))
{
return cachedObject as T;
}
T newObject = Resources.Load<T>(path);
this.m_cachedObjects.Add(path, newObject);
return newObject;
}
internal void LoadAsync<T>(string path, Action<T> onComplete) where T : Object
{
if (this.m_cachedObjects.TryGetValue(path, out Object cachedObject))
{
onComplete?.Invoke(cachedObject as T);
return;
}
if (this.m_pendingCallbacks.TryGetValue(path, out List<Action<Object>> callbacks))
{
callbacks.Add(obj => onComplete?.Invoke(obj as T));
return;
}
this.m_pendingCallbacks[path] = new List<Action<Object>> { obj => onComplete?.Invoke(obj as T) };
ResourceRequest newRequest = Resources.LoadAsync<T>(path);
StartCoroutine(HandleAsyncLoadCompletion(path, newRequest));
}
IEnumerator HandleAsyncLoadCompletion(string path, ResourceRequest request)
{
yield return request;
if (request.asset)
{
this.m_cachedObjects[path] = request.asset;
}
if (this.m_pendingCallbacks.Remove(path, out List<Action<Object>> callbacks))
{
foreach (Action<Object> callback in callbacks)
{
callback?.Invoke(request.asset);
}
}
}
internal void PreloadAsync<T>(string[] paths)
{
}
internal void Release<T>(string path)
{
}
internal void ReleaseUnused()
{
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 845d98765be843f9a943f41f45cdf013
timeCreated: 1778573329
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 508434b1d13bc42879e0033e2fbde015
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+16
View File
@@ -0,0 +1,16 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: cf447f41468045bf9789408bd135ebeb, type: 3}
m_Name: HapticSettings
m_EditorClassIdentifier:
hapticConfigPath: HapticData/
hapticResourcePath: Haptics/
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 84578921d0f5c46718f1826c55c7856a
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant: