BASIC TELEPORTING QUEST: ‘Teleport Player’ - Solutions

Feel free to share your solutions, ideas and creations below. If you get stuck, you can find some ideas here for completing this challenge.

This was a cool challenge that I went a little crazy with!

First things first, I wanted the player to land directly on the teleporter (not in the middle of the object), so I had to get the extents of the mesh renderer, and add an offset to the target destination so the player would sit snuggly ontop of the target platform.

Next!
I wanted the player to actually face in the direction that the platform was facing, in the current iteration (with just moving the player to the target position) the player would be facing whatever direction they went into the portal with, but I wanted the player to always be directed to the next section, so I had to set the rotation of the player.
The problem with this is that the players rotation is constantly being updated every frame in the first person controller script, so this needed some tweaking. I needed to send my new rotation (collected from the target transform) and send that to the first person controller script in order to update the value that was being operated on every frame.

Finally, I wanted some portal fun, so I stored the player velocity going into the portal, then when then player teleported, I would re-apply their velocity in reverse! Now we’re thinking in portals!

1 Like

I like your thinking! Much more advanced than my simple “plop them somewhere else”!

Quick side question for you, I broke this discussion forum the other day and only just fixed it… have you been trying to use it before today for Challenge Club discussion, or did you only just jump on now to add these couple of posts?

1 Like

Hi Rick!
I confess, I didn’t sign up right away because I’ve been meddling with C++, so I’ve only just posted. I’ve been trying to be active on the discord though :slight_smile:

2 Likes

Really cool stuff ! I would love to have a look at your code if you don’t mind sharing !

So here is what I changed / added for this challenge :slight_smile:

 void OnTriggerEnter(Collider other) 
    {
        TeleportPlayer();
        // Challenge 3: DeactivateObject();
        // Challenge 4: IlluminateArea();
        // Challenge 5: StartCoroutine ("BlinkWorldLight");
        // Challenge 6: TeleportPlayerRandom();
    }

void TeleportPlayer()
    {
        player.transform.position = teleportTarget.transform.position;
    }

Also, in the second set of teleporters, you have to make sure that everything is linked properly :wink: !

2 Likes

I can share! :slight_smile: I’ll mark it as a spoiler:

    void TeleportPlayer()
    {
        var targetRenderer = teleportTarget.GetComponent<Renderer>();
        var lookController = FindObjectOfType<FirstPersonLook>();
        var playerRigidbody = player.GetComponent<Rigidbody>();

        // Store player velocity
        Vector3 storedVelocity = playerRigidbody.velocity;
        
        // Set the target teleport position on the boundary of the target
        Vector3 newPosition = new Vector3(teleportTarget.position.x, 
            teleportTarget.position.y + targetRenderer.bounds.extents.y, 
            teleportTarget.position.z);

        // Teleport
        player.transform.SetPositionAndRotation(newPosition, teleportTarget.localRotation);

        // Setting the camera rotation to match the new target rotation
        lookController.UpdateRotation(teleportTarget.eulerAngles.y);

        // Reflecting the player velocity back
        playerRigidbody.velocity = storedVelocity * -1;
    }

Note: I’ve also added a new function in the FirstPersonLook script which sets the camera rotation, but i’ll leave that as a mystery because it was a good learning experience that took me about an hour :stuck_out_tongue:

In hindsight the extents code was unnecessary because the box collider on the teleport platforms is a trigger, so i didn’t need to adjust for the height of the platform, but I think the concept is solid :slight_smile:

1 Like

Teleport logic:
Stepping on the platform for the first time , the player will be teleported inside the house. The second Platform will bring the player back to the starting location.

After the first teleport:
I am not deactivating the platform, instead the player will be teleported to a new random chosen (predefined list) location.

    [SerializeField] Transform teleportTarget;
    [SerializeField] Vector3 randomTeleportPosition;
    [SerializeField] bool canTeleport;

    [SerializeField] private Transform[] teleportPrefab;
    [SerializeField] private List<Transform> teleportTargets;
    private int teleportIndex;

Initializing the list and finding our possible spawn locations.

private void Awake()
{
    teleportTargets = new List<Transform>();
    canTeleport = true;
    GetListOfSpawnLocations();
}

The player steps on the platform and gets teleported inside the house.

    private void OnTriggerEnter(Collider other) 
    {
        if (other.CompareTag("Player") && canTeleport)
        {
            TeleportPlayer(other);
            IlluminateArea();
            StartCoroutine ("BlinkWorldLight");
            //DeactivateObject();
            canTeleport = false;
        }

If the player steps on the platform after the first teleport, he will be teleported to a random location.

    else
    {
        TeleportPlayerRandom();
        canTeleport = true;
    }
}
    private void TeleportPlayer(Collider other)
    {
        // The endPos can be set via Inspector
        other.transform.position = teleportTarget.position;
    }

Could add serialized variables for the “amountToBlink” and “durationToBlink”

IEnumerator BlinkWorldLight()

{
    var amountToBlink = 12;
    mainWorldLight.enabled = false;

    for (var i = 0; i < amountToBlink; i++)
    {
        mainWorldLight.enabled = true;
        yield return new WaitForSeconds(.1f);
        mainWorldLight.enabled = false;
        yield return new WaitForSeconds(.05f);
    }
}

We are keeping track of all possible spawnlocations inside the inspector.

    private Vector3 GetListOfSpawnLocations()
    {
        foreach (var t in teleportPrefab)
        {
            teleportTargets.Add(t);
        }

        teleportIndex = Random.Range(0, teleportTargets.Count);
        return randomTeleportPosition = teleportTargets[teleportIndex].position;
    }

The location, chosen from predefined list, will change everytime on teleporting.

private void TeleportPlayerRandom()
{
    player.transform.position = GetListOfSpawnLocations();
    Debug.Log("Teleporting to "+ transform.position);
    canTeleport = false;
}

Thanks for the solution, I’ve been playing around with the code for around 3h and I just cant seem to make it work. Whats that special function you added to the script? I’ve tried adding a “LookAt” + look target and it doesn’t do anything.

My take on it

 [SerializeField] bool hasTriggered = false;

    void Start() 
    {
        // CHALLENGE TIP: Make sure all relevant lights are turned off until you need them on
        areaLight.enabled = false;
        mainWorldLight.enabled = false;
        // because, you know, that would look cool.
    }

    void OnTriggerEnter(Collider other) 
    {
        // Challenge 2: TeleportPlayer();
        TeleportPlayer(other);
        // Challenge 3: DeactivateObject();
        // Challenge 4: IlluminateArea();
        // Challenge 5: StartCoroutine ("BlinkWorldLight");
        // Challenge 6: TeleportPlayerRandom();
    }

    void TeleportPlayer(Collider other)
    {
        if(player || hasTriggered == false)
        {
            player.transform.position = teleportTarget.position;
            areaLight.enabled = true;
            DeactivateObject();
        }
    }

    void DeactivateObject()
    {
       hasTriggered = true; 
    }

I changed my code a bit for this task, and I hope that it won’t come back and bite me in later task.
What I did is to change the Teleport class to be like this

{
    [SerializeField] private Transform[] teleportTarget;
    public Transform[] TeleportTarget
    {
        get
        {
            return teleportTarget;
        }
    }
    [SerializeField] Light[] areaLight;
    [SerializeField] GameObject player;
}

So it won’t sit on both teleport pads. Now it’s on the Gameplay game object.

I created a new class named TeleportPlayer and it looks like this

public class TeleportPlayer : MonoBehaviour
{
    [SerializeField] Teleport teleportPad;
    [SerializeField] float waitFloat = 0.2f;
    private WaitForSecondsRealtime waitTime;

    private void Awake()
    {
        waitTime = new WaitForSecondsRealtime(waitFloat);
    }
    void OnTriggerEnter(Collider other)
    {
        // Challenge 2:
        StartCoroutine(TeleportPlayerToRand(other));
    }
    IEnumerator TeleportPlayerToRand(Collider other)
    {
        GameObject player = other.gameObject;
        if (player.CompareTag("Player"))
        {
            int randIndex = Random.Range(0, teleportPad.TeleportTarget.Length);
            yield return waitTime;
            player.transform.position = teleportPad.TeleportTarget[randIndex].position;
        }
    }
}

What I did is to do a Coroutine and set a small delay between the teleport phases, and the player is moving to random teleport pads.

What do you guys think? Is it ok?

Seems Ok if you only want to Teleport to Random Platforms (which I think is actually a later Challenge).
However if you want to make a game where A goes to B and C goes to D etc to complete a course then what you’ve done wont work or it will be pot luck and possibly frustrating for the player.

Code wise i’d do the player check in the TriggerEnter and only start the coroutine then which saves passing the collider thru etc

I found doing the quests that I’d read all the challenges and see if a game would form in my mind while hitting most of the challenges and that would effect how I coded the quest and challenges as a whole

1 Like