VRTK_Pointer脚本简析(VRTK_v3.3.0版)

该脚本的父类是VRTK_DestinationMarker
提供能够从指定的GameObject发出指针;

必备组建

渲染类 VRTK_BasePointerRenderer:一般是继承它的两个子类,一个是直线渲染,一个是贝塞尔曲线渲染;

可选组建

VRTK_ControllerEvents :监听按钮,
VRTK_InteractUse:

事件

ControllerInteractionEventHandler ActivationButtonPressed
ControllerInteractionEventHandler ActivationButtonReleased
ControllerInteractionEventHandler SelectionButtonPressed
ControllerInteractionEventHandler SelectionButtonReleased
ControllerInteractionEventHandler 委托来自VRTK_ControllerEvents类;
DestinationMarkerEventHandler PointerStateValid
DestinationMarkerEventHandler PointerStateInvalid
DestinationMarkerEventHandler委托来自VRTK_DestinationMarker父类;

方法

bool IsActivationButtonPressed()
IsSelectionButtonPressed()

Pointer(RaycastHit givenHit)
当指针首次进入有效对象时,PointerEnter方法会发出DestinationMarkerEnter事件,它会为指针停留在有效对象上的每个后续帧发出一个DestinationMarkerHover。
PointerExit(RaycastHit givenHit)
当指针离开先前输入的对象时,PointerExit方法会发出DestinationMarkerExit事件
bool CanActivate()
用于确定指针是否已超过激活时间限制。
bool CanSelect()
用于确定指针是否已超过选择时间限制。
bool IsPointerActive()
用于确定指针的当前状态是否处于活动状态。
ResetActivationTimer(bool forceZero = false)
用于将指针激活计时器重置为下一个有效的激活时间
ResetSelectionTimer(bool forceZero = false)
用于将指针选择计时器重置为下一个有效的激活时间
Toggle(bool state)
用于启用或禁用指针。
bool IsStateValid()
用于确定指针当前是否处于有效状态(即,它的有效颜色);

私有方法

Awake() 调用VRTK_SDKManager类的一个Add方法
OnEnable()
OnDisable()
OnDestroy()

