Welcome to the third tutorial in the series of tutorials on Adventure Creator plugin for Unity. In the second tutorial, we promised that we will talk about UI tips and tricks next. And we will talk about UI but the main actors in this tutorial will be the cameras.
Reading the previous posts is not required, but if you like, feel free to check them out. The first tutorial gives the introduction to the series and covers the Adventure Creator (AC) project and scene structure. The second tutorial elaborates on how to efficiently use action lists.
In this part we will be focusing on camera tips and tricks, we will present some solutions from Bear With Me through answering 3 questions:
- How to implement a simple static Adventure Creator scene?
- How to implement a scrolling scene?
- How to implement a close up scene?
How to implement a simple static Adventure Creator scene?
By a simple static scene, we mean the scene with one static camera. This is, in regard to the camera system, the simplest type of scene. We talk about this just for the sake of setting a context for answering the other two questions which will follow. On a simple static scene, we have only one camera and everything is rendered through that camera.
This picture shows how scene hierarchy looks for this type of scene:
We can see that MainCamera is the only camera present on this scene, it has it defaults LookAt child which we are not using in our 2D adventure game.
Let’s look at the MainCamera inspector:
We can see that only the camera and the main camera components are needed for this camera to work. The important thing to note here is the culling mask property of Camera component, it is set to “Everything”, so everything will be rendered by this camera (UI, scene background, scene sprites… all layers).
We use this simple camera setup in a couple of scenes in our project. but let’s move on.
How to implement a scrolling scene?
If we want to have a scrolling scene, in addition to MainCamera we will need two cameras; scrolling camera which will be moved to achieve scrolling effect, and UI camera which will render the UI separately, because we don’t want the UI to be scrolled, to escape from our screen, we need our UI, if it runs away we won’t be able to open inventory through our inventory button, or use our map button to open the city map.
Scene hierarchy for this type of scene looks like this:
Let’s take a look at our scrolling camera inspector:
Here we have a standard Camera script but we also have GameCamera2D script, which is a script used by Adventure Creator to make the camera follow our player.
You can see the GameCamera2D component allows us to adjust parameters such as camera follow speed and to constrain camera movement to the edges of our scene. Note that culling mask is set to Mixed, it renders everything except UI layer, we will explain why in a bit.
Furthermore, scrolling camera is slotted in Adventure Creator scene settings as default camera, this will automatically attach this camera to the main camera on scene start at runtime. By slotting we mean simply dragging a scene object to a public property of a component of some Unity object.
In Adventure Creator we always want to have one and only one main camera (the one which has main camera component attached to it).
The main camera basically says to all other cameras “ok I know that you are all cameras but I am the main camera and I will do the rendering. If you want me to show something that is not in my default frame, just tell me where should I position myself, just tell me where to look, what to capture?”. In this particular case, the main camera will use scrolling camera, in other words, it will adjust its position according to scrolling camera position.
When we play the AC scene, in runtime, we can see the main camera has it’s attached camera, this means our main camera is currently using Scrolling camera which tells her where to position itself.
When we use AC action “Camera:Switch”, we are actually changing the attached camera of the main camera, in other words ordering the main camera to render some other part of the scene. Also, we added Constant ID to our scrolling camera so it works with AC save and load system, so current camera position can be saved and loaded.
*Important tip: It is smart to include this action in OnLoad cutscene:
Because it seems that the main camera sometimes loses the reference to the scrolling camera upon loading, this way we make sure that the scrolling camera is attached to our main camera after scene load.
Now we have the scrolling camera, but the problem is, not only our scene will scroll, our UI will scroll too (for example inventory, buttons or anything that we have as our UI will scroll and eventually leave the screen). Bye, bye UI! Since we don’t want this, we will have to use one more camera, we will call it UI camera and it will only render objects which are located on UI layer.
And this is why, on our scrolling camera, we unchecked the UI layer. Also, we have to do the same for our main camera, uncheck the UI layer in culling mask, because we don’t want these cameras to render anything from the UI layer, now that we have the UI camera for that.
Our UI Camera looks like this:
We can see that the UI camera has a Camera component, and it’s culling mask is set to UI only. Also, we have written a CameraAspectEnforcer script, which enforces aspect ratio for this camera. We had to do this because the AC camera settings, where we set enforced aspect ratio for our game, will not apply to this UI camera, it will apply only to our main camera, and cameras attached to our main camera, such as the scrolling camera.
So we need to do this manually for the UI camera.
The code for this script is simple:
using UnityEngine;
using System;
using System.Collections;
public class CameraAspectEnforcer : MonoBehaviour
{
[Serializable]
public struct AspectRatio
{
public float widthAspect;
public float heightAspect;
}
public AspectRatio aspectRatio;
void Awake()
{
float targetAspect = aspectRatio.widthAspect / aspectRatio.heightAspect;
float windowAspect = (float)Screen.width / (float)Screen.height;
float scaleHeight = windowAspect / targetAspect;
Camera camera = GetComponent<Camera>();
if (scaleHeight < 1.0f)
{
camera.orthographicSize = camera.orthographicSize / scaleHeight;
}
}
}
aspectRatio.widthAspect / aspectRatio.heightAspect should give us the number which is set in the AC camera settings as the desired aspect ratio, in our case that is 16/9 which is cca. 1.77778.
Now our UI camera aspect ratio will be the same as the main camera aspect ratio.
And now we finally have both our scrolling camera and our UI camera set up, but we need to do one more thing, we have to tweak the UI canvases. We implemented the UI canvases as Unity prefabs which are slotted as menus in the AC menu tab.
We will have to:
- Set all our UI canvases to UI layer, because we set our UI camera to render the UI layer
- All UI canvases must use world space
- Adjust the UI camera position so it can see the UI canvases, so we can actually see them through the UI camera.
- Set canvas.worldCamera for each of our UI canvases to the UI camera. We do this to be able to receive click events from the UI camera (to be able to click on our UI elements like inventory button).
The first three points are trivial, but the fourth point might require a bit of coding.
UI canvases are instantiated only once on the start of our game, and they have “Don’t destroy on load” which means that they are carried from scene to scene, they just get disabled (invisible) sometimes, for example on the loading screen or when we want to disable them for any reason.
Upon instantiation they will automatically search for the main camera on the scene where they are instantiated, and when we switch the scene, if the main camera is not persisted between scenes, the UI menus will lose their reference camera for receiving click events, we won’t be able to open our inventory menu through the UI inventory button, we might not even see our UI.
So to solve this, we have two options:
- Create a UI camera on game start, keep this camera alive through all scenes, same as UI canvases, and have the UI canvases reference this camera, so we’ll always have the same instance of the UI camera available across all scenes and the UI canvases will always use that same camera instance.
- Or we can write a script which we attach to each of our UI canvases. This script will use OnEnable() function to find the UI camera in the current scene hierarchy. When we switch the scene, we disable the UI canvases. Then we have a loading scene, and when the next scene is loaded our UI canvases are enabled again triggering OnEnable(). In the OnEable() function canvases are searching for the new UI camera to set it as their canvas.worldCamera. If they don’t find the UI camera, the canvases will then use the main camera as their canvas.worldCamera.
We went for the second option, we don’t have a UI camera in each of our scenes. As you saw in the case of the most simple static scene, sometimes we have the only main camera which renders everything (including UI).
So we created this custom script which takes care of the canvas.worldCamera for each of our UI menus.
We defined the tag for the UI camera so we can find this camera in every scene from our script. We attached the script to every AC menu, every AC menu is Unity UI prefab (canvas).
This is the script, also a rather simple one:
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
public class CanvasCameraEnforcer: MonoBehaviour
{
public string CameraTag = “UICamera“;
private Canvas _canvas;
private Camera _camera;
void Awake ()
{
_canvas = GetComponent<Canvas> ();
}
void OnEnable()
{
SetCamera ();
}
private void SetCamera()
{
GameObject cameraGO = GameObject.FindGameObjectWithTag(CameraTag);
if (cameraGO)
_camera = cameraGO.GetComponent<Camera>();
else
_camera = Camera.main;
_canvas.worldCamera = _camera;
}
}
After attaching this script to all of our UI canvases, we have to do one final thing.
Our main camera, UI canvases and UI camera should be centered at the same world position, the best candidate is (0,0,0). Except if you make sure to have a UI camera on each scene and never use the main camera to display UI, then it is only important that the UI camera can see the UI canvases.
For the sake of explaining why this is required, we can place our UI canvases anywhere on the scene, and we set the UI camera world position so that it sees those UI canvases, and the UI will always be rendered on screen atop anything else that is rendered (i.e. scrolling scene background or close up scene) while there is a UI camera present on the scene. Even though action on the main camera might be happening on a completely different part of our Unity world space, our UI, which is miles away in the unity world space, will be rendered properly as if it was rendered by the main camera.
This is because anything a UI camera sees is rendered atop anything the main camera sees, these two cameras are merged for the final picture, our viewport combines these two cameras. It does not matter where the things the UI camera sees are located, they will be rendered together with the main camera content. But if we don’t have a UI camera on every scene, we have to make sure that in that case, the UI canvases are in the main camera frame.
And that is it, we are done with the scrolling scene and we actually did most of the work required to setup a close-up scene. Save your UI camera and scrolling camera as prefabs, don’t forget to apply to all your UI canvas prefabs which now have CanvasCameraEnforcer component attached to them.
If we centered everything at (0,0,0) we don’t have to worry if our scene contains a UI camera or not, our UI will be rendered properly, be it a scrolling scene or a static scene or a close-up scene.
How to implement a close-up scene?
A close-up scene is the “scene inside a scene”. For example, we have a scene showing a room with a control panel in it, let’s call this scene the parent scene. When we click on the control panel we want to open another scene, which shows the control panel close-up, let’s call this one close-up scene. We don’t want our control panel close-up to open a window in Windows OS, we want it to completely overlay our scene. So we have two scenes, why not simply implement them as two separate scene files and when clicking on the control panel just switch the scene through AC “scene:switch” action?
Well, the thing is that we don’t want to have a loading screen on this scene switch (switch between parent scene and close-up scene) because we are essentially still on the parent scene, we just opened the control panel close-up. We did not find a way to turn off the loading screen for a particular scene switch, in AC it seems to be all or nothing regarding this (loading screen on every scene switch or none). Also, we wanted the ambient sounds to be the same, continuous throughout the two scenes. So how to simulate scene switching? Change the scene’s visual setting without actually switching to another scene file?
One way to achieve this is this:
- Put all of the close-up scene objects (background, scene sprites, hotspots…) next to our parent scene objects (now we have assets for two scenes in the same scene file).
- Create a new camera which looks at the closeup scene objects (so the camera will have to be in world space), we will call this camera “CloseupSceneCamera”
- Create one more camera which will look at the parent scene objects, we will call this camera “ParentSceneCamera”
- Create a UI camera which will render the UI (setting up the UI camera is explained in the section before, where we implemented a scrolling scene. If you have created a UI camera just use the existing prefab. Also, our UI canvases have to be set up as we described for the scrolling scene.)
- Set “ParentSceneCamera” to be the default camera on scene load (slot it as default camera for this scene file.)
- Place a control panel hotspot on the parent scene. In the hotspot action list switch the camera to “CloseupSceneCamera”.
- Place a “ReturnHotspot” on the closeup scene area. In this hotspot, action list switch the camera back to “ParentSceneCamera” (You can add “fade out”/”fade in” to this camera switches if you want.)
On next picture, we can see our editor with the parent scene objects on the left and the close-up scene objects on the right:
You can notice two camera icons in the centers of each of these object groups, those are the centers of our two cameras, ParentSceneCamera on the left and CloseupSceneCamera on the right. So basically switching between this two cameras is the trick we used.
To note once again, this is the case when a closeup scene is completely overlaying the parent scene. All our closeups are like this. If you want your closeup to overlay the parent scene, but not completely (to appear like a window in Windows), send us a message and we will try that out for you.
Finally, let’s take a look at the scene structure we got:
You can see we separated our close-up scene geometry and logic under “ControlpanelCloseupScene” parent.
And, the camera is ready!
That’s it for the camera tips and tricks, for now, we hope you found something useful and maybe gained some insight into how cameras work. These were a couple of our approaches to implementing a scrolling scene and a close-up scene. There are other ways to do it for sure. Please let us know if you have other solutions. Maybe better ones?
If you have questions, solution ideas or just want to say hello, please leave a comment below or write to us via Facebook or Twitter!
Peace out and see you in our next tutorial on the Adventure Creator plugin for Unity.
By Jakša