SteamVR 2.0 交互(二)

脚本分析

player.cs

Interable.cs  

加上可以进行交互。创建高亮。

InterableHoverEvents.cs

交互后的Event

Throw.cs 

加上可以被捡起。

下边文章都是代码,自行复制。后几篇,更新 传送Teleport.cs TelelportPoint等脚本。

疑问: 不知道Hand使用了什么方法对物体检测,应该也是碰撞检测,做了个测试 ,禁用BoxColider后 ,就拿不起来。

但是Hand.cs中没有找到OnTrrigerEnter碰撞函数。也许放到其他脚本中。

这里我对脚本进行了注释,后期完善

如果多的话 ,可以复制脚本去查看。

还有一个问题,启动查看实例 后,会发现 Player 为不销毁的,

最后找了半天,这个逻辑DontDestroyOnLoad  写在了这个脚本中。勾选即可

 

 

 

 

事件检测,其实不是事件  steamvr用的是SendMessage   

这里是检测,给可以建起来的物品添加脚本,拾取 就可以看到输出。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Valve.VR.InteractionSystem;

public class VREvent : MonoBehaviour
{
    /// <summary>
    /// 当手悬浮开始
    /// </summary>
    /// <param name="hand"></param>
    protected virtual void OnHandHoverBegin(Hand hand)
    {
        TimorDebug.LogBold("当手悬浮开始");
    }

    /// <summary>
    /// 当手悬浮更新
    /// </summary>
    /// <param name="hand"></param>
    protected virtual void HandHoverUpdate(Hand hand)
    {
        TimorDebug.LogBold("当手悬浮更新");
    }

    /// <summary>
    /// 当手悬浮结束
    /// </summary>
    /// <param name="hand"></param>
    protected virtual void OnHandHoverEnd(Hand hand)
    {
        TimorDebug.LogBold("当手悬浮结束");
    }



    /// <summary>
    ///  当被添加到手
    /// </summary>
    /// <param name="hand"></param>
    protected virtual void OnAttachedToHand(Hand hand)
    {
        TimorDebug.LogBold("当被添加到手");
    }
    /// <summary>
    /// 当被添加到手每帧
    /// </summary>
    /// <param name="hand"></param>
    protected virtual void HandAttachedUpdate(Hand hand)
    {
        TimorDebug.LogBold("当被添加到手每帧");
    }
    /// <summary>
    /// 当从手中分离
    /// </summary>
    /// <param name="hand"></param>
    protected virtual void OnDetachedFromHand(Hand hand)
    {
        TimorDebug.LogBold("当从手中分离");
    }






    /// <summary>
    /// 当手焦点获取
    /// </summary>
    /// <param name="hand"></param>
    protected virtual void OnHandFocusAcquired(Hand hand)
    {
        TimorDebug.LogBold("当手焦点获取");
    }
    /// <summary>
    /// 当手焦点丢失
    /// </summary>
    /// <param name="hand"></param>
    protected virtual void OnHandFocusLost(Hand hand)
    {
        TimorDebug.LogBold("当手焦点丢失");
    }

}

场景预览

这个有拾取 传送区域 传送点 射箭 操纵小车 人物 交互UI等

名字:Interactions_Example

路径:

 Player.cs

用途:持有2个手,以及头显

//======= Copyright (c) Valve Corporation, All rights reserved. ===============
//
// Purpose: Player interface used to query HMD transforms and VR hands
// 目的:用于查询头显位置和vr手
//=============================================================================

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

namespace Valve.VR.InteractionSystem
{
	//-------------------------------------------------------------------------
	// Singleton representing the local VR player/user, with methods for getting
	// the player's hands, head, tracking origin, and guesses for various properties.
    //表示本地虚拟现实播放器/用户,其方法是
    //玩家的手、头、追踪原点,以及对各种属性的猜测
    //-------------------------------------------------------------------------
    /// <summary>
    /// 玩家
    /// </summary>
    public class Player : MonoBehaviour
	{
		[Tooltip( "Virtual transform corresponding to the meatspace tracking origin. Devices are tracked relative to this." )]
		public Transform trackingOriginTransform;

		[Tooltip( "List of possible transforms for the head/HMD, including the no-SteamVR fallback camera." )]
		public Transform[] hmdTransforms;
        
		[Tooltip( "List of possible Hands, including no-SteamVR fallback Hands." )]
		public Hand[] hands;

		[Tooltip( "Reference to the physics collider that follows the player's HMD position." )]
		public Collider headCollider;

		[Tooltip( "These objects are enabled when SteamVR is available" )]
		public GameObject rigSteamVR;

		[Tooltip( "These objects are enabled when SteamVR is not available, or when the user toggles out of VR" )]
		public GameObject rig2DFallback;

		[Tooltip( "The audio listener for this player" )]
		public Transform audioListener;

        [Tooltip("This action lets you know when the player has placed the headset on their head")]
        public SteamVR_Action_Boolean headsetOnHead = SteamVR_Input.GetBooleanAction("HeadsetOnHead");

		public bool allowToggleTo2D = true;

        #region 玩家单例
        //-------------------------------------------------
        // Singleton instance of the Player. Only one can exist at a time.
        //-------------------------------------------------
        private static Player _instance;
		public static Player instance
		{
			get
			{
				if ( _instance == null )
				{
					_instance = FindObjectOfType<Player>();
				}
				return _instance;
			}
		}
        #endregion

        //-------------------------------------------------
        // Get the number of active Hands.
        //-------------------------------------------------
        /// <summary>
        /// 获取活动手的数量
        /// </summary>
        public int handCount
		{
			get
			{
				int count = 0;
				for ( int i = 0; i < hands.Length; i++ )
				{
					if ( hands[i].gameObject.activeInHierarchy )
					{
						count++;
					}
				}
				return count;
			}
		}


		//-------------------------------------------------
		// Get the i-th active Hand.
		//
		// i - Zero-based index of the active Hand to get
		//-------------------------------------------------
        /// <summary>
        /// 获取活动的手
        /// </summary>
        /// <param name="i"></param>
        /// <returns></returns>
		public Hand GetHand( int i )
		{
			for ( int j = 0; j < hands.Length; j++ )
			{
				if ( !hands[j].gameObject.activeInHierarchy )
				{
					continue;
				}

				if ( i > 0 )
				{
					i--;
					continue;
				}

				return hands[j];
			}

			return null;
		}


		public Hand leftHand
		{
			get
			{
				for ( int j = 0; j < hands.Length; j++ )
				{
					if ( !hands[j].gameObject.activeInHierarchy )
					{
						continue;
					}

					if ( hands[j].handType != SteamVR_Input_Sources.LeftHand)
					{
						continue;
					}

					return hands[j];
				}

				return null;
			}
		}


		//-------------------------------------------------
		public Hand rightHand
		{
			get
			{
				for ( int j = 0; j < hands.Length; j++ )
				{
					if ( !hands[j].gameObject.activeInHierarchy )
					{
						continue;
					}

					if ( hands[j].handType != SteamVR_Input_Sources.RightHand)
					{
						continue;
					}

					return hands[j];
				}

				return null;
			}
		}

        //-------------------------------------------------
        // Get Player scale. Assumes it is scaled equally on all axes.
        //-------------------------------------------------
        /// <summary>
        /// 获取玩家缩放。假设它在所有轴上的比例相等。
        /// </summary>
        public float scale
        {
            get
            {
                return transform.lossyScale.x;
            }
        }


        //-------------------------------------------------
        // Get the HMD transform. This might return the fallback camera transform if SteamVR is unavailable or disabled.
        //-------------------------------------------------
        public Transform hmdTransform
		{
			get
			{
                if (hmdTransforms != null)
                {
                    for (int i = 0; i < hmdTransforms.Length; i++)
                    {
                        if (hmdTransforms[i].gameObject.activeInHierarchy)
                            return hmdTransforms[i];
                    }
                }
				return null;
			}
		}


        //-------------------------------------------------
        // Height of the eyes above the ground - useful for estimating player height.
        //-------------------------------------------------
        /// <summary>
        /// 眼睛离地面的高度-用于估计玩家的高度。
        /// </summary>
        public float eyeHeight
		{
			get
			{
				Transform hmd = hmdTransform;
				if ( hmd )
				{
					Vector3 eyeOffset = Vector3.Project( hmd.position - trackingOriginTransform.position, trackingOriginTransform.up );
					return eyeOffset.magnitude / trackingOriginTransform.lossyScale.x;
				}
				return 0.0f;
			}
		}


		//-------------------------------------------------
		// Guess for the world-space position of the player's feet, directly beneath the HMD.
		//-------------------------------------------------
        /// <summary>
        /// 猜测脚的的位置
        /// </summary>
		public Vector3 feetPositionGuess
		{
			get
			{
				Transform hmd = hmdTransform;
				if ( hmd )
				{
					return trackingOriginTransform.position + Vector3.ProjectOnPlane( hmd.position - trackingOriginTransform.position, trackingOriginTransform.up );
				}
				return trackingOriginTransform.position;
			}
		}


		//-------------------------------------------------
		// Guess for the world-space direction of the player's hips/torso. This is effectively just the gaze direction projected onto the floor plane.
		//-------------------------------------------------
		public Vector3 bodyDirectionGuess
		{
			get
			{
				Transform hmd = hmdTransform;
				if ( hmd )
				{
					Vector3 direction = Vector3.ProjectOnPlane( hmd.forward, trackingOriginTransform.up );
					if ( Vector3.Dot( hmd.up, trackingOriginTransform.up ) < 0.0f )
					{
						// The HMD is upside-down. Either
						// -The player is bending over backwards
						// -The player is bent over looking through their legs
						direction = -direction;
					}
					return direction;
				}
				return trackingOriginTransform.forward;
			}
		}


		//-------------------------------------------------
		private void Awake()
		{
			if ( trackingOriginTransform == null )
			{
				trackingOriginTransform = this.transform;
			}
		}


		//-------------------------------------------------
		private IEnumerator Start()
		{
			_instance = this;

            while (SteamVR.initializedState == SteamVR.InitializedStates.None || SteamVR.initializedState == SteamVR.InitializedStates.Initializing)
                yield return null;

			if ( SteamVR.instance != null )
			{
				ActivateRig( rigSteamVR );
			}
			else
			{
#if !HIDE_DEBUG_UI
				ActivateRig( rig2DFallback );
#endif
			}
        }

        protected virtual void Update()
        {
            if (SteamVR.initializedState != SteamVR.InitializedStates.InitializeSuccess)
                return;

            if (headsetOnHead != null)
            {
                if (headsetOnHead.GetStateDown(SteamVR_Input_Sources.Head))
                {
                    Debug.Log("<b>SteamVR Interaction System</b> Headset placed on head");
                }
                else if (headsetOnHead.GetStateUp(SteamVR_Input_Sources.Head))
                {
                    Debug.Log("<b>SteamVR Interaction System</b> Headset removed");
                }
            }
        }

		//-------------------------------------------------
		void OnDrawGizmos()
		{
			if ( this != instance )
			{
				return;
			}

			//NOTE: These gizmo icons don't work in the plugin since the icons need to exist in a specific "Gizmos"
			//		folder in your Asset tree. These icons are included under Core/Icons. Moving them into a
			//		"Gizmos" folder should make them work again.

			Gizmos.color = Color.white;
			Gizmos.DrawIcon( feetPositionGuess, "vr_interaction_system_feet.png" );

			Gizmos.color = Color.cyan;
			Gizmos.DrawLine( feetPositionGuess, feetPositionGuess + trackingOriginTransform.up * eyeHeight );

			// Body direction arrow
			Gizmos.color = Color.blue;
			Vector3 bodyDirection = bodyDirectionGuess;
			Vector3 bodyDirectionTangent = Vector3.Cross( trackingOriginTransform.up, bodyDirection );
			Vector3 startForward = feetPositionGuess + trackingOriginTransform.up * eyeHeight * 0.75f;
			Vector3 endForward = startForward + bodyDirection * 0.33f;
			Gizmos.DrawLine( startForward, endForward );
			Gizmos.DrawLine( endForward, endForward - 0.033f * ( bodyDirection + bodyDirectionTangent ) );
			Gizmos.DrawLine( endForward, endForward - 0.033f * ( bodyDirection - bodyDirectionTangent ) );

			Gizmos.color = Color.red;
			int count = handCount;
			for ( int i = 0; i < count; i++ )
			{
				Hand hand = GetHand( i );

				if ( hand.handType == SteamVR_Input_Sources.LeftHand)
				{
					Gizmos.DrawIcon( hand.transform.position, "vr_interaction_system_left_hand.png" );
				}
				else if ( hand.handType == SteamVR_Input_Sources.RightHand)
				{
					Gizmos.DrawIcon( hand.transform.position, "vr_interaction_system_right_hand.png" );
				}
				else
				{
                    /*
					Hand.HandType guessHandType = hand.currentHandType;

					if ( guessHandType == Hand.HandType.Left )
					{
						Gizmos.DrawIcon( hand.transform.position, "vr_interaction_system_left_hand_question.png" );
					}
					else if ( guessHandType == Hand.HandType.Right )
					{
						Gizmos.DrawIcon( hand.transform.position, "vr_interaction_system_right_hand_question.png" );
					}
					else
					{
						Gizmos.DrawIcon( hand.transform.position, "vr_interaction_system_unknown_hand.png" );
					}
                    */
				}
			}
		}


		//-------------------------------------------------
        /// <summary>
        /// 绘制2D调试
        /// </summary>
		public void Draw2DDebug()
		{
			if ( !allowToggleTo2D )
				return;

			if ( !SteamVR.active )
				return;

			int width = 100;
			int height = 25;
			int left = Screen.width / 2 - width / 2;
			int top = Screen.height - height - 10;

			string text = ( rigSteamVR.activeSelf ) ? "2D Debug" : "VR";

			if ( GUI.Button( new Rect( left, top, width, height ), text ) )
			{
				if ( rigSteamVR.activeSelf )
				{
					ActivateRig( rig2DFallback );
				}
				else
				{
					ActivateRig( rigSteamVR );
				}
			}
		}


		//-------------------------------------------------
		private void ActivateRig( GameObject rig )
		{
			rigSteamVR.SetActive( rig == rigSteamVR );
			rig2DFallback.SetActive( rig == rig2DFallback );

			if ( audioListener )
			{
				audioListener.transform.parent = hmdTransform;
				audioListener.transform.localPosition = Vector3.zero;
				audioListener.transform.localRotation = Quaternion.identity;
			}
		}


