I am having trouble with my portal script

I am trying to make a working portal but I have been having trouble with it and have been trying to fix it for many hours. I am relatively new to this. Right now the problem I am having is that if I move too fast through the portal, it teleports me and then teleports me back. Here is my code:

void OnTriggerEnter(Collider other)
    {
        if (other.tag == "Player" && transform.tag == "CurrentPortalA")
        {
            Vector3 portalToPlayer = player.position - transform.position;

            float dotProduct = Vector3.Dot(transform.forward, portalToPlayer);

            if (dotProduct > 0f)
            {
                var q = Quaternion.LookRotation(-this.transform.forward, this.transform.up);
                var dq = Quaternion.Inverse(q) * this.portalDestination.rotation;

                //update position
                var dv = player.position - this.transform.position;
                dv = dq * dv;
                player.position = this.portalDestination.position + dv;

                //update rot
                player.rotation = dq * player.rotation;

                portalDestination.GetComponentInParent<DoorOpen>().OpenDoor();
            }
        }
    }

I was using a combination of a Brakeys tutorial, code from someone else’s forum post, and some of my one code. That is why some things, like variable names, may look somewhat inconsistent.

From your description, my first guess is that you’re stepping back into the portal.

To fix, I would add a cooldown to the portal. If the portal is in cooldown, don’t reposition/rotate the player.

1 Like

I did that and it worked! But then I did some work to clean up my code and it doesn’t work anymore for some reason. As far as I know I haven’t made any meaningful changes to the dot product section, but now sometimes, if I am to fast or to the side of the portal, I will get the incorrect dot product.

Here is my new code for the section:

void OnTriggerEnter(Collider other)
{
    if (other.tag != "Player" || gameManager.fromPortal != gameObject || gameManager.timer >= 0) return;

    Vector3 portalToPlayer = player.position - transform.position;

    float dotProduct = Vector3.Dot(transform.forward, portalToPlayer);

    if (dotProduct <= 0f) return;

    var q = Quaternion.LookRotation(-this.transform.forward, this.transform.up);
        var dq = Quaternion.Inverse(q) * this.portalDestination.rotation;

    // Update position

    var dv = player.position - this.transform.position;
    dv = dq * dv;
    player.position = this.portalDestination.position + dv;

    // Update rotation

    player.rotation = dq * player.rotation;

    // Change the target portal to the active portal and update the portal cooldown

    portalDestination.GetComponentInParent<DoorOpen>().OpenDoor();
    gameManager.portalTimer = gameManager.portalCooldownTime;
}

!< is not a C# operator

What should I use instead?

Are you trying to determine if it is “not less than 0”? If just less than zero you don’t need (and can’t have) the exclamation point. If less than or equal then if (dotProduct <= 0f) return;

A cooldown should not be necessary. However currently you don’t transform your players velocity which is probably the main cause of the issue. Your velocity is a world space direction and just teleporting the player to the other side does not magically change that direction. So if you have two portals on the same wall, when you enter one you have a velocity towards / into that wall. When you teleport yourself to the other side, you still have the same velocity and you would keep moving towards the wall and enter the portal you came out of. It gets worse when the portals are at a 90° angle as your forward velocity would suddenly push you sideways.

You forgot one of the most important aspects about portals: “Speedy thing goes in, speedy thing comes out”.

You should be able to use your dq offset rotation to rotate your players velocity, or any “portable” physics object for that matter in case you need that :slight_smile:

In case you want boring portals that really just teleports (like a StarTrek transporter), you may want to set the velocity to 0 when you teleport. Though depending on how you actually trigger the teleportation and where exactly you teleport to you may run into issues

I will try that, but the current portals that I am trying it with are facing the same direction, so the direction of velocity is probably the same, so that is probably not the problem.

When you say same direction, do you mean they literally face the same direction? In that case this is the issue. When they face in opposite directions, you would not be affected at all since your worldspace orientation would not change when you go through a portal. All you need to do is rotate the rigidbody’s velocity vector by your dq and you’re done.

Whoops, I meant opposite directions. I come out facing the same direction as I went in.

I think it will be a lot easier for you if you stop using the Quaternion and Dot functions here, and instead use the Transform.TransformX and Transform.InverseTransformX functions, as they’re a lot more intuitive. The relevant functions are TransformPoint/InverseTransformPoint, which transforms a point to/from world space from/to localSpace, and TransformDirection/InverseTransformDirection, which does the same thing with a direction.

As an example, TransformDirection could be used to preserve velocity or facing after going through the portal;

var playerRelativeFacing   = fromPortal.InverseTransformDirection(playerTransform.forward);
var playerRelativeVelocity = fromPortal.InverseTransformDirection(playerRigidbody.velocity);

// after teleporting the player to the other portal, we want to flip the vectors (into the old portal is out of the new portal), relative to the new portal;

playerTransform.forward  = toPortal.TransformDirection(-playerRelativeFacing);
playerRigidbody.velocity = toPortal.TransformDirection(-playerRelativeVelocity);

I feel like this is a lot more intuitive than dealing with the rotation values as your original script.

I have another bone to pick, which is this part from your code;

I don’t get this. Why do you only want the portal to work if you are facing it? Shouldn’t you be able to back into a portal? You also probably don’t want to move through the portal if you strafe past it while looking into it!

The correct approach is probably to check if the player’s current velocity will make it cross the plane that defines the portal’s entrance on the next frame, and then do the crossing only then. With the direction code I wrote above, you’d simply check if the player’s relative position has a positive z-value (is in front of the portal), and the player’s relative position + the player’s relative velocity has a z-value below 0 (will go behind the portal)

Brackeys said that the dot product is supposed to detect whether you are entering the portal from the correct side or not. Is that not what that does?

I fixed it. It turns out I accidentally messed up my cooldown script, but I fixed it and now it works like a charm. Thanks for all of the help! I will post here again if I have another problem, but so far it works.

It could be used for that but the details are what you feed in. See Wikipedia for what Dot gives you; it’s super-simple but super-useful.

If transform.forward was pointing out of the portal, that might tell if you walked into its “front,” depending on what you Dot() it with.

But if transform.forward is something else (like the player), then it explains what Baste pointed out above there: you’re considering the back / front of the player.

Scribbling a few vector arrows on paper coming out of a portal or out of a player can help immensely in visualizing the abstract thing you’re trying to pull off in code.

Then back up your assumptions with actual debugging because it only counts what the computer is actually doing, not what you think it is doing. :slight_smile:

Okay, so it did work how I thought it did. This script is attached to the portal, so transform.forward is the direction the portal is facing. I found the problem, it was somewhere else in my code.

1 Like

One must work through the six stages of debugging:

  1. DENIAL. That can’t happen.
  2. FRUSTRATION. That doesn’t happen on my machine.
  3. DISBELIEF. That shouldn’t happen.
  4. TESTING. Why does that happen?
  5. GOTCHA. Oh, I see.
  6. RELIEF. How did that ever work?

Go ahead and update your programmer character sheet to add +3 XP to debugging. :slight_smile: