Gameplay.Portal.cs - robblofield/TomeboundDocs GitHub Wiki

Gameplay.Portal.cs

Author: Youngju Yun
Last Updated: 03/01/2024

Overview

The script for the Portal GameObject which is instantiated by the player character in Action_Abilites. It takes part in the "Gameplay".

Dependencies

  • PortalStart.prehab
  • PortalExit.prehab

Contents

  • Use case
  • Breakdown of code
    • Using Statements
    • Variables
    • Start()
    • Update()
    • MoveCursor()
    • EndAbilityMode()
    • ActivateAbility()
  • Future Expansion
  • Full Code Reference

Use case

It's usually used when the PortalStart.prehab and PortalExit.prehab are instantiated on behalf of the portal spawn ability of the player character.

Breakdown of Code

Using Statements

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

Variables

    public bool isStart;
    public GameObject otherPortal;
    private GameObject hitObject;
    private bool isObjectFalling;
    private float groundPosY;

    //Lava Interaction Tiles
    private GameObject[] lavaTiles;
    private GameObject lavaTileBelow;

bool isStart: A boolean should be set to true for PortalStart.prehab and false for PortalExit.prehab.

GameObject otherPortal: A GameObject reference to the other portal type. For example, PortalExit.prehab is the otherPortal for the PortalStart.prehab.

GameObject hitObject: A local reference to the movable object that is transported through portals

bool isObjectFalling: A boolean which is set to true when the object is falling because of portals

float groundPosY: A local value of the object's original y global position before it falls.

GameObject[] lavaTiles: An array which stores all GameObject data of lava tiles in the Scene.

GameObject lavaTileBelow: A reference for the lava tile below the PortalExit.prehab.

Start()

    void Start()
    {
        if(gameObject.name == "PortalStart(Clone)"){
            isStart = true;
        }else{
            otherPortal = GameObject.Find("PortalStart(Clone)");
            otherPortal.GetComponent<Portal>().otherPortal = gameObject;
        }

        if(!isStart){
            checkLavaTileBelow();
        }
    }

The function runs once at the start of the program.

If the name of the object is the same as the PortalStart.prehab, isStart is set to true.

If not, it searches for the GameObject PortalStart as otherPortal, then it sets other PortalStart's otherPortal variable to itself.

Also, PortalExit has to run a function checkLavaTileBelow() which will be explained later in the document.

Update()

        if(isStart && otherPortal != null){
            if(checkObjectColliding() && !isObjectFalling){
                StartCoroutine(transportObject());
            }
        }

        Debug.DrawRay(transform.position, Vector3.forward * 1.25f, Color.green);
        Debug.DrawRay(transform.position, Vector3.back * 1.25f, Color.green);
        Debug.DrawRay(transform.position, Vector3.right * 1.25f, Color.green);
        Debug.DrawRay(transform.position, Vector3.left * 1.25f, Color.green);

A method runs once every frame to start transporting an object and draw a debug ray of checkObjectColliding().

If both Portals have instantiated and checkObjectColliding() is true and the object hasn't interacted yet, it starts subroutine transportObject().

checkObjectColliding()

   private bool checkObjectColliding(){
        RaycastHit hit = new RaycastHit();
		Debug.DrawRay(transform.GetChild(0).position, Vector3.up * 1.25f, Color.green);
		if (Physics.Raycast(transform.GetChild(0).position, Vector3.up, out hit, 1.25f) || Physics.Raycast(transform.GetChild(0).position, Vector3.down, out hit, 1.25f))
		{
			Debug.DrawRay(transform.GetChild(0).position, Vector3.up * 1.25f, Color.red);
			if (hit.collider.tag == "Movable" || hit.collider.tag == "Block")
			{
				Debug.DrawRay(transform.GetChild(0).position, Vector3.up * 1.25f, Color.blue);
                hitObject = hit.collider.gameObject;
				return true;
			}
		}
		return false;
    }

This function casts a ray in 2 directions, up and down to look for the GameObject with the tag "Movable" or "Block".

It returns true if the movable object is found and the hitObject variable is set as the object found.

checkLavaTileBelow()

    private bool checkLavaTileBelow(){
        lavaTiles = GameObject.FindGameObjectsWithTag("Lava");
        foreach(GameObject tile in lavaTiles){
            if((tile.transform.position.x + tile.transform.position.z) == (transform.position.x + transform.position.z)){
                lavaTileBelow = tile;
                Debug.Log("Lava Tile is below the Exit Portal!");
                return true;
            }
        }
        Debug.Log("Lava Tile is not below the Exit Portal!");
        return false;
        
    }

A method to run by PortalExit.prehab to look for a lava tile below the portal.

Firstly, it finds all the Lava tile GameObjects in the Scene.

Then inside of the foreach loop, if x and z global position of one of the lava tiles with that of the portal, it means the lava tile is below, therefore it returns true.

transportObject()

    private IEnumerator transportObject(){
        float maxLoops = 10f; //Failsafe for infinite while loop
        float i = 0;
        isObjectFalling = true;
        //The object fall through the ground
        hitObject.GetComponent<Collider>().isTrigger = true;
        hitObject.GetComponent<Rigidbody>().useGravity = true;
        hitObject.GetComponent<Rigidbody>().isKinematic = false;
        groundPosY = hitObject.transform.position.y;
        while(hitObject.transform.position.y > -hitObject.transform.localScale.y && i < maxLoops){
            Debug.Log(hitObject+"is falling!");
            yield return null;
            i += Time.deltaTime;
        }
        i = 0;
        //After the object completely clip through the ground it is transported to the next floor
        hitObject.transform.position = otherPortal.transform.GetChild(0).position - Vector3.up * hitObject.transform.localScale.y;
        if(!otherPortal.GetComponent<Portal>().checkLavaTileBelow()){
            hitObject.GetComponent<Collider>().isTrigger = false;
            while(hitObject.transform.position.y > groundPosY && i < maxLoops){
                Debug.Log(hitObject+"is falling!");
                yield return null;
                i += Time.deltaTime;
            }
            hitObject.transform.position = new Vector3(hitObject.transform.position.x, groundPosY, hitObject.transform.position.z);
            hitObject.GetComponent<Rigidbody>().useGravity = false;
            hitObject.GetComponent<Rigidbody>().isKinematic = true;
        }
        isObjectFalling = false;
    }

A subroutine that transports the movable object from PortalStart to PortalExit on the next floor.

transportObject() Initialization

        float maxLoops = 10f; //Failsafe for infinite while loop
        float i = 0;

float maxLoops functions as the failsafe to prevent infinite while loop.

transportObject() The Object Falls Through the Ground

        isObjectFalling = true;
        //The object fall through the ground
        hitObject.GetComponent<Collider>().isTrigger = true;
        hitObject.GetComponent<Rigidbody>().useGravity = true;
        hitObject.GetComponent<Rigidbody>().isKinematic = false;
        groundPosY = hitObject.transform.position.y;
        while(hitObject.transform.position.y > -hitObject.transform.localScale.y && i < maxLoops){
            Debug.Log(hitObject+"is falling!");
            yield return null;
            i += Time.deltaTime;
        }
        i = 0;

In this section, the hitObject's Collider and Rigidbody settings are set to fall through other colliders so it could look like the object fell into the portal.

Then it uses a while loop to wait for every part of the object to completely fall through the ground or after 10 seconds have passed.

the index i will return to 0 after the loop.

transportObject() Transport the Falling Object to the PortalExit

        hitObject.transform.position = otherPortal.transform.GetChild(0).position - Vector3.up * hitObject.transform.localScale.y;
        if(!otherPortal.GetComponent<Portal>().checkLavaTileBelow()){
            hitObject.GetComponent<Collider>().isTrigger = false;
            while(hitObject.transform.position.y > groundPosY && i < maxLoops){
                Debug.Log(hitObject+"is falling!");
                yield return null;
                i += Time.deltaTime;
            }
            hitObject.transform.position = new Vector3(hitObject.transform.position.x, groundPosY, hitObject.transform.position.z);
            hitObject.GetComponent<Rigidbody>().useGravity = false;
            hitObject.GetComponent<Rigidbody>().isKinematic = true;
        }
        isObjectFalling = false;
    }

After the first while loop, the falling object is transported to the top of the PortalExit.

If PortalExit has found no lava tile below, The collider of the object no longer clips through colliders.

Then there goes a second while loop to wait until the y position of the falling object has reached the variable groundPosY or after 10 seconds.

when the loop ends, the y position object is set to the groundPosY and its Rigidbody is set to simulate gravity for the object to stop falling.

Finally, a boolean isObjectFalling is set to false because the object no longer falls.

Future Expansion

In this current build, the visual changes are set to be minimal on purpose to focus on mechanics. So as further expansion, variables or functions to handle animation and effects of the Portal will be added in the Portal.cs.

Full Code Reference

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Portal : MonoBehaviour
{
    public bool isStart;
    public GameObject otherPortal;
    private GameObject hitObject;
    private bool isObjectFalling;
    private float groundPosY;

    //Lava Interaction Tiles
    private GameObject[] lavaTiles;
    private GameObject lavaTileBelow;
    // Start is called before the first frame update
    void Start()
    {
        if(gameObject.name == "PortalStart(Clone)"){
            isStart = true;
        }else{
            otherPortal = GameObject.Find("PortalStart(Clone)");
            otherPortal.GetComponent<Portal>().otherPortal = gameObject;
        }

        if(!isStart){
            checkLavaTileBelow();
        }
    }

    // Update is called once per frame
    void Update()
    {
        if(isStart && otherPortal != null){
            if(checkObjectColliding() && !isObjectFalling){
                StartCoroutine(transportObject());
            }
        }

        Debug.DrawRay(transform.position, Vector3.forward * 1.25f, Color.green);
        Debug.DrawRay(transform.position, Vector3.back * 1.25f, Color.green);
        Debug.DrawRay(transform.position, Vector3.right * 1.25f, Color.green);
        Debug.DrawRay(transform.position, Vector3.left * 1.25f, Color.green);
    }
    
    private bool checkObjectColliding(){
        RaycastHit hit = new RaycastHit();
		Debug.DrawRay(transform.GetChild(0).position, Vector3.up * 1.25f, Color.green);
		if (Physics.Raycast(transform.GetChild(0).position, Vector3.up, out hit, 1.25f) || Physics.Raycast(transform.GetChild(0).position, Vector3.down, out hit, 1.25f))
		{
			Debug.DrawRay(transform.GetChild(0).position, Vector3.up * 1.25f, Color.red);
			if (hit.collider.tag == "Movable" || hit.collider.tag == "Block")
			{
				Debug.DrawRay(transform.GetChild(0).position, Vector3.up * 1.25f, Color.blue);
                hitObject = hit.collider.gameObject;
				return true;
			}
		}
		return false;
    }

    private bool checkLavaTileBelow(){
        lavaTiles = GameObject.FindGameObjectsWithTag("Lava");
        foreach(GameObject tile in lavaTiles){
            if((tile.transform.position.x + tile.transform.position.z) == (transform.position.x + transform.position.z)){
                lavaTileBelow = tile;
                Debug.Log("Lava Tile is below the Exit Portal!");
                return true;
            }
        }
        Debug.Log("Lava Tile is not below the Exit Portal!");
        return false;
        
    }

    private IEnumerator transportObject(){
        float maxLoops = 10f; //Failsafe for infinite while loop
        float i = 0;
        isObjectFalling = true;
        //The object fall through the ground
        hitObject.GetComponent<Collider>().isTrigger = true;
        hitObject.GetComponent<Rigidbody>().useGravity = true;
        hitObject.GetComponent<Rigidbody>().isKinematic = false;
        groundPosY = hitObject.transform.position.y;
        while(hitObject.transform.position.y > -hitObject.transform.localScale.y && i < maxLoops){
            Debug.Log(hitObject+"is falling!");
            yield return null;
            i += Time.deltaTime;
        }
        i = 0;
        //After the object completely clip through the ground it is transported to the next floor
        hitObject.transform.position = otherPortal.transform.GetChild(0).position - Vector3.up * hitObject.transform.localScale.y;
        if(!otherPortal.GetComponent<Portal>().checkLavaTileBelow()){
            hitObject.GetComponent<Collider>().isTrigger = false;
            while(hitObject.transform.position.y > groundPosY && i < maxLoops){
                Debug.Log(hitObject+"is falling!");
                yield return null;
                i += Time.deltaTime;
            }
            hitObject.transform.position = new Vector3(hitObject.transform.position.x, groundPosY, hitObject.transform.position.z);
            hitObject.GetComponent<Rigidbody>().useGravity = false;
            hitObject.GetComponent<Rigidbody>().isKinematic = true;
        }
        isObjectFalling = false;
    }

}
⚠️ **GitHub.com Fallback** ⚠️