		//-------------------------------------------------
		public void PlayerShotSelf()
		{
			//Do something appropriate here
		}
	}
}

Hand.cs:

 vr手柄按键检测 ,拾取物品,需要 Throw.cs Interable.cs等脚本 

在处理,拾取物品时遇到一些问题,

Steamvr案例给的是,扳机、侧键、鼠标左键。按下拾取物品,松开丢弃

然后我更改为,扳机点击、鼠标点击拾取物品,空格、侧键丢弃物品。

,自行查看逻辑。 中文都注释好了。

//======= Copyright (c) Valve Corporation, All rights reserved. ===============
//
// Purpose: The hands used by the player in the vr interaction system
//  
//=============================================================================

using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using UnityEngine.Events;
using System.Threading;

namespace Valve.VR.InteractionSystem
{
    //-------------------------------------------------------------------------
    // Links with an appropriate SteamVR controller and facilitates
    // interactions with objects in the virtual world.
    //-------------------------------------------------------------------------

    public class Hand : MonoBehaviour
    {
        public KeyCode DetachedKey = KeyCode.Space;
        // The flags used to determine how an object is attached to the hand.
        /// <summary>
        /// 用于确定对象如何附加到手的标志。
        /// </summary>
        [Flags]
        public enum AttachmentFlags
        {
            /// <summary>
            /// 该对象应卡到手上指定附着点的位置。
            /// </summary>
            SnapOnAttach = 1 << 0, // The object should snap to the position of the specified attachment point on the hand.

            /// <summary>
            /// 其他附着在这只手上的物体将被分离
            /// </summary>
            DetachOthers = 1 << 1, // Other objects attached to this hand will be detached.

            /// <summary>
            /// 这个物体将与另一只手分开
            /// </summary>
            DetachFromOtherHand = 1 << 2, // This object will be detached from the other hand.

            /// <summary>
            /// 对象将由手作为父对象
            /// </summary>
            ParentToHand = 1 << 3, // The object will be parented to the hand.

            /// <summary>
            /// 对象将尝试移动以匹配手的位置和旋转
            /// </summary>
            VelocityMovement = 1 << 4, // The object will attempt to move to match the position and rotation of the hand.

            /// <summary>
            /// 物体不会对外部物理产生反应
            /// </summary>
            TurnOnKinematic = 1 << 5, // The object will not respond to external physics.
            TurnOffGravity = 1 << 6, // The object will not respond to external physics.

            /// <summary>
            /// 该物体能够从夹持器切换到夹持器。降低好投掷的可能性,同时也降低意外下降的可能性
            /// </summary>
            AllowSidegrade = 1 << 7, // The object is able to switch from a pinch grab to a grip grab. Decreases likelyhood of a good throw but also decreases likelyhood of accidental drop
        };

        /// <summary>
        /// 默认附加标识
        /// </summary>
        public const AttachmentFlags defaultAttachmentFlags = AttachmentFlags.ParentToHand |
                                                              AttachmentFlags.DetachOthers |
                                                              AttachmentFlags.DetachFromOtherHand |
                                                              AttachmentFlags.TurnOnKinematic |
                                                              AttachmentFlags.SnapOnAttach;

        /// <summary>
        /// 另一只手
        /// </summary>
        public Hand otherHand;
        public SteamVR_Input_Sources handType;

        /// <summary>
        /// 追踪对象
        /// </summary>
        public SteamVR_Behaviour_Pose trackedObject;
        
        /// <summary>
        /// 扳机
        /// </summary>
        public SteamVR_Action_Boolean grabPinchAction = SteamVR_Input.GetAction<SteamVR_Action_Boolean>("GrabPinch");
        
        /// <summary>
        /// 侧键
        /// </summary>
        public SteamVR_Action_Boolean grabGripAction = SteamVR_Input.GetAction<SteamVR_Action_Boolean>("GrabGrip");
        
        public SteamVR_Action_Vibration hapticAction = SteamVR_Input.GetAction<SteamVR_Action_Vibration>("Haptic");
        
        public SteamVR_Action_Boolean uiInteractAction = SteamVR_Input.GetAction<SteamVR_Action_Boolean>("InteractUI");

        /// <summary>
        /// 是否使用悬浮球体
        /// </summary>
        public bool useHoverSphere = true;

        /// <summary>
        /// 悬浮球体变换
        /// </summary>
        public Transform hoverSphereTransform;

        /// <summary>
        /// 悬浮球体半径
        /// </summary>
        public float hoverSphereRadius = 0.05f;
        public LayerMask hoverLayerMask = -1;
        /// <summary>
        /// /悬浮更新间隔
        /// </summary>
        public float hoverUpdateInterval = 0.1f;

        /// <summary>
        /// 是否使用控制器悬停组件
        /// </summary>
        public bool useControllerHoverComponent = true;

        /// <summary>
        /// 控制器悬停组件
        /// </summary>
        public string controllerHoverComponent = "tip";

        /// <summary>
        /// 控制器悬停组件半径
        /// </summary>
        public float controllerHoverRadius = 0.075f;

        /// <summary>
        /// 是否使用手指关节悬停
        /// </summary>
        public bool useFingerJointHover = true;
        public SteamVR_Skeleton_JointIndexEnum fingerJointHover = SteamVR_Skeleton_JointIndexEnum.indexTip;

        /// <summary>
        /// 手指关节悬停半径
        /// </summary>
        public float fingerJointHoverRadius = 0.025f;

        /// <summary>
        /// 对象附加点
        /// </summary>
        [Tooltip("A transform on the hand to center attached objects on")]
        public Transform objectAttachmentPoint;

        /// <summary>
        /// 无SteamVR备用相机
        /// </summary>
        public Camera noSteamVRFallbackCamera;
        /// <summary>
        /// 无SteamVR备用最大距离无项目
        /// </summary>
        public float noSteamVRFallbackMaxDistanceNoItem = 10.0f;
        /// <summary>
        /// 无SteamVR备用最大距离有项目
        /// </summary>
        public float noSteamVRFallbackMaxDistanceWithItem = 0.5f;
        /// <summary>
        /// 无SteamVR备用交互距离
        /// </summary>
        private float noSteamVRFallbackInteractorDistance = -1.0f;

        /// <summary>
        /// 渲染预制件
        /// </summary>
        public GameObject renderModelPrefab;
        protected List<RenderModel> renderModels = new List<RenderModel>();
        /// <summary>
        /// 主要渲染模型
        /// </summary>
        protected RenderModel mainRenderModel;
        /// <summary>
        /// 高亮渲染模型
        /// </summary>
        protected RenderModel hoverhighlightRenderModel;

        /// <summary>
        /// 是否显示调试文本
        /// </summary>
        public bool showDebugText = false;
        public bool spewDebugText = false;

        /// <summary>
        /// 是否调试交互
        /// </summary>
        public bool showDebugInteractables = false;

        /// <summary>
        /// 附加对象
        /// </summary>
        public struct AttachedObject
        {
            /// <summary>
            /// 附加对象
            /// </summary>
            public GameObject attachedObject;
            /// <summary>
            /// 交互
            /// </summary>
            public Interactable interactable;
            /// <summary>
            /// 附加刚体
            /// </summary>
            public Rigidbody attachedRigidbody;
            /// <summary>
            /// 碰撞检测模式
            /// </summary>
            public CollisionDetectionMode collisionDetectionMode;
            /// <summary>
            /// 是否附近刚提为运动学
            /// </summary>
            public bool attachedRigidbodyWasKinematic;
            /// <summary>
            /// 是否附近刚体使用重力
            /// </summary>
            public bool attachedRigidbodyUsedGravity;
            /// <summary>
            /// 原来父物体
            /// </summary>
            public GameObject originalParent;
            /// <summary>
            /// 是否父物体到手
            /// </summary>
            public bool isParentedToHand;
            /// <summary>
            /// 抓取类型
            /// </summary>
            public GrabTypes grabbedWithType;
            /// <summary>
            /// 附加标志
            /// </summary>
            public AttachmentFlags attachmentFlags;
            /// <summary>
            /// 初始化位置偏移
            /// </summary>
            public Vector3 initialPositionalOffset;
            /// <summary>
            /// 初始化旋转便宜
            /// </summary>
            public Quaternion initialRotationalOffset;
            /// <summary>
            /// 附加偏移位置
            /// </summary>
            public Transform attachedOffsetTransform;
            public Transform handAttachmentPointTransform;
            public Vector3 easeSourcePosition;
            public Quaternion easeSourceRotation;
            /// <summary>
            /// 附加时间
            /// </summary>
            public float attachTime;

            /// <summary>
            /// 是否有附加标志
            /// </summary>
            /// <param name="flag"></param>
            /// <returns></returns>
            public bool HasAttachFlag(AttachmentFlags flag)
            {
                return (attachmentFlags & flag) == flag;
            }
        }

        /// <summary>
        /// 附加对象List
        /// </summary>
        private List<AttachedObject> attachedObjects = new List<AttachedObject>();

        public ReadOnlyCollection<AttachedObject> AttachedObjects
        {
            get { return attachedObjects.AsReadOnly(); }
        }

        /// <summary>
        /// 是否悬浮锁定
        /// </summary>
        public bool hoverLocked { get; private set; }

        /// <summary>
        /// 悬浮中交互
        /// </summary>
        private Interactable _hoveringInteractable;

        /// <summary>
        /// 调试文本
        /// </summary>
        private TextMesh debugText;

        private int prevOverlappingColliders = 0;
        /// <summary>
        /// 碰撞器数组大小
        /// </summary>
        private const int ColliderArraySize = 16;
        /// <summary>
        /// 重叠碰撞器
        /// </summary>
        private Collider[] overlappingColliders;

        /// <summary>
        /// 角色单例
        /// </summary>
        private Player playerInstance;

        /// <summary>
        /// 应用程序丢失焦点对象
        /// </summary>
        private GameObject applicationLostFocusObject;

        /// <summary>
        /// 输入焦点对象
        /// </summary>
        private SteamVR_Events.Action inputFocusAction;

        /// <summary>
        /// 是否活动
        /// </summary>
        public bool isActive
        {
            get
            {
                if (trackedObject != null)
                    return trackedObject.isActive;

                return this.gameObject.activeInHierarchy;
            }
        }

        /// <summary>
        /// 是否Pose有效
        /// </summary>
        public bool isPoseValid
        {
            get
            {
                return trackedObject.isValid;
            }
        }


        //-------------------------------------------------
        // The Interactable object this Hand is currently hovering over
        //-------------------------------------------------
        /// <summary>
        /// 悬浮中交互
        /// </summary>
        public Interactable hoveringInteractable
        {
            get { return _hoveringInteractable; }
            set
            {
                if (_hoveringInteractable != value)
                {
                    if (_hoveringInteractable != null)
                    {
                        if (spewDebugText)
                            HandDebugLog("HoverEnd " + _hoveringInteractable.gameObject);
                        _hoveringInteractable.SendMessage("OnHandHoverEnd", this, SendMessageOptions.DontRequireReceiver);

                        //Note: The _hoveringInteractable can change after sending the OnHandHoverEnd message so we need to check it again before broadcasting this message
                        if (_hoveringInteractable != null)
                        {
                            this.BroadcastMessage("OnParentHandHoverEnd", _hoveringInteractable, SendMessageOptions.DontRequireReceiver); // let objects attached to the hand know that a hover has ended
                        }
                    }

                    _hoveringInteractable = value;

                    if (_hoveringInteractable != null)
                    {
                        if (spewDebugText)
                            HandDebugLog("HoverBegin " + _hoveringInteractable.gameObject);
                        _hoveringInteractable.SendMessage("OnHandHoverBegin", this, SendMessageOptions.DontRequireReceiver);

                        //Note: The _hoveringInteractable can change after sending the OnHandHoverBegin message so we need to check it again before broadcasting this message
                        if (_hoveringInteractable != null)
                        {
                            this.BroadcastMessage("OnParentHandHoverBegin", _hoveringInteractable, SendMessageOptions.DontRequireReceiver); // let objects attached to the hand know that a hover has begun
                        }
                    }
                }
            }
        }


        //-------------------------------------------------
        // Active GameObject attached to this Hand
        // 与此手相连的活动游戏对象
        //-------------------------------------------------
        /// <summary>
        /// 当前附加对象
        /// </summary>
        public GameObject currentAttachedObject
        {
            get
            {
                CleanUpAttachedObjectStack();

                if (attachedObjects.Count > 0)
                {
                    return attachedObjects[attachedObjects.Count - 1].attachedObject;
                }

                return null;
            }
        }

        /// <summary>
        /// 当前附加对象信息
        /// </summary>
        public AttachedObject? currentAttachedObjectInfo
        {
            get
            {
                CleanUpAttachedObjectStack();

                if (attachedObjects.Count > 0)
                {
                    return attachedObjects[attachedObjects.Count - 1];
                }

                return null;
            }
        }

        /// <summary>
        /// 骨骼
        /// </summary>
        public SteamVR_Behaviour_Skeleton skeleton
        {
            get
            {
                if (mainRenderModel != null)
                    return mainRenderModel.GetSkeleton();

                return null;
            }
        }

        /// <summary>
        /// 显示控制器
        /// </summary>
        /// <param name="permanent"></param>
        public void ShowController(bool permanent = false)
        {
            if (mainRenderModel != null)
                mainRenderModel.SetControllerVisibility(true, permanent);

            if (hoverhighlightRenderModel != null)
                hoverhighlightRenderModel.SetControllerVisibility(true, permanent);
        }

        /// <summary>
        /// 隐藏控制器
        /// </summary>
        /// <param name="permanent"></param>
        public void HideController(bool permanent = false)
        {
            if (mainRenderModel != null)
                mainRenderModel.SetControllerVisibility(false, permanent);

            if (hoverhighlightRenderModel != null)
                hoverhighlightRenderModel.SetControllerVisibility(false, permanent);
        }

        /// <summary>
        /// 显示骨骼
        /// </summary>
        /// <param name="permanent"></param>
        public void ShowSkeleton(bool permanent = false)
        {
            if (mainRenderModel != null)
                mainRenderModel.SetHandVisibility(true, permanent);

            if (hoverhighlightRenderModel != null)
                hoverhighlightRenderModel.SetHandVisibility(true, permanent);
        }

        /// <summary>
        /// 隐藏骨骼
        /// </summary>
        /// <param name="permanent"></param>
        public void HideSkeleton(bool permanent = false)
        {
            if (mainRenderModel != null)
                mainRenderModel.SetHandVisibility(false, permanent);

            if (hoverhighlightRenderModel != null)
                hoverhighlightRenderModel.SetHandVisibility(false, permanent);
        }

        /// <summary>
        /// 是否有骨骼
        /// </summary>
        /// <returns></returns>
        public bool HasSkeleton()
        {
            return mainRenderModel != null && mainRenderModel.GetSkeleton() != null;
        }


        public void Show()
        {
            SetVisibility(true);
        }

        public void Hide()
        {
            SetVisibility(false);
        }

        /// <summary>
        /// 设置可见性
        /// </summary>
        /// <param name="visible"></param>
        public void SetVisibility(bool visible)
        {
            if (mainRenderModel != null)
                mainRenderModel.SetVisibility(visible);
        }

        /// <summary>
        /// 设置骨架运动范围
        /// </summary>
        /// <param name="newRangeOfMotion">新的运动范围</param>
        /// <param name="blendOverSeconds">几秒钟后混合</param>
        public void SetSkeletonRangeOfMotion(EVRSkeletalMotionRange newRangeOfMotion, float blendOverSeconds = 0.1f)
        {
            for (int renderModelIndex = 0; renderModelIndex < renderModels.Count; renderModelIndex++)
            {
                renderModels[renderModelIndex].SetSkeletonRangeOfMotion(newRangeOfMotion, blendOverSeconds);
            }
        }

        /// <summary>
        /// 设置临时骨架运动范围
        /// </summary>
        /// <param name="temporaryRangeOfMotionChange"></param>
        /// <param name="blendOverSeconds">几秒钟后混合</param>
        public void SetTemporarySkeletonRangeOfMotion(SkeletalMotionRangeChange temporaryRangeOfMotionChange, float blendOverSeconds = 0.1f)
        {
            for (int renderModelIndex = 0; renderModelIndex < renderModels.Count; renderModelIndex++)
            {
                renderModels[renderModelIndex].SetTemporarySkeletonRangeOfMotion(temporaryRangeOfMotionChange, blendOverSeconds);
            }
        }

        /// <summary>
        /// 重置骨架运动范围
        /// </summary>
        /// <param name="blendOverSeconds">几秒钟后混合</param>
        public void ResetTemporarySkeletonRangeOfMotion(float blendOverSeconds = 0.1f)
        {
            for (int renderModelIndex = 0; renderModelIndex < renderModels.Count; renderModelIndex++)
            {
                renderModels[renderModelIndex].ResetTemporarySkeletonRangeOfMotion(blendOverSeconds);
            }
        }

        /// <summary>
        /// 设置动画状态
        /// </summary>
        /// <param name="stateValue">状态值</param>
        public void SetAnimationState(int stateValue)
        {
            for (int renderModelIndex = 0; renderModelIndex < renderModels.Count; renderModelIndex++)
            {
                renderModels[renderModelIndex].SetAnimationState(stateValue);
            }
        }

        /// <summary>
        /// 停止动画
        /// </summary>
        public void StopAnimation()
        {
            for (int renderModelIndex = 0; renderModelIndex < renderModels.Count; renderModelIndex++)
            {
                renderModels[renderModelIndex].StopAnimation();
            }
        }


        //-------------------------------------------------
        // Attach a GameObject to this GameObject
        //
        // objectToAttach - The GameObject to attach
        // flags - The flags to use for attaching the object
        // attachmentPoint - Name of the GameObject in the hierarchy of this Hand which should act as the attachment point for this GameObject
        //-------------------------------------------------
        /// <summary>
        /// 附加对象
        /// </summary>
        /// <param name="objectToAttach">要附加的对象</param>
        /// <param name="grabbedWithType">抓取类型</param>
        /// <param name="flags">附加标志</param>
        /// <param name="attachmentOffset">附加偏移</param>
        public void AttachObject(GameObject objectToAttach, GrabTypes grabbedWithType, AttachmentFlags flags = defaultAttachmentFlags, Transform attachmentOffset = null)
        {
            AttachedObject attachedObject = new AttachedObject();
            attachedObject.attachmentFlags = flags;
            attachedObject.attachedOffsetTransform = attachmentOffset;
            attachedObject.attachTime = Time.time;

            if (flags == 0)
            {
                flags = defaultAttachmentFlags;
            }

            //Make sure top object on stack is non-null
            CleanUpAttachedObjectStack();

            //Detach the object if it is already attached so that it can get re-attached at the top of the stack
            if(ObjectIsAttached(objectToAttach))
                DetachObject(objectToAttach);

            //Detach from the other hand if requested
            if (attachedObject.HasAttachFlag(AttachmentFlags.DetachFromOtherHand))
            {
                if (otherHand != null)
                    otherHand.DetachObject(objectToAttach);
            }

            if (attachedObject.HasAttachFlag(AttachmentFlags.DetachOthers))
            {
                //Detach all the objects from the stack
                while (attachedObjects.Count > 0)
                {
                    DetachObject(attachedObjects[0].attachedObject);
                }
            }

            if (currentAttachedObject)
            {
                currentAttachedObject.SendMessage("OnHandFocusLost", this, SendMessageOptions.DontRequireReceiver);
            }

            attachedObject.attachedObject = objectToAttach;
            attachedObject.interactable = objectToAttach.GetComponent<Interactable>();
            attachedObject.handAttachmentPointTransform = this.transform;

            if (attachedObject.interactable != null)
            {
                if (attachedObject.interactable.attachEaseIn)
                {
                    attachedObject.easeSourcePosition = attachedObject.attachedObject.transform.position;
                    attachedObject.easeSourceRotation = attachedObject.attachedObject.transform.rotation;
                    attachedObject.interactable.snapAttachEaseInCompleted = false;  
                }

                if (attachedObject.interactable.useHandObjectAttachmentPoint)
                    attachedObject.handAttachmentPointTransform = objectAttachmentPoint;

                if (attachedObject.interactable.hideHandOnAttach)
                    Hide();

                if (attachedObject.interactable.hideSkeletonOnAttach && mainRenderModel != null && mainRenderModel.displayHandByDefault)
                    HideSkeleton();

                if (attachedObject.interactable.hideControllerOnAttach && mainRenderModel != null && mainRenderModel.displayControllerByDefault)
                    HideController();

                if (attachedObject.interactable.handAnimationOnPickup != 0)
                    SetAnimationState(attachedObject.interactable.handAnimationOnPickup);

                if (attachedObject.interactable.setRangeOfMotionOnPickup != SkeletalMotionRangeChange.None)
                    SetTemporarySkeletonRangeOfMotion(attachedObject.interactable.setRangeOfMotionOnPickup);

            }

            attachedObject.originalParent = objectToAttach.transform.parent != null ? objectToAttach.transform.parent.gameObject : null;

            attachedObject.attachedRigidbody = objectToAttach.GetComponent<Rigidbody>();
            if (attachedObject.attachedRigidbody != null)
            {
                if (attachedObject.interactable.attachedToHand != null) //already attached to another hand
                {
                    //if it was attached to another hand, get the flags from that hand
                    
                    for (int attachedIndex = 0; attachedIndex < attachedObject.interactable.attachedToHand.attachedObjects.Count; attachedIndex++)
                    {
                        AttachedObject attachedObjectInList = attachedObject.interactable.attachedToHand.attachedObjects[attachedIndex];
                        if (attachedObjectInList.interactable == attachedObject.interactable)
                        {
                            attachedObject.attachedRigidbodyWasKinematic = attachedObjectInList.attachedRigidbodyWasKinematic;
                            attachedObject.attachedRigidbodyUsedGravity = attachedObjectInList.attachedRigidbodyUsedGravity;
                            attachedObject.originalParent = attachedObjectInList.originalParent;
                        }
                    }
                }
                else
                {
                    attachedObject.attachedRigidbodyWasKinematic = attachedObject.attachedRigidbody.isKinematic;
                    attachedObject.attachedRigidbodyUsedGravity = attachedObject.attachedRigidbody.useGravity;
                }
            }

            attachedObject.grabbedWithType = grabbedWithType;

            if (attachedObject.HasAttachFlag(AttachmentFlags.ParentToHand))
            {
                //Parent the object to the hand
                objectToAttach.transform.parent = this.transform;
                attachedObject.isParentedToHand = true;
            }
            else
            {
                attachedObject.isParentedToHand = false;
            }

            if (attachedObject.HasAttachFlag(AttachmentFlags.SnapOnAttach))
            {
                if (attachedObject.interactable != null && attachedObject.interactable.skeletonPoser != null && HasSkeleton())
                {
                    SteamVR_Skeleton_PoseSnapshot pose = attachedObject.interactable.skeletonPoser.GetBlendedPose(skeleton);

                    //snap the object to the center of the attach point
                    objectToAttach.transform.position = this.transform.TransformPoint(pose.position);
                    objectToAttach.transform.rotation = this.transform.rotation * pose.rotation;

                    attachedObject.initialPositionalOffset = attachedObject.handAttachmentPointTransform.InverseTransformPoint(objectToAttach.transform.position);
                    attachedObject.initialRotationalOffset = Quaternion.Inverse(attachedObject.handAttachmentPointTransform.rotation) * objectToAttach.transform.rotation;
                }
                else
                { 
                    if (attachmentOffset != null)
                    {
                        //offset the object from the hand by the positional and rotational difference between the offset transform and the attached object
                        Quaternion rotDiff = Quaternion.Inverse(attachmentOffset.transform.rotation) * objectToAttach.transform.rotation;
                        objectToAttach.transform.rotation = attachedObject.handAttachmentPointTransform.rotation * rotDiff;

                        Vector3 posDiff = objectToAttach.transform.position - attachmentOffset.transform.position;
                        objectToAttach.transform.position = attachedObject.handAttachmentPointTransform.position + posDiff;
                    }
                    else
                    {
                        //snap the object to the center of the attach point
                        objectToAttach.transform.rotation = attachedObject.handAttachmentPointTransform.rotation;
                        objectToAttach.transform.position = attachedObject.handAttachmentPointTransform.position;
                    }

                    Transform followPoint = objectToAttach.transform;

                    attachedObject.initialPositionalOffset = attachedObject.handAttachmentPointTransform.InverseTransformPoint(followPoint.position);
                    attachedObject.initialRotationalOffset = Quaternion.Inverse(attachedObject.handAttachmentPointTransform.rotation) * followPoint.rotation;
                }
            }
            else
            {
                if (attachedObject.interactable != null && attachedObject.interactable.skeletonPoser != null && HasSkeleton())
                {
                    attachedObject.initialPositionalOffset = attachedObject.handAttachmentPointTransform.InverseTransformPoint(objectToAttach.transform.position);
                    attachedObject.initialRotationalOffset = Quaternion.Inverse(attachedObject.handAttachmentPointTransform.rotation) * objectToAttach.transform.rotation;
                }
                else
                {
                    if (attachmentOffset != null)
                    {
                        //get the initial positional and rotational offsets between the hand and the offset transform
                        Quaternion rotDiff = Quaternion.Inverse(attachmentOffset.transform.rotation) * objectToAttach.transform.rotation;
                        Quaternion targetRotation = attachedObject.handAttachmentPointTransform.rotation * rotDiff;
                        Quaternion rotationPositionBy = targetRotation * Quaternion.Inverse(objectToAttach.transform.rotation);

                        Vector3 posDiff = (rotationPositionBy * objectToAttach.transform.position) - (rotationPositionBy * attachmentOffset.transform.position);

                        attachedObject.initialPositionalOffset = attachedObject.handAttachmentPointTransform.InverseTransformPoint(attachedObject.handAttachmentPointTransform.position + posDiff);
                        attachedObject.initialRotationalOffset = Quaternion.Inverse(attachedObject.handAttachmentPointTransform.rotation) * (attachedObject.handAttachmentPointTransform.rotation * rotDiff);
                    }
                    else
                    {
                        attachedObject.initialPositionalOffset = attachedObject.handAttachmentPointTransform.InverseTransformPoint(objectToAttach.transform.position);
                        attachedObject.initialRotationalOffset = Quaternion.Inverse(attachedObject.handAttachmentPointTransform.rotation) * objectToAttach.transform.rotation;
                    }
                }
            }



            if (attachedObject.HasAttachFlag(AttachmentFlags.TurnOnKinematic))
            {
                if (attachedObject.attachedRigidbody != null)
                {
                    attachedObject.collisionDetectionMode = attachedObject.attachedRigidbody.collisionDetectionMode;
                    if (attachedObject.collisionDetectionMode == CollisionDetectionMode.Continuous)
                        attachedObject.attachedRigidbody.collisionDetectionMode = CollisionDetectionMode.Discrete;

                    attachedObject.attachedRigidbody.isKinematic = true;
                }
            }

            if (attachedObject.HasAttachFlag(AttachmentFlags.TurnOffGravity))
            {
                if (attachedObject.attachedRigidbody != null)
                {
                    attachedObject.attachedRigidbody.useGravity = false;
                }
            }

            if (attachedObject.interactable != null && attachedObject.interactable.attachEaseIn)
            {
                attachedObject.attachedObject.transform.position = attachedObject.easeSourcePosition;
                attachedObject.attachedObject.transform.rotation = attachedObject.easeSourceRotation;
            }

            attachedObjects.Add(attachedObject);

            UpdateHovering();

            if (spewDebugText)
                HandDebugLog("AttachObject " + objectToAttach);
            objectToAttach.SendMessage("OnAttachedToHand", this, SendMessageOptions.DontRequireReceiver);
        }

        /// <summary>
        /// 是否对象被附加
        /// </summary>
        /// <param name="go">要检测的对象</param>
        /// <returns></returns>
        public bool ObjectIsAttached(GameObject go)
        {
            for (int attachedIndex = 0; attachedIndex < attachedObjects.Count; attachedIndex++)
            {
                if (attachedObjects[attachedIndex].attachedObject == go)
                    return true;
            }

            return false;
        }

        /// <summary>
        /// 强制悬停解锁
        /// </summary>
        public void ForceHoverUnlock()
        {
            hoverLocked = false;
        }

