提供跟蹤指針光標位置的播放區(qū)域邊界的可視化表示;
// Play Area Cursor|Pointers|10050
namespace VRTK
{
using UnityEngine;
/// <summary>
/// Event Payload
/// </summary>
/// <param name="collidedWith">The collider that is/was being collided with.</param>
public struct PlayAreaCursorEventArgs
{
public Collider collider;//
}
/// <summary>
/// Event Payload
/// </summary>
/// <param name="sender">this object</param>
/// <param name="e"><see cref="PlayAreaCursorEventArgs"/></param>
public delegate void PlayAreaCursorEventHandler(object sender, PlayAreaCursorEventArgs e);
/// <summary>
/// Provides a visual representation of the play area boundaries that tracks to the cursor position of a pointer.
/// </summary>
/// <remarks>
/// **Optional Components:**
/// * `VRTK_PointerDirectionIndicator` - A Pointer Direction Indicator to set the cursor rotation to.
///
/// **Script Usage:**
/// * Place the `VRTK_PlayAreaCursor` script on the same GameObject as the Pointer Renderer script it is linked to.
/// * Link the required Play Area Cursor script to the `Playarea Cursor` parameter on the required Pointer Renderer script.
///
/// **Script Dependencies:**
/// * A Base Pointer Renderer script attached to a valid Pointer script is required so the PlayArea Cursor script can be linked to follow the valid Base Pointer Renderer cursor GameObject.
/// </remarks>
/// <example>
/// `VRTK/Examples/012_Controller_PointerWithAreaCollision` shows how a Bezier Pointer with the Play Area Cursor and Collision Detection enabled can be used to traverse a game area but not allow teleporting into areas where the walls or other objects would fall into the play area space enabling the user to enter walls.
/// </example>
[AddComponentMenu("VRTK/Scripts/Pointers/VRTK_PlayAreaCursor")]
public class VRTK_PlayAreaCursor : MonoBehaviour
{
[Header("Appearance Settings")]
[Tooltip("If this is checked then the pointer valid/invalid colours will also be used to change the colour of the play area cursor when colliding/not colliding.")]
public bool usePointerColor = true;//使用指針顏色:如果選中此項嘹叫,則指針有效/無效顏色也將用于在碰撞/不碰撞時更改游戲區(qū)域光標的顏色
[Tooltip("Determines the size of the play area cursor and collider. If the values are left as zero then the Play Area Cursor will be sized to the calibrated Play Area space.")]
public Vector2 playAreaCursorDimensions = Vector2.zero;//播放區(qū)域光標尺寸:確定播放區(qū)域光標和對撞機的大小筒占。如果值保留為零,則播放區(qū)域光標將調(diào)整為校準的播放區(qū)域空間谦絮。
[Tooltip("If this is checked then if the play area cursor is colliding with any other object then the pointer colour will change to the `Pointer Miss Color` and the `DestinationMarkerSet` event will not be triggered, which will prevent teleporting into areas where the play area will collide.")]
public bool handlePlayAreaCursorCollisions = false;//處理游戲區(qū)域光標碰撞:如果選中此項,則如果游戲區(qū)域光標與任何其他對象發(fā)生碰撞拷淘,則指針顏色將變?yōu)椴叻停琍ointer Miss Color并且DestinationMarkerSet不會觸發(fā)事件逛裤,這將阻止傳送到游戲區(qū)域?qū)l(fā)生碰撞的區(qū)域。
[Tooltip("If this is checked then if the user's headset is outside of the play area cursor bounds then it is considered a collision even if the play area isn't colliding with anything.")]
public bool headsetOutOfBoundsIsCollision = false;//耳機超出界限是碰撞:如果選中此項猴抹,則如果用戶的耳機位于游戲區(qū)域光標范圍之外带族,則即使游戲區(qū)域未與任何物體發(fā)生碰撞,也會將其視為碰撞蟀给。
[Tooltip("If this is checked then the play area cursor will be displayed when the location is invalid.")]
public bool displayOnInvalidLocation = true;//顯示無效位置:如果選中此選項蝙砌,則當位置無效時將顯示播放區(qū)域光標
[Tooltip("A specified VRTK_PolicyList to use to determine whether the play area cursor collisions will be acted upon.")]
public VRTK_PolicyList targetListPolicy;
[Header("Custom Settings")]
[Tooltip("A custom Pointer Direction Indicator to use to determine the rotation of the Play Area Cursor.")]
public VRTK_PointerDirectionIndicator directionIndicator;//方向指示器:用于確定游戲區(qū)域光標旋轉(zhuǎn)的自定義指針方向指示器。
[Tooltip("A custom GameObject to use for the play area cursor representation for when the location is valid.")]
public GameObject validLocationObject;//有效位置對象:用于位置有效時的游戲區(qū)域光標表示的自定義GameObject跋理。
[Tooltip("A custom GameObject to use for the play area cursor representation for when the location is invalid.")]
public GameObject invalidLocationObject;//無效的位置對象:用于播放區(qū)域光標表示的自定義GameObject择克,用于位置無效。
/// <summary>
/// Emitted when the play area collides with another object.
/// </summary>
public event PlayAreaCursorEventHandler PlayAreaCursorStartCollision;// 當游戲區(qū)域與另一個對象發(fā)生碰撞時發(fā)出前普。
/// <summary>
/// Emitted when the play area stops colliding with another object.
/// </summary>
public event PlayAreaCursorEventHandler PlayAreaCursorEndCollision;//當游戲區(qū)域停止與另一個物體碰撞時發(fā)出肚邢。
protected bool headsetPositionCompensation;
protected bool playAreaCursorCollided = false;
protected bool headsetOutOfBounds = false;
protected Transform playArea;
protected GameObject playAreaCursor;
protected GameObject[] playAreaCursorBoundaries;
protected BoxCollider playAreaCursorCollider;
protected Transform headset;
protected Renderer[] boundaryRenderers = new Renderer[0];
protected GameObject playAreaCursorValidChild;
protected GameObject playAreaCursorInvalidChild;
protected int btmRightInner = 0;
protected int btmLeftInner = 1;
protected int topLeftInner = 2;
protected int topRightInner = 3;
protected int btmRightOuter = 4;
protected int btmLeftOuter = 5;
protected int topLeftOuter = 6;
protected int topRightOuter = 7;
public virtual void OnPlayAreaCursorStartCollision(PlayAreaCursorEventArgs e)
{
if (PlayAreaCursorStartCollision != null)
{
PlayAreaCursorStartCollision(this, e);
}
}
public virtual void OnPlayAreaCursorEndCollision(PlayAreaCursorEventArgs e)
{
if (PlayAreaCursorEndCollision != null)
{
PlayAreaCursorEndCollision(this, e);
}
}
/// <summary>
/// The HasCollided method returns the state of whether the play area cursor has currently collided with another valid object.
/// </summary>
/// <returns>Returns `true` if the play area is colliding with a valid object and `false` if not.</returns>
public virtual bool HasCollided()
{
return (playAreaCursorCollided || headsetOutOfBounds);//返回播放區(qū)域光標當前是否與另一個有效對象沖突的狀態(tài)
}
/// <summary>
/// The SetHeadsetPositionCompensation method determines whether the offset position of the headset from the centre of the play area should be taken into consideration when setting the destination marker. If `true` then it will take the offset position into consideration.
/// </summary>
/// <param name="state">The state of whether to take the position of the headset within the play area into account when setting the destination marker.</param>
public virtual void SetHeadsetPositionCompensation(bool state)
{
headsetPositionCompensation = state;//確定在設(shè)置目標標記時是否應(yīng)考慮耳機距游戲區(qū)域中心的偏移位置。如果true那時它將考慮偏移位置拭卿。
}
/// <summary>
/// 確定是否應(yīng)該使用播放區(qū)域光標考慮播放區(qū)域沖突
/// The SetPlayAreaCursorCollision method determines whether play area collisions should be taken into consideration with the play area cursor.
/// </summary>
/// <param name="state">The state of whether to check for play area collisions.</param>
/// <param name="collider">The state of whether to check for play area collisions.</param>
public virtual void SetPlayAreaCursorCollision(bool state, Collider collider = null)
{
playAreaCursorCollided = false;
if (handlePlayAreaCursorCollisions)
{
playAreaCursorCollided = (!enabled ? false : state);
EmitEvent(collider);
}
}
/// <summary>
/// 設(shè)置播放區(qū)域光標上的當前材質(zhì)顏色
/// The SetMaterialColor method sets the current material colour on the play area cursor.
/// </summary>
/// <param name="color">The colour to update the play area cursor material to.</param>
/// <param name="validity">Determines if the colour being set is based from a valid location or invalid location.</param>
public virtual void SetMaterialColor(Color color, bool validity)
{
if (validLocationObject == null)
{
ToggleVisibility(!(!validity && !displayOnInvalidLocation));
if (usePointerColor)
{
for (int i = 0; i < playAreaCursorBoundaries.Length; i++)
{
SetCursorColor(playAreaCursorBoundaries[i], color);
}
}
}
else
{
ToggleValidPlayAreaState(!playAreaCursorCollided);
if (usePointerColor)
{
SetCursorColor(playAreaCursor, color);
}
}
}
/// <summary>
/// 用于將世界空間中游戲區(qū)域光標的位置更新為給定位置
/// The SetPlayAreaCursorTransform method is used to update the position of the play area cursor in world space to the given location.
/// </summary>
/// <param name="location">The location where to draw the play area cursor.</param>
public virtual void SetPlayAreaCursorTransform(Vector3 location)
{
Vector3 offset = Vector3.zero;
if (headsetPositionCompensation)
{
Vector3 playAreaPos = new Vector3(playArea.transform.position.x, 0f, playArea.transform.position.z);
Vector3 headsetPos = new Vector3(headset.position.x, 0f, headset.position.z);
offset = playAreaPos - headsetPos;
}
if (playAreaCursor != null)
{
if (playAreaCursor.activeInHierarchy && handlePlayAreaCursorCollisions && headsetOutOfBoundsIsCollision)
{
Vector3 checkPoint = new Vector3(location.x, playAreaCursor.transform.position.y + (playAreaCursor.transform.localScale.y * 2), location.z);
if (!playAreaCursorCollider.bounds.Contains(checkPoint))
{
headsetOutOfBounds = true;
}
else
{
headsetOutOfBounds = false;
}
}
playAreaCursor.transform.rotation = (directionIndicator != null && directionIndicator.gameObject.activeInHierarchy ? directionIndicator.transform.rotation : playArea.rotation);
playAreaCursor.transform.position = location + offset;
}
}
/// <summary>
/// 啟用或禁用播放區(qū)域光標的可見性
/// The ToggleState method enables or disables the visibility of the play area cursor.
/// </summary>
/// <param name="state">The state of whether to show or hide the play area cursor.</param>
public virtual void ToggleState(bool state)
{
state = (!enabled ? false : state);
if (playAreaCursor != null)
{
playAreaCursor.SetActive(state);
}
}
/// <summary>
/// 返回游戲區(qū)域游標GameObject是否處于活動狀態(tài)
/// The IsActive method returns whether the play area cursor GameObject is active or not.
/// </summary>
/// <returns>Returns `true` if the play area cursor GameObject is active.</returns>
public virtual bool IsActive()
{
return (playAreaCursor != null ? playAreaCursor.activeInHierarchy : false);
}
/// <summary>
/// 返回創(chuàng)建的GameObject道偷,其中包含游戲區(qū)域光標表示
/// The GetPlayAreaContainer method returns the created GameObject that holds the play area cursor representation.
/// </summary>
/// <returns>The GameObject that is the container of the play area cursor.</returns>
public virtual GameObject GetPlayAreaContainer()
{
return playAreaCursor;
}
/// <summary>
/// 啟用或禁用播放區(qū)域光標渲染器以允許查看或隱藏光標
/// The ToggleVisibility method enables or disables the play area cursor renderers to allow the cursor to be seen or hidden.
/// </summary>
/// <param name="state">The state of the cursor visibility. True will show the renderers and false will hide the renderers.</param>
public virtual void ToggleVisibility(bool state)
{
if (playAreaCursor != null && boundaryRenderers.Length == 0)
{
boundaryRenderers = playAreaCursor.GetComponentsInChildren<Renderer>();
}
for (int i = 0; i < boundaryRenderers.Length; i++)
{
if (boundaryRenderers[i] != null)
{
boundaryRenderers[i].enabled = state;
}
}
}
protected virtual void Awake()
{
VRTK_SDKManager.AttemptAddBehaviourToToggleOnLoadedSetupChange(this);
}
protected virtual void OnEnable()
{
VRTK_PlayerObject.SetPlayerObject(gameObject, VRTK_PlayerObject.ObjectTypes.Pointer);
headset = VRTK_DeviceFinder.HeadsetTransform();
playArea = VRTK_DeviceFinder.PlayAreaTransform();
playAreaCursorBoundaries = new GameObject[4];
InitPlayAreaCursor();
}
protected virtual void OnDisable()
{
if (playAreaCursor != null)
{
Destroy(playAreaCursor);
}
}
protected virtual void OnDestroy()
{
VRTK_SDKManager.AttemptRemoveBehaviourToToggleOnLoadedSetupChange(this);
}
protected virtual void Update()
{
if (enabled && IsActive())
{
UpdateCollider();
}
}
protected virtual PlayAreaCursorEventArgs SetEventPayload(Collider collider)
{
PlayAreaCursorEventArgs e;
e.collider = collider;
return e;
}
protected virtual void EmitEvent(Collider collider)
{
if (collider != null)
{
if (playAreaCursorCollided)
{
OnPlayAreaCursorStartCollision(SetEventPayload(collider));
}
else
{
OnPlayAreaCursorEndCollision(SetEventPayload(collider));
}
}
}
protected virtual void InitPlayAreaCursor()
{
if (playArea == null)
{
VRTK_Logger.Error(VRTK_Logger.GetCommonMessage(VRTK_Logger.CommonMessageKeys.SDK_OBJECT_NOT_FOUND, "PlayArea", "Boundaries SDK"));
return;
}
Vector3[] cursorDrawVertices = VRTK_SDK_Bridge.GetPlayAreaVertices();
if (validLocationObject != null)
{
GeneratePlayAreaCursorFromPrefab(cursorDrawVertices);
}
else
{
if (cursorDrawVertices == null || cursorDrawVertices.Length < 8)
{
cursorDrawVertices = new Vector3[] {
new Vector3(0.8f, 0f, -0.8f),
new Vector3(-0.8f, 0f, -0.8f),
new Vector3(-0.8f, 0f, 0.8f),
new Vector3(0.8f, 0f, 0.8f),
new Vector3(1f, 0f, -1f),
new Vector3(-1f, 0f, -1f),
new Vector3(-1f, 0f, 1f),
new Vector3(1f, 0f, 1f) };
}
GeneratePlayAreaCursor(cursorDrawVertices);
}
if (playAreaCursor != null)
{
playAreaCursor.SetActive(false);
VRTK_PlayerObject.SetPlayerObject(playAreaCursor, VRTK_PlayerObject.ObjectTypes.Pointer);
CreateCursorCollider(playAreaCursor);
playAreaCursor.AddComponent<Rigidbody>().isKinematic = true;
VRTK_PlayAreaCollider playAreaCursorScript = playAreaCursor.AddComponent<VRTK_PlayAreaCollider>();
playAreaCursorScript.SetParent(this);
playAreaCursorScript.SetIgnoreTarget(targetListPolicy);
playAreaCursor.layer = LayerMask.NameToLayer("Ignore Raycast");
}
}
protected virtual void SetCursorColor(GameObject cursorObject, Color color)
{
Renderer playareaRenderer = cursorObject.GetComponentInChildren<Renderer>();
if (playareaRenderer != null && playareaRenderer.material && playareaRenderer.material.HasProperty("_Color"))
{
playareaRenderer.material.color = color;
playareaRenderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
playareaRenderer.receiveShadows = false;
}
}
protected virtual void ToggleValidPlayAreaState(bool state)
{
if (playAreaCursorValidChild != null)
{
playAreaCursorValidChild.SetActive(state);
}
if (playAreaCursorInvalidChild != null)
{
playAreaCursorInvalidChild.SetActive((displayOnInvalidLocation ? !state : false));
}
}
protected virtual string GeneratePlayAreaCursorName()
{
return VRTK_SharedMethods.GenerateVRTKObjectName(true, gameObject.name, "PlayAreaCursor");
}
protected virtual void GeneratePlayAreaCursorFromPrefab(Vector3[] cursorDrawVertices)
{
playAreaCursor = new GameObject(GeneratePlayAreaCursorName());
float width = cursorDrawVertices[btmRightOuter].x - cursorDrawVertices[topLeftOuter].x;
float length = cursorDrawVertices[topLeftOuter].z - cursorDrawVertices[btmRightOuter].z;
if (playAreaCursorDimensions != Vector2.zero)
{
width = (playAreaCursorDimensions.x == 0 ? playAreaCursor.transform.localScale.x : playAreaCursorDimensions.x);
length = (playAreaCursorDimensions.y == 0 ? playAreaCursor.transform.localScale.z : playAreaCursorDimensions.y);
}
float height = 0.01f;
playAreaCursorValidChild = Instantiate(validLocationObject);
playAreaCursorValidChild.name = VRTK_SharedMethods.GenerateVRTKObjectName(true, "ValidArea");
playAreaCursorValidChild.transform.SetParent(playAreaCursor.transform);
if (invalidLocationObject != null)
{
playAreaCursorInvalidChild = Instantiate(invalidLocationObject);
playAreaCursorInvalidChild.name = VRTK_SharedMethods.GenerateVRTKObjectName(true, "InvalidArea");
playAreaCursorInvalidChild.transform.SetParent(playAreaCursor.transform);
}
playAreaCursor.transform.localScale = new Vector3(width, height, length);
playAreaCursorValidChild.transform.localScale = Vector3.one;
if (invalidLocationObject != null)
{
playAreaCursorInvalidChild.transform.localScale = Vector3.one;
}
playAreaCursor.SetActive(false);
}
protected virtual void GeneratePlayAreaCursor(Vector3[] cursorDrawVertices)
{
if (playAreaCursorDimensions != Vector2.zero)
{
float customAreaPadding = VRTK_SDK_Bridge.GetPlayAreaBorderThickness();
cursorDrawVertices[btmRightOuter] = new Vector3(playAreaCursorDimensions.x / 2, 0f, (playAreaCursorDimensions.y / 2) * -1);
cursorDrawVertices[btmLeftOuter] = new Vector3((playAreaCursorDimensions.x / 2) * -1, 0f, (playAreaCursorDimensions.y / 2) * -1);
cursorDrawVertices[topLeftOuter] = new Vector3((playAreaCursorDimensions.x / 2) * -1, 0f, playAreaCursorDimensions.y / 2);
cursorDrawVertices[topRightOuter] = new Vector3(playAreaCursorDimensions.x / 2, 0f, playAreaCursorDimensions.y / 2);
cursorDrawVertices[btmRightInner] = cursorDrawVertices[btmRightOuter] + new Vector3(-customAreaPadding, 0f, customAreaPadding);
cursorDrawVertices[btmLeftInner] = cursorDrawVertices[btmLeftOuter] + new Vector3(customAreaPadding, 0f, customAreaPadding);
cursorDrawVertices[topLeftInner] = cursorDrawVertices[topLeftOuter] + new Vector3(customAreaPadding, 0f, -customAreaPadding);
cursorDrawVertices[topRightInner] = cursorDrawVertices[topRightOuter] + new Vector3(-customAreaPadding, 0f, -customAreaPadding);
}
float width = cursorDrawVertices[btmRightOuter].x - cursorDrawVertices[topLeftOuter].x;
float length = cursorDrawVertices[topLeftOuter].z - cursorDrawVertices[btmRightOuter].z;
float height = 0.01f;
playAreaCursor = new GameObject(GeneratePlayAreaCursorName());
playAreaCursor.transform.SetParent(null);
playAreaCursor.transform.localScale = new Vector3(width, height, length);
float playAreaBoundaryX = playArea.transform.localScale.x / 2;
float playAreaBoundaryZ = playArea.transform.localScale.z / 2;
float heightOffset = 0f;
DrawPlayAreaCursorBoundary(0, cursorDrawVertices[btmLeftOuter].x, cursorDrawVertices[btmRightOuter].x, cursorDrawVertices[btmRightInner].z, cursorDrawVertices[btmRightOuter].z, height, new Vector3(0f, heightOffset, playAreaBoundaryZ));
DrawPlayAreaCursorBoundary(1, cursorDrawVertices[btmLeftOuter].x, cursorDrawVertices[btmLeftInner].x, cursorDrawVertices[topLeftOuter].z, cursorDrawVertices[btmLeftOuter].z, height, new Vector3(playAreaBoundaryX, heightOffset, 0f));
DrawPlayAreaCursorBoundary(2, cursorDrawVertices[btmLeftOuter].x, cursorDrawVertices[btmRightOuter].x, cursorDrawVertices[btmRightInner].z, cursorDrawVertices[btmRightOuter].z, height, new Vector3(0f, heightOffset, -playAreaBoundaryZ));
DrawPlayAreaCursorBoundary(3, cursorDrawVertices[btmLeftOuter].x, cursorDrawVertices[btmLeftInner].x, cursorDrawVertices[topLeftOuter].z, cursorDrawVertices[btmLeftOuter].z, height, new Vector3(-playAreaBoundaryX, heightOffset, 0f));
}
protected virtual void DrawPlayAreaCursorBoundary(int index, float left, float right, float top, float bottom, float thickness, Vector3 localPosition)
{
GameObject playAreaCursorBoundary = GameObject.CreatePrimitive(PrimitiveType.Cube);
playAreaCursorBoundary.name = VRTK_SharedMethods.GenerateVRTKObjectName(true, gameObject.name, "PlayAreaCursorBoundary", index);
VRTK_PlayerObject.SetPlayerObject(playAreaCursorBoundary, VRTK_PlayerObject.ObjectTypes.Pointer);
float width = (right - left) / 1.065f;
float length = (top - bottom) / 1.08f;
float height = thickness;
playAreaCursorBoundary.transform.localScale = new Vector3(width, height, length);
Destroy(playAreaCursorBoundary.GetComponent<BoxCollider>());
playAreaCursorBoundary.layer = LayerMask.NameToLayer("Ignore Raycast");
playAreaCursorBoundary.transform.SetParent(playAreaCursor.transform);
playAreaCursorBoundary.transform.localPosition = localPosition;
playAreaCursorBoundaries[index] = playAreaCursorBoundary;
}
protected virtual void CreateCursorCollider(GameObject cursor)
{
playAreaCursorCollider = cursor.AddComponent<BoxCollider>();
playAreaCursorCollider.isTrigger = true;
playAreaCursorCollider.center = new Vector3(0f, 65f, 0f);
playAreaCursorCollider.size = new Vector3(1f, 1f, 1f);
}
protected virtual void UpdateCollider()
{
float playAreaHeightAdjustment = 1f;
float newBCYSize = (headset.transform.position.y - playArea.transform.position.y) * 100f;
float newBCYCenter = (newBCYSize != 0 ? (newBCYSize / 2) + playAreaHeightAdjustment : 0);
playAreaCursorCollider.size = new Vector3(playAreaCursorCollider.size.x, newBCYSize, playAreaCursorCollider.size.z);
playAreaCursorCollider.center = new Vector3(playAreaCursorCollider.center.x, newBCYCenter, playAreaCursorCollider.center.z);
}
}
public class VRTK_PlayAreaCollider : MonoBehaviour
{
protected VRTK_PlayAreaCursor parent;
protected VRTK_PolicyList targetListPolicy;
public virtual void SetParent(VRTK_PlayAreaCursor setParent)
{
parent = setParent;
}
public virtual void SetIgnoreTarget(VRTK_PolicyList list = null)
{
targetListPolicy = list;
}
protected virtual void OnDisable()
{
if (parent != null)
{
parent.SetPlayAreaCursorCollision(false);
}
}
protected virtual void OnTriggerStay(Collider collider)
{
if (parent != null && parent.enabled && parent.gameObject.activeInHierarchy && ValidTarget(collider))
{
parent.SetPlayAreaCursorCollision(true, collider);
}
}
protected virtual void OnTriggerExit(Collider collider)
{
if (parent != null && ValidTarget(collider))
{
parent.SetPlayAreaCursorCollision(false, collider);
}
}
protected virtual bool ValidTarget(Collider collider)
{
return (!collider.isTrigger && !VRTK_PlayerObject.IsPlayerObject(collider.gameObject) && !(VRTK_PolicyList.Check(collider.gameObject, targetListPolicy)));
}
}
}