Advertisement

#3 Adding a User Interface Camera

In this article we will focus on adding a new camera to our scene that will be reposible for only displaying our user interface. We will then need to adjust the culling mask of our main camera so that it does not display the UI. For everything to work correctly it will be very important for us to make sure that our UI camera is placed in the correct position and in order to do that we will create a small script and create our first custom editor in order to be able to interact with it.

Main Camera Culling

Depending on the type of game you are creating and/or the flexible you need or want it is possible that having a single camera would work just fine. By default the canvas that was added to our project had its render mode set to overlay which means our UI will always be painted on top of the scene so that it is always visible. In some instances we may want to allow the UI to be turned off, for instance to allow players to take screen shots, and we can accomplish this in several different ways. One way would be to have a mechanism that deactivates the canvas itself. This could cause a few issues for example we may have a UI manager script attached to the canvas and we may still want to be able to respond to user input using it which of course would not be possible until the canvas was reactivated. It also begs the question just where do we put the functionality for enabling and disabling the canvas. Instead of doing this we will add an additional camera to our scene that will only display the UI and nothing else.

The first thing we are going to do is to adjust the culling mask of our main camera so that any game objects that we assign to the UI layer will not be displayed by the main camera (a).

Adjusting the main camera culling mask so that it does not display anything in the UI layer.
(a) Adjusting the main camera culling mask so that it does not display anything in the UI layer.

Adding a UI Camera

Next up will just need to add another camera that we will use to just display our user interface (b). While we are at it we will also adjust its culling mask to only display our UI layer. We will be adding this camera as a child of our canvas.

Do not forget to remove the audio listener component from the UI camera otherwise Unity will spam you with warnings in the console since there should only be one audio listener per scene.

alt
(b) Adjusting the ui camera culling mask so that it only shows objects that are in the UI layer.

Assigning our UI camera

With our UI camera created and set up we need to change the render mode of our canvas from screen space overlay to screen space camera and assign our UI camera to it (c).

Converting the canvas render mode to screen space camera and assigning our UI camera to it.
(c) Converting the canvas render mode to screen space camera and assigning our UI camera to it.

With that done if we play our scene there is a very good chance that we will not be able to see our panel that we have been clicking on in our past articles. The problem is that the camera we have added has its position set at (0, 0, 0) which places it right on top of our canvas and we need it positioned away from it so that the canvas is visible.

Positioning our UI camera

  • Assets
    • UserInterface
      • UiCameraPositioner.cs

Making sure our camera is positioned the correct distance from the canvas is very important and a rather tedious thing to do if you want to do it by hand. Luckily we are programmers and we can write some code that will take of doing that for us. First up is to create a new script that we will be adding to our UI camera (d).

UiCameraPositioner.cs

using UnityEngine;

namespace UserInterface
{
    public sealed class UiCameraPositioner : MonoBehaviour
    {
        public void SetCameraPosition()
        {
            Debug.Log("positioning the ui camera");
        }
    }
}
(d) Our initial script for positioning our UI camera at the correct distance from the canvas.
Advertisement

We need to be able to call that method

  • Assets
    • UserInterface
      • Editor
        • UiCameraPositionerEditor.cs

In order for us to be able to call the method that we created on our positioner script we will create our first custom editor (e). The only thing we really need from this editor is for it to have a button that we can press to run the calculations on a positioner script.

UiCameraPositionerEditor.cs

using UnityEditor;
using UnityEngine;

namespace UserInterface.Editor
{
    [CustomEditor(typeof(UiCameraPositioner))]
    public class UiCameraPositionerEditor : UnityEditor.Editor
    {
        private UiCameraPositioner Target => (UiCameraPositioner) target;

        public override void OnInspectorGUI()
        {
            if (GUILayout.Button("Set Position"))
            {
                Target.SetCameraPosition();
            }
        }
    }
}
(e) Custom editor for our positioner script that will create a button so that we can invoke our set position method.

We need a reference to something

  • Assets
    • Utilities
      • ComponentUtilities.cs

In order to perform the calculations that we need to do we will need a reference to the canvas element. Remembering that when we added the UI camera to our scene we placed it as a child of the canvas and that I prefer to do as little configuration as possible we need a way to find the canvas without setting it the inspector. To do this we will add a recursive method to our component utilities (f) that will search through the ancestors of a component until it finds one of the specified type.

ComponentUtilities.cs

using System;
...
namespace Utilities
{
    public static class ComponentUtilities
    {
        public static T FindFirstComponentInAncestor<T>(this Transform transform,
            Action<string> tracing = null)
            where T : Component
        {
            if (tracing == null)
            {
                tracing = Debug.LogError;
            }

            var name = transform.name;

            while (transform.parent != null)
            {
                var component = transform.parent.GetComponent<T>();
                if (component != null)
                {
                    return component;
                }

                transform = transform.parent;
            }

            tracing($"[{name}] The ancestors of {name} do not contain a {typeof(T).Name} component.");
            return null;
        }
        ...
    }
}
(f) Recursive method that will search through the ancestors of a component until it finds a component of the specified type.

Where are you canvas?

  • Assets
    • UserInterface
      • UiCameraPositioner.cs

With our newly created find in ancestor method we are now in a position to get a reference to our canvas (g). For testing we will just log it to our console.

UiCameraPositioner.cs

...
using Utilities;

namespace UserInterface
{
    public sealed class UiCameraPositioner : MonoBehaviour
    {
        private Canvas _canvas;

        private Canvas Canvas
        {
            get
            {
                if (_canvas == null)
                {
                    _canvas = transform.FindFirstComponentInAncestor<Canvas>();
                }

                return _canvas;
            }
        }

        public void SetCameraPosition()
        {
            Debug.Log(Canvas);
        }
    }
}
(g) Updating our positioner script to make use of our find in ancestor method.

Time for math

  • Assets
    • UserInterface
      • UiCameraPositioner.cs

All of the previous preparation was done so that we can perform some simple geometry to find the correct distance from the canvas to place the camera (h). Of course if we needed to or were so inclined we could make this more general by adding some configuration options to our editor script. But for now this will work for our project.

UiCameraPositioner.cs

using UnityEngine;
using Utilities;

namespace UserInterface
{
    public sealed class UiCameraPositioner : MonoBehaviour
    {
        ...
        public void SetCameraPosition()
        {
            var canvasHalfHeight = 0.5f * ((RectTransform) Canvas.transform).rect.height;
            var cameraHalfFieldOfView = 0.5f * Canvas.worldCamera.fieldOfView;
            var distance = -canvasHalfHeight / Mathf.Tan(cameraHalfFieldOfView * Mathf.Deg2Rad);
            transform.localPosition = new Vector3(0, 0, distance);
        }
    }
}
(h) Time to do some math to determine what the correct distance from the canvas is and placing our camera at that locations.

Automatic is good

  • Assets
    • UserInterface
      • UiCameraPositioner.cs

Making sure that the camera is at the correct position whenever we play our scene is important enough that it would probably be good to run our positioning code automatically. To do this we will just make a call to our method within our positioning script's awake life cycle hook (i).

UiCameraPositioner.cs

...
namespace UserInterface
{
    public sealed class UiCameraPositioner : MonoBehaviour
    {
        ...
        private void Awake()
        {
            SetCameraPosition();
        }
    }
}
(i) Updating our positioner script so that we make sure that the camera is in the correct location whenever our scene is played.
Exciton Interactive LLC
Advertisement