        //-------------------------------------------------
        // Detach this GameObject from the attached object stack of this Hand
        //
        // objectToDetach - The GameObject to detach from this Hand
        //-------------------------------------------------
        /// <summary>
        /// 分离对象
        /// </summary>
        /// <param name="objectToDetach">要分离的对象</param>
        /// <param name="restoreOriginalParent">是否还原原始父级,默认还原</param>
        public void DetachObject(GameObject objectToDetach, bool restoreOriginalParent = true)
        {
            int index = attachedObjects.FindIndex(l => l.attachedObject == objectToDetach);
            if (index != -1)
            {
                if (spewDebugText)
                    HandDebugLog("DetachObject " + objectToDetach);

                GameObject prevTopObject = currentAttachedObject;


                if (attachedObjects[index].interactable != null)
                {
                    if (attachedObjects[index].interactable.hideHandOnAttach)
                        Show();

                    if (attachedObjects[index].interactable.hideSkeletonOnAttach && mainRenderModel != null && mainRenderModel.displayHandByDefault)
                        ShowSkeleton();

                    if (attachedObjects[index].interactable.hideControllerOnAttach && mainRenderModel != null && mainRenderModel.displayControllerByDefault)
                        ShowController();

                    if (attachedObjects[index].interactable.handAnimationOnPickup != 0)
                        StopAnimation();

                    if (attachedObjects[index].interactable.setRangeOfMotionOnPickup != SkeletalMotionRangeChange.None)
                        ResetTemporarySkeletonRangeOfMotion();
                }

                Transform parentTransform = null;
                if (attachedObjects[index].isParentedToHand)
                {
                    if (restoreOriginalParent && (attachedObjects[index].originalParent != null))
                    {
                        parentTransform = attachedObjects[index].originalParent.transform;
                    }

                    if (attachedObjects[index].attachedObject != null)
                    {
                        attachedObjects[index].attachedObject.transform.parent = parentTransform;
                    }
                }

                if (attachedObjects[index].HasAttachFlag(AttachmentFlags.TurnOnKinematic))
                {
                    if (attachedObjects[index].attachedRigidbody != null)
                    {
                        attachedObjects[index].attachedRigidbody.isKinematic = attachedObjects[index].attachedRigidbodyWasKinematic;
                        attachedObjects[index].attachedRigidbody.collisionDetectionMode = attachedObjects[index].collisionDetectionMode;
                    }
                }

                if (attachedObjects[index].HasAttachFlag(AttachmentFlags.TurnOffGravity))
                {
                    if (attachedObjects[index].attachedObject != null)
                    {
                        if (attachedObjects[index].attachedRigidbody != null)
                            attachedObjects[index].attachedRigidbody.useGravity = attachedObjects[index].attachedRigidbodyUsedGravity;
                    }
                }

                if (attachedObjects[index].interactable != null && attachedObjects[index].interactable.handFollowTransform && HasSkeleton())
                {
                    skeleton.transform.localPosition = Vector3.zero;
                    skeleton.transform.localRotation = Quaternion.identity;
                }

                if (attachedObjects[index].attachedObject != null)
                {
                    if (attachedObjects[index].interactable == null || (attachedObjects[index].interactable != null && attachedObjects[index].interactable.isDestroying == false))
                        attachedObjects[index].attachedObject.SetActive(true);

                    attachedObjects[index].attachedObject.SendMessage("OnDetachedFromHand", this, SendMessageOptions.DontRequireReceiver);
                }

                attachedObjects.RemoveAt(index);

                CleanUpAttachedObjectStack();

                GameObject newTopObject = currentAttachedObject;

                hoverLocked = false;


                //Give focus to the top most object on the stack if it changed
                if (newTopObject != null && newTopObject != prevTopObject)
                {
                    newTopObject.SetActive(true);
                    newTopObject.SendMessage("OnHandFocusAcquired", this, SendMessageOptions.DontRequireReceiver);
                }
            }

            CleanUpAttachedObjectStack();

            if (mainRenderModel != null)
                mainRenderModel.MatchHandToTransform(mainRenderModel.transform);
            if (hoverhighlightRenderModel != null)
                hoverhighlightRenderModel.MatchHandToTransform(hoverhighlightRenderModel.transform);
        }


        //-------------------------------------------------
        // Get the world velocity of the VR Hand.
        //-------------------------------------------------
        /// <summary>
        /// 获取跟踪对象速度
        /// </summary>
        /// <param name="timeOffset">时间偏移</param>
        /// <returns></returns>
        public Vector3 GetTrackedObjectVelocity(float timeOffset = 0)
        {
            if (trackedObject == null)
            {
                Vector3 velocityTarget, angularTarget;
                GetUpdatedAttachedVelocities(currentAttachedObjectInfo.Value, out velocityTarget, out angularTarget);
                return velocityTarget;
            }

                if (isActive)
            {
                if (timeOffset == 0)
                    return Player.instance.trackingOriginTransform.TransformVector(trackedObject.GetVelocity());
                else
                {
                    Vector3 velocity;
                    Vector3 angularVelocity;

                    trackedObject.GetVelocitiesAtTimeOffset(timeOffset, out velocity, out angularVelocity);
                    return Player.instance.trackingOriginTransform.TransformVector(velocity);
                }
            }

            return Vector3.zero;
        }
        

        //-------------------------------------------------
        // Get the world space angular velocity of the VR Hand.
        //-------------------------------------------------
        /// <summary>
        /// 获取追踪对象角速度
        /// </summary>
        /// <param name="timeOffset">时间偏移</param>
        /// <returns></returns>
        public Vector3 GetTrackedObjectAngularVelocity(float timeOffset = 0)
        {
            if (trackedObject == null)
            {
                Vector3 velocityTarget, angularTarget;
                GetUpdatedAttachedVelocities(currentAttachedObjectInfo.Value, out velocityTarget, out angularTarget);
                return angularTarget;
            }

            if (isActive)
            {
                if (timeOffset == 0)
                    return Player.instance.trackingOriginTransform.TransformDirection(trackedObject.GetAngularVelocity());
                else
                {
                    Vector3 velocity;
                    Vector3 angularVelocity;

                    trackedObject.GetVelocitiesAtTimeOffset(timeOffset, out velocity, out angularVelocity);
                    return Player.instance.trackingOriginTransform.TransformDirection(angularVelocity);
                }
            }

            return Vector3.zero;
        }

        /// <summary>
        /// 获取估计的峰值速度
        /// </summary>
        /// <param name="velocity">速度</param>
        /// <param name="angularVelocity">角速度</param>
        public void GetEstimatedPeakVelocities(out Vector3 velocity, out Vector3 angularVelocity)
        {
            trackedObject.GetEstimatedPeakVelocities(out velocity, out angularVelocity);
            velocity = Player.instance.trackingOriginTransform.TransformVector(velocity);
            angularVelocity = Player.instance.trackingOriginTransform.TransformDirection(angularVelocity);
        }


        //-------------------------------------------------
        /// <summary>
        /// 清理附加的对象堆栈
        /// </summary>
        private void CleanUpAttachedObjectStack()
        {
            attachedObjects.RemoveAll(l => l.attachedObject == null);
        }


        //-------------------------------------------------
        protected virtual void Awake()
        {
            inputFocusAction = SteamVR_Events.InputFocusAction(OnInputFocus);

            if (hoverSphereTransform == null)
                hoverSphereTransform = this.transform;

            if (objectAttachmentPoint == null)
                objectAttachmentPoint = this.transform;

            applicationLostFocusObject = new GameObject("_application_lost_focus");
            applicationLostFocusObject.transform.parent = transform;
            applicationLostFocusObject.SetActive(false);

            if (trackedObject == null)
            {
                trackedObject = this.gameObject.GetComponent<SteamVR_Behaviour_Pose>();

                if (trackedObject != null)
                    trackedObject.onTransformUpdatedEvent += OnTransformUpdated;
            }
        }

        protected virtual void OnDestroy()
        {
            if (trackedObject != null)
            {
                trackedObject.onTransformUpdatedEvent -= OnTransformUpdated;
            }
        }

        /// <summary>
        /// 当变换更新
        /// </summary>
        /// <param name="updatedPose">更新姿势</param>
        /// <param name="updatedSource">更新源</param>
        protected virtual void OnTransformUpdated(SteamVR_Behaviour_Pose updatedPose, SteamVR_Input_Sources updatedSource)
        {
            HandFollowUpdate();
        }

        //-------------------------------------------------
        /// <summary>
        /// IEnumerator Start
        /// </summary>
        /// <returns></returns>
        protected virtual IEnumerator Start()
        {
            // save off player instance
            playerInstance = Player.instance;
            if (!playerInstance)
            {
                Debug.LogError("<b>[SteamVR Interaction]</b> No player instance found in Hand Start()");
            }

            // allocate array for colliders
            overlappingColliders = new Collider[ColliderArraySize];

            // We are a "no SteamVR fallback hand" if we have this camera set
            // we'll use the right mouse to look around and left mouse to interact
            // - don't need to find the device
            if (noSteamVRFallbackCamera)
            {
                yield break;
            }

            //Debug.Log( "<b>[SteamVR Interaction]</b> Hand - initializing connection routine" );

            while (true)
            {
                if (isPoseValid)
                {
                    InitController();
                    break;
                }

                yield return null;
            }
        }


        //-------------------------------------------------
        /// <summary>
        /// 更新悬浮
        /// </summary>
        protected virtual void UpdateHovering()
        {
            if ((noSteamVRFallbackCamera == null) && (isActive == false))
            {
                return;
            }

            if (hoverLocked)
                return;

            if (applicationLostFocusObject.activeSelf)
                return;

            float closestDistance = float.MaxValue;
            Interactable closestInteractable = null;

            if (useHoverSphere)
            {
                float scaledHoverRadius = hoverSphereRadius * Mathf.Abs(SteamVR_Utils.GetLossyScale(hoverSphereTransform));
                CheckHoveringForTransform(hoverSphereTransform.position, scaledHoverRadius, ref closestDistance, ref closestInteractable, Color.green);
            }

            if (useControllerHoverComponent && mainRenderModel != null && mainRenderModel.IsControllerVisibile())
            {
                float scaledHoverRadius = controllerHoverRadius * Mathf.Abs(SteamVR_Utils.GetLossyScale(this.transform));
                CheckHoveringForTransform(mainRenderModel.GetControllerPosition(controllerHoverComponent), scaledHoverRadius / 2f, ref closestDistance, ref closestInteractable, Color.blue);
            }

            if (useFingerJointHover && mainRenderModel != null && mainRenderModel.IsHandVisibile())
            {
                float scaledHoverRadius = fingerJointHoverRadius * Mathf.Abs(SteamVR_Utils.GetLossyScale(this.transform));
                CheckHoveringForTransform(mainRenderModel.GetBonePosition((int)fingerJointHover), scaledHoverRadius / 2f, ref closestDistance, ref closestInteractable, Color.yellow);
            }

            // Hover on this one
            hoveringInteractable = closestInteractable;
        }

        /// <summary>
        /// 检查悬浮从位置
        /// </summary>
        /// <param name="hoverPosition">悬浮位置</param>
        /// <param name="hoverRadius">悬浮半径</param>
        /// <param name="closestDistance">最近距离</param>
        /// <param name="closestInteractable">最近交互</param>
        /// <param name="debugColor">调试颜色</param>
        /// <returns></returns>
        protected virtual bool CheckHoveringForTransform(Vector3 hoverPosition, float hoverRadius, ref float closestDistance, ref Interactable closestInteractable, Color debugColor)
        {
            bool foundCloser = false;

            // null out old vals
            for (int i = 0; i < overlappingColliders.Length; ++i)
            {
                overlappingColliders[i] = null;
            }

            int numColliding = Physics.OverlapSphereNonAlloc(hoverPosition, hoverRadius, overlappingColliders, hoverLayerMask.value);

            if (numColliding == ColliderArraySize)
                Debug.LogWarning("<b>[SteamVR Interaction]</b> This hand is overlapping the max number of colliders: " + ColliderArraySize + ". Some collisions may be missed. Increase ColliderArraySize on Hand.cs");

            // DebugVar
            int iActualColliderCount = 0;

            // Pick the closest hovering
            for (int colliderIndex = 0; colliderIndex < overlappingColliders.Length; colliderIndex++)
            {
                Collider collider = overlappingColliders[colliderIndex];

                if (collider == null)
                    continue;

                Interactable contacting = collider.GetComponentInParent<Interactable>();

                // Yeah, it's null, skip
                if (contacting == null)
                    continue;

                // Ignore this collider for hovering
                IgnoreHovering ignore = collider.GetComponent<IgnoreHovering>();
                if (ignore != null)
                {
                    if (ignore.onlyIgnoreHand == null || ignore.onlyIgnoreHand == this)
                    {
                        continue;
                    }
                }

                // Can't hover over the object if it's attached
                bool hoveringOverAttached = false;
                for (int attachedIndex = 0; attachedIndex < attachedObjects.Count; attachedIndex++)
                {
                    if (attachedObjects[attachedIndex].attachedObject == contacting.gameObject)
                    {
                        hoveringOverAttached = true;
                        break;
                    }
                }
                if (hoveringOverAttached)
                    continue;

                // Occupied by another hand, so we can't touch it
                if (otherHand && otherHand.hoveringInteractable == contacting)
                    continue;

                // Best candidate so far...
                float distance = Vector3.Distance(contacting.transform.position, hoverPosition);
                if (distance < closestDistance)
                {
                    closestDistance = distance;
                    closestInteractable = contacting;
                    foundCloser = true;
                }
                iActualColliderCount++;
            }

            if (showDebugInteractables && foundCloser)
            {
                Debug.DrawLine(hoverPosition, closestInteractable.transform.position, debugColor, .05f, false);
            }

            if (iActualColliderCount > 0 && iActualColliderCount != prevOverlappingColliders)
            {
                prevOverlappingColliders = iActualColliderCount;

                if (spewDebugText)
                    HandDebugLog("Found " + iActualColliderCount + " overlapping colliders.");
            }

            return foundCloser;
        }


