deltavr multiplayer 2.0
This commit is contained in:
8
Assets/XRI_Examples/Global/Scripts/Analytics.meta
Normal file
8
Assets/XRI_Examples/Global/Scripts/Analytics.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e5bd8ad0287c0f6409b134bb397bed24
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cc72213c6872fba4b84b5f24188cb5d9
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,63 @@
|
||||
using UnityEngine.XR.Interaction.Toolkit;
|
||||
|
||||
namespace UnityEngine.XR.Content.Interaction.Analytics
|
||||
{
|
||||
/// <summary>
|
||||
/// Class that connects the 3DUI Interaction station scene objects with their respective analytics events.
|
||||
/// </summary>
|
||||
[AddComponentMenu("")]
|
||||
[DisallowMultipleComponent]
|
||||
class Xrc3DUIInteractionStationAnalytics : MonoBehaviour
|
||||
{
|
||||
[Header("3DUI Simple Controls Substation")]
|
||||
[SerializeField]
|
||||
XRLever m_Lever;
|
||||
|
||||
[SerializeField]
|
||||
XRJoystick m_Joystick;
|
||||
|
||||
[SerializeField]
|
||||
XRKnob m_Dial;
|
||||
|
||||
[SerializeField]
|
||||
XRKnob m_Wheel;
|
||||
|
||||
[SerializeField]
|
||||
XRSlider m_Slider;
|
||||
|
||||
[SerializeField]
|
||||
XRGripButton m_GripButton;
|
||||
|
||||
[SerializeField]
|
||||
XRPushButton m_PushButton;
|
||||
|
||||
[Header("Claw Machine Substation")]
|
||||
[SerializeField]
|
||||
XRJoystick m_ClawMachineJoystick;
|
||||
|
||||
[SerializeField]
|
||||
XRPushButton m_ClawMachinePushButton;
|
||||
|
||||
[SerializeField]
|
||||
XRSocketInteractor m_UfoGrabberSocket;
|
||||
|
||||
[SerializeField]
|
||||
XRBaseInteractable[] m_PrizeInteractables;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
XrcAnalyticsUtils.Register(m_Lever, new LeverInteraction());
|
||||
XrcAnalyticsUtils.Register(m_Joystick, new JoystickInteraction());
|
||||
XrcAnalyticsUtils.Register(m_Dial, new DialInteraction());
|
||||
XrcAnalyticsUtils.Register(m_Wheel, new WheelInteraction());
|
||||
XrcAnalyticsUtils.Register(m_Slider, new SliderInteraction());
|
||||
XrcAnalyticsUtils.Register(m_GripButton, new GripButtonPressed());
|
||||
XrcAnalyticsUtils.Register(m_PushButton, new PushButtonPressed());
|
||||
|
||||
XrcAnalyticsUtils.Register(m_ClawMachineJoystick, new ClawMachineJoystickInteraction());
|
||||
XrcAnalyticsUtils.Register(m_ClawMachinePushButton, new ClawMachinePushButtonPressed());
|
||||
XrcAnalyticsUtils.Register(m_UfoGrabberSocket, new ConnectClawMachineToPrize());
|
||||
XrcAnalyticsUtils.Register(m_PrizeInteractables, new GrabClawMachinePrize());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b5d488e69bc9d004dbebfd7cfe3f3103
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,59 @@
|
||||
using UnityEngine.XR.Interaction.Toolkit;
|
||||
|
||||
namespace UnityEngine.XR.Content.Interaction.Analytics
|
||||
{
|
||||
/// <summary>
|
||||
/// Class that connects the Active Interactable station scene objects with their respective analytics events.
|
||||
/// </summary>
|
||||
[AddComponentMenu("")]
|
||||
[DisallowMultipleComponent]
|
||||
class XrcActiveInteractableStationAnalytics : MonoBehaviour
|
||||
{
|
||||
[Header("Active SimpleObject Substation")]
|
||||
[SerializeField]
|
||||
XRBaseInteractable[] m_SimpleActiveInteractables;
|
||||
|
||||
[Header("Candle Substation")]
|
||||
[SerializeField]
|
||||
XRBaseInteractable m_LighterInteractable;
|
||||
|
||||
[SerializeField]
|
||||
XRBaseInteractable[] m_CandleInteractables;
|
||||
|
||||
[SerializeField]
|
||||
OnTrigger[] m_CandleTriggers;
|
||||
|
||||
[Header("Launcher Substation")]
|
||||
[SerializeField]
|
||||
XRBaseInteractable m_LauncherInteractable;
|
||||
|
||||
[SerializeField]
|
||||
OnTrigger m_EasyRingTrigger;
|
||||
|
||||
[SerializeField]
|
||||
OnTrigger m_MediumRingTrigger;
|
||||
|
||||
[SerializeField]
|
||||
OnTrigger m_HardRingTrigger;
|
||||
|
||||
[Header("Megaphone Substation")]
|
||||
[SerializeField]
|
||||
XRBaseInteractable m_MegaphoneInteractable;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
XrcAnalyticsUtils.Register(m_SimpleActiveInteractables, new GrabActiveSimpleObject(), new SimpleObjectActivated());
|
||||
|
||||
XrcAnalyticsUtils.Register(m_LighterInteractable, new GrabLighter(), new LighterActivated());
|
||||
XrcAnalyticsUtils.Register(m_CandleInteractables, new GrabCandle());
|
||||
XrcAnalyticsUtils.Register(m_CandleTriggers, new LightCandle());
|
||||
|
||||
XrcAnalyticsUtils.Register(m_LauncherInteractable, new GrabLauncher(), new LauncherActivated());
|
||||
XrcAnalyticsUtils.Register(m_EasyRingTrigger, new LauncherEasyTargetHit());
|
||||
XrcAnalyticsUtils.Register(m_MediumRingTrigger, new LauncherMediumTargetHit());
|
||||
XrcAnalyticsUtils.Register(m_HardRingTrigger, new LauncherHardTargetHit());
|
||||
|
||||
XrcAnalyticsUtils.Register(m_MegaphoneInteractable, new GrabMegaphone(), new MegaphoneActivated());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d919f1db183c42d4cac3edda16164e63
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,94 @@
|
||||
using UnityEngine.XR.Interaction.Toolkit;
|
||||
|
||||
namespace UnityEngine.XR.Content.Interaction.Analytics
|
||||
{
|
||||
/// <summary>
|
||||
/// Class that connects the Grab Interactable station scene objects with their respective analytics events.
|
||||
/// </summary>
|
||||
[AddComponentMenu("")]
|
||||
[DisallowMultipleComponent]
|
||||
class XrcGrabInteractableStationAnalytics : MonoBehaviour
|
||||
{
|
||||
const float k_FrequencyToSendWateringPlant = 4f;
|
||||
|
||||
static readonly WateringPlant k_WateringPlantParameter = new WateringPlant();
|
||||
static readonly BreakPiggyBank k_BreakPiggyBankParameter = new BreakPiggyBank();
|
||||
|
||||
[Header("Simple Object Substation")]
|
||||
[SerializeField]
|
||||
XRBaseInteractable[] m_InstantInteractables;
|
||||
|
||||
[SerializeField]
|
||||
XRBaseInteractable[] m_KinematicInteractables;
|
||||
|
||||
[SerializeField]
|
||||
XRBaseInteractable[] m_VelocityInteractables;
|
||||
|
||||
[Header("Watering Can Substation")]
|
||||
[SerializeField]
|
||||
XRBaseInteractable m_WateringCanInteractable;
|
||||
|
||||
[SerializeField]
|
||||
OnTrigger m_OnPlantGrowsTrigger;
|
||||
|
||||
[Header("Piggy Bank Substation")]
|
||||
[SerializeField]
|
||||
XRBaseInteractable m_MalletInteractable;
|
||||
|
||||
[SerializeField]
|
||||
GameObject m_PigBank;
|
||||
|
||||
[Header("Ribbon Stick Substation")]
|
||||
[SerializeField]
|
||||
XRBaseInteractable m_RibbonStickInteractable;
|
||||
|
||||
float m_TimeToSendWateringPlant;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
XrcAnalyticsUtils.Register(m_InstantInteractables, new GrabSimpleObjectInstant());
|
||||
XrcAnalyticsUtils.Register(m_KinematicInteractables, new GrabSimpleObjectKinematic());
|
||||
XrcAnalyticsUtils.Register(m_VelocityInteractables, new GrabSimpleObjectVelocity());
|
||||
|
||||
XrcAnalyticsUtils.Register(m_WateringCanInteractable, new GrabWateringCan());
|
||||
if (m_OnPlantGrowsTrigger != null)
|
||||
m_OnPlantGrowsTrigger.onEnter.AddListener(OnWateringPlant);
|
||||
|
||||
XrcAnalyticsUtils.Register(m_MalletInteractable, new GrabMallet());
|
||||
OnRestorePiggyBank(m_PigBank);
|
||||
|
||||
XrcAnalyticsUtils.Register(m_RibbonStickInteractable, new GrabRibbonStick());
|
||||
}
|
||||
|
||||
void OnWateringPlant(GameObject otherGameObject)
|
||||
{
|
||||
if (Time.unscaledTime < m_TimeToSendWateringPlant)
|
||||
return;
|
||||
|
||||
m_TimeToSendWateringPlant = Time.unscaledTime + k_FrequencyToSendWateringPlant;
|
||||
XrcAnalytics.interactionEvent.Send(k_WateringPlantParameter);
|
||||
}
|
||||
|
||||
void OnRestorePiggyBank(GameObject piggyBank)
|
||||
{
|
||||
if (piggyBank == null)
|
||||
return;
|
||||
|
||||
var breakable = piggyBank.GetComponent<Breakable>();
|
||||
if (breakable != null)
|
||||
breakable.onBreak.AddListener(OnBreakPiggyBank);
|
||||
}
|
||||
|
||||
void OnBreakPiggyBank(GameObject otherGameObject, GameObject brokenGameObject)
|
||||
{
|
||||
XrcAnalytics.interactionEvent.Send(k_BreakPiggyBankParameter);
|
||||
|
||||
if (brokenGameObject == null)
|
||||
return;
|
||||
|
||||
var unbreakable = brokenGameObject.GetComponent<Unbreakable>();
|
||||
if (unbreakable != null)
|
||||
unbreakable.onRestore.AddListener(OnRestorePiggyBank);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 64441dc1ef91cb442884a1d08fffdc1c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,72 @@
|
||||
using UnityEngine.XR.Interaction.Toolkit;
|
||||
|
||||
namespace UnityEngine.XR.Content.Interaction.Analytics
|
||||
{
|
||||
/// <summary>
|
||||
/// Class that connects the Physics Interaction station scene objects with their respective analytics events.
|
||||
/// </summary>
|
||||
[AddComponentMenu("")]
|
||||
[DisallowMultipleComponent]
|
||||
class XrcPhysicsInteractionStationAnalytics : MonoBehaviour
|
||||
{
|
||||
const float k_FrequencyToSendPushFlopDoor = 4f;
|
||||
|
||||
static readonly PushFlipDoor k_PushFlipDoorParameter = new PushFlipDoor();
|
||||
|
||||
[Header("Physics Simple Controls Substation")]
|
||||
[SerializeField]
|
||||
XRBaseInteractable[] m_SpringInteractables;
|
||||
|
||||
[SerializeField]
|
||||
XRBaseInteractable[] m_HingeInteractables;
|
||||
|
||||
[Header("Cabinet Example Substation")]
|
||||
[SerializeField]
|
||||
XRBaseInteractable m_Cabinet1Interactable;
|
||||
|
||||
[SerializeField]
|
||||
XRBaseInteractable m_Cabinet2Interactable;
|
||||
|
||||
[Header("Doors Example Substation")]
|
||||
[SerializeField]
|
||||
Rigidbody m_FlipDoorRigidbody;
|
||||
|
||||
[SerializeField]
|
||||
XRBaseInteractable m_DoorKeyInteractable;
|
||||
|
||||
[SerializeField]
|
||||
Door m_DoorLocked;
|
||||
|
||||
[SerializeField]
|
||||
XRBaseInteractable m_DoorHandleInteractable;
|
||||
|
||||
float m_TimeToSendPushFlopDoor;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
XrcAnalyticsUtils.Register(m_SpringInteractables, new GrabSpringJoint());
|
||||
XrcAnalyticsUtils.Register(m_HingeInteractables, new GrabHingeJoint());
|
||||
|
||||
XrcAnalyticsUtils.Register(m_Cabinet1Interactable, new GrabCabinet1());
|
||||
XrcAnalyticsUtils.Register(m_Cabinet2Interactable, new GrabCabinet2());
|
||||
|
||||
if (m_FlipDoorRigidbody != null)
|
||||
{
|
||||
var onCollision = m_FlipDoorRigidbody.gameObject.AddComponent<OnCollision>();
|
||||
onCollision.onEnter.AddListener(OnFlipDoorCollision);
|
||||
}
|
||||
XrcAnalyticsUtils.Register(m_DoorKeyInteractable, new GrabDoorKey());
|
||||
XrcAnalyticsUtils.Register(m_DoorLocked, new DoorLocked(), new DoorUnlocked());
|
||||
XrcAnalyticsUtils.Register(m_DoorHandleInteractable, new GrabDoorHandle());
|
||||
}
|
||||
|
||||
void OnFlipDoorCollision(Collision collision)
|
||||
{
|
||||
if (Time.unscaledTime < m_TimeToSendPushFlopDoor)
|
||||
return;
|
||||
|
||||
m_TimeToSendPushFlopDoor = Time.unscaledTime + k_FrequencyToSendPushFlopDoor;
|
||||
XrcAnalytics.interactionEvent.Send(k_PushFlipDoorParameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1af61162fa5f63a439f65912f4410ee5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,23 @@
|
||||
namespace UnityEngine.XR.Content.Interaction.Analytics
|
||||
{
|
||||
/// <summary>
|
||||
/// Class that manages scene analytics events for <c>XRContent</c>.
|
||||
/// </summary>
|
||||
[AddComponentMenu("")]
|
||||
[DisallowMultipleComponent]
|
||||
class XrcSceneAnalytics : MonoBehaviour
|
||||
{
|
||||
void Awake()
|
||||
{
|
||||
XrcAnalytics.interactionEvent.Send(new SceneStarted());
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
void OnValidate()
|
||||
{
|
||||
gameObject.hideFlags = UnityEditor.Unsupported.IsDeveloperMode() ? HideFlags.None : HideFlags.HideInHierarchy | HideFlags.NotEditable;
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1ff0aed9f9ef7084ca7f59a711befd28
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,48 @@
|
||||
using UnityEngine.XR.Interaction.Toolkit;
|
||||
|
||||
namespace UnityEngine.XR.Content.Interaction.Analytics
|
||||
{
|
||||
/// <summary>
|
||||
/// Class that connects the Socket Interactors station scene objects with their respective analytics events.
|
||||
/// </summary>
|
||||
[AddComponentMenu("")]
|
||||
[DisallowMultipleComponent]
|
||||
class XrcSocketInteractorsStationAnalytics : MonoBehaviour
|
||||
{
|
||||
[Header("Socket Simple Object Substation")]
|
||||
[SerializeField]
|
||||
XRSocketInteractor m_SimpleSocket;
|
||||
|
||||
[Header("Perler Machine")]
|
||||
[SerializeField]
|
||||
XRSocketInteractor m_BatterySlotSocket;
|
||||
|
||||
[SerializeField]
|
||||
XRSocketInteractor[] m_InfinityPegSockets;
|
||||
|
||||
[SerializeField]
|
||||
Transform m_GridCenter;
|
||||
|
||||
void Start()
|
||||
{
|
||||
XrcAnalyticsUtils.Register(m_SimpleSocket, new ConnectSocketSimpleObject(), new DisconnectSocketSimpleObject());
|
||||
|
||||
XrcAnalyticsUtils.Register(m_BatterySlotSocket, new ConnectPerlerMachineBattery());
|
||||
|
||||
var grabPerlerBeadParameter = new GrabPerlerBead();
|
||||
foreach (var socket in m_InfinityPegSockets)
|
||||
{
|
||||
foreach (var interactable in socket.interactablesSelected)
|
||||
{
|
||||
XrcAnalyticsUtils.Register(interactable as XRBaseInteractable, grabPerlerBeadParameter);
|
||||
}
|
||||
|
||||
socket.selectEntered.AddListener(args => XrcAnalyticsUtils.Register(args.interactableObject as XRBaseInteractable, grabPerlerBeadParameter));
|
||||
}
|
||||
|
||||
var connectPerlerBeadParameter = new ConnectPerlerBead();
|
||||
foreach (var gridSocket in m_GridCenter.GetComponentsInChildren<XRSocketInteractor>())
|
||||
XrcAnalyticsUtils.Register(gridSocket, connectPerlerBeadParameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 51e2b2d7ea5934243ad1542b8dc5f501
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/XRI_Examples/Global/Scripts/Analytics/Editor.meta
Normal file
8
Assets/XRI_Examples/Global/Scripts/Analytics/Editor.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8df8bc9cd77e5e7448cec848f8f57f9e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,41 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.XR.Content.Interaction.Analytics;
|
||||
using UnityObject = UnityEngine.Object;
|
||||
|
||||
namespace UnityEditor.XR.Content.Interaction.Analytics
|
||||
{
|
||||
/// <summary>
|
||||
/// Editor for <see cref="XrcSceneAnalytics"/> that makes it easy to spot missing analytics objects by validating
|
||||
/// object reference properties in all components in the same GameObject.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(XrcSceneAnalytics))]
|
||||
class XrcSceneAnalyticsEditor : Editor
|
||||
{
|
||||
const string k_WarningMessage = "Missing analytics object reference in property \'{0}.{1}\'";
|
||||
|
||||
static void ValidateObjectReferences(UnityObject target)
|
||||
{
|
||||
var serializedObject = new SerializedObject(target);
|
||||
var property = serializedObject.GetIterator();
|
||||
while (property.NextVisible(true))
|
||||
{
|
||||
if (property.propertyType == SerializedPropertyType.ObjectReference && property.objectReferenceValue == null)
|
||||
Debug.LogWarningFormat(target, k_WarningMessage, target.GetType().Name, property.propertyPath);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
if (GUILayout.Button("Validate References"))
|
||||
{
|
||||
var sceneAnalytics = target as XrcSceneAnalytics;
|
||||
if (sceneAnalytics == null)
|
||||
return;
|
||||
|
||||
var monoBehaviours = sceneAnalytics.GetComponentsInChildren<MonoBehaviour>();
|
||||
foreach (var monoBehaviour in monoBehaviours)
|
||||
ValidateObjectReferences(monoBehaviour);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cac85eae435a0fe4a9c4f0e6a8bfd8bb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/XRI_Examples/Global/Scripts/Analytics/Events.meta
Normal file
8
Assets/XRI_Examples/Global/Scripts/Analytics/Events.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 87a2345705c099943ae2e878d24a3bbe
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,72 @@
|
||||
using UnityEngine.Analytics;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
#if DEBUG_XRC_EDITOR_ANALYTICS
|
||||
using UnityEngine;
|
||||
#endif
|
||||
|
||||
namespace UnityEngine.XR.Content.Interaction.Analytics
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for <c>XRContent</c> editor events.
|
||||
/// </summary>
|
||||
abstract class EditorEvent
|
||||
{
|
||||
protected const int k_DefaultMaxEventsPerHour = 1000;
|
||||
protected const int k_DefaultMaxElementCount = 1000;
|
||||
|
||||
/// <summary>
|
||||
/// The event name determines which database table it goes into in the CDP backend.
|
||||
/// All events which we want grouped into a table must share the same event name.
|
||||
/// </summary>
|
||||
readonly string m_EventName;
|
||||
|
||||
readonly int m_MaxEventsPerHour;
|
||||
readonly int m_MaxElementCount;
|
||||
|
||||
internal EditorEvent(string eventName, int maxPerHour = k_DefaultMaxEventsPerHour, int maxElementCount = k_DefaultMaxElementCount)
|
||||
{
|
||||
m_EventName = eventName;
|
||||
m_MaxEventsPerHour = maxPerHour;
|
||||
m_MaxElementCount = maxElementCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call this method in the child classes to send an event.
|
||||
/// </summary>
|
||||
/// <param name="parameter">The parameter object within the event.</param>
|
||||
/// <returns>Returns whenever the event was successfully sent.</returns>
|
||||
protected bool Send(object parameter)
|
||||
{
|
||||
#if ENABLE_CLOUD_SERVICES_ANALYTICS
|
||||
// Analytics events will always refuse to send if analytics are disabled or the editor is for sure quitting
|
||||
if (XrcAnalytics.disabled || XrcAnalytics.quitting)
|
||||
return false;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
var result = EditorAnalytics.SendEventWithLimit(m_EventName, parameter);
|
||||
#else
|
||||
var result = AnalyticsResult.AnalyticsDisabled;
|
||||
#endif
|
||||
|
||||
#if DEBUG_XRC_EDITOR_ANALYTICS
|
||||
Debug.Log($"Event {m_EventName} : {parameter} sent with status {result}");
|
||||
#endif
|
||||
|
||||
return result == AnalyticsResult.Ok;
|
||||
#else // ENABLE_CLOUD_SERVICES_ANALYTICS
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
internal bool Register()
|
||||
{
|
||||
#if UNITY_EDITOR && ENABLE_CLOUD_SERVICES_ANALYTICS
|
||||
return EditorAnalytics.RegisterEventWithLimit(m_EventName, m_MaxEventsPerHour, m_MaxElementCount, XrcAnalytics.k_VendorKey) == AnalyticsResult.Ok;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 361f96375872bab42b1254af3fbc27e5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,453 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEngine.XR.Content.Interaction.Analytics
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for interaction parameters.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
abstract class InteractionEventParameter
|
||||
{
|
||||
/// <summary>
|
||||
/// The parameter unique name.
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
protected string Name;
|
||||
|
||||
internal InteractionEventParameter()
|
||||
{
|
||||
Name = GetType().Name;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return JsonUtility.ToJson(this);
|
||||
}
|
||||
}
|
||||
|
||||
// Scene Started Parameter
|
||||
[Serializable]
|
||||
sealed class SceneStarted : InteractionEventParameter
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Base class for station parameters. A station parameter has a <c>Station</c> and <c>Substaion</c> name.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
abstract class StationParameter : InteractionEventParameter
|
||||
{
|
||||
protected static class GrabInteractableStation
|
||||
{
|
||||
internal const string k_StationName = "GrabInteractable";
|
||||
|
||||
internal const string k_SimpleObjectSubstationName = "SimpleObject";
|
||||
internal const string k_WateringCanSubstationName = "WateringCan";
|
||||
internal const string k_PiggyBankSubstationName = "PiggyBank";
|
||||
internal const string k_RibbonStickSubstationName = "RibbonSitck";
|
||||
}
|
||||
|
||||
protected static class ActiveInteractableStation
|
||||
{
|
||||
internal const string k_StationName = "ActiveInteractable";
|
||||
|
||||
internal const string k_ActiveSimpleObjectSubstationName = "ActiveSimpleObject";
|
||||
internal const string k_CandleSubstationName = "Candle";
|
||||
internal const string k_LauncherSubstationName = "Launcher";
|
||||
internal const string k_MegaphoneSubstationName = "Megaphone";
|
||||
}
|
||||
|
||||
protected static class SocketInteractorStation
|
||||
{
|
||||
internal const string k_StationName = "SocketInteractors";
|
||||
|
||||
internal const string k_SimpleSocketSubstationName = "SocketSimpleObject";
|
||||
internal const string k_PerlerMachineSubstationName = "PerlerMachine";
|
||||
}
|
||||
|
||||
protected static class Interaction3DUIStation
|
||||
{
|
||||
internal const string k_StationName = "3DUIInteraction";
|
||||
|
||||
internal const string k_3DUISimpleControlsSubstationName = "3DUISimpleControls";
|
||||
internal const string k_ClawMachineSubstationName = "ClawMachine";
|
||||
}
|
||||
|
||||
protected static class PhysicsInteractableStation
|
||||
{
|
||||
internal const string k_StationName = "PhysicsInteraction";
|
||||
|
||||
internal const string k_PhysicsSimpleControlsSubstationName = "PhysicsSimpleControls";
|
||||
internal const string k_CabinetExampleSubstationName = "CabinetExample";
|
||||
internal const string k_DoorsExampleSubstationName = "DoorsExample";
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
protected string Station;
|
||||
|
||||
[SerializeField]
|
||||
protected string Substation;
|
||||
}
|
||||
|
||||
// Grab Simple Object Parameters
|
||||
[Serializable]
|
||||
abstract class SimpleObjectSubstation : StationParameter
|
||||
{
|
||||
internal SimpleObjectSubstation()
|
||||
{
|
||||
Station = GrabInteractableStation.k_StationName;
|
||||
Substation = GrabInteractableStation.k_SimpleObjectSubstationName;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
sealed class GrabSimpleObjectInstant : SimpleObjectSubstation
|
||||
{ }
|
||||
|
||||
[Serializable]
|
||||
sealed class GrabSimpleObjectKinematic : SimpleObjectSubstation
|
||||
{ }
|
||||
|
||||
[Serializable]
|
||||
sealed class GrabSimpleObjectVelocity : SimpleObjectSubstation
|
||||
{ }
|
||||
|
||||
// Watering Can Parameters
|
||||
[Serializable]
|
||||
abstract class WateringCanSubstation : StationParameter
|
||||
{
|
||||
internal WateringCanSubstation()
|
||||
{
|
||||
Station = GrabInteractableStation.k_StationName;
|
||||
Substation = GrabInteractableStation.k_WateringCanSubstationName;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
sealed class GrabWateringCan : WateringCanSubstation
|
||||
{ }
|
||||
|
||||
[Serializable]
|
||||
sealed class WateringPlant : WateringCanSubstation
|
||||
{ }
|
||||
|
||||
// Piggy Bank Parameters
|
||||
[Serializable]
|
||||
abstract class PiggyBankSubstation : StationParameter
|
||||
{
|
||||
internal PiggyBankSubstation()
|
||||
{
|
||||
Station = GrabInteractableStation.k_StationName;
|
||||
Substation = GrabInteractableStation.k_PiggyBankSubstationName;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
sealed class GrabMallet : PiggyBankSubstation
|
||||
{ }
|
||||
|
||||
[Serializable]
|
||||
sealed class BreakPiggyBank : PiggyBankSubstation
|
||||
{ }
|
||||
|
||||
// Grab Ribbon Stick Parameters
|
||||
[Serializable]
|
||||
sealed class GrabRibbonStick : StationParameter
|
||||
{
|
||||
internal GrabRibbonStick()
|
||||
{
|
||||
Station = GrabInteractableStation.k_StationName;
|
||||
Substation = GrabInteractableStation.k_RibbonStickSubstationName;
|
||||
}
|
||||
}
|
||||
|
||||
// Active Simple Object Parameters
|
||||
[Serializable]
|
||||
abstract class ActiveSimpleObjectSubstation : StationParameter
|
||||
{
|
||||
internal ActiveSimpleObjectSubstation()
|
||||
{
|
||||
Station = ActiveInteractableStation.k_StationName;
|
||||
Substation = ActiveInteractableStation.k_ActiveSimpleObjectSubstationName;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
sealed class GrabActiveSimpleObject : ActiveSimpleObjectSubstation
|
||||
{ }
|
||||
|
||||
[Serializable]
|
||||
sealed class SimpleObjectActivated : ActiveSimpleObjectSubstation
|
||||
{ }
|
||||
|
||||
// Candle Parameters
|
||||
[Serializable]
|
||||
abstract class CandleSubstation : StationParameter
|
||||
{
|
||||
internal CandleSubstation()
|
||||
{
|
||||
Station = ActiveInteractableStation.k_StationName;
|
||||
Substation = ActiveInteractableStation.k_CandleSubstationName;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
sealed class GrabLighter : CandleSubstation
|
||||
{ }
|
||||
|
||||
[Serializable]
|
||||
sealed class LighterActivated : CandleSubstation
|
||||
{ }
|
||||
|
||||
[Serializable]
|
||||
sealed class GrabCandle : CandleSubstation
|
||||
{ }
|
||||
|
||||
[Serializable]
|
||||
sealed class LightCandle : CandleSubstation
|
||||
{ }
|
||||
|
||||
// Launcher Parameters
|
||||
[Serializable]
|
||||
abstract class LauncherSubstation : StationParameter
|
||||
{
|
||||
internal LauncherSubstation()
|
||||
{
|
||||
Station = ActiveInteractableStation.k_StationName;
|
||||
Substation = ActiveInteractableStation.k_LauncherSubstationName;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
sealed class GrabLauncher : LauncherSubstation
|
||||
{ }
|
||||
|
||||
[Serializable]
|
||||
sealed class LauncherActivated : LauncherSubstation
|
||||
{ }
|
||||
|
||||
[Serializable]
|
||||
sealed class LauncherEasyTargetHit : LauncherSubstation
|
||||
{ }
|
||||
|
||||
[Serializable]
|
||||
sealed class LauncherMediumTargetHit : LauncherSubstation
|
||||
{ }
|
||||
|
||||
[Serializable]
|
||||
sealed class LauncherHardTargetHit : LauncherSubstation
|
||||
{ }
|
||||
|
||||
// Megaphone Parameters
|
||||
[Serializable]
|
||||
abstract class MegaphoneSubstation : StationParameter
|
||||
{
|
||||
internal MegaphoneSubstation()
|
||||
{
|
||||
Station = ActiveInteractableStation.k_StationName;
|
||||
Substation = ActiveInteractableStation.k_MegaphoneSubstationName;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
sealed class GrabMegaphone : MegaphoneSubstation
|
||||
{ }
|
||||
|
||||
[Serializable]
|
||||
sealed class MegaphoneActivated : MegaphoneSubstation
|
||||
{ }
|
||||
|
||||
// Socket Simple Object
|
||||
[Serializable]
|
||||
abstract class SimpleSocketSubstation : StationParameter
|
||||
{
|
||||
internal SimpleSocketSubstation()
|
||||
{
|
||||
Station = SocketInteractorStation.k_StationName;
|
||||
Substation = SocketInteractorStation.k_SimpleSocketSubstationName;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
sealed class ConnectSocketSimpleObject : SimpleSocketSubstation
|
||||
{ }
|
||||
|
||||
[Serializable]
|
||||
sealed class DisconnectSocketSimpleObject : SimpleSocketSubstation
|
||||
{ }
|
||||
|
||||
// Perler Machine
|
||||
[Serializable]
|
||||
abstract class PerlerMachineSubstation : StationParameter
|
||||
{
|
||||
internal PerlerMachineSubstation()
|
||||
{
|
||||
Station = SocketInteractorStation.k_StationName;
|
||||
Substation = SocketInteractorStation.k_PerlerMachineSubstationName;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
sealed class ConnectPerlerMachineBattery : PerlerMachineSubstation
|
||||
{ }
|
||||
|
||||
[Serializable]
|
||||
sealed class GrabPerlerBead : PerlerMachineSubstation
|
||||
{ }
|
||||
|
||||
[Serializable]
|
||||
sealed class ConnectPerlerBead : PerlerMachineSubstation
|
||||
{ }
|
||||
|
||||
// 3DUI Simple Controls
|
||||
[Serializable]
|
||||
abstract class SimpleControlsParameter : StationParameter
|
||||
{
|
||||
internal SimpleControlsParameter()
|
||||
{
|
||||
Station = Interaction3DUIStation.k_StationName;
|
||||
Substation = Interaction3DUIStation.k_3DUISimpleControlsSubstationName;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
sealed class LeverInteraction : SimpleControlsParameter
|
||||
{ }
|
||||
|
||||
[Serializable]
|
||||
sealed class JoystickInteraction : SimpleControlsParameter
|
||||
{ }
|
||||
|
||||
[Serializable]
|
||||
sealed class DialInteraction : SimpleControlsParameter
|
||||
{ }
|
||||
|
||||
[Serializable]
|
||||
sealed class WheelInteraction : SimpleControlsParameter
|
||||
{ }
|
||||
|
||||
[Serializable]
|
||||
sealed class SliderInteraction : SimpleControlsParameter
|
||||
{ }
|
||||
|
||||
[Serializable]
|
||||
sealed class GripButtonPressed : SimpleControlsParameter
|
||||
{ }
|
||||
|
||||
[Serializable]
|
||||
sealed class PushButtonPressed : SimpleControlsParameter
|
||||
{ }
|
||||
|
||||
// Claw Machine Parameters
|
||||
[Serializable]
|
||||
abstract class ClawMachineParameter : StationParameter
|
||||
{
|
||||
internal ClawMachineParameter()
|
||||
{
|
||||
Station = Interaction3DUIStation.k_StationName;
|
||||
Substation = Interaction3DUIStation.k_ClawMachineSubstationName;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
sealed class ClawMachineJoystickInteraction : ClawMachineParameter
|
||||
{ }
|
||||
|
||||
[Serializable]
|
||||
sealed class ClawMachinePushButtonPressed : ClawMachineParameter
|
||||
{ }
|
||||
|
||||
[Serializable]
|
||||
sealed class ConnectClawMachineToPrize : ClawMachineParameter
|
||||
{ }
|
||||
|
||||
[Serializable]
|
||||
sealed class GrabClawMachinePrize : ClawMachineParameter
|
||||
{ }
|
||||
|
||||
// Physics Simple Controls
|
||||
[Serializable]
|
||||
abstract class PhysicsSimpleControlsParameter : StationParameter
|
||||
{
|
||||
internal PhysicsSimpleControlsParameter()
|
||||
{
|
||||
Station = PhysicsInteractableStation.k_StationName;
|
||||
Substation = PhysicsInteractableStation.k_PhysicsSimpleControlsSubstationName;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
sealed class GrabSpringJoint : PhysicsSimpleControlsParameter
|
||||
{ }
|
||||
|
||||
[Serializable]
|
||||
sealed class GrabHingeJoint : PhysicsSimpleControlsParameter
|
||||
{ }
|
||||
|
||||
// Cabinet Example
|
||||
[Serializable]
|
||||
abstract class CabinetExampleParameter : StationParameter
|
||||
{
|
||||
internal CabinetExampleParameter()
|
||||
{
|
||||
Station = PhysicsInteractableStation.k_StationName;
|
||||
Substation = PhysicsInteractableStation.k_CabinetExampleSubstationName;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
sealed class GrabCabinet1 : CabinetExampleParameter
|
||||
{ }
|
||||
|
||||
[Serializable]
|
||||
sealed class GrabCabinet2 : CabinetExampleParameter
|
||||
{ }
|
||||
|
||||
// Doors Example
|
||||
[Serializable]
|
||||
abstract class DoorsExampleParameter : StationParameter
|
||||
{
|
||||
internal DoorsExampleParameter()
|
||||
{
|
||||
Station = PhysicsInteractableStation.k_StationName;
|
||||
Substation = PhysicsInteractableStation.k_DoorsExampleSubstationName;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
sealed class PushFlipDoor : DoorsExampleParameter
|
||||
{ }
|
||||
|
||||
[Serializable]
|
||||
sealed class GrabDoorKey : DoorsExampleParameter
|
||||
{ }
|
||||
|
||||
[Serializable]
|
||||
sealed class DoorUnlocked : DoorsExampleParameter
|
||||
{ }
|
||||
|
||||
[Serializable]
|
||||
sealed class DoorLocked : DoorsExampleParameter
|
||||
{ }
|
||||
|
||||
[Serializable]
|
||||
sealed class GrabDoorHandle : DoorsExampleParameter
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Editor event used to send <c>XRContent</c> interaction analytics data.
|
||||
/// Only accepts <c>InteractionEventParameter</c> objects as parameter.
|
||||
/// </summary>
|
||||
class InteractionEvent : EditorEvent
|
||||
{
|
||||
const string k_EventName = "xrcInteraction";
|
||||
|
||||
internal InteractionEvent(int maxPerHour = k_DefaultMaxEventsPerHour, int maxElementCount = k_DefaultMaxElementCount)
|
||||
: base(k_EventName, maxPerHour, maxElementCount)
|
||||
{ }
|
||||
|
||||
internal bool Send(InteractionEventParameter parameter)
|
||||
{
|
||||
return parameter != null && base.Send(parameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 14586b01c3c01004ba72418f6b2ed6c8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,3 @@
|
||||
// This file is generated. Do not modify by hand.
|
||||
// XML documentation file not found. To check if public methods have XML comments,
|
||||
// make sure the XML doc file is present and located next to the scraped dll
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6bd048109c94d014f8909979db78c6dd
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "Unity.XR.Content.Interaction.Analytics",
|
||||
"references": [
|
||||
"Unity.XR.Interaction.Toolkit",
|
||||
"Unity.XR.Content.Interaction"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [
|
||||
"Android",
|
||||
"CloudRendering",
|
||||
"GameCoreScarlett",
|
||||
"GameCoreXboxOne",
|
||||
"iOS",
|
||||
"LinuxStandalone64",
|
||||
"Lumin",
|
||||
"macOSStandalone",
|
||||
"PS4",
|
||||
"PS5",
|
||||
"Stadia",
|
||||
"Switch",
|
||||
"tvOS",
|
||||
"WSA",
|
||||
"WebGL",
|
||||
"WindowsStandalone32",
|
||||
"WindowsStandalone64",
|
||||
"XboxOne"
|
||||
],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": false,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9f85ef23860d39c4b844ae8331a7af94
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/XRI_Examples/Global/Scripts/Analytics/Utils.meta
Normal file
8
Assets/XRI_Examples/Global/Scripts/Analytics/Utils.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7067b90e80f2cd345855f8362f2abc5b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,95 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.XR.Interaction.Toolkit;
|
||||
|
||||
namespace UnityEngine.XR.Content.Interaction.Analytics
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains utility methods to easily send XRContent analytics data.
|
||||
/// </summary>
|
||||
class XrcAnalyticsUtils
|
||||
{
|
||||
internal static void Register(Door door, StationParameter lockedParameter, StationParameter unlockedParameter)
|
||||
{
|
||||
if (door == null)
|
||||
return;
|
||||
|
||||
door.onLock.AddListener(() => Send(lockedParameter));
|
||||
door.onUnlock.AddListener(() => Send(unlockedParameter));
|
||||
}
|
||||
|
||||
internal static void Register(XRPushButton pushButton, StationParameter parameter)
|
||||
{
|
||||
if (pushButton == null)
|
||||
return;
|
||||
|
||||
pushButton.onPress.AddListener(() => XrcAnalytics.interactionEvent.Send(parameter));
|
||||
}
|
||||
|
||||
internal static void Register(XRGripButton gripButton, StationParameter parameter)
|
||||
{
|
||||
if (gripButton == null)
|
||||
return;
|
||||
|
||||
gripButton.onPress.AddListener(() => XrcAnalytics.interactionEvent.Send(parameter));
|
||||
}
|
||||
|
||||
internal static void Register(XRSocketInteractor socket, StationParameter connectParameter, StationParameter disconnectParameter = null)
|
||||
{
|
||||
if (socket == null)
|
||||
return;
|
||||
|
||||
socket.selectEntered.AddListener(_ => Send(connectParameter));
|
||||
if (disconnectParameter != null)
|
||||
socket.selectExited.AddListener(args => OnSocketDisconnected(args, disconnectParameter));
|
||||
}
|
||||
|
||||
internal static void Register(IEnumerable<OnTrigger> onTriggers, StationParameter onEnterParameter)
|
||||
{
|
||||
foreach (var onTrigger in onTriggers)
|
||||
Register(onTrigger, onEnterParameter);
|
||||
}
|
||||
|
||||
internal static void Register(OnTrigger onTrigger, StationParameter onEnterParameter)
|
||||
{
|
||||
if (onTrigger == null)
|
||||
return;
|
||||
|
||||
onTrigger.onEnter.AddListener(otherGameObject => Send(onEnterParameter));
|
||||
}
|
||||
|
||||
internal static void Register(IEnumerable<XRBaseInteractable> interactables, StationParameter grabParameter, StationParameter activateParameter = null)
|
||||
{
|
||||
foreach (var interactable in interactables)
|
||||
Register(interactable, grabParameter, activateParameter);
|
||||
}
|
||||
|
||||
internal static void Register(XRBaseInteractable interactable, StationParameter grabParameter, StationParameter activateParameter = null)
|
||||
{
|
||||
if (interactable == null)
|
||||
return;
|
||||
|
||||
interactable.selectEntered.AddListener(args => OnGrabInteractable(args, grabParameter));
|
||||
if (activateParameter != null)
|
||||
interactable.activated.AddListener(_ => Send(activateParameter));
|
||||
}
|
||||
|
||||
static void OnSocketDisconnected(SelectExitEventArgs args, StationParameter parameter)
|
||||
{
|
||||
if (!args.isCanceled)
|
||||
XrcAnalytics.interactionEvent.Send(parameter);
|
||||
}
|
||||
|
||||
static void OnGrabInteractable(SelectEnterEventArgs args, StationParameter parameter)
|
||||
{
|
||||
if (!(args.interactorObject is XRBaseControllerInteractor))
|
||||
return;
|
||||
|
||||
XrcAnalytics.interactionEvent.Send(parameter);
|
||||
}
|
||||
|
||||
static void Send(StationParameter parameter)
|
||||
{
|
||||
XrcAnalytics.interactionEvent.Send(parameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fbfe64736ab97cd45b41f6c2052a799b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
43
Assets/XRI_Examples/Global/Scripts/Analytics/XrcAnalytics.cs
Normal file
43
Assets/XRI_Examples/Global/Scripts/Analytics/XrcAnalytics.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace UnityEngine.XR.Content.Interaction.Analytics
|
||||
{
|
||||
/// <summary>
|
||||
/// The entry point class to send XRContent analytics data.
|
||||
/// Stores all events usd by XRContent.
|
||||
/// </summary>
|
||||
#if UNITY_EDITOR
|
||||
[InitializeOnLoad]
|
||||
#endif
|
||||
static class XrcAnalytics
|
||||
{
|
||||
internal const string k_VendorKey = "unity.xrcontent.interaction";
|
||||
|
||||
internal static bool quitting { get; private set; }
|
||||
internal static bool disabled { get; }
|
||||
|
||||
internal static InteractionEvent interactionEvent { get; } = new InteractionEvent();
|
||||
|
||||
static XrcAnalytics()
|
||||
{
|
||||
// if the user has analytics disabled, respect that and make sure that no code actually tries to send events
|
||||
if (!interactionEvent.Register())
|
||||
{
|
||||
disabled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
EditorApplication.quitting += SetQuitting;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void SetQuitting()
|
||||
{
|
||||
// we set the Quitting variable so that we don't record window close events when the editor quits
|
||||
quitting = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f22e79d2138983e46bc000032bbb598d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,29 @@
|
||||
namespace UnityEngine.XR.Content.Animation
|
||||
{
|
||||
/// <summary>
|
||||
/// Enables a component to react to the 'ActionBegin' animation event.
|
||||
/// </summary>
|
||||
/// <seealso cref="IAnimationEventActionFinished"/>
|
||||
public interface IAnimationEventActionBegin
|
||||
{
|
||||
void ActionBegin(string label);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls the 'ActionBegin' function on any supported component when the target animation begins.
|
||||
/// </summary>
|
||||
/// <seealso cref="AnimationEventActionFinished"/>
|
||||
public class AnimationEventActionBegin : StateMachineBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
[Tooltip("A label identifying the animation that has started.")]
|
||||
string m_Label;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
|
||||
{
|
||||
var eventReceiver = animator.GetComponentInParent<IAnimationEventActionBegin>();
|
||||
eventReceiver?.ActionBegin(m_Label);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5d468d0443a045844aecfdcb26294078
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,29 @@
|
||||
namespace UnityEngine.XR.Content.Animation
|
||||
{
|
||||
/// <summary>
|
||||
/// Enables a component to react to the 'ActionFinished' animation event.
|
||||
/// </summary>
|
||||
/// <seealso cref="IAnimationEventActionBegin"/>
|
||||
public interface IAnimationEventActionFinished
|
||||
{
|
||||
void ActionFinished(string label);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls the 'ActionFinished' function on any supported component when the target animation exits.
|
||||
/// </summary>
|
||||
/// <seealso cref="AnimationEventActionBegin"/>
|
||||
public class AnimationEventActionFinished : StateMachineBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
[Tooltip("A label identifying the animation that has finished.")]
|
||||
string m_Label;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
|
||||
{
|
||||
var eventReceiver = animator.GetComponentInParent<IAnimationEventActionFinished>();
|
||||
eventReceiver?.ActionFinished(m_Label);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f0a02bc85c6eec44099da45f41b649bf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
36
Assets/XRI_Examples/Global/Scripts/AutoSocketAttach.cs
Normal file
36
Assets/XRI_Examples/Global/Scripts/AutoSocketAttach.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using UnityEngine.XR.Interaction.Toolkit;
|
||||
|
||||
namespace UnityEngine.XR.Content.Interaction
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes an <see cref="XRSocketInteractor"/> attach point to match the initial scene position of the object it is containing.
|
||||
/// </summary>
|
||||
public class AutoSocketAttach : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
[Tooltip("The Socket Interactor that controls this socket attach point.")]
|
||||
XRSocketInteractor m_ControllingInteractor;
|
||||
|
||||
void Start()
|
||||
{
|
||||
// If there is an existing interactable, we match its position so the object does not move
|
||||
if (m_ControllingInteractor == null)
|
||||
m_ControllingInteractor = GetComponentInParent<XRSocketInteractor>();
|
||||
|
||||
if (m_ControllingInteractor == null)
|
||||
{
|
||||
Debug.LogWarning("Script is not associated with an XRSocketInteractor and will have no effect.", this);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_ControllingInteractor.startingSelectedInteractable == null)
|
||||
{
|
||||
Debug.Log("AutoSocketAttach does not have a starting selected interactable to match its position.", this);
|
||||
return;
|
||||
}
|
||||
|
||||
var targetTransform = m_ControllingInteractor.startingSelectedInteractable.GetAttachTransform(m_ControllingInteractor);
|
||||
transform.SetPositionAndRotation(targetTransform.position, targetTransform.rotation);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/XRI_Examples/Global/Scripts/AutoSocketAttach.cs.meta
Normal file
11
Assets/XRI_Examples/Global/Scripts/AutoSocketAttach.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 130246251d17c034db762f6d1a49150e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
17
Assets/XRI_Examples/Global/Scripts/DestroyObject.cs
Normal file
17
Assets/XRI_Examples/Global/Scripts/DestroyObject.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace UnityEngine.XR.Content.Interaction
|
||||
{
|
||||
/// <summary>
|
||||
/// Destroys GameObject after a few seconds.
|
||||
/// </summary>
|
||||
public class DestroyObject : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
[Tooltip("Time before destroying in seconds.")]
|
||||
float m_Lifetime = 5f;
|
||||
|
||||
void Start()
|
||||
{
|
||||
Destroy(gameObject, m_Lifetime);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/XRI_Examples/Global/Scripts/DestroyObject.cs.meta
Normal file
11
Assets/XRI_Examples/Global/Scripts/DestroyObject.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b56b982c85b468f45a3260808d7ea637
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
414
Assets/XRI_Examples/Global/Scripts/InputMediator.cs
Normal file
414
Assets/XRI_Examples/Global/Scripts/InputMediator.cs
Normal file
@@ -0,0 +1,414 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.InputSystem.Controls;
|
||||
|
||||
namespace UnityEngine.XR.Content.Interaction
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows for actions to 'lock' the input controls they use so that others actions using the same controls will not receive input at the same time.
|
||||
/// InputMediator works by injecting input processors on every binding in active action maps.
|
||||
/// Usage is via <see cref="ConsumeControl"/> and <see cref="ReleaseControl"/>.
|
||||
/// </summary>
|
||||
public static class InputMediator
|
||||
{
|
||||
/// <summary>
|
||||
/// Substring of the names that all of the input processors that are injected have.
|
||||
/// </summary>
|
||||
/// <seealso cref="Initialize"/>
|
||||
const string k_ConsumeKey = "Consume";
|
||||
static bool s_Updating;
|
||||
|
||||
// Data associated with each control, storing if an action has locked it,
|
||||
// and other actions that are allowed to make use of this control at the same time
|
||||
class ConsumptionState
|
||||
{
|
||||
public int m_LockedAction = -1;
|
||||
public int m_AllowedAction1 = -1;
|
||||
public int m_AllowedAction2 = -1;
|
||||
|
||||
public bool m_Automatic;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generic Consumption processor - handles all the aspects of looking up actions that have locked a control
|
||||
/// Implementations merely need to implement the methods to determine if a control has returned to rest (and thus should reset)
|
||||
/// And the 'identity' value of a control, which is the value it should have when the control is locked
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue"></typeparam>
|
||||
abstract class ConsumeProcessor<TValue> : InputProcessor<TValue> where TValue : struct
|
||||
{
|
||||
public int m_ActionIndex = -1;
|
||||
|
||||
public override TValue Process(TValue value, InputControl control)
|
||||
{
|
||||
// Check the dictionary for this control
|
||||
// If it does not exist, proceed unhindered
|
||||
if (control == null || !s_ConsumedControls.TryGetValue(control, out var currentState))
|
||||
return value;
|
||||
|
||||
// If there is no locked action, also proceed unhindered
|
||||
if (currentState.m_LockedAction == -1)
|
||||
return value;
|
||||
|
||||
// Check for an action match
|
||||
var actionMatched = (currentState.m_LockedAction == m_ActionIndex) || (currentState.m_AllowedAction1 == m_ActionIndex) || (currentState.m_AllowedAction2 == m_ActionIndex);
|
||||
|
||||
// Check if we should automatically release
|
||||
if (actionMatched)
|
||||
{
|
||||
if (currentState.m_Automatic && ValueNearZero(value))
|
||||
currentState.m_LockedAction = -1;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
return IdentityValue();
|
||||
}
|
||||
|
||||
public abstract bool ValueNearZero(TValue value);
|
||||
|
||||
public abstract TValue IdentityValue();
|
||||
}
|
||||
|
||||
class ConsumeFloat : ConsumeProcessor<float>
|
||||
{
|
||||
public override bool ValueNearZero(float value)
|
||||
{
|
||||
return value < float.Epsilon;
|
||||
}
|
||||
|
||||
public override float IdentityValue()
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
class ConsumeVector2 : ConsumeProcessor<Vector2>
|
||||
{
|
||||
public override bool ValueNearZero(Vector2 value)
|
||||
{
|
||||
return value.sqrMagnitude < float.Epsilon;
|
||||
}
|
||||
|
||||
public override Vector2 IdentityValue()
|
||||
{
|
||||
return Vector2.zero;
|
||||
}
|
||||
}
|
||||
|
||||
class ConsumeVector3 : ConsumeProcessor<Vector3>
|
||||
{
|
||||
public override bool ValueNearZero(Vector3 value)
|
||||
{
|
||||
return value.sqrMagnitude < float.Epsilon;
|
||||
}
|
||||
|
||||
public override Vector3 IdentityValue()
|
||||
{
|
||||
return Vector3.zero;
|
||||
}
|
||||
}
|
||||
|
||||
class ConsumeQuaternion : ConsumeProcessor<Quaternion>
|
||||
{
|
||||
public override bool ValueNearZero(Quaternion value)
|
||||
{
|
||||
return Quaternion.Angle(value, Quaternion.identity) < float.Epsilon;
|
||||
}
|
||||
|
||||
public override Quaternion IdentityValue()
|
||||
{
|
||||
return Quaternion.identity;
|
||||
}
|
||||
}
|
||||
|
||||
static Dictionary<InputControl, ConsumptionState> s_ConsumedControls = new Dictionary<InputControl, ConsumptionState>();
|
||||
static Dictionary<InputAction, int> s_ActionIndices = new Dictionary<InputAction, int>();
|
||||
static HashSet<InputAction> s_InitializedActions = new HashSet<InputAction>();
|
||||
|
||||
[RuntimeInitializeOnLoadMethod]
|
||||
static void Initialize()
|
||||
{
|
||||
InputSystem.InputSystem.RegisterProcessor<ConsumeFloat>(nameof(ConsumeFloat));
|
||||
InputSystem.InputSystem.RegisterProcessor<ConsumeVector2>(nameof(ConsumeVector2));
|
||||
InputSystem.InputSystem.RegisterProcessor<ConsumeVector3>(nameof(ConsumeVector3));
|
||||
InputSystem.InputSystem.RegisterProcessor<ConsumeQuaternion>(nameof(ConsumeQuaternion));
|
||||
Application.quitting += OnApplicationQuitting;
|
||||
InputSystem.InputSystem.onActionChange += OnActionChange;
|
||||
InitializeConsumeProcessors();
|
||||
}
|
||||
|
||||
static void OnApplicationQuitting()
|
||||
{
|
||||
InputSystem.InputSystem.onActionChange -= OnActionChange;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to 'lock' the controls belonging to an action - which means other actions using the same control will only get zero/identity values during this time
|
||||
/// </summary>
|
||||
/// <param name="source">The action that should lock their controls</param>
|
||||
/// <param name="automaticRelease">If the control lock should release automatically when the controls go to a resting state</param>
|
||||
/// <param name="force">If the action should forcefully take a lock from another consuming action</param>
|
||||
/// <param name="friendAction1">An additional action that can access these controls at this time</param>
|
||||
/// <param name="friendAction2">An additional action that can access these controls at this time</param>
|
||||
/// <returns>False if _any_ of the associated controls were unable to be locked </returns>
|
||||
public static bool ConsumeControl(InputAction source, bool automaticRelease, bool force = false, InputAction friendAction1 = null, InputAction friendAction2 = null)
|
||||
{
|
||||
if (source == null)
|
||||
return false;
|
||||
|
||||
var actionIndex1 = GetActionIndex(source);
|
||||
var actionIndex2 = GetActionIndex(friendAction1);
|
||||
var actionIndex3 = GetActionIndex(friendAction2);
|
||||
var lockCount = 0;
|
||||
|
||||
var sourceControls = source.controls;
|
||||
foreach (var currentControl in sourceControls)
|
||||
{
|
||||
// Check to see if it is in the list already
|
||||
// If not, make an entry for it
|
||||
if (!s_ConsumedControls.TryGetValue(currentControl, out var controlState))
|
||||
{
|
||||
var parent = currentControl.parent;
|
||||
if (currentControl is AxisControl && parent is Vector2Control)
|
||||
{
|
||||
if (!s_ConsumedControls.TryGetValue(parent, out controlState))
|
||||
{
|
||||
controlState = new ConsumptionState { m_Automatic = automaticRelease };
|
||||
s_ConsumedControls.Add(parent, controlState);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
controlState = new ConsumptionState { m_Automatic = automaticRelease };
|
||||
}
|
||||
|
||||
s_ConsumedControls.Add(currentControl, controlState);
|
||||
}
|
||||
|
||||
if (force || controlState.m_LockedAction == -1)
|
||||
{
|
||||
controlState.m_LockedAction = actionIndex1;
|
||||
controlState.m_AllowedAction1 = actionIndex2;
|
||||
controlState.m_AllowedAction2 = actionIndex3;
|
||||
lockCount++;
|
||||
}
|
||||
}
|
||||
return (lockCount == sourceControls.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases an action's lock over its associated controls. Other actions using the same controls will begin receiving input again
|
||||
/// </summary>
|
||||
/// <param name="source">The action that is attempting to release its lock</param>
|
||||
/// <param name="force">If this input lock should be released regardless of requesting action</param>
|
||||
/// <returns>False if _any_ of the associated controls were unable to be released </returns>
|
||||
public static bool ReleaseControl(InputAction source, bool force = false)
|
||||
{
|
||||
if (source == null)
|
||||
return false;
|
||||
|
||||
var actionIndex = GetActionIndex(source);
|
||||
var lockCount = 0;
|
||||
|
||||
var sourceControls = source.controls;
|
||||
foreach (var currentControl in sourceControls)
|
||||
{
|
||||
// Check to see if it is in the list already
|
||||
// If not, nothing to release
|
||||
if (!s_ConsumedControls.TryGetValue(currentControl, out var controlState))
|
||||
{
|
||||
lockCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (force || controlState.m_LockedAction == actionIndex)
|
||||
{
|
||||
controlState.m_LockedAction = -1;
|
||||
lockCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return (lockCount == sourceControls.Count);
|
||||
}
|
||||
|
||||
static void InitializeConsumeProcessors()
|
||||
{
|
||||
s_Updating = true;
|
||||
var actionList = InputSystem.InputSystem.ListEnabledActions();
|
||||
foreach (var action in actionList)
|
||||
{
|
||||
EnsureConsumeProcessorAdded(action);
|
||||
|
||||
// Since this list only contains currently enabled actions,
|
||||
// any actions that are enabled later will need to
|
||||
// have the consume processor added. Since those actions may not
|
||||
// trigger a BoundControlsChanged change, the OnActionChange event handler
|
||||
// will check against this list and append to it as actions are enabled.
|
||||
// This set is checked against for performance reasons
|
||||
// to avoid the more costly EnsureConsumeProcessorAdded(InputAction) method.
|
||||
s_InitializedActions.Add(action);
|
||||
}
|
||||
s_Updating = false;
|
||||
}
|
||||
|
||||
static void OnActionChange(object actionSource, InputActionChange change)
|
||||
{
|
||||
if (s_Updating)
|
||||
return;
|
||||
|
||||
s_Updating = true;
|
||||
|
||||
if (change == InputActionChange.ActionEnabled)
|
||||
{
|
||||
var action = (InputAction)actionSource;
|
||||
if (s_InitializedActions.Add(action))
|
||||
EnsureConsumeProcessorAdded(action);
|
||||
}
|
||||
else if (change == InputActionChange.ActionMapEnabled)
|
||||
{
|
||||
var actionMap = (InputActionMap)actionSource;
|
||||
foreach (var action in actionMap.actions)
|
||||
{
|
||||
if (s_InitializedActions.Add(action))
|
||||
EnsureConsumeProcessorAdded(action);
|
||||
}
|
||||
}
|
||||
else if (change == InputActionChange.BoundControlsChanged)
|
||||
{
|
||||
// We skip pure actions here as they can get into an invalid state if bindings were changed
|
||||
if (actionSource is InputActionMap actionMap)
|
||||
{
|
||||
EnsureConsumeProcessorAdded(actionMap);
|
||||
}
|
||||
else if (actionSource is InputActionAsset actionAsset)
|
||||
{
|
||||
EnsureConsumeProcessorAdded(actionAsset);
|
||||
}
|
||||
}
|
||||
|
||||
s_Updating = false;
|
||||
}
|
||||
|
||||
static string ControlTypeToConsumeType(string controlType)
|
||||
{
|
||||
switch (controlType)
|
||||
{
|
||||
case "Single":
|
||||
case "Button":
|
||||
case "float":
|
||||
return nameof(ConsumeFloat);
|
||||
case "Vector2":
|
||||
return nameof(ConsumeVector2);
|
||||
case "Vector3":
|
||||
return nameof(ConsumeVector3);
|
||||
case "Quaternion":
|
||||
return nameof(ConsumeQuaternion);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
static string ProcessBindingControl(string bindingPath)
|
||||
{
|
||||
var control = InputSystem.InputSystem.FindControl(bindingPath);
|
||||
var consumeType = "";
|
||||
|
||||
if (control != null)
|
||||
consumeType = ControlTypeToConsumeType(control.valueType.Name);
|
||||
else
|
||||
{
|
||||
// Try to fall back based on path keywords
|
||||
var bindingLower = bindingPath.ToLower();
|
||||
if (bindingLower.EndsWith("position"))
|
||||
consumeType = ControlTypeToConsumeType("Vector3");
|
||||
|
||||
if (bindingLower.EndsWith("rotation"))
|
||||
consumeType = ControlTypeToConsumeType("Quaternion");
|
||||
|
||||
if (bindingLower.EndsWith("x"))
|
||||
consumeType = ControlTypeToConsumeType("float");
|
||||
|
||||
if (bindingLower.EndsWith("y"))
|
||||
consumeType = ControlTypeToConsumeType("float");
|
||||
|
||||
if (bindingLower.EndsWith("axis"))
|
||||
consumeType = ControlTypeToConsumeType("Vector2");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(consumeType))
|
||||
return "";
|
||||
|
||||
return consumeType;
|
||||
}
|
||||
|
||||
static void EnsureConsumeProcessorAdded(InputAction action)
|
||||
{
|
||||
var bindingCount = action.bindings.Count;
|
||||
for (var i = 0; i < bindingCount; i++)
|
||||
{
|
||||
var currentBinding = action.bindings[i];
|
||||
|
||||
// Ignore composites, but not parts of composites
|
||||
if (currentBinding.isComposite)
|
||||
continue;
|
||||
|
||||
// Ignore bindings that aren't ready yet
|
||||
if (currentBinding.effectiveProcessors == null)
|
||||
continue;
|
||||
|
||||
var actionIndex = GetActionIndex(action);
|
||||
if (!currentBinding.effectiveProcessors.Contains(k_ConsumeKey))
|
||||
{
|
||||
// Ignore unused bindings
|
||||
if (string.IsNullOrEmpty(currentBinding.path))
|
||||
continue;
|
||||
|
||||
// Get the binding's control type and cache it in the control lookup
|
||||
var bindingType = ProcessBindingControl(currentBinding.path);
|
||||
|
||||
// If the composite can't figure out its type, then skip it
|
||||
if (string.IsNullOrEmpty(bindingType))
|
||||
{
|
||||
//Debug.LogWarning($"Could not add consume processor for binding { currentBinding.path }, in {action.name}");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (currentBinding.processors.Length > 0)
|
||||
action.ApplyBindingOverride(i, new InputBinding { overrideProcessors = $"{bindingType}(m_ActionIndex={actionIndex}), {currentBinding.processors}" });
|
||||
else
|
||||
action.ApplyBindingOverride(i, new InputBinding { overrideProcessors = $"{bindingType}(m_ActionIndex={actionIndex})" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void EnsureConsumeProcessorAdded(InputActionMap actionMap)
|
||||
{
|
||||
foreach (var action in actionMap.actions)
|
||||
{
|
||||
EnsureConsumeProcessorAdded(action);
|
||||
}
|
||||
}
|
||||
|
||||
static void EnsureConsumeProcessorAdded(InputActionAsset actionAsset)
|
||||
{
|
||||
foreach (var map in actionAsset.actionMaps)
|
||||
{
|
||||
EnsureConsumeProcessorAdded(map);
|
||||
}
|
||||
}
|
||||
|
||||
static int GetActionIndex(InputAction source)
|
||||
{
|
||||
if (source == null)
|
||||
return -1;
|
||||
|
||||
if (!s_ActionIndices.TryGetValue(source, out var actionIndex))
|
||||
{
|
||||
actionIndex = s_ActionIndices.Count;
|
||||
s_ActionIndices.Add(source, actionIndex);
|
||||
}
|
||||
|
||||
return actionIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/XRI_Examples/Global/Scripts/InputMediator.cs.meta
Normal file
11
Assets/XRI_Examples/Global/Scripts/InputMediator.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3e05b88149759dd47aa37ca6dfe979cc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
79
Assets/XRI_Examples/Global/Scripts/InteractionAnimator.cs
Normal file
79
Assets/XRI_Examples/Global/Scripts/InteractionAnimator.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
using UnityEngine.Playables;
|
||||
using UnityEngine.XR.Interaction.Toolkit;
|
||||
|
||||
namespace UnityEngine.XR.Content.Interaction
|
||||
{
|
||||
/// <summary>
|
||||
/// Component that when paired with an interactable will drive an associated timeline with the activate button
|
||||
/// Must be used with an action-based controller
|
||||
/// </summary>
|
||||
public class InteractionAnimator : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
[Tooltip("The timeline to drive with the activation button.")]
|
||||
PlayableDirector m_ToAnimate;
|
||||
|
||||
bool m_Animating;
|
||||
XRBaseController m_Controller;
|
||||
|
||||
void Start()
|
||||
{
|
||||
// We want to hook up to the Select events so we can read data about the interacting controller
|
||||
var interactable = GetComponent<IXRSelectInteractable>();
|
||||
if (interactable == null || interactable as Object == null)
|
||||
{
|
||||
Debug.LogWarning($"No interactable on {name} - no animation will be played.", this);
|
||||
enabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_ToAnimate == null)
|
||||
{
|
||||
Debug.LogWarning($"No timeline configured on {name} - no animation will be played.", this);
|
||||
enabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
interactable.selectEntered.AddListener(OnSelect);
|
||||
interactable.selectExited.AddListener(OnDeselect);
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (m_Animating && m_Controller != null)
|
||||
{
|
||||
var floatValue = m_Controller.activateInteractionState.value;
|
||||
m_ToAnimate.time = floatValue;
|
||||
}
|
||||
}
|
||||
|
||||
void OnSelect(SelectEnterEventArgs args)
|
||||
{
|
||||
// Get the controller from the interactor, and then the activation control from there
|
||||
var controllerInteractor = args.interactorObject as XRBaseControllerInteractor;
|
||||
if (controllerInteractor == null)
|
||||
{
|
||||
Debug.LogWarning($"Selected by {args.interactorObject.transform.name}, which is not an XRBaseControllerInteractor", this);
|
||||
return;
|
||||
}
|
||||
|
||||
m_Controller = controllerInteractor.xrController;
|
||||
if (m_Controller == null)
|
||||
{
|
||||
Debug.LogWarning($"Selected by {controllerInteractor.name}, which does not have a valid XRBaseController", this);
|
||||
return;
|
||||
}
|
||||
|
||||
// Ready to animate
|
||||
m_ToAnimate.Play();
|
||||
m_Animating = true;
|
||||
}
|
||||
|
||||
void OnDeselect(SelectExitEventArgs args)
|
||||
{
|
||||
m_Animating = false;
|
||||
m_ToAnimate.Stop();
|
||||
m_Controller = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b160c11b363003842a85c3f90316faca
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
317
Assets/XRI_Examples/Global/Scripts/LocomotionManager.cs
Normal file
317
Assets/XRI_Examples/Global/Scripts/LocomotionManager.cs
Normal file
@@ -0,0 +1,317 @@
|
||||
using UnityEngine.XR.Interaction.Toolkit;
|
||||
using UnityEngine.XR.Interaction.Toolkit.Samples.StarterAssets;
|
||||
|
||||
namespace UnityEngine.XR.Content.Interaction
|
||||
{
|
||||
/// <summary>
|
||||
/// Use this class as a central manager to configure locomotion control schemes and configuration preferences.
|
||||
/// </summary>
|
||||
public class LocomotionManager : MonoBehaviour
|
||||
{
|
||||
const ContinuousMoveProviderBase.GravityApplicationMode k_DefaultGravityApplicationMode =
|
||||
ContinuousMoveProviderBase.GravityApplicationMode.AttemptingMove;
|
||||
const ConstrainedMoveProvider.GravityApplicationMode k_DefaultGravityMode =
|
||||
ConstrainedMoveProvider.GravityApplicationMode.AttemptingMove;
|
||||
|
||||
/// <summary>
|
||||
/// Sets which movement control scheme to use.
|
||||
/// </summary>
|
||||
/// <seealso cref="leftHandLocomotionType"/>
|
||||
/// <seealso cref="rightHandLocomotionType"/>
|
||||
public enum LocomotionType
|
||||
{
|
||||
/// <summary>
|
||||
/// Use smooth (continuous) movement control scheme.
|
||||
/// </summary>
|
||||
MoveAndStrafe,
|
||||
|
||||
/// <summary>
|
||||
/// Use teleport movement control scheme.
|
||||
/// </summary>
|
||||
TeleportAndTurn,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets which turn style of locomotion to use.
|
||||
/// </summary>
|
||||
/// <seealso cref="leftHandTurnStyle"/>
|
||||
/// <seealso cref="rightHandTurnStyle"/>
|
||||
public enum TurnStyle
|
||||
{
|
||||
/// <summary>
|
||||
/// Use snap turning to rotate the direction you are facing by snapping by a specified angle.
|
||||
/// </summary>
|
||||
Snap,
|
||||
|
||||
/// <summary>
|
||||
/// Use continuous turning to smoothly rotate the direction you are facing by a specified speed.
|
||||
/// </summary>
|
||||
Smooth,
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Stores the locomotion provider for smooth (continuous) movement.")]
|
||||
DynamicMoveProvider m_DynamicMoveProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Stores the locomotion provider for smooth (continuous) movement.
|
||||
/// </summary>
|
||||
/// <seealso cref="DynamicMoveProvider"/>
|
||||
public DynamicMoveProvider dynamicMoveProvider
|
||||
{
|
||||
get => m_DynamicMoveProvider;
|
||||
set => m_DynamicMoveProvider = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Stores the locomotion provider for smooth (continuous) turning.")]
|
||||
ContinuousTurnProviderBase m_SmoothTurnProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Stores the locomotion provider for smooth (continuous) turning.
|
||||
/// </summary>
|
||||
/// <seealso cref="ContinuousTurnProviderBase"/>
|
||||
public ContinuousTurnProviderBase smoothTurnProvider
|
||||
{
|
||||
get => m_SmoothTurnProvider;
|
||||
set => m_SmoothTurnProvider = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Stores the locomotion provider for snap turning.")]
|
||||
SnapTurnProviderBase m_SnapTurnProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Stores the locomotion provider for snap turning.
|
||||
/// </summary>
|
||||
/// <seealso cref="SnapTurnProviderBase"/>
|
||||
public SnapTurnProviderBase snapTurnProvider
|
||||
{
|
||||
get => m_SnapTurnProvider;
|
||||
set => m_SnapTurnProvider = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Stores the locomotion provider for two-handed grab movement.")]
|
||||
TwoHandedGrabMoveProvider m_TwoHandedGrabMoveProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Stores the locomotion provider for two-handed grab movement.
|
||||
/// </summary>
|
||||
/// <seealso cref="TwoHandedGrabMoveProvider"/>
|
||||
public TwoHandedGrabMoveProvider twoHandedGrabMoveProvider
|
||||
{
|
||||
get => m_TwoHandedGrabMoveProvider;
|
||||
set => m_TwoHandedGrabMoveProvider = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Reference to the manager that mediates the left-hand controllers.")]
|
||||
ActionBasedControllerManager m_LeftHandManager;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Reference to the manager that mediates the right-hand controllers.")]
|
||||
ActionBasedControllerManager m_RightHandManager;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Controls which movement control scheme to use for the left hand.")]
|
||||
LocomotionType m_LeftHandLocomotionType;
|
||||
|
||||
/// <summary>
|
||||
/// Controls which movement control scheme to use for the left hand.
|
||||
/// </summary>
|
||||
/// <seealso cref="LocomotionType"/>
|
||||
public LocomotionType leftHandLocomotionType
|
||||
{
|
||||
get => m_LeftHandLocomotionType;
|
||||
set
|
||||
{
|
||||
SetMoveScheme(value, true);
|
||||
m_LeftHandLocomotionType = value;
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Controls which movement control scheme to use for the right hand.")]
|
||||
LocomotionType m_RightHandLocomotionType;
|
||||
|
||||
/// <summary>
|
||||
/// Controls which movement control scheme to use for the left hand.
|
||||
/// </summary>
|
||||
/// <seealso cref="LocomotionType"/>
|
||||
public LocomotionType rightHandLocomotionType
|
||||
{
|
||||
get => m_RightHandLocomotionType;
|
||||
set
|
||||
{
|
||||
SetMoveScheme(value, false);
|
||||
m_RightHandLocomotionType = value;
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Controls which turn style of locomotion to use for the left hand.")]
|
||||
TurnStyle m_LeftHandTurnStyle;
|
||||
|
||||
/// <summary>
|
||||
/// Controls which turn style of locomotion to use for the left hand.
|
||||
/// </summary>
|
||||
/// <seealso cref="TurnStyle"/>
|
||||
public TurnStyle leftHandTurnStyle
|
||||
{
|
||||
get => m_LeftHandTurnStyle;
|
||||
set
|
||||
{
|
||||
SetTurnStyle(value, true);
|
||||
m_LeftHandTurnStyle = value;
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Controls which turn style of locomotion to use for the right hand.")]
|
||||
TurnStyle m_RightHandTurnStyle;
|
||||
|
||||
/// <summary>
|
||||
/// Controls which turn style of locomotion to use for the left hand.
|
||||
/// </summary>
|
||||
/// <seealso cref="TurnStyle"/>
|
||||
public TurnStyle rightHandTurnStyle
|
||||
{
|
||||
get => m_RightHandTurnStyle;
|
||||
set
|
||||
{
|
||||
SetTurnStyle(value, false);
|
||||
m_RightHandTurnStyle = value;
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Whether to enable the comfort mode that applies the tunneling vignette effect to smooth movement and turn.")]
|
||||
bool m_EnableComfortMode;
|
||||
|
||||
public bool enableComfortMode
|
||||
{
|
||||
get => m_EnableComfortMode;
|
||||
set
|
||||
{
|
||||
m_EnableComfortMode = value;
|
||||
if (m_ComfortMode != null)
|
||||
m_ComfortMode.SetActive(m_EnableComfortMode);
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Stores the GameObject for the comfort mode.")]
|
||||
GameObject m_ComfortMode;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Whether gravity affects continuous and grab movement when flying is disabled.")]
|
||||
bool m_UseGravity;
|
||||
|
||||
/// <summary>
|
||||
/// Whether gravity affects continuous and grab movement when flying is disabled.
|
||||
/// </summary>
|
||||
public bool useGravity
|
||||
{
|
||||
get => m_UseGravity;
|
||||
set
|
||||
{
|
||||
m_UseGravity = value;
|
||||
m_DynamicMoveProvider.useGravity = value;
|
||||
m_TwoHandedGrabMoveProvider.useGravity = value;
|
||||
m_TwoHandedGrabMoveProvider.leftGrabMoveProvider.useGravity = value;
|
||||
m_TwoHandedGrabMoveProvider.rightGrabMoveProvider.useGravity = value;
|
||||
if (value)
|
||||
{
|
||||
m_DynamicMoveProvider.gravityApplicationMode = k_DefaultGravityApplicationMode;
|
||||
m_TwoHandedGrabMoveProvider.gravityMode = k_DefaultGravityMode;
|
||||
m_TwoHandedGrabMoveProvider.leftGrabMoveProvider.gravityMode = k_DefaultGravityMode;
|
||||
m_TwoHandedGrabMoveProvider.rightGrabMoveProvider.gravityMode = k_DefaultGravityMode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Whether to enable flying for continuous and grab movement. This overrides the use of gravity.")]
|
||||
bool m_EnableFly;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to enable flying for continuous and grab movement. This overrides the use of gravity.
|
||||
/// </summary>
|
||||
public bool enableFly
|
||||
{
|
||||
get => m_EnableFly;
|
||||
set
|
||||
{
|
||||
m_EnableFly = value;
|
||||
m_DynamicMoveProvider.enableFly = value;
|
||||
m_TwoHandedGrabMoveProvider.enableFreeYMovement = value;
|
||||
m_TwoHandedGrabMoveProvider.leftGrabMoveProvider.enableFreeYMovement = value;
|
||||
m_TwoHandedGrabMoveProvider.rightGrabMoveProvider.enableFreeYMovement = value;
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Whether to enable grab movement.")]
|
||||
bool m_EnableGrabMovement;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to enable grab movement.
|
||||
/// </summary>
|
||||
public bool enableGrabMovement
|
||||
{
|
||||
get => m_EnableGrabMovement;
|
||||
set
|
||||
{
|
||||
m_EnableGrabMovement = value;
|
||||
m_TwoHandedGrabMoveProvider.enabled = value;
|
||||
m_TwoHandedGrabMoveProvider.leftGrabMoveProvider.enabled = value;
|
||||
m_TwoHandedGrabMoveProvider.rightGrabMoveProvider.enabled = value;
|
||||
}
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
SetMoveScheme(m_LeftHandLocomotionType, true);
|
||||
SetMoveScheme(m_RightHandLocomotionType, false);
|
||||
SetTurnStyle(m_LeftHandTurnStyle, true);
|
||||
SetTurnStyle(m_RightHandTurnStyle, false);
|
||||
|
||||
if (m_ComfortMode != null)
|
||||
m_ComfortMode.SetActive(m_EnableComfortMode);
|
||||
|
||||
m_DynamicMoveProvider.useGravity = m_UseGravity;
|
||||
m_TwoHandedGrabMoveProvider.useGravity = m_UseGravity;
|
||||
m_TwoHandedGrabMoveProvider.leftGrabMoveProvider.useGravity = m_UseGravity;
|
||||
m_TwoHandedGrabMoveProvider.rightGrabMoveProvider.useGravity = m_UseGravity;
|
||||
if (m_UseGravity)
|
||||
{
|
||||
m_DynamicMoveProvider.gravityApplicationMode = k_DefaultGravityApplicationMode;
|
||||
m_TwoHandedGrabMoveProvider.gravityMode = k_DefaultGravityMode;
|
||||
m_TwoHandedGrabMoveProvider.leftGrabMoveProvider.gravityMode = k_DefaultGravityMode;
|
||||
m_TwoHandedGrabMoveProvider.rightGrabMoveProvider.gravityMode = k_DefaultGravityMode;
|
||||
}
|
||||
|
||||
m_DynamicMoveProvider.enableFly = m_EnableFly;
|
||||
m_TwoHandedGrabMoveProvider.enableFreeYMovement = m_EnableFly;
|
||||
m_TwoHandedGrabMoveProvider.leftGrabMoveProvider.enableFreeYMovement = m_EnableFly;
|
||||
m_TwoHandedGrabMoveProvider.rightGrabMoveProvider.enableFreeYMovement = m_EnableFly;
|
||||
|
||||
m_TwoHandedGrabMoveProvider.enabled = m_EnableGrabMovement;
|
||||
m_TwoHandedGrabMoveProvider.leftGrabMoveProvider.enabled = m_EnableGrabMovement;
|
||||
m_TwoHandedGrabMoveProvider.rightGrabMoveProvider.enabled = m_EnableGrabMovement;
|
||||
}
|
||||
|
||||
void SetMoveScheme(LocomotionType scheme, bool leftHand)
|
||||
{
|
||||
var targetHand = leftHand ? m_LeftHandManager : m_RightHandManager;
|
||||
targetHand.smoothMotionEnabled = (scheme == LocomotionType.MoveAndStrafe);
|
||||
}
|
||||
|
||||
void SetTurnStyle(TurnStyle style, bool leftHand)
|
||||
{
|
||||
var targetHand = leftHand ? m_LeftHandManager : m_RightHandManager;
|
||||
targetHand.smoothTurnEnabled = (style == TurnStyle.Smooth);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/XRI_Examples/Global/Scripts/LocomotionManager.cs.meta
Normal file
11
Assets/XRI_Examples/Global/Scripts/LocomotionManager.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fa74d384629b02340bc59709603b0771
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
75
Assets/XRI_Examples/Global/Scripts/ObjectReset.cs
Normal file
75
Assets/XRI_Examples/Global/Scripts/ObjectReset.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnityEngine.XR.Content.Interaction
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides the ability to reset specified objects if they fall below a certain position - designated by this transform's height.
|
||||
/// </summary>
|
||||
public class ObjectReset : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
[Tooltip("Which objects to reset if falling out of range.")]
|
||||
List<Transform> m_ObjectsToReset = new List<Transform>();
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("How often to check if objects should be reset.")]
|
||||
float m_CheckDuration = 2f;
|
||||
|
||||
readonly List<Pose> m_OriginalPositions = new List<Pose>();
|
||||
|
||||
float m_CheckTimer;
|
||||
|
||||
/// <summary>
|
||||
/// See <see cref="MonoBehaviour"/>.
|
||||
/// </summary>
|
||||
protected void Start()
|
||||
{
|
||||
foreach (var currentTransform in m_ObjectsToReset)
|
||||
{
|
||||
if (currentTransform != null)
|
||||
{
|
||||
m_OriginalPositions.Add(new Pose(currentTransform.position, currentTransform.rotation));
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("Objects To Reset contained a null element. Update the reference or delete the array element of the missing object.", this);
|
||||
m_OriginalPositions.Add(new Pose());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// See <see cref="MonoBehaviour"/>.
|
||||
/// </summary>
|
||||
protected void Update()
|
||||
{
|
||||
m_CheckTimer -= Time.deltaTime;
|
||||
|
||||
if (m_CheckTimer > 0)
|
||||
return;
|
||||
|
||||
m_CheckTimer = m_CheckDuration;
|
||||
|
||||
var resetPlane = transform.position.y;
|
||||
|
||||
for (var transformIndex = 0; transformIndex < m_ObjectsToReset.Count; transformIndex++)
|
||||
{
|
||||
var currentTransform = m_ObjectsToReset[transformIndex];
|
||||
if (currentTransform == null)
|
||||
continue;
|
||||
|
||||
if (currentTransform.position.y < resetPlane)
|
||||
{
|
||||
currentTransform.SetPositionAndRotation(m_OriginalPositions[transformIndex].position, m_OriginalPositions[transformIndex].rotation);
|
||||
|
||||
var rigidBody = currentTransform.GetComponentInChildren<Rigidbody>();
|
||||
if (rigidBody != null)
|
||||
{
|
||||
rigidBody.velocity = Vector3.zero;
|
||||
rigidBody.angularVelocity = Vector3.zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/XRI_Examples/Global/Scripts/ObjectReset.cs.meta
Normal file
11
Assets/XRI_Examples/Global/Scripts/ObjectReset.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 44d90717e41f7864b9265cc48de52361
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
51
Assets/XRI_Examples/Global/Scripts/OnAnimationEvent.cs
Normal file
51
Assets/XRI_Examples/Global/Scripts/OnAnimationEvent.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace UnityEngine.XR.Content.Animation
|
||||
{
|
||||
/// <summary>
|
||||
/// Will receive triggers from the <see cref="AnimationEventActionBegin"/> and <see cref="AnimationEventActionFinished"/> classes,
|
||||
/// and forward them to Unity Events.
|
||||
/// </summary>
|
||||
public class OnAnimationEvent : MonoBehaviour, IAnimationEventActionBegin, IAnimationEventActionFinished
|
||||
{
|
||||
[Serializable]
|
||||
struct ActionEvent
|
||||
{
|
||||
public string m_Label;
|
||||
public UnityEvent m_Action;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
ActionEvent[] m_ActionBeginEvents;
|
||||
|
||||
[SerializeField]
|
||||
ActionEvent[] m_ActionEndEvents;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ActionBegin(string label)
|
||||
{
|
||||
if (m_ActionBeginEvents == null)
|
||||
return;
|
||||
|
||||
foreach (var currentAction in m_ActionBeginEvents)
|
||||
{
|
||||
if (currentAction.m_Label == label)
|
||||
currentAction.m_Action.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ActionFinished(string label)
|
||||
{
|
||||
if (m_ActionEndEvents == null)
|
||||
return;
|
||||
|
||||
foreach (var currentAction in m_ActionEndEvents)
|
||||
{
|
||||
if (currentAction.m_Label == label)
|
||||
currentAction.m_Action.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/XRI_Examples/Global/Scripts/OnAnimationEvent.cs.meta
Normal file
11
Assets/XRI_Examples/Global/Scripts/OnAnimationEvent.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5f41c6b958fe5744f9a8db4b5e83a155
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
32
Assets/XRI_Examples/Global/Scripts/OnSelectInteractable.cs
Normal file
32
Assets/XRI_Examples/Global/Scripts/OnSelectInteractable.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using UnityEngine.XR.Interaction.Toolkit;
|
||||
|
||||
namespace UnityEngine.XR.Content.Interaction
|
||||
{
|
||||
/// <summary>
|
||||
/// Triggers a Unity Event once when an interactable is selected by an interactor.
|
||||
/// </summary>
|
||||
public class OnSelectInteractable : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
[Tooltip("The interactable that is checked for selection.")]
|
||||
XRBaseInteractable m_TargetInteractable;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The function to call when the interactable is selected.")]
|
||||
SelectEnterEvent m_OnSelected;
|
||||
|
||||
void Start()
|
||||
{
|
||||
if (m_TargetInteractable != null)
|
||||
m_TargetInteractable.selectEntered.AddListener(OnSelected);
|
||||
}
|
||||
|
||||
void OnSelected(SelectEnterEventArgs args)
|
||||
{
|
||||
m_OnSelected.Invoke(args);
|
||||
|
||||
if (m_TargetInteractable != null)
|
||||
m_TargetInteractable.selectEntered.RemoveListener(OnSelected);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cbba2ee228ba19f4f9e0fc5c6ec8ed04
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
84
Assets/XRI_Examples/Global/Scripts/OnTilt.cs
Normal file
84
Assets/XRI_Examples/Global/Scripts/OnTilt.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace UnityEngine.XR.Content.Interaction
|
||||
{
|
||||
/// <summary>
|
||||
/// Runs functionality when an object is tilted.
|
||||
/// Used with grabbable objects for pouring.
|
||||
/// </summary>
|
||||
public class OnTilt : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// Extra angle value that is added/removed from the threshold to events from rapid-fire triggering on and off.
|
||||
/// </summary>
|
||||
const float k_AngleBuffer = 0.05f;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Tilt range, 0 - 180 degrees.")]
|
||||
[Range(k_AngleBuffer * 2f, (1 - k_AngleBuffer * 2f))]
|
||||
float m_Threshold = 0.5f;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The transform to check for tilt. Will default to this object if not set.")]
|
||||
Transform m_Target;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The transform to get as the source of the 'up' direction. Will default to world up if not set.")]
|
||||
Transform m_UpSource;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Event to trigger when tilting goes over the threshold.")]
|
||||
UnityEvent m_OnBegin = new UnityEvent();
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Event to trigger when tilting returns from the threshold.")]
|
||||
UnityEvent m_OnEnd = new UnityEvent();
|
||||
|
||||
/// <summary>
|
||||
/// Event to trigger when tilting goes over the threshold.
|
||||
/// </summary>
|
||||
public UnityEvent onBegin => m_OnBegin;
|
||||
|
||||
/// <summary>
|
||||
/// Event to trigger when tilting returns from the threshold.
|
||||
/// </summary>
|
||||
public UnityEvent onEnd => m_OnEnd;
|
||||
|
||||
bool m_WithinThreshold;
|
||||
|
||||
void Update()
|
||||
{
|
||||
CheckOrientation();
|
||||
}
|
||||
|
||||
void CheckOrientation()
|
||||
{
|
||||
var targetUp = m_Target != null ? m_Target.up : transform.up;
|
||||
var baseUp = m_UpSource != null ? m_UpSource.up : Vector3.up;
|
||||
|
||||
var similarity = Vector3.Dot(-targetUp, baseUp);
|
||||
similarity = Mathf.InverseLerp(-1, 1, similarity);
|
||||
|
||||
if (m_WithinThreshold)
|
||||
similarity += k_AngleBuffer;
|
||||
else
|
||||
similarity -= k_AngleBuffer;
|
||||
|
||||
var thresholdCheck = (similarity >= m_Threshold);
|
||||
|
||||
if (m_WithinThreshold != thresholdCheck)
|
||||
{
|
||||
m_WithinThreshold = thresholdCheck;
|
||||
|
||||
if (m_WithinThreshold)
|
||||
{
|
||||
m_OnBegin.Invoke();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_OnEnd.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/XRI_Examples/Global/Scripts/OnTilt.cs.meta
Normal file
11
Assets/XRI_Examples/Global/Scripts/OnTilt.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e311508d4777a834fb25a1729a564503
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
85
Assets/XRI_Examples/Global/Scripts/OnVelocity.cs
Normal file
85
Assets/XRI_Examples/Global/Scripts/OnVelocity.cs
Normal file
@@ -0,0 +1,85 @@
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace UnityEngine.XR.Content.Interaction
|
||||
{
|
||||
/// <summary>
|
||||
/// Calls events for when the velocity of this objects breaks the begin and end threshold.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Rigidbody))]
|
||||
public class OnVelocity : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
[Tooltip("The speed that will trigger the begin event.")]
|
||||
float m_BeginThreshold = 1.25f;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The speed that will trigger the end event.")]
|
||||
float m_EndThreshold = 0.25f;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Event that triggers when speed meets the begin threshold.")]
|
||||
UnityEvent m_OnBegin = new UnityEvent();
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Event that triggers when the speed dips below the end threshold.")]
|
||||
UnityEvent m_OnEnd = new UnityEvent();
|
||||
|
||||
/// <summary>
|
||||
/// Event that triggers when speed meets the begin threshold.
|
||||
/// </summary>
|
||||
public UnityEvent onBegin => m_OnBegin;
|
||||
|
||||
/// <summary>
|
||||
/// Event that triggers when the speed dips below the end threshold.
|
||||
/// </summary>
|
||||
public UnityEvent onEnd => m_OnEnd;
|
||||
|
||||
Rigidbody m_RigidBody;
|
||||
bool m_HasBegun;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
m_RigidBody = GetComponent<Rigidbody>();
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
CheckVelocity();
|
||||
}
|
||||
|
||||
void CheckVelocity()
|
||||
{
|
||||
var speed = m_RigidBody.velocity.magnitude;
|
||||
m_HasBegun = HasVelocityBegun(speed);
|
||||
|
||||
if (HasVelocityEnded(speed))
|
||||
m_HasBegun = false;
|
||||
}
|
||||
|
||||
bool HasVelocityBegun(float speed)
|
||||
{
|
||||
if (m_HasBegun)
|
||||
return true;
|
||||
|
||||
var beginCheck = speed > m_BeginThreshold;
|
||||
|
||||
if (beginCheck)
|
||||
m_OnBegin.Invoke();
|
||||
|
||||
return beginCheck;
|
||||
}
|
||||
|
||||
bool HasVelocityEnded(float speed)
|
||||
{
|
||||
if (!m_HasBegun)
|
||||
return false;
|
||||
|
||||
var endCheck = speed < m_EndThreshold;
|
||||
|
||||
if (endCheck)
|
||||
m_OnEnd.Invoke();
|
||||
|
||||
return endCheck;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/XRI_Examples/Global/Scripts/OnVelocity.cs.meta
Normal file
11
Assets/XRI_Examples/Global/Scripts/OnVelocity.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2eb756a4b82b24040a468c0a73063e4d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
41
Assets/XRI_Examples/Global/Scripts/PlayQuickSound.cs
Normal file
41
Assets/XRI_Examples/Global/Scripts/PlayQuickSound.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
namespace UnityEngine.XR.Content.Interaction
|
||||
{
|
||||
/// <summary>
|
||||
/// Play a simple sound using <c>PlayOneShot</c> with volume and randomized pitch.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(AudioSource))]
|
||||
public class PlayQuickSound : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
[Tooltip("The sound that is played.")]
|
||||
AudioClip m_Sound;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The volume of the sound.")]
|
||||
float m_Volume = 1f;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The range of pitch the sound is played at (-pitch, pitch).")]
|
||||
[Range(0, 1)]
|
||||
float m_RandomPitchVariance;
|
||||
|
||||
AudioSource m_AudioSource;
|
||||
|
||||
const float k_DefaultPitch = 1f;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
m_AudioSource = GetComponent<AudioSource>();
|
||||
}
|
||||
|
||||
public void Play()
|
||||
{
|
||||
var randomVariance = Random.Range(-m_RandomPitchVariance, m_RandomPitchVariance);
|
||||
randomVariance += k_DefaultPitch;
|
||||
|
||||
m_AudioSource.pitch = randomVariance;
|
||||
m_AudioSource.PlayOneShot(m_Sound, m_Volume);
|
||||
m_AudioSource.pitch = k_DefaultPitch;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/XRI_Examples/Global/Scripts/PlayQuickSound.cs.meta
Normal file
11
Assets/XRI_Examples/Global/Scripts/PlayQuickSound.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0755a2ba17a31d643b4a27e3034d8a79
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
42
Assets/XRI_Examples/Global/Scripts/RayAttachModifier.cs
Normal file
42
Assets/XRI_Examples/Global/Scripts/RayAttachModifier.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using Unity.XR.CoreUtils;
|
||||
using UnityEngine.XR.Interaction.Toolkit;
|
||||
|
||||
namespace UnityEngine.XR.Content.Interaction
|
||||
{
|
||||
/// <summary>
|
||||
/// Add this to your interactable to make it snap to the source of the XR Ray Interactor
|
||||
/// instead of staying at a distance. Has a similar outcome as enabling Force Grab.
|
||||
/// </summary>
|
||||
public class RayAttachModifier : MonoBehaviour
|
||||
{
|
||||
IXRSelectInteractable m_SelectInteractable;
|
||||
|
||||
protected void OnEnable()
|
||||
{
|
||||
m_SelectInteractable = GetComponent<IXRSelectInteractable>();
|
||||
if (m_SelectInteractable as Object == null)
|
||||
{
|
||||
Debug.LogError($"Ray Attach Modifier missing required Select Interactable on {name}", this);
|
||||
return;
|
||||
}
|
||||
|
||||
m_SelectInteractable.selectEntered.AddListener(OnSelectEntered);
|
||||
}
|
||||
|
||||
protected void OnDisable()
|
||||
{
|
||||
if (m_SelectInteractable as Object != null)
|
||||
m_SelectInteractable.selectEntered.RemoveListener(OnSelectEntered);
|
||||
}
|
||||
|
||||
void OnSelectEntered(SelectEnterEventArgs args)
|
||||
{
|
||||
if (!(args.interactorObject is XRRayInteractor))
|
||||
return;
|
||||
|
||||
var attachTransform = args.interactorObject.GetAttachTransform(m_SelectInteractable);
|
||||
var originalAttachPose = args.interactorObject.GetLocalAttachPoseOnSelect(m_SelectInteractable);
|
||||
attachTransform.SetLocalPose(originalAttachPose);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/XRI_Examples/Global/Scripts/RayAttachModifier.cs.meta
Normal file
11
Assets/XRI_Examples/Global/Scripts/RayAttachModifier.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 62fb9fb1177925e4b8a383be2b9db66c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
488
Assets/XRI_Examples/Global/Scripts/TransformJoint.cs
Normal file
488
Assets/XRI_Examples/Global/Scripts/TransformJoint.cs
Normal file
@@ -0,0 +1,488 @@
|
||||
namespace UnityEngine.XR.Content.Interaction
|
||||
{
|
||||
/// <summary>
|
||||
/// Joins a rigidbody and transform together in a way that optimizes for transform and rigidbody-based motion automatically when appropriate.
|
||||
/// Exerts an increasing force when the rigidbody is separate from the anchor position, but does not oscillate like a spring.
|
||||
/// </summary>
|
||||
public class TransformJoint : MonoBehaviour, ISerializationCallbackReceiver
|
||||
{
|
||||
const float k_MinMass = 0.01f;
|
||||
const float k_MaxForceDistance = 0.01f;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("A reference to another transform this joint connects to.")]
|
||||
Transform m_ConnectedBody;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The Position of the anchor around which the joints motion is constrained.")]
|
||||
Vector3 m_Anchor;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The Rotation of the anchor around which the joints motion is constrained")]
|
||||
Vector3 m_AnchorAngle;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Should the connectedAnchor be calculated automatically?")]
|
||||
bool m_AutoConfigureConnectedAnchor;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Position of the anchor relative to the connected transform.")]
|
||||
Vector3 m_ConnectedAnchor;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The Rotation of the anchor relative to the connected transform.")]
|
||||
Vector3 m_ConnectedAnchorAngle;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Enable collision between bodies connected with the joint.")]
|
||||
bool m_EnableCollision;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Baseline force applied when an obstacle is between the joint and the connected transform.")]
|
||||
float m_BaseForce = 0.25f;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Additional force applied based on the distance between joint and connected transform")]
|
||||
float m_SpringForce = 1f;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The distance this joint must be from the anchor before teleporting.")]
|
||||
float m_BreakDistance = 1.5f;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The angular distance this joint must be from the anchor before teleporting.")]
|
||||
float m_BreakAngle = 120f;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Should the angle be matched?")]
|
||||
bool m_MatchRotation = true;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Should the mass of the rigidbody be temporarily adjusted to stabilize very strong motion?")]
|
||||
bool m_AdjustMass = true;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A reference to another transform this joint connects to.
|
||||
/// </summary>
|
||||
public Transform connectedBody
|
||||
{
|
||||
get => m_ConnectedBody;
|
||||
set
|
||||
{
|
||||
if (m_ConnectedBody == value)
|
||||
return;
|
||||
|
||||
m_ConnectedBody = value;
|
||||
SetupConnectedBodies(true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The Position of the anchor around which the joints motion is constrained.
|
||||
/// </summary>
|
||||
public Vector3 anchor
|
||||
{
|
||||
get => m_Anchor;
|
||||
set => m_Anchor = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The Rotation of the anchor around which the joints motion is constrained
|
||||
/// </summary>
|
||||
public Vector3 anchorAngle
|
||||
{
|
||||
get => m_AnchorAngle;
|
||||
set
|
||||
{
|
||||
m_AnchorAngle = value;
|
||||
m_AnchorRotation.eulerAngles = m_AnchorAngle;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Should the connectedAnchor be calculated automatically?
|
||||
/// </summary>
|
||||
public bool autoConfigureConnectedAnchor
|
||||
{
|
||||
get => m_AutoConfigureConnectedAnchor;
|
||||
set
|
||||
{
|
||||
m_AutoConfigureConnectedAnchor = value;
|
||||
SetupConnectedBodies(true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Position of the anchor relative to the connected transform.
|
||||
/// </summary>
|
||||
public Vector3 connectedAnchor
|
||||
{
|
||||
get => m_ConnectedAnchor;
|
||||
set => m_ConnectedAnchor = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The Rotation of the anchor relative to the connected transform.
|
||||
/// </summary>
|
||||
public Vector3 connectedAnchorAngle
|
||||
{
|
||||
get => m_ConnectedAnchorAngle;
|
||||
set
|
||||
{
|
||||
m_ConnectedAnchorAngle = value;
|
||||
m_ConnectedAnchorRotation.eulerAngles = m_ConnectedAnchorAngle;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enable collision between bodies connected with the joint.
|
||||
/// </summary>
|
||||
public bool enableCollision
|
||||
{
|
||||
get => m_EnableCollision;
|
||||
set
|
||||
{
|
||||
m_EnableCollision = value;
|
||||
SetupConnectedBodies();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Should the mass of the rigidbody be temporarily adjusted to stabilize very strong motion?
|
||||
/// </summary>
|
||||
public bool adjustMass
|
||||
{
|
||||
get => m_AdjustMass;
|
||||
set => m_AdjustMass = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Baseline force applied when an obstacle is between the joint and the connected transform.
|
||||
/// </summary>
|
||||
public float baseForce
|
||||
{
|
||||
get => m_BaseForce;
|
||||
set => m_BaseForce = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Additional force applied based on the distance between joint and connected transform
|
||||
/// </summary>
|
||||
public float springForce
|
||||
{
|
||||
get => m_SpringForce;
|
||||
set => m_SpringForce = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The distance this joint must be from the anchor before teleporting.
|
||||
/// </summary>
|
||||
public float breakDistance
|
||||
{
|
||||
get => m_BreakDistance;
|
||||
set => m_BreakDistance = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The angular distance this joint must be from the anchor before teleporting.
|
||||
/// </summary>
|
||||
public float breakAngle
|
||||
{
|
||||
get => m_BreakAngle;
|
||||
set => m_BreakAngle = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The angular distance this joint must be from the anchor before teleporting.
|
||||
/// </summary>
|
||||
public bool matchRotation
|
||||
{
|
||||
get => m_MatchRotation;
|
||||
set => m_MatchRotation = value;
|
||||
}
|
||||
|
||||
|
||||
Quaternion m_AnchorRotation;
|
||||
Quaternion m_ConnectedAnchorRotation;
|
||||
|
||||
Transform m_Transform;
|
||||
Rigidbody m_Rigidbody;
|
||||
|
||||
bool m_FixedSyncFrame;
|
||||
bool m_ActiveCollision;
|
||||
bool m_CollisionFrame;
|
||||
bool m_LastCollisionFrame;
|
||||
|
||||
Vector3 m_LastPosition;
|
||||
Vector3 m_LastDirection;
|
||||
|
||||
Collider m_SourceCollider;
|
||||
Collider m_ConnectedCollider;
|
||||
|
||||
float m_BaseMass = 1f;
|
||||
float m_AppliedForce;
|
||||
float m_OldForce;
|
||||
|
||||
void Start()
|
||||
{
|
||||
m_Rigidbody = GetComponent<Rigidbody>();
|
||||
m_SourceCollider = GetComponent<Collider>();
|
||||
|
||||
m_Transform = transform;
|
||||
|
||||
m_AnchorRotation.eulerAngles = m_AnchorAngle;
|
||||
m_ConnectedAnchorRotation.eulerAngles = m_ConnectedAnchorAngle;
|
||||
|
||||
if (m_Rigidbody != null && m_Rigidbody.mass > k_MinMass)
|
||||
m_BaseMass = m_Rigidbody.mass;
|
||||
|
||||
// Set up connected anchor if attached
|
||||
SetupConnectedBodies(true);
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
if (m_Rigidbody != null)
|
||||
m_Rigidbody.mass = m_BaseMass;
|
||||
}
|
||||
|
||||
void SetupConnectedBodies(bool updateAnchor = false)
|
||||
{
|
||||
// Handle undoing old setup
|
||||
// If any properties are pre-existing and have changed, reset the last saved collision ignore pairing
|
||||
if (m_SourceCollider != null && m_ConnectedCollider != null)
|
||||
{
|
||||
Physics.IgnoreCollision(m_SourceCollider, m_ConnectedCollider, false);
|
||||
m_ConnectedCollider = null;
|
||||
}
|
||||
|
||||
// Handle current setup
|
||||
if (m_ConnectedBody != null)
|
||||
{
|
||||
if (m_AutoConfigureConnectedAnchor && updateAnchor)
|
||||
{
|
||||
// Calculate what offsets are currently, set them as anchor
|
||||
m_ConnectedAnchor = m_ConnectedBody.InverseTransformPoint(m_Rigidbody.position + Vector3.Scale((m_Rigidbody.rotation * m_Anchor), m_Transform.lossyScale));
|
||||
m_ConnectedAnchorRotation = (m_Rigidbody.rotation * m_AnchorRotation);
|
||||
m_ConnectedAnchorAngle = m_ConnectedAnchorRotation.eulerAngles;
|
||||
}
|
||||
if (m_EnableCollision)
|
||||
{
|
||||
// Get collider on connected body
|
||||
m_ConnectedCollider = m_ConnectedBody.GetComponent<Collider>();
|
||||
|
||||
if (m_SourceCollider != null && m_ConnectedCollider != null)
|
||||
{
|
||||
Physics.IgnoreCollision(m_SourceCollider, m_ConnectedCollider, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LateUpdate()
|
||||
{
|
||||
// Move freely unless collision has occurred - then rely on physics
|
||||
if ((m_CollisionFrame || m_ActiveCollision) && !m_FixedSyncFrame)
|
||||
{
|
||||
m_Transform.position = m_Rigidbody.position;
|
||||
|
||||
if (m_MatchRotation)
|
||||
m_Transform.rotation = m_Rigidbody.rotation;
|
||||
}
|
||||
|
||||
m_FixedSyncFrame = false;
|
||||
}
|
||||
|
||||
void FixedUpdate()
|
||||
{
|
||||
m_FixedSyncFrame = true;
|
||||
m_OldForce = m_AppliedForce;
|
||||
m_AppliedForce = 0f;
|
||||
|
||||
// Zero out any existing velocity, we are going to set force manually if needed
|
||||
m_Rigidbody.velocity = Vector3.zero;
|
||||
m_Rigidbody.angularVelocity = Vector3.zero;
|
||||
|
||||
UpdateBufferedCollision();
|
||||
UpdatePosition();
|
||||
|
||||
if (m_MatchRotation)
|
||||
UpdateRotation();
|
||||
|
||||
if (m_AdjustMass)
|
||||
{
|
||||
var offset = (m_AppliedForce / m_BaseMass) * Time.fixedDeltaTime * Time.fixedDeltaTime * 0.5f;
|
||||
var massScale = Mathf.Max((offset / k_MaxForceDistance), 1f);
|
||||
m_Rigidbody.mass = m_BaseMass * massScale;
|
||||
}
|
||||
// and acc = f/m
|
||||
// offset = acc * fixedTimestep * fixedTimestep * .5
|
||||
|
||||
// Is offset over certain desirable distance? ie. .1m
|
||||
// scale offset down by scaling mass up
|
||||
// offset*scale = acc * ftp^2 * .5
|
||||
// offset = acc *
|
||||
//
|
||||
// Based on total force, scale mass
|
||||
}
|
||||
|
||||
void UpdateBufferedCollision()
|
||||
{
|
||||
// We buffer collision over three updates
|
||||
// Once from the actual collision to the first fixed update (m_ActiveCollision)
|
||||
// Once for an entire fixedUpdate-to-fixedUpdate cycle (m_CollisionFrame)
|
||||
// And once when a collision is lost - to correct against potential errors when a moving a parent transform
|
||||
m_LastCollisionFrame = m_CollisionFrame;
|
||||
m_CollisionFrame = m_ActiveCollision;
|
||||
m_ActiveCollision = false;
|
||||
}
|
||||
|
||||
void UpdatePosition()
|
||||
{
|
||||
// Assume transform is synced to the rigid body position from late update
|
||||
// Convert anchors to world space
|
||||
var worldSourceAnchor = m_Rigidbody.position + Vector3.Scale((m_Rigidbody.rotation * m_Anchor), m_Transform.lossyScale);
|
||||
var worldDestAnchor = m_ConnectedBody.TransformPoint(m_ConnectedAnchor);
|
||||
|
||||
// Get the delta between these two positions
|
||||
// Use this to calculate the target world position for the rigidbody
|
||||
var positionDelta = worldDestAnchor - worldSourceAnchor;
|
||||
var offset = positionDelta.magnitude;
|
||||
var direction = positionDelta.normalized;
|
||||
var targetPos = m_Rigidbody.position + positionDelta;
|
||||
|
||||
// Convert the target and actual positions to world space
|
||||
var worldPos = m_Rigidbody.position;
|
||||
|
||||
if (offset > Mathf.Epsilon)
|
||||
{
|
||||
// Are we past the break distance?
|
||||
if (offset > m_BreakDistance)
|
||||
{
|
||||
// Warp back to the target
|
||||
m_Rigidbody.position = targetPos;
|
||||
m_Transform.position = targetPos;
|
||||
m_LastDirection = direction;
|
||||
return;
|
||||
}
|
||||
|
||||
// Can we move back unobstructed? Do that
|
||||
if (!m_CollisionFrame)
|
||||
{
|
||||
if (m_Rigidbody.SweepTest(direction, out var hitInfo, offset))
|
||||
{
|
||||
targetPos = worldPos + (hitInfo.distance * direction);
|
||||
m_CollisionFrame = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If there was a collision during the previous update, we let one more update cycle pass at the current location
|
||||
// This helps prevent teleporting through objects during scenarios where many things are playing into the object's position
|
||||
if (m_LastCollisionFrame)
|
||||
{
|
||||
// Compare last direction to this direction
|
||||
// If they are facing opposite directions, no worry of collision
|
||||
if (Vector3.Dot(direction, m_LastDirection) > 0)
|
||||
{
|
||||
targetPos = worldPos;
|
||||
m_AppliedForce = m_OldForce;
|
||||
}
|
||||
}
|
||||
}
|
||||
m_Rigidbody.position = targetPos;
|
||||
m_Transform.position = targetPos;
|
||||
}
|
||||
|
||||
if (m_CollisionFrame)
|
||||
{
|
||||
// Apply a constant force based on spring logic
|
||||
//Debug.Log(m_Rigidbody.velocity);
|
||||
var force = (m_BaseForce + offset * m_SpringForce);
|
||||
m_AppliedForce = force;
|
||||
m_Rigidbody.AddForce(direction * force, ForceMode.Impulse);
|
||||
m_LastPosition = m_Rigidbody.position;
|
||||
}
|
||||
m_LastDirection = direction;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateRotation()
|
||||
{
|
||||
// Assume transform is synced to the rigid body position from late update
|
||||
// Convert anchor rotations to world space
|
||||
var worldSourceAnchor = m_Rigidbody.rotation * m_AnchorRotation;
|
||||
var worldDestAnchor = m_ConnectedBody.rotation * m_ConnectedAnchorRotation;
|
||||
|
||||
// Get the delta between these two positions
|
||||
// Use this to calculate the target world position for the rigidbody
|
||||
var rotationDelta = worldDestAnchor * Quaternion.Inverse(worldSourceAnchor);
|
||||
var targetRotation = rotationDelta * m_Rigidbody.rotation;
|
||||
|
||||
rotationDelta.ToAngleAxis(out var angleInDegrees, out var rotationAxis);
|
||||
if (angleInDegrees > 180f)
|
||||
angleInDegrees -= 360f;
|
||||
|
||||
var angleOffset = Mathf.Abs(angleInDegrees);
|
||||
|
||||
if (angleOffset > Mathf.Epsilon)
|
||||
{
|
||||
// Are we past the break distance?
|
||||
if (angleOffset > m_BreakAngle)
|
||||
{
|
||||
// Warp back to the target
|
||||
m_Rigidbody.rotation = targetRotation;
|
||||
m_Transform.rotation = targetRotation;
|
||||
}
|
||||
|
||||
// Can we move back unobstructed? Do that
|
||||
if (!m_CollisionFrame)
|
||||
{
|
||||
m_Rigidbody.rotation = targetRotation;
|
||||
m_Transform.rotation = targetRotation;
|
||||
}
|
||||
else
|
||||
{
|
||||
var force = ((angleInDegrees / 360f) * (m_BaseForce + m_SpringForce));
|
||||
m_Rigidbody.AddTorque(rotationAxis * force, ForceMode.Impulse);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OnCollisionEnter()
|
||||
{
|
||||
// While in a collision state, we change state so that the regular transform/visual updates are locked to the fixed update rate
|
||||
m_ActiveCollision = true;
|
||||
m_CollisionFrame = true;
|
||||
}
|
||||
|
||||
void OnCollisionStay()
|
||||
{
|
||||
m_ActiveCollision = true;
|
||||
m_CollisionFrame = true;
|
||||
}
|
||||
|
||||
void OnCollisionExit()
|
||||
{
|
||||
if (!enabled)
|
||||
return;
|
||||
|
||||
// When exiting collision, we lock to the last known rigidbody position.
|
||||
// This is because we can end up putting fairly strong forces on this object
|
||||
// If a parent or pure transform change invalidates the collision these forces can cause an object to move through things
|
||||
m_Rigidbody.velocity = Vector3.zero;
|
||||
m_Rigidbody.position = m_LastPosition;
|
||||
transform.position = m_LastPosition;
|
||||
}
|
||||
|
||||
public void OnBeforeSerialize()
|
||||
{
|
||||
}
|
||||
|
||||
public void OnAfterDeserialize()
|
||||
{
|
||||
m_AnchorRotation.eulerAngles = m_AnchorAngle;
|
||||
m_ConnectedAnchorRotation.eulerAngles = m_ConnectedAnchorAngle;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/XRI_Examples/Global/Scripts/TransformJoint.cs.meta
Normal file
11
Assets/XRI_Examples/Global/Scripts/TransformJoint.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e690dfc0dda5a514bad7aa8d349a97a7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user