Добавлен скрипт генерации карты

This commit is contained in:
LikhenkoVG
2024-12-08 20:54:21 +03:00
parent a194af17f9
commit 11da45e604
296 changed files with 3815 additions and 234 deletions

View File

@ -0,0 +1,482 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine.Events;
using UnityEngine.InputSystem;
using UnityEngine.XR.Interaction.Toolkit.UI;
namespace UnityEngine.XR.Interaction.Toolkit.Samples.StarterAssets
{
/// <summary>
/// Use this class to mediate the controllers and their associated interactors and input actions under different interaction states.
/// </summary>
[AddComponentMenu("XR/Action Based Controller Manager")]
[DefaultExecutionOrder(k_UpdateOrder)]
public class ActionBasedControllerManager : MonoBehaviour
{
/// <summary>
/// Order when instances of type <see cref="ActionBasedControllerManager"/> are updated.
/// </summary>
/// <remarks>
/// Executes before controller components to ensure input processors can be attached
/// to input actions and/or bindings before the controller component reads the current
/// values of the input actions.
/// </remarks>
public const int k_UpdateOrder = XRInteractionUpdateOrder.k_Controllers - 1;
[Space]
[Header("Interactors")]
[SerializeField]
[Tooltip("The GameObject containing the interaction group used for direct and distant manipulation.")]
XRInteractionGroup m_ManipulationInteractionGroup;
[SerializeField]
[Tooltip("The GameObject containing the interactor used for direct manipulation.")]
XRDirectInteractor m_DirectInteractor;
[SerializeField]
[Tooltip("The GameObject containing the interactor used for distant/ray manipulation.")]
XRRayInteractor m_RayInteractor;
[SerializeField]
[Tooltip("The GameObject containing the interactor used for teleportation.")]
XRRayInteractor m_TeleportInteractor;
[Space]
[Header("Controller Actions")]
[SerializeField]
[Tooltip("The reference to the action to start the teleport aiming mode for this controller.")]
InputActionReference m_TeleportModeActivate;
[SerializeField]
[Tooltip("The reference to the action to cancel the teleport aiming mode for this controller.")]
InputActionReference m_TeleportModeCancel;
[SerializeField]
[Tooltip("The reference to the action of continuous turning the XR Origin with this controller.")]
InputActionReference m_Turn;
[SerializeField]
[Tooltip("The reference to the action of snap turning the XR Origin with this controller.")]
InputActionReference m_SnapTurn;
[SerializeField]
[Tooltip("The reference to the action of moving the XR Origin with this controller.")]
InputActionReference m_Move;
[SerializeField]
[Tooltip("The reference to the action of scrolling UI with this controller.")]
InputActionReference m_UIScroll;
[Space]
[Header("Locomotion Settings")]
[SerializeField]
[Tooltip("If true, continuous movement will be enabled. If false, teleport will enabled.")]
bool m_SmoothMotionEnabled;
[SerializeField]
[Tooltip("If true, continuous turn will be enabled. If false, snap turn will be enabled. Note: If smooth motion is enabled and enable strafe is enabled on the continuous move provider, turn will be overriden in favor of strafe.")]
bool m_SmoothTurnEnabled;
[Space]
[Header("UI Settings")]
[SerializeField]
[Tooltip("If true, UI scrolling will be enabled.")]
bool m_UIScrollingEnabled;
[Space]
[Header("Mediation Events")]
[SerializeField]
[Tooltip("Event fired when the active ray interactor changes between interaction and teleport.")]
UnityEvent<IXRRayProvider> m_RayInteractorChanged;
public bool smoothMotionEnabled
{
get => m_SmoothMotionEnabled;
set
{
m_SmoothMotionEnabled = value;
UpdateLocomotionActions();
}
}
public bool smoothTurnEnabled
{
get => m_SmoothTurnEnabled;
set
{
m_SmoothTurnEnabled = value;
UpdateLocomotionActions();
}
}
public bool uiScrollingEnabled
{
get => m_UIScrollingEnabled;
set
{
m_UIScrollingEnabled = value;
UpdateUIActions();
}
}
bool m_StartCalled;
bool m_PostponedDeactivateTeleport;
bool m_HoveringScrollableUI;
const int k_InteractorNotInGroup = -1;
IEnumerator m_AfterInteractionEventsRoutine;
HashSet<InputAction> m_LocomotionUsers = new HashSet<InputAction>();
/// <summary>
/// Temporary scratch list to populate with the group members of the interaction group.
/// </summary>
static readonly List<IXRGroupMember> s_GroupMembers = new List<IXRGroupMember>();
// For our input mediation, we are enforcing a few rules between direct, ray, and teleportation interaction:
// 1. If the Teleportation Ray is engaged, the Ray interactor is disabled
// 2. The interaction group ensures that the Direct and Ray interactors cannot interact at the same time, with the Direct interactor taking priority
// 3. If the Ray interactor is selecting, all locomotion controls are disabled (teleport ray, move, and turn controls) to prevent input collision
void SetupInteractorEvents()
{
if (m_RayInteractor != null)
{
m_RayInteractor.selectEntered.AddListener(OnRaySelectEntered);
m_RayInteractor.selectExited.AddListener(OnRaySelectExited);
m_RayInteractor.uiHoverEntered.AddListener(OnUIHoverEntered);
m_RayInteractor.uiHoverExited.AddListener(OnUIHoverExited);
}
var teleportModeActivateAction = GetInputAction(m_TeleportModeActivate);
if (teleportModeActivateAction != null)
{
teleportModeActivateAction.performed += OnStartTeleport;
teleportModeActivateAction.performed += OnStartLocomotion;
teleportModeActivateAction.canceled += OnCancelTeleport;
teleportModeActivateAction.canceled += OnStopLocomotion;
}
var teleportModeCancelAction = GetInputAction(m_TeleportModeCancel);
if (teleportModeCancelAction != null)
{
teleportModeCancelAction.performed += OnCancelTeleport;
}
var moveAction = GetInputAction(m_Move);
if (moveAction != null)
{
moveAction.started += OnStartLocomotion;
moveAction.canceled += OnStopLocomotion;
}
var turnAction = GetInputAction(m_Turn);
if (turnAction != null)
{
turnAction.started += OnStartLocomotion;
turnAction.canceled += OnStopLocomotion;
}
var snapTurnAction = GetInputAction(m_SnapTurn);
if (snapTurnAction != null)
{
snapTurnAction.started += OnStartLocomotion;
snapTurnAction.canceled += OnStopLocomotion;
}
}
void TeardownInteractorEvents()
{
if (m_RayInteractor != null)
{
m_RayInteractor.selectEntered.RemoveListener(OnRaySelectEntered);
m_RayInteractor.selectExited.RemoveListener(OnRaySelectExited);
}
var teleportModeActivateAction = GetInputAction(m_TeleportModeActivate);
if (teleportModeActivateAction != null)
{
teleportModeActivateAction.performed -= OnStartTeleport;
teleportModeActivateAction.performed -= OnStartLocomotion;
teleportModeActivateAction.canceled -= OnCancelTeleport;
teleportModeActivateAction.canceled -= OnStopLocomotion;
}
var teleportModeCancelAction = GetInputAction(m_TeleportModeCancel);
if (teleportModeCancelAction != null)
{
teleportModeCancelAction.performed -= OnCancelTeleport;
}
var moveAction = GetInputAction(m_Move);
if (moveAction != null)
{
moveAction.started -= OnStartLocomotion;
moveAction.canceled -= OnStopLocomotion;
}
var turnAction = GetInputAction(m_Turn);
if (turnAction != null)
{
turnAction.started -= OnStartLocomotion;
turnAction.canceled -= OnStopLocomotion;
}
var snapTurnAction = GetInputAction(m_SnapTurn);
if (snapTurnAction != null)
{
snapTurnAction.started -= OnStartLocomotion;
snapTurnAction.canceled -= OnStopLocomotion;
}
}
void OnStartTeleport(InputAction.CallbackContext context)
{
m_PostponedDeactivateTeleport = false;
if (m_TeleportInteractor != null)
m_TeleportInteractor.gameObject.SetActive(true);
if (m_RayInteractor != null)
m_RayInteractor.gameObject.SetActive(false);
m_RayInteractorChanged?.Invoke(m_TeleportInteractor);
}
void OnCancelTeleport(InputAction.CallbackContext context)
{
// Do not deactivate the teleport interactor in this callback.
// We delay turning off the teleport interactor in this callback so that
// the teleport interactor has a chance to complete the teleport if needed.
// OnAfterInteractionEvents will handle deactivating its GameObject.
m_PostponedDeactivateTeleport = true;
if (m_RayInteractor != null)
m_RayInteractor.gameObject.SetActive(true);
m_RayInteractorChanged?.Invoke(m_RayInteractor);
}
void OnStartLocomotion(InputAction.CallbackContext context)
{
m_LocomotionUsers.Add(context.action);
}
void OnStopLocomotion(InputAction.CallbackContext context)
{
m_LocomotionUsers.Remove(context.action);
if (m_LocomotionUsers.Count == 0 && m_HoveringScrollableUI)
{
DisableLocomotionActions();
UpdateUIActions();
}
}
void OnRaySelectEntered(SelectEnterEventArgs args)
{
// Disable locomotion and turn actions
DisableLocomotionActions();
}
void OnRaySelectExited(SelectExitEventArgs args)
{
// Re-enable the locomotion and turn actions
UpdateLocomotionActions();
}
void OnUIHoverEntered(UIHoverEventArgs args)
{
m_HoveringScrollableUI = m_UIScrollingEnabled && args.deviceModel.isScrollable;
UpdateUIActions();
// If locomotion is occurring, wait
if (m_HoveringScrollableUI && m_LocomotionUsers.Count == 0)
{
// Disable locomotion and turn actions
DisableLocomotionActions();
}
}
void OnUIHoverExited(UIHoverEventArgs args)
{
m_HoveringScrollableUI = false;
UpdateUIActions();
// Re-enable the locomotion and turn actions
UpdateLocomotionActions();
}
protected void Awake()
{
m_AfterInteractionEventsRoutine = OnAfterInteractionEvents();
}
protected void OnEnable()
{
if (m_TeleportInteractor != null)
m_TeleportInteractor.gameObject.SetActive(false);
// Allow the locomotion actions to be refreshed when this is re-enabled.
// See comments in Start for why we wait until Start to enable/disable locomotion actions.
if (m_StartCalled)
UpdateLocomotionActions();
SetupInteractorEvents();
// Start the coroutine that executes code after the Update phase (during yield null).
// Since this behavior has an execution order that runs before the XRInteractionManager,
// we use the coroutine to run after the selection events
StartCoroutine(m_AfterInteractionEventsRoutine);
}
protected void OnDisable()
{
TeardownInteractorEvents();
StopCoroutine(m_AfterInteractionEventsRoutine);
}
protected void Start()
{
m_StartCalled = true;
// Ensure the enabled state of locomotion and turn actions are properly set up.
// Called in Start so it is done after the InputActionManager enables all input actions earlier in OnEnable.
UpdateLocomotionActions();
UpdateUIActions();
if (m_ManipulationInteractionGroup == null)
{
Debug.LogError("Missing required Manipulation Interaction Group reference. Use the Inspector window to assign the XR Interaction Group component reference.", this);
return;
}
// Ensure interactors are properly set up in the interaction group by adding
// them if necessary and ordering Direct before Ray interactor.
var directInteractorIndex = k_InteractorNotInGroup;
var rayInteractorIndex = k_InteractorNotInGroup;
m_ManipulationInteractionGroup.GetGroupMembers(s_GroupMembers);
for (var i = 0; i < s_GroupMembers.Count; ++i)
{
var groupMember = s_GroupMembers[i];
if (ReferenceEquals(groupMember, m_DirectInteractor))
directInteractorIndex = i;
else if (ReferenceEquals(groupMember, m_RayInteractor))
rayInteractorIndex = i;
}
if (directInteractorIndex == k_InteractorNotInGroup)
{
// Must add Direct interactor to group, and make sure it is ordered before the Ray interactor
if (rayInteractorIndex == k_InteractorNotInGroup)
{
// Must add Ray interactor to group
if (m_DirectInteractor != null)
m_ManipulationInteractionGroup.AddGroupMember(m_DirectInteractor);
if (m_RayInteractor != null)
m_ManipulationInteractionGroup.AddGroupMember(m_RayInteractor);
}
else if (m_DirectInteractor != null)
{
m_ManipulationInteractionGroup.MoveGroupMemberTo(m_DirectInteractor, rayInteractorIndex);
}
}
else
{
if (rayInteractorIndex == k_InteractorNotInGroup)
{
// Must add Ray interactor to group
if (m_RayInteractor != null)
m_ManipulationInteractionGroup.AddGroupMember(m_RayInteractor);
}
else
{
// Must make sure Direct interactor is ordered before the Ray interactor
if (rayInteractorIndex < directInteractorIndex)
{
m_ManipulationInteractionGroup.MoveGroupMemberTo(m_DirectInteractor, rayInteractorIndex);
}
}
}
}
IEnumerator OnAfterInteractionEvents()
{
while (true)
{
// Yield so this coroutine is resumed after the teleport interactor
// has a chance to process its select interaction event during Update.
yield return null;
if (m_PostponedDeactivateTeleport)
{
if (m_TeleportInteractor != null)
m_TeleportInteractor.gameObject.SetActive(false);
m_PostponedDeactivateTeleport = false;
}
}
}
void UpdateLocomotionActions()
{
// Disable/enable Teleport and Turn when Move is enabled/disabled.
SetEnabled(m_Move, m_SmoothMotionEnabled);
SetEnabled(m_TeleportModeActivate, !m_SmoothMotionEnabled);
SetEnabled(m_TeleportModeCancel, !m_SmoothMotionEnabled);
// Disable ability to turn when using continuous movement
SetEnabled(m_Turn, !m_SmoothMotionEnabled && m_SmoothTurnEnabled);
SetEnabled(m_SnapTurn, !m_SmoothMotionEnabled && !m_SmoothTurnEnabled);
}
void DisableLocomotionActions()
{
DisableAction(m_Move);
DisableAction(m_TeleportModeActivate);
DisableAction(m_TeleportModeCancel);
DisableAction(m_Turn);
DisableAction(m_SnapTurn);
}
void UpdateUIActions()
{
SetEnabled(m_UIScroll, m_UIScrollingEnabled && m_HoveringScrollableUI && m_LocomotionUsers.Count == 0);
}
static void SetEnabled(InputActionReference actionReference, bool enabled)
{
if (enabled)
EnableAction(actionReference);
else
DisableAction(actionReference);
}
static void EnableAction(InputActionReference actionReference)
{
var action = GetInputAction(actionReference);
if (action != null && !action.enabled)
action.Enable();
}
static void DisableAction(InputActionReference actionReference)
{
var action = GetInputAction(actionReference);
if (action != null && action.enabled)
action.Disable();
}
static InputAction GetInputAction(InputActionReference actionReference)
{
#pragma warning disable IDE0031 // Use null propagation -- Do not use for UnityEngine.Object types
return actionReference != null ? actionReference.action : null;
#pragma warning restore IDE0031
}
}
}

View File

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

View File

@ -0,0 +1,143 @@
using UnityEngine.XR.Interaction.Toolkit.Locomotion.Climbing;
using UnityEngine.XR.Interaction.Toolkit.Locomotion.Teleportation;
using UnityEngine.XR.Interaction.Toolkit.Utilities;
namespace UnityEngine.XR.Interaction.Toolkit.Samples.StarterAssets
{
/// <summary>
/// Affordance component used in conjunction with a <see cref="ClimbTeleportInteractor"/> to display an object
/// pointing at the target teleport destination while climbing.
/// </summary>
public class ClimbTeleportDestinationIndicator : MonoBehaviour
{
[SerializeField]
[Tooltip("The interactor that drives the display and placement of the pointer object.")]
ClimbTeleportInteractor m_ClimbTeleportInteractor;
/// <summary>
/// The interactor that drives the display and placement of the pointer object.
/// </summary>
public ClimbTeleportInteractor climbTeleportInteractor
{
get => m_ClimbTeleportInteractor;
set => m_ClimbTeleportInteractor = value;
}
[SerializeField]
[Tooltip("The prefab to spawn when a teleport destination is chosen. The instance will spawn next to the " +
"destination and point its forward vector at the destination and its up vector at the camera.")]
GameObject m_PointerPrefab;
/// <summary>
/// The prefab to spawn when a teleport destination is chosen. The instance will spawn next to the destination
/// and point its forward vector at the destination and its up vector at the camera.
/// </summary>
public GameObject pointerPrefab
{
get => m_PointerPrefab;
set => m_PointerPrefab = value;
}
[SerializeField]
[Tooltip("The distance from the destination at which the pointer object spawns.")]
float m_PointerDistance = 0.3f;
/// <summary>
/// The distance from the destination at which the pointer object spawns.
/// </summary>
public float pointerDistance
{
get => m_PointerDistance;
set => m_PointerDistance = value;
}
TeleportationMultiAnchorVolume m_ActiveTeleportVolume;
Transform m_PointerInstance;
/// <summary>
/// See <see cref="MonoBehaviour"/>.
/// </summary>
protected void OnEnable()
{
if (m_ClimbTeleportInteractor == null)
{
if (!ComponentLocatorUtility<ClimbTeleportInteractor>.TryFindComponent(out m_ClimbTeleportInteractor))
{
Debug.LogError($"Could not find {nameof(ClimbTeleportInteractor)} in scene.");
enabled = false;
return;
}
}
m_ClimbTeleportInteractor.hoverEntered.AddListener(OnInteractorHoverEntered);
m_ClimbTeleportInteractor.hoverExited.AddListener(OnInteractorHoverExited);
}
/// <summary>
/// See <see cref="MonoBehaviour"/>.
/// </summary>
protected void OnDisable()
{
HideIndicator();
if (m_ActiveTeleportVolume != null)
{
m_ActiveTeleportVolume.destinationAnchorChanged -= OnClimbTeleportDestinationAnchorChanged;
m_ActiveTeleportVolume = null;
}
if (m_ClimbTeleportInteractor != null)
{
m_ClimbTeleportInteractor.hoverEntered.RemoveListener(OnInteractorHoverEntered);
m_ClimbTeleportInteractor.hoverExited.RemoveListener(OnInteractorHoverExited);
}
}
void OnInteractorHoverEntered(HoverEnterEventArgs args)
{
if (m_ActiveTeleportVolume != null || !(args.interactableObject is TeleportationMultiAnchorVolume teleportVolume))
return;
m_ActiveTeleportVolume = teleportVolume;
if (m_ActiveTeleportVolume.destinationAnchor != null)
OnClimbTeleportDestinationAnchorChanged(m_ActiveTeleportVolume);
m_ActiveTeleportVolume.destinationAnchorChanged += OnClimbTeleportDestinationAnchorChanged;
}
void OnInteractorHoverExited(HoverExitEventArgs args)
{
if (!(args.interactableObject is TeleportationMultiAnchorVolume teleportVolume) || teleportVolume != m_ActiveTeleportVolume)
return;
HideIndicator();
m_ActiveTeleportVolume.destinationAnchorChanged -= OnClimbTeleportDestinationAnchorChanged;
m_ActiveTeleportVolume = null;
}
void OnClimbTeleportDestinationAnchorChanged(TeleportationMultiAnchorVolume teleportVolume)
{
HideIndicator();
var destinationAnchor = teleportVolume.destinationAnchor;
if (destinationAnchor == null)
return;
m_PointerInstance = Instantiate(m_PointerPrefab).transform;
var cameraTrans = teleportVolume.teleportationProvider.system.xrOrigin.Camera.transform;
var cameraPosition = cameraTrans.position;
var destinationPosition = destinationAnchor.position;
var destinationDirectionInScreenSpace = cameraTrans.InverseTransformDirection(destinationPosition - cameraPosition);
destinationDirectionInScreenSpace.z = 0f;
var pointerDirection = cameraTrans.TransformDirection(destinationDirectionInScreenSpace).normalized;
m_PointerInstance.position = destinationPosition - pointerDirection * m_PointerDistance;
m_PointerInstance.rotation = Quaternion.LookRotation(pointerDirection, -cameraTrans.forward);
}
void HideIndicator()
{
if (m_PointerInstance != null)
Destroy(m_PointerInstance.gameObject);
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e766f86cb7d2461683eb37d8a971fb14
timeCreated: 1694639993

View File

@ -0,0 +1,29 @@
namespace UnityEngine.XR.Interaction.Toolkit.Samples.StarterAssets
{
/// <summary>
/// Destroys the GameObject it is attached to after a specified amount of time.
/// </summary>
public class DestroySelf : MonoBehaviour
{
[SerializeField]
[Tooltip("The amount of time, in seconds, to wait after Start before destroying the GameObject.")]
float m_Lifetime = 0.25f;
/// <summary>
/// The amount of time, in seconds, to wait after Start before destroying the GameObject.
/// </summary>
public float lifetime
{
get => m_Lifetime;
set => m_Lifetime = value;
}
/// <summary>
/// See <see cref="MonoBehaviour"/>.
/// </summary>
void Start()
{
Destroy(gameObject, m_Lifetime);
}
}
}

View File

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

View File

@ -0,0 +1,189 @@
using Unity.XR.CoreUtils;
using UnityEngine.Assertions;
namespace UnityEngine.XR.Interaction.Toolkit.Samples.StarterAssets
{
/// <summary>
/// A version of action-based continuous movement that automatically controls the frame of reference that
/// determines the forward direction of movement based on user preference for each hand.
/// For example, can configure to use head relative movement for the left hand and controller relative movement for the right hand.
/// </summary>
public class DynamicMoveProvider : ActionBasedContinuousMoveProvider
{
/// <summary>
/// Defines which transform the XR Origin's movement direction is relative to.
/// </summary>
/// <seealso cref="leftHandMovementDirection"/>
/// <seealso cref="rightHandMovementDirection"/>
public enum MovementDirection
{
/// <summary>
/// Use the forward direction of the head (camera) as the forward direction of the XR Origin's movement.
/// </summary>
HeadRelative,
/// <summary>
/// Use the forward direction of the hand (controller) as the forward direction of the XR Origin's movement.
/// </summary>
HandRelative,
}
[Space, Header("Movement Direction")]
[SerializeField]
[Tooltip("Directs the XR Origin's movement when using the head-relative mode. If not set, will automatically find and use the XR Origin Camera.")]
Transform m_HeadTransform;
/// <summary>
/// Directs the XR Origin's movement when using the head-relative mode. If not set, will automatically find and use the XR Origin Camera.
/// </summary>
public Transform headTransform
{
get => m_HeadTransform;
set => m_HeadTransform = value;
}
[SerializeField]
[Tooltip("Directs the XR Origin's movement when using the hand-relative mode with the left hand.")]
Transform m_LeftControllerTransform;
/// <summary>
/// Directs the XR Origin's movement when using the hand-relative mode with the left hand.
/// </summary>
public Transform leftControllerTransform
{
get => m_LeftControllerTransform;
set => m_LeftControllerTransform = value;
}
[SerializeField]
[Tooltip("Directs the XR Origin's movement when using the hand-relative mode with the right hand.")]
Transform m_RightControllerTransform;
public Transform rightControllerTransform
{
get => m_RightControllerTransform;
set => m_RightControllerTransform = value;
}
[SerializeField]
[Tooltip("Whether to use the specified head transform or left controller transform to direct the XR Origin's movement for the left hand.")]
MovementDirection m_LeftHandMovementDirection;
/// <summary>
/// Whether to use the specified head transform or controller transform to direct the XR Origin's movement for the left hand.
/// </summary>
/// <seealso cref="MovementDirection"/>
public MovementDirection leftHandMovementDirection
{
get => m_LeftHandMovementDirection;
set => m_LeftHandMovementDirection = value;
}
[SerializeField]
[Tooltip("Whether to use the specified head transform or right controller transform to direct the XR Origin's movement for the right hand.")]
MovementDirection m_RightHandMovementDirection;
/// <summary>
/// Whether to use the specified head transform or controller transform to direct the XR Origin's movement for the right hand.
/// </summary>
/// <seealso cref="MovementDirection"/>
public MovementDirection rightHandMovementDirection
{
get => m_RightHandMovementDirection;
set => m_RightHandMovementDirection = value;
}
Transform m_CombinedTransform;
Pose m_LeftMovementPose = Pose.identity;
Pose m_RightMovementPose = Pose.identity;
/// <inheritdoc />
protected override void Awake()
{
base.Awake();
m_CombinedTransform = new GameObject("[Dynamic Move Provider] Combined Forward Source").transform;
m_CombinedTransform.SetParent(transform, false);
m_CombinedTransform.localPosition = Vector3.zero;
m_CombinedTransform.localRotation = Quaternion.identity;
forwardSource = m_CombinedTransform;
}
/// <inheritdoc />
protected override Vector3 ComputeDesiredMove(Vector2 input)
{
// Don't need to do anything if the total input is zero.
// This is the same check as the base method.
if (input == Vector2.zero)
return Vector3.zero;
// Initialize the Head Transform if necessary, getting the Camera from XR Origin
if (m_HeadTransform == null)
{
var xrOrigin = system.xrOrigin;
if (xrOrigin != null)
{
var xrCamera = xrOrigin.Camera;
if (xrCamera != null)
m_HeadTransform = xrCamera.transform;
}
}
// Get the forward source for the left hand input
switch (m_LeftHandMovementDirection)
{
case MovementDirection.HeadRelative:
if (m_HeadTransform != null)
m_LeftMovementPose = m_HeadTransform.GetWorldPose();
break;
case MovementDirection.HandRelative:
if (m_LeftControllerTransform != null)
m_LeftMovementPose = m_LeftControllerTransform.GetWorldPose();
break;
default:
Assert.IsTrue(false, $"Unhandled {nameof(MovementDirection)}={m_LeftHandMovementDirection}");
break;
}
// Get the forward source for the right hand input
switch (m_RightHandMovementDirection)
{
case MovementDirection.HeadRelative:
if (m_HeadTransform != null)
m_RightMovementPose = m_HeadTransform.GetWorldPose();
break;
case MovementDirection.HandRelative:
if (m_RightControllerTransform != null)
m_RightMovementPose = m_RightControllerTransform.GetWorldPose();
break;
default:
Assert.IsTrue(false, $"Unhandled {nameof(MovementDirection)}={m_RightHandMovementDirection}");
break;
}
// Combine the two poses into the forward source based on the magnitude of input
var leftHandValue = leftHandMoveAction.action?.ReadValue<Vector2>() ?? Vector2.zero;
var rightHandValue = rightHandMoveAction.action?.ReadValue<Vector2>() ?? Vector2.zero;
var totalSqrMagnitude = leftHandValue.sqrMagnitude + rightHandValue.sqrMagnitude;
var leftHandBlend = 0.5f;
if (totalSqrMagnitude > Mathf.Epsilon)
leftHandBlend = leftHandValue.sqrMagnitude / totalSqrMagnitude;
var combinedPosition = Vector3.Lerp(m_RightMovementPose.position, m_LeftMovementPose.position, leftHandBlend);
var combinedRotation = Quaternion.Slerp(m_RightMovementPose.rotation, m_LeftMovementPose.rotation, leftHandBlend);
m_CombinedTransform.SetPositionAndRotation(combinedPosition, combinedRotation);
return base.ComputeDesiredMove(input);
}
}
}

View File

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

View File

@ -0,0 +1,95 @@
using System.Collections.Generic;
using UnityEngine.InputSystem;
namespace UnityEngine.XR.Interaction.Toolkit.Samples.StarterAssets
{
/// <summary>
/// Manages input fallback for <see cref="XRGazeInteractor"/> when eye tracking is not available.
/// </summary>
public class GazeInputManager : MonoBehaviour
{
// This is the name of the layout that is registered by EyeGazeInteraction in the OpenXR Plugin package
const string k_EyeGazeLayoutName = "EyeGaze";
[SerializeField]
[Tooltip("Enable fallback to head tracking if eye tracking is unavailable.")]
bool m_FallbackIfEyeTrackingUnavailable = true;
/// <summary>
/// Enable fallback to head tracking if eye tracking is unavailable.
/// </summary>
public bool fallbackIfEyeTrackingUnavailable
{
get => m_FallbackIfEyeTrackingUnavailable;
set => m_FallbackIfEyeTrackingUnavailable = value;
}
bool m_EyeTrackingDeviceFound;
/// <summary>
/// See <see cref="MonoBehaviour"/>.
/// </summary>
protected void Awake()
{
// Check if we have eye tracking support
var inputDeviceList = new List<InputDevice>();
InputDevices.GetDevicesWithCharacteristics(InputDeviceCharacteristics.EyeTracking, inputDeviceList);
if (inputDeviceList.Count > 0)
{
Debug.Log("Eye tracking device found!", this);
m_EyeTrackingDeviceFound = true;
return;
}
foreach (var device in InputSystem.InputSystem.devices)
{
if (device.layout == k_EyeGazeLayoutName)
{
Debug.Log("Eye gaze device found!", this);
m_EyeTrackingDeviceFound = true;
return;
}
}
Debug.LogWarning($"Could not find a device that supports eye tracking on Awake. {this} has subscribed to device connected events and will activate the GameObject when an eye tracking device is connected.", this);
InputDevices.deviceConnected += OnDeviceConnected;
InputSystem.InputSystem.onDeviceChange += OnDeviceChange;
gameObject.SetActive(m_FallbackIfEyeTrackingUnavailable);
}
/// <summary>
/// See <see cref="MonoBehaviour"/>.
/// </summary>
protected void OnDestroy()
{
InputDevices.deviceConnected -= OnDeviceConnected;
InputSystem.InputSystem.onDeviceChange -= OnDeviceChange;
}
void OnDeviceConnected(InputDevice inputDevice)
{
if (m_EyeTrackingDeviceFound || !inputDevice.characteristics.HasFlag(InputDeviceCharacteristics.EyeTracking))
return;
Debug.Log("Eye tracking device found!", this);
m_EyeTrackingDeviceFound = true;
gameObject.SetActive(true);
}
void OnDeviceChange(InputSystem.InputDevice device, InputDeviceChange change)
{
if (m_EyeTrackingDeviceFound || change != InputDeviceChange.Added)
return;
if (device.layout == k_EyeGazeLayoutName)
{
Debug.Log("Eye gaze device found!", this);
m_EyeTrackingDeviceFound = true;
gameObject.SetActive(true);
}
}
}
}

View File

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

View File

@ -0,0 +1,237 @@
using System;
using System.Collections.Generic;
using UnityEngine.XR.Interaction.Toolkit.Utilities;
namespace UnityEngine.XR.Interaction.Toolkit.Samples.StarterAssets
{
/// <summary>
/// Behavior with an API for spawning objects from a given set of prefabs.
/// </summary>
public class ObjectSpawner : MonoBehaviour
{
[SerializeField]
[Tooltip("The camera that objects will face when spawned. If not set, defaults to the main camera.")]
Camera m_CameraToFace;
/// <summary>
/// The camera that objects will face when spawned. If not set, defaults to the <see cref="Camera.main"/> camera.
/// </summary>
public Camera cameraToFace
{
get
{
EnsureFacingCamera();
return m_CameraToFace;
}
set => m_CameraToFace = value;
}
[SerializeField]
[Tooltip("The list of prefabs available to spawn.")]
List<GameObject> m_ObjectPrefabs = new List<GameObject>();
/// <summary>
/// The list of prefabs available to spawn.
/// </summary>
public List<GameObject> objectPrefabs
{
get => m_ObjectPrefabs;
set => m_ObjectPrefabs = value;
}
[SerializeField]
[Tooltip("Optional prefab to spawn for each spawned object. Use a prefab with the Destroy Self component to make " +
"sure the visualization only lives temporarily.")]
GameObject m_SpawnVisualizationPrefab;
/// <summary>
/// Optional prefab to spawn for each spawned object.
/// </summary>
/// <remarks>Use a prefab with <see cref="DestroySelf"/> to make sure the visualization only lives temporarily.</remarks>
public GameObject spawnVisualizationPrefab
{
get => m_SpawnVisualizationPrefab;
set => m_SpawnVisualizationPrefab = value;
}
[SerializeField]
[Tooltip("The index of the prefab to spawn. If outside the range of the list, this behavior will select " +
"a random object each time it spawns.")]
int m_SpawnOptionIndex = -1;
/// <summary>
/// The index of the prefab to spawn. If outside the range of <see cref="objectPrefabs"/>, this behavior will
/// select a random object each time it spawns.
/// </summary>
/// <seealso cref="isSpawnOptionRandomized"/>
public int spawnOptionIndex
{
get => m_SpawnOptionIndex;
set => m_SpawnOptionIndex = value;
}
/// <summary>
/// Whether this behavior will select a random object from <see cref="objectPrefabs"/> each time it spawns.
/// </summary>
/// <seealso cref="spawnOptionIndex"/>
/// <seealso cref="RandomizeSpawnOption"/>
public bool isSpawnOptionRandomized => m_SpawnOptionIndex < 0 || m_SpawnOptionIndex >= m_ObjectPrefabs.Count;
[SerializeField]
[Tooltip("Whether to only spawn an object if the spawn point is within view of the camera.")]
bool m_OnlySpawnInView = true;
/// <summary>
/// Whether to only spawn an object if the spawn point is within view of the <see cref="cameraToFace"/>.
/// </summary>
public bool onlySpawnInView
{
get => m_OnlySpawnInView;
set => m_OnlySpawnInView = value;
}
[SerializeField]
[Tooltip("The size, in viewport units, of the periphery inside the viewport that will not be considered in view.")]
float m_ViewportPeriphery = 0.15f;
/// <summary>
/// The size, in viewport units, of the periphery inside the viewport that will not be considered in view.
/// </summary>
public float viewportPeriphery
{
get => m_ViewportPeriphery;
set => m_ViewportPeriphery = value;
}
[SerializeField]
[Tooltip("When enabled, the object will be rotated about the y-axis when spawned by Spawn Angle Range, " +
"in relation to the direction of the spawn point to the camera.")]
bool m_ApplyRandomAngleAtSpawn = true;
/// <summary>
/// When enabled, the object will be rotated about the y-axis when spawned by <see cref="spawnAngleRange"/>
/// in relation to the direction of the spawn point to the camera.
/// </summary>
public bool applyRandomAngleAtSpawn
{
get => m_ApplyRandomAngleAtSpawn;
set => m_ApplyRandomAngleAtSpawn = value;
}
[SerializeField]
[Tooltip("The range in degrees that the object will randomly be rotated about the y axis when spawned, " +
"in relation to the direction of the spawn point to the camera.")]
float m_SpawnAngleRange = 45f;
/// <summary>
/// The range in degrees that the object will randomly be rotated about the y axis when spawned, in relation
/// to the direction of the spawn point to the camera.
/// </summary>
public float spawnAngleRange
{
get => m_SpawnAngleRange;
set => m_SpawnAngleRange = value;
}
[SerializeField]
[Tooltip("Whether to spawn each object as a child of this object.")]
bool m_SpawnAsChildren;
/// <summary>
/// Whether to spawn each object as a child of this object.
/// </summary>
public bool spawnAsChildren
{
get => m_SpawnAsChildren;
set => m_SpawnAsChildren = value;
}
/// <summary>
/// Event invoked after an object is spawned.
/// </summary>
/// <seealso cref="TrySpawnObject"/>
public event Action<GameObject> objectSpawned;
/// <summary>
/// See <see cref="MonoBehaviour"/>.
/// </summary>
void Awake()
{
EnsureFacingCamera();
}
void EnsureFacingCamera()
{
if (m_CameraToFace == null)
m_CameraToFace = Camera.main;
}
/// <summary>
/// Sets this behavior to select a random object from <see cref="objectPrefabs"/> each time it spawns.
/// </summary>
/// <seealso cref="spawnOptionIndex"/>
/// <seealso cref="isSpawnOptionRandomized"/>
public void RandomizeSpawnOption()
{
m_SpawnOptionIndex = -1;
}
/// <summary>
/// Attempts to spawn an object from <see cref="objectPrefabs"/> at the given position. The object will have a
/// yaw rotation that faces <see cref="cameraToFace"/>, plus or minus a random angle within <see cref="spawnAngleRange"/>.
/// </summary>
/// <param name="spawnPoint">The world space position at which to spawn the object.</param>
/// <param name="spawnNormal">The world space normal of the spawn surface.</param>
/// <returns>Returns <see langword="true"/> if the spawner successfully spawned an object. Otherwise returns
/// <see langword="false"/>, for instance if the spawn point is out of view of the camera.</returns>
/// <remarks>
/// The object selected to spawn is based on <see cref="spawnOptionIndex"/>. If the index is outside
/// the range of <see cref="objectPrefabs"/>, this method will select a random prefab from the list to spawn.
/// Otherwise, it will spawn the prefab at the index.
/// </remarks>
/// <seealso cref="objectSpawned"/>
public bool TrySpawnObject(Vector3 spawnPoint, Vector3 spawnNormal)
{
if (m_OnlySpawnInView)
{
var inViewMin = m_ViewportPeriphery;
var inViewMax = 1f - m_ViewportPeriphery;
var pointInViewportSpace = cameraToFace.WorldToViewportPoint(spawnPoint);
if (pointInViewportSpace.z < 0f || pointInViewportSpace.x > inViewMax || pointInViewportSpace.x < inViewMin ||
pointInViewportSpace.y > inViewMax || pointInViewportSpace.y < inViewMin)
{
return false;
}
}
var objectIndex = isSpawnOptionRandomized ? Random.Range(0, m_ObjectPrefabs.Count) : m_SpawnOptionIndex;
var newObject = Instantiate(m_ObjectPrefabs[objectIndex]);
if (m_SpawnAsChildren)
newObject.transform.parent = transform;
newObject.transform.position = spawnPoint;
EnsureFacingCamera();
var facePosition = m_CameraToFace.transform.position;
var forward = facePosition - spawnPoint;
BurstMathUtility.ProjectOnPlane(forward, spawnNormal, out var projectedForward);
newObject.transform.rotation = Quaternion.LookRotation(projectedForward, spawnNormal);
if (m_ApplyRandomAngleAtSpawn)
{
var randomRotation = Random.Range(-m_SpawnAngleRange, m_SpawnAngleRange);
newObject.transform.Rotate(Vector3.up, randomRotation);
}
if (m_SpawnVisualizationPrefab != null)
{
var visualizationTrans = Instantiate(m_SpawnVisualizationPrefab).transform;
visualizationTrans.position = spawnPoint;
visualizationTrans.rotation = newObject.transform.rotation;
}
objectSpawned?.Invoke(newObject);
return true;
}
}
}

View File

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

View File

@ -0,0 +1,44 @@
using UnityEngine.XR.Interaction.Toolkit.Transformers;
namespace UnityEngine.XR.Interaction.Toolkit.Samples.StarterAssets
{
/// <summary>
/// An XR grab transformer that allows for the locking of specific rotation axes. When an object is grabbed and manipulated,
/// this class ensures that rotations are only applied to the specified axes, preserving the initial rotation for the others.
/// </summary>
public class RotationAxisLockGrabTransformer : XRBaseGrabTransformer
{
[SerializeField]
[Tooltip("Defines which rotation axes are allowed when an object is grabbed. Axes not selected will maintain their initial rotation.")]
XRGeneralGrabTransformer.ManipulationAxes m_PermittedRotationAxis = XRGeneralGrabTransformer.ManipulationAxes.All;
/// <inheritdoc />
protected override RegistrationMode registrationMode => RegistrationMode.SingleAndMultiple;
Vector3 m_InitialEulerRotation;
/// <inheritdoc />
public override void OnLink(XRGrabInteractable grabInteractable)
{
base.OnLink(grabInteractable);
m_InitialEulerRotation = grabInteractable.transform.rotation.eulerAngles;
}
/// <inheritdoc />
public override void Process(XRGrabInteractable grabInteractable, XRInteractionUpdateOrder.UpdatePhase updatePhase, ref Pose targetPose, ref Vector3 localScale)
{
Vector3 newRotationEuler = targetPose.rotation.eulerAngles;
if ((m_PermittedRotationAxis & XRGeneralGrabTransformer.ManipulationAxes.X) == 0)
newRotationEuler.x = m_InitialEulerRotation.x;
if ((m_PermittedRotationAxis & XRGeneralGrabTransformer.ManipulationAxes.Y) == 0)
newRotationEuler.y = m_InitialEulerRotation.y;
if ((m_PermittedRotationAxis & XRGeneralGrabTransformer.ManipulationAxes.Z) == 0)
newRotationEuler.z = m_InitialEulerRotation.z;
targetPose.rotation = Quaternion.Euler(newRotationEuler);
}
}
}

View File

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

View File

@ -0,0 +1,206 @@
using Unity.Mathematics;
using Unity.XR.CoreUtils.Bindings;
using UnityEngine.XR.Interaction.Toolkit.AffordanceSystem.State;
using UnityEngine.XR.Interaction.Toolkit.Filtering;
using UnityEngine.XR.Interaction.Toolkit.Utilities.Tweenables.Primitives;
namespace UnityEngine.XR.Interaction.Toolkit.Samples.StarterAssets
{
/// <summary>
/// Follow animation affordance for <see cref="IPokeStateDataProvider"/>, such as <see cref="XRPokeFilter"/>.
/// Used to animate a pressed transform, such as a button to follow the poke position.
/// </summary>
[AddComponentMenu("XR/XR Poke Follow Affordance", 22)]
public class XRPokeFollowAffordance : MonoBehaviour
{
[SerializeField]
[Tooltip("Transform that will move in the poke direction when this or a parent GameObject is poked." +
"\nNote: Should be a direct child GameObject.")]
Transform m_PokeFollowTransform;
/// <summary>
/// Transform that will animate along the axis of interaction when this interactable is poked.
/// Note: Must be a direct child GameObject as it moves in local space relative to the poke target's transform.
/// </summary>
public Transform pokeFollowTransform
{
get => m_PokeFollowTransform;
set => m_PokeFollowTransform = value;
}
[SerializeField]
[Range(0f, 20f)]
[Tooltip("Multiplies transform position interpolation as a factor of Time.deltaTime. If 0, no smoothing will be applied.")]
float m_SmoothingSpeed = 16f;
/// <summary>
/// Multiplies transform position interpolation as a factor of <see cref="Time.deltaTime"/>. If <c>0</c>, no smoothing will be applied.
/// </summary>
public float smoothingSpeed
{
get => m_SmoothingSpeed;
set => m_SmoothingSpeed = value;
}
[SerializeField]
[Tooltip("When this component is no longer the target of the poke, the Poke Follow Transform returns to the original position.")]
bool m_ReturnToInitialPosition = true;
/// <summary>
/// When this component is no longer the target of the poke, the <see cref="pokeFollowTransform"/> returns to the original position.
/// </summary>
public bool returnToInitialPosition
{
get => m_ReturnToInitialPosition;
set => m_ReturnToInitialPosition = value;
}
[SerializeField]
[Tooltip("Whether to apply the follow animation if the target of the poke is a child of this transform. " +
"This is useful for UI objects that may have child graphics.")]
bool m_ApplyIfChildIsTarget = true;
/// <summary>
/// Whether to apply the follow animation if the target of the poke is a child of this transform.
/// This is useful for UI objects that may have child graphics.
/// </summary>
public bool applyIfChildIsTarget
{
get => m_ApplyIfChildIsTarget;
set => m_ApplyIfChildIsTarget = value;
}
[SerializeField]
[Tooltip("Whether to keep the Poke Follow Transform from moving past a maximum distance from the poke target.")]
bool m_ClampToMaxDistance;
/// <summary>
/// Whether to keep the <see cref="pokeFollowTransform"/> from moving past <see cref="maxDistance"/> from the poke target.
/// </summary>
public bool clampToMaxDistance
{
get => m_ClampToMaxDistance;
set => m_ClampToMaxDistance = value;
}
[SerializeField]
[Tooltip("The maximum distance from this transform that the Poke Follow Transform can move.")]
float m_MaxDistance;
/// <summary>
/// The maximum distance from this transform that the <see cref="pokeFollowTransform"/> can move when
/// <see cref="clampToMaxDistance"/> is <see langword="true"/>.
/// </summary>
public float maxDistance
{
get => m_MaxDistance;
set => m_MaxDistance = value;
}
/// <summary>
/// The original position of this interactable before any pushes have been applied.
/// </summary>
public Vector3 initialPosition
{
get => m_InitialPosition;
set => m_InitialPosition = value;
}
IPokeStateDataProvider m_PokeDataProvider;
IMultiPokeStateDataProvider m_MultiPokeStateDataProvider;
readonly Vector3TweenableVariable m_TransformTweenableVariable = new Vector3TweenableVariable();
readonly BindingsGroup m_BindingsGroup = new BindingsGroup();
Vector3 m_InitialPosition;
bool m_IsFirstFrame;
/// <summary>
/// See <see cref="MonoBehaviour"/>.
/// </summary>
protected void Awake()
{
m_MultiPokeStateDataProvider = GetComponentInParent<IMultiPokeStateDataProvider>();
if (m_MultiPokeStateDataProvider == null)
m_PokeDataProvider = GetComponentInParent<IPokeStateDataProvider>();
}
/// <summary>
/// See <see cref="MonoBehaviour"/>.
/// </summary>
protected void Start()
{
if (m_PokeFollowTransform != null)
{
m_InitialPosition = m_PokeFollowTransform.localPosition;
m_BindingsGroup.AddBinding(m_TransformTweenableVariable.Subscribe(OnTransformTweenableVariableUpdated));
if (m_MultiPokeStateDataProvider != null)
m_BindingsGroup.AddBinding(m_MultiPokeStateDataProvider.GetPokeStateDataForTarget(transform).Subscribe(OnPokeStateDataUpdated));
else if (m_PokeDataProvider != null)
m_BindingsGroup.AddBinding(m_PokeDataProvider.pokeStateData.SubscribeAndUpdate(OnPokeStateDataUpdated));
}
else
{
enabled = false;
Debug.LogWarning($"Missing Poke Follow Transform assignment on {this}. Disabling component.", this);
}
}
/// <summary>
/// See <see cref="MonoBehaviour"/>.
/// </summary>
protected void OnDestroy()
{
m_BindingsGroup.Clear();
m_TransformTweenableVariable?.Dispose();
}
/// <summary>
/// See <see cref="MonoBehaviour"/>.
/// </summary>
protected void LateUpdate()
{
if (m_IsFirstFrame)
{
m_TransformTweenableVariable.HandleTween(1f);
m_IsFirstFrame = false;
return;
}
m_TransformTweenableVariable.HandleTween(m_SmoothingSpeed > 0f ? Time.deltaTime * m_SmoothingSpeed : 1f);
}
void OnTransformTweenableVariableUpdated(float3 position)
{
m_PokeFollowTransform.localPosition = position;
}
void OnPokeStateDataUpdated(PokeStateData data)
{
var pokeTarget = data.target;
var applyFollow = m_ApplyIfChildIsTarget
? pokeTarget != null && pokeTarget.IsChildOf(transform)
: pokeTarget == transform;
if (applyFollow)
{
var targetPosition = pokeTarget.InverseTransformPoint(data.axisAlignedPokeInteractionPoint);
if (m_ClampToMaxDistance && targetPosition.sqrMagnitude > m_MaxDistance * m_MaxDistance)
targetPosition = Vector3.ClampMagnitude(targetPosition, m_MaxDistance);
m_TransformTweenableVariable.target = targetPosition;
}
else if (m_ReturnToInitialPosition)
{
m_TransformTweenableVariable.target = m_InitialPosition;
}
}
public void ResetFollowTransform()
{
if (!m_ClampToMaxDistance || m_PokeFollowTransform == null)
return;
m_PokeFollowTransform.localPosition = m_InitialPosition;
}
}
}

View File

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