尝试在眼镜碰撞事件时将游戏区域的位置倒回到最后一个已知的有效位置
将VRTK_PositionRewind_UnityEvents组件添加到VRTK_PositionRewind对象允许访问UnityEvents它将对类事件做出相同的反应。所有C#委托事件都映射到带有On前缀的Unity事件。例如MyEvent- > OnMyEvent。
必备组建
VRTK_BodyPhysics - Body Physics脚本,用于管理场景中身体存在的碰撞。
VRTK_HeadsetCollision - 耳机碰撞脚本,用于确定耳机何时与有效几何体发生碰撞。
结构体 PositionRewindEventArgs
Vector3 collidedPosition 碰撞体位置
Vector3 resetPosition 恢复位置
委托
public delegate void PositionRewindEventHandler(object sender, PositionRewindEventArgs e);
参数:当前object 上述结构体
枚举
CollisionDetectors 有效的碰撞检测器
有三种情况 HeadsetOnly 仅侦听耳机对撞机上的碰撞
BodyOnly 仅侦听身体物理对撞机上的碰撞。
HeadsetAndBody 侦听取耳机对撞机和身体物理对撞机上的碰撞。
字段介绍
见代码注释
事件
public event PositionRewindEventHandler PositionRewindToSafe;
方法
OnPositionRewindToSafe(PositionRewindEventArgs e) 执行事件的方法
SetLastGoodPosition() 存储当前有效的播放区域和耳机位置
RewindPosition() 将播放区域位置重置为播放区域的最后已知良好位置。
私有方法
见代码注释
// Position Rewind|Presence|70070
namespace VRTK
{
using UnityEngine;
/// <summary>
/// Event Payload
/// </summary>
/// <param name="collidedPosition">The position of the play area when it collded.</param>
/// <param name="resetPosition">The position of the play area when it has been rewinded to a safe position.</param>
public struct PositionRewindEventArgs
{
public Vector3 collidedPosition;//碰撞位置
public Vector3 resetPosition;//安全位置
}
/// <summary>
/// Event Payload
/// </summary>
/// <param name="sender">this object</param>
/// <param name="e"><see cref="PositionRewindEventArgs"/></param>
public delegate void PositionRewindEventHandler(object sender, PositionRewindEventArgs e);
/// <summary>
/// Attempts to rewind the position of the play area to a last know valid position upon the headset collision event.
/// </summary>
/// <remarks>
/// **Required Components:**
/// * `VRTK_BodyPhysics` - A Body Physics script to manage the collisions of the body presence within the scene.
/// * `VRTK_HeadsetCollision` - A Headset Collision script to determine when the headset is colliding with valid geometry.
///
/// **Script Usage:**
/// * Place the `VRTK_PositionRewind` script on any active scene GameObject.
/// </remarks>
/// <example>
/// `VRTK/Examples/017_CameraRig_TouchpadWalking` has the position rewind script to reset the user's position if they walk into objects.
/// </example>
[AddComponentMenu("VRTK/Scripts/Presence/VRTK_PositionRewind")]
public class VRTK_PositionRewind : MonoBehaviour
{
/// <summary>
/// Valid collision detectors.
/// </summary>
public enum CollisionDetectors
{
/// <summary>
/// Listen for collisions on the headset collider only.
/// </summary>
HeadsetOnly,
/// <summary>
/// Listen for collisions on the body physics collider only.
/// </summary>
BodyOnly,
/// <summary>
/// Listen for collisions on both the headset collider and body physics collider.
/// </summary>
HeadsetAndBody
}
[Header("Rewind Settings")]
[Tooltip("The colliders to determine if a collision has occured for the rewind to be actioned.")]
public CollisionDetectors collisionDetector = CollisionDetectors.HeadsetOnly;//上述的枚举
[Tooltip("If this is checked then the collision detector will ignore colliders set to `Is Trigger = true`.")]
public bool ignoreTriggerColliders = false;//是否忽略Is Trigger = true
[Tooltip("The amount of time from original headset collision until the rewind to the last good known position takes place.")]
public float rewindDelay = 0.5f;//时间 从原始耳机碰撞到倒回到最后一个已知位置的时间量
[Tooltip("The additional distance to push the play area back upon rewind to prevent being right next to the wall again.")]
public float pushbackDistance = 0.5f;//距离 在倒带时将游戏区域推回的额外距离,以防止再次靠近墙壁。
[Tooltip("The threshold to determine how low the headset has to be before it is considered the user is crouching. The last good position will only be recorded in a non-crouching position.")]
public float crouchThreshold = 0.5f;确定耳机在被认为是蹲伏之前必须有多低的阈值。最后一个好位置只会记录在非蹲伏位置。
[Tooltip("The threshold to determind how low the headset can be to perform a position rewind. If the headset Y position is lower than this threshold then a rewind won't occur.")]
public float crouchRewindThreshold = 0.1f;确定耳机执行位置倒带的程度的阈值。如果耳机Y位置低于此阈值,则不会发生倒带。
[Tooltip("A specified VRTK_PolicyList to use to determine whether any objects will be acted upon by the Position Rewind.")]
public VRTK_PolicyList targetListPolicy;//目标列表策略 用于确定位置回滚是否将对任何对象执行操作。
[Header("Custom Settings")]
[Tooltip("The VRTK Body Physics script to use for the collisions and rigidbodies. If this is left blank then the first Body Physics script found in the scene will be used.")]
public VRTK_BodyPhysics bodyPhysics;//用于碰撞和刚体的VRTK身体物理学脚本。如果将其留空,则将使用场景中找到的第一个Body Physics脚本。
[Tooltip("The VRTK Headset Collision script to use to determine if the headset is colliding. If this is left blank then the script will need to be applied to the same GameObject.")]
public VRTK_HeadsetCollision headsetCollision;//用于确定耳机是否发生碰撞的VRTK耳机碰撞脚本。如果将其留空,则需要将脚本应用于同一GameObject。
//事件
public event PositionRewindEventHandler PositionRewindToSafe;
//私有字段
protected Transform headset;
protected Transform playArea;
protected Vector3 lastGoodStandingPosition;
protected Vector3 lastGoodHeadsetPosition;
protected float highestHeadsetY;
protected float lastPlayAreaY;
protected bool lastGoodPositionSet = false;
protected bool hasCollided = false;
protected bool isColliding = false;
protected bool isRewinding = false;
protected float collideTimer = 0f;
public virtual void OnPositionRewindToSafe(PositionRewindEventArgs e)
{
if (PositionRewindToSafe != null)
{
PositionRewindToSafe(this, e);
}
}
/// <summary>
/// The SetLastGoodPosition method stores the current valid play area and headset position.
/// </summary>
public virtual void SetLastGoodPosition()
{
if (playArea != null && headset != null)
{
lastGoodPositionSet = true;
lastGoodStandingPosition = playArea.position;
lastGoodHeadsetPosition = headset.position;
}
}
/// <summary>
/// The RewindPosition method resets the play area position to the last known good position of the play area.
/// </summary>
public virtual void RewindPosition()
{
if (headset != null)
{
Vector3 storedPosition = playArea.position;
Vector3 resetVector = lastGoodHeadsetPosition - headset.position;
Vector3 moveOffset = resetVector.normalized * pushbackDistance;
playArea.position += resetVector + moveOffset;
if (bodyPhysics != null)
{
bodyPhysics.ResetVelocities();
}
OnPositionRewindToSafe(SetEventPayload(storedPosition));
}
}
protected virtual void Awake()//Add
{
VRTK_SDKManager.AttemptAddBehaviourToToggleOnLoadedSetupChange(this);
}
protected virtual void OnEnable()
{
lastGoodPositionSet = false;//
headset = VRTK_DeviceFinder.HeadsetTransform();
playArea = VRTK_DeviceFinder.PlayAreaTransform();//获取
bodyPhysics = (bodyPhysics != null ? bodyPhysics : FindObjectOfType<VRTK_BodyPhysics>());//获取BodyPhysics
headsetCollision = (headsetCollision != null ? headsetCollision : GetComponentInChildren<VRTK_HeadsetCollision>());//获取VRTK_HeadsetCollision
ManageListeners(true);
}
protected virtual void OnDisable()
{
ManageListeners(false);
}
protected virtual void OnDestroy()//Remove
{
VRTK_SDKManager.AttemptRemoveBehaviourToToggleOnLoadedSetupChange(this);
}
protected virtual void Update()
{
if (isColliding)
{
if (collideTimer > 0f)
{
collideTimer -= Time.deltaTime;
}
else
{
collideTimer = 0f;
isColliding = false;
DoPositionRewind();
}
}
}
protected virtual PositionRewindEventArgs SetEventPayload(Vector3 previousPosition)
{
PositionRewindEventArgs e;
e.collidedPosition = previousPosition;
e.resetPosition = playArea.position;
return e;
}
protected virtual bool CrouchThresholdReached()
{
float floorVariant = 0.005f;
return (playArea.position.y > (lastPlayAreaY + floorVariant) || playArea.position.y < (lastPlayAreaY - floorVariant));
}
protected virtual void SetHighestHeadsetY()
{
highestHeadsetY = (CrouchThresholdReached() ? crouchThreshold : (headset.localPosition.y > highestHeadsetY) ? headset.localPosition.y : highestHeadsetY);
}
protected virtual void UpdateLastGoodPosition()
{
float highestYDiff = highestHeadsetY - crouchThreshold;
if (headset.localPosition.y > highestYDiff && highestYDiff > crouchThreshold)
{
SetLastGoodPosition();
}
lastPlayAreaY = playArea.position.y;
}
protected virtual void FixedUpdate()
{
if (!isColliding && playArea != null)
{
SetHighestHeadsetY();
UpdateLastGoodPosition();
}
}
//
protected virtual void StartCollision(GameObject target, Collider collider)
{
if (ignoreTriggerColliders && collider.isTrigger)
{
return;
}
if (!VRTK_PolicyList.Check(target, targetListPolicy))
{
isColliding = true;
if (!hasCollided && collideTimer <= 0f)
{
hasCollided = true;
collideTimer = rewindDelay;
}
}
}
protected virtual void EndCollision(Collider collider)
{
if (ignoreTriggerColliders && collider != null && collider.isTrigger)
{
return;
}
isColliding = false;
hasCollided = false;
isRewinding = false;
}
protected virtual bool BodyCollisionsEnabled()
{
return (bodyPhysics == null || bodyPhysics.enableBodyCollisions);
}
protected virtual bool CanRewind()
{
return (!isRewinding && playArea != null & lastGoodPositionSet && headset.localPosition.y > crouchRewindThreshold && BodyCollisionsEnabled());
}
protected virtual void DoPositionRewind()
{
if (CanRewind())
{
isRewinding = true;
RewindPosition();
}
}
protected virtual bool HeadsetListen()
{
return (collisionDetector == CollisionDetectors.HeadsetAndBody || collisionDetector == CollisionDetectors.HeadsetOnly);
}
protected virtual bool BodyListen()
{
return (collisionDetector == CollisionDetectors.HeadsetAndBody || collisionDetector == CollisionDetectors.BodyOnly);
}
protected virtual void ManageListeners(bool state)
{
if (state)
{
if (headsetCollision != null && HeadsetListen())
{
//注册headsetCollision的事件
headsetCollision.HeadsetCollisionDetect += HeadsetCollisionDetect;
headsetCollision.HeadsetCollisionEnded += HeadsetCollisionEnded;
}
if (bodyPhysics != null && BodyListen())
{
//注册bodyPhysics的事件
bodyPhysics.StartColliding += StartColliding;
bodyPhysics.StopColliding += StopColliding;
}
}
else
{
if (headsetCollision != null && HeadsetListen())
{
//移除注册
headsetCollision.HeadsetCollisionDetect -= HeadsetCollisionDetect;
headsetCollision.HeadsetCollisionEnded -= HeadsetCollisionEnded;
}
if (bodyPhysics != null && BodyListen())
{
bodyPhysics.StartColliding -= StartColliding;
bodyPhysics.StopColliding -= StopColliding;
}
}
}
private void StartColliding(object sender, BodyPhysicsEventArgs e)
{
StartCollision(e.target, e.collider);
}
private void StopColliding(object sender, BodyPhysicsEventArgs e)
{
EndCollision(e.collider);
}
//
protected virtual void HeadsetCollisionDetect(object sender, HeadsetCollisionEventArgs e)
{
StartCollision(e.collider.gameObject, e.collider);
}
//
protected virtual void HeadsetCollisionEnded(object sender, HeadsetCollisionEventArgs e)
{
EndCollision(e.collider);
}
}
}