Files
2025-05-26 00:46:28 +02:00

436 lines
18 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Serialization;
namespace Fragilem17.MirrorsAndPortals
{
[RequireComponent(typeof(Collider))]
public class PortalableObject : MonoBehaviour
{
[Tooltip("Leave empty to target this gameObject, use this to transport a PlayerController as soon as the 'head' passes through.")]
[FormerlySerializedAs("TransformToTransport")]
public Transform TransformToPortal;
[Tooltip("When true, this object will be transported by a portal when passing through it. unless PortalWithMasterPortalable is turned on, then it will only portal when the master is portalled. ")]
[FormerlySerializedAs("AllowTransporting")]
public bool PortallingEnabled = true;
[Space(10)] // 10 pixels of spacing here.
[Header("Events")]
public UnityEvent<Portal> OnEnterPortalCollider;
[FormerlySerializedAs("OnPreWarpEvent")]
public UnityEvent<PortalableObject, Portal> OnPrePortalEvent;
[FormerlySerializedAs("OnPostWarpEvent")]
public UnityEvent<PortalableObject, Portal> OnPostPortalEvent;
public UnityEvent<PortalableObject, Portal> OnExitPortalCollider;
[Space(10)] // 10 pixels of spacing here.
[Tooltip("Turn on if this is your playerController.")]
public bool IsMasterPortalableObject;
[Tooltip("When true, this object will transport keeping it's relative distance from the MasterPortalableObject (your PlayerController)")]
[FormerlySerializedAs("PortalWithMasterPortalable")]
public bool PortalAlongWithMasterPortalable;
protected Portal _inPortal;
protected TransformTargetOfPortalableObject _myTransformTargetOfPortalableObject;
protected Rigidbody _rigidbody;
protected CharacterController _characterController;
protected Collider[] _colliders;
public List<Rigidbody> RigidbodiesToPortal;
[HideInInspector]
public CloneRenderer[] childCloneRenderers;
private static readonly Quaternion halfTurn = Quaternion.Euler(0.0f, 180.0f, 0.0f);
private bool childOfAnotherPortalable = false;
public static PortalableObject MasterPortalable;
public bool EnablePortalAlongWithMasterPortalable
{
get => PortalAlongWithMasterPortalable;
set
{
PortalAlongWithMasterPortalable = value;
/*if (PortalAlongWithMasterPortalable != value)
{
PortalAlongWithMasterPortalable = value;
if (!PortalAlongWithMasterPortalable && _inPortal != null)
{
// if we're no longer gonna portal along, then make sure we exit the current portal
ExitPortal(_inPortal);
}
}*/
}
}
public bool SetPortallingEnabled { get => PortallingEnabled; set => PortallingEnabled = value; }
[Tooltip("Assign a transform and it will always be positioned on the other side of the nearest portal relative to this object")]
public Transform ClonePositionToClosestPortal;
protected virtual void Awake()
{
//Debug.Log("AWAKE!! " + name);
if (!TransformToPortal)
{
TransformToPortal = GetComponent<Transform>();
}
if (MasterPortalable != null && IsMasterPortalableObject && MasterPortalable != this)
{
Debug.LogError("There appears to be a second MasterPortalableObject in the scene, there should be only one! (" + name + ")");
}
if (IsMasterPortalableObject)
{
MasterPortalable = this;
}
_characterController = TransformToPortal.GetComponentInChildren<CharacterController>();
_rigidbody = TransformToPortal.GetComponent<Rigidbody>();
_colliders = TransformToPortal.GetComponentsInChildren<Collider>(true);
}
protected virtual void LateUpdate()
{
if (ClonePositionToClosestPortal)
{
Portal closest = PortalRenderer.FindClosestPortalInAllRenderers(transform.position);
Transform _inTransform = closest.PortalSurface.transform;
Transform _outTransform = closest.OtherPortal.PortalSurface.transform;
float scaleFactor = (closest.OtherPortal.transform.lossyScale.x / closest.transform.lossyScale.x);
ClonePositionToClosestPortal.transform.localScale = transform.lossyScale * scaleFactor;
// Update position of clone.
Vector3 relativePos = _inTransform.InverseTransformPoint(transform.position);
relativePos = halfTurn * relativePos;
ClonePositionToClosestPortal.transform.position = _outTransform.TransformPoint(relativePos);
// Update rotation of clone.
Quaternion relativeRot = Quaternion.Inverse(_inTransform.rotation) * transform.rotation;
relativeRot = halfTurn * relativeRot;
ClonePositionToClosestPortal.transform.rotation = _outTransform.rotation * relativeRot;
}
}
protected void OnEnable()
{
// todo, if we change the TransformToTransport pointer, we need to remove the target below
_myTransformTargetOfPortalableObject = TransformToPortal.GetComponent<TransformTargetOfPortalableObject>();
if (_myTransformTargetOfPortalableObject == null)
{
_myTransformTargetOfPortalableObject = TransformToPortal.gameObject.AddComponent<TransformTargetOfPortalableObject>();
}
_myTransformTargetOfPortalableObject.PortalableObject = this;
if (IsMasterPortalableObject)
{
MasterPortalable = this;
}
if (MasterPortalable != null && MasterPortalable != this)
{
MasterPortalable.OnEnterPortalCollider.RemoveListener(OnMasterPortalEnterPortalCollider);
MasterPortalable.OnExitPortalCollider.RemoveListener(OnMasterPortalExitPortalCollider);
MasterPortalable.OnPrePortalEvent.RemoveListener(OnMasterPortalablePreWarp);
MasterPortalable.OnPostPortalEvent.RemoveListener(OnMasterPortalableWarped);
//Debug.Log("ADDING LISTENER! " + name);
MasterPortalable.OnEnterPortalCollider.AddListener(OnMasterPortalEnterPortalCollider);
MasterPortalable.OnExitPortalCollider.AddListener(OnMasterPortalExitPortalCollider);
MasterPortalable.OnPrePortalEvent.AddListener(OnMasterPortalablePreWarp);
MasterPortalable.OnPostPortalEvent.AddListener(OnMasterPortalableWarped);
}
OnTransformParentChanged();
}
protected virtual void Start()
{
// Awake and OnEnable are called together, Start is called when all other components' Awake and OnEnable have been Called
// only here are we sure a MasterPortalable exists.
OnEnable();
}
protected void OnDisable()
{
if (MasterPortalable != null && MasterPortalable != this)
{
MasterPortalable.OnEnterPortalCollider.RemoveListener(OnMasterPortalEnterPortalCollider);
MasterPortalable.OnExitPortalCollider.RemoveListener(OnMasterPortalExitPortalCollider);
MasterPortalable.OnPrePortalEvent.RemoveListener(OnMasterPortalablePreWarp);
MasterPortalable.OnPostPortalEvent.RemoveListener(OnMasterPortalableWarped);
}
if (IsMasterPortalableObject)
{
MasterPortalable = null;
}
}
protected virtual void OnMasterPortalExitPortalCollider(PortalableObject masterObject, Portal portal)
{
}
protected virtual void OnMasterPortalEnterPortalCollider(Portal portal)
{
if (PortalAlongWithMasterPortalable)
{
SetIsInPortal(portal);
}
}
protected virtual void OnMasterPortalablePreWarp(PortalableObject masterObject, Portal fromPortal)
{
if (PortalAlongWithMasterPortalable)
{
PreWarp(fromPortal);
}
}
protected virtual void OnMasterPortalableWarped(PortalableObject masterObject, Portal fromPortal)
{
if (PortalAlongWithMasterPortalable)
{
//Debug.Log("OnMasterPortalableWarped! i will go along " + name);
PortalFrom(fromPortal, true);
}
}
protected virtual void OnTransformParentChanged()
{
CheckForParentPortalables();
}
protected void CheckForParentPortalables()
{
// check for the existance of a parent PortalableObject
TransformTargetOfPortalableObject[] parentPortalables = GetComponentsInParent<TransformTargetOfPortalableObject>();
//Debug.Log("check OnTransformParentChanged: " + name + " :" + parentPortalables.Length);
childOfAnotherPortalable = (parentPortalables.Length > 1);
}
public virtual void SetIsInPortal(Portal portal)
{
ExitPortal(portal.OtherPortal);
_inPortal = portal;
OnEnterPortalCollider?.Invoke(portal);
if (PortallingEnabled) // !childOfAnotherPortalable &&
{
//Debug.Log("SetIsInPortal " + this.name + " inPortal: " + portal.name + " turning OFF colliders ");
if (portal.wallCollider)
{
// if that's the case.. then we can turn of the colliders
// disable collisions with other portal
_colliders = TransformToPortal.GetComponentsInChildren<Collider>(true);
for (int i = 0; i < _colliders.Length; i++)
{
Physics.IgnoreCollision(_colliders[i], portal.wallCollider);
}
}
}
}
public virtual void ExitPortal(Portal portal)
{
if (_inPortal == portal)
{
_inPortal = null;
}
OnExitPortalCollider?.Invoke(this, portal);
//Debug.Log("ExitPortal " + this.name + " inPortal: " + portal.name + " turning ON colliders ");
if (portal.wallCollider)
{
for (int i = 0; i < _colliders.Length; i++)
{
Physics.IgnoreCollision(_colliders[i], portal.wallCollider, false);
}
}
}
protected virtual void PreWarp(Portal fromPortal)
{
//Debug.Log(" PRE: " + name);
OnPrePortalEvent.Invoke(this, fromPortal);
if (_characterController)
{
_characterController.enabled = false;
}
}
protected virtual void PostWarp(Portal fromPortal)
{
//Debug.Log("POST: " + name);
OnPostPortalEvent.Invoke(this, fromPortal);
//Debug.Log("PortalableObject postWarp, gonna triggered teleport function in HVR");
if (_characterController)
{
_characterController.enabled = true;
}
// instant move clones to other portal
childCloneRenderers = FindChildCloneRenderers();
for (int i = 0; i < childCloneRenderers.Length; i++)
{
fromPortal.PortalTransporter.cloneObjects.Remove(childCloneRenderers[i]);
childCloneRenderers[i].ExitPortal(fromPortal, false);
Collider[] myColliders = childCloneRenderers[i].GetComponents<Collider>();
foreach (Collider c in myColliders) {
//Debug.DrawLine(childCloneRenderers[i].transform.position, fromPortal.OtherPortal.PortalTransporter.transform.position, Color.cyan, 10);
Vector3 direction;
float distance;
bool overlapped = Physics.ComputePenetration(
c, childCloneRenderers[i].transform.position, childCloneRenderers[i].transform.rotation,
fromPortal.OtherPortal.PortalTransporter.MyCollider, fromPortal.OtherPortal.PortalTransporter.transform.position, fromPortal.OtherPortal.PortalTransporter.transform.rotation,
out direction, out distance);
//Debug.Log(childCloneRenderers[i].name + " : " + direction + " : " + distance + " : " + overlapped);
if (overlapped)
{
//Debug.Log("AFTER WARP " + childCloneRenderers[i].name + " WOULD BE TOUCHING SO SET ME IN PORTAL: " + fromPortal.OtherPortal.name);
childCloneRenderers[i].SetIsInPortal(fromPortal.OtherPortal, false);
fromPortal.OtherPortal.PortalTransporter.cloneObjects.Add(childCloneRenderers[i]);
break;
}
/*else
{
Debug.Log("AFTER WARP not touching new portal: " + fromPortal.OtherPortal.name);
}*/
}
}
}
protected virtual CloneRenderer[] FindChildCloneRenderers()
{
return TransformToPortal.GetComponentsInChildren<CloneRenderer>(true);
}
public virtual bool CanPortal() {
//Debug.Log("AllowTransporting canWarp?: " + name + " :" + AllowTransporting);
return (!childOfAnotherPortalable && PortallingEnabled && !PortalAlongWithMasterPortalable);
}
public virtual bool PortalFrom(Portal fromPortal, bool force = false)
{
if (!force && !CanPortal())
{
return false;
}
//Debug.Log("WARP " + name + " from " + fromPortal.name + " childOfAnotherPortalable: " + childOfAnotherPortalable + " force: " + force);
// if we're forced, that means the Master got us here and a preWarp has already been done
if (!force) {
PreWarp(fromPortal);
}
if (force)
{
// master got us here.. we might have entered through physics.. but on the wrong side of the portal therefor not adding to the list in transporter
if (!fromPortal.OtherPortal.PortalTransporter.portalableObjects.Contains(this))
{
//Debug.Log("(from master) Adding to portal list of " + fromPortal.OtherPortal.name + " object:" + this.name);
fromPortal.OtherPortal.PortalTransporter.portalableObjects.Add(this);
}
}
// disable collisions with other portal
if (fromPortal.OtherPortal.wallCollider)
{
for (int i = 0; i < _colliders.Length; i++)
{
Physics.IgnoreCollision(_colliders[i], fromPortal.OtherPortal.wallCollider);
}
}
var inTransform = fromPortal.PortalSurface.transform;
var outTransform = fromPortal.OtherPortal.PortalSurface.transform;
// van scale p1.0 to p0.5 = player 0.5 = portal out / portal in
// van scale p0.5 to p1.0 = player 2 = portal out / portal in
float scaleFactor = (fromPortal.OtherPortal.transform.lossyScale.x / fromPortal.transform.lossyScale.x);
Vector3 originalScaleBeforeWarp = TransformToPortal.localScale;
TransformToPortal.localScale = TransformToPortal.localScale * scaleFactor;
// Position the camera behind the other portal.
Vector3 relativePos = inTransform.InverseTransformPoint(TransformToPortal.position);
relativePos = halfTurn * relativePos;
TransformToPortal.position = outTransform.TransformPoint(relativePos);
// Rotate the camera to look through the other portal.
Quaternion relativeRot = Quaternion.Inverse(inTransform.rotation) * TransformToPortal.rotation;
relativeRot = halfTurn * relativeRot;
TransformToPortal.rotation = outTransform.rotation * relativeRot;
// Update velocity of rigidbody.
if (_rigidbody)
{
Vector3 relativeVel = inTransform.InverseTransformDirection(_rigidbody.linearVelocity);
relativeVel = halfTurn * relativeVel;
_rigidbody.linearVelocity = outTransform.TransformDirection(relativeVel);
Vector3 relativeAngVel = inTransform.InverseTransformDirection(_rigidbody.angularVelocity);
relativeAngVel = halfTurn * relativeAngVel;
_rigidbody.angularVelocity = outTransform.TransformDirection(relativeAngVel);
_rigidbody.position = TransformToPortal.position; // so it works for RB with interpolation
_rigidbody.rotation = TransformToPortal.rotation; // so it works for RB with interpolation
}
if (RigidbodiesToPortal.Count > 0)
{
foreach (Rigidbody rb in RigidbodiesToPortal)
{
if (rb && rb != _rigidbody)
{
Vector3 relativeVel = inTransform.InverseTransformDirection(rb.linearVelocity);
relativeVel = halfTurn * relativeVel;
rb.linearVelocity = outTransform.TransformDirection(relativeVel);
Vector3 relativeAngVel = inTransform.InverseTransformDirection(rb.angularVelocity);
relativeAngVel = halfTurn * relativeAngVel;
rb.angularVelocity = outTransform.TransformDirection(relativeAngVel);
}
}
}
PostWarp(fromPortal);
SendMessage("OnWarped", transform.rotation, SendMessageOptions.DontRequireReceiver);
return true;
}
}
}