        //-------------------------------------------------
        /// <summary>
        /// 更新无SteamVR备用
        /// </summary>
        protected virtual void UpdateNoSteamVRFallback()
        {
            if (noSteamVRFallbackCamera)
            {
                Ray ray = noSteamVRFallbackCamera.ScreenPointToRay(Input.mousePosition);

                if (attachedObjects.Count > 0)
                {
                    // Holding down the mouse:
                    // move around a fixed distance from the camera
                    transform.position = ray.origin + noSteamVRFallbackInteractorDistance * ray.direction;
                }
                else
                {
                    // Not holding down the mouse:
                    // cast out a ray to see what we should mouse over

                    // Don't want to hit the hand and anything underneath it
                    // So move it back behind the camera when we do the raycast
                    Vector3 oldPosition = transform.position;
                    transform.position = noSteamVRFallbackCamera.transform.forward * (-1000.0f);

                    RaycastHit raycastHit;
                    if (Physics.Raycast(ray, out raycastHit, noSteamVRFallbackMaxDistanceNoItem))
                    {
                        transform.position = raycastHit.point;

                        // Remember this distance in case we click and drag the mouse
                        noSteamVRFallbackInteractorDistance = Mathf.Min(noSteamVRFallbackMaxDistanceNoItem, raycastHit.distance);
                    }
                    else if (noSteamVRFallbackInteractorDistance > 0.0f)
                    {
                        // Move it around at the distance we last had a hit
                        transform.position = ray.origin + Mathf.Min(noSteamVRFallbackMaxDistanceNoItem, noSteamVRFallbackInteractorDistance) * ray.direction;
                    }
                    else
                    {
                        // Didn't hit, just leave it where it was
                        transform.position = oldPosition;
                    }
                }
            }
        }


        //-------------------------------------------------
        /// <summary>
        /// 更新调试文本
        /// </summary>
        private void UpdateDebugText()
        {
            if (showDebugText)
            {
                if (debugText == null)
                {
                    debugText = new GameObject("_debug_text").AddComponent<TextMesh>();
                    debugText.fontSize = 120;
                    debugText.characterSize = 0.001f;
                    debugText.transform.parent = transform;

                    debugText.transform.localRotation = Quaternion.Euler(90.0f, 0.0f, 0.0f);
                }

                if (handType == SteamVR_Input_Sources.RightHand)
                {
                    debugText.transform.localPosition = new Vector3(-0.05f, 0.0f, 0.0f);
                    debugText.alignment = TextAlignment.Right;
                    debugText.anchor = TextAnchor.UpperRight;
                }
                else
                {
                    debugText.transform.localPosition = new Vector3(0.05f, 0.0f, 0.0f);
                    debugText.alignment = TextAlignment.Left;
                    debugText.anchor = TextAnchor.UpperLeft;
                }

                debugText.text = string.Format(
                    "Hovering: {0}\n" +
                    "Hover Lock: {1}\n" +
                    "Attached: {2}\n" +
                    "Total Attached: {3}\n" +
                    "Type: {4}\n",
                    (hoveringInteractable ? hoveringInteractable.gameObject.name : "null"),
                    hoverLocked,
                    (currentAttachedObject ? currentAttachedObject.name : "null"),
                    attachedObjects.Count,
                    handType.ToString());
            }
            else
            {
                if (debugText != null)
                {
                    Destroy(debugText.gameObject);
                }
            }
        }


        //-------------------------------------------------
        protected virtual void OnEnable()
        {
            inputFocusAction.enabled = true;

            // Stagger updates between hands
            float hoverUpdateBegin = ((otherHand != null) && (otherHand.GetInstanceID() < GetInstanceID())) ? (0.5f * hoverUpdateInterval) : (0.0f);
            InvokeRepeating("UpdateHovering", hoverUpdateBegin, hoverUpdateInterval);
            InvokeRepeating("UpdateDebugText", hoverUpdateBegin, hoverUpdateInterval);
        }


        //-------------------------------------------------
        protected virtual void OnDisable()
        {
            inputFocusAction.enabled = false;

            CancelInvoke();
        }

        
        //-------------------------------------------------
        protected virtual void Update()
        {
            UpdateNoSteamVRFallback();

            GameObject attachedObject = currentAttachedObject;
            if (attachedObject != null)
            {
                attachedObject.SendMessage("HandAttachedUpdate", this, SendMessageOptions.DontRequireReceiver);
            }

            if (hoveringInteractable)
            {
                hoveringInteractable.SendMessage("HandHoverUpdate", this, SendMessageOptions.DontRequireReceiver);
            }
        }

        /// <summary>
        /// 当手当前悬停在可交互传入的
        /// Returns true when the hand is currently hovering over the interactable passed in
        /// </summary>
        public bool IsStillHovering(Interactable interactable)
        {
            return hoveringInteractable == interactable;
        }

        /// <summary>
        /// 手跟踪更新
        /// </summary>
        protected virtual void HandFollowUpdate()
        {
            GameObject attachedObject = currentAttachedObject;
            if (attachedObject != null)
            {
                if (currentAttachedObjectInfo.Value.interactable != null)
                {
                    SteamVR_Skeleton_PoseSnapshot pose = null;

                    if (currentAttachedObjectInfo.Value.interactable.skeletonPoser != null && HasSkeleton())
                    {
                        pose = currentAttachedObjectInfo.Value.interactable.skeletonPoser.GetBlendedPose(skeleton);
                    }

                    if (currentAttachedObjectInfo.Value.interactable.handFollowTransform)
                    {
                        Quaternion targetHandRotation;
                        Vector3 targetHandPosition;

                        if (pose == null)
                        {
                            Quaternion offset = Quaternion.Inverse(this.transform.rotation) * currentAttachedObjectInfo.Value.handAttachmentPointTransform.rotation;
                            targetHandRotation = currentAttachedObjectInfo.Value.interactable.transform.rotation * Quaternion.Inverse(offset);

                            Vector3 worldOffset = (this.transform.position - currentAttachedObjectInfo.Value.handAttachmentPointTransform.position);
                            Quaternion rotationDiff = mainRenderModel.GetHandRotation() * Quaternion.Inverse(this.transform.rotation);
                            Vector3 localOffset = rotationDiff * worldOffset;
                            targetHandPosition = currentAttachedObjectInfo.Value.interactable.transform.position + localOffset;
                        }
                        else
                        {
                            Transform objectT = currentAttachedObjectInfo.Value.attachedObject.transform;
                            Vector3 oldItemPos = objectT.position;
                            Quaternion oldItemRot = objectT.transform.rotation;
                            objectT.position = TargetItemPosition(currentAttachedObjectInfo.Value);
                            objectT.rotation = TargetItemRotation(currentAttachedObjectInfo.Value);
                            Vector3 localSkelePos = objectT.InverseTransformPoint(transform.position);
                            Quaternion localSkeleRot = Quaternion.Inverse(objectT.rotation) * transform.rotation;
                            objectT.position = oldItemPos;
                            objectT.rotation = oldItemRot;

                            targetHandPosition = objectT.TransformPoint(localSkelePos);
                            targetHandRotation = objectT.rotation * localSkeleRot;
                        }

                        if (mainRenderModel != null)
                            mainRenderModel.SetHandRotation(targetHandRotation);
                        if (hoverhighlightRenderModel != null)
                            hoverhighlightRenderModel.SetHandRotation(targetHandRotation);

                        if (mainRenderModel != null)
                            mainRenderModel.SetHandPosition(targetHandPosition);
                        if (hoverhighlightRenderModel != null)
                            hoverhighlightRenderModel.SetHandPosition(targetHandPosition);
                    }
                }
            }
        }

        protected virtual void FixedUpdate()
        {
            if (currentAttachedObject != null)
            {
                AttachedObject attachedInfo = currentAttachedObjectInfo.Value;
                if (attachedInfo.attachedObject != null)
                {
                    if (attachedInfo.HasAttachFlag(AttachmentFlags.VelocityMovement))
                    {
                        if(attachedInfo.interactable.attachEaseIn == false || attachedInfo.interactable.snapAttachEaseInCompleted)
                            UpdateAttachedVelocity(attachedInfo);

                        /*if (attachedInfo.interactable.handFollowTransformPosition)
                        {
                            skeleton.transform.position = TargetSkeletonPosition(attachedInfo);
                            skeleton.transform.rotation = attachedInfo.attachedObject.transform.rotation * attachedInfo.skeletonLockRotation;
                        }*/
                    }else
                    {
                        if (attachedInfo.HasAttachFlag(AttachmentFlags.ParentToHand))
                        {
                            attachedInfo.attachedObject.transform.position = TargetItemPosition(attachedInfo);
                            attachedInfo.attachedObject.transform.rotation = TargetItemRotation(attachedInfo);
                        }
                    }


                    if (attachedInfo.interactable.attachEaseIn)
                    {
                        float t = Util.RemapNumberClamped(Time.time, attachedInfo.attachTime, attachedInfo.attachTime + attachedInfo.interactable.snapAttachEaseInTime, 0.0f, 1.0f);
                        if (t < 1.0f)
                        {
                            if (attachedInfo.HasAttachFlag(AttachmentFlags.VelocityMovement))
                            {
                                attachedInfo.attachedRigidbody.velocity = Vector3.zero;
                                attachedInfo.attachedRigidbody.angularVelocity = Vector3.zero;
                            }
                            t = attachedInfo.interactable.snapAttachEaseInCurve.Evaluate(t);
                            attachedInfo.attachedObject.transform.position = Vector3.Lerp(attachedInfo.easeSourcePosition, TargetItemPosition(attachedInfo), t);
                            attachedInfo.attachedObject.transform.rotation = Quaternion.Lerp(attachedInfo.easeSourceRotation, TargetItemRotation(attachedInfo), t);
                        }
                        else if (!attachedInfo.interactable.snapAttachEaseInCompleted)
                        {
                            attachedInfo.interactable.gameObject.SendMessage("OnThrowableAttachEaseInCompleted", this, SendMessageOptions.DontRequireReceiver);
                            attachedInfo.interactable.snapAttachEaseInCompleted = true;
                        }
                    }
                }
            }
        }

        /// <summary>
        /// 最大速度变化
        /// </summary>
        protected const float MaxVelocityChange = 10f;
        /// <summary>
        /// 速度Magic
        /// </summary>
        protected const float VelocityMagic = 6000f;
        /// <summary>
        /// 角速度Magic
        /// </summary>
        protected const float AngularVelocityMagic = 50f;
        /// <summary>
        /// 最大角速度变化
        /// </summary>
        protected const float MaxAngularVelocityChange = 20f;

        /// <summary>
        /// 更新附加的速度
        /// </summary>
        /// <param name="attachedObjectInfo">附加物体信息</param>
        protected void UpdateAttachedVelocity(AttachedObject attachedObjectInfo)
        {
            Vector3 velocityTarget, angularTarget;
            bool success = GetUpdatedAttachedVelocities(attachedObjectInfo, out velocityTarget, out angularTarget);
            if (success)
            {
                float scale = SteamVR_Utils.GetLossyScale(currentAttachedObjectInfo.Value.handAttachmentPointTransform);
                float maxAngularVelocityChange = MaxAngularVelocityChange * scale;
                float maxVelocityChange = MaxVelocityChange * scale;

                attachedObjectInfo.attachedRigidbody.velocity = Vector3.MoveTowards(attachedObjectInfo.attachedRigidbody.velocity, velocityTarget, maxVelocityChange);
                attachedObjectInfo.attachedRigidbody.angularVelocity = Vector3.MoveTowards(attachedObjectInfo.attachedRigidbody.angularVelocity, angularTarget, maxAngularVelocityChange);
            }
        }

        /// <summary>
        /// /目标项目位置
        /// </summary>
        /// <param name="attachedObject"></param>
        /// <returns></returns>
        protected Vector3 TargetItemPosition(AttachedObject attachedObject)
        {
            if (attachedObject.interactable != null && attachedObject.interactable.skeletonPoser != null && HasSkeleton())
            {
                Vector3 tp = attachedObject.handAttachmentPointTransform.InverseTransformPoint(transform.TransformPoint(attachedObject.interactable.skeletonPoser.GetBlendedPose(skeleton).position));
                //tp.x *= -1;
                return currentAttachedObjectInfo.Value.handAttachmentPointTransform.TransformPoint(tp);
            }
            else
            {
                return currentAttachedObjectInfo.Value.handAttachmentPointTransform.TransformPoint(attachedObject.initialPositionalOffset);
            }
        }

        /// <summary>
        /// 目标项目旋转
        /// </summary>
        /// <param name="attachedObject"></param>
        /// <returns></returns>
        protected Quaternion TargetItemRotation(AttachedObject attachedObject)
        {
            if (attachedObject.interactable != null && attachedObject.interactable.skeletonPoser != null && HasSkeleton())
            {
                Quaternion tr = Quaternion.Inverse(attachedObject.handAttachmentPointTransform.rotation) * (transform.rotation * attachedObject.interactable.skeletonPoser.GetBlendedPose(skeleton).rotation);
                return currentAttachedObjectInfo.Value.handAttachmentPointTransform.rotation * tr;
            }
            else
            {
                return currentAttachedObjectInfo.Value.handAttachmentPointTransform.rotation * attachedObject.initialRotationalOffset;
            }
        }

        /// <summary>
        /// 是否获取更新附加的速度
        /// </summary>
        /// <param name="attachedObjectInfo">附加对象信息</param>
        /// <param name="velocityTarget">目标速度</param>
        /// <param name="angularTarget">目标角度</param>
        /// <returns></returns>
        protected bool GetUpdatedAttachedVelocities(AttachedObject attachedObjectInfo, out Vector3 velocityTarget, out Vector3 angularTarget)
        {
            bool realNumbers = false;


            float velocityMagic = VelocityMagic;
            float angularVelocityMagic = AngularVelocityMagic;

            Vector3 targetItemPosition = TargetItemPosition(attachedObjectInfo);
            Vector3 positionDelta = (targetItemPosition - attachedObjectInfo.attachedRigidbody.position);
            velocityTarget = (positionDelta * velocityMagic * Time.deltaTime);

            if (float.IsNaN(velocityTarget.x) == false && float.IsInfinity(velocityTarget.x) == false)
            {
                if (noSteamVRFallbackCamera)
                    velocityTarget /= 10; //hacky fix for fallback

                realNumbers = true;
            }
            else
                velocityTarget = Vector3.zero;


            Quaternion targetItemRotation = TargetItemRotation(attachedObjectInfo);
            Quaternion rotationDelta = targetItemRotation * Quaternion.Inverse(attachedObjectInfo.attachedObject.transform.rotation);


            float angle;
            Vector3 axis;
            rotationDelta.ToAngleAxis(out angle, out axis);

            if (angle > 180)
                angle -= 360;

            if (angle != 0 && float.IsNaN(axis.x) == false && float.IsInfinity(axis.x) == false)
            {
                angularTarget = angle * axis * angularVelocityMagic * Time.deltaTime;

                if (noSteamVRFallbackCamera)
                    angularTarget /= 10; //hacky fix for fallback

                realNumbers &= true;
            }
            else
                angularTarget = Vector3.zero;

            return realNumbers;
        }


