deltavr multiplayer 2.0

This commit is contained in:
Toomas Tamm
2023-05-08 15:56:10 +03:00
parent 978809a002
commit 07b9b9e2f4
10937 changed files with 2968397 additions and 1521012 deletions

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e5bd8ad0287c0f6409b134bb397bed24
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: cc72213c6872fba4b84b5f24188cb5d9
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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());
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b5d488e69bc9d004dbebfd7cfe3f3103
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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());
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d919f1db183c42d4cac3edda16164e63
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 64441dc1ef91cb442884a1d08fffdc1c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1af61162fa5f63a439f65912f4410ee5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1ff0aed9f9ef7084ca7f59a711befd28
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 51e2b2d7ea5934243ad1542b8dc5f501
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 8df8bc9cd77e5e7448cec848f8f57f9e
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cac85eae435a0fe4a9c4f0e6a8bfd8bb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 87a2345705c099943ae2e878d24a3bbe
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 361f96375872bab42b1254af3fbc27e5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 14586b01c3c01004ba72418f6b2ed6c8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 6bd048109c94d014f8909979db78c6dd
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 9f85ef23860d39c4b844ae8331a7af94
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7067b90e80f2cd345855f8362f2abc5b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fbfe64736ab97cd45b41f6c2052a799b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f22e79d2138983e46bc000032bbb598d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5d468d0443a045844aecfdcb26294078
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f0a02bc85c6eec44099da45f41b649bf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 130246251d17c034db762f6d1a49150e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b56b982c85b468f45a3260808d7ea637
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3e05b88149759dd47aa37ca6dfe979cc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b160c11b363003842a85c3f90316faca
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fa74d384629b02340bc59709603b0771
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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;
}
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 44d90717e41f7864b9265cc48de52361
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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();
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5f41c6b958fe5744f9a8db4b5e83a155
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cbba2ee228ba19f4f9e0fc5c6ec8ed04
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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();
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e311508d4777a834fb25a1729a564503
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2eb756a4b82b24040a468c0a73063e4d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0755a2ba17a31d643b4a27e3034d8a79
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 62fb9fb1177925e4b8a383be2b9db66c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e690dfc0dda5a514bad7aa8d349a97a7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: