Files
dungeons/Assets/ThirdParty/UltimateXR/Runtime/Scripts/Manipulation/UxrGrabbableObjectAnchor.cs

422 lines
16 KiB
C#

// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrGrabbableObjectAnchor.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using UltimateXR.Core.Components;
using UnityEngine;
namespace UltimateXR.Manipulation
{
/// <summary>
/// Component that, added to a <see cref="GameObject" />, will enable <see cref="UxrGrabbableObject" /> objects to be
/// placed on it.
/// Some of the main features of grabbable object anchors are:
/// <list type="bullet">
/// <item>
/// Placement mechanics are handled automatically by the <see cref="UxrGrabManager" />. There is no special
/// requirement to set it up in a scene, the grab manager will be available as soon as it is required.
/// </item>
/// <item>
/// Compatible tags allow to model which objects can be placed on the anchor. If the list is empty, the
/// anchor is compatible with all other <see cref="UxrGrabbableObject" /> that do not have a tag.
/// </item>
/// <item>
/// Events such as <see cref="Placed" /> and <see cref="Removed" /> allow to write logic when a user interacts
/// with the anchor. Each one has pre and post events.
/// </item>
/// <item>
/// <see cref="ActivateOnCompatibleNear" />, <see cref="ActivateOnCompatibleNotNear" />,
/// <see cref="ActivateOnHandNearAndGrabbable" />, <see cref="ActivateOnPlaced" /> and
/// <see cref="ActivateOnEmpty" /> can be used to activate/deactivate objects on manipulation events. They can
/// be assigned during edit-time using the inspector and also at runtime.
/// </item>
/// <item>
/// <see cref="AddPlacingValidator" /> and <see cref="RemovePlacingValidator" /> can be used to model complex
/// compatibility behaviour that changes at runtime.
/// </item>
/// </list>
/// </summary>
public partial class UxrGrabbableObjectAnchor : UxrComponent<UxrGrabbableObjectAnchor>
{
#region Inspector Properties/Serialized Fields
[SerializeField] private List<string> _compatibleTags = new List<string>();
[SerializeField] private float _maxPlaceDistance = 0.1f;
[SerializeField] private bool _alignTransformUseSelf = true;
[SerializeField] private Transform _alignTransform;
[SerializeField] private bool _dropProximityTransformUseSelf = true;
[SerializeField] private Transform _dropProximityTransform;
[SerializeField] private GameObject _activateOnCompatibleNear;
[SerializeField] private GameObject _activateOnCompatibleNotNear;
[SerializeField] private GameObject _activateOnHandNearAndGrabbable;
[SerializeField] private GameObject _activateOnPlaced;
[SerializeField] private GameObject _activateOnEmpty;
#endregion
#region Public Types & Data
/// <summary>
/// Event called right before an object is placed on the anchor.
/// </summary>
public event EventHandler<UxrManipulationEventArgs> Placing;
/// <summary>
/// Event called right after an object was placed on the anchor.
/// </summary>
public event EventHandler<UxrManipulationEventArgs> Placed;
/// <summary>
/// Event called right before the currently placed object is removed from the anchor.
/// </summary>
public event EventHandler<UxrManipulationEventArgs> Removing;
/// <summary>
/// Event called right after the currently placed object is removed from the anchor.
/// </summary>
public event EventHandler<UxrManipulationEventArgs> Removed;
/// <summary>
/// Event called right after an object that was placed on the anchor ended its smooth placing transition.
/// </summary>
public event EventHandler<UxrManipulationEventArgs> SmoothPlaceTransitionEnded;
/// <summary>
/// Gets the <see cref="Transform" /> that will be used to snap the <see cref="UxrGrabbableObject" /> placed on it.
/// </summary>
public Transform AlignTransform
{
get
{
if (_alignTransform == null || _alignTransformUseSelf)
{
return transform;
}
return _alignTransform;
}
}
/// <summary>
/// Gets the <see cref="Transform" /> that will be used to compute the distance to <see cref="UxrGrabbableObject" />
/// that can be placed on it, in order to determine if they are close enough.
/// </summary>
public Transform DropProximityTransform
{
get
{
if (_dropProximityTransform == null || _dropProximityTransformUseSelf)
{
return transform;
}
return _dropProximityTransform;
}
}
/// <summary>
/// Gets the <see cref="UxrGrabbableObject" /> that is currently placed on the anchor.
/// </summary>
public UxrGrabbableObject CurrentPlacedObject
{
get => _currentPlacedObject;
internal set => _currentPlacedObject = value;
}
/// <summary>
/// Gets or sets the maximum distance from which an object that is released will be placed on the anchor.
/// </summary>
public float MaxPlaceDistance
{
get => _maxPlaceDistance;
set => _maxPlaceDistance = value;
}
/// <summary>
/// Gets or sets the object that will be enabled or disabled depending on if there is a grabbed compatible
/// <see cref="UxrGrabbableObject" /> close enough to be placed on it.
/// </summary>
public GameObject ActivateOnCompatibleNear
{
get => _activateOnCompatibleNear;
set => _activateOnCompatibleNear = value;
}
/// <summary>
/// Gets or sets the object that will be enabled or disabled depending on if there isn't a grabbed compatible
/// <see cref="UxrGrabbableObject" /> close enough to be placed on it.
/// </summary>
public GameObject ActivateOnCompatibleNotNear
{
get => _activateOnCompatibleNotNear;
set => _activateOnCompatibleNotNear = value;
}
/// <summary>
/// Gets or sets the object that will be enabled or disabled depending on if there is a
/// <see cref="UxrGrabbableObject" /> currently placed on the anchor and a <see cref="UxrGrabber" /> close enough to
/// grab it.
/// </summary>
public GameObject ActivateOnHandNearAndGrabbable
{
get => _activateOnHandNearAndGrabbable;
set => _activateOnHandNearAndGrabbable = value;
}
/// <summary>
/// Gets or sets the object that will be enabled or disabled depending on if there is a
/// <see cref="UxrGrabbableObject" /> currently placed on the anchor.
/// </summary>
public GameObject ActivateOnPlaced
{
get => _activateOnPlaced;
set => _activateOnPlaced = value;
}
/// <summary>
/// Gets or sets the object that will be enabled or disabled depending on if there isn't a
/// <see cref="UxrGrabbableObject" /> currently placed on the anchor.
/// </summary>
public GameObject ActivateOnEmpty
{
get => _activateOnEmpty;
set => _activateOnEmpty = value;
}
#endregion
#region Public Methods
/// <summary>
/// Adds compatible tags to the list of compatible tags that control which objects can be placed on the anchor.
/// </summary>
/// <param name="tags">Tags to add</param>
/// <exception cref="ArgumentNullException">tags is null</exception>
public void AddCompatibleTags(params string[] tags)
{
if (tags == null)
{
throw new ArgumentNullException(nameof(tags));
}
_compatibleTags.AddRange(tags);
}
/// <summary>
/// Removes compatible tags from the list of compatible tags that control which objects can be placed on the anchor.
/// </summary>
/// <param name="tags">Tags to remove</param>
/// <exception cref="ArgumentNullException">tags is null</exception>
public void RemoveCompatibleTags(params string[] tags)
{
if (tags == null)
{
throw new ArgumentNullException(nameof(tags));
}
foreach (string tag in tags)
{
_compatibleTags.Remove(tag);
}
}
/// <summary>
/// Removes the currently placed object, if there is any, from the anchor.
/// </summary>
/// <param name="propagateEvents">Whether the call should generate any events or not</param>
public void RemoveObject(bool propagateEvents)
{
if (CurrentPlacedObject != null)
{
UxrGrabManager.Instance.RemoveObjectFromAnchor(CurrentPlacedObject, propagateEvents);
}
}
/// <summary>
/// Adds a placing validator to the internal list of validators. Placing validators are functions that are used in
/// addition to compatibility tags in order to determine if a <see cref="UxrGrabbableObject" /> can be placed on the
/// anchor.
/// An object can be placed on an anchor if the tag is compatible and if it is allowed by all of the placing
/// validators.
/// </summary>
/// <param name="validator">
/// New placing validator function to add. It takes an <see cref="UxrGrabbableObject" /> as input
/// and returns a boolean telling whether it can be placed or not
/// </param>
/// <exception cref="ArgumentNullException">The validator function is null</exception>
public void AddPlacingValidator(Func<UxrGrabbableObject, bool> validator)
{
if (validator == null)
{
throw new ArgumentNullException(nameof(validator));
}
_placingValidators.Add(validator);
}
/// <summary>
/// Removes a placing validator added using <see cref="AddPlacingValidator" />.
/// </summary>
/// <param name="validator">Validator to remove</param>
/// <exception cref="ArgumentNullException">the validator function is null</exception>
public void RemovePlacingValidator(Func<UxrGrabbableObject, bool> validator)
{
if (validator == null)
{
throw new ArgumentNullException(nameof(validator));
}
_placingValidators.Remove(validator);
}
/// <summary>
/// Checks whether the given <see cref="UxrGrabbableObject" /> is compatible with the anchor, which means that it can
/// potentially be placed on it if there is no other object placed.
/// </summary>
/// <param name="grabbableObject">Object to check</param>
/// <returns>Whether the object is compatible with the anchor</returns>
public bool IsCompatibleObject(UxrGrabbableObject grabbableObject)
{
return grabbableObject != null && _placingValidators.All(v => v(grabbableObject)) && IsCompatibleObjectTag(grabbableObject.Tag);
}
#endregion
#region Unity
/// <summary>
/// Initializes the component.
/// </summary>
protected override void Awake()
{
base.Awake();
UxrGrabManager.Instance.Poke();
if (_activateOnCompatibleNear != null)
{
_activateOnCompatibleNear.SetActive(false);
}
if (_activateOnCompatibleNotNear != null)
{
_activateOnCompatibleNotNear.SetActive(false);
}
if (_activateOnHandNearAndGrabbable != null)
{
_activateOnHandNearAndGrabbable.SetActive(false);
}
}
/// <summary>
/// Removes the validators.
/// </summary>
protected override void OnDestroy()
{
base.OnDestroy();
_placingValidators.Clear();
}
/// <summary>
/// Performs additional initialization.
/// </summary>
protected override void Start()
{
base.Start();
if (_activateOnPlaced != null)
{
_activateOnPlaced.SetActive(CurrentPlacedObject != null);
}
if (_activateOnEmpty != null)
{
_activateOnEmpty.SetActive(CurrentPlacedObject == null);
}
}
#endregion
#region Event Trigger Methods
/// <summary>
/// Event trigger for <see cref="Placing" />.
/// </summary>
/// <param name="e">Event parameters</param>
internal void RaisePlacingEvent(UxrManipulationEventArgs e)
{
Placing?.Invoke(this, e);
}
/// <summary>
/// Event trigger for <see cref="Placed" />.
/// </summary>
/// <param name="e">Event parameters</param>
internal void RaisePlacedEvent(UxrManipulationEventArgs e)
{
Placed?.Invoke(this, e);
_smoothPlaceEventArgs = e;
}
/// <summary>
/// Event trigger for <see cref="Removing" />.
/// </summary>
/// <param name="e">Event parameters</param>
internal void RaiseRemovingEvent(UxrManipulationEventArgs e)
{
Removing?.Invoke(this, e);
}
/// <summary>
/// Event trigger for <see cref="Removed" />.
/// </summary>
/// <param name="e">Event parameters</param>
internal void RaiseRemovedEvent(UxrManipulationEventArgs e)
{
Removed?.Invoke(this, e);
}
/// <summary>
/// Event trigger for <see cref="SmoothPlaceTransitionEnded" />.
/// </summary>
internal void RaiseSmoothTransitionPlaceEnded()
{
SmoothPlaceTransitionEnded?.Invoke(this, _smoothPlaceEventArgs);
}
#endregion
#region Private Methods
/// <summary>
/// Checking whether the given tag is compatible with the anchor.
/// </summary>
/// <param name="otherTag">Tag to check whether it is compatible</param>
/// <returns>Whether the tag is compatible</returns>
private bool IsCompatibleObjectTag(string otherTag)
{
if (_compatibleTags == null || _compatibleTags.Count == 0)
{
return string.IsNullOrEmpty(otherTag);
}
return _compatibleTags.Contains(otherTag);
}
#endregion
#region Private Types & Data
private readonly List<Func<UxrGrabbableObject, bool>> _placingValidators = new List<Func<UxrGrabbableObject, bool>>();
private UxrManipulationEventArgs _smoothPlaceEventArgs;
private UxrGrabbableObject _currentPlacedObject;
#endregion
}
}