        //-------------------------------------------------
        /// <summary>
        /// 当输入焦点
        /// </summary>
        /// <param name="hasFocus">是否有焦点</param>
        protected virtual void OnInputFocus(bool hasFocus)
        {
            if (hasFocus)
            {
                DetachObject(applicationLostFocusObject, true);
                applicationLostFocusObject.SetActive(false);
                UpdateHovering();
                BroadcastMessage("OnParentHandInputFocusAcquired", SendMessageOptions.DontRequireReceiver);
            }
            else
            {
                applicationLostFocusObject.SetActive(true);
                AttachObject(applicationLostFocusObject, GrabTypes.Scripted, AttachmentFlags.ParentToHand);
                BroadcastMessage("OnParentHandInputFocusLost", SendMessageOptions.DontRequireReceiver);
            }
        }

        //-------------------------------------------------
        protected virtual void OnDrawGizmos()
        {
            if (useHoverSphere && hoverSphereTransform != null)
            {
                Gizmos.color = Color.green;
                float scaledHoverRadius = hoverSphereRadius * Mathf.Abs(SteamVR_Utils.GetLossyScale(hoverSphereTransform));
                Gizmos.DrawWireSphere(hoverSphereTransform.position, scaledHoverRadius/2);
            }

            if (useControllerHoverComponent && mainRenderModel != null && mainRenderModel.IsControllerVisibile())
            {
                Gizmos.color = Color.blue;
                float scaledHoverRadius = controllerHoverRadius * Mathf.Abs(SteamVR_Utils.GetLossyScale(this.transform));
                Gizmos.DrawWireSphere(mainRenderModel.GetControllerPosition(controllerHoverComponent), scaledHoverRadius/2);
            }

            if (useFingerJointHover && mainRenderModel != null && mainRenderModel.IsHandVisibile())
            {
                Gizmos.color = Color.yellow;
                float scaledHoverRadius = fingerJointHoverRadius * Mathf.Abs(SteamVR_Utils.GetLossyScale(this.transform));
                Gizmos.DrawWireSphere(mainRenderModel.GetBonePosition((int)fingerJointHover), scaledHoverRadius/2);
            }
        }


        //-------------------------------------------------
        /// <summary>
        /// 调试日志
        /// </summary>
        /// <param name="msg">消息</param>
        private void HandDebugLog(string msg)
        {
            if (spewDebugText)
            {
                Debug.Log("<b>[SteamVR Interaction]</b> Hand (" + this.name + "): " + msg);
            }
        }


        //-------------------------------------------------
        // Continue to hover over this object indefinitely, whether or not the Hand moves out of its interaction trigger volume.
        //
        // interactable - The Interactable to hover over indefinitely.
        //-------------------------------------------------
        /// <summary>
        /// 悬浮锁定
        /// </summary>
        /// <param name="interactable">交互</param>
        public void HoverLock(Interactable interactable)
        {
            if (spewDebugText)
                HandDebugLog("HoverLock " + interactable);
            hoverLocked = true;
            hoveringInteractable = interactable;
        }


        //-------------------------------------------------
        // Stop hovering over this object indefinitely.
        //
        // interactable - The hover-locked Interactable to stop hovering over indefinitely.
        //-------------------------------------------------
        /// <summary>
        /// 悬浮解锁
        /// </summary>
        /// <param name="interactable"></param>
        public void HoverUnlock(Interactable interactable)
        {
            if (spewDebugText)
                HandDebugLog("HoverUnlock " + interactable);

            if (hoveringInteractable == interactable)
            {
                hoverLocked = false;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="microSecondsDuration"></param>
        public void TriggerHapticPulse(ushort microSecondsDuration)
        {
            float seconds = (float)microSecondsDuration / 1000000f;
            hapticAction.Execute(0, seconds, 1f / seconds, 1, handType);
        }

        /// <summary>
        /// 触发触觉脉冲,或许是震动
        /// </summary>
        /// <param name="duration">持续时间</param>
        /// <param name="frequency">频率</param>
        /// <param name="amplitude">振幅</param>
        public void TriggerHapticPulse(float duration, float frequency, float amplitude)
        {
            hapticAction.Execute(0, duration, frequency, amplitude, handType);
        }

        /// <summary>
        /// 显示抓取提示
        /// </summary>
        public void ShowGrabHint()
        {
            ControllerButtonHints.ShowButtonHint(this, grabGripAction); //todo: assess
        }
        
        /// <summary>
        /// 隐藏抓取信息
        /// </summary>
        public void HideGrabHint()
        {
            ControllerButtonHints.HideButtonHint(this, grabGripAction); //todo: assess
        }

        /// <summary>
        /// 显示抓取提示
        /// </summary>
        /// <param name="text">文本</param>
        public void ShowGrabHint(string text)
        {
            ControllerButtonHints.ShowTextHint(this, grabGripAction, text);
        }

        /// <summary>
        /// 获取抓取开始
        /// </summary>
        /// <param name="explicitType"></param>
        /// <returns></returns>
        public GrabTypes GetGrabStarting(GrabTypes explicitType = GrabTypes.None)
        {
            if (explicitType != GrabTypes.None)
            {
                if (noSteamVRFallbackCamera)
                {
                    if (Input.GetMouseButtonDown(0))
                        return explicitType;
                }

                if (explicitType == GrabTypes.Pinch && grabPinchAction.GetStateDown(handType))
                    return GrabTypes.Pinch;
              //  if (explicitType == GrabTypes.Grip && grabGripAction.GetStateDown(handType))
              //      return GrabTypes.Grip;
            }
            else
            {
                if (noSteamVRFallbackCamera)
                {
                    if (Input.GetMouseButtonDown(0))
                        return GrabTypes.Grip;
                }

                if (grabPinchAction.GetStateDown(handType))
                    return GrabTypes.Pinch;
               // if (grabGripAction.GetStateDown(handType))
               //     return GrabTypes.Grip;
            }

            return GrabTypes.None;
        }

        /// <summary>
        /// 获取抓取结束
        /// </summary>
        /// <param name="explicitType"></param>
        /// <returns></returns>
        public GrabTypes GetGrabEnding(GrabTypes explicitType = GrabTypes.None)
        {
            if (explicitType != GrabTypes.None)
            {
                if (noSteamVRFallbackCamera)
                {
                    //   if (Input.GetMouseButtonUp(0))
                    //       return explicitType;
                    if (Input.GetKeyUp(DetachedKey))
                    {
                        return explicitType;
                    }
                }

                //if (explicitType == GrabTypes.Pinch && grabPinchAction.GetStateUp(handType))
               //     return GrabTypes.Pinch;
                if (explicitType == GrabTypes.Grip && grabGripAction.GetStateUp(handType))
                    return GrabTypes.Grip;
            }
            else
            {
                if (noSteamVRFallbackCamera)
                {
                    //if (Input.GetMouseButtonUp(0))
                    //    return GrabTypes.Grip;
                    if (Input.GetKeyUp(DetachedKey))
                    {
                        return explicitType;
                    }
                }

               // if (grabPinchAction.GetStateUp(handType))
                //    return GrabTypes.Pinch;
                if (grabGripAction.GetStateUp(handType))
                    return GrabTypes.Grip;
            }

            return GrabTypes.None;
        }

        /// <summary>
        /// 物体是否抓取结束
        /// </summary>
        /// <param name="attachedObject">要附加的物体</param>
        /// <returns></returns>
        public bool IsGrabEnding(GameObject attachedObject)
        {
            for (int attachedObjectIndex = 0; attachedObjectIndex < attachedObjects.Count; attachedObjectIndex++)
            {
                if (attachedObjects[attachedObjectIndex].attachedObject == attachedObject)
                {
                    return IsGrabbingWithType(attachedObjects[attachedObjectIndex].grabbedWithType) == false;
                }
            }

            return false;
        }

        /// <summary>
        /// 是否正在抓取
        /// </summary>
        /// <param name="type">类型</param>
        /// <returns></returns>
        public bool IsGrabbingWithType(GrabTypes type)
        {
            if (noSteamVRFallbackCamera)
            {
                if (Input.GetKeyUp(DetachedKey))
                {
                    return false;
                }
            }

            if (grabGripAction.GetStateUp(handType))
            {
                return false;
            }
            return true;
            //if (noSteamVRFallbackCamera)
            //{
            //    if (Input.GetMouseButton(0))
            //        return true;
            //}

           
            //switch (type)
            //{
            //    case GrabTypes.Pinch:
            //        return grabPinchAction.GetState(handType);

            //    case GrabTypes.Grip:
            //        return grabGripAction.GetState(handType);

            //    default:
            //        return false;
            //}
        }

        /// <summary>
        /// 正在用相反的类型抓取
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public bool IsGrabbingWithOppositeType(GrabTypes type)
        {
            if (noSteamVRFallbackCamera)
            {
                if (Input.GetMouseButton(0))
                    return true;
            }

            switch (type)
            {
                case GrabTypes.Pinch:
                    return grabGripAction.GetState(handType);

                case GrabTypes.Grip:
                    return grabPinchAction.GetState(handType);

                default:
                    return false;
            }
        }

        /// <summary>
        /// 获得做好的抓取类型???
        /// </summary>
        /// <returns></returns>
        public GrabTypes GetBestGrabbingType()
        {
            return GetBestGrabbingType(GrabTypes.None);
        }

        /// <summary>
        /// 获取最好的抓取类型
        /// </summary>
        /// <param name="preferred">首选</param>
        /// <param name="forcePreference">是否强制偏好</param>
        /// <returns></returns>
        public GrabTypes GetBestGrabbingType(GrabTypes preferred, bool forcePreference = false)
        {
            if (noSteamVRFallbackCamera)
            {
                if (Input.GetMouseButton(0))
                    return preferred;
            }

            if (preferred == GrabTypes.Pinch)
            {
                if (grabPinchAction.GetState(handType))
                    return GrabTypes.Pinch;
                else if (forcePreference)
                    return GrabTypes.None;
            }
            if (preferred == GrabTypes.Grip)
            {
                if (grabGripAction.GetState(handType))
                    return GrabTypes.Grip;
                else if (forcePreference)
                    return GrabTypes.None;
            }

            if (grabPinchAction.GetState(handType))
                return GrabTypes.Pinch;
            if (grabGripAction.GetState(handType))
                return GrabTypes.Grip;

            return GrabTypes.None;
        }


        //-------------------------------------------------
        /// <summary>
        /// 初始化控制器
        /// </summary>
        private void InitController()
        {
            if (spewDebugText)
                HandDebugLog("Hand " + name + " connected with type " + handType.ToString());

            bool hadOldRendermodel = mainRenderModel != null;
            EVRSkeletalMotionRange oldRM_rom = EVRSkeletalMotionRange.WithController;
            if(hadOldRendermodel)
                oldRM_rom = mainRenderModel.GetSkeletonRangeOfMotion;


            foreach (RenderModel r in renderModels)
            {
                if (r != null)
                    Destroy(r.gameObject);
            }

            renderModels.Clear();

            GameObject renderModelInstance = GameObject.Instantiate(renderModelPrefab);
            renderModelInstance.layer = gameObject.layer;
            renderModelInstance.tag = gameObject.tag;
            renderModelInstance.transform.parent = this.transform;
            renderModelInstance.transform.localPosition = Vector3.zero;
            renderModelInstance.transform.localRotation = Quaternion.identity;
            renderModelInstance.transform.localScale = renderModelPrefab.transform.localScale;

            //TriggerHapticPulse(800);  //pulse on controller init

            int deviceIndex = trackedObject.GetDeviceIndex();

            mainRenderModel = renderModelInstance.GetComponent<RenderModel>();
            renderModels.Add(mainRenderModel);

            if (hadOldRendermodel)
                mainRenderModel.SetSkeletonRangeOfMotion(oldRM_rom);

            this.BroadcastMessage("SetInputSource", handType, SendMessageOptions.DontRequireReceiver); // let child objects know we've initialized
            this.BroadcastMessage("OnHandInitialized", deviceIndex, SendMessageOptions.DontRequireReceiver); // let child objects know we've initialized
        }

        /// <summary>
        /// 设置模型
        /// </summary>
        /// <param name="prefab"></param>
        public void SetRenderModel(GameObject prefab)
        {
            renderModelPrefab = prefab;

            if (mainRenderModel != null && isPoseValid)
                InitController();
        }

        /// <summary>
        /// 设置悬浮模型
        /// </summary>
        /// <param name="hoverRenderModel"></param>
        public void SetHoverRenderModel(RenderModel hoverRenderModel)
        {
            hoverhighlightRenderModel = hoverRenderModel;
            renderModels.Add(hoverRenderModel);
        }

        /// <summary>
        /// 得到设备索引
        /// </summary>
        /// <returns></returns>
        public int GetDeviceIndex()
        {
            return trackedObject.GetDeviceIndex();
        }
    }


    [System.Serializable]
    public class HandEvent : UnityEvent<Hand> { }


#if UNITY_EDITOR
    //-------------------------------------------------------------------------
    [UnityEditor.CustomEditor(typeof(Hand))]
    public class HandEditor : UnityEditor.Editor
    {
        //-------------------------------------------------
        // Custom Inspector GUI allows us to click from within the UI
        //-------------------------------------------------
        public override void OnInspectorGUI()
        {
            DrawDefaultInspector();

            /*
            Hand hand = (Hand)target;

            if (hand.otherHand)
            {
                if (hand.otherHand.otherHand != hand)
                {
                    UnityEditor.EditorGUILayout.HelpBox("The otherHand of this Hand's otherHand is not this Hand.", UnityEditor.MessageType.Warning);
                }

                if (hand.handType == SteamVR_Input_Sources.LeftHand && hand.otherHand && hand.otherHand.handType != SteamVR_Input_Sources.RightHand)
                {
                    UnityEditor.EditorGUILayout.HelpBox("This is a left Hand but otherHand is not a right Hand.", UnityEditor.MessageType.Warning);
                }

                if (hand.handType == SteamVR_Input_Sources.RightHand && hand.otherHand && hand.otherHand.handType != SteamVR_Input_Sources.LeftHand)
                {
                    UnityEditor.EditorGUILayout.HelpBox("This is a right Hand but otherHand is not a left Hand.", UnityEditor.MessageType.Warning);
                }

                if (hand.handType == SteamVR_Input_Sources.Any && hand.otherHand && hand.otherHand.handType != SteamVR_Input_Sources.Any)
                {
                    UnityEditor.EditorGUILayout.HelpBox("This is an any-handed Hand but otherHand is not an any-handed Hand.", UnityEditor.MessageType.Warning);
                }
            }
            */ //removing for now because it conflicts with other input sources (trackers and such)
        }       //暂时删除,因为它与其他输入源(跟踪程序等)冲突
    }
#endif
}

Thorw.cs

//======= Copyright (c) Valve Corporation, All rights reserved. ===============
//
// Purpose: Basic throwable object
//
//=============================================================================

using UnityEngine;
using UnityEngine.Events;
using System.Collections;
using UnityEditor;

namespace Valve.VR.InteractionSystem
{
	//-------------------------------------------------------------------------
	[RequireComponent( typeof( Interactable ) )]
	[RequireComponent( typeof( Rigidbody ) )]
    [RequireComponent( typeof(VelocityEstimator))]
	public class Throwable : MonoBehaviour
	{
        /// <summary>
        /// 附加标志
        /// </summary>
		[EnumFlags]
		[Tooltip("The flags used to attach this object to the hand. 用于将此对象附加到手的标志")]
		public Hand.AttachmentFlags attachmentFlags = Hand.AttachmentFlags.ParentToHand | Hand.AttachmentFlags.DetachFromOtherHand | Hand.AttachmentFlags.TurnOnKinematic;

        /// <summary>
        /// 附加偏移
        /// 局部点,在保持时起位置偏移和旋转偏移的作用
        /// </summary>
        [Tooltip("The local point which acts as a positional and rotational offset to use while held 局部点,在保持时起位置偏移和旋转偏移的作用")]
        public Transform attachmentOffset;

        /// <summary>
        /// 捕捉速度阈值
        /// </summary>
		[Tooltip("How fast must this object be moving to attach due to a trigger hold instead of a trigger press? (-1 to disable)由于触发器保持而不是触发器按下,该对象移动到附加时的速度必须有多快?(1禁用)")]
        public float catchingSpeedThreshold = -1;

        /// <summary>
        /// 释放类型
        /// </summary>
        public ReleaseStyle releaseVelocityStyle = ReleaseStyle.GetFromHand;

        /// <summary>
        /// 释放速度时间偏移
        /// </summary>
        [Tooltip("The time offset used when releasing the object with the RawFromHand option释放带有Raw From Hand选项的对象时使用的时间偏移量")]
        public float releaseVelocityTimeOffset = -0.011f;

        /// <summary>
        /// 缩放
        /// </summary>
        public float scaleReleaseVelocity = 1.1f;

        /// <summary>
        /// 是否恢复原来的父
        /// 当分离对象时,它应该返回到原来的父对象吗
        /// </summary>
		[Tooltip("When detaching the object, should it return to its original parent? 当分离对象时,它应该返回到原来的父对象吗")]
		public bool restoreOriginalParent = false;

        

		protected VelocityEstimator velocityEstimator;

        /// <summary>
        /// 是否附加
        /// </summary>
        protected bool attached = false;

        /// <summary>
        /// 附加时间
        /// </summary>
        protected float attachTime;

        /// <summary>
        /// 附加位置
        /// </summary>
        protected Vector3 attachPosition;

        /// <summary>
        /// 附加旋转
        /// </summary>
        protected Quaternion attachRotation;

        /// <summary>
        /// 附加变换
        /// </summary>
        protected Transform attachEaseInTransform;

        /// <summary>
        /// 当捡起
        /// </summary>
		public UnityEvent onPickUp;
        /// <summary>
        /// 当从手分离
        /// </summary>
        public UnityEvent onDetachFromHand;

        /// <summary>
        /// 在进行更新?
        /// </summary>
        public UnityEvent<Hand> onHeldUpdate;

        
        protected RigidbodyInterpolation hadInterpolation = RigidbodyInterpolation.None;

        /// <summary>
        /// 刚体
        /// </summary>
        protected new Rigidbody rigidbody;

        /// <summary>
        /// 交互
        /// </summary>
        [HideInInspector]
        public Interactable interactable;


        //-------------------------------------------------
        protected virtual void Awake()
		{
			velocityEstimator = GetComponent<VelocityEstimator>();
            interactable = GetComponent<Interactable>();



            rigidbody = GetComponent<Rigidbody>();
            //最大角速度
            rigidbody.maxAngularVelocity = 50.0f;


            if(attachmentOffset != null)
            {
                // remove?
                //interactable.handFollowTransform = attachmentOffset;
            }

		}


        //-------------------------------------------------
        /// <summary>
        /// 当手悬浮开始
        /// </summary>
        /// <param name="hand"></param>
        protected virtual void OnHandHoverBegin( Hand hand )
		{
			bool showHint = false;

            // "Catch" the throwable by holding down the interaction button instead of pressing it.
            // Only do this if the throwable is moving faster than the prescribed threshold speed,
            // and if it isn't attached to another hand
            if ( !attached && catchingSpeedThreshold != -1)
            {
                float catchingThreshold = catchingSpeedThreshold * SteamVR_Utils.GetLossyScale(Player.instance.trackingOriginTransform);

                GrabTypes bestGrabType = hand.GetBestGrabbingType();

                if ( bestGrabType != GrabTypes.None )
				{
					if (rigidbody.velocity.magnitude >= catchingThreshold)
					{
						hand.AttachObject( gameObject, bestGrabType, attachmentFlags );
						showHint = false;
					}
				}
			}

			if ( showHint )
			{
                hand.ShowGrabHint();
			}
		}


        //-------------------------------------------------
        /// <summary>
        /// 当手悬浮结束
        /// </summary>
        /// <param name="hand"></param>
        protected virtual void OnHandHoverEnd( Hand hand )
		{
            hand.HideGrabHint();
		}


        //-------------------------------------------------
        /// <summary>
        /// 当手悬浮更新
        /// </summary>
        /// <param name="hand"></param>
        protected virtual void HandHoverUpdate( Hand hand )
        {
            GrabTypes startingGrabType = hand.GetGrabStarting();
            
            if (startingGrabType != GrabTypes.None)
            {
				hand.AttachObject( gameObject, startingGrabType, attachmentFlags, attachmentOffset );
                hand.HideGrabHint();
            }
		}

        //-------------------------------------------------
        /// <summary>
        /// 当被添加到手
        /// </summary>
        /// <param name="hand"></param>
        protected virtual void OnAttachedToHand( Hand hand )
		{
            //Debug.Log("<b>[SteamVR Interaction]</b> Pickup: " + hand.GetGrabStarting().ToString());

            hadInterpolation = this.rigidbody.interpolation;

            attached = true;

			onPickUp.Invoke();

			hand.HoverLock( null );
            
            rigidbody.interpolation = RigidbodyInterpolation.None;
            
		    velocityEstimator.BeginEstimatingVelocity();

			attachTime = Time.time;
			attachPosition = transform.position;
			attachRotation = transform.rotation;

		}


        //-------------------------------------------------
        /// <summary>
        /// 当从手中分离
        /// </summary>
        /// <param name="hand"></param>
        protected virtual void OnDetachedFromHand(Hand hand)
        {
            attached = false;

            onDetachFromHand.Invoke();

            hand.HoverUnlock(null);
            
            rigidbody.interpolation = hadInterpolation;

            Vector3 velocity;
            Vector3 angularVelocity;

            GetReleaseVelocities(hand, out velocity, out angularVelocity);
	 
            rigidbody.velocity = velocity;
            rigidbody.angularVelocity = angularVelocity;
        }

        /// <summary>
        /// 得到释放速度
        /// </summary>
        /// <param name="hand">手</param>
        /// <param name="velocity">速度</param>
        /// <param name="angularVelocity">角速度</param>
        public virtual void GetReleaseVelocities(Hand hand, out Vector3 velocity, out Vector3 angularVelocity)
        {
            if (hand.noSteamVRFallbackCamera && releaseVelocityStyle != ReleaseStyle.NoChange)
                releaseVelocityStyle = ReleaseStyle.ShortEstimation; // only type that works with fallback hand is short estimation.

            switch (releaseVelocityStyle)
            {
                case ReleaseStyle.ShortEstimation:
                    velocityEstimator.FinishEstimatingVelocity();
                    velocity = velocityEstimator.GetVelocityEstimate();
                    angularVelocity = velocityEstimator.GetAngularVelocityEstimate();
                    break;
                case ReleaseStyle.AdvancedEstimation:
                    hand.GetEstimatedPeakVelocities(out velocity, out angularVelocity);
                    break;
                case ReleaseStyle.GetFromHand:
                    velocity = hand.GetTrackedObjectVelocity(releaseVelocityTimeOffset);
                    angularVelocity = hand.GetTrackedObjectAngularVelocity(releaseVelocityTimeOffset);
                    break;
                default:
                case ReleaseStyle.NoChange:
                    velocity = rigidbody.velocity;
                    angularVelocity = rigidbody.angularVelocity;
                    break;
            }

            if (releaseVelocityStyle != ReleaseStyle.NoChange)
                velocity *= scaleReleaseVelocity;
        }

        //-------------------------------------------------
        /// <summary>
        ///  当被添加到手每帧
        /// </summary>
        /// <param name="hand"></param>
        protected virtual void HandAttachedUpdate(Hand hand)
        {


            if (hand.IsGrabEnding(this.gameObject))
            {
                hand.DetachObject(gameObject, restoreOriginalParent);

                // Uncomment to detach ourselves late in the frame.
                // This is so that any vehicles the player is attached to
                // have a chance to finish updating themselves.
                // If we detach now, our position could be behind what it
                // will be at the end of the frame, and the object may appear
                // to teleport behind the hand when the player releases it.
                //StartCoroutine( LateDetach( hand ) );
                //取消注释,以便在稍后的帧中分离我们自己。
                //这就是玩家所依附的任何车辆
                //有机会完成自我更新。
                //如果我们现在脱离,我们的位置可能会落后于形势
                //将位于帧的末尾,对象可能会出现
                //当玩家松开手的时候,在手的后面传送。
                //StartCoroutine(LateDetach(hand));
            }

            if (onHeldUpdate != null)
                onHeldUpdate.Invoke(hand);
        }


        //-------------------------------------------------
        /// <summary>
        /// 延迟分离
        /// </summary>
        /// <param name="hand"></param>
        /// <returns></returns>
        protected virtual IEnumerator LateDetach( Hand hand )
		{
			yield return new WaitForEndOfFrame();

			hand.DetachObject( gameObject, restoreOriginalParent );
		}


        //-------------------------------------------------
        /// <summary>
        /// 当手焦点获取
        /// </summary>
        /// <param name="hand"></param>
        protected virtual void OnHandFocusAcquired( Hand hand )
		{
			gameObject.SetActive( true );
			velocityEstimator.BeginEstimatingVelocity();
		}


        //-------------------------------------------------
        /// <summary>
        /// 当手焦点丢失
        /// </summary>
        /// <param name="hand"></param>
        protected virtual void OnHandFocusLost( Hand hand )
		{
			gameObject.SetActive( false );
			velocityEstimator.FinishEstimatingVelocity();
		}
	}

    /// <summary>
    /// 释放类型
    /// </summary>
    public enum ReleaseStyle
    {
        /// <summary>
        /// 无变化
        /// </summary>
        NoChange,
        /// <summary>
        /// 得到来自手
        /// </summary>
        GetFromHand,
        ShortEstimation,
        AdvancedEstimation,
    }
}

Interable.cs

//======= Copyright (c) Valve Corporation, All rights reserved. ===============
//
// Purpose: This object will get hover events and can be attached to the hands
//目的:该对象将获得悬停事件,并可以附加到指针上
//=============================================================================

using UnityEngine;
using UnityEngine.Events;
using System.Collections;
using System.Collections.Generic;

namespace Valve.VR.InteractionSystem
{
    //-------------------------------------------------------------------------
    public class Interactable : MonoBehaviour
    {
        [Tooltip("Activates an action set on attach and deactivates on detach 在连接时激活一个动作集,在分离时禁用该动作集")]
        public SteamVR_ActionSet activateActionSetOnAttach;

        /// <summary>
        /// 是否隐藏手当被附加时
        /// </summary>
        [Tooltip("Hide the whole hand on attachment and show on detach")]
        public bool hideHandOnAttach = true;

        /// <summary>
        /// 将手的骨骼部分隐藏在附件中,并在分离时显示出来
        /// </summary>
        [Tooltip("Hide the skeleton part of the hand on attachment and show on detach 将手的骨骼部分隐藏在附件中,并在分离时显示出来")]
        public bool hideSkeletonOnAttach = false;

        /// <summary>
        /// 将手的控制器部分隐藏在附件中,并在分离时显示出来。
        /// </summary>
        [Tooltip("Hide the controller part of the hand on attachment and show on detach 将手的控制器部分隐藏在附件中,并在分离时显示出来。")]
        public bool hideControllerOnAttach = false;

        /// <summary>
        /// 要在拾取时触发的动画器中的整数。0没有
        /// </summary>
        [Tooltip("The integer in the animator to trigger on pickup. 0 for none 要在拾取时触发的动画器中的整数。0没有”)")]
        public int handAnimationOnPickup = 0;

        /// <summary>
        /// 在骨架上设定的运动范围。没有不改变的
        /// </summary>
        [Tooltip("The range of motion to set on the skeleton. None for no change.在骨架上设定的运动范围。没有不改变的")]
        public SkeletalMotionRangeChange setRangeOfMotionOnPickup = SkeletalMotionRangeChange.None;

      
        public delegate void OnAttachedToHandDelegate(Hand hand);
        public delegate void OnDetachedFromHandDelegate(Hand hand);

        /// <summary>
        /// 当被附加到手 
        /// </summary>
        public event OnAttachedToHandDelegate onAttachedToHand;
        /// <summary>
        /// 当从手中分离
        /// </summary>
        public event OnDetachedFromHandDelegate onDetachedFromHand;

        /// <summary>
        /// 指定是要对齐到指针的对象附着点,还是只对齐到原始指针
        /// </summary>
        [Tooltip("Specify whether you want to snap to the hand's object attachment point, or just the raw hand 指定是要对齐到指针的对象附着点,还是只对齐到原始指针")]
        public bool useHandObjectAttachmentPoint = true;

        /// <summary>
        /// 附加在
        /// </summary>
        public bool attachEaseIn = false;
        [HideInInspector]
        public AnimationCurve snapAttachEaseInCurve = AnimationCurve.EaseInOut(0.0f, 0.0f, 1.0f, 1.0f);
        public float snapAttachEaseInTime = 0.15f;

        /// <summary>
        /// 完成后,快速安装
        /// </summary>
        public bool snapAttachEaseInCompleted = false;

        //抓取时的骨架姿势。只能设置这个或handFollowTransform
        // [Tooltip("The skeleton pose to apply when grabbing. Can only set this or handFollowTransform.")]
        [HideInInspector]
        public SteamVR_Skeleton_Poser skeletonPoser;

        /// <summary>
        /// 呈现的手应该锁定并跟随对象吗
        /// </summary>
        [Tooltip("Should the rendered hand lock on to and follow the object")]
        public bool handFollowTransform= true;

        /// <summary>
        /// 设置是否要在悬停时突出显示此可交互项
        /// </summary>
        [Tooltip("Set whether or not you want this interactible to highlight when hovering over it 设置是否要在悬停时突出显示此可交互项")]
        public bool highlightOnHover = true;

        /// <summary>
        /// 高亮渲染
        /// </summary>
        protected MeshRenderer[] highlightRenderers;
        /// <summary>
        /// 当前渲染
        /// </summary>
        protected MeshRenderer[] existingRenderers;
        /// <summary>
        /// 高亮持有者
        /// </summary>
        protected GameObject highlightHolder;

        /// <summary>
        /// 蒙皮网格渲染
        /// </summary>
        protected SkinnedMeshRenderer[] highlightSkinnedRenderers;
        /// <summary>
        /// 当前蒙皮网格渲染
        /// </summary>
        protected SkinnedMeshRenderer[] existingSkinnedRenderers;
        protected static Material highlightMat;
        [Tooltip("An array of child gameObjects to not render a highlight for. Things like transparent parts, vfx, etc.一组子游戏对象,不呈现突出显示。比如透明的部分,特效等等。")]
        public GameObject[] hideHighlight;


        [System.NonSerialized]
        public Hand attachedToHand;

        [System.NonSerialized]
        public Hand hoveringHand;

        /// <summary>
        /// 是否销毁
        /// </summary>
        public bool isDestroying { get; protected set; }
        /// <summary>
        /// 是否悬浮
        /// </summary>
        public bool isHovering { get; protected set; }
        public bool wasHovering { get; protected set; }
        

        private void Awake()
        {
            skeletonPoser = GetComponent<SteamVR_Skeleton_Poser>();
        }

        protected virtual void Start()
        {
            highlightMat = (Material)Resources.Load("SteamVR_HoverHighlight", typeof(Material));

            if (highlightMat == null)
                Debug.LogError("<b>[SteamVR Interaction]</b> Hover Highlight Material is missing. Please create a material named 'SteamVR_HoverHighlight' and place it in a Resources folder");

            if (skeletonPoser != null)
            {
                if (useHandObjectAttachmentPoint)
                {
                    //Debug.LogWarning("<b>[SteamVR Interaction]</b> SkeletonPose and useHandObjectAttachmentPoint both set at the same time. Ignoring useHandObjectAttachmentPoint.");
                    useHandObjectAttachmentPoint = false;
                }
            }
        }
        protected virtual void OnDestroy()
        {
            isDestroying = true;

            if (attachedToHand != null)
            {
                attachedToHand.DetachObject(this.gameObject, false);
                attachedToHand.skeleton.BlendToSkeleton(0.1f);
            }

            if (highlightHolder != null)
                Destroy(highlightHolder);

        }


        protected virtual void OnDisable()
        {
            isDestroying = true;

            if (attachedToHand != null)
            {
                attachedToHand.ForceHoverUnlock();
            }

            if (highlightHolder != null)
                Destroy(highlightHolder);
        }
        /// <summary>
        /// 应该忽略高亮
        /// </summary>
        /// <param name="component"></param>
        /// <returns></returns>
        protected virtual bool ShouldIgnoreHighlight(Component component)
        {
            return ShouldIgnore(component.gameObject);
        }

        protected virtual bool ShouldIgnore(GameObject check)
        {
            for (int ignoreIndex = 0; ignoreIndex < hideHighlight.Length; ignoreIndex++)
            {
                if (check == hideHighlight[ignoreIndex])
                    return true;
            }

            return false;
        }

        /// <summary>
        /// 创建高亮
        /// </summary>
        protected virtual void CreateHighlightRenderers()
        {
            existingSkinnedRenderers = this.GetComponentsInChildren<SkinnedMeshRenderer>(true);
            highlightHolder = new GameObject("Highlighter -- 高亮");
            highlightSkinnedRenderers = new SkinnedMeshRenderer[existingSkinnedRenderers.Length];

            for (int skinnedIndex = 0; skinnedIndex < existingSkinnedRenderers.Length; skinnedIndex++)
            {
                SkinnedMeshRenderer existingSkinned = existingSkinnedRenderers[skinnedIndex];

                if (ShouldIgnoreHighlight(existingSkinned))
                    continue;

                GameObject newSkinnedHolder = new GameObject("SkinnedHolder");
                newSkinnedHolder.transform.parent = highlightHolder.transform;
                SkinnedMeshRenderer newSkinned = newSkinnedHolder.AddComponent<SkinnedMeshRenderer>();
                Material[] materials = new Material[existingSkinned.sharedMaterials.Length];
                for (int materialIndex = 0; materialIndex < materials.Length; materialIndex++)
                {
                    materials[materialIndex] = highlightMat;
                }

                newSkinned.sharedMaterials = materials;
                newSkinned.sharedMesh = existingSkinned.sharedMesh;
                newSkinned.rootBone = existingSkinned.rootBone;
                newSkinned.updateWhenOffscreen = existingSkinned.updateWhenOffscreen;
                newSkinned.bones = existingSkinned.bones;

                highlightSkinnedRenderers[skinnedIndex] = newSkinned;
            }

            MeshFilter[] existingFilters = this.GetComponentsInChildren<MeshFilter>(true);
            existingRenderers = new MeshRenderer[existingFilters.Length];
            highlightRenderers = new MeshRenderer[existingFilters.Length];

            for (int filterIndex = 0; filterIndex < existingFilters.Length; filterIndex++)
            {
                MeshFilter existingFilter = existingFilters[filterIndex];
                MeshRenderer existingRenderer = existingFilter.GetComponent<MeshRenderer>();

                if (existingFilter == null || existingRenderer == null || ShouldIgnoreHighlight(existingFilter))
                    continue;

                GameObject newFilterHolder = new GameObject("FilterHolder");
                newFilterHolder.transform.parent = highlightHolder.transform;
                MeshFilter newFilter = newFilterHolder.AddComponent<MeshFilter>();
                newFilter.sharedMesh = existingFilter.sharedMesh;
                MeshRenderer newRenderer = newFilterHolder.AddComponent<MeshRenderer>();

                Material[] materials = new Material[existingRenderer.sharedMaterials.Length];
                for (int materialIndex = 0; materialIndex < materials.Length; materialIndex++)
                {
                    materials[materialIndex] = highlightMat;
                }
                newRenderer.sharedMaterials = materials;

                highlightRenderers[filterIndex] = newRenderer;
                existingRenderers[filterIndex] = existingRenderer;
            }
        }

        /// <summary>
        /// 更新高亮渲染
        /// </summary>
        protected virtual void UpdateHighlightRenderers()
        {
            if (highlightHolder == null)
                return;

            for (int skinnedIndex = 0; skinnedIndex < existingSkinnedRenderers.Length; skinnedIndex++)
            {
                SkinnedMeshRenderer existingSkinned = existingSkinnedRenderers[skinnedIndex];
                SkinnedMeshRenderer highlightSkinned = highlightSkinnedRenderers[skinnedIndex];

                if (existingSkinned != null && highlightSkinned != null && attachedToHand == false)
                {
                    highlightSkinned.transform.position = existingSkinned.transform.position;
                    highlightSkinned.transform.rotation = existingSkinned.transform.rotation;
                    highlightSkinned.transform.localScale = existingSkinned.transform.lossyScale;
                    highlightSkinned.localBounds = existingSkinned.localBounds;
                    highlightSkinned.enabled = isHovering && existingSkinned.enabled && existingSkinned.gameObject.activeInHierarchy;

                    int blendShapeCount = existingSkinned.sharedMesh.blendShapeCount;
                    for (int blendShapeIndex = 0; blendShapeIndex < blendShapeCount; blendShapeIndex++)
                    {
                        highlightSkinned.SetBlendShapeWeight(blendShapeIndex, existingSkinned.GetBlendShapeWeight(blendShapeIndex));
                    }
                }
                else if (highlightSkinned != null)
                    highlightSkinned.enabled = false;

            }

            for (int rendererIndex = 0; rendererIndex < highlightRenderers.Length; rendererIndex++)
            {
                MeshRenderer existingRenderer = existingRenderers[rendererIndex];
                MeshRenderer highlightRenderer = highlightRenderers[rendererIndex];

                if (existingRenderer != null && highlightRenderer != null && attachedToHand == false)
                {
                    highlightRenderer.transform.position = existingRenderer.transform.position;
                    highlightRenderer.transform.rotation = existingRenderer.transform.rotation;
                    highlightRenderer.transform.localScale = existingRenderer.transform.lossyScale;
                    highlightRenderer.enabled = isHovering && existingRenderer.enabled && existingRenderer.gameObject.activeInHierarchy;
                }
                else if (highlightRenderer != null)
                    highlightRenderer.enabled = false;
            }
        }

        /// <summary>
        /// 当一只手开始悬停在该对象上时调用
        /// Called when a Hand starts hovering over this object
        /// </summary>
        protected virtual void OnHandHoverBegin(Hand hand)
        {
            wasHovering = isHovering;
            isHovering = true;

            hoveringHand = hand;

            if (highlightOnHover == true)
            {
                CreateHighlightRenderers();
                UpdateHighlightRenderers();
            }
        }


        /// <summary>
        /// 当一只手停止悬停在该对象上时调用
        /// Called when a Hand stops hovering over this object
        /// </summary>
        private void OnHandHoverEnd(Hand hand)
        {
            wasHovering = isHovering;
            isHovering = false;

            if (highlightOnHover && highlightHolder != null)
                Destroy(highlightHolder);
        }

        protected virtual void Update()
        {
            if (highlightOnHover)
            {
                UpdateHighlightRenderers();

                if (isHovering == false && highlightHolder != null)
                    Destroy(highlightHolder);
            }
        }
        

        protected float blendToPoseTime = 0.1f;
        protected float releasePoseBlendTime = 0.2f;

        /// <summary>
        /// 当被附加到手
        /// </summary>
        /// <param name="hand"></param>
        protected virtual void OnAttachedToHand(Hand hand)
        {
            if (activateActionSetOnAttach != null)
                activateActionSetOnAttach.Activate(hand.handType);

            if (onAttachedToHand != null)
            {
                onAttachedToHand.Invoke(hand);
            }

            if (skeletonPoser != null && hand.skeleton != null)
            {
                hand.skeleton.BlendToPoser(skeletonPoser, blendToPoseTime);
            }

            attachedToHand = hand;
        }

        /// <summary>
        /// 当从手分离
        /// </summary>
        /// <param name="hand"></param>
        protected virtual void OnDetachedFromHand(Hand hand)
        {
            if (activateActionSetOnAttach != null)
            {
                if (hand.otherHand == null || hand.otherHand.currentAttachedObjectInfo.HasValue == false ||
                    (hand.otherHand.currentAttachedObjectInfo.Value.interactable != null &&
                     hand.otherHand.currentAttachedObjectInfo.Value.interactable.activateActionSetOnAttach != this.activateActionSetOnAttach))
                {
                    activateActionSetOnAttach.Deactivate(hand.handType);
                }
            }

            if (onDetachedFromHand != null)
            {
                onDetachedFromHand.Invoke(hand);
            }


            if (skeletonPoser != null)
            {
                if (hand.skeleton != null)
                    hand.skeleton.BlendToSkeleton(releasePoseBlendTime);
            }

            attachedToHand = null;
        }

      
    }
}

InterableHoverEvents.cs

跟我之前写的检测 事件脚本同理。也是使用sendmessage,由Hand.cs发送,这些脚本接收

不过可以绑定,这个脚本使用了UnityEvent,虽然我还不明白

//======= Copyright (c) Valve Corporation, All rights reserved. ===============
//
// Purpose: Sends UnityEvents for basic hand interactions
//
//=============================================================================

using UnityEngine;
using UnityEngine.Events;
using System.Collections;

namespace Valve.VR.InteractionSystem
{
	//-------------------------------------------------------------------------
	[RequireComponent( typeof( Interactable ) )]
	public class InteractableHoverEvents : MonoBehaviour
	{
		public UnityEvent onHandHoverBegin;
		public UnityEvent onHandHoverEnd;
		public UnityEvent onAttachedToHand;
		public UnityEvent onDetachedFromHand;

		//-------------------------------------------------
		private void OnHandHoverBegin()
		{
			onHandHoverBegin.Invoke();
		}


		//-------------------------------------------------
		private void OnHandHoverEnd()
		{
			onHandHoverEnd.Invoke();
		}


		//-------------------------------------------------
		private void OnAttachedToHand( Hand hand )
		{
			onAttachedToHand.Invoke();
		}


		//-------------------------------------------------
		private void OnDetachedFromHand( Hand hand )
		{
			onDetachedFromHand.Invoke();
		}
	}
}
发布了57 篇原创文章 · 获赞 37 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qq_35030499/article/details/94145146