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 OnEnterPortalCollider; [FormerlySerializedAs("OnPreWarpEvent")] public UnityEvent OnPrePortalEvent; [FormerlySerializedAs("OnPostWarpEvent")] public UnityEvent OnPostPortalEvent; public UnityEvent 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 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(); } 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(); _rigidbody = TransformToPortal.GetComponent(); _colliders = TransformToPortal.GetComponentsInChildren(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(); if (_myTransformTargetOfPortalableObject == null) { _myTransformTargetOfPortalableObject = TransformToPortal.gameObject.AddComponent(); } _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(); //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(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(); 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(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; } } }