#3 Adding a User Interface Camera
Saturday, May 25, 2019
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.
Parts
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).
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.
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).
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
- UserInterface
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");
}
}
}
We need to be able to call that method
- Assets
- UserInterface
- Editor
- UiCameraPositionerEditor.cs
- Editor
- UserInterface
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();
}
}
}
}
We need a reference to something
- Assets
- Utilities
- ComponentUtilities.cs
- Utilities
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;
}
...
}
}
Where are you canvas?
- Assets
- UserInterface
- UiCameraPositioner.cs
- UserInterface
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);
}
}
}
Time for math
- Assets
- UserInterface
- UiCameraPositioner.cs
- UserInterface
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);
}
}
}
Automatic is good
- Assets
- UserInterface
- UiCameraPositioner.cs
- UserInterface
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();
}
}
}