// Pointer|Pointers|10020
 namespace VRTK
 {
using UnityEngine;

/// <summary>
/// Provides a basis of being able to emit a pointer from a specified GameObject.
/// </summary>
/// <remarks>
/// **Required Components:**
///  * `VRTK_BasePointerRenderer` - The visual representation of the pointer when activated.
///
/// **Optional Components:**
///  * `VRTK_ControllerEvents` - The events component to listen for the button presses on. This must be applied on the same GameObject as this script if one is not provided via the `Controller` parameter.
///  * `VRTK_InteractUse` - The use component to utilise when the pointer is to activate the use action on an Interactable Object. This must be applied on the same GameObject as this script if one is not provided via the `Interact Use` parameter.
/// 
/// **Script Usage:**
///  * Place the `VRTK_Pointer` script on either:
///    * The controller script alias GameObject of the controller to emit the pointer from (e.g. Right Controller Script Alias).
///    * Any other scene GameObject and provide a valid `Transform` component to the `Custom Origin` parameter of this script. This does not have to be a controller and can be any GameObject that will emit the pointer.
///  * Link the required Base Pointer Renderer script to the `Pointer Renderer` parameter of this script.
/// </remarks>
[AddComponentMenu("VRTK/Scripts/Pointers/VRTK_Pointer")]
public class VRTK_Pointer : VRTK_DestinationMarker
{
    [Header("Pointer Activation Settings")]

    [Tooltip("The specific renderer to use when the pointer is activated. The renderer also determines how the pointer reaches it's destination (e.g. straight line, bezier curve).")]
    public VRTK_BasePointerRenderer pointerRenderer;//指针渲染器
    [Tooltip("The button used to activate/deactivate the pointer.")]
    public VRTK_ControllerEvents.ButtonAlias activationButton = VRTK_ControllerEvents.ButtonAlias.TouchpadPress;//指针激活按钮
    [Tooltip("If this is checked then the Activation Button needs to be continuously held down to keep the pointer active. If this is unchecked then the Activation Button works as a toggle, the first press/release enables the pointer and the second press/release disables the pointer.")]
    public bool holdButtonToActivate = true;//true是保持按钮激活,false反之;
    [Tooltip("If this is checked then the pointer will be toggled on when the script is enabled.")]
    public bool activateOnEnable = false;//指针是否激活
    [Tooltip("The time in seconds to delay the pointer being able to be active again.")]
    public float activationDelay = 0f;//延迟时间

    [Header("Pointer Selection Settings")]

    [Tooltip("The button used to execute the select action at the pointer's target position.")]
    public VRTK_ControllerEvents.ButtonAlias selectionButton = VRTK_ControllerEvents.ButtonAlias.TouchpadPress;//选则按钮用于在指针的目标位置执行选择操作的按钮。
    [Tooltip("If this is checked then the pointer selection action is executed when the Selection Button is pressed down. If this is unchecked then the selection action is executed when the Selection Button is released.")]
    public bool selectOnPress = false;//如果选中此选项,则在按下选择按钮时执行指针选择操作。如果未选中此选项,则在释放选择按钮时执行选择操作。
    [Tooltip("The time in seconds to delay the pointer being able to execute the select action again.")]
    public float selectionDelay = 0f;//选择延迟:延迟指针再次执行选择操作的时间(以秒为单位)
    [Tooltip("The amount of time the pointer can be over the same collider before it automatically attempts to select it. 0f means no selection attempt will be made.")]
    public float selectAfterHoverDuration = 0f;//指针在自动尝试选择它之前可以在同一个对撞机上的时间。0f表示不会进行任何选择尝试。

    [Header("Pointer Interaction Settings")]

    [Tooltip("If this is checked then the pointer will be an extension of the controller and able to interact with Interactable Objects.")]
    public bool interactWithObjects = false;//如果选中此项,则指针将成为控制器的扩展,并且能够与Interactable对象进行交互
    [Tooltip("If `Interact With Objects` is checked and this is checked then when an object is grabbed with the pointer touching it, the object will attach to the pointer tip and not snap to the controller.")]
    public bool grabToPointerTip = false;//如果Interact With Objects选中并且已选中此选项,则当指针触摸它时抓取对象时,对象将附加到指针尖端而不会捕捉到控制器。

    [Header("Pointer Customisation Settings")]

    [Tooltip("An optional GameObject that determines what the pointer is to be attached to. If this is left blank then the GameObject the script is on will be used.")]
    public GameObject attachedTo;//用于确定指针要附加到的内容。如果将其留空,则将使用脚本所在的GameObject
    [Tooltip("An optional Controller Events that will be used to toggle the pointer. If the script is being applied onto a controller then this parameter can be left blank as it will be auto populated by the controller the script is on at runtime.")]
    public VRTK_ControllerEvents controllerEvents;//控制器事件
    [Tooltip("An optional InteractUse script that will be used when using interactable objects with pointer. If this is left blank then it will attempt to get the InteractUse script from the same GameObject and if it cannot find one then it will attempt to get it from the attached controller.")]
    public VRTK_InteractUse interactUse;//可选的InteractUse脚本,在将可交互对象与指针一起使用时将使用。如果这是空白,那么它将尝试从同一个GameObject获取InteractUse脚本,如果它找不到,那么它将尝试从连接的控制器获取它。
    [Tooltip("A custom transform to use as the origin of the pointer. If no pointer origin transform is provided then the transform the script is attached to is used.")]
    public Transform customOrigin;//用作指针原点的自定义变换。如果未提供指针原点变换,则使用脚本附加到的变换。

    [Header("Obsolete Settings")]

    [System.Obsolete("`VRTK_Pointer.controller` has been replaced with `VRTK_Pointer.controllerEvents`. This parameter will be removed in a future version of VRTK.")]
    [ObsoleteInspector]
    public VRTK_ControllerEvents controller;

    /// <summary>
    /// Emitted when the pointer activation button is pressed.
    /// </summary>
    public event ControllerInteractionEventHandler ActivationButtonPressed;//按下指针激活按钮时发出
    /// <summary>
    /// Emitted when the pointer activation button is released.
    /// </summary>
    public event ControllerInteractionEventHandler ActivationButtonReleased;//释放指针激活按钮时发出。
    /// <summary>
    /// Emitted when the pointer selection button is pressed.
    /// </summary>
    public event ControllerInteractionEventHandler SelectionButtonPressed;//按下指针选择按钮时发出。
    /// <summary>
    /// Emitted when the pointer selection button is released.
    /// </summary>
    public event ControllerInteractionEventHandler SelectionButtonReleased; //释放指针选择按钮时发出。
    /// <summary>
    /// Emitted when the pointer is in a valid state.
    /// </summary>
    public event DestinationMarkerEventHandler PointerStateValid;//指针处于有效状态时发出。
    /// <summary>
    /// Emitted when the pointer is in an invalid state.
    /// </summary>
    public event DestinationMarkerEventHandler PointerStateInvalid;//指针处于无效状态时发出。

    protected VRTK_ControllerEvents.ButtonAlias subscribedActivationButton = VRTK_ControllerEvents.ButtonAlias.Undefined;
    protected VRTK_ControllerEvents.ButtonAlias subscribedSelectionButton = VRTK_ControllerEvents.ButtonAlias.Undefined;
    protected bool currentSelectOnPress;
    protected float activateDelayTimer;
    protected float selectDelayTimer;
    protected float hoverDurationTimer;
    protected int currentActivationState;
    protected bool willDeactivate;
    protected bool wasActivated;
    protected VRTK_ControllerReference controllerReference;
    protected VRTK_InteractableObject pointerInteractableObject = null;
    protected Collider currentCollider;
    protected bool canClickOnHover;
    protected bool activationButtonPressed;
    protected bool selectionButtonPressed;
    protected bool attemptControllerSetup;
    protected VRTK_StraightPointerRenderer autogenPointerRenderer;

    public virtual void OnActivationButtonPressed(ControllerInteractionEventArgs e)
    {
        if (ActivationButtonPressed != null)
        {
            ActivationButtonPressed(this, e);
        }
    }

    public virtual void OnActivationButtonReleased(ControllerInteractionEventArgs e)
    {
        if (ActivationButtonReleased != null)
        {
            ActivationButtonReleased(this, e);
        }
    }

    public virtual void OnSelectionButtonPressed(ControllerInteractionEventArgs e)
    {
        if (SelectionButtonPressed != null)
        {
            SelectionButtonPressed(this, e);
        }
    }

    public virtual void OnSelectionButtonReleased(ControllerInteractionEventArgs e)
    {
        if (SelectionButtonReleased != null)
        {
            SelectionButtonReleased(this, e);
        }
    }

    public virtual void OnPointerStateValid()
    {
        if (PointerStateValid != null)
        {
            PointerStateValid(this, GetStateEventPayload());
        }
    }

    public virtual void OnPointerStateInvalid()
    {
        if (PointerStateInvalid != null)
        {
            PointerStateInvalid(this, GetStateEventPayload());
        }
    }

    /// <summary>
    /// The IsActivationButtonPressed method returns whether the configured activation button is being pressed.
    /// </summary>
    /// <returns>Returns `true` if the activationButton is being pressed.</returns>
    public virtual bool IsActivationButtonPressed()//返回是否按下配置的激活按钮
    {
        return activationButtonPressed;
    }

    /// <summary>
    /// The IsSelectionButtonPressed method returns whether the configured activation button is being pressed.
    /// </summary>
    /// <returns>Returns `true` if the selectionButton is being pressed.</returns>
    public virtual bool IsSelectionButtonPressed()//返回是否按下配置的选择按钮
    {
        return selectionButtonPressed;
    }


    /// <summary>
    /// The PointerEnter method emits a DestinationMarkerEnter event when the pointer first enters a valid object, it emits a DestinationMarkerHover for every following frame that the pointer stays over the valid object.
    /// </summary>
    /// <param name="givenHit">The valid collision.有效的碰撞</param>
    public virtual void PointerEnter(RaycastHit givenHit)
    {
        if (enabled && givenHit.transform != null && (!ControllerRequired() || VRTK_ControllerReference.IsValid(controllerReference)))
        {
            SetHoverSelectionTimer(givenHit.collider);
            DestinationMarkerEventArgs destinationEventArgs = SetDestinationMarkerEvent(givenHit.distance, givenHit.transform, givenHit, givenHit.point, controllerReference, false, GetCursorRotation());
            if (pointerRenderer != null && givenHit.collider != pointerRenderer.GetDestinationHit().collider)
            {
                OnDestinationMarkerEnter(destinationEventArgs);
            }
            else
            {
                OnDestinationMarkerHover(destinationEventArgs);
            }
            StartUseAction(givenHit.transform);
        }
    }

    /// <summary>
    /// The PointerExit method emits a DestinationMarkerExit event when the pointer leaves a previously entered object.
    /// </summary>
    /// <param name="givenHit">The previous valid collision.</param>
    public virtual void PointerExit(RaycastHit givenHit)
    {
        ResetHoverSelectionTimer(givenHit.collider);
        if (givenHit.transform != null && (!ControllerRequired() || VRTK_ControllerReference.IsValid(controllerReference)))
        {
            OnDestinationMarkerExit(SetDestinationMarkerEvent(givenHit.distance, givenHit.transform, givenHit, givenHit.point, controllerReference, false, GetCursorRotation()));
            StopUseAction();
        }
    }

    /// <summary>
    /// The CanActivate method is used to determine if the pointer has passed the activation time limit.
    /// </summary>
    /// <returns>Returns `true` if the pointer can be activated.</returns>
    public virtual bool CanActivate()
    {
        return (Time.time >= activateDelayTimer);
    }

    /// <summary>
    /// The CanSelect method is used to determine if the pointer has passed the selection time limit.
    /// </summary>
    /// <returns>Returns `true` if the pointer can execute the select action.</returns>
    public virtual bool CanSelect()
    {
        return (Time.time >= selectDelayTimer);
    }

    /// <summary>
    /// The IsPointerActive method is used to determine if the pointer's current state is active or not.
    /// </summary>
    /// <returns>Returns `true` if the pointer is currently active.</returns>
    public virtual bool IsPointerActive()
    {
        return (currentActivationState != 0);
    }

    /// <summary>
    /// The ResetActivationTimer method is used to reset the pointer activation timer to the next valid activation time.
    /// </summary>
    /// <param name="forceZero">If this is `true` then the next activation time will be 0.</param>
    public virtual void ResetActivationTimer(bool forceZero = false)
    {
        activateDelayTimer = (forceZero ? 0f : Time.time + activationDelay);
    }

    /// <summary>
    /// The ResetSelectionTimer method is used to reset the pointer selection timer to the next valid activation time.
    /// </summary>
    /// <param name="forceZero">If this is `true` then the next activation time will be 0.</param>
    public virtual void ResetSelectionTimer(bool forceZero = false)
    {
        selectDelayTimer = (forceZero ? 0f : Time.time + selectionDelay);
    }

    /// <summary>
    /// The Toggle method is used to enable or disable the pointer.
    /// </summary>
    /// <param name="state">If `true` the pointer will be enabled if possible, if `false` the pointer will be disabled if possible.</param>
    public virtual void Toggle(bool state)
    {
        if (!CanActivate() || NoPointerRenderer() || CanActivateOnToggleButton(state) || (state && IsPointerActive()) || (!state && !IsPointerActive()))
        {
            return;
        }

        ManageActivationState(willDeactivate ? true : state);
        pointerRenderer.Toggle(IsPointerActive(), state);
        willDeactivate = false;
        if (!state)
        {
            StopUseAction();
        }
    }

    /// <summary>
    /// The IsStateValid method is used to determine if the pointer is currently in a valid state (i.e. on it's valid colour).
    /// </summary>
    /// <returns>Returns `true` if the pointer is in the valid state (showing the valid colour), returns `false` if the pointer is in the invalid state (showing the invalid colour).</returns>
    public virtual bool IsStateValid()
    {
        return (EnabledPointerRenderer() && pointerRenderer.IsValidCollision());
    }

    protected virtual void Awake()
    {
        VRTK_SDKManager.AttemptAddBehaviourToToggleOnLoadedSetupChange(this);
    }

    protected override void OnEnable()
    {
    #pragma warning disable 0618
        controllerEvents = (controller != null && controllerEvents == null ? controller : controllerEvents);
    #pragma warning restore 0618
        base.OnEnable();
        attachedTo = (attachedTo == null ? gameObject : attachedTo);
        if (!VRTK_PlayerObject.IsPlayerObject(gameObject))
        {
            VRTK_PlayerObject.SetPlayerObject(gameObject, VRTK_PlayerObject.ObjectTypes.Pointer);
        }
        SetDefaultValues();
    }

    protected override void OnDisable()
    {
        base.OnDisable();
        Toggle(false);
        if (pointerRenderer != null)
        {
            pointerRenderer.Toggle(false, false);
        }
        UnsubscribeActivationButton();
        UnsubscribeSelectionButton();
        if (autogenPointerRenderer != null)
        {
            Destroy(autogenPointerRenderer);
        }
    }

    protected virtual void OnDestroy()
    {
        VRTK_SDKManager.AttemptRemoveBehaviourToToggleOnLoadedSetupChange(this);
    }

    protected virtual void Update()
    {
        AttemptControllerSetup();
        CheckButtonSubscriptions();
        HandleEnabledPointer();
    }

    protected virtual void SetDefaultValues()
    {
        SetupRenderer();
        activateDelayTimer = 0f;
        selectDelayTimer = 0f;
        hoverDurationTimer = 0f;
        currentActivationState = 0;
        wasActivated = false;
        willDeactivate = false;
        canClickOnHover = false;
        attemptControllerSetup = true;
    }

    protected virtual void AttemptControllerSetup()
    {
        if (attemptControllerSetup)
        {
            if (FindController())
            {
                attemptControllerSetup = false;
                SetupController();
                SetupRenderer();
                if (activateOnEnable)
                {
                    Toggle(true);
                }
            }
        }
    }

    protected virtual void HandleEnabledPointer()
    {
        if (EnabledPointerRenderer())
        {
            pointerRenderer.InitalizePointer(this, targetListPolicy, navmeshData, headsetPositionCompensation);
            pointerRenderer.UpdateRenderer();
            if (!IsPointerActive())
            {
                bool currentPointerVisibility = pointerRenderer.IsVisible();
                pointerRenderer.ToggleInteraction(currentPointerVisibility);
            }
            CheckHoverSelect();
        }
        else
        {
            Toggle(false);
            currentActivationState = 0;
        }
    }

    protected virtual Quaternion? GetCursorRotation()
    {
        if (EnabledPointerRenderer() && pointerRenderer.directionIndicator != null && pointerRenderer.directionIndicator.gameObject.activeInHierarchy)
        {
            return pointerRenderer.directionIndicator.GetRotation();
        }
        return null;
    }

    protected virtual bool EnabledPointerRenderer()
    {
        return (pointerRenderer != null && pointerRenderer.enabled);
    }

    protected virtual bool NoPointerRenderer()
    {
        return (pointerRenderer == null || !pointerRenderer.enabled);
    }

    protected virtual bool CanActivateOnToggleButton(bool state)
    {
        bool result = (state && !holdButtonToActivate && IsPointerActive());
        if (result)
        {
            willDeactivate = true;
        }
        return result;
    }

    protected virtual bool ControllerRequired()
    {
        return (activationButton != VRTK_ControllerEvents.ButtonAlias.Undefined || selectionButton != VRTK_ControllerEvents.ButtonAlias.Undefined);
    }

    protected virtual bool FindController()
    {
        controllerEvents = (controllerEvents == null ? GetComponentInParent<VRTK_ControllerEvents>() : controllerEvents);
        controllerReference = VRTK_ControllerReference.GetControllerReference((controllerEvents != null ? controllerEvents.gameObject : null));

        if (ControllerRequired() && controllerEvents == null)
        {
            VRTK_Logger.Warn(VRTK_Logger.GetCommonMessage(VRTK_Logger.CommonMessageKeys.REQUIRED_COMPONENT_MISSING_FROM_GAMEOBJECT, "VRTK_Pointer", "VRTK_ControllerEvents", "the Controller Alias", ". To omit this warning, set the `Activation Button` and `Selection Button` to `Undefined`"));
            return false;
        }

        GetInteractUse();

        return true;
    }

    protected virtual void GetInteractUse()
    {
        interactUse = (interactUse != null ? interactUse : GetComponentInChildren<VRTK_InteractUse>());
        interactUse = (interactUse == null && controllerEvents != null ? controllerEvents.GetComponentInChildren<VRTK_InteractUse>() : interactUse);
    }

    protected virtual void SetupController()
    {
        if (controllerEvents != null)
        {
            CheckButtonMappingConflict();
            SubscribeSelectionButton();
            SubscribeActivationButton();
        }
    }

    protected virtual void SetupRenderer()
    {
        if (pointerRenderer == null)
        {
            pointerRenderer = GeneratePointerRenderer();
        }
        if (EnabledPointerRenderer())
        {
            pointerRenderer.InitalizePointer(this, targetListPolicy, navmeshData, headsetPositionCompensation);
        }
    }

    protected virtual VRTK_BasePointerRenderer GeneratePointerRenderer()
    {
        VRTK_BasePointerRenderer returnRenderer = GetComponentInChildren<VRTK_BasePointerRenderer>();
        if (returnRenderer == null)
        {
            returnRenderer = gameObject.AddComponent<VRTK_StraightPointerRenderer>();
            autogenPointerRenderer = (VRTK_StraightPointerRenderer)returnRenderer;
        }
        return returnRenderer;
    }

    protected virtual bool ButtonMappingIsUndefined(VRTK_ControllerEvents.ButtonAlias givenButton, VRTK_ControllerEvents.ButtonAlias givenSubscribedButton)
    {
        return (givenSubscribedButton != VRTK_ControllerEvents.ButtonAlias.Undefined && givenButton == VRTK_ControllerEvents.ButtonAlias.Undefined);
    }

    protected virtual void CheckButtonMappingConflict()
    {
        if (activationButton == selectionButton)
        {
            if (selectOnPress && holdButtonToActivate)
            {
                VRTK_Logger.Warn("`Hold Button To Activate` and `Select On Press` cannot both be checked when using the same button for Activation and Selection. Fixing by setting `Select On Press` to `false`.");
            }

            if (!selectOnPress && !holdButtonToActivate)
            {
                VRTK_Logger.Warn("`Hold Button To Activate` and `Select On Press` cannot both be unchecked when using the same button for Activation and Selection. Fixing by setting `Select On Press` to `true`.");
            }
            selectOnPress = !holdButtonToActivate;
        }
    }

    protected virtual void CheckButtonSubscriptions()
    {
        CheckButtonMappingConflict();

        if (ButtonMappingIsUndefined(selectionButton, subscribedSelectionButton) || selectOnPress != currentSelectOnPress)
        {
            UnsubscribeSelectionButton();
        }

        if (selectionButton != subscribedSelectionButton)
        {
            SubscribeSelectionButton();
            UnsubscribeActivationButton();
        }

        if (ButtonMappingIsUndefined(activationButton, subscribedActivationButton))
        {
            UnsubscribeActivationButton();
        }

        if (activationButton != subscribedActivationButton)
        {
            SubscribeActivationButton();
        }
    }

    protected virtual void SubscribeActivationButton()
    {
        if (subscribedActivationButton != VRTK_ControllerEvents.ButtonAlias.Undefined)
        {
            UnsubscribeActivationButton();
        }

        if (controllerEvents != null)
        {
            controllerEvents.SubscribeToButtonAliasEvent(activationButton, true, DoActivationButtonPressed);
            controllerEvents.SubscribeToButtonAliasEvent(activationButton, false, DoActivationButtonReleased);
            subscribedActivationButton = activationButton;
        }
    }

    protected virtual void UnsubscribeActivationButton()
    {
        if (controllerEvents != null && subscribedActivationButton != VRTK_ControllerEvents.ButtonAlias.Undefined)
        {
            controllerEvents.UnsubscribeToButtonAliasEvent(subscribedActivationButton, true, DoActivationButtonPressed);
            controllerEvents.UnsubscribeToButtonAliasEvent(subscribedActivationButton, false, DoActivationButtonReleased);
            subscribedActivationButton = VRTK_ControllerEvents.ButtonAlias.Undefined;
        }
    }

    protected virtual void PointerActivated()
    {
        if (EnabledPointerRenderer())
        {
            Toggle(true);
        }
    }

    protected virtual void PointerDeactivated()
    {
        if (EnabledPointerRenderer())
        {
            if (IsPointerActive())
            {
                Toggle(false);
            }
        }
    }

    protected virtual void DoActivationButtonPressed(object sender, ControllerInteractionEventArgs e)
    {
        controllerReference = e.controllerReference;
        OnActivationButtonPressed(controllerEvents.SetControllerEvent(ref activationButtonPressed, true));
        PointerActivated();
    }

    protected virtual void DoActivationButtonReleased(object sender, ControllerInteractionEventArgs e)
    {
        controllerReference = e.controllerReference;
        PointerDeactivated();
        OnActivationButtonReleased(controllerEvents.SetControllerEvent(ref activationButtonPressed, false));
    }

    protected virtual void SubscribeSelectionButton()
    {
        if (subscribedSelectionButton != VRTK_ControllerEvents.ButtonAlias.Undefined)
        {
            UnsubscribeSelectionButton();
        }

        if (controllerEvents != null)
        {
            controllerEvents.SubscribeToButtonAliasEvent(selectionButton, true, DoSelectionButtonPressed);
            controllerEvents.SubscribeToButtonAliasEvent(selectionButton, false, DoSelectionButtonReleased);
            controllerEvents.SubscribeToButtonAliasEvent(selectionButton, selectOnPress, SelectionButtonAction);
            subscribedSelectionButton = selectionButton;
            currentSelectOnPress = selectOnPress;
        }
    }

    protected virtual void UnsubscribeSelectionButton()
    {
        if (controllerEvents != null && subscribedSelectionButton != VRTK_ControllerEvents.ButtonAlias.Undefined)
        {
            controllerEvents.UnsubscribeToButtonAliasEvent(selectionButton, true, DoSelectionButtonPressed);
            controllerEvents.UnsubscribeToButtonAliasEvent(selectionButton, false, DoSelectionButtonReleased);
            controllerEvents.UnsubscribeToButtonAliasEvent(subscribedSelectionButton, currentSelectOnPress, SelectionButtonAction);
            subscribedSelectionButton = VRTK_ControllerEvents.ButtonAlias.Undefined;
        }
    }

    protected virtual void DoSelectionButtonPressed(object sender, ControllerInteractionEventArgs e)
    {
        OnSelectionButtonPressed(controllerEvents.SetControllerEvent(ref selectionButtonPressed, true));
    }

    protected virtual void DoSelectionButtonReleased(object sender, ControllerInteractionEventArgs e)
    {
        OnSelectionButtonReleased(controllerEvents.SetControllerEvent(ref selectionButtonPressed, false));
    }

    protected virtual void SelectionButtonAction(object sender, ControllerInteractionEventArgs e)
    {
        controllerReference = e.controllerReference;
        ExecuteSelectionButtonAction();
    }

    protected virtual void ExecuteSelectionButtonAction()
    {
        if (EnabledPointerRenderer() && CanSelect() && (IsPointerActive() || wasActivated))
        {
            wasActivated = false;
            RaycastHit pointerRendererDestinationHit = pointerRenderer.GetDestinationHit();
            AttemptUseOnSet(pointerRendererDestinationHit.transform);
            if (pointerRendererDestinationHit.transform && IsPointerActive() && pointerRenderer.ValidPlayArea() && !PointerActivatesUseAction(pointerInteractableObject) && pointerRenderer.IsValidCollision())
            {
                ResetHoverSelectionTimer(pointerRendererDestinationHit.collider);
                ResetSelectionTimer();
                OnDestinationMarkerSet(SetDestinationMarkerEvent(pointerRendererDestinationHit.distance, pointerRendererDestinationHit.transform, pointerRendererDestinationHit, pointerRendererDestinationHit.point, controllerReference, false, GetCursorRotation()));
            }
        }
    }

    protected virtual bool CanResetActivationState(bool givenState)
    {
        return ((!givenState && holdButtonToActivate) || (givenState && !holdButtonToActivate && currentActivationState >= 2));
    }

    protected virtual void ManageActivationState(bool state)
    {
        if (state)
        {
            currentActivationState++;
        }

        wasActivated = (currentActivationState == 2);

        if (CanResetActivationState(state))
        {
            currentActivationState = 0;
        }
    }

    protected virtual bool PointerActivatesUseAction(VRTK_InteractableObject givenInteractableObject)
    {
        return (givenInteractableObject != null && givenInteractableObject.pointerActivatesUseAction && (!ControllerRequired() || givenInteractableObject.IsValidInteractableController(controllerEvents.gameObject, givenInteractableObject.allowedUseControllers)));
    }

    protected virtual void StartUseAction(Transform target)
    {
        pointerInteractableObject = target.GetComponent<VRTK_InteractableObject>();
        bool cannotUseBecauseNotGrabbed = (pointerInteractableObject && pointerInteractableObject.useOnlyIfGrabbed && !pointerInteractableObject.IsGrabbed());

        if (interactUse != null && PointerActivatesUseAction(pointerInteractableObject) && pointerInteractableObject.holdButtonToUse && !cannotUseBecauseNotGrabbed && pointerInteractableObject.usingState == 0)
        {
            pointerInteractableObject.StartUsing(interactUse);
            pointerInteractableObject.usingState++;
        }
    }

    protected virtual void StopUseAction()
    {
        if (interactUse != null && PointerActivatesUseAction(pointerInteractableObject) && pointerInteractableObject.holdButtonToUse && pointerInteractableObject.IsUsing())
        {
            pointerInteractableObject.StopUsing(interactUse);
            pointerInteractableObject.usingState = 0;
        }
    }

    protected virtual void AttemptUseOnSet(Transform target)
    {
        if (pointerInteractableObject != null && target != null && interactUse != null && PointerActivatesUseAction(pointerInteractableObject))
        {
            if (pointerInteractableObject.IsUsing())
            {
                pointerInteractableObject.StopUsing(interactUse);
                pointerInteractableObject.usingState = 0;
            }
            else if (!pointerInteractableObject.holdButtonToUse)
            {
                pointerInteractableObject.StartUsing(interactUse);
                pointerInteractableObject.usingState++;
            }
        }
    }

    protected virtual void SetHoverSelectionTimer(Collider collider)
    {
        if (collider != currentCollider)
        {
            hoverDurationTimer = 0f;
        }

        if (selectAfterHoverDuration > 0f && hoverDurationTimer <= 0f)
        {
            canClickOnHover = true;
            hoverDurationTimer = selectAfterHoverDuration;
        }

        currentCollider = collider;
    }

    protected virtual void ResetHoverSelectionTimer(Collider collider)
    {
        canClickOnHover = false;
        hoverDurationTimer = (collider == currentCollider ? 0f : hoverDurationTimer);
    }

    protected virtual void CheckHoverSelect()
    {
        if (hoverDurationTimer > 0f)
        {
            hoverDurationTimer -= Time.deltaTime;
        }

        if (canClickOnHover && hoverDurationTimer <= 0f)
        {
            canClickOnHover = false;
            ExecuteSelectionButtonAction();
        }
    }

    protected virtual DestinationMarkerEventArgs GetStateEventPayload()
    {
        DestinationMarkerEventArgs eventPayload = new DestinationMarkerEventArgs();
        if (EnabledPointerRenderer())
        {
            RaycastHit pointerRendererDestinationHit = pointerRenderer.GetDestinationHit();
            eventPayload = SetDestinationMarkerEvent(pointerRendererDestinationHit.distance, pointerRendererDestinationHit.transform, pointerRendererDestinationHit, pointerRendererDestinationHit.point, controllerReference, false, GetCursorRotation());
        }
        return eventPayload;
    }
}

}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容