Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Voting for the Unity Awards are OPEN! We’re looking to celebrate creators across games, industry, film, and many more categories. Cast your vote now for all categories
    Dismiss Notice
  3. Dismiss Notice

Portals

Discussion in 'Scripting' started by LimeLava, Mar 16, 2018.

  1. LimeLava

    LimeLava

    Joined:
    Mar 16, 2018
    Posts:
    6
    I have recently started using unity and for a game I am working on I need to transport a player from one area of the map to another but the script I am using does not work and I cannot seem to figure out why. I have a box collider on both of my portals which are the receivers for each other but when my player walks into the collider nothing happens.
    Portals.png
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class PortalTeleporter : MonoBehaviour
    6. {
    7.  
    8.     public Transform player;
    9.     public Transform receiver;
    10.  
    11.     private bool playerIsOverlapping = false;
    12.  
    13.     // Update is called once per frame
    14.     void Update()
    15.     {
    16.         if (playerIsOverlapping)
    17.         {
    18.             Vector3 portalToPlayer = player.position - transform.position;
    19.             float dotProduct = Vector3.Dot(transform.up, portalToPlayer);
    20.  
    21.             if (dotProduct < 0f)
    22.             {
    23.                 float rotationDiff = -Quaternion.Angle(transform.rotation, receiver.rotation);
    24.                 rotationDiff += 180;
    25.                 player.Rotate(Vector3.up, rotationDiff);
    26.  
    27.                 Vector3 positionOffset = Quaternion.Euler(0f, rotationDiff, 0f) * portalToPlayer;
    28.                 player.position = receiver.position + positionOffset;
    29.  
    30.                 playerIsOverlapping = false;
    31.             }
    32.         }
    33.     }
    34.  
    35.     void OnTriggerEnter(Collider other)
    36.     {
    37.         if (other.tag == "Player")
    38.         {
    39.             playerIsOverlapping = true;
    40.         }
    41.     }
    42.  
    43.     void OnTriggerExit(Collider other)
    44.     {
    45.         if (other.tag == "Player")
    46.         {
    47.             playerIsOverlapping = false;
    48.         }
    49.     }
    50. }
     
    Last edited: Mar 16, 2018
  2. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,378
    First off, use code tags:
    https://forum.unity.com/threads/using-code-tags-properly.143875/

    Code (csharp):
    1.  
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5.  
    6. public class PortalTeleporter : MonoBehaviour
    7. {
    8.  
    9.     public Transform player;
    10.     public Transform receiver;
    11.  
    12.     private bool playerIsOverlapping = false;
    13.  
    14.     // Update is called once per frame
    15.     void Update()
    16.     {
    17.         if (playerIsOverlapping)
    18.         {
    19.             Vector3 portalToPlayer = player.position - transform.position;
    20.             float dotProduct = Vector3.Dot(transform.up, portalToPlayer);
    21.  
    22.             if (dotProduct < 0f)
    23.             {
    24.                 float rotationDiff = -Quaternion.Angle(transform.rotation, receiver.rotation);
    25.                 rotationDiff += 180;
    26.                 player.Rotate(Vector3.up, rotationDiff);
    27.  
    28.                 Vector3 positionOffset = Quaternion.Euler(0f, rotationDiff, 0f) * portalToPlayer;
    29.                 player.position = receiver.position + positionOffset;
    30.  
    31.                 playerIsOverlapping = false;
    32.             }
    33.         }
    34.     }
    35.  
    36.     void OnTriggerEnter(Collider other)
    37.     {
    38.         if (other.tag == "Player")
    39.         {
    40.             playerIsOverlapping = true;
    41.         }
    42.     }
    43.  
    44.     void OnTriggerExit(Collider other)
    45.     {
    46.         if (other.tag == "Player")
    47.         {
    48.             playerIsOverlapping = false;
    49.         }
    50.  
    51.     }
    52. }
    53.  
    Also, why is the actual teleport done in 'Update'? This could all be done in OnTriggerEnter.

    Also, why are you limiting yourself to having to reference the player directly, but check the Collider for the tag. Why not get the target from the Collider, and that way you can make the portal work for any range of entities that intersect it that meet some requirement (tag, layer, what not). Rather than only supporting a single object, the player.

    Also you might want to add functionality to handle multi-directional portals. Otherwise you might trigger the portal's targets return trigger box.
     
    Deeeds and LimeLava like this.
  3. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,378
    Give you an example of what I mean... here:
    Code (csharp):
    1.  
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5.  
    6. public class PortalTeleporter : MonoBehaviour
    7. {
    8.     [Tooltip("The location to which we teleport.")
    9.     public Transform RemoteLocation;
    10.     public PortalTeleporter RemoteReturnPortal;
    11.    
    12.     private HashSet<Transform> _ignore = new HashSet();
    13.  
    14.     void OnTriggerEnter(Collider other)
    15.     {
    16.         if(other.CompareTag("Player"))
    17.         {
    18.             var t = other.transform; //assuming the player's root transform is the same as the collider's
    19.             if(_ignore.Contains(t)) return;
    20.            
    21.             //for now I'm just doing a direct teleport, we can add on fluffy stuff later
    22.             t.position = this.RemoteLocation.position;
    23.            
    24.             if(RemoteReturnPortal != null)
    25.             {
    26.                 this.RemoteReturnPortal.IgnoreForAMoment(t);
    27.             }
    28.         }
    29.     }
    30.    
    31.     public void IgnoreForAMoment(Transform obj)
    32.     {
    33.         _ignore.Add(obj);
    34.         this.StartCoroutine(this.RemoveAfterDuration(obj, 1.0f));
    35.     }
    36.    
    37.     private void RemoveAfterDuration(Transform obj, float dur)
    38.     {
    39.         yield return new WaitForSeconds(dur);
    40.         _ignore.Remove(obj);
    41.     }
    42.  
    43. }
    44.  
    But as for the fluffy stuff. You have this:

    Code (csharp):
    1.  
    2. Vector3 portalToPlayer = player.position - transform.position;
    3. float dotProduct = Vector3.Dot(transform.up, portalToPlayer);
    4.  
    5. if (dotProduct < 0f)
    6. {
    7.     float rotationDiff = -Quaternion.Angle(transform.rotation, receiver.rotation);
    8.     rotationDiff += 180;
    9.     player.Rotate(Vector3.up, rotationDiff);
    10.  
    11.     Vector3 positionOffset = Quaternion.Euler(0f, rotationDiff, 0f) * portalToPlayer;
    12.     player.position = receiver.position + positionOffset;
    13.  
    14.     playerIsOverlapping = false;
    15. }
    16.  
    So in this, you get the delta between the player and the portal. Then you project that onto the up vector (dot product). And you only teleport if that projection is less than 0?

    Why?

    Basically you're saying that the player's position must be BELOW the origin/position of the portal trigger. What is the intent of this? This can block you from working if that position is say inside the floor collision or something. A point below the standing position of the player.

    After that we do all this rotation stuff. What is your intent there?

    Describe the intent of the fluff to figure out what you need to do.
     
    Deeeds and LimeLava like this.
  4. LimeLava

    LimeLava

    Joined:
    Mar 16, 2018
    Posts:
    6
    The first part of the fluff to ensure that the player can only enter the portal from one side because on one side of the portal I have a render plane that shows the output of a camera in the other world so the player knows what is on the other side of the portal and I don't want them to be able to enter from a side that the render plane is not showing. The second part of the fluff is to push the player out with the same rotation info as they had when they entered because if they go through the portal looking to the left I want them to come out looking left and that is because like noted above there is a render plane that shows the other side of the portal to the player and it moves with them.
    portal.png
     
    Last edited: Mar 16, 2018
  5. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,378
    OK.

    Well, you're projecting it on the 'up' vector.

    Is this plane rotated so that the up vector is in the facing direction? Because usually up is in the y direction, and 'forward' is in the facing direction... unless otherwise rotated.
     
    LimeLava likes this.
  6. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,378
    Would you mind exporting a demo of this in action for us to look at it? So I can get a better idea of what specifically your setting is?

    As you describe it, there are a lot of conditions that are specific to how you chose to do it.
     
    LimeLava likes this.
  7. LimeLava

    LimeLava

    Joined:
    Mar 16, 2018
    Posts:
    6
    I uploaded it to Github. https://github.com/LimeLava/PortalsDemo. One vector, portalToPlayer points to the player and the other one, Up, points up So that they create a 90-degree angle when the player walks into the portal. That way if the player comes from the wrong side the angle will be greater than 90 and the Dot Product will be less than 0.
     
    Last edited: Mar 16, 2018
  8. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,378
    I'll take a look at this later. In the middle of something.
     
  9. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,378
    For starters there was a lot wrong in the scene.

    1) The collider's that acted as the portals were not set to be 'trigger'. So instead they were solid and you couldn't walk through them.

    2) The render plane's had colliders on them as well, so those too blocked you from walking through.

    3) Your player was not tagged 'Player', which your code required.

    4) Everything was rotated really weird.

    Once that was all said and fixed. Your script worked.

    Well... it technically worked. It teleported you, and at the positions of your portals as they are it worked. Because there was no rotational difference between them.

    BUT, if you oriented the doors differently it broke since your rotation code doesn't do what you assume it does.

    I also don't think you should use Update like that though....

    So I rewrote it:
    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections.Generic;
    4.  
    5. public class Portal : MonoBehaviour
    6. {
    7.  
    8.     [Tooltip("Tag mask to test for, leave blank if dont' want to filter.")]
    9.     public string TagMask;
    10.     public Transform TargetLocation;
    11.     public Camera PortalCamera;
    12.     public Renderer PortalVisualSurface;
    13.  
    14.     #region Messages
    15.  
    16.     private void Start()
    17.     {
    18.         if(this.PortalCamera.targetTexture != null)
    19.         {
    20.             this.PortalCamera.targetTexture.Release();
    21.         }
    22.    
    23.         this.PortalCamera.targetTexture = new RenderTexture(Screen.width, Screen.height, 24);
    24.         this.PortalVisualSurface.material.mainTexture = this.PortalCamera.targetTexture;
    25.     }
    26.  
    27.     private void LateUpdate()
    28.     {
    29.         var cam = Camera.main; //get the main rendering camera... we're going to be positioning based off this
    30.    
    31.         var q = Quaternion.LookRotation(-this.TargetLocation.forward, this.TargetLocation.up);
    32.         var dq = Quaternion.Inverse(this.transform.rotation) * q;
    33.  
    34.         //update pos
    35.         var dv = cam.transform.position - this.transform.position;
    36.         dv = dq * dv;
    37.         this.PortalCamera.transform.position = this.TargetLocation.position + dv;
    38.    
    39.         //update rot
    40.         this.PortalCamera.transform.rotation = dq * cam.transform.rotation;
    41.  
    42.         //make sure camera projection matches
    43.         this.PortalCamera.projectionMatrix = cam.projectionMatrix;
    44.     }
    45.  
    46.     private void OnTriggerEnter(Collider other)
    47.     {
    48.         if (string.IsNullOrEmpty(this.TagMask) || other.CompareTag(this.TagMask))
    49.         {
    50.             var targ = other.transform;
    51.             float dotProduct = Vector3.Dot(transform.forward, targ.position - transform.position);
    52.             if (dotProduct > 0f)
    53.             {
    54.                 var q = Quaternion.LookRotation(-this.transform.forward, this.transform.up);
    55.                 var dq = Quaternion.Inverse(q) * this.TargetLocation.rotation;
    56.  
    57.                 //update position
    58.                 var dv = targ.position - this.transform.position;
    59.                 dv = dq * dv;
    60.                 targ.position = this.TargetLocation.position + dv;
    61.  
    62.                 //update rot
    63.                 targ.rotation = dq * targ.rotation;
    64.  
    65.                 UnityEngine.EventSystems.ExecuteEvents.Execute<ITeleportMessageReceiver>(targ.gameObject, null, (x, y) => x.Teleported(this));
    66.             }
    67.        
    68.         }
    69.     }
    70.  
    71.     #endregion
    72.  
    73.  
    74.     public interface ITeleportMessageReceiver : UnityEngine.EventSystems.IEventSystemHandler
    75.     {
    76.  
    77.         void Teleported(Portal portal);
    78.  
    79.     }
    80.  
    81.  
    82. }
    83.  
    Note - when I fixed the rotations, I made the portal's forward be the forward of its transform rather than up.

    I also scrapped your PortalTextureSetup, PortalCamera, and PortalTeleporter. Instead putting everything in the 'Portal' class I wrote.

    1) PortalTextureSetup, this was a generic adhoc class that you set globally. The way you had it, you'd have to have edit this PortalTextureSetup with every portal you added. And you'd have to have a adhoc version for every scene.

    2) PortalCamera - I could have left this a separate class from Portal. It's effectively the 'LateUpdate' portion of my Portal class. I just stuck it in one place, because a Portal will always have a PortalCamera. It's part of what makes it a Portal. You can't have one with out the other.

    I also made some major changes to how the portal is rendered... see the code.

    3) PortalTeleporter - basically became Portal, and was cleaned up.

    Also the MouseLook of the FirstPersonController is not directly compatible. You need to add a public method that allows updating the current rotations of MouseLook. This came from the stanard assets I presume... but yeah, it wasn't made with portals in mind.

    I use the ITelepoertMessageReceiver to do this, by attaching this script to the Player:
    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections.Generic;
    4.  
    5. public class FirstPersonTeleportReceiver : MonoBehaviour, Portal.ITeleportMessageReceiver
    6. {
    7.  
    8.     public UnityStandardAssets.Characters.FirstPerson.FirstPersonController Controller;
    9.  
    10.     void Portal.ITeleportMessageReceiver.Teleported(Portal portal)
    11.     {
    12.         this.Controller.MouseLook.Init(this.transform, Camera.main.transform);
    13.     }
    14. }
    15.  
    Note - this requires that m_MouseLook on FirstPersonController is uncovered with a public property.

    ...

    I tried sending you a pull request for a branch with my changes, but you have your github configured to block pull requests. (I get a permission denied error)
     
    Last edited: Mar 17, 2018
    Deeeds and LimeLava like this.
  10. LimeLava

    LimeLava

    Joined:
    Mar 16, 2018
    Posts:
    6
    Thanks for all your help, I appreciate it. I am pretty new to GitHub so I don't really know how to allow people to submit pull requests. If you could enlighten me as to how and then submit a pull request with your updates version I would appreciate it. Thanks for the help with my main problem though.
     
  11. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,378
    Sorry, my fault on the pull request.

    There you go. You should see it in your github.
     
  12. LimeLava

    LimeLava

    Joined:
    Mar 16, 2018
    Posts:
    6
    Thanks a lot. By the way, what object do I put the portal script on?
     
    Last edited: Mar 18, 2018