116 lines
5.5 KiB
C#
116 lines
5.5 KiB
C#
using UnityEngine;
|
|
|
|
namespace Unity.Cinemachine.Samples
|
|
{
|
|
// Interface for behaviours implementing teleportation
|
|
public interface ITeleportable
|
|
{
|
|
// Teleport the object to a new worldspace location and rotation
|
|
public void Teleport(Vector3 newPos, Quaternion newRot);
|
|
}
|
|
|
|
// This class will teleport an object and the main CinemachineCamera tracking it to a target Teleporter object.
|
|
// This class co-operates with the ITeleportables interface which implements the teleportation.
|
|
public class Teleporter : MonoBehaviour
|
|
{
|
|
// The target portal to which the player will be teleported
|
|
public Teleporter TargetPortal;
|
|
|
|
// Sets the teleported in receivce-only mode, which prevents received
|
|
// objects from immediately being teleported out
|
|
public bool ReceiveOnlyMode { get; set; }
|
|
|
|
// Teleport an object to the target portal
|
|
void TeleportToTarget(Transform player)
|
|
{
|
|
// If a target portal is not specified, choose one at random
|
|
var targetPortal = TargetPortal;
|
|
if (targetPortal == null)
|
|
{
|
|
var allPortals = FindObjectsByType<Teleporter>(FindObjectsInactive.Exclude, FindObjectsSortMode.None);
|
|
if (allPortals.Length < 2)
|
|
return; // no other portals
|
|
|
|
// Choose a random one
|
|
var index = Random.Range(0, allPortals.Length);
|
|
if (allPortals[index] == this && --index < 0)
|
|
index = allPortals.Length - 1;
|
|
targetPortal = allPortals[index];
|
|
}
|
|
targetPortal.ReceiveObjectFromPortal(player, transform);
|
|
}
|
|
|
|
// Receive an object from another portal
|
|
void ReceiveObjectFromPortal(Transform player, Transform srcPortal)
|
|
{
|
|
// Deactivate so we can receive player without re-teleporting
|
|
ReceiveOnlyMode = true;
|
|
|
|
var pivot = srcPortal.position;
|
|
var rotDelta = Quaternion.FromToRotation(srcPortal.forward, transform.forward);
|
|
var posDelta = transform.position - pivot;
|
|
|
|
// Teleport the player. Either set the new pos/rot directly, or if a ITeleportable
|
|
// is present, get it to do the teleportation.
|
|
player.GetPositionAndRotation(out var playerPos, out var playerRot);
|
|
var newPlayerPos = RotateAround(playerPos, pivot, rotDelta) + posDelta;
|
|
var newPlayerRot = rotDelta * playerRot;
|
|
if (player.TryGetComponent(out ITeleportable teleportable))
|
|
teleportable.Teleport(newPlayerPos, newPlayerRot);
|
|
else
|
|
player.SetPositionAndRotation(newPlayerPos, newPlayerRot);
|
|
|
|
// Teleport the camera. This implementation only works for cameras which directly target the player.
|
|
// If your camera target is a child of player, then you will need to do some extra work here
|
|
// to find the appropriate target.
|
|
var cameraTarget = player;
|
|
|
|
// Now we iterate all active cameras targeting the player, teleporting each one.
|
|
for (int i = 0; i < CinemachineCore.VirtualCameraCount; ++i)
|
|
{
|
|
var cam = CinemachineCore.GetVirtualCamera(i);
|
|
if (cam.Follow != cameraTarget)
|
|
continue;
|
|
|
|
// Note that we don't use the camera's transform since that doesn't always reflect the
|
|
// actual position and rotation. Instead, we snapshot the VirtualCamera's State member.
|
|
var state = cam.State;
|
|
|
|
// This call will seamlessly teleport the camera based on the player's change of position,
|
|
// but it will not handle a change of rotation
|
|
cam.OnTargetObjectWarped(cameraTarget, newPlayerPos - playerPos);
|
|
|
|
// If the player-camera combo is not only being translated but also rotated by the portal,
|
|
// we need to do some additional work to tell the CinemachineCamera to rotate its state
|
|
// and teleport seamlessly.
|
|
if (rotDelta != Quaternion.identity)
|
|
{
|
|
// Here we grab the camera's original position and put it through the same teleportation
|
|
// as the player, preserving the relationship between camera and player.
|
|
var camPos = state.GetFinalPosition();
|
|
var camRot = state.GetFinalOrientation();
|
|
var newCamPos = RotateAround(camPos, pivot, rotDelta) + posDelta;
|
|
var newCamRot = rotDelta * camRot;
|
|
|
|
// Now force the CinemachineCamera to be at the desired position and rotation. This will
|
|
// also manipulate the internal camera state, so that no spurious damping or gitching will occur
|
|
cam.ForceCameraPosition(newCamPos, newCamRot);
|
|
}
|
|
}
|
|
|
|
// This is a helper function to rotate a point around a pivot using a quaternion rotation
|
|
static Vector3 RotateAround(Vector3 p, Vector3 pivot, Quaternion rot) => rot * (p - pivot) + pivot;
|
|
}
|
|
|
|
// When something enters the trigger zone, we teleport it
|
|
void OnTriggerEnter(Collider other)
|
|
{
|
|
if (!ReceiveOnlyMode)
|
|
TeleportToTarget(other.transform);
|
|
}
|
|
|
|
// After receiving an object, we can't teleport it away until after it leaves the trigger zone
|
|
void OnTriggerExit(Collider other) => ReceiveOnlyMode = false;
|
|
}
|
|
}
|