Feat : Wwise Integration

This commit is contained in:
Alex
2026-03-23 14:28:21 +01:00
parent 627d787695
commit 004bc328ed
1149 changed files with 226567 additions and 19 deletions
+7
View File
@@ -0,0 +1,7 @@
# Changelog
## Version 2025.1.6
- Initial release
- Wwise Motion haptics support for Meta Quest devices via OpenXR
- Support for Android (Quest standalone) and Windows 64-bit (Quest Link)
- XR_FB_haptic_pcm extension integration
+7
View File
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 70fdf7a14d7b70e4fa0cd9837d530e46
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+18
View File
@@ -0,0 +1,18 @@
# License
/*******************************************************************************
The content of this file includes portions of the proprietary AUDIOKINETIC Wwise
Technology released in source code form as part of the game integration package.
The content of this file may not be used without valid licenses to the AUDIOKINETIC
Wwise Technology.
Note that the use of the game engine is subject to the Unity Terms of Service
at https://unity3d.com/legal/terms-of-service.
License Usage
Licensees holding valid licenses to the AUDIOKINETIC Wwise Technology may use
this file in accordance with the end user license agreement provided with the
software or, alternatively, in accordance with the terms contained
in a written agreement between you and Audiokinetic Inc.
Copyright (c) 2026 Audiokinetic Inc.
*******************************************************************************/
+7
View File
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 78170b08336ddb14aa3f6aabc1719994
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+32
View File
@@ -0,0 +1,32 @@
# Wwise Unity OpenXR Motion
This package adds support for Wwise Motion haptics on Meta Quest devices via OpenXR.
## Requirements
* Unity 2021.3 or later
* Unity XR Plugin Management with OpenXR plugin >= 1.16.0
* Wwise Unity Integration
* Wwise Motion plugin
## Usage
See [Unity Wwise Motion OpenXR Documentation]() for more information about how to use the package.
## Legal
/*******************************************************************************
The content of this file includes portions of the proprietary AUDIOKINETIC Wwise
Technology released in source code form as part of the game integration package.
The content of this file may not be used without valid licenses to the AUDIOKINETIC
Wwise Technology.
Note that the use of the game engine is subject to the Unity Terms of Service
at https://unity3d.com/legal/terms-of-service.
License Usage
Licensees holding valid licenses to the AUDIOKINETIC Wwise Technology may use
this file in accordance with the end user license agreement provided with the
software or, alternatively, in accordance with the terms contained
in a written agreement between you and Audiokinetic Inc.
Copyright (c) 2026 Audiokinetic Inc.
*******************************************************************************/
+7
View File
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: f17bbf5953ca4aa48bd6e9ceb022017e
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: af770b0b88b333846b96dbd1d7b9c6d1
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,33 @@
{
"name": "AK.Wwise.Unity.OpenXR",
"rootNamespace": "",
"references": [
"AK.Wwise.Unity.API",
"AK.Wwise.Unity.MonoBehaviour",
"Unity.XR.OpenXR",
"Unity.InputSystem",
"AK.Wwise.Unity.Logging"
],
"includePlatforms": [
"Android",
"Editor",
"WindowsStandalone32",
"WindowsStandalone64"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [
"!UNITY_SERVER"
],
"versionDefines": [
{
"name": "com.unity.xr.openxr",
"expression": "[1.16.1]",
"define": "AK_ENABLE_OPENXR"
}
],
"noEngineReferences": false
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 1ecfa77477d417946b10ed76bcaf6775
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,107 @@
/*******************************************************************************
The content of this file includes portions of the proprietary AUDIOKINETIC Wwise
Technology released in source code form as part of the game integration package.
The content of this file may not be used without valid licenses to the AUDIOKINETIC
Wwise Technology.
Note that the use of the game engine is subject to the Unity Terms of Service
at https://unity3d.com/legal/terms-of-service.
License Usage
Licensees holding valid licenses to the AUDIOKINETIC Wwise Technology may use
this file in accordance with the end user license agreement provided with the
software or, alternatively, in accordance with the terms contained
in a written agreement between you and Audiokinetic Inc.
Copyright (c) 2026 Audiokinetic Inc.
*******************************************************************************/
#if AK_ENABLE_OPENXR
using System;
using System.Runtime.InteropServices;
using UnityEngine;
using UnityEngine.XR.OpenXR.NativeTypes;
using AK.Wwise.Unity.Logging;
namespace AK.Wwise.Unity.OpenXR
{
/// <summary>
/// P/Invoke bridge to native AkMotion OpenXR functions.
/// Provides C# bindings to pass OpenXR function pointers and handles to the Wwise Motion plugin.
/// </summary>
public static class AkWwiseOpenXRMotionBridge
{
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate XrResult XrGetInstanceProcAddrDelegate(ulong instance, IntPtr name, ref IntPtr function);
[DllImport("AkMotion", CallingConvention = CallingConvention.Cdecl)]
private static extern void AkMotionInitializeOpenXRFunctions(IntPtr xrResultToString, IntPtr xrEnumerateInstanceExtensionProperties, IntPtr xrStringToPath, IntPtr xrGetInstanceProcAddr, IntPtr xrApplyHapticFeedback);
[DllImport("AkMotion", CallingConvention = CallingConvention.Cdecl)]
private static extern void AkMotionSetOpenXRHapticAction(ulong xrInstance, ulong xrSession, ulong xrAction);
private static IntPtr GetOpenXRFunction(IntPtr xrGetInstanceProcAddr, ulong instance, string functionName)
{
var getProcAddr = Marshal.GetDelegateForFunctionPointer<XrGetInstanceProcAddrDelegate>(xrGetInstanceProcAddr);
IntPtr functionPtr = IntPtr.Zero;
IntPtr namePtr = Marshal.StringToHGlobalAnsi(functionName);
try
{
XrResult result = getProcAddr(instance, namePtr, ref functionPtr);
if (result != XrResult.Success)
{
WwiseLogger.Warning($"[AkWwiseOpenXRMotionBridge] Failed to get function pointer for {functionName}: {result}");
return IntPtr.Zero;
}
}
finally
{
Marshal.FreeHGlobal(namePtr);
}
return functionPtr;
}
// Initializes the Wwise Motion plugin with OpenXR function pointers and handles.
// This must be called after OpenXR initialization and after the Wwise sound engine has loaded Init.bnk.
public static bool Initialize(IntPtr xrGetInstanceProcAddr, ulong xrInstance, ulong xrSession, ulong xrAction)
{
if (xrGetInstanceProcAddr == IntPtr.Zero || xrInstance == 0 || xrSession == 0 || xrAction == 0)
{
WwiseLogger.Error("[AkWwiseOpenXRMotionBridge] Invalid parameters: all handles must be non-zero");
return false;
}
try
{
IntPtr xrResultToString = GetOpenXRFunction(xrGetInstanceProcAddr, xrInstance, "xrResultToString");
IntPtr xrEnumerateInstanceExtensionProperties = GetOpenXRFunction(xrGetInstanceProcAddr, xrInstance, "xrEnumerateInstanceExtensionProperties");
IntPtr xrStringToPath = GetOpenXRFunction(xrGetInstanceProcAddr, xrInstance, "xrStringToPath");
IntPtr xrApplyHapticFeedback = GetOpenXRFunction(xrGetInstanceProcAddr, xrInstance, "xrApplyHapticFeedback");
if (xrResultToString == IntPtr.Zero || xrEnumerateInstanceExtensionProperties == IntPtr.Zero || xrStringToPath == IntPtr.Zero || xrApplyHapticFeedback == IntPtr.Zero)
{
WwiseLogger.Error("[AkWwiseOpenXRMotionBridge] Failed to retrieve required OpenXR function pointers");
return false;
}
AkMotionInitializeOpenXRFunctions(xrResultToString, xrEnumerateInstanceExtensionProperties, xrStringToPath, xrGetInstanceProcAddr, xrApplyHapticFeedback);
WwiseLogger.Verbose("[AkWwiseOpenXRMotionBridge] Passed OpenXR function pointers to AkMotion");
AkMotionSetOpenXRHapticAction(xrInstance, xrSession, xrAction);
WwiseLogger.Verbose("[AkWwiseOpenXRMotionBridge] Passed OpenXR handles to AkMotion");
return true;
}
catch (Exception ex)
{
WwiseLogger.Error($"[AkWwiseOpenXRMotionBridge] Exception during initialization: {ex.Message}");
return false;
}
}
}
}
#endif // AK_ENABLE_OPENXR
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: be344861e7aa3364f965a0f97037c58a
@@ -0,0 +1,210 @@
/*******************************************************************************
The content of this file includes portions of the proprietary AUDIOKINETIC Wwise
Technology released in source code form as part of the game integration package.
The content of this file may not be used without valid licenses to the AUDIOKINETIC
Wwise Technology.
Note that the use of the game engine is subject to the Unity Terms of Service
at https://unity3d.com/legal/terms-of-service.
License Usage
Licensees holding valid licenses to the AUDIOKINETIC Wwise Technology may use
this file in accordance with the end user license agreement provided with the
software or, alternatively, in accordance with the terms contained
in a written agreement between you and Audiokinetic Inc.
Copyright (c) 2026 Audiokinetic Inc.
*******************************************************************************/
#if AK_ENABLE_OPENXR
using UnityEngine;
using UnityEngine.XR.OpenXR;
using UnityEngine.XR.OpenXR.Features;
using AK.Wwise.Unity.Logging;
using UnityEngine.InputSystem;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.XR.OpenXR.Features;
#endif
namespace AK.Wwise.Unity.OpenXR
{
/// <summary>
/// Enables Wwise Motion haptics support for Meta Quest devices via OpenXR.
/// This feature bridges Unity's OpenXR plugin with the Wwise AkMotion plugin.
/// </summary>
#if UNITY_EDITOR
[OpenXRFeature(
UiName = "Wwise OpenXR Motion",
BuildTargetGroups = new[] { BuildTargetGroup.Android, BuildTargetGroup.Standalone },
Company = "Audiokinetic",
Desc = "Wwise Motion haptics support via OpenXR for Meta Quest devices.",
DocumentationLink = "https://www.audiokinetic.com/",
OpenxrExtensionStrings = "XR_FB_haptic_pcm",
Version = "1.0.0",
FeatureId = featureId
)]
#endif
public class AkWwiseOpenXRMotionFeature : OpenXRFeature
{
public const string featureId = "com.audiokinetic.wwise.openxr.motion";
// Event that fires when OpenXR Motion is initialized and ready.
public static event System.Action OnOpenXRMotionInitialized;
private static bool s_isInitialized = false;
public static bool IsInitialized => s_isInitialized;
// Unity creates one feature instance per BuildTargetGroup (Android, Standalone),
// but only the active build target's instance receives OpenXR callbacks (OnInstanceCreate, OnSessionCreate).
// We set s_instance in OnInstanceCreate() to identify which instance has valid OpenXR handles.
private static AkWwiseOpenXRMotionFeature s_instance = null;
// OpenXR handles
private ulong m_xrInstance = 0;
private ulong m_xrSession = 0;
private ulong m_xrHapticAction = 0;
private System.IntPtr m_xrGetInstanceProcAddr = System.IntPtr.Zero;
public static AkWwiseOpenXRMotionFeature Instance
{
get
{
if (s_instance == null)
{
s_instance = OpenXRSettings.Instance?.GetFeature<AkWwiseOpenXRMotionFeature>();
}
return s_instance;
}
}
protected override void OnDisable()
{
if (s_instance == this) // Only clear s_instance if we're the active instance
{
s_instance = null;
}
base.OnDisable();
}
protected override System.IntPtr HookGetInstanceProcAddr(System.IntPtr func)
{
m_xrGetInstanceProcAddr = func;
return base.HookGetInstanceProcAddr(func);
}
// Called when an OpenXR instance is created. Captures the XrInstance handle.
// Only called for the active build target's feature instance.
protected override bool OnInstanceCreate(ulong xrInstance)
{
m_xrInstance = xrInstance;
s_instance = this; // This instance is the active one - set as singleton
return base.OnInstanceCreate(xrInstance);
}
protected override void OnInstanceDestroy(ulong xrInstance)
{
m_xrInstance = 0;
base.OnInstanceDestroy(xrInstance);
}
// Called when an OpenXR session is created. Captures the XrSession handle.
protected override void OnSessionCreate(ulong xrSession)
{
m_xrSession = xrSession;
InputSystem.onAfterUpdate -= OnInputSystemAfterUpdate;
InputSystem.onAfterUpdate += OnInputSystemAfterUpdate;
base.OnSessionCreate(xrSession);
}
protected override void OnSessionDestroy(ulong xrSession)
{
InputSystem.onAfterUpdate -= OnInputSystemAfterUpdate;
m_xrSession = 0;
m_xrHapticAction = 0;
base.OnSessionDestroy(xrSession);
}
// Will be called until the Input System's controller is ready. Then we unregister the callback.
static void OnInputSystemAfterUpdate()
{
if (AkUnitySoundEngine.IsInitialized())
{
if (Instance && Instance.SetWwiseMotionOpenXREnvironment())
{
InputSystem.onAfterUpdate -= OnInputSystemAfterUpdate;
}
}
}
// This method fails if the Unity Input System is not ready and can be called again on the next frame.
public bool SetWwiseMotionOpenXREnvironment()
{
if (m_xrInstance == 0 || m_xrSession == 0)
{
WwiseLogger.Error("[AkWwiseOpenXRMotionFeature] XrInstance or XrSession are invalid.");
return false;
}
// Getting the haptic action from any of the two controller is valid since it resolves to the same XrAction.
if (!TryGetHapticActionHandle("<XRController>{LeftHand}/haptic") && !TryGetHapticActionHandle("<XRController>{RightHand}/haptic"))
{
return false;
}
if (!AkWwiseOpenXRMotionBridge.Initialize(m_xrGetInstanceProcAddr, m_xrInstance, m_xrSession, m_xrHapticAction))
{
WwiseLogger.Error("[AkWwiseOpenXRMotionFeature] Failed to initialize AkMotion bridge");
return false;
}
WwiseLogger.Log("[AkWwiseOpenXRMotionFeature] Wwise Motion OpenXR integration initialized successfully!");
s_isInitialized = true;
OnOpenXRMotionInitialized?.Invoke();
return true;
}
// Retrieves the XrAction handle by piggybacking on the controller profile's existing haptic action.
// It will return false silently while waiting for the Input System to bind controls.
private bool TryGetHapticActionHandle(string bindingPath)
{
bool gotAction = false;
// This relies on a controller profile (e.g., Meta Quest Touch Pro Controller Profile) being enabled.
// Note: Any of the hand bindings (LeftHand or RightHand) resolves to the controller profile's unified
// haptic output, which automatically applies to both controllers. This is handled by the OpenXR runtime.
var hapticAction = new UnityEngine.InputSystem.InputAction(
name: "WwiseMotionHaptic",
type: UnityEngine.InputSystem.InputActionType.PassThrough,
binding: bindingPath
);
hapticAction.Enable();
// The control count can be 0 when Unity's Input System is not ready yet. This could happen because the controller profile
// is not loaded, the controllers are not detected or action's path is not bound. We need to try again later.
if (hapticAction.controls.Count > 0)
{
m_xrHapticAction = GetAction(hapticAction); // Get the native XrAction handle from Unity OpenXR
if (m_xrHapticAction != 0)
{
gotAction = true;
}
else
{
WwiseLogger.Error("[AkWwiseOpenXRMotionFeature] Could not get XrAction with control bound.");
}
}
hapticAction.Disable(); // Does not need to be enabled once we have the XrAction handle.
return gotAction;
}
}
}
#endif // AK_ENABLE_OPENXR
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 61726edca15cc5c4d8faab9a8a3b58c0
@@ -0,0 +1,92 @@
/*******************************************************************************
The content of this file includes portions of the proprietary AUDIOKINETIC Wwise
Technology released in source code form as part of the game integration package.
The content of this file may not be used without valid licenses to the AUDIOKINETIC
Wwise Technology.
Note that the use of the game engine is subject to the Unity Terms of Service
at https://unity3d.com/legal/terms-of-service.
License Usage
Licensees holding valid licenses to the AUDIOKINETIC Wwise Technology may use
this file in accordance with the end user license agreement provided with the
software or, alternatively, in accordance with the terms contained
in a written agreement between you and Audiokinetic Inc.
Copyright (c) 2026 Audiokinetic Inc.
*******************************************************************************/
#if AK_ENABLE_OPENXR
using AK.Wwise.Unity.Logging;
using UnityEngine;
namespace AK.Wwise.Unity.OpenXR
{
/// <summary>
/// Helper component that adds a Wwise Motion output when OpenXR integration is ready.
///
/// IMPORTANT: The Motion output must be added AFTER OpenXR is fully initialized for the
/// OpenXR Motion sink to be selected. This component handles the correct timing by subscribing
/// to AkWwiseOpenXRMotionFeature.OnOpenXRMotionInitialized.
///
/// Usage: Attach this component to any persistent GameObject.
/// It will automatically add the Motion output at the correct time and remove it on destroy.
///
/// For custom implementations, subscribe to AkWwiseOpenXRMotionFeature.OnOpenXRMotionInitialized
/// and call AkUnitySoundEngine.AddOutput(new AkOutputSettings("Motion"), out deviceId) in your handler.
/// </summary>
[AddComponentMenu("Wwise/Motion Output")]
public class AkWwiseOpenXRMotionOutput : MonoBehaviour
{
private ulong m_motionOutputDeviceId = AkUnitySoundEngine.AK_INVALID_OUTPUT_DEVICE_ID;
[Tooltip("The name of the Motion shareset in your Wwise project.")]
public string MotionSharesetName = "Motion";
void Start()
{
// Check if already initialized (in case initialization happened before Start)
if (AkWwiseOpenXRMotionFeature.IsInitialized)
{
AddMotionOutput();
}
else
{
// Subscribe to the event
AkWwiseOpenXRMotionFeature.OnOpenXRMotionInitialized += AddMotionOutput;
}
}
void OnDestroy()
{
// Always unsubscribe to avoid memory leaks
AkWwiseOpenXRMotionFeature.OnOpenXRMotionInitialized -= AddMotionOutput;
if (m_motionOutputDeviceId != AkUnitySoundEngine.AK_INVALID_OUTPUT_DEVICE_ID)
{
AkUnitySoundEngine.RemoveOutput(m_motionOutputDeviceId);
}
}
private void AddMotionOutput()
{
// Unsubscribe immediately since we only want this called once
AkWwiseOpenXRMotionFeature.OnOpenXRMotionInitialized -= AddMotionOutput;
if (m_motionOutputDeviceId != AkUnitySoundEngine.AK_INVALID_OUTPUT_DEVICE_ID)
return; // Already added
var outputSettings = new AkOutputSettings(MotionSharesetName);
if (AkUnitySoundEngine.AddOutput(outputSettings, out m_motionOutputDeviceId) == AKRESULT.AK_Success)
{
WwiseLogger.Verbose($"[AkWwiseOpenXRMotionOutput] Successfully added Motion output device (ID: {m_motionOutputDeviceId})");
}
else
{
WwiseLogger.Error("[AkWwiseOpenXRMotionOutput] Failed to add Motion output.");
}
}
}
}
#endif // AK_ENABLE_OPENXR
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 5819149c005dfa34f94c12dfc34a6b47
+28
View File
@@ -0,0 +1,28 @@
{
"name": "com.audiokinetic.wwise.openxr",
"version": "2025.1.6",
"displayName": "Wwise Unity OpenXR Motion",
"description": "Wwise Motion haptics support via OpenXR for Meta Quest devices",
"unity": "2021.3",
"unityRelease": "32f1",
"dependencies": {
"com.unity.xr.openxr": "1.16.1"
},
"keywords": [
"Wwise",
"wwise",
"audiokinetic",
"ak",
"Audiokinetic",
"OpenXR",
"openxr",
"motion",
"haptics",
"VR"
],
"author": {
"name": "Audiokinetic Inc.",
"email": "info@audiokinetic.com",
"url": "https://www.audiokinetic.com"
}
}
+7
View File
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 5cd93a947f5a8df4f8485913873ca753
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant: