Endless Unity Camera Tricks


In our game currently under development called Endless Elevator I decided to add a new feature of more depth.  The game is 2.5D and mostly sits in a very shallow Z axis, a limited X axis, and an endless Y axis. As the name suggests your character is inside a never ending building trying to kill or avoid the enemies by escaping up elevators and escalators. The mock-up of a level below in the Scene view of Unity gives you the picture.  

Endless Elevator Opening Level Spawn

The player view is a much smaller slice of this level…about this much:

Roughly how much the camera views

I had the idea that I wanted to extend this playing field into a deeper third dimension where the character could walk down a hallway,  away from the camera, and seemingly deeper into the building.  Instead of the camera following the character down the hall on the Z axis (as it follows him on the X and Y) I wanted to pan around the edge of the building and take up the character again on the new parallel so that it looks like the camera is turning Ninety degrees and that we are looking at a new side of the building.  

Have a look at the .gif below and I’ll try and explain that better. The top half of the image is the Scene view and the bottom half is what the camera (and the player) sees in the game.  In the top half  Scene view you can see my character in green and highlighted by the handler arrows. He scoots around a bit and then disappears down the hall. When he gets to a set depth it triggers the camera to move in the game window below and you will see the levels reconfigure into a new building face (note the elevators and escalators and doors will be in different positions).

On the bottom half of the .gif that shows what the player sees it looks like once the character disappears down the hall the camera pans right, looks at the edge wall of the building as it goes around the corner in a Ninety degree turn, and then follows the character on the new level again.

(Watch the top half for a bit then the bottom half)

You can see in the Scene view we are not really moving anything with the building. It just recreates the levels. The camera is doing all the work.  It’s not perfect yet and without any background around the building to relate the movement to it’s a bit hard to tell if we are turning Ninety or One Hundred and Eighty degrees on that camera flip but it’s getting there.

It took a while to work out how to do this and I tried several different methods but this is the basic logic of the camera move script that is attached to the character.

  1. The movement of the camera is triggered when the character moves past a certain point on the Z axis.
  2. Stop the regular character and camera movement functions by disabling that scripted behaviour on each object.
  3. I set up an empty game object called the CameraLookAtPoint that hovers at the end of the building on the far Right of the X axis.
  4. Pan the camera Right toward the CameraLookAtPoint.
  5. When the camera gets to within a certain distance to the LookAtPoint it starts to rotate towards it.
  6. The camera moves around the LookAtPoint so that it faces that end wall of the building as it turns.
  7. At this point while the only thing the camera can see is that blank edge wall of the building I destroy the old level we just walked out of down the hall and create a new randomly generated level.
  8. This is the great illusion! The camera is then moved instantly to the far Left of the building (the opposite end) and it appears as if we have just turned a corner.
  9. Lastly the camera picks up the character again and we hand control back to the normal camera and character movement scripts.
This is where the CameraLookAtPoint sits that the camera rotates around  Ninety degrees as it gets to the edge of the building.

I’ll post the whole script below with comments but I’ll walk through the interesting bits here. 

To start off with I needed to grab references to several external elements like the camera, the level instantiating script, the character controller script, and the characters Rigidbody. (I needed the rb because when the levels were destroyed and recreated gravity would take hold between them and the character would fall into the endless abyss!)

I had to generate a series of “if” conditionals and Boolean flags to control the movements of the camera. This was surprisingly hard to get right. It’s often not intuitive when you are in the Update function what the looping iterations will do with your code but this allowed me to slow things down and get control back. 

The calls to the instantiateScene1 script were needed to copy variables used there in the main flow of the game to track what level we were on and how high up the building we had climbed. That way I could decouple that mechanism from this one and happily destroy levels and recreate them without interrupting the flow of the rest of the game.

 


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

public class SpinLevelDownHall : MonoBehaviour {

    public GameObject cameraLookAtPoint;
    public bool triggered;
    public bool cleanAndRebuild;
    public bool moveRightEnd;
    public bool moveRightEndBack;
    public bool moveLeftEnd;
    public bool movebacktoCharacter;
    public int levelHolder;
    public float cleanUpHeightHolder;
    public bool firstRunHolder;
    public Vector3 target_camera_Position;
    private GameObject the_camera;
    private RigidbodyCharacter move_script;
    private CameraFollow cameraFollow_script;
    private InstantiateScene1 instantiate_script;
    private Rigidbody rb;

    // Use this for initialization
    void Start () {
        the_camera = GameObject.FindGameObjectWithTag("MainCamera");
        move_script = GetComponent();
        cameraFollow_script = the_camera.GetComponent();
        instantiate_script = GetComponent();
        rb = GetComponent();
    }
	
	// Update is called once per frame
	void Update () {
	
        if (transform.position.z > 15)
        {
            triggered = true;
            moveRightEnd = true;
            cleanAndRebuild = false;

            if (triggered)
            {
                // Stop the character and the camera moving
                move_script.enabled = false;
                cameraFollow_script.enabled = false;

                if (moveRightEnd)
                {
// Set the target camera position on the x axis at the far right of the building
Vector3 target_camera_Position = new Vector3(78f, the_camera.transform.position.y, the_camera.transform.position.z);

// Set the camera look at point on the y axis (so it's on the same level as the player)
cameraLookAtPoint.transform.position = new Vector3(cameraLookAtPoint.transform.position.x, transform.position.y + 4f, cameraLookAtPoint.transform.position.z);

// start moving the camera
the_camera.transform.position = Vector3.MoveTowards(the_camera.transform.position, target_camera_Position, 50 * Time.deltaTime);

// When you get close to the end start swinging the camera around to look at the wall
if (the_camera.transform.position.x > cameraLookAtPoint.transform.position.x - 10)
                    {                        the_camera.transform.LookAt(cameraLookAtPoint.transform);
                    }
// When you get really close to the first position move the camera beyond the wall to the side
if (the_camera.transform.position.x > target_camera_Position.x - 0.5f)
 {
target_camera_Position = new Vector3(78f, the_camera.transform.position.y, 4f);  // 4f is perfect when the camera is at -90 deg
                        moveRightEnd = false;
                        moveRightEndBack = true;
                        cleanAndRebuild = true;

                        if (moveRightEndBack)
                        {
the_camera.transform.position = Vector3.MoveTowards(the_camera.transform.position, target_camera_Position, 50 * Time.deltaTime);

// When you get really REALLY close to the second position move the camera to the negative X Axis side
 if (the_camera.transform.position.z > target_camera_Position.z - 0.2f)
                            {
                                moveLeftEnd = true;
                                if (moveLeftEnd)
                                {
target_camera_Position = new Vector3(-78f, the_camera.transform.position.y, 4f);  // The other side
the_camera.transform.position = target_camera_Position;  // snap move the camera
                                    the_camera.transform.LookAt(cameraLookAtPoint.transform);
                                    moveRightEndBack = false;
                                    moveLeftEnd = false;
                                    movebacktoCharacter = true;
                                }
                            }
                        }
                    }
                }

                if (cleanAndRebuild)
                {
                    // Call cleanup on everything
                    rb.useGravity = false;  // so the character doesn't fall through the floor

                    //cleanUp;
                    cleanUpHeightHolder = instantiate_script.floorCntr;
                    cleanUpHeightHolder = cleanUpHeightHolder * 8;  // so it cleans up all the floors
                    firstRunHolder = instantiate_script.firstRun;
                    instantiate_script.cleanUp(cleanUpHeightHolder, firstRunHolder);  // cleanup height is usually two levels below the character - we are raising it to two levels above him
                   //then make three levels
                    levelHolder = Mathf.RoundToInt(instantiate_script.player_level);
                    instantiate_script.makeLevel(levelHolder);
                    instantiate_script.makeLevel(levelHolder + 8);
                    instantiate_script.makeLevel(levelHolder + 16);
                    cleanAndRebuild = false;
                }                
                triggered = false;  // makes it only run once
            }
        }

        if (movebacktoCharacter)
        {
            // new position is back to the character
            // Start breaking and making your new levels in here
            // first move the character
            transform.position = new Vector3(transform.position.x, transform.position.y, -4);


            target_camera_Position = new Vector3(transform.position.x, transform.position.y + 4.9f, -20.51f);  // the starting camera position
            the_camera.transform.position = Vector3.MoveTowards(the_camera.transform.position, target_camera_Position, 50 * Time.deltaTime);
            the_camera.transform.LookAt(transform);
            // Once again when you get close to your original camera position disable and enable normal camera tracking again
        
           if (the_camera.transform.position.x > transform.position.x - 0.02f)
            {
                movebacktoCharacter = false;
                rb.useGravity = true;
                move_script.enabled = true;
                cameraFollow_script.enabled = true;
            }
        }
    }
}

It’s not the prettiest code, and I admit to hacking my way through it, but it works.  Maybe you have a better method for doing something similar – if you do please feel free to add a comment – I’d like to hear it.

Zulu Out.

,

Leave a Reply

Your email address will not be published. Required fields are marked *