444 lines
12 KiB
C#
444 lines
12 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using UnityEngine;
|
|
using UnityEngine.XR;
|
|
|
|
public class LocomotionTeleport : MonoBehaviour
|
|
{
|
|
public enum States
|
|
{
|
|
Ready = 0,
|
|
Aim = 1,
|
|
CancelAim = 2,
|
|
PreTeleport = 3,
|
|
CancelTeleport = 4,
|
|
Teleporting = 5,
|
|
PostTeleport = 6
|
|
}
|
|
|
|
public enum TeleportIntentions
|
|
{
|
|
None = 0,
|
|
Aim = 1,
|
|
PreTeleport = 2,
|
|
Teleport = 3
|
|
}
|
|
|
|
public enum AimCollisionTypes
|
|
{
|
|
Point = 0,
|
|
Sphere = 1,
|
|
Capsule = 2
|
|
}
|
|
|
|
public class AimData
|
|
{
|
|
public RaycastHit TargetHitInfo;
|
|
|
|
public bool TargetValid;
|
|
|
|
public Vector3? Destination;
|
|
|
|
public float Radius;
|
|
|
|
public List<Vector3> Points { get; private set; }
|
|
|
|
public AimData()
|
|
{
|
|
Points = new List<Vector3>();
|
|
}
|
|
|
|
public void Reset()
|
|
{
|
|
Points.Clear();
|
|
TargetValid = false;
|
|
Destination = null;
|
|
}
|
|
}
|
|
|
|
[Tooltip("Allow linear movement prior to the teleport system being activated.")]
|
|
public bool EnableMovementDuringReady = true;
|
|
|
|
[Tooltip("Allow linear movement while the teleport system is in the process of aiming for a teleport target.")]
|
|
public bool EnableMovementDuringAim = true;
|
|
|
|
[Tooltip("Allow linear movement while the teleport system is in the process of configuring the landing orientation.")]
|
|
public bool EnableMovementDuringPreTeleport = true;
|
|
|
|
[Tooltip("Allow linear movement after the teleport has occurred but before the system has returned to the ready state.")]
|
|
public bool EnableMovementDuringPostTeleport = true;
|
|
|
|
[Tooltip("Allow rotation prior to the teleport system being activated.")]
|
|
public bool EnableRotationDuringReady = true;
|
|
|
|
[Tooltip("Allow rotation while the teleport system is in the process of aiming for a teleport target.")]
|
|
public bool EnableRotationDuringAim = true;
|
|
|
|
[Tooltip("Allow rotation while the teleport system is in the process of configuring the landing orientation.")]
|
|
public bool EnableRotationDuringPreTeleport = true;
|
|
|
|
[Tooltip("Allow rotation after the teleport has occurred but before the system has returned to the ready state.")]
|
|
public bool EnableRotationDuringPostTeleport = true;
|
|
|
|
[NonSerialized]
|
|
public TeleportAimHandler AimHandler;
|
|
|
|
[Tooltip("This prefab will be instantiated as needed and updated to match the current aim target.")]
|
|
public TeleportDestination TeleportDestinationPrefab;
|
|
|
|
[NonSerialized]
|
|
public TeleportInputHandler InputHandler;
|
|
|
|
[NonSerialized]
|
|
public TeleportIntentions CurrentIntention;
|
|
|
|
[NonSerialized]
|
|
public bool IsPreTeleportRequested;
|
|
|
|
[NonSerialized]
|
|
public bool IsTransitioning;
|
|
|
|
[NonSerialized]
|
|
public bool IsPostTeleportRequested;
|
|
|
|
private TeleportDestination _teleportDestination;
|
|
|
|
[Tooltip("When aiming at possible destinations, the aim collision type determines which shape to use for collision tests.")]
|
|
public AimCollisionTypes AimCollisionType;
|
|
|
|
[Tooltip("Use the character collision radius/height/skinwidth for sphere/capsule collision tests.")]
|
|
public bool UseCharacterCollisionData;
|
|
|
|
[Tooltip("Radius of the sphere or capsule used for collision testing when aiming to possible teleport destinations. Ignored if UseCharacterCollisionData is true.")]
|
|
public float AimCollisionRadius;
|
|
|
|
[Tooltip("Height of the capsule used for collision testing when aiming to possible teleport destinations. Ignored if UseCharacterCollisionData is true.")]
|
|
public float AimCollisionHeight;
|
|
|
|
public States CurrentState { get; private set; }
|
|
|
|
public Quaternion DestinationRotation
|
|
{
|
|
get
|
|
{
|
|
return _teleportDestination.OrientationIndicator.rotation;
|
|
}
|
|
}
|
|
|
|
public LocomotionController LocomotionController { get; private set; }
|
|
|
|
public event Action<bool, Vector3?, Quaternion?, Quaternion?> UpdateTeleportDestination;
|
|
|
|
public event Action EnterStateReady;
|
|
|
|
public event Action EnterStateAim;
|
|
|
|
public event Action<AimData> UpdateAimData;
|
|
|
|
public event Action ExitStateAim;
|
|
|
|
public event Action EnterStateCancelAim;
|
|
|
|
public event Action EnterStatePreTeleport;
|
|
|
|
public event Action EnterStateCancelTeleport;
|
|
|
|
public event Action EnterStateTeleporting;
|
|
|
|
public event Action EnterStatePostTeleport;
|
|
|
|
public event Action<Transform, Vector3, Quaternion> Teleported;
|
|
|
|
public void EnableMovement(bool ready, bool aim, bool pre, bool post)
|
|
{
|
|
EnableMovementDuringReady = ready;
|
|
EnableMovementDuringAim = aim;
|
|
EnableMovementDuringPreTeleport = pre;
|
|
EnableMovementDuringPostTeleport = post;
|
|
}
|
|
|
|
public void EnableRotation(bool ready, bool aim, bool pre, bool post)
|
|
{
|
|
EnableRotationDuringReady = ready;
|
|
EnableRotationDuringAim = aim;
|
|
EnableRotationDuringPreTeleport = pre;
|
|
EnableRotationDuringPostTeleport = post;
|
|
}
|
|
|
|
public void OnUpdateTeleportDestination(bool isValidDestination, Vector3? position, Quaternion? rotation, Quaternion? landingRotation)
|
|
{
|
|
if (this.UpdateTeleportDestination != null)
|
|
{
|
|
this.UpdateTeleportDestination(isValidDestination, position, rotation, landingRotation);
|
|
}
|
|
}
|
|
|
|
public bool AimCollisionTest(Vector3 start, Vector3 end, LayerMask aimCollisionLayerMask, out RaycastHit hitInfo)
|
|
{
|
|
Vector3 vector = end - start;
|
|
float magnitude = vector.magnitude;
|
|
Vector3 direction = vector / magnitude;
|
|
switch (AimCollisionType)
|
|
{
|
|
case AimCollisionTypes.Capsule:
|
|
{
|
|
float num;
|
|
float num2;
|
|
if (UseCharacterCollisionData)
|
|
{
|
|
CharacterController characterController2 = LocomotionController.CharacterController;
|
|
num = characterController2.height;
|
|
num2 = characterController2.radius;
|
|
}
|
|
else
|
|
{
|
|
num = AimCollisionHeight;
|
|
num2 = AimCollisionRadius;
|
|
}
|
|
return Physics.CapsuleCast(start + new Vector3(0f, num2, 0f), start + new Vector3(0f, num + num2, 0f), num2, direction, out hitInfo, magnitude, aimCollisionLayerMask, QueryTriggerInteraction.Ignore);
|
|
}
|
|
case AimCollisionTypes.Point:
|
|
return Physics.Raycast(start, direction, out hitInfo, magnitude, aimCollisionLayerMask, QueryTriggerInteraction.Ignore);
|
|
case AimCollisionTypes.Sphere:
|
|
{
|
|
float radius;
|
|
if (UseCharacterCollisionData)
|
|
{
|
|
CharacterController characterController = LocomotionController.CharacterController;
|
|
radius = characterController.radius - characterController.skinWidth;
|
|
}
|
|
else
|
|
{
|
|
radius = AimCollisionRadius;
|
|
}
|
|
return Physics.SphereCast(start, radius, direction, out hitInfo, magnitude, aimCollisionLayerMask, QueryTriggerInteraction.Ignore);
|
|
}
|
|
default:
|
|
throw new Exception();
|
|
}
|
|
}
|
|
|
|
[Conditional("DEBUG_TELEPORT_STATES")]
|
|
protected void LogState(string msg)
|
|
{
|
|
UnityEngine.Debug.Log(Time.frameCount + ": " + msg);
|
|
}
|
|
|
|
protected void CreateNewTeleportDestination()
|
|
{
|
|
TeleportDestinationPrefab.gameObject.SetActive(false);
|
|
TeleportDestination teleportDestination = UnityEngine.Object.Instantiate(TeleportDestinationPrefab);
|
|
teleportDestination.LocomotionTeleport = this;
|
|
_teleportDestination = teleportDestination;
|
|
_teleportDestination.LocomotionTeleport = this;
|
|
}
|
|
|
|
private void DeactivateDestination()
|
|
{
|
|
_teleportDestination.OnDeactivated();
|
|
}
|
|
|
|
public void RecycleTeleportDestination(TeleportDestination oldDestination)
|
|
{
|
|
if (oldDestination == _teleportDestination)
|
|
{
|
|
CreateNewTeleportDestination();
|
|
}
|
|
UnityEngine.Object.Destroy(oldDestination.gameObject);
|
|
}
|
|
|
|
private void EnableMotion(bool enableLinear, bool enableRotation)
|
|
{
|
|
LocomotionController.PlayerController.EnableLinearMovement = enableLinear;
|
|
LocomotionController.PlayerController.EnableRotation = enableRotation;
|
|
}
|
|
|
|
private void Awake()
|
|
{
|
|
LocomotionController = GetComponent<LocomotionController>();
|
|
CreateNewTeleportDestination();
|
|
}
|
|
|
|
public virtual void OnEnable()
|
|
{
|
|
CurrentState = States.Ready;
|
|
StartCoroutine(ReadyStateCoroutine());
|
|
}
|
|
|
|
protected IEnumerator ReadyStateCoroutine()
|
|
{
|
|
yield return null;
|
|
CurrentState = States.Ready;
|
|
EnableMotion(EnableMovementDuringReady, EnableRotationDuringReady);
|
|
if (this.EnterStateReady != null)
|
|
{
|
|
this.EnterStateReady();
|
|
}
|
|
while (CurrentIntention != TeleportIntentions.Aim)
|
|
{
|
|
yield return null;
|
|
}
|
|
yield return null;
|
|
StartCoroutine(AimStateCoroutine());
|
|
}
|
|
|
|
public void OnUpdateAimData(AimData aimData)
|
|
{
|
|
if (this.UpdateAimData != null)
|
|
{
|
|
this.UpdateAimData(aimData);
|
|
}
|
|
}
|
|
|
|
protected IEnumerator AimStateCoroutine()
|
|
{
|
|
CurrentState = States.Aim;
|
|
EnableMotion(EnableMovementDuringAim, EnableRotationDuringAim);
|
|
if (this.EnterStateAim != null)
|
|
{
|
|
this.EnterStateAim();
|
|
}
|
|
_teleportDestination.gameObject.SetActive(true);
|
|
while (CurrentIntention == TeleportIntentions.Aim)
|
|
{
|
|
yield return null;
|
|
}
|
|
if (this.ExitStateAim != null)
|
|
{
|
|
this.ExitStateAim();
|
|
}
|
|
yield return null;
|
|
if ((CurrentIntention == TeleportIntentions.PreTeleport || CurrentIntention == TeleportIntentions.Teleport) && _teleportDestination.IsValidDestination)
|
|
{
|
|
StartCoroutine(PreTeleportStateCoroutine());
|
|
}
|
|
else
|
|
{
|
|
StartCoroutine(CancelAimStateCoroutine());
|
|
}
|
|
}
|
|
|
|
protected IEnumerator CancelAimStateCoroutine()
|
|
{
|
|
CurrentState = States.CancelAim;
|
|
if (this.EnterStateCancelAim != null)
|
|
{
|
|
this.EnterStateCancelAim();
|
|
}
|
|
DeactivateDestination();
|
|
yield return null;
|
|
StartCoroutine(ReadyStateCoroutine());
|
|
}
|
|
|
|
protected IEnumerator PreTeleportStateCoroutine()
|
|
{
|
|
CurrentState = States.PreTeleport;
|
|
EnableMotion(EnableMovementDuringPreTeleport, EnableRotationDuringPreTeleport);
|
|
if (this.EnterStatePreTeleport != null)
|
|
{
|
|
this.EnterStatePreTeleport();
|
|
}
|
|
while (CurrentIntention == TeleportIntentions.PreTeleport || IsPreTeleportRequested)
|
|
{
|
|
yield return null;
|
|
}
|
|
if (_teleportDestination.IsValidDestination)
|
|
{
|
|
StartCoroutine(TeleportingStateCoroutine());
|
|
}
|
|
else
|
|
{
|
|
StartCoroutine(CancelTeleportStateCoroutine());
|
|
}
|
|
}
|
|
|
|
protected IEnumerator CancelTeleportStateCoroutine()
|
|
{
|
|
CurrentState = States.CancelTeleport;
|
|
if (this.EnterStateCancelTeleport != null)
|
|
{
|
|
this.EnterStateCancelTeleport();
|
|
}
|
|
DeactivateDestination();
|
|
yield return null;
|
|
StartCoroutine(ReadyStateCoroutine());
|
|
}
|
|
|
|
protected IEnumerator TeleportingStateCoroutine()
|
|
{
|
|
CurrentState = States.Teleporting;
|
|
EnableMotion(false, false);
|
|
if (this.EnterStateTeleporting != null)
|
|
{
|
|
this.EnterStateTeleporting();
|
|
}
|
|
while (IsTransitioning)
|
|
{
|
|
yield return null;
|
|
}
|
|
yield return null;
|
|
StartCoroutine(PostTeleportStateCoroutine());
|
|
}
|
|
|
|
protected IEnumerator PostTeleportStateCoroutine()
|
|
{
|
|
CurrentState = States.PostTeleport;
|
|
EnableMotion(EnableMovementDuringPostTeleport, EnableRotationDuringPostTeleport);
|
|
if (this.EnterStatePostTeleport != null)
|
|
{
|
|
this.EnterStatePostTeleport();
|
|
}
|
|
while (IsPostTeleportRequested)
|
|
{
|
|
yield return null;
|
|
}
|
|
DeactivateDestination();
|
|
yield return null;
|
|
StartCoroutine(ReadyStateCoroutine());
|
|
}
|
|
|
|
public void DoTeleport()
|
|
{
|
|
CharacterController characterController = LocomotionController.CharacterController;
|
|
Transform transform = characterController.transform;
|
|
Transform orientationIndicator = _teleportDestination.OrientationIndicator;
|
|
Vector3 position = orientationIndicator.position;
|
|
position.y += characterController.height * 0.5f;
|
|
Quaternion landingRotation = _teleportDestination.LandingRotation;
|
|
if (this.Teleported != null)
|
|
{
|
|
this.Teleported(transform, position, landingRotation);
|
|
}
|
|
transform.position = position;
|
|
transform.rotation = landingRotation;
|
|
LocomotionController.PlayerController.Teleported = true;
|
|
}
|
|
|
|
public Vector3 GetCharacterPosition()
|
|
{
|
|
return LocomotionController.CharacterController.transform.position;
|
|
}
|
|
|
|
public Quaternion GetHeadRotationY()
|
|
{
|
|
Vector3 eulerAngles = InputTracking.GetLocalRotation(XRNode.Head).eulerAngles;
|
|
eulerAngles.x = 0f;
|
|
eulerAngles.z = 0f;
|
|
return Quaternion.Euler(eulerAngles);
|
|
}
|
|
|
|
public void DoWarp(Vector3 startPos, float positionPercent)
|
|
{
|
|
Transform orientationIndicator = _teleportDestination.OrientationIndicator;
|
|
Vector3 position = orientationIndicator.position;
|
|
position.y += LocomotionController.CharacterController.height / 2f;
|
|
CharacterController characterController = LocomotionController.CharacterController;
|
|
Transform transform = characterController.transform;
|
|
Vector3 position2 = Vector3.Lerp(startPos, position, positionPercent);
|
|
transform.position = position2;
|
|
LocomotionController.PlayerController.Teleported = true;
|
|
}
|
|
}
|