上传YomovSDK
This commit is contained in:
88
Packages/com.unity.xr.openxr/Editor/BootConfig.cs
Normal file
88
Packages/com.unity.xr.openxr/Editor/BootConfig.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor.Build.Reporting;
|
||||
using UnityEngine.XR.OpenXR;
|
||||
|
||||
namespace UnityEditor.XR.OpenXR
|
||||
{
|
||||
/// <summary>
|
||||
/// Small utility class for reading, updating and writing boot config.
|
||||
/// </summary>
|
||||
internal class BootConfig
|
||||
{
|
||||
private const string XrBootSettingsKey = "xr-boot-settings";
|
||||
|
||||
private readonly Dictionary<string, string> bootConfigSettings;
|
||||
private readonly string buildTargetName;
|
||||
|
||||
public BootConfig(BuildReport report) : this(report.summary.platform)
|
||||
{ }
|
||||
|
||||
public BootConfig(BuildTarget target)
|
||||
{
|
||||
bootConfigSettings = new Dictionary<string, string>();
|
||||
buildTargetName = BuildPipeline.GetBuildTargetName(target);
|
||||
}
|
||||
|
||||
public void ReadBootConfig()
|
||||
{
|
||||
bootConfigSettings.Clear();
|
||||
|
||||
string xrBootSettings = EditorUserBuildSettings.GetPlatformSettings(buildTargetName, XrBootSettingsKey);
|
||||
if (!string.IsNullOrEmpty(xrBootSettings))
|
||||
{
|
||||
// boot settings string format
|
||||
// <boot setting>:<value>[;<boot setting>:<value>]*
|
||||
var bootSettings = xrBootSettings.Split(';');
|
||||
foreach (var bootSetting in bootSettings)
|
||||
{
|
||||
var setting = bootSetting.Split(':');
|
||||
if (setting.Length == 2 && !string.IsNullOrEmpty(setting[0]) && !string.IsNullOrEmpty(setting[1]))
|
||||
{
|
||||
bootConfigSettings.Add(setting[0], setting[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<string, string> Settings => bootConfigSettings;
|
||||
|
||||
public void SetValueForKey(string key, string value) => bootConfigSettings[key] = value;
|
||||
|
||||
public bool TryGetValue(string key, out string value) => bootConfigSettings.TryGetValue(key, out value);
|
||||
|
||||
public void ClearEntryForKeyAndValue(string key, string value)
|
||||
{
|
||||
if (bootConfigSettings.TryGetValue(key, out string dictValue) && dictValue == value)
|
||||
{
|
||||
bootConfigSettings.Remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
public bool CheckValuePairExists(string key, string value)
|
||||
{
|
||||
var foundKey = bootConfigSettings.TryGetValue(key, out string result);
|
||||
|
||||
return foundKey && value.Equals(result);
|
||||
}
|
||||
|
||||
public void WriteBootConfig()
|
||||
{
|
||||
// boot settings string format
|
||||
// <boot setting>:<value>[;<boot setting>:<value>]*
|
||||
bool firstEntry = true;
|
||||
var sb = new System.Text.StringBuilder();
|
||||
foreach (var kvp in bootConfigSettings)
|
||||
{
|
||||
if (!firstEntry)
|
||||
{
|
||||
sb.Append(";");
|
||||
}
|
||||
|
||||
sb.Append($"{kvp.Key}:{kvp.Value}");
|
||||
firstEntry = false;
|
||||
}
|
||||
|
||||
EditorUserBuildSettings.SetPlatformSettings(buildTargetName, XrBootSettingsKey, sb.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Packages/com.unity.xr.openxr/Editor/BootConfig.cs.meta
Normal file
3
Packages/com.unity.xr.openxr/Editor/BootConfig.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2d11a038f66347888788fed0c117d2a2
|
||||
timeCreated: 1677875779
|
||||
56
Packages/com.unity.xr.openxr/Editor/BuildHelperUtils.cs
Normal file
56
Packages/com.unity.xr.openxr/Editor/BuildHelperUtils.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using System.Linq;
|
||||
using UnityEditor.Build;
|
||||
using UnityEditor.Build.Reporting;
|
||||
using UnityEditor.XR.Management;
|
||||
using UnityEngine.XR.Management;
|
||||
|
||||
namespace UnityEditor.XR.OpenXR
|
||||
{
|
||||
[InitializeOnLoad]
|
||||
internal class BuildHelperUtils : IPreprocessBuildWithReport
|
||||
{
|
||||
public static bool HasLoader(BuildTargetGroup targetGroup, System.Type loader)
|
||||
{
|
||||
var settings = XRGeneralSettingsPerBuildTarget.XRGeneralSettingsForBuildTarget(targetGroup);
|
||||
|
||||
if (settings)
|
||||
{
|
||||
#pragma warning disable CS0618
|
||||
return settings.Manager.loaders.Any(loader.IsInstanceOfType);
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public int callbackOrder => -100;
|
||||
|
||||
public void OnPreprocessBuild(BuildReport report)
|
||||
{
|
||||
MakeSureXRGeneralSettingsExists(report.summary.platformGroup);
|
||||
}
|
||||
|
||||
public static XRGeneralSettings MakeSureXRGeneralSettingsExists(BuildTargetGroup targetGroup)
|
||||
{
|
||||
// If we don't have XRGeneralSettings in EditorBuildSettings, check if we have one in the project and set it.
|
||||
var settings = XRGeneralSettingsPerBuildTarget.XRGeneralSettingsForBuildTarget(targetGroup);
|
||||
if (!settings)
|
||||
{
|
||||
string searchText = "t:XRGeneralSettings";
|
||||
string[] assets = AssetDatabase.FindAssets(searchText);
|
||||
for (int i = 0; i < assets.Length; ++i)
|
||||
{
|
||||
string path = AssetDatabase.GUIDToAssetPath(assets[i]);
|
||||
var allSettings = AssetDatabase.LoadAssetAtPath(path, typeof(XRGeneralSettingsPerBuildTarget)) as XRGeneralSettingsPerBuildTarget;
|
||||
if (allSettings != null)
|
||||
{
|
||||
EditorBuildSettings.AddConfigObject(XRGeneralSettings.k_SettingsKey, allSettings, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return settings;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Packages/com.unity.xr.openxr/Editor/BuildHelperUtils.cs.meta
Normal file
11
Packages/com.unity.xr.openxr/Editor/BuildHelperUtils.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 01028bf4492c94a2aab50778b3074bfc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9f78bba0495c6e449b1d9a0db3a978bf
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,76 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor.Build;
|
||||
using UnityEditor.Build.Reporting;
|
||||
using UnityEditor.XR.Management;
|
||||
using UnityEngine.XR.OpenXR;
|
||||
|
||||
namespace UnityEditor.XR.OpenXR
|
||||
{
|
||||
internal class UWPCoreWindowBuildHooks : IPreprocessBuildWithReport, IPostprocessBuildWithReport
|
||||
{
|
||||
// After MixedRealityBuildProcessor, if it's in the project.
|
||||
public int callbackOrder => 2;
|
||||
|
||||
private static readonly Dictionary<string, string> BootVars = new Dictionary<string, string>()
|
||||
{
|
||||
{"force-primary-window-holographic", "1"},
|
||||
{"vr-enabled", "1"},
|
||||
{"xrsdk-windowsmr-library", "UnityOpenXR.dll"},
|
||||
{"early-boot-windows-holographic", "1"},
|
||||
};
|
||||
|
||||
public void OnPreprocessBuild(BuildReport report)
|
||||
{
|
||||
if (report.summary.platform != BuildTarget.WSAPlayer)
|
||||
return;
|
||||
|
||||
if (!BuildHelperUtils.HasLoader(BuildTargetGroup.WSA, typeof(OpenXRLoaderBase)))
|
||||
return;
|
||||
|
||||
var bootConfig = new BootConfig(report);
|
||||
bootConfig.ReadBootConfig();
|
||||
|
||||
var initXRManagerOnStart = XRGeneralSettingsPerBuildTarget.XRGeneralSettingsForBuildTarget(BuildTargetGroup.WSA).InitManagerOnStart;
|
||||
BootVars["vr-enabled"] = initXRManagerOnStart ? "1" : "0";
|
||||
|
||||
// MixedRealityBuildProcessor may skip setting `force-primary-window-holographic` in certain cases:
|
||||
//
|
||||
// When AppRemoting is enabled, skip the flag to force primary corewindow to be holographic (it won't be).
|
||||
// If this flag exist, Unity might hit a bug that it skips rendering into the CoreWindow on the desktop.
|
||||
var skipPrimaryWindowHolographic = bootConfig.TryGetValue("xrsdk-windowsmr-library", out var unused1) &&
|
||||
(bootConfig.TryGetValue("vr-enabled", out var vrEnabled) && vrEnabled == "1") &&
|
||||
(bootConfig.TryGetValue("early-boot-windows-holographic", out var earlyBoot) && earlyBoot == "1") &&
|
||||
(!bootConfig.TryGetValue("force-primary-window-holographic", out var forceHolographic) || forceHolographic == "0");
|
||||
|
||||
foreach (var entry in BootVars)
|
||||
{
|
||||
if (entry.Key == "force-primary-window-holographic" && skipPrimaryWindowHolographic)
|
||||
continue;
|
||||
|
||||
bootConfig.SetValueForKey(entry.Key, entry.Value);
|
||||
}
|
||||
|
||||
bootConfig.WriteBootConfig();
|
||||
}
|
||||
|
||||
public void OnPostprocessBuild(BuildReport report)
|
||||
{
|
||||
if (report.summary.platform != BuildTarget.WSAPlayer)
|
||||
return;
|
||||
|
||||
if (!BuildHelperUtils.HasLoader(BuildTargetGroup.WSA, typeof(OpenXRLoaderBase)))
|
||||
return;
|
||||
|
||||
// Clean up boot settings after build
|
||||
BootConfig bootConfig = new BootConfig(report);
|
||||
bootConfig.ReadBootConfig();
|
||||
|
||||
foreach (KeyValuePair<string, string> entry in BootVars)
|
||||
{
|
||||
bootConfig.ClearEntryForKeyAndValue(entry.Key, entry.Value);
|
||||
}
|
||||
|
||||
bootConfig.WriteBootConfig();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4b8db528654998e4a8a2e81f00abc149
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Packages/com.unity.xr.openxr/Editor/FeatureSupport.meta
Normal file
8
Packages/com.unity.xr.openxr/Editor/FeatureSupport.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bf0a3147a1827ec4baf6196d5bf7f17d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,193 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor.Build;
|
||||
using UnityEditor.Build.Reporting;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.XR.OpenXR.Features
|
||||
{
|
||||
/// <summary>
|
||||
/// The Boot Config builder exposes a centralized call-site for populating BootConfig options.
|
||||
/// </summary>
|
||||
public class BootConfigBuilder
|
||||
{
|
||||
private struct SettingEntry
|
||||
{
|
||||
public bool IsDirty;
|
||||
public string Setting;
|
||||
}
|
||||
|
||||
private readonly Dictionary<string, SettingEntry> _bootConfigSettings;
|
||||
|
||||
/// <summary>
|
||||
/// Internal constructor. Should only ever get called inside this assembly. More to the point, it should only ever
|
||||
/// get called inside <see cref="OpenXRFeatureBuildHooks"/>
|
||||
/// </summary>
|
||||
internal BootConfigBuilder()
|
||||
{
|
||||
_bootConfigSettings = new Dictionary<string, SettingEntry>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populate the boot config settings from the current EditorUserBuildSettings based on the BuildReport.
|
||||
/// If <see cref="SetBootConfigValue"/> or <see cref="SetBootConfigBoolean"/> have been called before this call, we do not overwrite
|
||||
/// the value set, as we assume that these were meant to be the new, updated values.
|
||||
/// </summary>
|
||||
/// <param name="report">The BuildReport load the bootconfig from.</param>
|
||||
internal void ReadBootConfig(BuildReport report)
|
||||
{
|
||||
var bootConfig = new BootConfig(report);
|
||||
bootConfig.ReadBootConfig();
|
||||
|
||||
foreach (var setting in bootConfig.Settings)
|
||||
{
|
||||
// only update the boot config if the key doesn't currently live in _bootConfigSettings
|
||||
// We may have updated _bootConfigSettings before we've called `ReadBootConfig`. If that is the case,
|
||||
// this value overrides what's in the boot config.
|
||||
if (!_bootConfigSettings.TryGetValue(setting.Key, out var entry))
|
||||
_bootConfigSettings[setting.Key] = new SettingEntry { IsDirty = false, Setting = setting.Value };
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To ensure we don't have any lingering values carried over into the next build, we clear out the current
|
||||
/// boot config as part of the post build step.
|
||||
/// Any setting that we have added via a <see cref="SetBootConfigValue"/> or <see cref="SetBootConfigBoolean"/> will be cleaned up.
|
||||
/// Any setting that was already in the boot config will not be removed.
|
||||
/// </summary>
|
||||
/// <param name="report"></param>
|
||||
internal void ClearAndWriteBootConfig(BuildReport report)
|
||||
{
|
||||
var bootConfig = new BootConfig(report);
|
||||
|
||||
bootConfig.ReadBootConfig();
|
||||
|
||||
foreach (var entry in _bootConfigSettings)
|
||||
{
|
||||
if (entry.Value.IsDirty)
|
||||
bootConfig.ClearEntryForKeyAndValue(entry.Key, entry.Value.Setting);
|
||||
}
|
||||
|
||||
bootConfig.WriteBootConfig();
|
||||
|
||||
_bootConfigSettings.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write the current boot config.
|
||||
/// Since we can override the <see cref="IPostprocessBuildWithReport.OnPostprocessBuild"/>, <see cref="IPreprocessBuildWithReport.OnPreprocessBuild"/> methods,
|
||||
/// we cannot guarantee this method will get called, nor the order in which this method can be called. If you override these methods,
|
||||
/// unless you call the base methods last, you'll want to invoke this method manually.
|
||||
/// </summary>
|
||||
/// <param name="report">Build report that we want to write</param>
|
||||
internal void WriteBootConfig(BuildReport report)
|
||||
{
|
||||
// don't bother writing out if there's no boot config settings, or there isn't an OpenXR loader active.
|
||||
if (_bootConfigSettings.Count <= 0)
|
||||
return;
|
||||
|
||||
var bootConfig = new BootConfig(report);
|
||||
bootConfig.ReadBootConfig();
|
||||
|
||||
foreach (var entry in _bootConfigSettings)
|
||||
{
|
||||
// We only want to clean up the entries that we've added in this build processor.
|
||||
// Any other entries, we want to leave as is.
|
||||
if (entry.Value.IsDirty)
|
||||
bootConfig.SetValueForKey(entry.Key, entry.Value.Setting);
|
||||
}
|
||||
|
||||
bootConfig.WriteBootConfig();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method for setting a specific boot config option, given the key and the string value to store.
|
||||
/// </summary>
|
||||
/// <param name="key">Key of the value to be stored</param>
|
||||
/// <param name="value">String value to write to the key</param>
|
||||
/// <returns>True if we are able to set the config value, otherwise returns false</returns>
|
||||
public bool SetBootConfigValue(string key, string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
Debug.LogError("Cannot write a boot config value with an empty key.");
|
||||
return false;
|
||||
}
|
||||
|
||||
_bootConfigSettings[key] = new SettingEntry { IsDirty = true, Setting = value };
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method for setting a specific BOOLEAN config option. This method ensures a consistent method for writing a boolean value
|
||||
/// </summary>
|
||||
/// <param name="key">Key of the value to be stored</param>
|
||||
/// <param name="value">Boolean value to set</param>
|
||||
/// <returns>If the `key` existing in the boot config and it's "1", return true. Otherwise return false.</returns>
|
||||
public bool SetBootConfigBoolean(string key, bool value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
Debug.LogError("Cannot write a boot config with an empty key");
|
||||
return false;
|
||||
}
|
||||
|
||||
_bootConfigSettings[key] = new SettingEntry { IsDirty = true, Setting = value ? "1" : "0" };
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a config value from the boot config, given a specific key.
|
||||
/// </summary>
|
||||
/// <param name="key">Key we want to locate in the boot config</param>
|
||||
/// <param name="value">Where we store the result.</param>
|
||||
/// <returns>true if we find the key in the bootconfig, otherwise we return false</returns>
|
||||
public bool TryGetBootConfigValue(string key, out string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
Debug.LogError("Cannot write a boot config with an empty key");
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool result = _bootConfigSettings.TryGetValue(key, out var entry);
|
||||
value = result ? entry.Setting : null;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a boolean based on the value stored at `key`
|
||||
/// </summary>
|
||||
/// <param name="key">key to look for in the boot config</param>
|
||||
/// <param name="value">Where we store the result.</param>
|
||||
/// <returns>true if we find the key in the bootconfig, otherwise we return false</returns>
|
||||
public bool TryGetBootConfigBoolean(string key, out bool value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
Debug.LogError("Cannot perform a look up with a null or empty string key");
|
||||
value = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool result = _bootConfigSettings.TryGetValue(key, out var entry);
|
||||
value = result && entry.Setting.Equals("1");
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try and remove an entry from the boot config.
|
||||
/// </summary>
|
||||
/// <param name="key">The key to attempt to remove</param>
|
||||
/// <returns>true if we were able to remove the boot config entry, otherwise false</returns>
|
||||
public bool TryRemoveBootConfigEntry(string key)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key) || !_bootConfigSettings.ContainsKey(key))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _bootConfigSettings.Remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 538109467cb24d89bc714edf64bef64b
|
||||
timeCreated: 1680043148
|
||||
@@ -0,0 +1,71 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.XR.OpenXR.Features.Interactions;
|
||||
|
||||
namespace UnityEngine.XR.OpenXR.Features.Interactions
|
||||
{
|
||||
[CustomEditor(typeof(DPadInteraction))]
|
||||
internal class DPadInteractionCustomEditor : Editor
|
||||
{
|
||||
private SerializedProperty forceThresholdLeft;
|
||||
private SerializedProperty forceThresholdReleaseLeft;
|
||||
private SerializedProperty centerRegionLeft;
|
||||
private SerializedProperty wedgeAngleLeft;
|
||||
private SerializedProperty isStickyLeft;
|
||||
|
||||
private SerializedProperty forceThresholdRight;
|
||||
private SerializedProperty forceThresholdReleaseRight;
|
||||
private SerializedProperty centerRegionRight;
|
||||
private SerializedProperty wedgeAngleRight;
|
||||
private SerializedProperty isStickyRight;
|
||||
|
||||
static GUIContent s_ForceThresholdLabelLeft = EditorGUIUtility.TrTextContent("ForceThreshold");
|
||||
static GUIContent s_ForceThresholdReleaseLabelLeft = EditorGUIUtility.TrTextContent("ForceThresholdRelease");
|
||||
static GUIContent s_CenterRegionLeft = EditorGUIUtility.TrTextContent("centerRegion");
|
||||
static GUIContent s_WedgeAngleLeft = EditorGUIUtility.TrTextContent("wedgeAngle");
|
||||
static GUIContent s_IsStickyLeft = EditorGUIUtility.TrTextContent("isSticky");
|
||||
|
||||
static GUIContent s_ForceThresholdLabelRight = EditorGUIUtility.TrTextContent("ForceThreshold");
|
||||
static GUIContent s_ForceThresholdReleaseLabelRight = EditorGUIUtility.TrTextContent("ForceThresholdRelease");
|
||||
static GUIContent s_CenterRegionRight = EditorGUIUtility.TrTextContent("centerRegion");
|
||||
static GUIContent s_WedgeAngleRight = EditorGUIUtility.TrTextContent("wedgeAngle");
|
||||
static GUIContent s_IsStickyRight = EditorGUIUtility.TrTextContent("isSticky");
|
||||
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
forceThresholdLeft = serializedObject.FindProperty("forceThresholdLeft");
|
||||
forceThresholdReleaseLeft = serializedObject.FindProperty("forceThresholdReleaseLeft");
|
||||
centerRegionLeft = serializedObject.FindProperty("centerRegionLeft");
|
||||
wedgeAngleLeft = serializedObject.FindProperty("wedgeAngleLeft");
|
||||
isStickyLeft = serializedObject.FindProperty("isStickyLeft");
|
||||
|
||||
forceThresholdRight = serializedObject.FindProperty("forceThresholdRight");
|
||||
forceThresholdReleaseRight = serializedObject.FindProperty("forceThresholdReleaseRight");
|
||||
centerRegionRight = serializedObject.FindProperty("centerRegionRight");
|
||||
wedgeAngleRight = serializedObject.FindProperty("wedgeAngleRight");
|
||||
isStickyRight = serializedObject.FindProperty("isStickyRight");
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
EditorGUILayout.LabelField("Dpad Bindings Custom Values For Left Controller:", EditorStyles.boldLabel);
|
||||
EditorGUILayout.PropertyField(forceThresholdLeft, s_ForceThresholdLabelLeft);
|
||||
EditorGUILayout.PropertyField(forceThresholdReleaseLeft, s_ForceThresholdReleaseLabelLeft);
|
||||
EditorGUILayout.PropertyField(centerRegionLeft, s_CenterRegionLeft);
|
||||
EditorGUILayout.PropertyField(wedgeAngleLeft, s_WedgeAngleLeft);
|
||||
EditorGUILayout.PropertyField(isStickyLeft, s_IsStickyLeft);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.LabelField("Dpad Bindings Custom Values For Right Controller:", EditorStyles.boldLabel);
|
||||
EditorGUILayout.PropertyField(forceThresholdRight, s_ForceThresholdLabelRight);
|
||||
EditorGUILayout.PropertyField(forceThresholdReleaseRight, s_ForceThresholdReleaseLabelRight);
|
||||
EditorGUILayout.PropertyField(centerRegionRight, s_CenterRegionRight);
|
||||
EditorGUILayout.PropertyField(wedgeAngleRight, s_WedgeAngleRight);
|
||||
EditorGUILayout.PropertyField(isStickyRight, s_IsStickyRight);
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 363c557b3b6241808ae890327f748be8
|
||||
timeCreated: 1658880764
|
||||
@@ -0,0 +1,285 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine.XR.OpenXR.Features;
|
||||
using UnityEngine;
|
||||
using UnityEngine.XR.OpenXR;
|
||||
|
||||
[assembly: InternalsVisibleTo("Unity.XR.OpenXR.Tests.Editor")]
|
||||
[assembly: InternalsVisibleTo("Unity.XR.OpenXR.Tests")]
|
||||
namespace UnityEditor.XR.OpenXR.Features
|
||||
{
|
||||
/// <summary>
|
||||
/// Editor OpenXR Feature helpers.
|
||||
/// </summary>
|
||||
public static class FeatureHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Discovers all features in project and ensures that OpenXRSettings.Instance.features is up to date
|
||||
/// for selected build target group.
|
||||
/// </summary>
|
||||
/// <param name="group">build target group to refresh</param>
|
||||
public static void RefreshFeatures(BuildTargetGroup group)
|
||||
{
|
||||
FeatureHelpersInternal.GetAllFeatureInfo(group);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a feature id, returns the first instance of <see cref="OpenXRFeature" /> associated with that id.
|
||||
/// </summary>
|
||||
/// <param name="featureId">The unique id identifying the feature</param>
|
||||
/// <returns>The instance of the feature matching thd id, or null.</returns>
|
||||
public static OpenXRFeature GetFeatureWithIdForActiveBuildTarget(string featureId)
|
||||
{
|
||||
return GetFeatureWithIdForBuildTarget(BuildPipeline.GetBuildTargetGroup(UnityEditor.EditorUserBuildSettings.activeBuildTarget), featureId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given an array of feature ids, returns an array of matching <see cref="OpenXRFeature" /> instances.
|
||||
/// </summary>
|
||||
/// <param name="featureIds">Array of feature ids to match against.</param>
|
||||
/// <returns>An array of all matching features.</returns>
|
||||
public static OpenXRFeature[] GetFeaturesWithIdsForActiveBuildTarget(string[] featureIds)
|
||||
{
|
||||
return GetFeaturesWithIdsForBuildTarget(BuildPipeline.GetBuildTargetGroup(UnityEditor.EditorUserBuildSettings.activeBuildTarget), featureIds);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a feature id, returns the first <see cref="OpenXRFeature" /> associated with that id.
|
||||
/// </summary>
|
||||
/// <param name="buildTargetGroup">The build target group to get the feature from.</param>
|
||||
/// <param name="featureId">The unique id identifying the feature</param>
|
||||
/// <returns>The instance of the feature matching thd id, or null.</returns>
|
||||
public static OpenXRFeature GetFeatureWithIdForBuildTarget(BuildTargetGroup buildTargetGroup, string featureId)
|
||||
{
|
||||
if (String.IsNullOrEmpty(featureId))
|
||||
return null;
|
||||
|
||||
var settings = OpenXRSettings.GetSettingsForBuildTargetGroup(buildTargetGroup);
|
||||
if (settings == null)
|
||||
return null;
|
||||
|
||||
foreach (var feature in settings.features)
|
||||
{
|
||||
if (String.Compare(featureId, feature.featureIdInternal, true) == 0)
|
||||
return feature;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given an array of feature ids, returns an array of matching <see cref="OpenXRFeature" /> instances that match.
|
||||
/// </summary>
|
||||
/// <param name="buildTargetGroup">The build target group to get the feature from.</param>
|
||||
/// <param name="featureIds">Array of feature ids to match against.</param>
|
||||
/// <returns>An array of all matching features.</returns>
|
||||
public static OpenXRFeature[] GetFeaturesWithIdsForBuildTarget(BuildTargetGroup buildTargetGroup, string[] featureIds)
|
||||
{
|
||||
List<OpenXRFeature> ret = new List<OpenXRFeature>();
|
||||
|
||||
if (featureIds == null || featureIds.Length == 0)
|
||||
return ret.ToArray();
|
||||
|
||||
foreach (var featureId in featureIds)
|
||||
{
|
||||
var feature = GetFeatureWithIdForBuildTarget(buildTargetGroup, featureId);
|
||||
if (feature != null)
|
||||
ret.Add(feature);
|
||||
}
|
||||
|
||||
return ret.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal static class FeatureHelpersInternal
|
||||
{
|
||||
public class AllFeatureInfo
|
||||
{
|
||||
public List<FeatureInfo> Features;
|
||||
public BuildTarget[] CustomLoaderBuildTargets;
|
||||
}
|
||||
|
||||
public enum FeatureInfoCategory
|
||||
{
|
||||
Feature,
|
||||
Interaction
|
||||
}
|
||||
|
||||
public struct FeatureInfo
|
||||
{
|
||||
public string PluginPath;
|
||||
public OpenXRFeatureAttribute Attribute;
|
||||
public OpenXRFeature Feature;
|
||||
public FeatureInfoCategory Category;
|
||||
}
|
||||
|
||||
private static FeatureInfoCategory DetermineExtensionCategory(string extensionCategoryString)
|
||||
{
|
||||
if (String.Compare(extensionCategoryString, FeatureCategory.Interaction) == 0)
|
||||
{
|
||||
return FeatureInfoCategory.Interaction;
|
||||
}
|
||||
|
||||
return FeatureInfoCategory.Feature;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all features for group. If serialized feature instances do not exist, creates them.
|
||||
/// </summary>
|
||||
/// <param name="group">BuildTargetGroup to get feature information for.</param>
|
||||
/// <returns>feature info</returns>
|
||||
public static AllFeatureInfo GetAllFeatureInfo(BuildTargetGroup group)
|
||||
{
|
||||
AllFeatureInfo ret = new AllFeatureInfo { Features = new List<FeatureInfo>() };
|
||||
var openXrPackageSettings = OpenXRPackageSettings.GetOrCreateInstance();
|
||||
var openXrSettings = openXrPackageSettings.GetSettingsForBuildTargetGroup(group);
|
||||
if (openXrSettings == null)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
var assetPath = Path.Combine(OpenXRPackageSettings.GetAssetPathForComponents(OpenXRPackageSettings.s_PackageSettingsDefaultSettingsPath), openXrPackageSettings.name + ".asset");
|
||||
var openXrExtensionAssets = AssetDatabase.LoadAllAssetsAtPath(assetPath);
|
||||
|
||||
// Find any current extensions that are already serialized
|
||||
var currentExts = new Dictionary<OpenXRFeatureAttribute, OpenXRFeature>();
|
||||
var buildGroupName = group.ToString();
|
||||
|
||||
foreach (var ext in openXrExtensionAssets)
|
||||
{
|
||||
if (ext == null || !ext.name.Contains(buildGroupName))
|
||||
continue;
|
||||
|
||||
foreach (Attribute attr in Attribute.GetCustomAttributes(ext.GetType()))
|
||||
{
|
||||
if (attr is OpenXRFeatureAttribute)
|
||||
{
|
||||
var extAttr = (OpenXRFeatureAttribute)attr;
|
||||
currentExts[extAttr] = (OpenXRFeature)ext;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// only one custom loader is allowed per platform.
|
||||
string customLoaderExtName = "";
|
||||
|
||||
// Find any extensions that haven't yet been added to the feature list and create instances of them
|
||||
List<OpenXRFeature> all = new List<OpenXRFeature>();
|
||||
foreach (var extType in TypeCache.GetTypesWithAttribute<OpenXRFeatureAttribute>())
|
||||
{
|
||||
foreach (Attribute attr in Attribute.GetCustomAttributes(extType))
|
||||
{
|
||||
if (attr is OpenXRFeatureAttribute)
|
||||
{
|
||||
var extAttr = (OpenXRFeatureAttribute)attr;
|
||||
if (extAttr.BuildTargetGroups != null && !((IList)extAttr.BuildTargetGroups).Contains(group))
|
||||
continue;
|
||||
|
||||
if (!currentExts.TryGetValue(extAttr, out var extObj))
|
||||
{
|
||||
// Create a new one
|
||||
extObj = (OpenXRFeature)ScriptableObject.CreateInstance(extType);
|
||||
extObj.name = extType.Name + " " + group;
|
||||
AssetDatabase.AddObjectToAsset(extObj, openXrSettings);
|
||||
AssetDatabase.SaveAssets();
|
||||
}
|
||||
else
|
||||
{
|
||||
extObj.name = extType.Name + " " + group;
|
||||
}
|
||||
|
||||
if (extObj == null)
|
||||
continue;
|
||||
|
||||
bool enabled = (extObj.enabled);
|
||||
var ms = MonoScript.FromScriptableObject(extObj);
|
||||
var path = AssetDatabase.GetAssetPath(ms);
|
||||
|
||||
var dir = "";
|
||||
if (!String.IsNullOrEmpty(path))
|
||||
dir = Path.GetDirectoryName(path);
|
||||
ret.Features.Add(new FeatureInfo()
|
||||
{
|
||||
PluginPath = dir,
|
||||
Attribute = extAttr,
|
||||
Feature = extObj,
|
||||
Category = DetermineExtensionCategory(extAttr.Category)
|
||||
});
|
||||
|
||||
if (enabled && extAttr.CustomRuntimeLoaderBuildTargets?.Length > 0)
|
||||
{
|
||||
if (ret.CustomLoaderBuildTargets != null && (bool)extAttr.CustomRuntimeLoaderBuildTargets?.Intersect(ret.CustomLoaderBuildTargets).Any())
|
||||
{
|
||||
Debug.LogError($"Only one OpenXR feature may have a custom runtime loader per platform. Disable {customLoaderExtName} or {extAttr.UiName}.");
|
||||
}
|
||||
ret.CustomLoaderBuildTargets = extAttr.CustomRuntimeLoaderBuildTargets?.Union(ret?.CustomLoaderBuildTargets ?? new BuildTarget[] { }).ToArray();
|
||||
customLoaderExtName = extAttr.UiName;
|
||||
}
|
||||
|
||||
all.Add(extObj);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the feature list
|
||||
var originalFeatures = openXrSettings.features;
|
||||
var newFeatures = all
|
||||
.Where(f => f != null)
|
||||
.OrderByDescending(f => f.priority)
|
||||
.ThenBy(f => f.nameUi)
|
||||
.ToArray();
|
||||
|
||||
// Populate the internal feature variables for all features
|
||||
bool fieldChanged = false;
|
||||
foreach (var feature in newFeatures)
|
||||
{
|
||||
if (feature.internalFieldsUpdated)
|
||||
continue;
|
||||
|
||||
feature.internalFieldsUpdated = true;
|
||||
|
||||
foreach (var attr in feature.GetType().GetCustomAttributes<OpenXRFeatureAttribute>())
|
||||
{
|
||||
foreach (var sourceField in attr.GetType().GetFields())
|
||||
{
|
||||
var copyField = sourceField.GetCustomAttribute<OpenXRFeatureAttribute.CopyFieldAttribute>();
|
||||
if (copyField == null)
|
||||
continue;
|
||||
|
||||
var targetField = feature.GetType().GetField(copyField.FieldName,
|
||||
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
|
||||
if (targetField == null)
|
||||
continue;
|
||||
|
||||
// Only set value if value is different
|
||||
if ((targetField.GetValue(feature) == null && sourceField.GetValue(attr) != null) ||
|
||||
targetField.GetValue(feature) == null || targetField.GetValue(feature).Equals(sourceField.GetValue(attr)) == false)
|
||||
{
|
||||
targetField.SetValue(feature, sourceField.GetValue(attr));
|
||||
fieldChanged = true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the settings are saved after the features are populated
|
||||
if (fieldChanged || originalFeatures == null || originalFeatures.SequenceEqual(newFeatures) == false)
|
||||
{
|
||||
openXrSettings.features = newFeatures;
|
||||
#if UNITY_EDITOR
|
||||
EditorUtility.SetDirty(openXrSettings);
|
||||
#endif
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 492341990ff1a774f9aec136ed15b8d6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEditor.Build;
|
||||
using UnityEditor.XR.OpenXR;
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.XR.OpenXR;
|
||||
|
||||
namespace UnityEditor.XR.OpenXR.Features
|
||||
{
|
||||
internal static class KnownFeatureSetsContent
|
||||
{
|
||||
internal static readonly string s_MicrosoftHoloLensFeatureSetId = "com.microsoft.openxr.featureset.hololens";
|
||||
internal static readonly string s_MicrosoftHoloLensTitle = "Microsoft HoloLens";
|
||||
internal static readonly string s_MicrosoftHoloLensInformationText = "Enable the full suite of features for Microsoft HoloLens 2.";
|
||||
|
||||
internal static readonly string s_MicrosoftDownloadText = "This package must be installed. Click this icon to visit the download page for this package.";
|
||||
internal static readonly string s_MicrosoftDownloadLink = "http://aka.ms/openxr-unity-install";
|
||||
}
|
||||
|
||||
|
||||
internal static class KnownFeatureSets
|
||||
{
|
||||
internal static Dictionary<BuildTargetGroup, OpenXRFeatureSetManager.FeatureSet[]> k_KnownFeatureSets =
|
||||
new Dictionary<BuildTargetGroup, OpenXRFeatureSetManager.FeatureSet[]>()
|
||||
{
|
||||
{
|
||||
BuildTargetGroup.WSA,
|
||||
new OpenXRFeatureSetManager.FeatureSet[]
|
||||
{
|
||||
new OpenXRFeatureSetManager.FeatureSet() {
|
||||
isEnabled = false,
|
||||
name = KnownFeatureSetsContent.s_MicrosoftHoloLensTitle,
|
||||
featureSetId = KnownFeatureSetsContent.s_MicrosoftHoloLensFeatureSetId,
|
||||
description = KnownFeatureSetsContent.s_MicrosoftHoloLensInformationText,
|
||||
downloadText = KnownFeatureSetsContent.s_MicrosoftDownloadText,
|
||||
downloadLink = KnownFeatureSetsContent.s_MicrosoftDownloadLink,
|
||||
},
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f8eb84d75a4f7f64a86d587416e11ef1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,139 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UnityEditor.Build;
|
||||
using UnityEditor.Build.Reporting;
|
||||
using UnityEngine.XR.OpenXR;
|
||||
|
||||
namespace UnityEditor.XR.OpenXR.Features
|
||||
{
|
||||
internal class OpenXRChooseRuntimeLibraries : IPreprocessBuildWithReport
|
||||
{
|
||||
public int callbackOrder => 0;
|
||||
|
||||
public static string GetLoaderLibraryPath()
|
||||
{
|
||||
var extensions = FeatureHelpersInternal.GetAllFeatureInfo(BuildTargetGroup.Standalone);
|
||||
|
||||
// Loop over all the native plugin importers and find the custom loader
|
||||
var importers = PluginImporter.GetAllImporters();
|
||||
foreach (var importer in importers)
|
||||
{
|
||||
if (!importer.GetCompatibleWithEditor() || !importer.assetPath.Contains("openxr_loader"))
|
||||
continue;
|
||||
|
||||
#if UNITY_EDITOR_WIN
|
||||
if (!importer.GetCompatibleWithPlatform(BuildTarget.StandaloneWindows64) || !importer.assetPath.EndsWith(".dll"))
|
||||
continue;
|
||||
#elif UNITY_EDITOR_OSX
|
||||
if (!importer.GetCompatibleWithPlatform(BuildTarget.StandaloneOSX) || !importer.assetPath.EndsWith(".dylib"))
|
||||
continue;
|
||||
#endif
|
||||
|
||||
bool importerPartOfExtension = false;
|
||||
var root = Path.GetDirectoryName(importer.assetPath);
|
||||
foreach (var extInfo in extensions.Features)
|
||||
{
|
||||
bool extensionContainsLoader = (root != null && root.Contains(extInfo.PluginPath));
|
||||
importerPartOfExtension |= extensionContainsLoader;
|
||||
|
||||
bool customRuntimeLoaderOnEditorTarget = extInfo.Attribute.CustomRuntimeLoaderBuildTargets?.Intersect(
|
||||
new[] { BuildTarget.StandaloneWindows64, BuildTarget.StandaloneOSX, BuildTarget.StandaloneLinux64 }).Any() ?? false;
|
||||
|
||||
if (extensionContainsLoader &&
|
||||
customRuntimeLoaderOnEditorTarget &&
|
||||
extInfo.Feature.enabled)
|
||||
{
|
||||
return AssetPathToAbsolutePath(importer.assetPath);
|
||||
}
|
||||
}
|
||||
|
||||
// return default loader
|
||||
bool hasCustomLoader = extensions.CustomLoaderBuildTargets?.Length > 0;
|
||||
if (!importerPartOfExtension && !hasCustomLoader)
|
||||
return AssetPathToAbsolutePath(importer.assetPath);
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
private static string AssetPathToAbsolutePath(string assetPath)
|
||||
{
|
||||
var path = assetPath.Replace('/', Path.DirectorySeparatorChar);
|
||||
if (assetPath.StartsWith("Packages"))
|
||||
{
|
||||
path = String.Join("" + Path.DirectorySeparatorChar, path.Split(Path.DirectorySeparatorChar).Skip(2));
|
||||
|
||||
return Path.Combine(PackageManager.PackageInfo.FindForAssetPath(assetPath).resolvedPath, path);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
public void OnPreprocessBuild(BuildReport report)
|
||||
{
|
||||
var enabled = BuildHelperUtils.HasLoader(report.summary.platformGroup, typeof(OpenXRLoaderBase));
|
||||
|
||||
var extensions = FeatureHelpersInternal.GetAllFeatureInfo(report.summary.platformGroup);
|
||||
|
||||
// Keep set of seen plugins, only disable plugins that haven't been seen.
|
||||
HashSet<string> seenPlugins = new HashSet<string>();
|
||||
|
||||
// Loop over all the native plugin importers and only include the enabled ones in the build
|
||||
var importers = PluginImporter.GetAllImporters();
|
||||
foreach (var importer in importers)
|
||||
{
|
||||
if (!importer.GetCompatibleWithPlatform(report.summary.platform))
|
||||
continue;
|
||||
bool loader = false;
|
||||
if (importer.assetPath.Contains("openxr_loader"))
|
||||
{
|
||||
loader = true;
|
||||
if (extensions.CustomLoaderBuildTargets?.Contains(report.summary.platform) ?? false)
|
||||
importer.SetIncludeInBuildDelegate(path => false);
|
||||
else
|
||||
importer.SetIncludeInBuildDelegate(path => enabled);
|
||||
}
|
||||
|
||||
if (importer.assetPath.Contains("UnityOpenXR"))
|
||||
{
|
||||
importer.SetIncludeInBuildDelegate(path => enabled);
|
||||
}
|
||||
|
||||
var root = Path.GetDirectoryName(importer.assetPath);
|
||||
foreach (var extInfo in extensions.Features)
|
||||
{
|
||||
if (root != null && root.Contains(extInfo.PluginPath))
|
||||
{
|
||||
if (extInfo.Feature.enabled &&
|
||||
(!loader || (extInfo.Attribute.CustomRuntimeLoaderBuildTargets?.Contains(report.summary.platform) ?? false)))
|
||||
{
|
||||
importer.SetIncludeInBuildDelegate(path => enabled);
|
||||
}
|
||||
else if (!seenPlugins.Contains(importer.assetPath))
|
||||
{
|
||||
importer.SetIncludeInBuildDelegate(path => false);
|
||||
}
|
||||
seenPlugins.Add(importer.assetPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[InitializeOnLoadMethod]
|
||||
static void InitializeOnLoad()
|
||||
{
|
||||
var importers = PluginImporter.GetAllImporters();
|
||||
|
||||
// fixes asset bundle building since IPreProcessBuildWithReport isn't called
|
||||
foreach (var importer in importers)
|
||||
{
|
||||
if (importer.assetPath.Contains("openxr_loader"))
|
||||
{
|
||||
importer.SetIncludeInBuildDelegate(path => false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 95e24fd5228ad9f4da756618cc40a329
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,157 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor.Android;
|
||||
using UnityEditor.Build;
|
||||
using UnityEditor.Build.Reporting;
|
||||
using UnityEngine;
|
||||
using UnityEngine.XR.OpenXR;
|
||||
using UnityEngine.XR.OpenXR.Features;
|
||||
#if XR_MGMT_4_4_0_OR_NEWER
|
||||
using Unity.XR.Management.AndroidManifest.Editor;
|
||||
#endif
|
||||
|
||||
namespace UnityEditor.XR.OpenXR.Features
|
||||
{
|
||||
/// <summary>
|
||||
/// Inherit from this class to get callbacks to hook into the build process when your OpenXR Extension is enabled.
|
||||
/// </summary>
|
||||
#pragma warning disable 0618
|
||||
public abstract class OpenXRFeatureBuildHooks : IPostGenerateGradleAndroidProject, IPostprocessBuildWithReport, IPreprocessBuildWithReport
|
||||
#pragma warning restore 0618
|
||||
#if XR_MGMT_4_4_0_OR_NEWER
|
||||
, IAndroidManifestRequirementProvider
|
||||
#endif
|
||||
{
|
||||
private OpenXRFeature _ext;
|
||||
private readonly BootConfigBuilder _bootConfigBuilder = new BootConfigBuilder();
|
||||
|
||||
private bool IsExtensionEnabled(BuildTarget target, BuildTargetGroup group)
|
||||
{
|
||||
if (!BuildHelperUtils.HasLoader(group, typeof(OpenXRLoaderBase)))
|
||||
return false;
|
||||
|
||||
OpenXRSettings buildTargetOpenXRSettings = OpenXRSettings.GetSettingsForBuildTargetGroup(BuildPipeline.GetBuildTargetGroup(target));
|
||||
if (buildTargetOpenXRSettings == null || buildTargetOpenXRSettings.features == null)
|
||||
{
|
||||
UnityEngine.Debug.LogWarning($"Could not find valid OpenXR settings for build target {target}.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_ext == null || _ext.GetType() != featureType)
|
||||
{
|
||||
foreach (var ext in buildTargetOpenXRSettings.features)
|
||||
{
|
||||
if (featureType == ext.GetType())
|
||||
{
|
||||
_ext = ext;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return _ext != null && _ext.enabled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current callback order for build processing.
|
||||
/// </summary>
|
||||
/// <value>Int value denoting the callback order.</value>
|
||||
public abstract int callbackOrder { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Post process build step for checking if a feature is enabled. If so will call to the feature to run their build pre processing.
|
||||
/// </summary>
|
||||
/// <param name="report">Build report.</param>
|
||||
public virtual void OnPreprocessBuild(BuildReport report)
|
||||
{
|
||||
if (!IsExtensionEnabled(report.summary.platform, report.summary.platformGroup))
|
||||
return;
|
||||
|
||||
_bootConfigBuilder.ReadBootConfig(report);
|
||||
|
||||
OnProcessBootConfigExt(report, _bootConfigBuilder);
|
||||
|
||||
OnPreprocessBuildExt(report);
|
||||
|
||||
_bootConfigBuilder.WriteBootConfig(report);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Post process build step for checking if a feature is enabled for android builds. If so will call to the feature to run their build post processing for android builds.
|
||||
/// </summary>
|
||||
/// <param name="path">Path to gradle project.</param>
|
||||
public virtual void OnPostGenerateGradleAndroidProject(string path)
|
||||
{
|
||||
if (!IsExtensionEnabled(BuildTarget.Android, BuildTargetGroup.Android))
|
||||
return;
|
||||
|
||||
OnPostGenerateGradleAndroidProjectExt(path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Post-process build step for any necessary clean-up. This will also call to the feature to run their build post processing.
|
||||
/// </summary>
|
||||
/// <param name="report">Build report.</param>
|
||||
public virtual void OnPostprocessBuild(BuildReport report)
|
||||
{
|
||||
if (!IsExtensionEnabled(report.summary.platform, report.summary.platformGroup))
|
||||
return;
|
||||
|
||||
OnPostprocessBuildExt(report);
|
||||
|
||||
_bootConfigBuilder.ClearAndWriteBootConfig(report);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// System.Type of the class that implements OpenXRFeature.
|
||||
/// </summary>
|
||||
public abstract Type featureType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Called during the build process when the feature is enabled. Implement this function to receive a callback before the build starts.
|
||||
/// </summary>
|
||||
/// <param name="report">Report that contains information about the build, such as its target platform and output path.</param>
|
||||
protected abstract void OnPreprocessBuildExt(BuildReport report);
|
||||
|
||||
/// <summary>
|
||||
/// Called during build process when extension is enabled. Implement this function to receive a callback after the Android Gradle project is generated and before building begins. Function is not called for Internal builds.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to the root of the Gradle project. Note: When exporting the project, this parameter holds the path to the folder specified for export.</param>
|
||||
protected abstract void OnPostGenerateGradleAndroidProjectExt(string path);
|
||||
|
||||
/// <summary>
|
||||
/// Called during the build process when extension is enabled. Implement this function to receive a callback after the build is complete.
|
||||
/// </summary>
|
||||
/// <param name="report">BuildReport that contains information about the build, such as the target platform and output path.</param>
|
||||
protected abstract void OnPostprocessBuildExt(BuildReport report);
|
||||
|
||||
/// <summary>
|
||||
/// Called during the build process when extension is enabled. Implement this function to add Boot Config Settings.
|
||||
/// </summary>
|
||||
/// <param name="report">BuildReport that contains information about the build, such as the target platform and output path.</param>
|
||||
/// <param name="builder">This is the Boot Config interface tha can be used to write boot configs</param>
|
||||
protected virtual void OnProcessBootConfigExt(BuildReport report, BootConfigBuilder builder)
|
||||
{
|
||||
}
|
||||
|
||||
#if XR_MGMT_4_4_0_OR_NEWER
|
||||
/// <summary>
|
||||
/// Post process build step for checking if the hooks' related feature is enabled for Android builds If so, the hook can safely provide its Android manifest requirements.
|
||||
/// </summary>
|
||||
public virtual ManifestRequirement ProvideManifestRequirement()
|
||||
{
|
||||
if (!IsExtensionEnabled(BuildTarget.Android, BuildTargetGroup.Android))
|
||||
return null;
|
||||
|
||||
return ProvideManifestRequirementExt();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called during build process when collecting requirements for Android Manifest. Implement this function to add, override or remove Android manifest entries.
|
||||
/// </summary>
|
||||
protected virtual ManifestRequirement ProvideManifestRequirementExt()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8d5e18cf6f984a146a3b372338d4571c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,663 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.XR.OpenXR;
|
||||
using UnityEngine.XR.OpenXR.Features;
|
||||
|
||||
using UnityEditorInternal;
|
||||
|
||||
namespace UnityEditor.XR.OpenXR.Features
|
||||
{
|
||||
enum IssueType
|
||||
{
|
||||
None,
|
||||
Warning,
|
||||
Error
|
||||
}
|
||||
|
||||
|
||||
internal class ChildListItem
|
||||
{
|
||||
public GUIContent uiName;
|
||||
public GUIContent documentationIcon;
|
||||
public GUIContent categoryName;
|
||||
public GUIContent version;
|
||||
public GUIContent partner;
|
||||
public string partnerName;
|
||||
public string documentationLink;
|
||||
public bool settingsExpanded;
|
||||
public OpenXRFeature feature;
|
||||
public bool shouldDisplaySettings;
|
||||
public UnityEditor.Editor settingsEditor;
|
||||
public string featureId;
|
||||
public IssueType issueType;
|
||||
}
|
||||
|
||||
|
||||
internal class OpenXRFeatureEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// Path of the OpenXR settings in the Settings window. Uses "/" as separator. The last token becomes the settings label if none is provided.
|
||||
/// </summary>
|
||||
public const string k_FeatureSettingsPathUI =
|
||||
#if XR_MGMT_3_2_0_OR_NEWER
|
||||
"Project/XR Plug-in Management/OpenXR/Features";
|
||||
#else
|
||||
"Project/XR Plugin Management/OpenXR/Features";
|
||||
#endif
|
||||
|
||||
static class Styles
|
||||
{
|
||||
public static float k_IconWidth = 16f;
|
||||
public static float k_DefaultSelectionWidth = 200f;
|
||||
public static float k_DefualtLineMultiplier = 2f;
|
||||
|
||||
public static GUIStyle s_SelectionStyle = "TV Selection";
|
||||
public static GUIStyle s_SelectionBackground = "ScrollViewAlt";
|
||||
public static GUIStyle s_FeatureSetTitleLable;
|
||||
public static GUIStyle s_ListLabel;
|
||||
public static GUIStyle s_ListSelectedLabel;
|
||||
public static GUIStyle s_ListLabelToggle;
|
||||
public static GUIStyle s_Feature;
|
||||
public static GUIStyle s_FeatureSettings;
|
||||
}
|
||||
|
||||
static class Content
|
||||
{
|
||||
public static readonly GUIContent k_HelpIcon = EditorGUIUtility.IconContent("_Help");
|
||||
public static readonly GUIContent k_SettingsIcon = EditorGUIUtility.IconContent("Settings");
|
||||
|
||||
public static readonly GUIContent k_Settings = new GUIContent("", k_SettingsIcon.image, "Open settings editor for this feature.");
|
||||
public static readonly GUIContent k_InteractionProfilesTitle = new GUIContent("Enabled Interaction Profiles");
|
||||
}
|
||||
|
||||
List<OpenXRFeatureSetManager.FeatureSetInfo> selectionListItems = new List<OpenXRFeatureSetManager.FeatureSetInfo>();
|
||||
OpenXRFeatureSetManager.FeatureSetInfo selectedItem = null;
|
||||
private List<IssueType> issuesPerFeatureSet = new List<IssueType>();
|
||||
|
||||
List<ChildListItem> filteredListItems = new List<ChildListItem>();
|
||||
List<ChildListItem> allListItems = new List<ChildListItem>();
|
||||
|
||||
Dictionary<string, ChildListItem> interactionItems = new Dictionary<string, ChildListItem>();
|
||||
List<string> selectedFeatureIds = new List<string>();
|
||||
ReorderableList interactionFeaturesList = null;
|
||||
HashSet<string> requiredFeatures = new HashSet<string>();
|
||||
|
||||
FeatureHelpersInternal.AllFeatureInfo allFeatureInfos = null;
|
||||
BuildTargetGroup activeBuildTarget = BuildTargetGroup.Unknown;
|
||||
|
||||
List<OpenXRFeature.ValidationRule> _issues = new List<OpenXRFeature.ValidationRule>();
|
||||
|
||||
Dictionary<BuildTargetGroup, int> lastSelectedItemIndex = new Dictionary<BuildTargetGroup, int>();
|
||||
|
||||
OpenXRFeatureSettingsEditor featureSetSettingsEditor = null;
|
||||
|
||||
bool mustInitializeFeatures = false;
|
||||
|
||||
static readonly string s_AllFeatures = "All Features";
|
||||
|
||||
public OpenXRFeatureEditor()
|
||||
{
|
||||
SetupInteractionListUI();
|
||||
}
|
||||
|
||||
(Rect, Rect) TakeFromFrontOfRect(Rect rect, float width)
|
||||
{
|
||||
var newRect = new Rect(rect);
|
||||
newRect.x = rect.x + 5;
|
||||
newRect.width = width;
|
||||
rect.x = (newRect.x + newRect.width) + 1;
|
||||
rect.width -= width + 1;
|
||||
return (newRect, rect);
|
||||
}
|
||||
|
||||
void SetupInteractionListUI()
|
||||
{
|
||||
if (interactionFeaturesList != null)
|
||||
return;
|
||||
|
||||
interactionFeaturesList = new ReorderableList(selectedFeatureIds, typeof(ChildListItem), false, true, true, true);
|
||||
|
||||
interactionFeaturesList.drawHeaderCallback = (rect) =>
|
||||
{
|
||||
var labelSize = EditorStyles.label.CalcSize(Content.k_InteractionProfilesTitle);
|
||||
var labelRect = new Rect(rect);
|
||||
labelRect.width = labelSize.x;
|
||||
|
||||
EditorGUI.LabelField(labelRect, Content.k_InteractionProfilesTitle, EditorStyles.label);
|
||||
};
|
||||
interactionFeaturesList.drawElementCallback = (rect, index, isActive, isFocused) =>
|
||||
{
|
||||
Rect fieldRect;
|
||||
string featureId = selectedFeatureIds[index];
|
||||
var item = interactionItems[featureId];
|
||||
var labelSize = EditorStyles.label.CalcSize(item.uiName);
|
||||
(fieldRect, rect) = TakeFromFrontOfRect(rect, labelSize.x);
|
||||
EditorGUI.BeginDisabledGroup(requiredFeatures.Contains(item.featureId));
|
||||
EditorGUI.LabelField(fieldRect, item.uiName, EditorStyles.label);
|
||||
EditorGUI.EndDisabledGroup();
|
||||
|
||||
if (!String.IsNullOrEmpty(item.documentationLink))
|
||||
{
|
||||
var size = EditorStyles.label.CalcSize(item.documentationIcon);
|
||||
(fieldRect, rect) = TakeFromFrontOfRect(rect, size.x);
|
||||
if (GUI.Button(fieldRect, item.documentationIcon, EditorStyles.label))
|
||||
{
|
||||
UnityEngine.Application.OpenURL(item.documentationLink);
|
||||
}
|
||||
}
|
||||
|
||||
if (item.issueType != IssueType.None)
|
||||
{
|
||||
GUIContent icon = (item.issueType == IssueType.Error) ? CommonContent.k_ValidationErrorIcon : CommonContent.k_ValidationWarningIcon;
|
||||
var size = EditorStyles.label.CalcSize(icon);
|
||||
(fieldRect, rect) = TakeFromFrontOfRect(rect, size.x);
|
||||
if (GUI.Button(fieldRect, icon, EditorStyles.label))
|
||||
{
|
||||
OpenXRProjectValidationRulesSetup.ShowWindow(activeBuildTarget);
|
||||
}
|
||||
}
|
||||
};
|
||||
interactionFeaturesList.onAddDropdownCallback = (rect, list) =>
|
||||
{
|
||||
GenericMenu menu = new GenericMenu();
|
||||
foreach (var kvp in interactionItems)
|
||||
{
|
||||
if (selectedFeatureIds.IndexOf(kvp.Key) == -1)
|
||||
{
|
||||
menu.AddItem(kvp.Value.uiName, false, (object obj) =>
|
||||
{
|
||||
string featureId = obj as string;
|
||||
if (!String.IsNullOrEmpty(featureId))
|
||||
{
|
||||
selectedFeatureIds.Add(featureId);
|
||||
var interactionItem = interactionItems[featureId];
|
||||
interactionItem.feature.enabled = true;
|
||||
}
|
||||
}, kvp.Key);
|
||||
}
|
||||
}
|
||||
menu.DropDown(rect);
|
||||
};
|
||||
interactionFeaturesList.onCanRemoveCallback = (list) =>
|
||||
{
|
||||
var featureId = selectedFeatureIds[list.index];
|
||||
return !requiredFeatures.Contains(featureId);
|
||||
};
|
||||
interactionFeaturesList.onRemoveCallback = (list) =>
|
||||
{
|
||||
var featureId = selectedFeatureIds[list.index];
|
||||
if (requiredFeatures.Contains(featureId))
|
||||
return;
|
||||
|
||||
var interactionItem = interactionItems[featureId];
|
||||
interactionItem.feature.enabled = false;
|
||||
selectedFeatureIds.RemoveAt(list.index);
|
||||
};
|
||||
}
|
||||
|
||||
void UpdateValidationIssues(BuildTargetGroup buildTargetGroup)
|
||||
{
|
||||
_issues.Clear();
|
||||
OpenXRProjectValidation.GetCurrentValidationIssues(_issues, buildTargetGroup);
|
||||
|
||||
foreach (var item in allListItems)
|
||||
{
|
||||
item.issueType = GetValidationIssueType(item.feature);
|
||||
}
|
||||
|
||||
foreach (var item in interactionItems.Values)
|
||||
{
|
||||
item.issueType = GetValidationIssueType(item.feature);
|
||||
}
|
||||
|
||||
issuesPerFeatureSet.Clear();
|
||||
foreach (var featureSet in selectionListItems)
|
||||
{
|
||||
var featureSetIssue = IssueType.None;
|
||||
foreach (var item in allListItems)
|
||||
{
|
||||
if (featureSet.featureIds == null)
|
||||
break;
|
||||
if (Array.IndexOf(featureSet.featureIds, item.featureId) == -1)
|
||||
continue;
|
||||
|
||||
if (item.issueType == IssueType.Error)
|
||||
{
|
||||
featureSetIssue = IssueType.Error;
|
||||
break;
|
||||
}
|
||||
|
||||
if (item.issueType == IssueType.Warning)
|
||||
{
|
||||
featureSetIssue = IssueType.Warning;
|
||||
}
|
||||
}
|
||||
issuesPerFeatureSet.Add(featureSetIssue);
|
||||
}
|
||||
}
|
||||
|
||||
IssueType GetValidationIssueType(OpenXRFeature feature)
|
||||
{
|
||||
IssueType ret = IssueType.None;
|
||||
|
||||
foreach (var issue in _issues)
|
||||
{
|
||||
if (feature == issue.feature)
|
||||
{
|
||||
if (issue.error)
|
||||
{
|
||||
ret = IssueType.Error;
|
||||
break;
|
||||
}
|
||||
|
||||
ret = IssueType.Warning;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void DrawInteractionList()
|
||||
{
|
||||
EditorGUILayout.Space();
|
||||
|
||||
if (interactionItems.Count == 0)
|
||||
return;
|
||||
|
||||
var iconSize = EditorGUIUtility.GetIconSize();
|
||||
EditorGUILayout.BeginVertical();
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUIUtility.SetIconSize(new Vector2(30, 30));
|
||||
GUILayout.Label(EditorGUIUtility.IconContent("console.infoicon"), new GUIStyle(EditorStyles.label));
|
||||
EditorGUIUtility.SetIconSize(iconSize);
|
||||
GUILayout.Label("Only enable interaction profiles that you actually test, to ensure their input bindings are complete. Otherwise, disable that interaction profile, to allow the OpenXR runtime to remap user input from a profile you do test.", EditorStyles.wordWrappedLabel);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
EditorGUILayout.EndVertical();
|
||||
|
||||
interactionFeaturesList.DoLayoutList();
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
void OnSelectItem(OpenXRFeatureSetManager.FeatureSetInfo selectedItem)
|
||||
{
|
||||
this.selectedItem = selectedItem;
|
||||
|
||||
int selectedItemIndex = selectionListItems.IndexOf(selectedItem);
|
||||
if (lastSelectedItemIndex.ContainsKey(activeBuildTarget))
|
||||
lastSelectedItemIndex[activeBuildTarget] = selectedItemIndex;
|
||||
else
|
||||
lastSelectedItemIndex.Add(activeBuildTarget, selectedItemIndex);
|
||||
|
||||
if (this.selectedItem != null)
|
||||
{
|
||||
if (String.IsNullOrEmpty(selectedItem.featureSetId))
|
||||
{
|
||||
filteredListItems = allListItems.
|
||||
OrderBy((item) => item.uiName.text).
|
||||
ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
filteredListItems = allListItems.
|
||||
Where((item) => Array.IndexOf(selectedItem.featureIds, item.featureId) > -1).
|
||||
OrderBy((item) => item.uiName.text).
|
||||
ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DrawSelectionList()
|
||||
{
|
||||
var skin = EditorGUIUtility.GetBuiltinSkin(EditorSkin.Inspector);
|
||||
var lineHeight = EditorGUIUtility.singleLineHeight * Styles.k_DefualtLineMultiplier;
|
||||
|
||||
EditorGUILayout.BeginVertical(GUILayout.Width(Styles.k_DefaultSelectionWidth), GUILayout.ExpandWidth(true));
|
||||
{
|
||||
EditorGUILayout.LabelField("OpenXR Feature Groups", Styles.s_FeatureSetTitleLable);
|
||||
|
||||
EditorGUILayout.BeginVertical(Styles.s_SelectionBackground, GUILayout.ExpandHeight(true));
|
||||
{
|
||||
int index = 0;
|
||||
foreach (var item in selectionListItems)
|
||||
{
|
||||
var typeOfIssues = issuesPerFeatureSet[index++];
|
||||
var selected = (item == this.selectedItem);
|
||||
var style = selected ? Styles.s_ListSelectedLabel : Styles.s_ListLabel;
|
||||
bool disabled = item.uiName.text != "All" && item.featureIds == null;
|
||||
EditorGUILayout.BeginHorizontal(style, GUILayout.ExpandWidth(true));
|
||||
{
|
||||
EditorGUI.BeginDisabledGroup(disabled);
|
||||
|
||||
if (String.Compare(item.uiName.text, s_AllFeatures, true) != 0)
|
||||
{
|
||||
var currentToggleState = item.isEnabled;
|
||||
var newToggleState = EditorGUILayout.ToggleLeft("", currentToggleState, GUILayout.ExpandWidth(false), GUILayout.Width(Styles.k_IconWidth), GUILayout.Height(lineHeight));
|
||||
if (newToggleState != currentToggleState)
|
||||
{
|
||||
item.isEnabled = newToggleState;
|
||||
OpenXRFeatureSetManager.SetFeaturesFromEnabledFeatureSets(activeBuildTarget);
|
||||
}
|
||||
}
|
||||
|
||||
if (GUILayout.Button(item.uiName, Styles.s_ListLabel, GUILayout.ExpandWidth(true), GUILayout.Height(lineHeight)))
|
||||
{
|
||||
OnSelectItem(item);
|
||||
}
|
||||
|
||||
EditorGUI.EndDisabledGroup();
|
||||
|
||||
if (disabled && item.helpIcon != null)
|
||||
{
|
||||
if (GUILayout.Button(item.helpIcon, EditorStyles.label, GUILayout.Width(Styles.k_IconWidth), GUILayout.Height(lineHeight)))
|
||||
{
|
||||
UnityEngine.Application.OpenURL(item.downloadLink);
|
||||
}
|
||||
}
|
||||
if (typeOfIssues != IssueType.None)
|
||||
{
|
||||
GUIContent icon = (typeOfIssues == IssueType.Error) ? CommonContent.k_ValidationErrorIcon : CommonContent.k_ValidationWarningIcon;
|
||||
if (GUILayout.Button(icon, EditorStyles.label, GUILayout.Width(Styles.k_IconWidth), GUILayout.Height(lineHeight)))
|
||||
{
|
||||
OpenXRProjectValidationRulesSetup.ShowWindow(activeBuildTarget);
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
}
|
||||
|
||||
void DrawFeatureList()
|
||||
{
|
||||
EditorGUILayout.BeginVertical();
|
||||
{
|
||||
EditorGUILayout.LabelField("", Styles.s_FeatureSetTitleLable);
|
||||
|
||||
foreach (var filteredListItem in filteredListItems)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal(GUILayout.ExpandWidth(false));
|
||||
{
|
||||
EditorGUILayout.BeginVertical(Styles.s_Feature, GUILayout.ExpandWidth(false));
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal(GUILayout.ExpandWidth(false));
|
||||
{
|
||||
var typeOfIssue = filteredListItem.issueType;
|
||||
var featureNameSize = EditorStyles.toggle.CalcSize(filteredListItem.uiName);
|
||||
var oldEnabledState = filteredListItem.feature.enabled;
|
||||
EditorGUI.BeginDisabledGroup(requiredFeatures.Contains(filteredListItem.featureId));
|
||||
filteredListItem.feature.enabled = EditorGUILayout.ToggleLeft(filteredListItem.uiName, filteredListItem.feature.enabled, GUILayout.ExpandWidth(false), GUILayout.Width(featureNameSize.x));
|
||||
EditorGUI.EndDisabledGroup();
|
||||
if (!String.IsNullOrEmpty(filteredListItem.documentationLink))
|
||||
{
|
||||
if (GUILayout.Button(filteredListItem.documentationIcon, EditorStyles.label, GUILayout.Width(Styles.k_IconWidth)))
|
||||
{
|
||||
UnityEngine.Application.OpenURL(filteredListItem.documentationLink);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeOfIssue != IssueType.None)
|
||||
{
|
||||
GUIContent icon = (typeOfIssue == IssueType.Error) ? CommonContent.k_ValidationErrorIcon : CommonContent.k_ValidationWarningIcon;
|
||||
if (GUILayout.Button(icon, EditorStyles.label, GUILayout.Width(Styles.k_IconWidth)))
|
||||
{
|
||||
OpenXRProjectValidationRulesSetup.ShowWindow(activeBuildTarget);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (filteredListItem.shouldDisplaySettings)
|
||||
{
|
||||
if (GUILayout.Button(Content.k_Settings, Styles.s_FeatureSettings, GUILayout.ExpandWidth(true)))
|
||||
{
|
||||
if (featureSetSettingsEditor == null)
|
||||
{
|
||||
if (EditorWindow.HasOpenInstances<OpenXRFeatureSettingsEditor>())
|
||||
{
|
||||
featureSetSettingsEditor = EditorWindow.GetWindow<OpenXRFeatureSettingsEditor>();
|
||||
}
|
||||
else
|
||||
{
|
||||
featureSetSettingsEditor = ScriptableObject.CreateInstance<OpenXRFeatureSettingsEditor>() as OpenXRFeatureSettingsEditor;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (featureSetSettingsEditor != null)
|
||||
{
|
||||
featureSetSettingsEditor.ActiveItem = filteredListItem.featureId;
|
||||
featureSetSettingsEditor.ActiveBuildTarget = activeBuildTarget;
|
||||
featureSetSettingsEditor.ShowUtility();
|
||||
featureSetSettingsEditor.Focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
}
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
}
|
||||
|
||||
void DrawFeatureSetUI()
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal(GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(true));
|
||||
|
||||
DrawSelectionList();
|
||||
DrawFeatureList();
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
public void OnGUI(BuildTargetGroup buildTargetGroup)
|
||||
{
|
||||
InitStyles();
|
||||
Vector2 iconSize = EditorGUIUtility.GetIconSize();
|
||||
EditorGUIUtility.SetIconSize(new Vector2(Styles.k_IconWidth, Styles.k_IconWidth));
|
||||
|
||||
if (buildTargetGroup != activeBuildTarget || mustInitializeFeatures)
|
||||
{
|
||||
var allFeatureInfo = InitializeFeatures(buildTargetGroup);
|
||||
OpenXRFeatureSetManager.activeBuildTarget = buildTargetGroup;
|
||||
OpenXRFeatureSetManager.SetFeaturesFromEnabledFeatureSets(buildTargetGroup, allFeatureInfo);
|
||||
|
||||
// This must be done after SetFeaturesFromEnabledFeatureSets to ensure we dont get an infinite update loop
|
||||
mustInitializeFeatures = false;
|
||||
|
||||
if (EditorWindow.HasOpenInstances<OpenXRFeatureSettingsEditor>())
|
||||
{
|
||||
featureSetSettingsEditor = EditorWindow.GetWindow<OpenXRFeatureSettingsEditor>();
|
||||
}
|
||||
|
||||
if (featureSetSettingsEditor != null)
|
||||
{
|
||||
featureSetSettingsEditor.ActiveBuildTarget = activeBuildTarget;
|
||||
}
|
||||
}
|
||||
|
||||
if (allFeatureInfos != null)
|
||||
{
|
||||
UpdateValidationIssues(buildTargetGroup);
|
||||
DrawInteractionList();
|
||||
EditorGUILayout.Space();
|
||||
DrawFeatureSetUI();
|
||||
}
|
||||
|
||||
EditorGUIUtility.SetIconSize(iconSize);
|
||||
}
|
||||
|
||||
bool HasSettingsToDisplay(OpenXRFeature feature)
|
||||
{
|
||||
FieldInfo[] fieldInfo = feature.GetType().GetFields(BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance);
|
||||
foreach (var field in fieldInfo)
|
||||
{
|
||||
var nonSerializedAttrs = field.GetCustomAttributes(typeof(NonSerializedAttribute));
|
||||
if (nonSerializedAttrs.Count() == 0)
|
||||
return true;
|
||||
}
|
||||
|
||||
fieldInfo = feature.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.DeclaredOnly | BindingFlags.Instance);
|
||||
foreach (var field in fieldInfo)
|
||||
{
|
||||
var serializedAttrs = field.GetCustomAttributes(typeof(SerializeField));
|
||||
if (serializedAttrs.Count() > 0)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
FeatureHelpersInternal.AllFeatureInfo InitializeFeatures(BuildTargetGroup group)
|
||||
{
|
||||
selectionListItems.Clear();
|
||||
filteredListItems.Clear();
|
||||
allListItems.Clear();
|
||||
interactionItems.Clear();
|
||||
selectedFeatureIds.Clear();
|
||||
requiredFeatures.Clear();
|
||||
|
||||
allFeatureInfos = FeatureHelpersInternal.GetAllFeatureInfo(group);
|
||||
|
||||
activeBuildTarget = group;
|
||||
|
||||
var featureSets = OpenXRFeatureSetManager.FeatureSetInfosForBuildTarget(group).
|
||||
OrderBy((fs) => fs.uiName.text);
|
||||
foreach (var featureSet in featureSets)
|
||||
{
|
||||
bool isKnownUninstalledFeatureSet = featureSet.featureIds == null && OpenXRFeatureSetManager.IsKnownFeatureSet(activeBuildTarget, featureSet.featureSetId);
|
||||
|
||||
var featureSetFeatures = allFeatureInfos.Features.
|
||||
Where((f) =>
|
||||
featureSet.featureIds != null && Array.IndexOf(featureSet.featureIds, f.Attribute.FeatureId) > -1);
|
||||
|
||||
if (isKnownUninstalledFeatureSet || featureSetFeatures.Any())
|
||||
{
|
||||
selectionListItems.Add(featureSet);
|
||||
if (featureSet.isEnabled && featureSet.requiredFeatureIds != null)
|
||||
requiredFeatures.UnionWith(featureSet.requiredFeatureIds);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var _ext in allFeatureInfos.Features)
|
||||
{
|
||||
if (_ext.Attribute.Hidden)
|
||||
continue;
|
||||
|
||||
var listItem = new ChildListItem()
|
||||
{
|
||||
uiName = new GUIContent(_ext.Attribute.UiName),
|
||||
documentationIcon = new GUIContent("", Content.k_HelpIcon.image, "Click for documentation"),
|
||||
categoryName = new GUIContent($"Category: {_ext.Category.ToString()}"),
|
||||
partner = new GUIContent($"Author: {_ext.Attribute.Company}"),
|
||||
version = new GUIContent($"Version: {_ext.Attribute.Version}"),
|
||||
partnerName = _ext.Attribute.Company,
|
||||
documentationLink = _ext.Attribute.InternalDocumentationLink,
|
||||
shouldDisplaySettings = HasSettingsToDisplay(_ext.Feature),
|
||||
feature = _ext.Feature,
|
||||
featureId = _ext.Attribute.FeatureId
|
||||
};
|
||||
|
||||
if (_ext.Attribute.Category == UnityEditor.XR.OpenXR.Features.FeatureCategory.Interaction)
|
||||
{
|
||||
interactionItems.Add(listItem.featureId, listItem);
|
||||
if (listItem.feature.enabled)
|
||||
{
|
||||
selectedFeatureIds.Add(listItem.featureId);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
allListItems.Add(listItem);
|
||||
}
|
||||
}
|
||||
|
||||
selectionListItems.Add(new OpenXRFeatureSetManager.FeatureSetInfo()
|
||||
{
|
||||
uiName = new GUIContent(s_AllFeatures),
|
||||
featureSetId = string.Empty,
|
||||
featureIds = allFeatureInfos.Features.Select((e) => e.Attribute.FeatureId).ToArray(),
|
||||
});
|
||||
|
||||
var initialSelectedItem = selectionListItems[selectionListItems.Count - 1];
|
||||
if (lastSelectedItemIndex.ContainsKey(activeBuildTarget))
|
||||
{
|
||||
initialSelectedItem = selectionListItems[lastSelectedItemIndex[activeBuildTarget]];
|
||||
}
|
||||
OnSelectItem(initialSelectedItem);
|
||||
|
||||
return allFeatureInfos;
|
||||
}
|
||||
|
||||
void InitStyles()
|
||||
{
|
||||
if (Styles.s_ListLabel == null)
|
||||
{
|
||||
Styles.s_FeatureSetTitleLable = new GUIStyle(EditorStyles.label);
|
||||
Styles.s_FeatureSetTitleLable.fontSize = 14;
|
||||
Styles.s_FeatureSetTitleLable.fontStyle = FontStyle.Bold;
|
||||
|
||||
Styles.s_ListLabel = new GUIStyle(EditorStyles.label);
|
||||
Styles.s_ListLabel.border = new RectOffset(0, 0, 0, 0);
|
||||
Styles.s_ListLabel.padding = new RectOffset(5, 0, 0, 0);
|
||||
Styles.s_ListLabel.margin = new RectOffset(2, 2, 2, 2);
|
||||
|
||||
Styles.s_ListSelectedLabel = new GUIStyle(Styles.s_SelectionStyle);
|
||||
Styles.s_ListSelectedLabel.border = Styles.s_ListLabel.border;
|
||||
Styles.s_ListSelectedLabel.padding = Styles.s_ListLabel.padding;
|
||||
Styles.s_ListSelectedLabel.margin = Styles.s_ListLabel.margin;
|
||||
|
||||
Styles.s_ListLabelToggle = new GUIStyle(EditorStyles.toggle);
|
||||
Styles.s_ListLabelToggle.border = Styles.s_ListLabel.border;
|
||||
Styles.s_ListLabelToggle.padding = Styles.s_ListLabel.padding;
|
||||
Styles.s_ListLabelToggle.margin = Styles.s_ListLabel.margin;
|
||||
|
||||
Styles.s_FeatureSettings = new GUIStyle(Styles.s_SelectionStyle);
|
||||
Styles.s_FeatureSettings.alignment = TextAnchor.MiddleRight;
|
||||
Styles.s_FeatureSettings.border = new RectOffset(2, 2, 0, 0);
|
||||
Styles.s_FeatureSettings.padding = new RectOffset(0, 2, 5, 0);
|
||||
|
||||
Styles.s_Feature = new GUIStyle(Styles.s_SelectionStyle);
|
||||
Styles.s_Feature.border = new RectOffset(0, 0, 0, 0);
|
||||
Styles.s_Feature.padding = new RectOffset(5, 0, 0, 0);
|
||||
Styles.s_Feature.margin = new RectOffset(2, 2, 2, 2);
|
||||
}
|
||||
}
|
||||
|
||||
public static OpenXRFeatureEditor CreateFeatureEditor()
|
||||
{
|
||||
if (OpenXRSettings.Instance == null)
|
||||
return null;
|
||||
if (TypeCache.GetTypesWithAttribute<OpenXRFeatureAttribute>().Count > 0)
|
||||
return new OpenXRFeatureEditor();
|
||||
return null;
|
||||
}
|
||||
|
||||
internal void OnFeatureSetStateChanged(BuildTargetGroup buildTargetGroup)
|
||||
{
|
||||
if (activeBuildTarget != buildTargetGroup)
|
||||
return;
|
||||
|
||||
mustInitializeFeatures = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1996830aac95a1546a907ea607836dd8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEditor.XR.OpenXR.Features
|
||||
{
|
||||
/// <summary>
|
||||
/// Attribute used to describe a feature set to the OpenXR Editor components.
|
||||
/// </summary>
|
||||
[System.AttributeUsage(System.AttributeTargets.Class, Inherited = false, AllowMultiple = true)]
|
||||
sealed public class OpenXRFeatureSetAttribute : System.Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// The list of feature ids that this feature set can enable or disable.
|
||||
/// </summary>
|
||||
public string[] FeatureIds;
|
||||
|
||||
/// <summary>
|
||||
/// The string used to represent the feature set in the UI.
|
||||
/// </summary>
|
||||
public string UiName;
|
||||
|
||||
/// <summary>
|
||||
/// Description of the feature set.
|
||||
/// </summary>
|
||||
public string Description;
|
||||
|
||||
/// <summary>
|
||||
/// The id used to uniquely define this feature set. It is recommended to use reverse DNS naming for this id.
|
||||
/// </summary>
|
||||
public string FeatureSetId;
|
||||
|
||||
/// <summary>
|
||||
/// The list of build targets that this feature set supports.
|
||||
/// </summary>
|
||||
public BuildTargetGroup[] SupportedBuildTargets;
|
||||
|
||||
/// <summary>
|
||||
/// The list of feature ids that this feature set requires. The features in this list will be enabled (and the UI will not allow them to be disabled) whenever the feature set itself is enabled.
|
||||
///
|
||||
/// Feature Ids are a subset of <see cref="FeatureIds"/>. Any feature id in this list and not also in <see cref="FeatureIds"/> will be ignored.
|
||||
/// </summary>
|
||||
public string[] RequiredFeatureIds;
|
||||
|
||||
/// <summary>
|
||||
/// The list of feature ids that this feature set desires (but does not require). The features in this list will be enabled (but the UI will allow them to be disabled) whenever the feature set itself is enabled.
|
||||
///
|
||||
/// Feature Ids are a subset of <see cref="FeatureIds"/>. Any feature id in this list and not also in <see cref="FeatureIds"/> will be ignored.
|
||||
/// </summary>
|
||||
public string[] DefaultFeatureIds;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 697e81cea9b72024098b4297d2c31678
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,462 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEditor.Build;
|
||||
using UnityEditor.XR.OpenXR;
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.XR.OpenXR;
|
||||
using UnityEngine.XR.OpenXR.Features;
|
||||
|
||||
namespace UnityEditor.XR.OpenXR.Features
|
||||
{
|
||||
/// <summary>
|
||||
/// API for finding and managing feature sets for OpenXR.
|
||||
/// </summary>
|
||||
public static class OpenXRFeatureSetManager
|
||||
{
|
||||
[InitializeOnLoadMethod]
|
||||
static void InitializeOnLoad()
|
||||
{
|
||||
void OnFirstUpdate()
|
||||
{
|
||||
EditorApplication.update -= OnFirstUpdate;
|
||||
InitializeFeatureSets();
|
||||
}
|
||||
|
||||
OpenXRFeature.canSetFeatureDisabled = CanFeatureBeDisabled;
|
||||
EditorApplication.update += OnFirstUpdate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Description of a known (either built-in or found) feature set.
|
||||
/// </summary>
|
||||
public class FeatureSet
|
||||
{
|
||||
/// <summary>
|
||||
/// Toggles the enabled state for this feature. Impacts the effect of <see cref="OpenXRFeatureSetManager.SetFeaturesFromEnabledFeatureSets"/>.
|
||||
/// If you change this value, you must call <see cref="OpenXRFeatureSetManager.SetFeaturesFromEnabledFeatureSets"/> to reflect that change on the actual feature sets.
|
||||
/// </summary>
|
||||
public bool isEnabled;
|
||||
|
||||
/// <summary>
|
||||
/// The name that displays in the UI.
|
||||
/// </summary>
|
||||
public string name;
|
||||
|
||||
/// <summary>
|
||||
/// Description of this feature set.
|
||||
/// </summary>
|
||||
public string description;
|
||||
|
||||
/// <summary>
|
||||
/// The feature set id as defined in <see cref="OpenXRFeatureSetAttribute.FeatureSetId"/>.
|
||||
/// </summary>
|
||||
public string featureSetId;
|
||||
|
||||
/// <summary>
|
||||
/// The text to be shown with the <see cref="downloadLink" />.
|
||||
/// </summary>
|
||||
public string downloadText;
|
||||
|
||||
/// <summary>
|
||||
/// The URI string used to link to external documentation.
|
||||
/// </summary>
|
||||
public string downloadLink;
|
||||
|
||||
/// <summary>
|
||||
/// The set of features that this feature set menages.
|
||||
/// </summary>
|
||||
public string[] featureIds;
|
||||
|
||||
/// <summary>
|
||||
/// State that tracks whether this feature set is built in or was detected after the user installed it.
|
||||
/// </summary>
|
||||
public bool isInstalled;
|
||||
|
||||
/// <summary>
|
||||
/// The set of required features that this feature set manages.
|
||||
/// </summary>
|
||||
public string[] requiredFeatureIds;
|
||||
|
||||
/// <summary>
|
||||
/// The set of default features that this feature set manages.
|
||||
/// </summary>
|
||||
public string[] defaultFeatureIds;
|
||||
}
|
||||
|
||||
internal class FeatureSetInfo : FeatureSet
|
||||
{
|
||||
public GUIContent uiName;
|
||||
|
||||
public GUIContent uiLongName;
|
||||
|
||||
public GUIContent uiDescription;
|
||||
|
||||
public GUIContent helpIcon;
|
||||
|
||||
/// <summary>
|
||||
/// Stores the previous known value of isEnabled as of the last call to `SetFeaturesFromEnabledFeatureSets`
|
||||
/// </summary>
|
||||
public bool wasEnabled;
|
||||
}
|
||||
|
||||
static Dictionary<BuildTargetGroup, List<FeatureSetInfo>> s_AllFeatureSets = null;
|
||||
|
||||
struct FeatureSetState
|
||||
{
|
||||
public HashSet<string> featureSetFeatureIds;
|
||||
public HashSet<string> requiredToEnabledFeatureIds;
|
||||
public HashSet<string> requiredToDisabledFeatureIds;
|
||||
public HashSet<string> defaultToEnabledFeatureIds;
|
||||
}
|
||||
|
||||
static Dictionary<BuildTargetGroup, FeatureSetState> s_FeatureSetState = new Dictionary<BuildTargetGroup, FeatureSetState>();
|
||||
|
||||
/// <summary>
|
||||
/// Event called when the feature set state has been changed.
|
||||
/// </summary>
|
||||
internal static event Action<BuildTargetGroup> onFeatureSetStateChanged;
|
||||
|
||||
/// <summary>
|
||||
/// The current active build target. Used to handle callbacks from <see cref="OpenXRFeature.enabled" /> into
|
||||
/// <see cref="CanFeatureBeDisabled" /> to determine if a feature can currently be disabled.
|
||||
/// </summary>
|
||||
public static BuildTargetGroup activeBuildTarget = BuildTargetGroup.Unknown;
|
||||
|
||||
static void FillKnownFeatureSets(bool addTestFeatureSet = false)
|
||||
{
|
||||
BuildTargetGroup[] buildTargetGroups = new BuildTargetGroup[] { BuildTargetGroup.Standalone, BuildTargetGroup.WSA, BuildTargetGroup.Android };
|
||||
|
||||
if (addTestFeatureSet)
|
||||
{
|
||||
foreach (var buildTargetGroup in buildTargetGroups)
|
||||
{
|
||||
List<FeatureSetInfo> knownFeatureSets = new List<FeatureSetInfo>();
|
||||
if (addTestFeatureSet)
|
||||
{
|
||||
knownFeatureSets.Add(new FeatureSetInfo()
|
||||
{
|
||||
isEnabled = false,
|
||||
name = "Known Test",
|
||||
featureSetId = "com.unity.xr.test.featureset",
|
||||
description = "Known Test feature group.",
|
||||
downloadText = "Click here to go to the Unity main website.",
|
||||
downloadLink = Constants.k_DocumentationURL,
|
||||
uiName = new GUIContent("Known Test"),
|
||||
uiDescription = new GUIContent("Known Test feature group."),
|
||||
helpIcon = new GUIContent("", CommonContent.k_HelpIcon.image, "Click here to go to the Unity main website."),
|
||||
});
|
||||
}
|
||||
s_AllFeatureSets.Add(buildTargetGroup, knownFeatureSets);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var kvp in KnownFeatureSets.k_KnownFeatureSets)
|
||||
{
|
||||
List<FeatureSetInfo> knownFeatureSets;
|
||||
if (!s_AllFeatureSets.TryGetValue(kvp.Key, out knownFeatureSets))
|
||||
{
|
||||
knownFeatureSets = new List<FeatureSetInfo>();
|
||||
foreach (var featureSet in kvp.Value)
|
||||
{
|
||||
knownFeatureSets.Add(new FeatureSetInfo()
|
||||
{
|
||||
isEnabled = false,
|
||||
name = featureSet.name,
|
||||
featureSetId = featureSet.featureSetId,
|
||||
description = featureSet.description,
|
||||
downloadText = featureSet.downloadText,
|
||||
downloadLink = featureSet.downloadLink,
|
||||
uiName = new GUIContent(featureSet.name),
|
||||
uiLongName = new GUIContent($"{featureSet.name} feature group"),
|
||||
uiDescription = new GUIContent(featureSet.description),
|
||||
helpIcon = new GUIContent("", CommonContent.k_HelpIcon.image, featureSet.downloadText),
|
||||
});
|
||||
}
|
||||
s_AllFeatureSets.Add(kvp.Key, knownFeatureSets);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes all currently known feature sets. This will do two initialization passes:
|
||||
///
|
||||
/// 1) Starts with all built in/known feature sets.
|
||||
/// 2) Queries the system for anything with an <see cref="OpenXRFeatureSetAttribute"/>
|
||||
/// defined on it and uses that to add/update the store of known feature sets.
|
||||
/// </summary>
|
||||
public static void InitializeFeatureSets()
|
||||
{
|
||||
InitializeFeatureSets(false);
|
||||
}
|
||||
|
||||
internal static void InitializeFeatureSets(bool addTestFeatureSet)
|
||||
{
|
||||
if (s_AllFeatureSets == null)
|
||||
s_AllFeatureSets = new Dictionary<BuildTargetGroup, List<FeatureSetInfo>>();
|
||||
|
||||
s_AllFeatureSets.Clear();
|
||||
|
||||
FillKnownFeatureSets(addTestFeatureSet);
|
||||
|
||||
var types = TypeCache.GetTypesWithAttribute<OpenXRFeatureSetAttribute>();
|
||||
foreach (var t in types)
|
||||
{
|
||||
var attrs = Attribute.GetCustomAttributes(t);
|
||||
foreach (var attr in attrs)
|
||||
{
|
||||
var featureSetAttr = attr as OpenXRFeatureSetAttribute;
|
||||
if (featureSetAttr == null)
|
||||
continue;
|
||||
|
||||
if (!addTestFeatureSet && featureSetAttr.FeatureSetId.Contains("com.unity.xr.test.featureset"))
|
||||
continue;
|
||||
|
||||
foreach (var buildTargetGroup in featureSetAttr.SupportedBuildTargets)
|
||||
{
|
||||
var key = buildTargetGroup;
|
||||
if (!s_AllFeatureSets.ContainsKey(key))
|
||||
{
|
||||
s_AllFeatureSets.Add(key, new List<FeatureSetInfo>());
|
||||
}
|
||||
|
||||
var isEnabled = OpenXREditorSettings.Instance.IsFeatureSetSelected(buildTargetGroup, featureSetAttr.FeatureSetId);
|
||||
var newFeatureSet = new FeatureSetInfo()
|
||||
{
|
||||
isEnabled = isEnabled,
|
||||
wasEnabled = isEnabled,
|
||||
name = featureSetAttr.UiName,
|
||||
description = featureSetAttr.Description,
|
||||
featureSetId = featureSetAttr.FeatureSetId,
|
||||
downloadText = "",
|
||||
downloadLink = "",
|
||||
featureIds = featureSetAttr.FeatureIds,
|
||||
requiredFeatureIds = featureSetAttr.RequiredFeatureIds,
|
||||
defaultFeatureIds = featureSetAttr.DefaultFeatureIds,
|
||||
isInstalled = true,
|
||||
uiName = new GUIContent(featureSetAttr.UiName),
|
||||
uiLongName = new GUIContent($"{featureSetAttr.UiName} feature group"),
|
||||
uiDescription = new GUIContent(featureSetAttr.Description),
|
||||
helpIcon = String.IsNullOrEmpty(featureSetAttr.Description) ? null : new GUIContent("", CommonContent.k_HelpIcon.image, featureSetAttr.Description),
|
||||
};
|
||||
|
||||
bool foundFeatureSet = false;
|
||||
var featureSets = s_AllFeatureSets[key];
|
||||
for (int i = 0; i < featureSets.Count; i++)
|
||||
{
|
||||
if (String.Compare(featureSets[i].featureSetId, newFeatureSet.featureSetId, true) == 0)
|
||||
{
|
||||
foundFeatureSet = true;
|
||||
featureSets[i] = newFeatureSet;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundFeatureSet)
|
||||
featureSets.Add(newFeatureSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var buildTargetGroups = Enum.GetValues(typeof(BuildTargetGroup));
|
||||
foreach (BuildTargetGroup buildTargetGroup in buildTargetGroups)
|
||||
{
|
||||
FeatureSetState fsi;
|
||||
if (!s_FeatureSetState.TryGetValue(buildTargetGroup, out fsi))
|
||||
{
|
||||
fsi = new FeatureSetState();
|
||||
fsi.featureSetFeatureIds = new HashSet<string>();
|
||||
fsi.requiredToEnabledFeatureIds = new HashSet<string>();
|
||||
fsi.requiredToDisabledFeatureIds = new HashSet<string>();
|
||||
fsi.defaultToEnabledFeatureIds = new HashSet<string>();
|
||||
|
||||
s_FeatureSetState.Add(buildTargetGroup, fsi);
|
||||
}
|
||||
|
||||
SetFeaturesFromEnabledFeatureSets(buildTargetGroup);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the list of all <see cref="FeatureSet"/> for the given build target group.
|
||||
/// </summary>
|
||||
/// <param name="buildTargetGroup">The build target group to find the feature sets for.</param>
|
||||
/// <returns>List of <see cref="FeatureSet"/> or null if there is nothing that matches the given input.</returns>
|
||||
public static List<FeatureSet> FeatureSetsForBuildTarget(BuildTargetGroup buildTargetGroup)
|
||||
{
|
||||
return OpenXRFeatureSetManager.FeatureSetInfosForBuildTarget(buildTargetGroup).Select((fi) => fi as FeatureSet).ToList();
|
||||
}
|
||||
|
||||
internal static List<FeatureSetInfo> FeatureSetInfosForBuildTarget(BuildTargetGroup buildTargetGroup)
|
||||
{
|
||||
List<FeatureSetInfo> ret = new List<FeatureSetInfo>();
|
||||
HashSet<FeatureSetInfo> featureSetsForBuildTargetGroup = new HashSet<FeatureSetInfo>();
|
||||
|
||||
if (s_AllFeatureSets == null)
|
||||
InitializeFeatureSets();
|
||||
|
||||
if (s_AllFeatureSets == null)
|
||||
return ret;
|
||||
|
||||
foreach (var key in s_AllFeatureSets.Keys)
|
||||
{
|
||||
if (key == buildTargetGroup)
|
||||
{
|
||||
featureSetsForBuildTargetGroup.UnionWith(s_AllFeatureSets[key]);
|
||||
}
|
||||
}
|
||||
|
||||
ret.AddRange(featureSetsForBuildTargetGroup);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a specific <see cref="FeatureSet"/> instance that matches the input.
|
||||
/// </summary>
|
||||
/// <param name="buildTargetGroup">The build target group this feature set supports.</param>
|
||||
/// <param name="featureSetId">The feature set id for the specific feature set being requested.</param>
|
||||
/// <returns>The matching <see cref="FeatureSet"/> or null.</returns>
|
||||
public static FeatureSet GetFeatureSetWithId(BuildTargetGroup buildTargetGroup, string featureSetId)
|
||||
{
|
||||
return GetFeatureSetInfoWithId(buildTargetGroup, featureSetId) as FeatureSet;
|
||||
}
|
||||
|
||||
internal static FeatureSetInfo GetFeatureSetInfoWithId(BuildTargetGroup buildTargetGroup, string featureSetId)
|
||||
{
|
||||
var featureSets = FeatureSetInfosForBuildTarget(buildTargetGroup);
|
||||
if (featureSets != null)
|
||||
{
|
||||
foreach (var featureSet in featureSets)
|
||||
{
|
||||
if (String.Compare(featureSet.featureSetId, featureSetId, true) == 0)
|
||||
return featureSet;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given the current enabled state of the feature sets that match for a build target group, enable and disable the features associated with
|
||||
/// each feature set. Features that overlap sets of varying enabled states will maintain their enabled setting.
|
||||
/// </summary>
|
||||
/// <param name="buildTargetGroup">The build target group to process features sets for.</param>
|
||||
public static void SetFeaturesFromEnabledFeatureSets(BuildTargetGroup buildTargetGroup)
|
||||
{
|
||||
var extInfo = FeatureHelpersInternal.GetAllFeatureInfo(buildTargetGroup);
|
||||
SetFeaturesFromEnabledFeatureSets(buildTargetGroup, extInfo);
|
||||
}
|
||||
|
||||
internal static void SetFeaturesFromEnabledFeatureSets(BuildTargetGroup buildTargetGroup, FeatureHelpersInternal.AllFeatureInfo extInfo)
|
||||
{
|
||||
var featureSets = FeatureSetInfosForBuildTarget(buildTargetGroup);
|
||||
|
||||
var fsi = s_FeatureSetState[buildTargetGroup];
|
||||
|
||||
fsi.featureSetFeatureIds.Clear();
|
||||
foreach (var featureSet in featureSets)
|
||||
{
|
||||
if (featureSet.featureIds != null)
|
||||
fsi.featureSetFeatureIds.UnionWith(featureSet.featureIds);
|
||||
}
|
||||
|
||||
fsi.featureSetFeatureIds.Clear();
|
||||
fsi.requiredToEnabledFeatureIds.Clear();
|
||||
fsi.requiredToDisabledFeatureIds.Clear();
|
||||
fsi.defaultToEnabledFeatureIds.Clear();
|
||||
|
||||
// Update the selected feature set states first
|
||||
foreach (var featureSet in featureSets)
|
||||
{
|
||||
if (featureSet.featureIds == null)
|
||||
continue;
|
||||
|
||||
OpenXREditorSettings.Instance.SetFeatureSetSelected(buildTargetGroup, featureSet.featureSetId, featureSet.isEnabled);
|
||||
}
|
||||
|
||||
foreach (var featureSet in featureSets)
|
||||
{
|
||||
if (featureSet.featureIds == null)
|
||||
continue;
|
||||
|
||||
if (featureSet.isEnabled && featureSet.requiredFeatureIds != null)
|
||||
{
|
||||
fsi.requiredToEnabledFeatureIds.UnionWith(featureSet.requiredFeatureIds);
|
||||
}
|
||||
|
||||
if (featureSet.isEnabled != featureSet.wasEnabled)
|
||||
{
|
||||
if (featureSet.isEnabled && featureSet.defaultFeatureIds != null)
|
||||
{
|
||||
fsi.defaultToEnabledFeatureIds.UnionWith(featureSet.defaultFeatureIds);
|
||||
}
|
||||
else if (!featureSet.isEnabled && featureSet.requiredFeatureIds != null)
|
||||
{
|
||||
fsi.requiredToDisabledFeatureIds.UnionWith(featureSet.requiredFeatureIds);
|
||||
}
|
||||
|
||||
featureSet.wasEnabled = featureSet.isEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var ext in extInfo.Features)
|
||||
{
|
||||
if (ext.Feature.enabled && fsi.requiredToDisabledFeatureIds.Contains(ext.Attribute.FeatureId))
|
||||
ext.Feature.enabled = false;
|
||||
|
||||
if (!ext.Feature.enabled && fsi.requiredToEnabledFeatureIds.Contains(ext.Attribute.FeatureId))
|
||||
ext.Feature.enabled = true;
|
||||
|
||||
if (!ext.Feature.enabled && fsi.defaultToEnabledFeatureIds.Contains(ext.Attribute.FeatureId))
|
||||
ext.Feature.enabled = true;
|
||||
}
|
||||
|
||||
s_FeatureSetState[buildTargetGroup] = fsi;
|
||||
|
||||
onFeatureSetStateChanged?.Invoke(buildTargetGroup);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tell the user if the feature with passed in feature id can be disabled.
|
||||
///
|
||||
/// Uses the currently set <see cref="activeBuildTarget" /> to determine
|
||||
/// the BuildTargetGroup to use for checking against. If this value is not set, or
|
||||
/// set for a different build target than expected then return value may be incorrect.
|
||||
/// </summary>
|
||||
/// <param name="featureId">The feature id of the feature to check.</param>
|
||||
/// <returns>True if currently required by some feature set, false otherwise.</returns>
|
||||
internal static bool CanFeatureBeDisabled(string featureId)
|
||||
{
|
||||
return CanFeatureBeDisabled(featureId, activeBuildTarget);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tell the user if the feature with passed in feature id can be disabled based on the build target group.
|
||||
/// </summary>
|
||||
/// <param name="featureId">The feature id of the feature to check.</param>
|
||||
/// <param name="buildTargetGroup">The build target group whose feature sets you are checking against.</param>
|
||||
/// <returns>True if currently required by some feature set, false otherwise.</returns>
|
||||
public static bool CanFeatureBeDisabled(string featureId, BuildTargetGroup buildTargetGroup)
|
||||
{
|
||||
if (!s_FeatureSetState.ContainsKey(buildTargetGroup))
|
||||
return true;
|
||||
|
||||
var fsi = s_FeatureSetState[buildTargetGroup];
|
||||
return !fsi.requiredToEnabledFeatureIds.Contains(featureId);
|
||||
}
|
||||
|
||||
internal static bool IsKnownFeatureSet(BuildTargetGroup buildTargetGroup, string featureSetId)
|
||||
{
|
||||
if (!KnownFeatureSets.k_KnownFeatureSets.ContainsKey(buildTargetGroup))
|
||||
return false;
|
||||
|
||||
var featureSets = KnownFeatureSets.k_KnownFeatureSets[buildTargetGroup].
|
||||
Where((fs) => fs.featureSetId == featureSetId);
|
||||
|
||||
return featureSets.Any();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 011454f4f10d7894fa63c7fcbdf46552
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,170 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.XR.OpenXR;
|
||||
using UnityEngine.XR.OpenXR.Features;
|
||||
|
||||
using UnityEditor;
|
||||
|
||||
namespace UnityEditor.XR.OpenXR.Features
|
||||
{
|
||||
internal class OpenXRFeatureSettingsEditor : EditorWindow
|
||||
{
|
||||
struct Styles
|
||||
{
|
||||
public static GUIStyle s_WrapTextLabel;
|
||||
}
|
||||
|
||||
struct Content
|
||||
{
|
||||
public static GUIContent k_NoSelectionTitleContent = new GUIContent("OpenXR Feature Settings");
|
||||
public static GUIContent k_NoSelectionHelpMsg = new GUIContent("There is no current feature selected for the this build target. Go to Player Settings->XR Plug-in Management->OpenXR to select a settings for a feature to edit.");
|
||||
}
|
||||
|
||||
|
||||
static void InitStyles()
|
||||
{
|
||||
if (Styles.s_WrapTextLabel == null)
|
||||
{
|
||||
Styles.s_WrapTextLabel = new GUIStyle(EditorStyles.label);
|
||||
Styles.s_WrapTextLabel.wordWrap = true;
|
||||
}
|
||||
}
|
||||
|
||||
BuildTargetGroup activeBuildTarget;
|
||||
string activeFeatureId = String.Empty;
|
||||
|
||||
Editor activeItemEditor = null;
|
||||
OpenXRFeature featureToEdit = null;
|
||||
OpenXRFeatureAttribute featureAttr = null;
|
||||
GUIContent uiName;
|
||||
GUIContent categoryName;
|
||||
GUIContent partner;
|
||||
GUIContent version;
|
||||
string documentationLink;
|
||||
bool itemDidChange = false;
|
||||
|
||||
internal BuildTargetGroup ActiveBuildTarget
|
||||
{
|
||||
get => activeBuildTarget;
|
||||
set
|
||||
{
|
||||
if (value != activeBuildTarget)
|
||||
{
|
||||
activeBuildTarget = value;
|
||||
itemDidChange = true;
|
||||
Repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal string ActiveItem
|
||||
{
|
||||
get => activeFeatureId;
|
||||
set
|
||||
{
|
||||
if (String.Compare(value, activeFeatureId, true) != 0)
|
||||
{
|
||||
activeFeatureId = value;
|
||||
itemDidChange = true;
|
||||
Repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void InitActiveItemInfo()
|
||||
{
|
||||
if (!itemDidChange)
|
||||
return;
|
||||
|
||||
itemDidChange = false;
|
||||
|
||||
featureToEdit = null;
|
||||
titleContent = Content.k_NoSelectionTitleContent;
|
||||
activeItemEditor = null;
|
||||
categoryName = null;
|
||||
partner = null;
|
||||
version = null;
|
||||
documentationLink = null;
|
||||
|
||||
featureToEdit = FeatureHelpers.GetFeatureWithIdForBuildTarget(activeBuildTarget, activeFeatureId);
|
||||
if (featureToEdit != null)
|
||||
{
|
||||
foreach (Attribute attr in Attribute.GetCustomAttributes(featureToEdit.GetType()))
|
||||
{
|
||||
if (attr is OpenXRFeatureAttribute)
|
||||
{
|
||||
featureAttr = (OpenXRFeatureAttribute)attr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (featureAttr != null)
|
||||
{
|
||||
activeItemEditor = UnityEditor.Editor.CreateEditor(featureToEdit);
|
||||
uiName = new GUIContent(featureAttr.UiName);
|
||||
categoryName = new GUIContent($"Category: {featureAttr.Category.ToString()}");
|
||||
partner = new GUIContent($"Author: {featureAttr.Company}");
|
||||
version = new GUIContent($"Version: {featureAttr.Version}");
|
||||
documentationLink = featureAttr.InternalDocumentationLink;
|
||||
titleContent = uiName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnGUI()
|
||||
{
|
||||
InitStyles();
|
||||
InitActiveItemInfo();
|
||||
|
||||
if (featureToEdit == null)
|
||||
{
|
||||
titleContent = Content.k_NoSelectionTitleContent;
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.BeginVertical();
|
||||
EditorGUILayout.LabelField(Content.k_NoSelectionHelpMsg, Styles.s_WrapTextLabel);
|
||||
EditorGUILayout.EndVertical();
|
||||
return;
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.BeginVertical();
|
||||
EditorGUILayout.LabelField("Feature Information", EditorStyles.boldLabel);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUI.indentLevel += 1;
|
||||
EditorGUILayout.LabelField(categoryName);
|
||||
EditorGUILayout.LabelField(partner);
|
||||
EditorGUILayout.LabelField(version);
|
||||
|
||||
EditorGUI.indentLevel -= 1;
|
||||
EditorGUILayout.EndVertical();
|
||||
|
||||
if (activeItemEditor != null)
|
||||
{
|
||||
EditorGUILayout.BeginVertical();
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("Feature Settings", EditorStyles.boldLabel);
|
||||
EditorGUILayout.Space();
|
||||
EditorGUI.indentLevel += 1;
|
||||
activeItemEditor.OnInspectorGUI();
|
||||
EditorGUI.indentLevel -= 1;
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
if (!String.IsNullOrEmpty(documentationLink))
|
||||
{
|
||||
if (GUILayout.Button("Documentation", EditorStyles.linkLabel))
|
||||
{
|
||||
UnityEngine.Application.OpenURL(documentationLink);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 15f4013cee477754ab28c0421e6c5dc2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
|
||||
using UnityEditor;
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.XR.OpenXR.Features
|
||||
{
|
||||
static class CommonContent
|
||||
{
|
||||
public static readonly GUIContent k_Download = new GUIContent("Download");
|
||||
public static readonly GUIContent k_WarningIcon = EditorGUIUtility.IconContent("Warning@2x");
|
||||
public static readonly GUIContent k_ErrorIcon = EditorGUIUtility.IconContent("Error@2x");
|
||||
public static readonly GUIContent k_HelpIcon = EditorGUIUtility.IconContent("_Help@2x");
|
||||
|
||||
public static readonly GUIContent k_Validation = new GUIContent("Your project has some settings that are incompatible with OpenXR. Click to open the project validator.");
|
||||
public static readonly GUIContent k_ValidationErrorIcon = new GUIContent("", CommonContent.k_ErrorIcon.image, k_Validation.text);
|
||||
public static readonly GUIContent k_ValidationWarningIcon = new GUIContent("", CommonContent.k_WarningIcon.image, k_Validation.text);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 58a54d90b4a70764b8711fcc8acc4a9e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,192 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEditor.XR.Management;
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.XR.OpenXR;
|
||||
using UnityEngine.XR.OpenXR.Features;
|
||||
|
||||
namespace UnityEditor.XR.OpenXR.Features
|
||||
{
|
||||
static class Content
|
||||
{
|
||||
public const float k_IconSize = 16.0f;
|
||||
|
||||
public static readonly GUIContent k_LoaderName = new GUIContent("OpenXR");
|
||||
public static readonly GUIContent k_OpenXRHelp = new GUIContent("You may need to configure additional settings for OpenXR to enable features and interactions for different runtimes.");
|
||||
public static readonly GUIContent k_OpenXRHelpIcon = new GUIContent("", CommonContent.k_HelpIcon.image, k_OpenXRHelp.text);
|
||||
}
|
||||
|
||||
|
||||
[XRCustomLoaderUI("UnityEngine.XR.OpenXR.OpenXRLoader", BuildTargetGroup.Standalone)]
|
||||
[XRCustomLoaderUI("UnityEngine.XR.OpenXR.OpenXRLoader", BuildTargetGroup.Android)]
|
||||
[XRCustomLoaderUI("UnityEngine.XR.OpenXR.OpenXRLoader", BuildTargetGroup.WSA)]
|
||||
internal class OpenXRLoaderUI : IXRCustomLoaderUI
|
||||
{
|
||||
protected bool shouldApplyFeatureSetChanges = false;
|
||||
|
||||
protected List<OpenXRFeatureSetManager.FeatureSetInfo> featureSets { get; set; }
|
||||
protected float renderLineHeight = 0;
|
||||
|
||||
private List<OpenXRFeature.ValidationRule> _validationRules = new List<OpenXRFeature.ValidationRule>();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsLoaderEnabled { get; set; }
|
||||
|
||||
public string[] IncompatibleLoaders => new string[]
|
||||
{
|
||||
"UnityEngine.XR.WindowsMR.WindowsMRLoader",
|
||||
"Unity.XR.Oculus.OculusLoader",
|
||||
};
|
||||
|
||||
/// <inheritdoc/>
|
||||
public float RequiredRenderHeight { get; protected set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual void SetRenderedLineHeight(float height)
|
||||
{
|
||||
renderLineHeight = height;
|
||||
RequiredRenderHeight = height;
|
||||
|
||||
if (IsLoaderEnabled && featureSets != null)
|
||||
{
|
||||
RequiredRenderHeight += featureSets.Count * height;
|
||||
}
|
||||
}
|
||||
|
||||
BuildTargetGroup activeBuildTargetGroup;
|
||||
/// <inheritdoc/>
|
||||
public BuildTargetGroup ActiveBuildTargetGroup
|
||||
{
|
||||
get => activeBuildTargetGroup;
|
||||
set
|
||||
{
|
||||
if (value != activeBuildTargetGroup)
|
||||
{
|
||||
activeBuildTargetGroup = value;
|
||||
this.featureSets = OpenXRFeatureSetManager.FeatureSetInfosForBuildTarget(activeBuildTargetGroup);
|
||||
foreach (var featureSet in this.featureSets)
|
||||
{
|
||||
featureSet.isEnabled = OpenXREditorSettings.Instance.IsFeatureSetSelected(activeBuildTargetGroup, featureSet.featureSetId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected Rect CalculateRectForContent(float xMin, float yMin, GUIStyle style, GUIContent content)
|
||||
{
|
||||
var size = style.CalcSize(content);
|
||||
var rect = new Rect();
|
||||
rect.xMin = xMin;
|
||||
rect.yMin = yMin;
|
||||
rect.width = size.x;
|
||||
rect.height = renderLineHeight;
|
||||
return rect;
|
||||
}
|
||||
|
||||
void RenderFeatureSet(ref OpenXRFeatureSetManager.FeatureSetInfo featureSet, Rect rect)
|
||||
{
|
||||
float xMin = rect.xMin;
|
||||
float yMin = rect.yMin;
|
||||
|
||||
var labelRect = CalculateRectForContent(xMin, yMin, EditorStyles.toggle, featureSet.uiLongName);
|
||||
labelRect.width += renderLineHeight;
|
||||
|
||||
EditorGUI.BeginDisabledGroup(!featureSet.isInstalled);
|
||||
var newToggled = EditorGUI.ToggleLeft(labelRect, featureSet.uiLongName, featureSet.isEnabled);
|
||||
if (newToggled != featureSet.isEnabled)
|
||||
{
|
||||
featureSet.isEnabled = newToggled;
|
||||
shouldApplyFeatureSetChanges = true;
|
||||
}
|
||||
|
||||
EditorGUI.EndDisabledGroup();
|
||||
xMin = labelRect.xMax + 1;
|
||||
|
||||
if (featureSet.helpIcon != null)
|
||||
{
|
||||
var iconRect = CalculateRectForContent(xMin, yMin, EditorStyles.label, CommonContent.k_HelpIcon);
|
||||
|
||||
if (GUI.Button(iconRect, featureSet.helpIcon, EditorStyles.label))
|
||||
{
|
||||
if (!String.IsNullOrEmpty(featureSet.downloadLink)) UnityEngine.Application.OpenURL(featureSet.downloadLink);
|
||||
}
|
||||
xMin = iconRect.xMax + 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual void OnGUI(Rect rect)
|
||||
{
|
||||
// If this editor is rendering then the make sure the project validator is showing
|
||||
// issues for the same build target.
|
||||
OpenXRProjectValidationRulesSetup.SetSelectedBuildTargetGroup(activeBuildTargetGroup);
|
||||
|
||||
Vector2 oldIconSize = EditorGUIUtility.GetIconSize();
|
||||
EditorGUIUtility.SetIconSize(new Vector2(Content.k_IconSize, Content.k_IconSize));
|
||||
shouldApplyFeatureSetChanges = false;
|
||||
|
||||
float xMin = rect.xMin;
|
||||
float yMin = rect.yMin;
|
||||
|
||||
var labelRect = CalculateRectForContent(xMin, yMin, EditorStyles.toggle, Content.k_LoaderName);
|
||||
var newToggled = EditorGUI.ToggleLeft(labelRect, Content.k_LoaderName, IsLoaderEnabled);
|
||||
if (newToggled != IsLoaderEnabled)
|
||||
{
|
||||
IsLoaderEnabled = newToggled;
|
||||
}
|
||||
|
||||
xMin = labelRect.xMax + 1.0f;
|
||||
|
||||
if (IsLoaderEnabled)
|
||||
{
|
||||
var iconRect = CalculateRectForContent(xMin, yMin, EditorStyles.label, Content.k_OpenXRHelpIcon);
|
||||
EditorGUI.LabelField(iconRect, Content.k_OpenXRHelpIcon);
|
||||
xMin += Content.k_IconSize + 1.0f;
|
||||
|
||||
OpenXRProjectValidation.GetCurrentValidationIssues(_validationRules, activeBuildTargetGroup);
|
||||
if (_validationRules.Count > 0)
|
||||
{
|
||||
bool anyErrors = _validationRules.Any(rule => rule.error);
|
||||
GUIContent icon = anyErrors ? CommonContent.k_ValidationErrorIcon : CommonContent.k_ValidationWarningIcon;
|
||||
iconRect = CalculateRectForContent(xMin, yMin, EditorStyles.label, icon);
|
||||
|
||||
if (GUI.Button(iconRect, icon, EditorStyles.label))
|
||||
{
|
||||
OpenXRProjectValidationRulesSetup.ShowWindow(activeBuildTargetGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
xMin = rect.xMin;
|
||||
yMin += renderLineHeight;
|
||||
Rect featureSetRect = new Rect(xMin, yMin, rect.width, renderLineHeight);
|
||||
|
||||
if (featureSets != null && featureSets.Count > 0 && IsLoaderEnabled)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
for (int i = 0; i < featureSets.Count; i++)
|
||||
{
|
||||
var featureSet = featureSets[i];
|
||||
RenderFeatureSet(ref featureSet, featureSetRect);
|
||||
featureSets[i] = featureSet;
|
||||
yMin += renderLineHeight;
|
||||
featureSetRect.yMin = yMin;
|
||||
featureSetRect.height = renderLineHeight;
|
||||
}
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
if (shouldApplyFeatureSetChanges)
|
||||
{
|
||||
OpenXRFeatureSetManager.SetFeaturesFromEnabledFeatureSets(ActiveBuildTargetGroup);
|
||||
shouldApplyFeatureSetChanges = false;
|
||||
}
|
||||
|
||||
EditorGUIUtility.SetIconSize(oldIconSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5a8992ed9d345dc4b9e452a96b131ebd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
39
Packages/com.unity.xr.openxr/Editor/OpenXRBuildProcessor.cs
Normal file
39
Packages/com.unity.xr.openxr/Editor/OpenXRBuildProcessor.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using UnityEditor.Build.Reporting;
|
||||
using UnityEditor.XR.Management;
|
||||
using UnityEditor.XR.OpenXR.Features;
|
||||
using UnityEngine.XR.OpenXR;
|
||||
|
||||
namespace UnityEditor.XR.OpenXR
|
||||
{
|
||||
internal class OpenXRBuildProcessor : XRBuildHelper<OpenXRSettings>
|
||||
{
|
||||
private const string kRequestAdditionalVulkanGraphicsQueue = "xr-request-additional-vulkan-graphics-queue";
|
||||
|
||||
public override string BuildSettingsKey => Constants.k_SettingsKey;
|
||||
|
||||
private readonly BootConfigBuilder _bootConfigBuilder = new BootConfigBuilder();
|
||||
|
||||
public override UnityEngine.Object SettingsForBuildTargetGroup(BuildTargetGroup buildTargetGroup)
|
||||
{
|
||||
EditorBuildSettings.TryGetConfigObject(Constants.k_SettingsKey, out OpenXRPackageSettings packageSettings);
|
||||
if (packageSettings == null)
|
||||
return null;
|
||||
return packageSettings.GetSettingsForBuildTargetGroup(buildTargetGroup);
|
||||
}
|
||||
|
||||
public override void OnPreprocessBuild(BuildReport report)
|
||||
{
|
||||
base.OnPreprocessBuild(report);
|
||||
|
||||
_bootConfigBuilder.ReadBootConfig(report);
|
||||
|
||||
#if UNITY_STANDALONE_WIN || UNITY_ANDROID
|
||||
var settings = OpenXREditorSettings.Instance;
|
||||
if (settings != null)
|
||||
{
|
||||
_bootConfigBuilder.SetBootConfigBoolean(kRequestAdditionalVulkanGraphicsQueue, settings.VulkanAdditionalGraphicsQueue);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2580abafffb1dc249ba2c1db66c66865
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
188
Packages/com.unity.xr.openxr/Editor/OpenXREditorSettings.cs
Normal file
188
Packages/com.unity.xr.openxr/Editor/OpenXREditorSettings.cs
Normal file
@@ -0,0 +1,188 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.XR.OpenXR
|
||||
{
|
||||
internal class OpenXREditorSettings : ScriptableObject, ISerializationCallbackReceiver
|
||||
{
|
||||
public static OpenXREditorSettings Instance => OpenXREditorSettings.GetInstance();
|
||||
|
||||
static OpenXREditorSettings s_Instance = null;
|
||||
static object s_Lock = new object();
|
||||
|
||||
static string GetAssetPathForComponents(string[] pathComponents)
|
||||
{
|
||||
if (pathComponents.Length <= 0)
|
||||
return null;
|
||||
|
||||
string path = "Assets";
|
||||
foreach (var pc in pathComponents)
|
||||
{
|
||||
string subFolder = Path.Combine(path, pc);
|
||||
bool shouldCreate = true;
|
||||
foreach (var f in AssetDatabase.GetSubFolders(path))
|
||||
{
|
||||
if (String.Compare(Path.GetFullPath(f), Path.GetFullPath(subFolder), true) == 0)
|
||||
{
|
||||
shouldCreate = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldCreate)
|
||||
AssetDatabase.CreateFolder(path, pc);
|
||||
path = subFolder;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
static OpenXREditorSettings CreateScriptableObjectInstance(string path)
|
||||
{
|
||||
ScriptableObject obj = ScriptableObject.CreateInstance(typeof(OpenXREditorSettings)) as ScriptableObject;
|
||||
if (obj != null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
string fileName = String.Format("OpenXR Editor Settings.asset");
|
||||
string targetPath = Path.Combine(path, fileName);
|
||||
AssetDatabase.CreateAsset(obj, targetPath);
|
||||
AssetDatabase.SaveAssets();
|
||||
return obj as OpenXREditorSettings;
|
||||
}
|
||||
}
|
||||
|
||||
Debug.LogError("Error attempting to create instance of OpenXR Editor Settings.");
|
||||
return null;
|
||||
}
|
||||
|
||||
static OpenXREditorSettings GetInstance()
|
||||
{
|
||||
if (s_Instance == null)
|
||||
{
|
||||
lock (s_Lock)
|
||||
{
|
||||
if (s_Instance == null)
|
||||
{
|
||||
string path = GetAssetPathForComponents(new string[] { "XR", "Settings" });
|
||||
var assetGuids = AssetDatabase.FindAssets($"t:{typeof(OpenXREditorSettings).Name}");
|
||||
foreach (var assetGuid in assetGuids)
|
||||
{
|
||||
var assetPath = AssetDatabase.GUIDToAssetPath(assetGuid);
|
||||
var asset = AssetDatabase.LoadAssetAtPath<OpenXREditorSettings>(assetPath);
|
||||
if (asset != null)
|
||||
{
|
||||
s_Instance = asset;
|
||||
}
|
||||
}
|
||||
|
||||
if (s_Instance == null)
|
||||
s_Instance = CreateScriptableObjectInstance(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return s_Instance;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
struct BuildTargetFeatureSets
|
||||
{
|
||||
public List<string> featureSets;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
List<BuildTargetGroup> Keys = new List<BuildTargetGroup>();
|
||||
|
||||
[SerializeField]
|
||||
List<BuildTargetFeatureSets> Values = new List<BuildTargetFeatureSets>();
|
||||
Dictionary<BuildTargetGroup, BuildTargetFeatureSets> selectedFeatureSets = new Dictionary<BuildTargetGroup, BuildTargetFeatureSets>();
|
||||
|
||||
|
||||
[SerializeField] bool m_vulkanAdditionalGraphicsQueue = false;
|
||||
|
||||
/// <summary>
|
||||
/// If enabled, when the application begins it will request an additional Vulkan graphics queue.
|
||||
/// </summary>
|
||||
public bool VulkanAdditionalGraphicsQueue
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_vulkanAdditionalGraphicsQueue;
|
||||
}
|
||||
set
|
||||
{
|
||||
m_vulkanAdditionalGraphicsQueue = value;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnBeforeSerialize()
|
||||
{
|
||||
Keys.Clear();
|
||||
Values.Clear();
|
||||
|
||||
foreach (var kv in selectedFeatureSets)
|
||||
{
|
||||
Keys.Add(kv.Key);
|
||||
Values.Add(kv.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnAfterDeserialize()
|
||||
{
|
||||
selectedFeatureSets = new Dictionary<BuildTargetGroup, BuildTargetFeatureSets>();
|
||||
for (int i = 0; i < Math.Min(Keys.Count, Values.Count); i++)
|
||||
{
|
||||
selectedFeatureSets.Add(Keys[i], Values[i]);
|
||||
}
|
||||
}
|
||||
|
||||
internal bool IsFeatureSetSelected(BuildTargetGroup buildTargetGroup, string featureSetId)
|
||||
{
|
||||
bool ret = false;
|
||||
|
||||
if (selectedFeatureSets.ContainsKey(buildTargetGroup))
|
||||
{
|
||||
ret = selectedFeatureSets[buildTargetGroup].featureSets.Contains(featureSetId);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the selected state of the given feature set
|
||||
/// </summary>
|
||||
/// <returns>True if the state was changed, false if not</returns>
|
||||
internal bool SetFeatureSetSelected(BuildTargetGroup buildTargetGroup, string featureSetId, bool selected)
|
||||
{
|
||||
var dirty = false;
|
||||
if (!selectedFeatureSets.ContainsKey(buildTargetGroup))
|
||||
{
|
||||
selectedFeatureSets.Add(buildTargetGroup, new BuildTargetFeatureSets() { featureSets = new List<string>() });
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
var featureSets = selectedFeatureSets[buildTargetGroup].featureSets;
|
||||
|
||||
if (selected && !featureSets.Contains(featureSetId))
|
||||
{
|
||||
featureSets.Add(featureSetId);
|
||||
dirty = true;
|
||||
}
|
||||
else if (!selected && featureSets.Contains(featureSetId))
|
||||
{
|
||||
featureSets.Remove(featureSetId);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
if (dirty)
|
||||
EditorUtility.SetDirty(this);
|
||||
|
||||
return dirty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 975057b4fdcfb8142b3080d19a5cc712
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,68 @@
|
||||
#if XR_MGMT_3_2_0_OR_NEWER
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.XR.OpenXR;
|
||||
using UnityEditor.XR.Management.Metadata;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.XR.OpenXR
|
||||
{
|
||||
public class OpenXRManagementSettings : IXRPackage
|
||||
{
|
||||
private class MyLoaderMetadata : IXRLoaderMetadata
|
||||
{
|
||||
public string loaderName { get; set; }
|
||||
public string loaderType { get; set; }
|
||||
public List<BuildTargetGroup> supportedBuildTargets { get; set; }
|
||||
}
|
||||
|
||||
private class MyPackageMetadata : IXRPackageMetadata
|
||||
{
|
||||
public string packageName { get; set; }
|
||||
public string packageId { get; set; }
|
||||
public string settingsType { get; set; }
|
||||
public List<IXRLoaderMetadata> loaderMetadata { get; set; }
|
||||
}
|
||||
|
||||
private static IXRPackageMetadata s_Metadata = new MyPackageMetadata()
|
||||
{
|
||||
packageName = "OpenXR XR Plugin",
|
||||
packageId = "com.unity.xr.openxr",
|
||||
settingsType = "UnityEditor.XR.OpenXR.OpenXRPackageSettings",
|
||||
loaderMetadata = new List<IXRLoaderMetadata>()
|
||||
{
|
||||
new MyLoaderMetadata()
|
||||
{
|
||||
loaderName = "OpenXR Loader",
|
||||
loaderType = "UnityEngine.XR.OpenXR.OpenXRLoader",
|
||||
supportedBuildTargets = new List<BuildTargetGroup>()
|
||||
{
|
||||
BuildTargetGroup.Standalone,
|
||||
BuildTargetGroup.Android,
|
||||
BuildTargetGroup.WSA
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
public bool PopulateNewSettingsInstance(ScriptableObject obj)
|
||||
{
|
||||
try
|
||||
{
|
||||
EditorBuildSettings.AddConfigObject(Constants.k_SettingsKey, obj, true);
|
||||
return true;
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Debug.Log($"Error adding new OpenXR Settings object to build settings.\n{ex.Message}");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public IXRPackageMetadata metadata => s_Metadata;
|
||||
|
||||
internal static string PackageId => s_Metadata.packageId;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5224b2f26b4da475c955288a10d0e51f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
202
Packages/com.unity.xr.openxr/Editor/OpenXRPackageSettings.cs
Normal file
202
Packages/com.unity.xr.openxr/Editor/OpenXRPackageSettings.cs
Normal file
@@ -0,0 +1,202 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UnityEditor.XR.OpenXR.Features;
|
||||
using UnityEditor.Build;
|
||||
using UnityEditor.Experimental;
|
||||
using UnityEngine;
|
||||
using UnityEngine.XR.Management;
|
||||
using UnityEngine.XR.OpenXR;
|
||||
using UnityEngine.XR.OpenXR.Features;
|
||||
|
||||
namespace UnityEditor.XR.OpenXR
|
||||
{
|
||||
[XRConfigurationData("OpenXR", Constants.k_SettingsKey)]
|
||||
internal class OpenXRPackageSettings : ScriptableObject, ISerializationCallbackReceiver, IPackageSettings
|
||||
{
|
||||
[SerializeField]
|
||||
List<BuildTargetGroup> Keys = new List<BuildTargetGroup>();
|
||||
[SerializeField]
|
||||
List<OpenXRSettings> Values = new List<OpenXRSettings>();
|
||||
|
||||
Dictionary<BuildTargetGroup, OpenXRSettings> Settings = new Dictionary<BuildTargetGroup, OpenXRSettings>();
|
||||
|
||||
|
||||
public static OpenXRPackageSettings Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
OpenXRPackageSettings ret = null;
|
||||
EditorBuildSettings.TryGetConfigObject(Constants.k_SettingsKey, out ret);
|
||||
if (ret == null)
|
||||
{
|
||||
string path = OpenXRPackageSettingsAssetPath();
|
||||
if (!path.Equals(String.Empty))
|
||||
{
|
||||
ret = AssetDatabase.LoadAssetAtPath(path, typeof(OpenXRPackageSettings)) as OpenXRPackageSettings;
|
||||
if (ret != null)
|
||||
{
|
||||
EditorBuildSettings.AddConfigObject(Constants.k_SettingsKey, ret, true);
|
||||
}
|
||||
|
||||
// Can't modify EditorBuildSettings once a build has already started - it won't get picked up
|
||||
// Not sure how to gracefully fix this, for now fail the build so there are now surprises.
|
||||
if (BuildPipeline.isBuildingPlayer)
|
||||
throw new BuildFailedException(
|
||||
"OpenXR Settings found in project but not yet loaded. Please build again.");
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
public static OpenXRPackageSettings GetOrCreateInstance()
|
||||
{
|
||||
if (Instance != null)
|
||||
return Instance;
|
||||
|
||||
OpenXRPackageSettings settings = ScriptableObject.CreateInstance<OpenXRPackageSettings>();
|
||||
if (settings != null)
|
||||
{
|
||||
string path = OpenXRPackageSettingsAssetPath();
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
AssetDatabase.CreateAsset(settings, path);
|
||||
EditorBuildSettings.AddConfigObject(Constants.k_SettingsKey, settings, true);
|
||||
AssetDatabase.SaveAssets();
|
||||
}
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
|
||||
internal static readonly string s_PackageSettingsAssetName = "OpenXR Package Settings.asset";
|
||||
|
||||
internal static readonly string[] s_PackageSettingsDefaultSettingsPath = { "XR", "Settings" };
|
||||
|
||||
string IPackageSettings.PackageSettingsAssetPath()
|
||||
{
|
||||
return OpenXRPackageSettingsAssetPath();
|
||||
}
|
||||
|
||||
internal static string OpenXRPackageSettingsAssetPath()
|
||||
{
|
||||
return Path.Combine(GetAssetPathForComponents(s_PackageSettingsDefaultSettingsPath), s_PackageSettingsAssetName);
|
||||
}
|
||||
|
||||
internal static string GetAssetPathForComponents(string[] pathComponents, string root = "Assets")
|
||||
{
|
||||
if (pathComponents.Length <= 0)
|
||||
return null;
|
||||
|
||||
string path = root;
|
||||
foreach (var pc in pathComponents)
|
||||
{
|
||||
string subFolder = Path.Combine(path, pc);
|
||||
bool shouldCreate = true;
|
||||
foreach (var f in AssetDatabase.GetSubFolders(path))
|
||||
{
|
||||
if (String.Compare(Path.GetFullPath(f), Path.GetFullPath(subFolder), true) == 0)
|
||||
{
|
||||
shouldCreate = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldCreate)
|
||||
AssetDatabase.CreateFolder(path, pc);
|
||||
path = subFolder;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
public string GetActiveLoaderLibraryPath()
|
||||
{
|
||||
return OpenXRChooseRuntimeLibraries.GetLoaderLibraryPath();
|
||||
}
|
||||
|
||||
void IPackageSettings.RefreshFeatureSets()
|
||||
{
|
||||
OpenXRFeatureSetManager.InitializeFeatureSets();
|
||||
}
|
||||
|
||||
private bool IsValidBuildTargetGroup(BuildTargetGroup buildTargetGroup) =>
|
||||
buildTargetGroup == BuildTargetGroup.Standalone ||
|
||||
Enum.GetValues(typeof(BuildTarget)).Cast<BuildTarget>().Any(bt =>
|
||||
{
|
||||
var group = BuildPipeline.GetBuildTargetGroup(bt);
|
||||
return group == buildTargetGroup && BuildPipeline.IsBuildTargetSupported(group, bt);
|
||||
});
|
||||
|
||||
public OpenXRSettings GetSettingsForBuildTargetGroup(BuildTargetGroup buildTargetGroup)
|
||||
{
|
||||
OpenXRSettings ret = null;
|
||||
Settings.TryGetValue(buildTargetGroup, out ret);
|
||||
if (ret == null)
|
||||
{
|
||||
if (!IsValidBuildTargetGroup(buildTargetGroup))
|
||||
return null;
|
||||
|
||||
ret = ScriptableObject.CreateInstance<OpenXRSettings>();
|
||||
if (Settings.ContainsKey(buildTargetGroup))
|
||||
{
|
||||
Settings[buildTargetGroup] = ret;
|
||||
}
|
||||
else
|
||||
{
|
||||
Settings.Add(buildTargetGroup, ret);
|
||||
}
|
||||
|
||||
ret.name = buildTargetGroup.ToString();
|
||||
|
||||
AssetDatabase.AddObjectToAsset(ret, this);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>Pre-serialization action</summary>
|
||||
public void OnBeforeSerialize()
|
||||
{
|
||||
Keys.Clear();
|
||||
Values.Clear();
|
||||
|
||||
foreach (var kv in Settings)
|
||||
{
|
||||
Keys.Add(kv.Key);
|
||||
Values.Add(kv.Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Post-deserialization action</summary>
|
||||
public void OnAfterDeserialize()
|
||||
{
|
||||
Settings = new Dictionary<BuildTargetGroup, OpenXRSettings>();
|
||||
for (int i = 0; i < Math.Min(Keys.Count, Values.Count); i++)
|
||||
{
|
||||
Settings.Add(Keys[i], Values[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return all features of the given type from all available build target groups.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Feature type to retrieve</typeparam>
|
||||
/// <returns>All features and their build target group that match the given feature type.</returns>
|
||||
public IEnumerable<(BuildTargetGroup buildTargetGroup, T feature)> GetFeatures<T>() where T : OpenXRFeature
|
||||
{
|
||||
foreach (var kv in Settings)
|
||||
{
|
||||
if (kv.Value.features == null)
|
||||
continue;
|
||||
|
||||
foreach (var feature in kv.Value.features)
|
||||
{
|
||||
if (feature is T featureT)
|
||||
yield return (kv.Key, featureT);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9f0ebc320a151d3408ea1e9fce54d40e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,227 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEditor.Build;
|
||||
using UnityEditor.Build.Reporting;
|
||||
using UnityEditor.Callbacks;
|
||||
using UnityEditor.PackageManager;
|
||||
using UnityEngine.XR.OpenXR;
|
||||
#if XR_COMPOSITION_LAYERS
|
||||
using UnityEngine.XR.OpenXR.Features.CompositionLayers;
|
||||
#endif
|
||||
using UnityEngine.XR.OpenXR.Features;
|
||||
using Unity.XR.CoreUtils.Editor;
|
||||
using UnityEditor.XR.Management;
|
||||
using UnityEngine.XR.Management;
|
||||
|
||||
|
||||
[assembly: InternalsVisibleTo("UnityEditor.XR.OpenXR.Tests")]
|
||||
namespace UnityEditor.XR.OpenXR
|
||||
{
|
||||
internal class OpenXRProjectValidationRulesSetup
|
||||
{
|
||||
static BuildTargetGroup[] s_BuildTargetGroups =
|
||||
((BuildTargetGroup[])Enum.GetValues(typeof(BuildTargetGroup))).Distinct().ToArray();
|
||||
|
||||
static BuildTargetGroup s_SelectedBuildTargetGroup = BuildTargetGroup.Unknown;
|
||||
|
||||
internal const string OpenXRProjectValidationSettingsPath = "Project/XR Plug-in Management/Project Validation";
|
||||
|
||||
[InitializeOnLoadMethod]
|
||||
static void OpenXRProjectValidationCheck()
|
||||
{
|
||||
UnityEditor.PackageManager.Events.registeredPackages += (packageRegistrationEventArgs) =>
|
||||
{
|
||||
// In the Player Settings UI we have to delay the call one frame to let OpenXRSettings constructor to get initialized
|
||||
EditorApplication.delayCall += () =>
|
||||
{
|
||||
if (HasXRPackageVersionChanged(packageRegistrationEventArgs))
|
||||
{
|
||||
ShowWindowIfIssuesExist();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
AddOpenXRValidationRules();
|
||||
}
|
||||
|
||||
private static bool HasXRPackageVersionChanged(PackageRegistrationEventArgs packageRegistrationEventArgs)
|
||||
{
|
||||
bool packageChanged = packageRegistrationEventArgs.changedTo.Any(p => p.name.Equals(OpenXRManagementSettings.PackageId));
|
||||
OpenXRSettings.Instance.versionChanged = packageChanged;
|
||||
return packageRegistrationEventArgs.added.Any(p => p.name.Equals(OpenXRManagementSettings.PackageId)) || packageChanged;
|
||||
}
|
||||
|
||||
private static void ShowWindowIfIssuesExist()
|
||||
{
|
||||
List<OpenXRFeature.ValidationRule> failures = new List<OpenXRFeature.ValidationRule>();
|
||||
BuildTargetGroup activeBuildTargetGroup = BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget);
|
||||
OpenXRProjectValidation.GetCurrentValidationIssues(failures, activeBuildTargetGroup);
|
||||
|
||||
if (failures.Count > 0)
|
||||
ShowWindow();
|
||||
}
|
||||
|
||||
internal static BuildValidationRule ConvertRuleToBuildValidationRule(OpenXRFeature.ValidationRule rule, BuildTargetGroup buildTargetGroup)
|
||||
{
|
||||
Type featureType = null;
|
||||
if (rule.feature != null)
|
||||
{
|
||||
featureType = rule.feature.GetType();
|
||||
}
|
||||
|
||||
return new BuildValidationRule
|
||||
{
|
||||
// This will hide the rules given a condition so that when you click "Show all" it doesn't show up as passed
|
||||
IsRuleEnabled = () =>
|
||||
{
|
||||
// If OpenXR isn't enabled, no need to show the rule
|
||||
if (!BuildHelperUtils.HasLoader(buildTargetGroup, typeof(OpenXRLoaderBase)))
|
||||
return false;
|
||||
|
||||
// If a rule specific feature isn't enabled, don't show this rule
|
||||
if (featureType != null)
|
||||
{
|
||||
OpenXRSettings openXrSettings = OpenXRSettings.GetSettingsForBuildTargetGroup(buildTargetGroup);
|
||||
if (openXrSettings == null)
|
||||
return false;
|
||||
|
||||
OpenXRFeature feature = openXrSettings.GetFeature(featureType);
|
||||
if (feature == null || !feature.enabled)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
CheckPredicate = rule.checkPredicate,
|
||||
Error = rule.error,
|
||||
FixIt = rule.fixIt,
|
||||
FixItAutomatic = rule.fixItAutomatic,
|
||||
FixItMessage = rule.fixItMessage,
|
||||
HelpLink = rule.helpLink,
|
||||
HelpText = rule.helpText,
|
||||
HighlighterFocus = new BuildValidationRule.HighlighterFocusData
|
||||
{
|
||||
WindowTitle = rule.highlighterFocus.windowTitle,
|
||||
SearchText = rule.highlighterFocus.searchText
|
||||
},
|
||||
Message = rule.message,
|
||||
Category = rule.feature != null ? rule.feature.nameUi : "OpenXR",
|
||||
SceneOnlyValidation = false
|
||||
};
|
||||
}
|
||||
|
||||
static void AddOpenXRValidationRules()
|
||||
{
|
||||
foreach (var buildTargetGroup in s_BuildTargetGroups)
|
||||
{
|
||||
bool isOpenXRSupportedPlatform = buildTargetGroup == BuildTargetGroup.Standalone || buildTargetGroup == BuildTargetGroup.Android || buildTargetGroup == BuildTargetGroup.WSA;
|
||||
if (!isOpenXRSupportedPlatform)
|
||||
continue;
|
||||
|
||||
var coreIssues = new List<BuildValidationRule>() { GetDefaultBuildValidationRule(buildTargetGroup) };
|
||||
var issues = new List<OpenXRFeature.ValidationRule>();
|
||||
OpenXRProjectValidation.GetAllValidationIssues(issues, buildTargetGroup);
|
||||
|
||||
foreach (var issue in issues)
|
||||
{
|
||||
coreIssues.Add(ConvertRuleToBuildValidationRule(issue, buildTargetGroup));
|
||||
}
|
||||
|
||||
BuildValidator.AddRules(buildTargetGroup, coreIssues);
|
||||
}
|
||||
}
|
||||
|
||||
static BuildValidationRule GetDefaultBuildValidationRule(BuildTargetGroup targetGroup)
|
||||
{
|
||||
var defaultRule = new BuildValidationRule()
|
||||
{
|
||||
// This will hide the rules given a condition so that when you click "Show all" it doesn't show up as passed
|
||||
IsRuleEnabled = () =>
|
||||
{
|
||||
// Only show this rule if there are enabled OpenXR features that aren't interaction profiles
|
||||
return ExistsOpenXRFeaturesEnabledForBuildTarget(targetGroup);
|
||||
},
|
||||
Message = "[OpenXR] Enabled OpenXR Features require OpenXR to be selected as the active loader for this platform",
|
||||
CheckPredicate = () => BuildHelperUtils.HasLoader(targetGroup, typeof(OpenXRLoaderBase)),
|
||||
Error = false,
|
||||
FixIt = () => { SettingsService.OpenProjectSettings("Project/XR Plug-in Management"); },
|
||||
FixItAutomatic = false,
|
||||
FixItMessage = "Open Project Settings to select OpenXR as the active loader for this platform."
|
||||
};
|
||||
|
||||
return defaultRule;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if there are any features enabled for this build target
|
||||
/// </summary>
|
||||
static bool ExistsOpenXRFeaturesEnabledForBuildTarget(BuildTargetGroup buildTargetGroup)
|
||||
{
|
||||
var openXrSettings = OpenXRSettings.GetSettingsForBuildTargetGroup(buildTargetGroup);
|
||||
foreach (var feature in openXrSettings?.features)
|
||||
{
|
||||
if (feature != null && feature.enabled && !(feature is OpenXRInteractionFeature interactionFeature && !interactionFeature.IsAdditive))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[MenuItem("Window/XR/OpenXR/Project Validation")]
|
||||
private static void MenuItem()
|
||||
{
|
||||
ShowWindow();
|
||||
}
|
||||
|
||||
internal static void SetSelectedBuildTargetGroup(BuildTargetGroup buildTargetGroup)
|
||||
{
|
||||
if (s_SelectedBuildTargetGroup == buildTargetGroup)
|
||||
return;
|
||||
|
||||
s_SelectedBuildTargetGroup = buildTargetGroup;
|
||||
}
|
||||
|
||||
internal static void ShowWindow(BuildTargetGroup buildTargetGroup = BuildTargetGroup.Unknown)
|
||||
{
|
||||
// Delay opening the window since sometimes other settings in the player settings provider redirect to the
|
||||
// project validation window causing serialized objects to be nullified
|
||||
EditorApplication.delayCall += () =>
|
||||
{
|
||||
SetSelectedBuildTargetGroup(buildTargetGroup);
|
||||
SettingsService.OpenProjectSettings(OpenXRProjectValidationSettingsPath);
|
||||
};
|
||||
}
|
||||
|
||||
internal static void CloseWindow() { }
|
||||
}
|
||||
|
||||
internal class OpenXRProjectValidationBuildStep : IPreprocessBuildWithReport
|
||||
{
|
||||
[OnOpenAsset(0)]
|
||||
static bool ConsoleErrorDoubleClicked(int instanceId, int line)
|
||||
{
|
||||
var objName = EditorUtility.InstanceIDToObject(instanceId).name;
|
||||
if (objName == "OpenXRProjectValidation")
|
||||
{
|
||||
OpenXRProjectValidationRulesSetup.ShowWindow();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public int callbackOrder { get; }
|
||||
|
||||
public void OnPreprocessBuild(BuildReport report)
|
||||
{
|
||||
if (!BuildHelperUtils.HasLoader(report.summary.platformGroup, typeof(OpenXRLoaderBase)))
|
||||
return;
|
||||
|
||||
// if (OpenXRProjectValidation.LogBuildValidationIssues(report.summary.platformGroup))
|
||||
// throw new BuildFailedException("OpenXR Build Failed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8b33bb889b0c8154b93647f78e433227
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
456
Packages/com.unity.xr.openxr/Editor/OpenXRRuntimeSelector.cs
Normal file
456
Packages/com.unity.xr.openxr/Editor/OpenXRRuntimeSelector.cs
Normal file
@@ -0,0 +1,456 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEditor.PackageManager;
|
||||
using UnityEditor.PackageManager.Requests;
|
||||
using UnityEngine;
|
||||
using Microsoft.Win32;
|
||||
|
||||
[assembly: InternalsVisibleTo("UnityEditor.XR.OpenXR.Tests")]
|
||||
namespace UnityEditor.XR.OpenXR
|
||||
{
|
||||
internal class OpenXRRuntimeSelector
|
||||
{
|
||||
internal class RuntimeDetector
|
||||
{
|
||||
internal const string k_RuntimeEnvKey = "XR_RUNTIME_JSON";
|
||||
|
||||
public virtual string name { get; set; }
|
||||
public virtual string jsonPath { get; }
|
||||
public virtual string tooltip => jsonPath;
|
||||
|
||||
public virtual bool detected
|
||||
{
|
||||
get
|
||||
{
|
||||
return File.Exists(jsonPath);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void PrepareRuntime()
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void Activate()
|
||||
{
|
||||
if (detected)
|
||||
{
|
||||
Environment.SetEnvironmentVariable(k_RuntimeEnvKey, jsonPath);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Deactivate()
|
||||
{
|
||||
Environment.SetEnvironmentVariable(k_RuntimeEnvKey, "");
|
||||
}
|
||||
|
||||
public RuntimeDetector(string _name)
|
||||
{
|
||||
name = _name;
|
||||
}
|
||||
};
|
||||
|
||||
internal class SystemDefault : RuntimeDetector
|
||||
{
|
||||
public override string name
|
||||
{
|
||||
get
|
||||
{
|
||||
string ret = "System Default";
|
||||
if (string.IsNullOrEmpty(tooltip))
|
||||
{
|
||||
ret += " (None Set)";
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
set
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public override string jsonPath => "";
|
||||
|
||||
public override string tooltip
|
||||
{
|
||||
get
|
||||
{
|
||||
string str = (string)Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Khronos\OpenXR\1", "ActiveRuntime", "");
|
||||
#if UNITY_EDITOR_OSX
|
||||
str = File.Exists("/usr/local/share/openxr/1/active_runtime.json") ? "/usr/local/share/openxr/1/active_runtime.json" : "";
|
||||
#endif
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool detected => true;
|
||||
|
||||
public SystemDefault()
|
||||
: base(null)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
internal class OtherRuntime : RuntimeDetector
|
||||
{
|
||||
public const string k_OtherRuntimeEnvKey = "OTHER_XR_RUNTIME_JSON";
|
||||
|
||||
private string runtimeJsonPath = "";
|
||||
|
||||
public override string jsonPath => runtimeJsonPath;
|
||||
|
||||
public override void PrepareRuntime()
|
||||
{
|
||||
var selectedJson = EditorUtility.OpenFilePanel("Select OpenXR Runtime json", "", "json");
|
||||
SetOtherRuntimeJsonPath(selectedJson);
|
||||
base.PrepareRuntime();
|
||||
}
|
||||
|
||||
public void SetOtherRuntimeJsonPath(string selectedJson)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(selectedJson))
|
||||
{
|
||||
runtimeJsonPath = selectedJson;
|
||||
Environment.SetEnvironmentVariable(k_OtherRuntimeEnvKey, selectedJson);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool detected => true;
|
||||
|
||||
public OtherRuntime()
|
||||
: base("Other")
|
||||
{
|
||||
string otherJsonPath = Environment.GetEnvironmentVariable(k_OtherRuntimeEnvKey);
|
||||
if (otherJsonPath != null)
|
||||
{
|
||||
runtimeJsonPath = otherJsonPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class OculusDetector : RuntimeDetector
|
||||
{
|
||||
private const string installLocKey = @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Oculus";
|
||||
private const string installLocValue = "InstallLocation";
|
||||
private const string jsonName = @"Support\oculus-runtime\oculus_openxr_64.json";
|
||||
|
||||
public override string jsonPath
|
||||
{
|
||||
get
|
||||
{
|
||||
var oculusPath = (string)Registry.GetValue(installLocKey, installLocValue, "");
|
||||
if (string.IsNullOrEmpty(oculusPath)) return "";
|
||||
return Path.Combine(oculusPath, jsonName);
|
||||
}
|
||||
}
|
||||
|
||||
public OculusDetector()
|
||||
: base("Oculus")
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
internal class SteamVRDetector : RuntimeDetector
|
||||
{
|
||||
public override string jsonPath => @"C:\Program Files (x86)\Steam\steamapps\common\SteamVR\steamxr_win64.json";
|
||||
|
||||
public SteamVRDetector()
|
||||
: base("SteamVR")
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
internal class WindowsMRDetector : RuntimeDetector
|
||||
{
|
||||
public override string jsonPath => @"C:\WINDOWS\system32\MixedRealityRuntime.json";
|
||||
|
||||
public WindowsMRDetector()
|
||||
: base("Windows Mixed Reality")
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
internal class MetaXRSimDetector : RuntimeDetector
|
||||
{
|
||||
const string PackageName = "com.meta.xr.simulator";
|
||||
string runtimePath = string.Empty;
|
||||
SearchRequest searchRequest = null;
|
||||
|
||||
public override string jsonPath => runtimePath;
|
||||
|
||||
public MetaXRSimDetector()
|
||||
: base("Meta XR Simulator")
|
||||
{
|
||||
SubmitPackageRequest();
|
||||
}
|
||||
|
||||
void SubmitPackageRequest()
|
||||
{
|
||||
searchRequest = Client.Search(PackageName);
|
||||
EditorApplication.update += OnEditorUpdate;
|
||||
}
|
||||
|
||||
void OnEditorUpdate()
|
||||
{
|
||||
switch (searchRequest.Status)
|
||||
{
|
||||
case StatusCode.Failure:
|
||||
{
|
||||
EditorApplication.update -= OnEditorUpdate;
|
||||
return;
|
||||
}
|
||||
|
||||
case StatusCode.InProgress:
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
EditorApplication.update -= OnEditorUpdate;
|
||||
|
||||
foreach (var packageInfo in searchRequest.Result)
|
||||
{
|
||||
if (packageInfo.name == PackageName)
|
||||
{
|
||||
string packageAssetPath = Path.GetFullPath(packageInfo.assetPath);
|
||||
runtimePath = GetRuntimeJsonPath(packageAssetPath);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetRuntimeJsonPath(string packageAssetPath)
|
||||
{
|
||||
var runtimePath = Path.Combine(packageAssetPath, Path.Combine("MetaXRSimulator", "meta_openxr_simulator_win64.json"));
|
||||
#if UNITY_EDITOR_OSX
|
||||
runtimePath = Path.Combine(Path.GetFullPath(packageAssetPath), Path.Combine("MetaXRSimulator", "meta_openxr_simulator_posix.json"));
|
||||
#endif
|
||||
if (!File.Exists(runtimePath))
|
||||
{
|
||||
runtimePath = Path.Combine(packageAssetPath, Path.Combine("MetaXRSimulator", "meta_openxr_simulator.json"));
|
||||
}
|
||||
return runtimePath;
|
||||
}
|
||||
}
|
||||
|
||||
internal class DiscoveredDetector : RuntimeDetector
|
||||
{
|
||||
public DiscoveredDetector(string _name, string jsonPath)
|
||||
: base(_name)
|
||||
{
|
||||
m_jsonPath = jsonPath;
|
||||
}
|
||||
|
||||
public override string jsonPath => m_jsonPath;
|
||||
|
||||
private string m_jsonPath;
|
||||
}
|
||||
|
||||
internal static List<RuntimeDetector> runtimeDetectors;
|
||||
internal static List<RuntimeDetector> RuntimeDetectors
|
||||
{
|
||||
get
|
||||
{
|
||||
if (runtimeDetectors == null)
|
||||
{
|
||||
runtimeDetectors = GenerateRuntimeDetectorList();
|
||||
}
|
||||
return runtimeDetectors;
|
||||
}
|
||||
}
|
||||
|
||||
internal static void RefreshRuntimeDetectorList()
|
||||
{
|
||||
runtimeDetectors = GenerateRuntimeDetectorList();
|
||||
}
|
||||
|
||||
const string k_availableRuntimesRegistryKey = @"SOFTWARE\Khronos\OpenXR\1\AvailableRuntimes";
|
||||
|
||||
internal static List<RuntimeDetector> GenerateRuntimeDetectorList()
|
||||
{
|
||||
RegistryKey availableRuntimesKey = Registry.LocalMachine.OpenSubKey(k_availableRuntimesRegistryKey, false);
|
||||
Dictionary<string, int> runtimePathToValue = new Dictionary<string, int>();
|
||||
|
||||
if (availableRuntimesKey != null)
|
||||
{
|
||||
foreach (string jsonPath in availableRuntimesKey.GetValueNames())
|
||||
{
|
||||
var availableValue = (int)availableRuntimesKey.GetValue(jsonPath, "");
|
||||
runtimePathToValue.Add(jsonPath, availableValue);
|
||||
}
|
||||
}
|
||||
|
||||
return GenerateRuntimeDetectorList(runtimePathToValue);
|
||||
}
|
||||
|
||||
private static void OverwriteRuntimeDetector(List<RuntimeDetector> runtimeList, string jsonPath, int registryValue)
|
||||
{
|
||||
int index = runtimeList.FindIndex(runtimeDetector => runtimeDetector.jsonPath.Equals(jsonPath));
|
||||
if (index < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (registryValue == 0)
|
||||
{
|
||||
string name = GetName(jsonPath) ?? jsonPath;
|
||||
if (name != null)
|
||||
{
|
||||
runtimeList[index] = new DiscoveredDetector(name, jsonPath);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
runtimeList.RemoveAt(index);
|
||||
}
|
||||
}
|
||||
|
||||
internal static List<RuntimeDetector> GenerateRuntimeDetectorList(Dictionary<string, int> runtimePathToValue)
|
||||
{
|
||||
WindowsMRDetector windowsMRDetector = new WindowsMRDetector();
|
||||
SteamVRDetector steamVRDetector = new SteamVRDetector();
|
||||
OculusDetector oculusDetector = new OculusDetector();
|
||||
MetaXRSimDetector metaXRSimDetector = new MetaXRSimDetector();
|
||||
List<RuntimeDetector> runtimeList = new List<RuntimeDetector>(5)
|
||||
{
|
||||
new SystemDefault(),
|
||||
windowsMRDetector,
|
||||
steamVRDetector,
|
||||
oculusDetector,
|
||||
metaXRSimDetector
|
||||
};
|
||||
|
||||
foreach (var pathValuePair in runtimePathToValue)
|
||||
{
|
||||
if (pathValuePair.Key.Equals(windowsMRDetector.jsonPath))
|
||||
{
|
||||
OverwriteRuntimeDetector(runtimeList, windowsMRDetector.jsonPath, pathValuePair.Value);
|
||||
}
|
||||
else if (pathValuePair.Key.Equals(steamVRDetector.jsonPath))
|
||||
{
|
||||
OverwriteRuntimeDetector(runtimeList, steamVRDetector.jsonPath, pathValuePair.Value);
|
||||
}
|
||||
else if (pathValuePair.Key.Equals(oculusDetector.jsonPath))
|
||||
{
|
||||
OverwriteRuntimeDetector(runtimeList, oculusDetector.jsonPath, pathValuePair.Value);
|
||||
}
|
||||
else if (pathValuePair.Value == 0)
|
||||
{
|
||||
string name = GetName(pathValuePair.Key) ?? pathValuePair.Key;
|
||||
runtimeList.Add(new DiscoveredDetector(name, pathValuePair.Key));
|
||||
}
|
||||
}
|
||||
|
||||
runtimeList.Add(new OtherRuntime());
|
||||
return runtimeList;
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
internal class RuntimeInformation
|
||||
{
|
||||
public string file_format_version;
|
||||
|
||||
public RuntimeDetails runtime;
|
||||
|
||||
[System.Serializable]
|
||||
internal class RuntimeDetails
|
||||
{
|
||||
public string library_path;
|
||||
|
||||
public string name;
|
||||
}
|
||||
}
|
||||
|
||||
internal static string GetName(string jsonPath)
|
||||
{
|
||||
if (!File.Exists(jsonPath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
string text = File.ReadAllText(jsonPath);
|
||||
RuntimeInformation runtimeInformation = JsonUtility.FromJson<RuntimeInformation>(text);
|
||||
if (runtimeInformation != null && runtimeInformation.runtime != null)
|
||||
{
|
||||
return runtimeInformation.runtime.name;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static class Content
|
||||
{
|
||||
public static readonly GUIContent k_ActiveRuntimeLabel = new GUIContent("Play Mode OpenXR Runtime", "Changing this value will only affect this instance of the editor.");
|
||||
}
|
||||
|
||||
internal const string k_SelectedRuntimeEnvKey = "XR_SELECTED_RUNTIME_JSON";
|
||||
|
||||
private static int GetActiveRuntimeIndex(List<RuntimeDetector> runtimes)
|
||||
{
|
||||
string envValue = Environment.GetEnvironmentVariable(k_SelectedRuntimeEnvKey);
|
||||
if (string.IsNullOrEmpty(envValue))
|
||||
return 0;
|
||||
|
||||
for (int i = 0; i < runtimes.Count; i++)
|
||||
{
|
||||
if (String.Compare(runtimes[i].jsonPath, envValue, StringComparison.InvariantCulture) == 0)
|
||||
return i;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
internal static void SetSelectedRuntime(string jsonPath)
|
||||
{
|
||||
Environment.SetEnvironmentVariable(k_SelectedRuntimeEnvKey, jsonPath);
|
||||
}
|
||||
|
||||
public static void DrawSelector()
|
||||
{
|
||||
EditorGUIUtility.labelWidth = 200;
|
||||
GUILayout.BeginHorizontal();
|
||||
var runtimes = OpenXRRuntimeSelector.RuntimeDetectors.Where(runtime => runtime.detected).ToList();
|
||||
|
||||
int selectedRuntimeIndex = GetActiveRuntimeIndex(runtimes);
|
||||
int index = EditorGUILayout.Popup(Content.k_ActiveRuntimeLabel, selectedRuntimeIndex, runtimes.Select(s => new GUIContent(s.name, s.tooltip)).ToArray());
|
||||
if (selectedRuntimeIndex != index)
|
||||
{
|
||||
selectedRuntimeIndex = index;
|
||||
runtimes[selectedRuntimeIndex].PrepareRuntime();
|
||||
SetSelectedRuntime(runtimes[selectedRuntimeIndex].jsonPath);
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
EditorGUIUtility.labelWidth = 0;
|
||||
}
|
||||
|
||||
internal static void ActivateOrDeactivateRuntime(PlayModeStateChange state)
|
||||
{
|
||||
var runtimes = OpenXRRuntimeSelector.RuntimeDetectors.Where(runtime => runtime.detected).ToList();
|
||||
int runtimeIndex = GetActiveRuntimeIndex(runtimes);
|
||||
switch (state)
|
||||
{
|
||||
case PlayModeStateChange.ExitingEditMode:
|
||||
if (runtimeIndex >= 0)
|
||||
{
|
||||
runtimes[runtimeIndex].Activate();
|
||||
}
|
||||
break;
|
||||
|
||||
case PlayModeStateChange.EnteredEditMode:
|
||||
if (runtimeIndex >= 0)
|
||||
{
|
||||
runtimes[runtimeIndex].Deactivate();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static OpenXRRuntimeSelector()
|
||||
{
|
||||
EditorApplication.playModeStateChanged -= ActivateOrDeactivateRuntime;
|
||||
EditorApplication.playModeStateChanged += ActivateOrDeactivateRuntime;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0cbcb2d3be8f3ad41bee05a404f24cd9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
234
Packages/com.unity.xr.openxr/Editor/PackageSettingsEditor.cs
Normal file
234
Packages/com.unity.xr.openxr/Editor/PackageSettingsEditor.cs
Normal file
@@ -0,0 +1,234 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.XR.OpenXR;
|
||||
using UnityEditor.XR.OpenXR.Features;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace UnityEditor.XR.OpenXR
|
||||
{
|
||||
[CustomEditor(typeof(OpenXRPackageSettings))]
|
||||
internal class PackageSettingsEditor : UnityEditor.Editor
|
||||
{
|
||||
OpenXRFeatureEditor m_FeatureEditor = null;
|
||||
Vector2 scrollPos = Vector2.zero;
|
||||
#if XR_MGMT_3_2_0_OR_NEWER
|
||||
OpenXRManagementSettings managementSettings = new();
|
||||
#endif
|
||||
|
||||
#if XR_MGMT_4_1_0_OR_OLDER
|
||||
static PackageSettingsEditor s_LastPackageSettingsEditor = null;
|
||||
#endif
|
||||
|
||||
static class Content
|
||||
{
|
||||
public const float k_Space = 15.0f;
|
||||
|
||||
public static readonly GUIContent k_renderModeLabel = new GUIContent("Render Mode");
|
||||
public static readonly GUIContent k_vulkanAdditionalGraphicsQueue = new GUIContent("Additional Graphics Queue (Vulkan)", "Request an additional Vulkan graphics queue for its own rendering at startup.");
|
||||
|
||||
public static readonly GUIContent[] k_renderModeOptions = new GUIContent[2]
|
||||
{
|
||||
new GUIContent("Multi-pass"),
|
||||
new GUIContent("Single Pass Instanced"),
|
||||
};
|
||||
|
||||
public static readonly GUIContent[] k_androidRenderModeOptions = new GUIContent[2]
|
||||
{
|
||||
new GUIContent("Multi-pass"),
|
||||
new GUIContent("Single Pass Instanced \\ Multi-view"),
|
||||
};
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
m_FeatureEditor = OpenXRFeatureEditor.CreateFeatureEditor();
|
||||
|
||||
#if XR_MGMT_4_1_0_OR_OLDER
|
||||
// Due to a bug in XRManagement that was fixed in builds newer than 4.1.0 the OnDestroy method
|
||||
// is not called when the old package settings editor is abandoned. This causes the OnUpdateFeatureSetState
|
||||
// event handlers to pile up unecessarily. To fix this we maintain a static reference to the last editor
|
||||
// that Awake was called on and unregister the event handler.
|
||||
if (s_LastPackageSettingsEditor != null)
|
||||
{
|
||||
OpenXRFeatureSetManager.onFeatureSetStateChanged -=
|
||||
s_LastPackageSettingsEditor.OnFeatureSetStateChanged;
|
||||
}
|
||||
|
||||
s_LastPackageSettingsEditor = this;
|
||||
#endif
|
||||
|
||||
OpenXRFeatureSetManager.onFeatureSetStateChanged += OnFeatureSetStateChanged;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
OpenXRFeatureSetManager.onFeatureSetStateChanged -= OnFeatureSetStateChanged;
|
||||
}
|
||||
|
||||
public void OnEnable()
|
||||
{
|
||||
OpenXRRuntimeSelector.RefreshRuntimeDetectorList();
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
var buildTargetGroup = EditorGUILayout.BeginBuildTargetSelectionGrouping();
|
||||
OpenXRProjectValidationRulesSetup.SetSelectedBuildTargetGroup(buildTargetGroup);
|
||||
|
||||
#if XR_MGMT_3_2_0_OR_NEWER
|
||||
if (
|
||||
!managementSettings.metadata.loaderMetadata[0].supportedBuildTargets.Contains(
|
||||
buildTargetGroup
|
||||
)
|
||||
)
|
||||
{
|
||||
EditorGUILayout.EndBuildTargetSelectionGrouping();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
OpenXRPackageSettings settings = serializedObject.targetObject as OpenXRPackageSettings;
|
||||
|
||||
scrollPos = EditorGUILayout.BeginScrollView(scrollPos);
|
||||
|
||||
EditorGUI.BeginDisabledGroup(EditorApplication.isPlaying);
|
||||
|
||||
EditorGUILayout.BeginVertical();
|
||||
|
||||
var openXrSettings = settings.GetSettingsForBuildTargetGroup(buildTargetGroup);
|
||||
var serializedOpenXrSettings = new SerializedObject(openXrSettings);
|
||||
|
||||
var openXrEditorSettings = OpenXREditorSettings.Instance;
|
||||
var serializedOpenXrEditorSettings = new SerializedObject(openXrEditorSettings);
|
||||
|
||||
EditorGUIUtility.labelWidth = 200;
|
||||
|
||||
int newRenderMode;
|
||||
GUILayout.BeginHorizontal();
|
||||
if (buildTargetGroup == BuildTargetGroup.Android)
|
||||
{
|
||||
newRenderMode = EditorGUILayout.Popup(
|
||||
Content.k_renderModeLabel,
|
||||
(int)openXrSettings.renderMode,
|
||||
Content.k_androidRenderModeOptions
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
newRenderMode = EditorGUILayout.Popup(
|
||||
Content.k_renderModeLabel,
|
||||
(int)openXrSettings.renderMode,
|
||||
Content.k_renderModeOptions
|
||||
);
|
||||
}
|
||||
|
||||
if (newRenderMode != (int)openXrSettings.renderMode)
|
||||
{
|
||||
openXrSettings.renderMode = (OpenXRSettings.RenderMode)newRenderMode;
|
||||
}
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
GUILayout.BeginHorizontal();
|
||||
|
||||
EditorGUIUtility.labelWidth = 210;
|
||||
|
||||
var newAutoColorSubmissionMode = EditorGUILayout.Toggle(
|
||||
"Auto Color Submission Mode",
|
||||
openXrSettings.autoColorSubmissionMode
|
||||
);
|
||||
if (newAutoColorSubmissionMode != openXrSettings.autoColorSubmissionMode)
|
||||
{
|
||||
openXrSettings.autoColorSubmissionMode = newAutoColorSubmissionMode;
|
||||
}
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
GUILayout.BeginHorizontal();
|
||||
|
||||
if (!openXrSettings.autoColorSubmissionMode)
|
||||
{
|
||||
EditorGUI.PropertyField(
|
||||
new(new(0, 0), new(0, 0)),
|
||||
serializedOpenXrSettings.FindProperty("m_colorSubmissionModes")
|
||||
);
|
||||
}
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
DrawPropertiesExcluding(
|
||||
serializedOpenXrSettings,
|
||||
"m_Script",
|
||||
"m_renderMode",
|
||||
"m_autoColorSubmissionMode",
|
||||
"m_colorSubmissionModes",
|
||||
"m_symmetricProjection",
|
||||
"m_optimizeBufferDiscards",
|
||||
"m_vulkanAdditionalGraphicsQueue",
|
||||
"m_optimizeMultiviewRenderRegions",
|
||||
"m_spacewarpMotionVectorTextureFormat"
|
||||
);
|
||||
if (
|
||||
buildTargetGroup == BuildTargetGroup.Android
|
||||
|| buildTargetGroup == BuildTargetGroup.Standalone
|
||||
)
|
||||
{
|
||||
serializedOpenXrEditorSettings
|
||||
.FindProperty("m_vulkanAdditionalGraphicsQueue")
|
||||
.boolValue = EditorGUILayout.Toggle(
|
||||
Content.k_vulkanAdditionalGraphicsQueue,
|
||||
openXrEditorSettings.VulkanAdditionalGraphicsQueue
|
||||
);
|
||||
}
|
||||
|
||||
EditorGUIUtility.labelWidth = 0;
|
||||
|
||||
if (serializedOpenXrSettings.hasModifiedProperties)
|
||||
serializedOpenXrSettings.ApplyModifiedProperties();
|
||||
if (serializedOpenXrEditorSettings.hasModifiedProperties)
|
||||
serializedOpenXrEditorSettings.ApplyModifiedProperties();
|
||||
|
||||
if (buildTargetGroup == BuildTargetGroup.Standalone)
|
||||
{
|
||||
EditorGUILayout.Space();
|
||||
OpenXRRuntimeSelector.DrawSelector();
|
||||
}
|
||||
|
||||
EditorGUILayout.EndVertical();
|
||||
|
||||
if (m_FeatureEditor != null)
|
||||
{
|
||||
EditorGUILayout.Space();
|
||||
m_FeatureEditor.OnGUI(buildTargetGroup);
|
||||
}
|
||||
EditorGUI.EndDisabledGroup();
|
||||
|
||||
EditorGUILayout.EndBuildTargetSelectionGrouping();
|
||||
|
||||
EditorGUILayout.EndScrollView();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to force the project settings window to repaint.
|
||||
/// </summary>
|
||||
private static void RepaintProjectSettingsWindow()
|
||||
{
|
||||
var type = Type.GetType("UnityEditor.ProjectSettingsWindow,UnityEditor");
|
||||
if (null == type)
|
||||
return;
|
||||
|
||||
var window = Resources.FindObjectsOfTypeAll(type).FirstOrDefault() as EditorWindow;
|
||||
if (window != null)
|
||||
window.Repaint();
|
||||
}
|
||||
|
||||
private void OnFeatureSetStateChanged(BuildTargetGroup buildTargetGroup)
|
||||
{
|
||||
if (null == m_FeatureEditor)
|
||||
return;
|
||||
|
||||
m_FeatureEditor.OnFeatureSetStateChanged(buildTargetGroup);
|
||||
|
||||
RepaintProjectSettingsWindow();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ec0c31554699ca441a69ca3d47cd3fcd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "Unity.XR.OpenXR.Editor",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"Unity.XR.Management",
|
||||
"Unity.XR.Management.Editor",
|
||||
"Unity.XR.OpenXR",
|
||||
"Unity.InputSystem",
|
||||
"Unity.XR.CoreUtils.Editor"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": true,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [
|
||||
{
|
||||
"name": "com.unity.xr.management",
|
||||
"expression": "3.2.0",
|
||||
"define": "XR_MGMT_3_2_0_OR_NEWER"
|
||||
},
|
||||
{
|
||||
"name": "com.unity.xr.management",
|
||||
"expression": "[0.0.0,4.1.0]",
|
||||
"define": "XR_MGMT_4_1_0_OR_OLDER"
|
||||
},
|
||||
{
|
||||
"name": "com.unity.xr.management",
|
||||
"expression": "4.4.0",
|
||||
"define": "XR_MGMT_4_4_0_OR_NEWER"
|
||||
},
|
||||
{
|
||||
"name": "com.unity.xr.compositionlayers",
|
||||
"expression": "1.0.0",
|
||||
"define": "XR_COMPOSITION_LAYERS"
|
||||
}
|
||||
],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 96aa6ba065960476598f8f643e7252b6
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user