Time for part 2 of how you could manage your UI windows in Unity 5.3! Part 1 brought up some questions that I like to deal with first. Things like having only one root canvas for all windows, preloading windows to not have a lag when opening one and animations when opening/closing a window.
To follow my explanations more easily, check out the sample project I uploaded to bitbucket. Check out the single commits in the repository as well, they document how the additions are added one by one and what changed.
One canvas to render them all
An issue that we came up with in our own project as well is that each window in its scene has an own canvas. This is required to separately test a window, otherwise it won’t be visible when running the window scene.
But in a project you want to have only one single Canvas to put your windows under. The Canvas object contains some common settings that you don’t want to setup for each window separately:
- Canvas
- Render Mode
- Sort Order
- Canvas Scaler
- UI Scale Mode
- Reference Resolution and Screen Match Mode
Adding one common UI root object
So let’s just add a single UI root game object to the Root scene. This will be used to anchor all open windows underneath it. Just set it up the way you UI should work.

Adjusting the window manager
Now the WindowManager has to use the UI root. So first of all we will add a reference to the UIRoot game object to WindowManager script and link it in the Root scene.
1 2 3 4 |
/// <summary> /// Root object to anchor windows under. /// </summary> public Transform UIRoot; |
Anchoring window roots when opening window
After a window was opened, its root transforms has to be re-parented underneath the UIRoot transform. We are already collecting the window root transforms after loading the window scene, so we just have to set their parents and adjust their RectTransform s so they are still stretched across the whole canvas area.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
private void AnchorWindow(Window window) { if (this.UIRoot == null) { return; } // Anchor window under the UI root. foreach (var windowRoot in window.Roots) { // Adjust transform of window canvas. var windowRectTransform = windowRoot as RectTransform; if (windowRectTransform == null) { continue; } var windowCanvas = windowRoot.GetComponent<Canvas>(); if (windowCanvas == null) { continue; } // Re-parent transform. windowRoot.SetParent(this.UIRoot, false); // Stretch window canvas to root canvas. windowRectTransform.anchorMin = Vector2.zero; windowRectTransform.anchorMax = Vector2.one; windowRectTransform.sizeDelta = Vector2.zero; windowRectTransform.localScale = Vector3.one; } } |
The method is just called after the window was loaded in the DoOpenWindow method.
1 2 3 4 5 6 |
// Setup window. window.Roots = windowRoots; window.Loaded = true; // Anchor window under UI root. this.AnchorWindow(window); |
Removing re-parented window roots on closing
As we moved the window roots under the UI root, unloading the scene won’t correctly remove the re-parented window root objects as they don’t belong to the scene anymore.
Therefore we have to remove all remaining window root objects manually after unloading the scene in the CloseWindow method.
1 2 3 4 5 6 7 8 9 10 11 |
// Unload window scene. SceneManager.UnloadScene(window.Scene.buildIndex); // Remove window roots that may have been anchored to the UI root. foreach (var windowRoot in window.Roots) { if (windowRoot.gameObject != null) { Destroy(windowRoot.gameObject); } } |
Disabling the window canvas
So right now we just re-parented the window roots under a common UI root object that has an own canvas and canvas scaler. The window canvases and canvas scalers still exist on the window root objects and are active.
Fortunately Unity takes care about it itself. Child canvases automatically inherit the settings from its main canvas and canvas scalers are automatically disabled. So there is nothing to do for us 🙂
Render order
Right now our windows are rendered in the order they are opened. This might be correct most of the time. But having a way to set the render order manually is often required when your windows are opened quite independent from each other.
For a canvas you can set the Sort Order in the inspector.

Unfortunately it is disabled by default when you re-parent the canvas under another one. So we have to enable the overrideSorting flag after we moved the window canvas in our AnchorWindow method:
1 2 3 4 5 |
// Re-parent transform. windowRoot.SetParent(this.UIRoot, false); // Make sure that sort order is still considered. windowCanvas.overrideSorting = true; |
This gives you the possibility to set the sort order for each window and they are correctly rendered independent from their loading order. The higher the sort order number, the more in front the window will be rendered.
Preloading windows to avoid lag
The performance question came up for a bunch of times. In fact there is a small lag when a window is opened as a new scene has to be loaded and a bit of setup stuff has to be made.
We can’t really remove the loading time, but what we can do is loading windows that are used regularly at startup and just showing/hiding them instead of loading/unloading them completely.
Specifying windows to preload
Therefore we add a list of window ids that should be preloaded to the window manager that we load when the window manager is started.
1 2 3 4 5 6 7 8 9 |
/// <summary> /// Preloaded windows. /// </summary> private readonly List<Window> preloadedWindows = new List<Window>(); /// <summary> /// Windows to preload for faster opening. /// </summary> public string[] PreloadedWindowIds; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
protected void Start() { // Preload windows. foreach (var windowId in this.PreloadedWindowIds) { // Create window. var window = new Window { WindowId = windowId }; // Load window. this.StartCoroutine(this.DoPreloadWindow(window)); } } private IEnumerator DoPreloadWindow(Window window) { // Load window. yield return this.DoLoadWindow(window); if (window.Loaded) { // Hide window. window.Hide(); // Add to preload windows. this.preloadedWindows.Add(window); } } |
To not duplicate the code for loading a window, it was moved out of the DoOpenWindow method and moved into an own DoLoadWindow method which is used for preloading and for loading when opening a window.

Using preloaded window on opening window
When a window should be opened we now have to check if the window is already preloaded in the OpenWindow method. In that case we don’t have to create a new window, but only show it and add it to the open windows.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// Check if preloaded. window = this.preloadedWindows.FirstOrDefault(preloadedWindow => preloadedWindow.WindowId == windowId); if (window == null) { // Create new window. window = new Window { WindowId = windowId }; } window.Context = context; window.OnOpened = onOpenedCallback; window.OnClosed = onClosedCallback; this.OpenWindow(window); |
In the DoOpenWindow method we check if the window is already loaded and just show it if it is.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
private IEnumerator DoOpenWindow(Window window) { // Add window to open windows. this.windows.Add(window); if (!window.Loaded) { yield return this.DoLoadWindow(window); } else { // Show window. window.Show(); } // Check if loading was successful. if (window.Loaded) { // Notify listeners. this.OnWindowOpened(window); var handler = window.OnOpened; if (handler != null) { handler(window); } } else { // Remove from open windows. this.windows.Remove(window); } } |
Hide instead of unload on closing window
When closing a preloaded window, we just want to hide it instead of unloading it. So we have to check if it is in the preloaded windows.
1 2 3 4 5 6 7 8 9 10 11 12 |
// Only hide if preloaded. if (this.preloadedWindows.Contains(window)) { window.Hide(); } else { this.DoUnloadWindow(window); } // Remove window. this.windows.Remove(window); |
With these changes, windows will show up almost instantly. To haven’t too much game objects loaded at one time, preloading should possibly only used for windows that are used regularly in your game. Furthermore keep in mind that populating a window with the data it should show will take up time as well. If the data is is dynamic this will cause a bit of a delay anyway when the window is opened, no matter if the window is already preloaded or not.
Animations for windows
Animations are a pretty big topic and I will probably write some distinct posts about it. But as the question came up I just added an open and close animation to one of the popups of the example application.
Creating the animations
The first step was to create the two animations. I went for two simple ones for opening and closing the window:
- Open Animation: Scales the game object up from 0 to 1 and rotates it by 360 degrees counter clockwise
- Close Animation: Scales the game object down from 1 to 0 and rotates it by 360 degrees clockwise
The animator controller, which will be put onto the window’s Animator , has two states, Open and Closed, and one bool parameter is_open which indicates if the window is currently open or closed. The Closed state is active when the is_open condition if false, the Open state is the default one.

Now we just have to add an Animator on to the root panel of the window (not the Canvas, but the game object directly below it) and reference the created animator controller.
Adjusting the window manager
Right now the window manager immediately closes the window or hides it when the window is closed. What we want is to give the control to the window presentation, which will play the close animation, and only afterwards cleaning up the window.
This is a bit tricky as you want the window manager as independent from the final look and feel of the windows as possible. Therefore I created an interface ( IWindowClosingHandler ) which indicates that a script takes care about closing a window. When a window should get closed, the window manager searches for this interface on the window’s root objects. If it can’t find one it cleans up the window immediately as before.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
/// <summary> /// Interface for a handler which does further actions (e.g. playing an animation) when a window should be closed. /// The handler has to make sure to invoke the callback when it has finished with its actions. /// </summary> public interface IWindowClosingHandler { #region Public Methods and Operators /// <summary> /// Called when the window was closed. /// </summary> /// <param name="closingFinishedCallback">This callback has to be called when the handler has finished with its actions.</param> void OnWindowClosed(Action closingFinishedCallback); #endregion } |
If the interface is found, the control is given to the script that implements it by calling its OnWindowClosed method. Furthermore a callback action is passed to the script which has to be invoked by the handler when it has finished its work.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
private void OnWindowClosed(Window window, Action cleanupWindowAction) { var handler = this.WindowClosed; if (handler != null) { handler(window); } // Check for handler that takes care about closing the window. var finishClosingHandler = window.Roots.Select(root => root.GetComponentInChildren<IWindowClosingHandler>()) .FirstOrDefault(closingHandler => closingHandler != null); if (finishClosingHandler != null) { finishClosingHandler.OnWindowClosed(cleanupWindowAction); } else { cleanupWindowAction(); } } |
Adding an animated closing handler
Now to connect the two parts, I added a WindowAnimator which implements the IWindowClosingHandler interface. It just switches the bool parameter is_open to false as soon as the OnWindowClosed method is called and waits until it gets the OnWindowCloseFinished animation event to invoke the callback. The animation event is triggered at the end of the close animation.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
/// <summary> /// Handles window closing and adjust the animator of the window accordingly. /// </summary> public class WindowAnimator : MonoBehaviour, IWindowClosingHandler { #region Fields /// <summary> /// Animator of window. /// </summary> public Animator Animator; /// <summary> /// Animator parameter that indicates if window is open. /// </summary> public string AnimatorOpenedParameter = "is_open"; /// <summary> /// Callback to be invoked when animation sends event that it is finished. /// </summary> private Action closeFinishedCallback; #endregion #region Public Methods and Operators /// <summary> /// Called when the window was closed. /// </summary> /// <param name="closingFinishedCallback">This callback has to be called when the handler has finished with its actions.</param> public void OnWindowClosed(Action closingFinishedCallback) { if (this.Animator != null && this.Animator.parameters.Any(parameter => parameter.name == this.AnimatorOpenedParameter)) { this.closeFinishedCallback = closingFinishedCallback; this.Animator.SetBool(this.AnimatorOpenedParameter, false); } else { closingFinishedCallback(); } } /// <summary> /// Called by an animation event when the close animation was finished. /// </summary> public void OnWindowCloseFinished() { if (this.closeFinishedCallback != null) { this.closeFinishedCallback(); this.closeFinishedCallback = null; } } #endregion } |
What has to be considered is that the close animation is really triggered by changing the animator parameter and that it triggers the animation event. Otherwise the cleanup of the closed window won’t be performed, so it stays in the scene.
I am not 100% happy with the solution as those mistakes in the setup can cause the window manager to not unload closed windows. It separates the window management from the window presentation quite good though.
It’s a similar problem with many events that occur right before an object should be destroyed (e.g. death animations). If you have any hints how to maintain a clean separation but also have the logic not depend on a callback to be triggered by the presentation, I would be very happy to hear from you! 🙂
Conclusion
With this post I added some requested features to the window management system which can be used with Unity 5.3. I was very happy to have received some feedback on the first part, especially in the Facebook Indie Game Developers group. Hope those additions showed up how a window management system can be further extended.
If there are more topics about window management that you like to have covered, don’t hesitate to comment here or get in contact with me! I would be happy to do another part with your requests. The next part will also contain explanations how to use our Unity asset Data Bind for Unity to populate UI windows with the data they should visualize.
If you haven’t already, now would be a good time to checkout the repository with the sample project and play around a bit with it yourself. Let me know if you have any questions! 🙂