<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Playable Design]]></title><description><![CDATA[An indie game development blog with tips and tutorials about game design, game development, and game art.]]></description><link>https://playable.design</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1659462399560/BvxzGY0MX.png</url><title>Playable Design</title><link>https://playable.design</link></image><generator>RSS for Node</generator><lastBuildDate>Thu, 16 Apr 2026 22:49:36 GMT</lastBuildDate><atom:link href="https://playable.design/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Thrift Shop Coming Soon]]></title><description><![CDATA[Thrift Shop is a casual, thrift shopping simulation game. You start with an empty workshop and a full wallet of Thrift Coins. Your mentor, Papa Pinchpenny, teaches you how to navigate thrift stores, find unique items, resell for profit, and complete ...]]></description><link>https://playable.design/thrift-shop-coming-soon</link><guid isPermaLink="true">https://playable.design/thrift-shop-coming-soon</guid><category><![CDATA[indie game]]></category><dc:creator><![CDATA[Steve Mcilwain]]></dc:creator><pubDate>Mon, 17 Jul 2023 20:36:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1689625936602/55d398a8-29fc-4e4b-83e5-3bfadb76aa08.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Thrift Shop is a casual, thrift shopping simulation game. You start with an empty workshop and a full wallet of Thrift Coins. Your mentor, Papa Pinchpenny, teaches you how to navigate thrift stores, find unique items, resell for profit, and complete quests that unlock stores and skills.</p>
<h2 id="heading-steam-next-fest-october-2023">Steam Next Fest - October 2023</h2>
<p>Follow on <a target="_blank" href="https://store.steampowered.com/app/2355270/Thrift_Shop/?beta=0">Steam</a> to get access to the demo in October as part of Next Fest.</p>
]]></content:encoded></item><item><title><![CDATA[Unity Single Scene Architecture - GameOver]]></title><description><![CDATA[This is a post in a series about building a game in Unity using a single scene. 
Unity Single Scene Architecture Series
In this post, we'll finish the game UI by adding a Game Over screen, a UI Manager and wire everything up to the Game Manager... wi...]]></description><link>https://playable.design/unity-single-scene-architecture-gameover</link><guid isPermaLink="true">https://playable.design/unity-single-scene-architecture-gameover</guid><category><![CDATA[unity]]></category><category><![CDATA[Unity Engine]]></category><category><![CDATA[Game Development]]></category><dc:creator><![CDATA[Steve Mcilwain]]></dc:creator><pubDate>Thu, 20 Oct 2022 14:06:07 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1666107744133/4ocIMdkRV.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This is a post in a series about building a game in Unity using a single scene. </p>
<p><a target="_blank" href="https://playable.design/series/single-scenery">Unity Single Scene Architecture Series</a></p>
<p>In this post, we'll finish the game UI by adding a Game Over screen, a UI Manager and wire everything up to the Game Manager... with tests.</p>
<h2 id="heading-refactoring-prefabs-with-prefab-variants">Refactoring Prefabs with Prefab Variants</h2>
<p>If we follow the same steps as the previous two posts for framing out the UI, we'd be duplicating some of the exact same game objects and components, such as the canvas and the background panel. Just like with code, when you see duplication, it can be an opportunity to refactor. In this case, instead of making a new prefab for the canvas we can make a base prefab, then use prefab variants to customize for each screen's content.</p>
<p>Let's do this now:</p>
<ul>
<li>Add a child empty game object underneath "UI" named "GameOverController"</li>
<li>Add a child to "GameOverController" from the context menu <code>UI &gt; Canvas</code> named "UICanvas"</li>
<li>Change the <code>Canvas Scaler</code> component to <code>Scale With Screen Size</code> with <code>ReferenceResolution</code> set to 1920 x 1080</li>
<li>Add a child to the canvas from the context menu <code>UI &gt; Panel</code>, named "Background"</li>
<li>Set the Image component <code>Color</code> to black with no transparency</li>
</ul>
<p>That's enough for the base prefab. In the <code>Project</code> window create a new folder under "Bundles" named "UI". Drag the "UICanvas" from the scene into this folder to make a prefab, then delete it from the scene.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666109380142/m32vOTzui.png" alt="image.png" /></p>
<p>Now we have a base prefab for the three canvases needed for the UI, as well as any future ones.</p>
<h2 id="heading-game-over-screen-variant">Game Over Screen Variant</h2>
<p>Now create the canvas for the Game Over screen.  Select "UICanvas" in the <code>Project</code> then use the context menu <code>Create &gt; Prefab Variant</code> to create a new variant named "GameOverCanvas" and open it in prefab editing mode:</p>
<ul>
<li>Add a child to "Background" with the context menu <code>UI &gt; Text - Text Mesh Pro</code></li>
<li>Set the <code>Rect Transform</code> to middle stretch. set the Y position to 100 and <code>Height</code> to 200</li>
<li>Set the text to "Game Over", center the horizontal and vertical alignments, set font size to 200</li>
<li>Add another child to "Background" from the context menu <code>UI &gt; Button - Text Mesh Pro</code></li>
<li>Set the width to 300, height to 100, Y position to -200</li>
<li>Set the Text to "PLAY" with font size 64</li>
</ul>
<p>Mark the prefab variant as <code>Addressable</code> with an address of "GameOverCanvas".</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666268154945/2KdZP6zbD.png" alt="image.png" /></p>
<p>Looks a lot like the MenuCanvas right? We could use one prefab for both and maybe just change the text at runtime, but I am going to assume in the future the MenuCanvas will have more menu items for things like settings, credits, saved games, and more. We'll continue to keep these separated and distinct.</p>
<h2 id="heading-loading-variant">Loading Variant</h2>
<p>Duplicate the "GameOverCanvas" variant we just created and rename it to "LoadingCanvas" then open it in prefab editing mode.</p>
<ul>
<li>Delete the Button </li>
<li>Change the text to "LOADING"</li>
<li>Set the text Y position to 0</li>
</ul>
<p>Mark the prefab variant as <code>Addressable</code> with an address of "LoadingCanvas".</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666112151375/hj9h7fQsP.png" alt="image.png" /></p>
<h2 id="heading-menu-screen-variant">Menu Screen Variant</h2>
<p>Duplicate the "GameOverCanvas" variant again and rename it to "MenuCanvas" then open it in prefab editing mode.</p>
<ul>
<li>Change the text to "Single Scenery"</li>
<li>Don't worry about the Button for now, we'll come back to that</li>
</ul>
<p>Mark the prefab variant as <code>Addressable</code> with an address of "MenuCanvas".</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666270348048/B0OGoIt2P.png" alt="image.png" /></p>
<p>We still have the two old prefabs for the LoadingCanvas and MenuCanvas. Delete them both. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666270507771/WOh-SUUw3.png" alt="image.png" /></p>
<h2 id="heading-refactor-for-events">Refactor for Events</h2>
<p>Before we add scripting to our "GameOverCanvas", I am unhappy with the way the UI events are being exposed for the "MenuCanvas". If you remember from the previous post, we're exposing the "OnClick" event of the button by making the entire button publicly accessible. </p>
<p>What if we later change that button to be something else, add more UI controls that have new events, or swap out UGUI for the new UI Toolkit (you know, when it's production ready in a few years)? Instead, let's have these loaded prefabs just emit plain events so that the parents of these objects do not need to know about their internals (encapsulation).</p>
<h2 id="heading-scriptable-object-events">Scriptable Object Events</h2>
<p>There are plenty of articles about using <code>ScriptableObject</code>, which is a sibling to <code>MonoBehaviour</code> meant to represent a custom asset in a Unity project. The script itself is not an asset, it is source code that gets compiled into a library at build time and distributed with the player, but the instances of that <code>ScriptableObject</code>, which can be created with the editor menu are assets (that can be addressable). Here's my version of a simple event class that allows listeners to add/remove callback methods, and publishers of the event to "invoke" it.  </p>
<pre><code class="lang-c#"><span class="hljs-keyword">using</span> System;
<span class="hljs-keyword">using</span> UnityEngine;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">SingleScenery</span>
{
    [<span class="hljs-meta">CreateAssetMenu(fileName = <span class="hljs-meta-string">"GameEvent"</span>, menuName = <span class="hljs-meta-string">"Single Scenery/GameEvent"</span>)</span>]
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">GameEvent</span> : <span class="hljs-title">ScriptableObject</span>
    {
        <span class="hljs-keyword">private</span> Action gameEvent;

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Invoke</span>(<span class="hljs-params"></span>)</span>
        {
            gameEvent?.Invoke();
        }

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">AddListener</span>(<span class="hljs-params">Action callback</span>)</span>
        {
            gameEvent += callback;
        }

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">RemoveListener</span>(<span class="hljs-params">Action callback</span>)</span>
        {
            gameEvent -= callback;
        }
    }
}
</code></pre>
<p>Now we can create instances of events and "inject" an event (via the editor) into an event invoker and one or more event listeners.</p>
<h2 id="heading-event-button">Event Button</h2>
<p>Let's create a new component that can be added to a UI button and simply invoke a configured event on click. In the "Scripts\UI" folder add new script named "UIEventButton":</p>
<pre><code class="lang-c#"><span class="hljs-keyword">using</span> UnityEngine;
<span class="hljs-keyword">using</span> UnityEngine.UI;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">SingleScenery</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">UIEventButton</span> : <span class="hljs-title">MonoBehaviour</span>
    {
        [<span class="hljs-meta">SerializeField</span>] <span class="hljs-keyword">private</span> GameEvent onClickEvent;

        <span class="hljs-keyword">private</span> Button _button;

        <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Awake</span>(<span class="hljs-params"></span>)</span>
        {
            _button = GetComponent&lt;Button&gt;();
        }

        <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnEnable</span>(<span class="hljs-params"></span>)</span>
        {
            _button.onClick.AddListener(OnClick);
        }

        <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnDisable</span>(<span class="hljs-params"></span>)</span>
        {
            _button.onClick.RemoveListener(OnClick);
        }

        <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnClick</span>(<span class="hljs-params"></span>)</span>
        {
            onClickEvent.Invoke();
        }
    }
}
</code></pre>
<h2 id="heading-configure-a-new-event">Configure a New Event</h2>
<p>Now we can use our new <code>GameEvent</code> to create a new instance of an event in the project as a custom asset.</p>
<ul>
<li>Create a new folder in "Bundles" named "Events"</li>
<li>Using the context menu <code>Create &gt; Single Scenery &gt; GameEvent</code> add a new event named "OnPlayEvent"</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666265809661/yxGwreTsC.png" alt="image.png" /></p>
<h2 id="heading-refactor-the-menu-screen">Refactor the Menu Screen</h2>
<p>Let's integrate our new event system into the menu screen classes.</p>
<ul>
<li>Open the "MenuCanvas" prefab variant in the <code>Project</code></li>
<li>Select the "Button" and use <code>Add Component</code> to add the new "UI Event Button" component</li>
<li>Assign "OnPlayEvent" to the <code>On Click Event</code> field</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666274261521/mu4v3Q01A.png" alt="image.png" /></p>
<p>Open the "MenuController.cs" script and refactor it to listen to this new event, so that when the Play button is clicked, the controller will hide the menu.</p>
<pre><code class="lang-c#"><span class="hljs-keyword">using</span> UnityEngine;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">SingleScenery</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">MenuController</span> : <span class="hljs-title">UIController</span>
    {

        [<span class="hljs-meta">SerializeField</span>] <span class="hljs-keyword">private</span> GameEvent onPlayEvent;

        <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnEnable</span>(<span class="hljs-params"></span>)</span>
        {
            onPlayEvent.AddListener(Hide);
        }

        <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnDisable</span>(<span class="hljs-params"></span>)</span>
        {
            onPlayEvent.RemoveListener(Hide);
        }

    }
}
</code></pre>
<p>Now the menu screen functionality is completely encapsulated.  </p>
<p>But now we've broken our test code. Open "MenuController_Tests.cs" and replace the code with:</p>
<pre><code class="lang-c#"><span class="hljs-keyword">using</span> System.Collections;
<span class="hljs-keyword">using</span> NUnit.Framework;
<span class="hljs-keyword">using</span> UnityEngine;
<span class="hljs-keyword">using</span> UnityEngine.AddressableAssets;
<span class="hljs-keyword">using</span> UnityEngine.ResourceManagement.AsyncOperations;
<span class="hljs-keyword">using</span> UnityEngine.TestTools;
<span class="hljs-keyword">using</span> UnityEngine.UI;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">SingleScenery</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">MenuController_Tests</span>
    {
        <span class="hljs-comment">// Test Settings</span>

        <span class="hljs-keyword">const</span> <span class="hljs-keyword">string</span> ADDRESS = <span class="hljs-string">"MenuController"</span>;
        <span class="hljs-keyword">const</span> <span class="hljs-keyword">float</span> DELAY = <span class="hljs-number">3f</span>;

        WaitForSeconds delay;
        MenuController controller;
        <span class="hljs-keyword">bool</span> _setup;

        <span class="hljs-keyword">bool</span> _playClicked;

        [<span class="hljs-meta">OneTimeSetUp</span>]
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OneTimeSetUp</span>(<span class="hljs-params"></span>)</span>
        {
            <span class="hljs-keyword">if</span> (GameObject.FindObjectOfType&lt;Camera&gt;() == <span class="hljs-literal">null</span>)
            {
                <span class="hljs-keyword">new</span> GameObject(<span class="hljs-string">"Camera"</span>).AddComponent&lt;Camera&gt;();
            }

            delay = <span class="hljs-keyword">new</span> WaitForSeconds(DELAY);
        }

        [<span class="hljs-meta">UnitySetUp</span>]
        <span class="hljs-function"><span class="hljs-keyword">public</span> IEnumerator <span class="hljs-title">Setup</span>(<span class="hljs-params"></span>)</span>
        {
            <span class="hljs-keyword">if</span> (_setup) <span class="hljs-keyword">yield</span> <span class="hljs-keyword">break</span>;

            <span class="hljs-keyword">var</span> handle = Addressables.InstantiateAsync(ADDRESS);
            <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> handle;  <span class="hljs-comment">// wait for async call completion</span>

            Assert.That(handle.Status == AsyncOperationStatus.Succeeded);
            Assert.IsNotNull(handle.Result);

            controller = handle.Result.GetComponent&lt;MenuController&gt;();

            Assert.IsFalse(controller.Ready);

            _setup = <span class="hljs-literal">true</span>;

            Debug.Log(<span class="hljs-string">"Setup: controller is instantiated in test scene"</span>);
        }

        [<span class="hljs-meta">OneTimeTearDown</span>]
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">TearDown</span>(<span class="hljs-params"></span>)</span>
        {
            <span class="hljs-keyword">if</span> (controller != <span class="hljs-literal">null</span>)
            {
                Addressables.ReleaseInstance(controller.gameObject);
            }

            Debug.Log(<span class="hljs-string">"Teardown: controller reference is released"</span>);
        }

        [<span class="hljs-meta">UnityTest, Order(1), Timeout(5000)</span>]
        <span class="hljs-function"><span class="hljs-keyword">public</span> IEnumerator <span class="hljs-title">Step_1_Load</span>(<span class="hljs-params"></span>)</span>
        {
            controller.Load();

            <span class="hljs-function"><span class="hljs-keyword">yield</span> return new <span class="hljs-title">WaitUntil</span>(<span class="hljs-params">(</span>)</span> =&gt; controller.Ready);

            <span class="hljs-keyword">var</span> canvas = controller.gameObject.GetComponentInChildren&lt;Canvas&gt;(<span class="hljs-literal">true</span>);

            Assert.That(controller.transform.childCount &gt; <span class="hljs-number">0</span>);
            Assert.IsNotNull(canvas);
            Assert.IsFalse(canvas.gameObject.activeInHierarchy);

            Debug.Log(<span class="hljs-string">"Test: controller load passed"</span>);
            <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> delay;
        }

        [<span class="hljs-meta">UnityTest, Order(2)</span>]
        <span class="hljs-function"><span class="hljs-keyword">public</span> IEnumerator <span class="hljs-title">Step_2_Show</span>(<span class="hljs-params"></span>)</span>
        {
            controller.Show();

            <span class="hljs-keyword">var</span> canvas = controller.gameObject.GetComponentInChildren&lt;Canvas&gt;(<span class="hljs-literal">true</span>);
            Assert.IsTrue(canvas.gameObject.activeInHierarchy);

            Debug.Log(<span class="hljs-string">"Test: controller show passed"</span>);
            <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> delay;
        }


        [<span class="hljs-meta">UnityTest, Order(3)</span>]
        <span class="hljs-function"><span class="hljs-keyword">public</span> IEnumerator <span class="hljs-title">Step_3_Click_Play</span>(<span class="hljs-params"></span>)</span>
        {
            <span class="hljs-keyword">var</span> button = controller.gameObject.GetComponentInChildren&lt;Button&gt;(<span class="hljs-literal">true</span>);
            button.onClick.Invoke(); <span class="hljs-comment">// simulate button click</span>

            <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;

            <span class="hljs-comment">// canvas should be hidden after event is consumed</span>
            <span class="hljs-keyword">var</span> canvas = controller.gameObject.GetComponentInChildren&lt;Canvas&gt;(<span class="hljs-literal">true</span>);
            Assert.IsFalse(canvas.gameObject.activeInHierarchy);

            Debug.Log(<span class="hljs-string">"Test: play clicked passed"</span>);
            <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> delay;
        }

        [<span class="hljs-meta">UnityTest, Order(4)</span>]
        <span class="hljs-function"><span class="hljs-keyword">public</span> IEnumerator <span class="hljs-title">Step_4_Hide</span>(<span class="hljs-params"></span>)</span>
        {
            controller.Show();
            <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
            controller.Hide();

            <span class="hljs-keyword">var</span> canvas = controller.gameObject.GetComponentInChildren&lt;Canvas&gt;(<span class="hljs-literal">true</span>);
            Assert.IsFalse(canvas.gameObject.activeInHierarchy);

            Debug.Log(<span class="hljs-string">"Test: controller hide passed"</span>);
            <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> delay;
        }

        [<span class="hljs-meta">UnityTest, Order(5)</span>]
        <span class="hljs-function"><span class="hljs-keyword">public</span> IEnumerator <span class="hljs-title">Step_5_Unload</span>(<span class="hljs-params"></span>)</span>
        {
            controller.Unload();
            <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> delay;
            Assert.That(controller.transform.childCount == <span class="hljs-number">0</span>);
            Debug.Log(<span class="hljs-string">"Test: controller unload passed"</span>);
        }

    }
}
</code></pre>
<p>Now you can delete the "Menu.cs" script from "Scripts\UI", we won't be using it anymore.  Always delete unused code. If you comment it out instead, then put a date on the comment  with a "TODO" or searchable tag so you can come delete it later.</p>
<p>Last step is to open the "MenuController" prefab in the <code>Project</code> and assign the "OnPlayEvent".</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666267589023/vPtbvJDlV.png" alt="image.png" /></p>
<h2 id="heading-finish-the-game-over-screen">Finish the Game Over Screen</h2>
<p>Now that we have successfully refactored, we can go back to the "GameOverCanvas" prefab variant, open it and add the "UIEventButton" script to the button.  Assign the same "OnPlayEvent".</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666267944742/JJKmcIVVY.png" alt="image.png" /></p>
<h2 id="heading-gameovercontroller">GameOverController</h2>
<p>Create a new script in the "Scripts/UI" folder named "GameOverController".</p>
<pre><code class="lang-c#"><span class="hljs-keyword">using</span> UnityEngine;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">SingleScenery</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">GameOverController</span> : <span class="hljs-title">UIController</span>
    {
        [<span class="hljs-meta">SerializeField</span>] <span class="hljs-keyword">private</span> GameEvent onPlayEvent;

        <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnEnable</span>(<span class="hljs-params"></span>)</span>
        {
            onPlayEvent.AddListener(Hide);
        }

        <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnDisable</span>(<span class="hljs-params"></span>)</span>
        {
            onPlayEvent.RemoveListener(Hide);
        }
    }
}
</code></pre>
<p>Drag this script onto "GameOverController" game object in the <code>Scene</code> and assign the fields. Drag the "GameOverController" into the "Bundles" folder to make it a variant.</p>
<p>Mark the prefab as <code>Addressable</code> with the address "GameOverController".</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666270745651/p0Gi87LfP.png" alt="image.png" /></p>
<h2 id="heading-add-tests">Add Tests</h2>
<p>Create a new test script using the context menu <code>Create &gt; Testing &gt; C# Test Script</code> in the "Tests\UI" folder named "GameOverController_Tests".</p>
<pre><code class="lang-c#"><span class="hljs-keyword">using</span> System.Collections;
<span class="hljs-keyword">using</span> NUnit.Framework;
<span class="hljs-keyword">using</span> UnityEngine;
<span class="hljs-keyword">using</span> UnityEngine.AddressableAssets;
<span class="hljs-keyword">using</span> UnityEngine.ResourceManagement.AsyncOperations;
<span class="hljs-keyword">using</span> UnityEngine.TestTools;
<span class="hljs-keyword">using</span> UnityEngine.UI;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">SingleScenery</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">GameOverController_Tests</span>
    {
        <span class="hljs-comment">// Test Settings</span>

        <span class="hljs-keyword">const</span> <span class="hljs-keyword">string</span> ADDRESS = <span class="hljs-string">"GameOverController"</span>;
        <span class="hljs-keyword">const</span> <span class="hljs-keyword">float</span> DELAY = <span class="hljs-number">3f</span>;

        WaitForSeconds delay;
        GameOverController controller;
        <span class="hljs-keyword">bool</span> _setup;

        <span class="hljs-keyword">bool</span> _playClicked;

        [<span class="hljs-meta">OneTimeSetUp</span>]
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OneTimeSetUp</span>(<span class="hljs-params"></span>)</span>
        {
            <span class="hljs-keyword">if</span> (GameObject.FindObjectOfType&lt;Camera&gt;() == <span class="hljs-literal">null</span>)
            {
                <span class="hljs-keyword">new</span> GameObject(<span class="hljs-string">"Camera"</span>).AddComponent&lt;Camera&gt;();
            }

            delay = <span class="hljs-keyword">new</span> WaitForSeconds(DELAY);
        }

        [<span class="hljs-meta">UnitySetUp</span>]
        <span class="hljs-function"><span class="hljs-keyword">public</span> IEnumerator <span class="hljs-title">Setup</span>(<span class="hljs-params"></span>)</span>
        {
            <span class="hljs-keyword">if</span> (_setup) <span class="hljs-keyword">yield</span> <span class="hljs-keyword">break</span>;

            <span class="hljs-keyword">var</span> handle = Addressables.InstantiateAsync(ADDRESS);
            <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> handle;  <span class="hljs-comment">// wait for async call completion</span>

            Assert.That(handle.Status == AsyncOperationStatus.Succeeded);
            Assert.IsNotNull(handle.Result);

            controller = handle.Result.GetComponent&lt;GameOverController&gt;();

            Assert.IsFalse(controller.Ready);

            _setup = <span class="hljs-literal">true</span>;

            Debug.Log(<span class="hljs-string">"Setup: controller is instantiated in test scene"</span>);
        }

        [<span class="hljs-meta">OneTimeTearDown</span>]
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">TearDown</span>(<span class="hljs-params"></span>)</span>
        {
            <span class="hljs-keyword">if</span> (controller != <span class="hljs-literal">null</span>)
            {
                Addressables.ReleaseInstance(controller.gameObject);
            }

            Debug.Log(<span class="hljs-string">"Teardown: controller reference is released"</span>);
        }

        [<span class="hljs-meta">UnityTest, Order(1), Timeout(5000)</span>]
        <span class="hljs-function"><span class="hljs-keyword">public</span> IEnumerator <span class="hljs-title">Step_1_Load</span>(<span class="hljs-params"></span>)</span>
        {
            controller.Load();

            <span class="hljs-function"><span class="hljs-keyword">yield</span> return new <span class="hljs-title">WaitUntil</span>(<span class="hljs-params">(</span>)</span> =&gt; controller.Ready);

            <span class="hljs-keyword">var</span> canvas = controller.gameObject.GetComponentInChildren&lt;Canvas&gt;(<span class="hljs-literal">true</span>);

            Assert.That(controller.transform.childCount &gt; <span class="hljs-number">0</span>);
            Assert.IsNotNull(canvas);
            Assert.IsFalse(canvas.gameObject.activeInHierarchy);

            Debug.Log(<span class="hljs-string">"Test: controller load passed"</span>);
            <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> delay;
        }

        [<span class="hljs-meta">UnityTest, Order(2)</span>]
        <span class="hljs-function"><span class="hljs-keyword">public</span> IEnumerator <span class="hljs-title">Step_2_Show</span>(<span class="hljs-params"></span>)</span>
        {
            controller.Show();

            <span class="hljs-keyword">var</span> canvas = controller.gameObject.GetComponentInChildren&lt;Canvas&gt;(<span class="hljs-literal">true</span>);
            Assert.IsTrue(canvas.gameObject.activeInHierarchy);

            Debug.Log(<span class="hljs-string">"Test: controller show passed"</span>);
            <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> delay;
        }

        [<span class="hljs-meta">UnityTest, Order(3)</span>]
        <span class="hljs-function"><span class="hljs-keyword">public</span> IEnumerator <span class="hljs-title">Step_3_Click_Play</span>(<span class="hljs-params"></span>)</span>
        {
            <span class="hljs-keyword">var</span> button = controller.gameObject.GetComponentInChildren&lt;Button&gt;(<span class="hljs-literal">true</span>);
            button.onClick.Invoke(); <span class="hljs-comment">// simulate button click</span>

            <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;

            <span class="hljs-comment">// canvas should be hidden after event is consumed</span>
            <span class="hljs-keyword">var</span> canvas = controller.gameObject.GetComponentInChildren&lt;Canvas&gt;(<span class="hljs-literal">true</span>);
            Assert.IsFalse(canvas.gameObject.activeInHierarchy);

            Debug.Log(<span class="hljs-string">"Test: play clicked passed"</span>);
            <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> delay;
        }

        [<span class="hljs-meta">UnityTest, Order(4)</span>]
        <span class="hljs-function"><span class="hljs-keyword">public</span> IEnumerator <span class="hljs-title">Step_4_Hide</span>(<span class="hljs-params"></span>)</span>
        {
            controller.Show();
            <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
            controller.Hide();

            <span class="hljs-keyword">var</span> canvas = controller.gameObject.GetComponentInChildren&lt;Canvas&gt;(<span class="hljs-literal">true</span>);
            Assert.IsFalse(canvas.gameObject.activeInHierarchy);

            Debug.Log(<span class="hljs-string">"Test: controller hide passed"</span>);
            <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> delay;
        }

        [<span class="hljs-meta">UnityTest, Order(5)</span>]
        <span class="hljs-function"><span class="hljs-keyword">public</span> IEnumerator <span class="hljs-title">Step_5_Unload</span>(<span class="hljs-params"></span>)</span>
        {
            controller.Unload();
            <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> delay;
            Assert.That(controller.transform.childCount == <span class="hljs-number">0</span>);
            Debug.Log(<span class="hljs-string">"Test: controller unload passed"</span>);
        }

    }

}
</code></pre>
<h2 id="heading-reconfigure-prefabs">Reconfigure Prefabs</h2>
<p>Select the "LoadingController" prefab in the <code>Project</code> and assign the "LoadingCanvas" prefab variant into the <code>Canvas Prefab</code> field in the <code>Inspector</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666273108129/tKeXbwabO.png" alt="image.png" /></p>
<p>Select the "MenuController" prefab in the <code>Project</code> and assign the "MenuCanvas" prefab variant into the <code>Canvas Prefab</code> and the <code>On Play Event</code> fields in the <code>Inspector</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666273165517/3N12cSxyL.png" alt="image.png" /></p>
<h2 id="heading-ui-manager">UI Manager</h2>
<p>We've got the screens we need for this very simple UI, now we need way to orchestrate them. Let's create the UIManager script now in "Scripts\UI".</p>
<pre><code class="lang-c#">
<span class="hljs-keyword">using</span> System.Collections;
<span class="hljs-keyword">using</span> UnityEngine;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">SingleScenery</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">UIManager</span> : <span class="hljs-title">MonoBehaviour</span>
    {

        [<span class="hljs-meta">SerializeField</span>] <span class="hljs-keyword">private</span> LoadingController loadingController;
        [<span class="hljs-meta">SerializeField</span>] <span class="hljs-keyword">private</span> MenuController menuController;
        [<span class="hljs-meta">SerializeField</span>] <span class="hljs-keyword">private</span> GameOverController gameOverController;

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">ShowLoading</span>(<span class="hljs-params"></span>)</span>
        {
            <span class="hljs-keyword">if</span> (loadingController.Ready)
            {
                HideAll();
                loadingController.Show();
            }
        }

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">ShowMenu</span>(<span class="hljs-params"></span>)</span>
        {
            <span class="hljs-keyword">if</span> (menuController.Ready)
            {
                HideAll();
                menuController.Show();
            }
        }

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">ShowGameOver</span>(<span class="hljs-params"></span>)</span>
        {
            <span class="hljs-keyword">if</span> (gameOverController.Ready)
            {
                HideAll();
                gameOverController.Show();
            }
        }

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">HideAll</span>(<span class="hljs-params"></span>)</span>
        {
            loadingController.Hide();
            menuController.Hide();
            gameOverController.Hide();
        }

        <span class="hljs-function"><span class="hljs-keyword">public</span> IEnumerator <span class="hljs-title">LoadAssets</span>(<span class="hljs-params"></span>)</span>
        {
            <span class="hljs-function"><span class="hljs-keyword">yield</span> return <span class="hljs-title">StartCoroutine</span>(<span class="hljs-params">loadingController.LoadAsync(</span>))</span>;

            <span class="hljs-function"><span class="hljs-keyword">yield</span> return <span class="hljs-title">StartCoroutine</span>(<span class="hljs-params">menuController.LoadAsync(</span>))</span>;

            <span class="hljs-function"><span class="hljs-keyword">yield</span> return <span class="hljs-title">StartCoroutine</span>(<span class="hljs-params">gameOverController.LoadAsync(</span>))</span>;

        }

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">UnloadAssets</span>(<span class="hljs-params"></span>)</span>
        {
            loadingController.Unload();
            menuController.Unload();
            gameOverController.Unload();
        }

    }
}
</code></pre>
<p>Not too complicated, the public API will allow the <code>GameManager</code> to choose when to load and unload assets, as well as methods to show and hide screens. </p>
<p>Drag this script onto the "UI" gameobject in the <code>Scene</code>, then drag the object to the "Bundles" folder in the <code>Project</code> to make it a prefab asset. Rename the prefab to "UIManager".</p>
<p>Mark the prefab as <code>Addressable</code> with the address "UIManager".</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666272434721/tb5zGoS5t.png" alt="image.png" /></p>
<h2 id="heading-ui-manager-testing">UI Manager Testing</h2>
<p>Just like the controllers, we can write tests to load the <code>UIManager</code> as an addressable asset and execute its public API to verify the prefab is working.</p>
<pre><code class="lang-c#"><span class="hljs-keyword">using</span> System.Collections;
<span class="hljs-keyword">using</span> NUnit.Framework;
<span class="hljs-keyword">using</span> UnityEngine;
<span class="hljs-keyword">using</span> UnityEngine.AddressableAssets;
<span class="hljs-keyword">using</span> UnityEngine.ResourceManagement.AsyncOperations;
<span class="hljs-keyword">using</span> UnityEngine.TestTools;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">SingleScenery</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">UIManager_Tests</span>
    {
        <span class="hljs-comment">// Test Settings</span>

        <span class="hljs-keyword">const</span> <span class="hljs-keyword">string</span> ADDRESS = <span class="hljs-string">"UIManager"</span>;
        <span class="hljs-keyword">const</span> <span class="hljs-keyword">float</span> DELAY = <span class="hljs-number">3f</span>;

        WaitForSeconds delay;
        UIManager manager;
        <span class="hljs-keyword">bool</span> _setup;

        [<span class="hljs-meta">OneTimeSetUp</span>]
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OneTimeSetUp</span>(<span class="hljs-params"></span>)</span>
        {
            <span class="hljs-keyword">if</span> (GameObject.FindObjectOfType&lt;Camera&gt;() == <span class="hljs-literal">null</span>)
            {
                <span class="hljs-keyword">new</span> GameObject(<span class="hljs-string">"Camera"</span>).AddComponent&lt;Camera&gt;();
            }

            delay = <span class="hljs-keyword">new</span> WaitForSeconds(DELAY);
        }

        [<span class="hljs-meta">UnitySetUp</span>]
        <span class="hljs-function"><span class="hljs-keyword">public</span> IEnumerator <span class="hljs-title">Setup</span>(<span class="hljs-params"></span>)</span>
        {

            <span class="hljs-keyword">if</span> (_setup) <span class="hljs-keyword">yield</span> <span class="hljs-keyword">break</span>;

            <span class="hljs-keyword">var</span> handle = Addressables.InstantiateAsync(ADDRESS);
            <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> handle;  <span class="hljs-comment">// wait for async call completion</span>

            Assert.That(handle.Status == AsyncOperationStatus.Succeeded);
            Assert.IsNotNull(handle.Result);

            manager = handle.Result.GetComponent&lt;UIManager&gt;();

            _setup = <span class="hljs-literal">true</span>;

            Debug.Log(<span class="hljs-string">"Setup: manager is instantiated in test scene"</span>);
        }

        [<span class="hljs-meta">OneTimeTearDown</span>]
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">TearDown</span>(<span class="hljs-params"></span>)</span>
        {
            <span class="hljs-keyword">if</span> (manager != <span class="hljs-literal">null</span>)
            {
                Addressables.ReleaseInstance(manager.gameObject);
            }

            Debug.Log(<span class="hljs-string">"Teardown: reference is released"</span>);
        }

        [<span class="hljs-meta">UnityTest</span>]
        <span class="hljs-function"><span class="hljs-keyword">public</span> IEnumerator <span class="hljs-title">Show_And_Hide_Loading</span>(<span class="hljs-params"></span>)</span>
        {

            <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> manager.StartCoroutine(manager.LoadAssets());

            <span class="hljs-keyword">var</span> canvases = manager.GetComponentsInChildren&lt;Canvas&gt;(<span class="hljs-literal">true</span>);

            Assert.That(canvases.Length == <span class="hljs-number">3</span>);

            manager.ShowLoading();

            <span class="hljs-keyword">var</span> canvas = manager.GetComponentInChildren&lt;Canvas&gt;();

            Assert.IsNotNull(canvas);
            Assert.IsTrue(canvas.gameObject.activeInHierarchy);

            <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> delay;

            manager.HideAll();

            Assert.IsFalse(canvas.gameObject.activeInHierarchy);

            manager.UnloadAssets();

            <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> delay;

            canvases = manager.GetComponentsInChildren&lt;Canvas&gt;(<span class="hljs-literal">true</span>);

            Assert.IsEmpty(canvases);

            Debug.Log(<span class="hljs-string">"Test: manager show and hide loading passed"</span>);

        }

        [<span class="hljs-meta">UnityTest</span>]
        <span class="hljs-function"><span class="hljs-keyword">public</span> IEnumerator <span class="hljs-title">Show_And_Hide_Menu</span>(<span class="hljs-params"></span>)</span>
        {

            <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> manager.StartCoroutine(manager.LoadAssets());

            <span class="hljs-keyword">var</span> canvases = manager.GetComponentsInChildren&lt;Canvas&gt;(<span class="hljs-literal">true</span>);

            Assert.That(canvases.Length == <span class="hljs-number">3</span>);

            manager.ShowMenu();

            <span class="hljs-keyword">var</span> canvas = manager.GetComponentInChildren&lt;Canvas&gt;();
            <span class="hljs-keyword">var</span> controller = manager.GetComponentInChildren&lt;MenuController&gt;();

            Assert.IsNotNull(canvas);
            Assert.IsTrue(canvas.gameObject.activeInHierarchy);
            Assert.IsNotNull(controller);

            <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> delay;

            manager.HideAll();

            Assert.IsFalse(canvas.gameObject.activeInHierarchy);

            manager.UnloadAssets();

            <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> delay;

            canvases = manager.GetComponentsInChildren&lt;Canvas&gt;(<span class="hljs-literal">true</span>);

            Assert.IsEmpty(canvases);

            Debug.Log(<span class="hljs-string">"Test: manager show and hide menu passed"</span>);

        }

        [<span class="hljs-meta">UnityTest</span>]
        <span class="hljs-function"><span class="hljs-keyword">public</span> IEnumerator <span class="hljs-title">Show_And_Hide_GameOver</span>(<span class="hljs-params"></span>)</span>
        {
            <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> manager.StartCoroutine(manager.LoadAssets());

            <span class="hljs-keyword">var</span> canvases = manager.GetComponentsInChildren&lt;Canvas&gt;(<span class="hljs-literal">true</span>);

            Assert.That(canvases.Length == <span class="hljs-number">3</span>);

            manager.ShowGameOver();

            <span class="hljs-keyword">var</span> canvas = manager.GetComponentInChildren&lt;Canvas&gt;();
            <span class="hljs-keyword">var</span> controller = manager.GetComponentInChildren&lt;GameOverController&gt;();

            Assert.IsNotNull(canvas);
            Assert.IsTrue(canvas.gameObject.activeInHierarchy);
            Assert.IsNotNull(controller);

            <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> delay;

            manager.HideAll();

            Assert.IsFalse(canvas.gameObject.activeInHierarchy);

            manager.UnloadAssets();

            <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> delay;

            canvases = manager.GetComponentsInChildren&lt;Canvas&gt;(<span class="hljs-literal">true</span>);

            Assert.IsEmpty(canvases);

            Debug.Log(<span class="hljs-string">"Test: manager show and hide game over passed"</span>);

        }

    }

}
</code></pre>
<p>Run the tests in the <code>Test Runner</code> to verify everything is working.  If you follow along in the <code>Addressables Event Viewer</code> you'll see that we load all the assets just to show one screen. We can easily optimize this later if needed by adding discrete methods to the UI Manager, but we'll come back to that when optimizing later (if needed).</p>
<h2 id="heading-game-manager">Game Manager</h2>
<p>Now that the UI Manager is working, we just need to hook it up to the GameManager, and then implement our first game workflow, which is to display the loading screen and the menu.</p>
<p>Go back to the "Scripts\GameManager.cs" file and edit:</p>
<pre><code class="lang-c#">
<span class="hljs-keyword">using</span> System.Collections;
<span class="hljs-keyword">using</span> UnityEngine;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">SingleScenery</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">GameManager</span> : <span class="hljs-title">MonoBehaviour</span>
    {

        <span class="hljs-comment">// Configuration</span>

        [<span class="hljs-meta">Header(<span class="hljs-meta-string">"Events"</span>)</span>]
        [<span class="hljs-meta">Space(10)</span>]

        [<span class="hljs-meta">SerializeField</span>] <span class="hljs-keyword">private</span> GameEvent onPlayEvent;

        [<span class="hljs-meta">Space(10)</span>]
        [<span class="hljs-meta">Header(<span class="hljs-meta-string">"Managers"</span>)</span>]
        [<span class="hljs-meta">Space(10)</span>]

        [<span class="hljs-meta">SerializeField</span>] <span class="hljs-keyword">private</span> UIManager uiManager;

        <span class="hljs-comment">// Unity Events</span>

        <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnEnable</span>(<span class="hljs-params"></span>)</span>
        {
            onPlayEvent.AddListener(OnPlay);
        }

        <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnDisable</span>(<span class="hljs-params"></span>)</span>
        {
            onPlayEvent.RemoveListener(OnPlay);
        }

        <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Start</span>(<span class="hljs-params"></span>)</span>
        {
            StartCoroutine(LoadGameWorkflow());
        }

        <span class="hljs-comment">// Game Event Handlers</span>

        <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnPlay</span>(<span class="hljs-params"></span>)</span>
        {
            Debug.Log(<span class="hljs-string">"Play!!!!"</span>);
        }

        <span class="hljs-comment">// Game Workflows</span>

        <span class="hljs-function"><span class="hljs-keyword">private</span> IEnumerator <span class="hljs-title">LoadGameWorkflow</span>(<span class="hljs-params"></span>)</span>
        {
            <span class="hljs-function"><span class="hljs-keyword">yield</span> return <span class="hljs-title">StartCoroutine</span>(<span class="hljs-params">uiManager.LoadAssets(</span>))</span>;
            uiManager.ShowLoading();

            <span class="hljs-comment">// Pretend we're loading more stuff here...</span>
            <span class="hljs-function"><span class="hljs-keyword">yield</span> return new <span class="hljs-title">WaitForSeconds</span>(<span class="hljs-params"><span class="hljs-number">3f</span></span>)</span>;

            uiManager.ShowMenu();
        }

    }
}
</code></pre>
<p>Select the "Game" root game object in the <code>Scene</code> and assign the "UI" game object in the scene to the <code>Ui Manager</code> field. Assign the "OnPlayEvent" to the <code>On Play Event</code> field.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666273293520/ymSkesJOG.png" alt="image.png" /></p>
<h2 id="heading-tests">Tests</h2>
<p>For now, open the "GameManager_Tests.cs" script and just delete the single test method completely.</p>
<p>Go to the <code>TestRunner</code> and <code>Run All</code> to verify everything should work.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666273613659/5ELANx8uI.png" alt="image.png" /></p>
<h2 id="heading-play">Play</h2>
<p>Finally we can play the game! Run play mode and you will see the loading screen display, then the menu. If you click the "PLAY" button, the menu should hide itself and a console log appear.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666273643089/2JFJ1HCn6.png" alt="image.png" /></p>
<h2 id="heading-summary">Summary</h2>
<p>We've completed our UI for the game with controllers and a manager that can load and unload assets at runtime. We have a Game Manager that can orchestrate the UI and respond to events to implement high level game workflows. We have some decent test coverage to ensure our prefabs don't break after changes.</p>
<p>Source Code for this Post:</p>
<p><a target="_blank" href="https://github.com/PlayableDesign/Unity-Single-Scenery/tree/GameOver">Github</a></p>
<h2 id="heading-next">Next</h2>
<p>Follow along to the next post in the series.</p>
<p><a target="_blank" href="https://playable.design/series/single-scenery">Unity Single Scene Architecture Series</a></p>
<p>If you're finding value in these posts, please leave a comment below to let me know (it's a lot of work) and share on your social media for others.  Thanks for reading!</p>
]]></content:encoded></item><item><title><![CDATA[Unity Single Scene Architecture - Menu]]></title><description><![CDATA[This is a post in a series about building a game in Unity using a single scene. 
Unity Single Scene Architecture Series
I will follow the same pattern of the previous post in which we built a loading screen as an Addressable asset along with tests. I...]]></description><link>https://playable.design/unity-single-scene-architecture-menu</link><guid isPermaLink="true">https://playable.design/unity-single-scene-architecture-menu</guid><category><![CDATA[unity]]></category><category><![CDATA[Unity Engine]]></category><category><![CDATA[Game Development]]></category><dc:creator><![CDATA[Steve Mcilwain]]></dc:creator><pubDate>Mon, 17 Oct 2022 16:28:12 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1666015170617/ie16F4lKb.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This is a post in a series about building a game in Unity using a single scene. </p>
<p><a target="_blank" href="https://playable.design/series/single-scenery">Unity Single Scene Architecture Series</a></p>
<p>I will follow the same pattern of the previous post in which we built a loading screen as an Addressable asset along with tests. If you didn't read the previous post, I recommend doing so.</p>
<h2 id="heading-framing-out-the-menu-screen">Framing Out the Menu Screen</h2>
<p>Let's start by framing out the game objects for the menu screen.</p>
<ul>
<li>Add a child empty game object underneath "UI" named "MenuController"</li>
<li>Add a child to "MenuController" from the context menu <code>UI &gt; Canvas</code> named "MenuCanvas"</li>
<li>Change the <code>Canvas Scaler</code> component to <code>Scale With Screen Size</code> with <code>ReferenceResolution</code> set to 1920 x 1080</li>
<li>Add a child to "MenuCanvas" from the context menu <code>UI &gt; Panel</code></li>
<li>Set the Image component <code>Color</code> to black with no transparency</li>
<li>Add a child to "Panel" from the context menu <code>UI &gt; Text - Text Mesh Pro</code> </li>
<li>Set the <code>Rect Transform</code> to middle stretch. set the Y position to 100</li>
<li>Set the text to "Single Scenery", center the horizontal and vertical alignments, set font size to 200</li>
<li>Add another child to "Panel" from the context menu <code>UI &gt; Button - Text Mesh Pro</code></li>
<li>Set the width to 300, height to 100, Y position to -200</li>
<li>Set the Text to "PLAY" with font size 64</li>
</ul>
<p>You should have a menu screen that looks like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666015737798/F2xKszRWU.png" alt="image.png" /></p>
<h2 id="heading-menu-events">Menu Events</h2>
<p>Unlike the loading screen our menu will have one or more interaction components like buttons. A Unity UGUI <a target="_blank" href="https://docs.unity3d.com/Packages/com.unity.ugui@1.0/manual/script-Button.html">Button</a> component exposes an <code>OnClick</code> event, that is often wired up in the editor. The problem with this approach is that this event will be handled by a parent object somewhere in the scene and I don't want a child to directly reference a parent, which could easily be the beginning of a spider web of dependencies.</p>
<p>Instead let's create a Menu component that will serve as a design time way to collect these interactable components together and can be accessed by a parent at runtime to wire up these events programmatically.</p>
<p>Create a new script in the "UI" folder called "Menu":</p>
<pre><code class="lang-c#"><span class="hljs-keyword">using</span> UnityEngine;
<span class="hljs-keyword">using</span> UnityEngine.UI;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">SingleScenery</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Menu</span> : <span class="hljs-title">MonoBehaviour</span>
    {

        [<span class="hljs-meta">SerializeField</span>] <span class="hljs-keyword">private</span> Button playButton;

        <span class="hljs-keyword">public</span> Button PlayButton =&gt; playButton;

    }
}
</code></pre>
<p>Drag this script on the "MenuCanvas" game object and select the button in the <code>PlayButton</code> field in the <code>Inspector</code>.  Now drag the MenuCanvas into the "Bundles" folder to make it a prefab and delete it from the scene. Mark MenuCanvas as <code>Addressable</code> using the address "MenuCanvas"</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666019307977/ro9aQtugO.png" alt="image.png" /></p>
<h2 id="heading-a-note-about-conventions">A Note About Conventions</h2>
<p>Why not just <code>public Button PlayButton</code>? The reason I use these conventions is to add guard rails for other developers or myself in a week when I've forgotten about this code. I always use <code>[SerializeField] private</code> for fields intended to be set in the editor, which is a Unity mechanism for dependency injection at design time. By making runtime access to this same field a public, read-only property then no one can mistakenly think this should be set at runtime, and the property will not be serialized or show up in the editor.</p>
<h2 id="heading-menucontroller">MenuController</h2>
<p>Other than the Menu component, this prefab asset is almost identical to the LoadingController, and if we write new code for the MenuController, it will look almost identical. When you see duplicate code it's a great indicator to refactor. </p>
<h2 id="heading-inheritance-or-composition">Inheritance or Composition?</h2>
<p>To refactor we have different ways to reuse the identical code. We could create a base class for all of the common code, then inherit from that base class and customize per "controller". Or we could follow the principal of composition over inheritance, which means we'd put the identical code in a controller component, then add a new component for the button related code. </p>
<p>In this case, I feel like inheritance is the best way to go, but remember these approaches, especially if inheritance starts becoming complicated. Then it may be time to switch to composition and creating several smaller components instead.</p>
<h2 id="heading-uicontroller-base-class">UIController Base Class</h2>
<p>Let's create a new script in the UI folder called "UIController" with the following code.</p>
<pre><code class="lang-c#"><span class="hljs-keyword">using</span> System.Collections;
<span class="hljs-keyword">using</span> UnityEngine;
<span class="hljs-keyword">using</span> UnityEngine.AddressableAssets;
<span class="hljs-keyword">using</span> UnityEngine.ResourceManagement.AsyncOperations;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">SingleScenery</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">UIController</span> : <span class="hljs-title">MonoBehaviour</span>
    {
        [<span class="hljs-meta">SerializeField</span>] <span class="hljs-keyword">private</span> AssetReference canvasPrefab;

        <span class="hljs-keyword">public</span> <span class="hljs-keyword">bool</span> Ready =&gt; _ready;
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">bool</span> _ready;

        <span class="hljs-keyword">private</span> GameObject _canvas;

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Load</span>(<span class="hljs-params"></span>)</span>
        {
            StartCoroutine(LoadAsync());
        }

        <span class="hljs-function"><span class="hljs-keyword">public</span> IEnumerator <span class="hljs-title">LoadAsync</span>(<span class="hljs-params"></span>)</span>
        {
            <span class="hljs-keyword">var</span> handle = Addressables.InstantiateAsync(canvasPrefab, transform);

            <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> handle;  <span class="hljs-comment">// wait for async call completion</span>

            <span class="hljs-keyword">if</span> (handle.Status == AsyncOperationStatus.Succeeded)
            {
                _canvas = handle.Result;
                _canvas.SetActive(<span class="hljs-literal">false</span>);
                _ready = <span class="hljs-literal">true</span>;
            }
        }

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Unload</span>(<span class="hljs-params"></span>)</span>
        {
            _ready = <span class="hljs-literal">false</span>;
            Addressables.ReleaseInstance(_canvas); <span class="hljs-comment">// will decrement refence count</span>
        }

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Show</span>(<span class="hljs-params"></span>)</span>
        {
            <span class="hljs-keyword">if</span> (_ready)
            {
                _canvas.SetActive(<span class="hljs-literal">true</span>);
            }
        }

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Hide</span>(<span class="hljs-params"></span>)</span>
        {
            <span class="hljs-keyword">if</span> (_ready)
            {
                _canvas.SetActive(<span class="hljs-literal">false</span>);
            }
        }
    }
}
</code></pre>
<p>Now our new MenuController script can be as simple as:</p>
<pre><code class="lang-c#"><span class="hljs-keyword">using</span> UnityEngine;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">SingleScenery</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">MenuController</span> : <span class="hljs-title">UIController</span>
    {
        [<span class="hljs-meta">SerializeField</span>] <span class="hljs-keyword">private</span> Menu menu;

        <span class="hljs-keyword">public</span> Menu Menu =&gt; menu;

    }
}
</code></pre>
<p>MenuController now has the same API as the LoadingController, with the addition of exposing its interactable menu components for the future wiring up of events.</p>
<h2 id="heading-refactor-loadingcontroller">Refactor LoadingController</h2>
<p>Don't forget, we need to go back and apply our refactoring to the loading controller, which is simple as this:</p>
<pre><code class="lang-c#"><span class="hljs-keyword">namespace</span> <span class="hljs-title">SingleScenery</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">LoadingController</span> : <span class="hljs-title">UIController</span>
    {
    }
}
</code></pre>
<p>Now let's go configure these changes in the editor:</p>
<ul>
<li>Drag the new MenuController script onto the game object in the scene</li>
<li>Drag the MenuCanvas prefab into the <code>Canvas Prefab</code> field of MenuController</li>
<li>Drag the MenuCanvas prefab into the <code>Menu</code> field of MenuController (Unity will find/assign the Menu component)</li>
<li>Drag the MenuController game object from the scene into the "Bundles" folder to make it a prefab</li>
<li>Mark the prefab as <code>Addressable</code> with address "MenuController"</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666020036342/8Q2fD5931.png" alt="image.png" /></p>
<h2 id="heading-loading-controller">Loading Controller</h2>
<p>We refactored and made some significant changes, did we break anything? Does LoadingController still work? Run your tests from the previous post!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666020155683/gFNGNkHYn.png" alt="image.png" /></p>
<p>Oops. Yeah we broke it. Select the LoadingController prefab in the <code>Project</code> and you'll see in the inspector that the <code>Canvas Prefab</code> field is now empty. That's because we changed the name of this field when replacing the controller code with the inherited version.  Assign the LoadingCanvas again then rerun the tests.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666020319617/2dS2V5WKg.png" alt="image.png" /></p>
<p>Everything should pass now, and while the test code is a pain, it probably just saved time investigating a bug or breaking the build for other team members. You're going to refactor a LOT. Investing in tests will help you keep momentum and not lose time with head scratching over bugs. </p>
<h2 id="heading-tests">Tests</h2>
<p>Speaking of tests, let's add more to cover our new code. Add a new test script "MenuController_Tests":</p>
<pre><code class="lang-c#"><span class="hljs-keyword">using</span> System.Collections;
<span class="hljs-keyword">using</span> NUnit.Framework;
<span class="hljs-keyword">using</span> UnityEngine;
<span class="hljs-keyword">using</span> UnityEngine.AddressableAssets;
<span class="hljs-keyword">using</span> UnityEngine.ResourceManagement.AsyncOperations;
<span class="hljs-keyword">using</span> UnityEngine.TestTools;
<span class="hljs-keyword">using</span> UnityEngine.UI;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">SingleScenery</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">MenuController_Tests</span>
    {
        <span class="hljs-comment">// Test Settings</span>

        <span class="hljs-keyword">const</span> <span class="hljs-keyword">string</span> ADDRESS = <span class="hljs-string">"MenuController"</span>;
        <span class="hljs-keyword">const</span> <span class="hljs-keyword">float</span> DELAY = <span class="hljs-number">3f</span>;

        WaitForSeconds delay;
        MenuController controller;
        <span class="hljs-keyword">bool</span> _setup;

        <span class="hljs-keyword">bool</span> _playClicked;

        [<span class="hljs-meta">OneTimeSetUp</span>]
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OneTimeSetUp</span>(<span class="hljs-params"></span>)</span>
        {
            <span class="hljs-keyword">if</span> (GameObject.FindObjectOfType&lt;Camera&gt;() == <span class="hljs-literal">null</span>)
            {
                <span class="hljs-keyword">new</span> GameObject(<span class="hljs-string">"Camera"</span>).AddComponent&lt;Camera&gt;();
            }

            delay = <span class="hljs-keyword">new</span> WaitForSeconds(DELAY);
        }

        [<span class="hljs-meta">UnitySetUp</span>]
        <span class="hljs-function"><span class="hljs-keyword">public</span> IEnumerator <span class="hljs-title">Setup</span>(<span class="hljs-params"></span>)</span>
        {
            <span class="hljs-keyword">if</span> (_setup) <span class="hljs-keyword">yield</span> <span class="hljs-keyword">break</span>;

            <span class="hljs-keyword">var</span> handle = Addressables.InstantiateAsync(ADDRESS);
            <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> handle;  <span class="hljs-comment">// wait for async call completion</span>

            Assert.That(handle.Status == AsyncOperationStatus.Succeeded);
            Assert.IsNotNull(handle.Result);

            controller = handle.Result.GetComponent&lt;MenuController&gt;();

            Assert.IsFalse(controller.Ready);

            _setup = <span class="hljs-literal">true</span>;

            Debug.Log(<span class="hljs-string">"Setup: controller is instantiated in test scene"</span>);
        }

        [<span class="hljs-meta">OneTimeTearDown</span>]
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">TearDown</span>(<span class="hljs-params"></span>)</span>
        {
            <span class="hljs-keyword">if</span> (controller != <span class="hljs-literal">null</span>)
            {
                Addressables.ReleaseInstance(controller.gameObject);
            }

            Debug.Log(<span class="hljs-string">"Teardown: controller reference is released"</span>);
        }

        [<span class="hljs-meta">UnityTest, Order(1), Timeout(5000)</span>]
        <span class="hljs-function"><span class="hljs-keyword">public</span> IEnumerator <span class="hljs-title">Step_1_Load</span>(<span class="hljs-params"></span>)</span>
        {
            controller.Load();

            <span class="hljs-function"><span class="hljs-keyword">yield</span> return new <span class="hljs-title">WaitUntil</span>(<span class="hljs-params">(</span>)</span> =&gt; controller.Ready);

            <span class="hljs-keyword">var</span> canvas = controller.gameObject.GetComponentInChildren&lt;Canvas&gt;(<span class="hljs-literal">true</span>);

            Assert.That(controller.transform.childCount &gt; <span class="hljs-number">0</span>);
            Assert.IsNotNull(canvas);
            Assert.IsFalse(canvas.gameObject.activeInHierarchy);

            Assert.IsNotNull(controller.Menu);
            Assert.IsNotNull(controller.Menu.PlayButton);

            Debug.Log(<span class="hljs-string">"Test: controller load passed"</span>);
            <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> delay;
        }

        [<span class="hljs-meta">UnityTest, Order(2)</span>]
        <span class="hljs-function"><span class="hljs-keyword">public</span> IEnumerator <span class="hljs-title">Step_2_Show</span>(<span class="hljs-params"></span>)</span>
        {
            controller.Show();

            <span class="hljs-keyword">var</span> canvas = controller.gameObject.GetComponentInChildren&lt;Canvas&gt;(<span class="hljs-literal">true</span>);
            Assert.IsTrue(canvas.gameObject.activeInHierarchy);

            Debug.Log(<span class="hljs-string">"Test: controller show passed"</span>);
            <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> delay;
        }

        [<span class="hljs-meta">UnityTest, Order(3)</span>]
        <span class="hljs-function"><span class="hljs-keyword">public</span> IEnumerator <span class="hljs-title">Step_3_Wire_Up_Button</span>(<span class="hljs-params"></span>)</span>
        {
            controller.Menu.PlayButton.onClick.AddListener(OnPlayClicked);
            controller.Menu.PlayButton.onClick.Invoke();
            controller.Menu.PlayButton.onClick.RemoveListener(OnPlayClicked);

            Assert.IsTrue(_playClicked);

            Debug.Log(<span class="hljs-string">"Test: controller button was clicked and handled"</span>);
            <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> delay;
        }

        <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnPlayClicked</span>(<span class="hljs-params"></span>)</span>
        {
            _playClicked = <span class="hljs-literal">true</span>;
        }

        [<span class="hljs-meta">UnityTest, Order(4)</span>]
        <span class="hljs-function"><span class="hljs-keyword">public</span> IEnumerator <span class="hljs-title">Step_4_Hide</span>(<span class="hljs-params"></span>)</span>
        {
            controller.Hide();

            <span class="hljs-keyword">var</span> canvas = controller.gameObject.GetComponentInChildren&lt;Canvas&gt;(<span class="hljs-literal">true</span>);
            Assert.IsFalse(canvas.gameObject.activeInHierarchy);

            Debug.Log(<span class="hljs-string">"Test: controller hide passed"</span>);
            <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> delay;
        }

        [<span class="hljs-meta">UnityTest, Order(5)</span>]
        <span class="hljs-function"><span class="hljs-keyword">public</span> IEnumerator <span class="hljs-title">Step_5_Unload</span>(<span class="hljs-params"></span>)</span>
        {
            controller.Unload();
            <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> delay;
            Assert.That(controller.transform.childCount == <span class="hljs-number">0</span>);
            Debug.Log(<span class="hljs-string">"Test: controller unload passed"</span>);
        }

    }

}
</code></pre>
<p>Yes, a lot of this code is duplicated from the LoadingController_Tests, but that's for another day and another post. Go back to <code>TestRunner</code> and <code>Run All</code>, everything should pass.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1666023081347/pAlrprYc4.png" alt="image.png" /></p>
<p>You can also follow along in the <code>Addressables Event Viewer</code> to see the loading and unloading of assets.</p>
<h2 id="heading-summary">Summary</h2>
<p>We've added a menu to the game, reused the patterns from the loading screen and done some light refactoring to reduce duplicate code. In addition, our tests saved us from committing a broken prefab to source control.</p>
<p>Source Code for this Post:</p>
<p><a target="_blank" href="https://github.com/PlayableDesign/Unity-Single-Scenery/tree/Menu">Github</a></p>
<h2 id="heading-next">Next</h2>
<p>Follow along to the next post in the series.</p>
<p><a target="_blank" href="https://playable.design/series/single-scenery">Unity Single Scene Architecture Series</a></p>
<p>If you're finding value in these posts, please leave a comment below to let me know (it's a lot of work) and share on your social media for others.  Thanks for reading!</p>
]]></content:encoded></item><item><title><![CDATA[Unity Single Scene Architecture - Loading]]></title><description><![CDATA[This is a post in a series about building a game in Unity using a single scene. 
Unity Single Scene Architecture Series
In this post, we'll build a loading screen and talk all about loading assets in Unity. The loading screen will display as the game...]]></description><link>https://playable.design/unity-single-scene-architecture-loading</link><guid isPermaLink="true">https://playable.design/unity-single-scene-architecture-loading</guid><category><![CDATA[unity]]></category><category><![CDATA[Unity Engine]]></category><category><![CDATA[Game Development]]></category><dc:creator><![CDATA[Steve Mcilwain]]></dc:creator><pubDate>Sun, 16 Oct 2022 19:53:54 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1665851436531/DSyXkz-2R.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This is a post in a series about building a game in Unity using a single scene. </p>
<p><a target="_blank" href="https://playable.design/series/single-scenery">Unity Single Scene Architecture Series</a></p>
<p>In this post, we'll build a loading screen and talk all about loading assets in Unity. The loading screen will display as the game starts. We'll also use it when loading levels and anywhere else that the player may need to wait on a long-running loading activity.</p>
<h2 id="heading-framing-out-the-loading-screen">Framing Out the Loading Screen</h2>
<p>Let's start by framing out the game objects for the loading screen.</p>
<ul>
<li>Add a child empty game object underneath "UI" named "LoadingController"</li>
<li>Add a child to "LoadingController" from the context menu <code>UI &gt; Canvas</code> named "LoadingCanvas"</li>
<li>Change the <code>Canvas Scaler</code> component to <code>Scale With Screen Size</code> with <code>ReferenceResolution</code> set to 1920 x 1080</li>
<li>Add a child to "LoadingCanvas" from the context menu <code>UI &gt; Panel</code></li>
<li>Set the Image component <code>Color</code> to black with no transparency</li>
<li>Add a child to "Panel" from the context menu <code>UI &gt; Text - Text Mesh Pro</code> (Import TMP Essentials if prompted)</li>
<li>Set the <code>Rect Transform</code> to middle stretch</li>
<li>Set the text to "Loading", center the horizontal and vertical alignments, set font size to 200</li>
</ul>
<p>You should have a loading screen that looks like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665929379676/8Q0SeF4NS.png" alt="image.png" /></p>
<h2 id="heading-loading-screen-prefab">Loading Screen - Prefab</h2>
<p>Create a prefab for the "LoadingCanvas". In the <code>Project</code> window create a new folder "Bundles" (this will be relevant in a later part of the series dealing with building asset bundles). Select the "LoadingCanvas" game object in the scene and drag it into the newly created Bundles folder to make it a prefab. Now you can delete "LoadingCanvas" from the scene.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665929730302/a41EzYIGO.png" alt="image.png" /></p>
<h2 id="heading-loading-screen-scripting">Loading Screen - Scripting</h2>
<p>In the <code>Project</code> under "Scripts" create a new folder called "UI", then add a new script named "LoadingController".</p>
<p>We will add methods for loading and unloading assets at runtime, which will be available later when optimizing memory usage vs load times. We also need methods to show and hide the loading screen once loaded and instantiated.</p>
<p>Add the following code to LoadingController:</p>
<pre><code class="lang-c#"><span class="hljs-keyword">using</span> System.Collections;
<span class="hljs-keyword">using</span> UnityEngine;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">SingleScenery</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">LoadingController</span> : <span class="hljs-title">MonoBehaviour</span>
    {
        [<span class="hljs-meta">SerializeField</span>] <span class="hljs-keyword">private</span> GameObject loadingCanvasPrefab;

        <span class="hljs-keyword">public</span> <span class="hljs-keyword">bool</span> Ready =&gt; _ready;
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">bool</span> _ready;

        <span class="hljs-keyword">private</span> GameObject _loadingCanvas;

        <span class="hljs-function"><span class="hljs-keyword">public</span> IEnumerator <span class="hljs-title">Load</span>(<span class="hljs-params"></span>)</span>
        {
            _loadingCanvas = Instantiate(loadingCanvasPrefab, transform);
            <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
            _ready = <span class="hljs-literal">true</span>;
        }

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Unload</span>(<span class="hljs-params"></span>)</span>
        {
            _ready = <span class="hljs-literal">false</span>;
            Destroy(_loadingCanvas);
        }

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Show</span>(<span class="hljs-params"></span>)</span>
        {
            <span class="hljs-keyword">if</span> (_ready)
            {
                _loadingCanvas.SetActive(<span class="hljs-literal">true</span>);
            }
        }

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Hide</span>(<span class="hljs-params"></span>)</span>
        {
            <span class="hljs-keyword">if</span> (_ready)
            {
                _loadingCanvas.SetActive(<span class="hljs-literal">false</span>);
            }
        }

    }
}
</code></pre>
<p>Nothing exciting here, you've probably seen this prefab pattern in many other examples. We'll change this shortly, but it is relevant to understanding why.</p>
<h2 id="heading-a-brief-explanation-of-assets-and-instantiation">A Brief Explanation of Assets and Instantiation</h2>
<p>I think of an "asset" in Unity as anything that is not source code (a script). A sprite, an audio file, a mesh, a material, instances of scriptable objects, and prefabs are all common examples. Assets get packaged at build time with the player via different methods depending on how they are organized.</p>
<p>Assets are often directly referenced by game objects in a scene, which by default get loaded into memory with the scene at runtime (<strong>even if they are not instantiated</strong>).</p>
<p>Now our current LoadingController references a prefab, which will be loaded into memory even before being instantiated and remain there until the scene is unloaded. We don't want that because <strong>this pattern will break the single scene architecture</strong>. </p>
<p>If every prefab used in the game is referenced in the scene (UI, levels, models, audio, etc) they will all be loaded into memory and likely make the game unplayable on most platforms. This is why we typically break up games into multiple scenes...</p>
<h2 id="heading-introducing-addressables">Introducing Addressables</h2>
<p>Instead of hard referencing the loading canvas prefab, let's use <code>Addressables</code> to instead load and unload the prefab on demand using a "weak reference". This means when the scene loads, the prefab being referenced will not be loaded into memory. </p>
<p>Let's learn a few core things about <code>Addressables</code>:</p>
<ul>
<li>Each asset marked as an Addressable has a string address that identifies it and can be used to load it without knowing where it exists in the project</li>
<li>The <code>Addressables</code> API is mostly asynchronous, which can have rippling effects when added to an existing code base. I'll be using <code>Coroutines</code> to encapsulate these async API calls</li>
<li><code>Addressables</code> tracks reference counts to loaded assets, you must also use Addressables to unload assets to avoid resource leaks</li>
</ul>
<p><a target="_blank" href="https://docs.unity3d.com/Packages/com.unity.addressables@1.19/manual/AddressableAssetsGettingStarted.html">Addressables</a></p>
<h2 id="heading-refactoring-for-weak-references">Refactoring for Weak References</h2>
<p>Let's change the loading controller code to use the <code>Addressables</code> API.</p>
<pre><code class="lang-c#"><span class="hljs-keyword">using</span> System.Collections;
<span class="hljs-keyword">using</span> UnityEngine;
<span class="hljs-keyword">using</span> UnityEngine.AddressableAssets;
<span class="hljs-keyword">using</span> UnityEngine.ResourceManagement.AsyncOperations;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">SingleScenery</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">LoadingController</span> : <span class="hljs-title">MonoBehaviour</span>
    {
        [<span class="hljs-meta">SerializeField</span>] <span class="hljs-keyword">private</span> AssetReference loadingCanvasPrefab;

        <span class="hljs-keyword">public</span> <span class="hljs-keyword">bool</span> Ready =&gt; _ready;
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">bool</span> _ready;

        <span class="hljs-keyword">private</span> GameObject _loadingCanvas;

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Load</span>(<span class="hljs-params"></span>)</span>
        {
            StartCoroutine(LoadAsync());
        }

        <span class="hljs-function"><span class="hljs-keyword">public</span> IEnumerator <span class="hljs-title">LoadAsync</span>(<span class="hljs-params"></span>)</span>
        {
            <span class="hljs-keyword">var</span> handle = Addressables.InstantiateAsync(loadingCanvasPrefab, transform);

            <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> handle;  <span class="hljs-comment">// wait for async call completion</span>

            <span class="hljs-keyword">if</span> (handle.Status == AsyncOperationStatus.Succeeded)
            {
                _loadingCanvas = handle.Result;
                _loadingCanvas.SetActive(<span class="hljs-literal">false</span>);
                _ready = <span class="hljs-literal">true</span>;
            }
        }

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Unload</span>(<span class="hljs-params"></span>)</span>
        {
            _ready = <span class="hljs-literal">false</span>;
            Addressables.ReleaseInstance(_loadingCanvas); <span class="hljs-comment">// will decrement refence count</span>
        }

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Show</span>(<span class="hljs-params"></span>)</span>
        {
            <span class="hljs-keyword">if</span> (_ready)
            {
                _loadingCanvas.SetActive(<span class="hljs-literal">true</span>);
            }
        }

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Hide</span>(<span class="hljs-params"></span>)</span>
        {
            <span class="hljs-keyword">if</span> (_ready)
            {
                _loadingCanvas.SetActive(<span class="hljs-literal">false</span>);
            }
        }

    }
}
</code></pre>
<p>You'll see some of the following changes:</p>
<ul>
<li>Changed the prefab reference to be an <code>AssetReference</code>, which requires an asset marked as Addressable</li>
<li>Changed the loading and unloading code to use the <code>Addressables</code> API</li>
</ul>
<p>If you read the docs, you'll quickly run into several different ways to accomplish the same thing. We're starting by using one of the easiest APIs in the package, which combines loading the asset and instantiating into one async call. When this is completed, which is trackable with the "handle", the prefab is not only loaded into memory but also instantiated into the game scene. There are other API calls we could use to do this in two steps (load and then instantiate), but we don't require that for now. </p>
<h2 id="heading-configuration">Configuration</h2>
<p>Switch back to the editor to setup 'Addressables'. You should have an error because we're missing an Assembly Definition Reference in the SingleScenery assembly definition file. Select it and add "Unity.ResourceManager" to the references, then <code>Apply</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665934893381/qqRI1MHyi.png" alt="image.png" /></p>
<p>Do the same for the SingleScenery.Tests assembly definition and <code>Apply</code>.</p>
<p>Welcome to the pains of assembly definitions... if you ever forget and find yourself unable to add a using statement for no reason, it's probably because of this.</p>
<p>Select the "LoadingCanvas" prefab in the <code>Project</code> and then check the <code>Addressable</code> field in the <code>Inspector</code>. The address will default to the project path but can be anything you put here. Let's just keep it simple and set the address to "LoadingCanvas".</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665934781926/OkWO9VrQl.png" alt="image.png" /></p>
<p>Also note that the package has initialized its configuration in the "AddressableAssetsData" folder.  We'll come back to that later.</p>
<p>Now select the "LoadingController" in the <code>Scene</code> and add the "LoadingCanvas" addressable asset to the field for "LoadingCanvasPrefab".  Let's also prefab the controller and make it an Addressable.  Drag it into the "Bundles" folder, check <code>Addressable</code> and name it "LoadingController".</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665935117371/m_LVkjHfI.png" alt="image.png" /></p>
<h2 id="heading-testing">Testing</h2>
<p>How do we know our LoadingController works? It's not wired up in the scene yet and we don't want to always have to manually check if this prefab is working or not. Let's add a PlayMode test.</p>
<p>Create a "UI" folder underneath "Tests" and add a new test script called "LoadingController_Tests".</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665935240496/2JJ84xyXm.png" alt="image.png" /></p>
<p>Another benefit of using <code>Addressables</code> is that we can load and unload assets easily from our tests, which avoids a lot of boilerplate code.  To setup a temporary scene the test script will need to:</p>
<ul>
<li>Add a camera to the scene, so we can visually inspect the test run if needed</li>
<li>Load and instantiate the "LoadingController" prefab asset using <code>Addressables</code></li>
<li>Clean up after the tests have run</li>
</ul>
<p>Our actual tests should cover the public API that will be used at runtime (Load, Show, Hide, and Unload).</p>
<p>Before adding our test code, we'll be referencing new packages <code>UnityEngine.UI</code> and <code>Unity.TextMeshPro</code>. Add them to the "SingleScenery.Test" assembly definition file. Be sure to click <code>Apply</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665946474132/Snl5ZHI_n.png" alt="image.png" /></p>
<p>Open the new test script add the following test code:</p>
<pre><code class="lang-c#"><span class="hljs-keyword">using</span> System.Collections;
<span class="hljs-keyword">using</span> NUnit.Framework;
<span class="hljs-keyword">using</span> TMPro;
<span class="hljs-keyword">using</span> UnityEngine;
<span class="hljs-keyword">using</span> UnityEngine.AddressableAssets;
<span class="hljs-keyword">using</span> UnityEngine.ResourceManagement.AsyncOperations;
<span class="hljs-keyword">using</span> UnityEngine.TestTools;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">SingleScenery</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">LoadingController_Tests</span>
    {
        <span class="hljs-comment">// Test Settings</span>

        <span class="hljs-keyword">const</span> <span class="hljs-keyword">string</span> ADDRESS = <span class="hljs-string">"LoadingController"</span>;
        <span class="hljs-keyword">const</span> <span class="hljs-keyword">string</span> LOADING = <span class="hljs-string">"LOADING"</span>;
        <span class="hljs-keyword">const</span> <span class="hljs-keyword">float</span> DELAY = <span class="hljs-number">3f</span>;

        WaitForSeconds delay;
        LoadingController controller;
        <span class="hljs-keyword">bool</span> _setup;

        [<span class="hljs-meta">OneTimeSetUp</span>]
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OneTimeSetUp</span>(<span class="hljs-params"></span>)</span>
        {
            <span class="hljs-keyword">new</span> GameObject(<span class="hljs-string">"Camera"</span>).AddComponent&lt;Camera&gt;();
            delay = <span class="hljs-keyword">new</span> WaitForSeconds(DELAY);
        }

        [<span class="hljs-meta">UnitySetUp</span>]
        <span class="hljs-function"><span class="hljs-keyword">public</span> IEnumerator <span class="hljs-title">Setup</span>(<span class="hljs-params"></span>)</span>
        {
            <span class="hljs-keyword">if</span> (_setup) <span class="hljs-keyword">yield</span> <span class="hljs-keyword">break</span>;

            <span class="hljs-keyword">var</span> handle = Addressables.InstantiateAsync(ADDRESS);
            <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> handle;  <span class="hljs-comment">// wait for async call completion</span>

            Assert.That(handle.Status == AsyncOperationStatus.Succeeded);
            Assert.IsNotNull(handle.Result);

            controller = handle.Result.GetComponent&lt;LoadingController&gt;();

            Assert.IsFalse(controller.Ready);

            _setup = <span class="hljs-literal">true</span>;

            Debug.Log(<span class="hljs-string">"Setup: controller is instantiated in test scene"</span>);
        }

        [<span class="hljs-meta">OneTimeTearDown</span>]
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">TearDown</span>(<span class="hljs-params"></span>)</span>
        {
            <span class="hljs-keyword">if</span> (controller != <span class="hljs-literal">null</span>)
            {
                Addressables.ReleaseInstance(controller.gameObject);
            }

            Debug.Log(<span class="hljs-string">"Teardown: controller reference is released"</span>);
        }

        [<span class="hljs-meta">UnityTest, Order(1), Timeout(5000)</span>]
        <span class="hljs-function"><span class="hljs-keyword">public</span> IEnumerator <span class="hljs-title">Step_1_Load</span>(<span class="hljs-params"></span>)</span>
        {
            controller.Load();

            <span class="hljs-function"><span class="hljs-keyword">yield</span> return new <span class="hljs-title">WaitUntil</span>(<span class="hljs-params">(</span>)</span> =&gt; controller.Ready);

            <span class="hljs-keyword">var</span> canvas = controller.gameObject.GetComponentInChildren&lt;Canvas&gt;(<span class="hljs-literal">true</span>);
            <span class="hljs-keyword">var</span> text = controller.gameObject.GetComponentInChildren&lt;TMP_Text&gt;(<span class="hljs-literal">true</span>);

            Assert.That(controller.transform.childCount &gt; <span class="hljs-number">0</span>);
            Assert.IsNotNull(canvas);
            Assert.IsNotNull(text);
            Assert.IsFalse(canvas.gameObject.activeInHierarchy);
            Assert.That(text.text == LOADING);

            Debug.Log(<span class="hljs-string">"Test: controller load passed"</span>);
            <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> delay;
        }

        [<span class="hljs-meta">UnityTest, Order(2)</span>]
        <span class="hljs-function"><span class="hljs-keyword">public</span> IEnumerator <span class="hljs-title">Step_2_Show</span>(<span class="hljs-params"></span>)</span>
        {
            controller.Show();

            <span class="hljs-keyword">var</span> canvas = controller.gameObject.GetComponentInChildren&lt;Canvas&gt;(<span class="hljs-literal">true</span>);
            Assert.IsTrue(canvas.gameObject.activeInHierarchy);

            Debug.Log(<span class="hljs-string">"Test: controller show passed"</span>);
            <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> delay;
        }

        [<span class="hljs-meta">UnityTest, Order(3)</span>]
        <span class="hljs-function"><span class="hljs-keyword">public</span> IEnumerator <span class="hljs-title">Step_3_Hide</span>(<span class="hljs-params"></span>)</span>
        {
            controller.Hide();

            <span class="hljs-keyword">var</span> canvas = controller.gameObject.GetComponentInChildren&lt;Canvas&gt;(<span class="hljs-literal">true</span>);
            Assert.IsFalse(canvas.gameObject.activeInHierarchy);

            Debug.Log(<span class="hljs-string">"Test: controller hide passed"</span>);
            <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> delay;
        }

        [<span class="hljs-meta">UnityTest, Order(4)</span>]
        <span class="hljs-function"><span class="hljs-keyword">public</span> IEnumerator <span class="hljs-title">Step_4_Unload</span>(<span class="hljs-params"></span>)</span>
        {
            controller.Unload();
            <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> delay;
            Assert.That(controller.transform.childCount == <span class="hljs-number">0</span>);
            Debug.Log(<span class="hljs-string">"Test: controller unload passed"</span>);
        }

    }

}
</code></pre>
<p>Tricky stuff... the <code>Unity Test Framework</code> is basically NUnit with modifications to add in support for coroutines, which we need with the async portions of our code. Now go back to the editor, use the <code>TestRunner</code> to execute all of the tests with <code>Run All</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665946682199/7HmUUNN2K.png" alt="image.png" /></p>
<p>You can follow along in the game view and watch the loading screen display, but how do we know that these assets are loading and unloading in memory?</p>
<p>Go to <code>Window &gt; Asset Management &gt; Addressables &gt; Settings</code> which will select the global settings for <code>Addressables</code> in the project. In the <code>Inspector</code>, check <code>Send Profiler Events</code>.  Now open the Addressables Event Viewer with <code>Window &gt; Asset Management &gt; Addressables &gt; Event Viewer</code>.  Keep this open and then run all of the tests again.</p>
<p>After "Load" is called, you will see the "LoadingCanvas" prefab is loaded into memory.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665947188072/a-UY4cAEc.png" alt="image.png" /></p>
<p>After "Unload" is called, you will see it is also removed from memory.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665947218148/PCDnXvn_j.png" alt="image.png" /></p>
<h2 id="heading-summary">Summary</h2>
<p>Wow! That's a lot for just a loading screen, right? While true, I hope this demonstrates the power behind <code>Addressables</code> and this architecture.</p>
<ul>
<li>You can load everything and keep it in memory for fast UX, but later optimize by inserting load/unload calls that are already part of the component's API</li>
<li>You could add several versions of a loading screen with no memory impact</li>
<li>You can later revise the loading screen and update it seamlessly after install</li>
<li>A team member can modify the LoadingCanvas without ever having to touch the scene</li>
<li>If someone breaks the LoadingCanvas prefab or the LoadingController, you'll know it with the tests</li>
<li>Create different loading canvases for different resolutions / platforms and only load the appropriate ones at runtime (or download, which we'll cover later)</li>
</ul>
<p>We'll be using this same pattern in the series and apply it to more UI, the Player, and Levels.</p>
<p>Source Code for this Post:</p>
<p><a target="_blank" href="https://github.com/PlayableDesign/Unity-Single-Scenery/tree/Loading">Github</a></p>
<h2 id="heading-next">Next</h2>
<p>Follow along to the next post in the series.</p>
<p><a target="_blank" href="https://playable.design/series/single-scenery">Unity Single Scene Architecture Series</a></p>
<p>If you're finding value in these posts, please leave a comment below to let me know (it's a lot of work) and share on your social media for others.  Thanks for reading!</p>
]]></content:encoded></item><item><title><![CDATA[Unity Single Scene Architecture - Game Manager]]></title><description><![CDATA[This is a post in a series about building a game in Unity using a single scene. 
Unity Single Scene Architecture Series
In this post, we will add a GameManager, Assembly Definitions, and play mode testing to the game.
Game Manager
Let's create a game...]]></description><link>https://playable.design/unity-single-scene-architecture-game-manager</link><guid isPermaLink="true">https://playable.design/unity-single-scene-architecture-game-manager</guid><category><![CDATA[unity]]></category><category><![CDATA[Unity Engine]]></category><category><![CDATA[Game Development]]></category><dc:creator><![CDATA[Steve Mcilwain]]></dc:creator><pubDate>Sat, 15 Oct 2022 17:38:47 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1665854011660/knXKPGs_O.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This is a post in a series about building a game in Unity using a single scene. </p>
<p><a target="_blank" href="https://playable.design/series/single-scenery">Unity Single Scene Architecture Series</a></p>
<p>In this post, we will add a GameManager, Assembly Definitions, and play mode testing to the game.</p>
<h2 id="heading-game-manager">Game Manager</h2>
<p>Let's create a game manager script that will be responsible for orchestrating its child game systems.  The script will be empty for now, but we'll be adding to it throughout the series.</p>
<p>In the <code>Project</code> window, create a new folder for <code>Scripts</code> underneath the "Assets" folder. Create a new C# script called "GameManager".</p>
<p>Open the script in your editor and replace with this placeholder code.</p>
<pre><code class="lang-c#"><span class="hljs-keyword">using</span> UnityEngine;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">SingleScenery</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">GameManager</span> : <span class="hljs-title">MonoBehaviour</span>
    {

    }
}
</code></pre>
<p>Then drag the script onto the "GameManager" object in the scene.</p>
<h2 id="heading-assembly-definition">Assembly Definition</h2>
<p>If you're not familiar with <a target="_blank" href="https://docs.unity3d.com/Manual/ScriptCompilationAssemblyDefinitionFiles.html">Assembly Definition</a> files, then review the topic. </p>
<p>We're going to add one that will contain all of the source code for Single Scenery. Using an assembly definition will help with domain reloads as code scales as well as simplify testing with the Unity Test Framework.</p>
<p>Right-click the "Scripts" folder and add an <code>Assembly Definition</code> named "SingleScenery".</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665851755164/xWAGku8HQ.png" alt="image.png" /></p>
<p>Once created modify its settings in the <code>Inspector</code> window.</p>
<ul>
<li>Set <code>Root Namespace</code> to "SingleScenery"</li>
<li>Add a reference under <code>Assembly Definition References</code> to <code>Unity.Addressables</code></li>
</ul>
<p>Click <code>Apply</code> to save the settings.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665852322465/buyw79Mf-.png" alt="image.png" /></p>
<h2 id="heading-test-assembly">Test Assembly</h2>
<p>Now we can create a test assembly using the <a target="_blank" href="https://docs.unity3d.com/Packages/com.unity.test-framework@1.1/manual/index.html">Unity Test Framework</a> for play mode tests that we will later use to ensure our prefabs don't break. </p>
<p>Go to <code>Window &gt; General &gt; TestRunner</code> and then select <code>PlayMode</code> at the top.  Click <code>Create Playmode Test Assembly Folder</code>. You should now have a folder called "Tests" with an assembly definition file of the same name.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665852761027/34fD-nf2X.png" alt="image.png" /></p>
<p>Rename the file to "SingleScenery.Tests", select it and make the following changes in the <code>Inspector</code> window.</p>
<ul>
<li>Change <code>Name</code> to "SingleScenery"</li>
<li>Set <code>Root Namespace</code> to "SingleScenery"</li>
<li>Add a reference under <code>ssembly Definition References</code> to <code>Unity.Addressables</code></li>
<li>Add a reference under <code>Assembly Definition References</code> to <code>SingleScenery</code></li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665853377492/9RuQOLVRC.png" alt="image.png" /></p>
<h2 id="heading-test-script">Test Script</h2>
<p>Now we can add our first play mode test. If you're not familiar with tests, I'll cover more as the series progresses. A play mode test is executed by the <code>TestRunner</code> which creates a temporary scene and then executes tests in the scripts that are compiled into the test assembly. </p>
<p>Right-click the "Tests" folder and go to <code>Create &gt; Testing &gt; C# Test Script</code> and create a new script called "GameManager_Tests".  Open in your editor and add the following placeholder code.</p>
<pre><code class="lang-c#"><span class="hljs-keyword">using</span> NUnit.Framework;
<span class="hljs-keyword">using</span> UnityEngine;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">SingleScenery</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">GameManager_Tests</span>
    {

        [<span class="hljs-meta">Test</span>]
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Can_Create_GameManager</span>(<span class="hljs-params"></span>)</span>
        {
            <span class="hljs-keyword">var</span> go = <span class="hljs-keyword">new</span> GameObject(<span class="hljs-string">"Game"</span>);
            <span class="hljs-keyword">var</span> gm = go.AddComponent&lt;GameManager&gt;();

            Assert.IsInstanceOf&lt;GameManager&gt;(gm);

        }

    }
}
</code></pre>
<p>When this test is run it will simply verify that all our references are correctly setup by creating a new game object in the temp scene and adding a GameManager component to it.</p>
<p>Go to the <code>TestRunner</code> window and <code>RunAll</code> to run the test.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665853811430/qnIqKd0Ia.png" alt="image.png" /></p>
<h2 id="heading-summary">Summary</h2>
<p>We now have a GameManager, an assembly for our game's source code, a test assembly for our tests, and correct references setup to start building more logic.</p>
<h2 id="heading-next">Next</h2>
<p>Follow along to the next post in the series.</p>
<p><a target="_blank" href="https://playable.design/series/single-scenery">Unity Single Scene Architecture Series</a></p>
]]></content:encoded></item><item><title><![CDATA[Unity Single Scene Architecture Introduction]]></title><description><![CDATA[In this series of posts, I am going to describe an approach to architecting a game with a single scene. Follow along as I add to this series covering practical topics often missed in typical Unity tutorials.
Unity Single Scene Architecture Series
Int...]]></description><link>https://playable.design/unity-single-scene-architecture-introduction</link><guid isPermaLink="true">https://playable.design/unity-single-scene-architecture-introduction</guid><category><![CDATA[unity]]></category><category><![CDATA[Unity Engine]]></category><category><![CDATA[Game Development]]></category><dc:creator><![CDATA[Steve Mcilwain]]></dc:creator><pubDate>Sat, 15 Oct 2022 17:32:46 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1665850825789/Z3vXXE8w9.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this series of posts, I am going to describe an approach to architecting a game with a single scene. Follow along as I add to this series covering practical topics often missed in typical Unity tutorials.</p>
<p><a target="_blank" href="https://playable.design/series/single-scenery">Unity Single Scene Architecture Series</a></p>
<h2 id="heading-introduction">Introduction</h2>
<p>Instead of multiple scenes, this architecture will rely on dynamically loading and unloading assets (such as prefabs) at runtime using the <a target="_blank" href="https://docs.unity3d.com/Manual/com.unity.addressables.html">Unity Addressables</a> package.</p>
<p>Additionally, the scene hierarchy will be used to organize and control complexity, manage state, and encourage dependency inversion (loose coupling). I'll also cover laying a good game foundation by including tests and a CI/CD pipeline as the project progresses.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665849436261/Z0BGSpJ6g.png" alt="Single-Scenery.png" /></p>
<h2 id="heading-why">Why?</h2>
<p>There are so many different ways to build and structure games. Most simple games will load the main menu scene, and when playing load the first scene by build index. When that level is complete, load the next scene index, etc.</p>
<p>Larger games may use additive scene loading to split up larger levels, reduce load times and optimize memory usage. Assets can be loaded and unloaded at runtime, often with the <code>Resources</code> folder (slow) or <code>AssetBundles</code> (complex). </p>
<p>Mix this all together and it becomes confusing when to group objects in a scene, when to use prefabs/variants, and when to load at runtime.</p>
<p>This architecture seeks to simplify these decisions, reduce the need for singletons/static scope, and create a better iterative experience.</p>
<h2 id="heading-caveats">Caveats</h2>
<p>If your game will rely heavily on baked lighting, then this architecture may not work for you. Unity does not have built in support for baking light with prefabs! I'll cover some options for lighting in a post as part of this series.</p>
<p>For now, let's forge ahead and dive in!</p>
<h2 id="heading-project-setup">Project Setup</h2>
<p>Create a new Unity project. I am using version <code>2021.3</code> with <code>3D core</code> features. Rename the SampleScene to "GameScene".</p>
<p>Use the Package Manager to install the <code>Addressables</code> package.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665849921771/H10UZcRa6.png" alt="image.png" /></p>
<h2 id="heading-game-design">Game Design</h2>
<p>In Single Scenery the game will be as simple as possible to demonstrate the architecture. The player will move through each level, which will simply be a landscape composed of Unity's built-in 3D objects.</p>
<p>The player will move through the landscape, enjoy the scenery, and find a "key" that allows them to move to the next level.</p>
<h2 id="heading-game-setup">Game Setup</h2>
<p>We'll use our single scene to frame out the main game systems needed for Single Scenery.</p>
<p>With "Game Scene" open in the editor, let's mirror the above architecture with empty game objects.  Create an empty game object in the root of the scene named "Game". Add empty child game objects named:</p>
<ul>
<li>Cameras</li>
<li>Lights</li>
<li>Input</li>
<li>UI</li>
<li>Player</li>
<li>Levels</li>
</ul>
<p>Drag "Directional Light" under "Lights".
Drag "Main Camera" under "Cameras".</p>
<p>You should now have a scene hierarchy that looks like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665850116986/JxqxmGRYX.png" alt="image.png" /></p>
<h2 id="heading-summary">Summary</h2>
<p>We now have the bones of a game, a scene that is framed out with empty objects and ready to start implementing.</p>
<h2 id="heading-next">Next</h2>
<p>Follow along to the next post in the series.</p>
<p><a target="_blank" href="https://playable.design/series/single-scenery">Unity Single Scene Architecture Series</a></p>
<p>Source Code:</p>
<p><a target="_blank" href="https://github.com/PlayableDesign/Unity-Single-Scenery">Github</a></p>
]]></content:encoded></item><item><title><![CDATA[Procedural Wording in Unity]]></title><description><![CDATA[In this article I am going to cover how to create a procedural system in Unity that creates unique text content at runtime. 
In the example project, I am satisfying an actual use case for a game I am working on that populates random books in a store....]]></description><link>https://playable.design/procedural-wording-in-unity</link><guid isPermaLink="true">https://playable.design/procedural-wording-in-unity</guid><category><![CDATA[unity]]></category><category><![CDATA[Unity Engine]]></category><category><![CDATA[game]]></category><category><![CDATA[Game Development]]></category><dc:creator><![CDATA[Steve Mcilwain]]></dc:creator><pubDate>Sun, 11 Sep 2022 18:29:20 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1662920862078/UwAbrk4Se.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this article I am going to cover how to create a procedural system in Unity that creates unique text content at runtime. </p>
<p>In the example project, I am satisfying an actual use case for a game I am working on that populates random books in a store. I need each book to have a unique title and condition.</p>
<h2 id="heading-requirements">Requirements</h2>
<ul>
<li>Unity 2021.3 LTS</li>
<li>GitHub Project: https://github.com/PlayableDesign/unity-procedural-wording</li>
</ul>
<h2 id="heading-setup">Setup</h2>
<p>Create a new Unity project. I am using the default 3D core template.</p>
<p>With the default "SampleScene" opened, choose the "Main Camera" game object and change the <code>Clear Flags</code> from <code>Skybox</code> to <code>Solid Color</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662743200877/lRfwFzpjG.png" alt="image.png" /></p>
<p>In the <code>Project</code> window, under <code>Assets</code>, create a new folder for "Scripts" and another for "Data".</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662911811734/eh1KC08-c.png" alt="image.png" /></p>
<h2 id="heading-procedural-books">Procedural Books</h2>
<p>In this example, I want to generate random books, including the following properties:</p>
<ul>
<li>Fiction or Nonfiction</li>
<li>Title</li>
<li>Condition</li>
</ul>
<p>We're going to build a very basic grammar system using one <code>MonoBehaviour</code> and two <code>ScriptableObject</code> scripts.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662911451501/lOnJd3x4J.png" alt="image.png" /></p>
<h2 id="heading-words-asset">Words Asset</h2>
<p>First we need a container for random words that will be used in titles and condition descriptions. Create a new script in the Scripts folder for storing random words.</p>
<pre><code class="lang-c#"><span class="hljs-keyword">using</span> System.Collections.Generic;
<span class="hljs-keyword">using</span> UnityEngine;

[<span class="hljs-meta">CreateAssetMenu(fileName = <span class="hljs-meta-string">"WordsAsset"</span>, menuName = <span class="hljs-meta-string">"Procedural Wording/WordsAsset"</span>)</span>]
<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">WordsAsset</span> : <span class="hljs-title">ScriptableObject</span>
{
    [<span class="hljs-meta">SerializeField</span>] <span class="hljs-keyword">private</span> List&lt;<span class="hljs-keyword">string</span>&gt; words;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> <span class="hljs-title">GetRandom</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-keyword">var</span> word = words[Random.Range(<span class="hljs-number">0</span>, words.Count)];
        <span class="hljs-keyword">return</span> word;
    }
}
</code></pre>
<p>Now you can create new assets in the editor to store a list of words at design time and use <code>GetRandom</code> to choose one from the list at runtime.</p>
<p>Let's extend this to also capitalize words based on a design time setting for each asset. I'll use the <code>System.Globalization</code> namespace for this.</p>
<pre><code class="lang-c#"><span class="hljs-keyword">using</span> System.Collections.Generic;
<span class="hljs-keyword">using</span> System.Globalization;
<span class="hljs-keyword">using</span> UnityEngine;

[<span class="hljs-meta">CreateAssetMenu(fileName = <span class="hljs-meta-string">"WordsAsset"</span>, menuName = <span class="hljs-meta-string">"Procedural Wording/WordsAsset"</span>)</span>]
<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">WordsAsset</span> : <span class="hljs-title">ScriptableObject</span>
{
    [<span class="hljs-meta">SerializeField</span>] <span class="hljs-keyword">private</span> <span class="hljs-keyword">bool</span> capitalize;
    [<span class="hljs-meta">SerializeField</span>] <span class="hljs-keyword">private</span> List&lt;<span class="hljs-keyword">string</span>&gt; words;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> <span class="hljs-title">GetRandom</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-keyword">var</span> word = words[Random.Range(<span class="hljs-number">0</span>, words.Count)];

        <span class="hljs-keyword">if</span> (capitalize)
            word = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(word);

        <span class="hljs-keyword">return</span> word;
    }
}
</code></pre>
<h2 id="heading-rule-asset">Rule Asset</h2>
<p>Now we need to be able to use one or more word lists to replace tokens in a sentence. To keep things simple, I am going to use the <code>string.Format</code> method to accomplish this. </p>
<p>At design time, you would create a sentence or paragraph that is peppered with numbered tokens that will be replaced by the format method. Each numbered token will correspond to the index of a list of <code>WordAsset</code> instances.</p>
<pre><code class="lang-c#">
<span class="hljs-keyword">using</span> System.Collections.Generic;
<span class="hljs-keyword">using</span> UnityEngine;

[<span class="hljs-meta">CreateAssetMenu(fileName = <span class="hljs-meta-string">"RuleAsset"</span>, menuName = <span class="hljs-meta-string">"Procedural Wording/RuleAsset"</span>)</span>]
<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">RuleAsset</span> : <span class="hljs-title">ScriptableObject</span>
{

    [<span class="hljs-meta">TextArea(10, 50)</span>]
    [<span class="hljs-meta">Tooltip(<span class="hljs-meta-string">"A valid format string using {n} to denote the position of each word list entry"</span>)</span>]
    [<span class="hljs-meta">SerializeField</span>] <span class="hljs-keyword">private</span> <span class="hljs-keyword">string</span> format;

    [<span class="hljs-meta">Tooltip(<span class="hljs-meta-string">"The position of each word list should match it's position in the format string"</span>)</span>]
    [<span class="hljs-meta">SerializeField</span>] <span class="hljs-keyword">private</span> List&lt;WordsAsset&gt; wordlists;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> <span class="hljs-title">Apply</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-comment">// store the results of each word list</span>
        <span class="hljs-keyword">var</span> words = <span class="hljs-keyword">new</span> List&lt;<span class="hljs-keyword">string</span>&gt;();

        <span class="hljs-comment">// get a random word from each words asset</span>
        <span class="hljs-keyword">foreach</span> (<span class="hljs-keyword">var</span> list <span class="hljs-keyword">in</span> wordlists)
        {
            words.Add(list.GetRandom());
        }

        <span class="hljs-comment">// pass the list of random words to the format method</span>
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">string</span>.Format(format, words.ToArray());
    }
}
</code></pre>
<h2 id="heading-data">Data</h2>
<p>We have our two new custom assets that derive from <code>ScriptableObject</code>. Let's create the data to create unique books.</p>
<p>Use the new context menu entries to add instances to the project.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662917819172/VbngWEgd7.png" alt="image.png" /></p>
<p>Create the following assets in the "Data" folder for random word lists.</p>
<ul>
<li>BookConditions</li>
<li>BookTitlesFiction_1</li>
<li>BookTitlesFiction_2</li>
<li>BookTitlesNonfiction_1</li>
<li>BookTitlesNonfiction_2</li>
</ul>
<p>Create the following assets in the "Data" folder for rules.</p>
<ul>
<li>ConditionRule</li>
<li>FictionTitleRule</li>
<li>NonfictionTitleRule</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662918018277/xl8G1fK9t.png" alt="image.png" /></p>
<h2 id="heading-data-random-words">Data - Random Words</h2>
<p>Add some book conditions to the "BookConditions" asset.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662918072353/IvrcOJJqA.png" alt="image.png" /></p>
<p>Add book title words to the other four assets.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662918146082/Isrv3JOwx.png" alt="image.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662918159812/K3UsFbUbE.png" alt="image.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662918176571/_pz0sVbVG.png" alt="image.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662918188375/Xzf5kjovC.png" alt="image.png" /></p>
<h2 id="heading-data-book-rules">Data - Book Rules</h2>
<p>Let's add some format strings and the random word data to the rules.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662918395468/eDu8AyLtR.png" alt="image.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662918407406/xyLXcEwtd.png" alt="image.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662918423315/rZrtHlNA5.png" alt="image.png" /></p>
<h2 id="heading-book-controller">Book Controller</h2>
<p>Now we have enough data to generate random books, we need a game object in the scene to orchestrate the procedure and display content using a UI text field.</p>
<p>Create a new script called "BookController" in the Scripts folder.</p>
<pre><code class="lang-c#"><span class="hljs-keyword">using</span> TMPro;
<span class="hljs-keyword">using</span> UnityEngine;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">BookController</span> : <span class="hljs-title">MonoBehaviour</span>
{
    [<span class="hljs-meta">Tooltip(<span class="hljs-meta-string">"The text field to update at runtime."</span>)</span>]
    [<span class="hljs-meta">SerializeField</span>] <span class="hljs-keyword">private</span> TMP_Text text;

    [<span class="hljs-meta">Header(<span class="hljs-meta-string">"Rules"</span>)</span>]
    [<span class="hljs-meta">Space(10)</span>]

    [<span class="hljs-meta">SerializeField</span>] <span class="hljs-keyword">private</span> RuleAsset fictionTitleRule;
    [<span class="hljs-meta">SerializeField</span>] <span class="hljs-keyword">private</span> RuleAsset nonfictionTitleRule;
    [<span class="hljs-meta">SerializeField</span>] <span class="hljs-keyword">private</span> RuleAsset conditionRule;

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnEnable</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-comment">// Choose fiction or nonfiction book randomnly </span>
        <span class="hljs-keyword">bool</span> fiction = Random.<span class="hljs-keyword">value</span> &gt; <span class="hljs-number">0.5f</span>;

        <span class="hljs-keyword">if</span> (fiction)
        {
            SetText(fictionTitleRule.Apply(), conditionRule.Apply());
        }
        <span class="hljs-keyword">else</span>
        {
            SetText(nonfictionTitleRule.Apply(), conditionRule.Apply());
        }
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">SetText</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> title, <span class="hljs-keyword">string</span> condition</span>)</span>
    {
        text.text = title + condition;
    }
}
</code></pre>
<p>This script will decide at random if the book is fiction or non-fiction and then reference the rule assets created earlier to generate random content.</p>
<p>Add a new, empty game object to the scene called "BookController" and add this script to it. Add the three rule assets to the appropriate fields.</p>
<p>With the "BookController" game object selected in the scene, add a TextMeshPro text element and then reference it in the "Text" field of the controller.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662919162235/Kl0yzTTpY.png" alt="image.png" /></p>
<p>** If prompted to import TextMeshPro Essentials, do so</p>
<p>Change the "Text (TMP)" component width and height.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662919359298/TBM8we-b2.png" alt="image.png" /></p>
<h2 id="heading-play">Play!</h2>
<p>Now we have implemented our architecture, the BookController has a reference to the text component to display a book, and it references the rules for the title and condition, which then reference our random word lists.</p>
<p>Test it out in play mode.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662919509097/7x97HhBYj.png" alt="image.png" /></p>
<p>With the BookController game object selected, check and uncheck the active flag to trigger the OnEnable method and generate new content.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662919577599/hb8aZxdJK.png" alt="image.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662919609555/D4fW-RSP4.png" alt="image.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662919624492/q1ujbyJtT.png" alt="image.png" /></p>
<h2 id="heading-summary">Summary</h2>
<p>In this article I demonstrated a simple way to generate random, unique content. There are many ways to extend this functionality with tokenizers, regex, etc. This solution also works well with the <code>Addressables</code> package, allowing you to update content after distribution - more on that another day. Feel free to contribute and comment. </p>
]]></content:encoded></item><item><title><![CDATA[Avoid Prefab Hell with ScriptableObject Services in Unity]]></title><description><![CDATA[The Gamble
You've been tuning a game object in a scene... one with many components, some child game objects, and those have components... it's time to apply prefab overrides. Your palms sweat. You click the button, like pulling the handle of a slot m...]]></description><link>https://playable.design/scriptableobject-services-in-unity</link><guid isPermaLink="true">https://playable.design/scriptableobject-services-in-unity</guid><category><![CDATA[unity]]></category><category><![CDATA[Unity Engine]]></category><category><![CDATA[Game Development]]></category><dc:creator><![CDATA[Steve Mcilwain]]></dc:creator><pubDate>Mon, 29 Aug 2022 17:34:29 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1661794379970/T3bBYOZym.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-the-gamble">The Gamble</h2>
<p>You've been tuning a game object in a scene... one with many components, some child game objects, and those have components... it's time to apply prefab overrides. Your palms sweat. You click the button, like pulling the handle of a slot machine. What unintentional changes have you rippled throughout the game, prefabs, variants and instances???</p>
<p>Sound familiar? If so, then I have a technique for you to try out. You should be familiar with <code>ScriptableObject</code>, a sibling of <code>MonoBehaviour</code>, used to create custom assets for a project, to centralize and organize design time settings, and often to handle runtime state and events.</p>
<h2 id="heading-a-different-approach">A Different Approach</h2>
<p>In this article, I'll cover using <code>ScriptableObjects</code> to create "service" assets, which will provide reusable game logic alongside settings that reduce dependence on the prefab workflow and introduce several other benefits such as:</p>
<ul>
<li>Pluggable services that can be added to game objects at design time or runtime.</li>
<li>Stateless, testable, independently versioned</li>
<li>Source control friendly</li>
<li>Follows Single Responsibility Principle (SRP)</li>
<li>Can fit into state machine, behavior tree and other common patterns</li>
</ul>
<p>Here are the rules I use for Service Assets.</p>
<ul>
<li>Inherit from <code>ScriptableObject</code></li>
<li>No "settings" related to this logic in the <code>MonoBehaviour</code>, instead they should live with the asset</li>
<li>No "state" or external references, everything must be passed in and then returned, like a pure function</li>
<li><code>MonoBehaviour</code> acts as the "controller" or orchestrator of Service Assets, supplies state and references</li>
</ul>
<h2 id="heading-tackling-physics">Tackling Physics</h2>
<p>With that, let's get hands on with an example to encapsulate some different physics scenarios with <code>RigidBody</code>. Physics is something I find myself often flipping settings around to get the "perfect" effects in game, which makes this a great candidate for packaging as an asset.</p>
<h2 id="heading-project-setup">Project Setup</h2>
<p>Create a new 3D project, I'm using Unity 2021.3 LTS.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661782705317/Vv12BEv71.png" alt="image.png" /></p>
<p>Let's start with a simple scene, add a cube that represents the ground with a box collider and green material.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661783333717/sjQcRwnkx.png" alt="image.png" /></p>
<p>Add another cube that will be what we move around in this example, with a blue material and a RigidBody.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661783428767/4B1Ov5m1f.png" alt="image.png" /></p>
<h2 id="heading-cubecontroller">CubeController</h2>
<p>Let's use a simple <code>MonoBehaviour</code> script to be able to move the Cube around with input.  Create a <code>CubeController</code> script and attach it to the Cube in the scene.</p>
<pre><code class="lang-c#">
<span class="hljs-keyword">using</span> UnityEngine;

[<span class="hljs-meta">RequireComponent(typeof(Rigidbody))</span>]
<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">CubeController</span> : <span class="hljs-title">MonoBehaviour</span>
{

    <span class="hljs-keyword">private</span> Rigidbody body;
    <span class="hljs-keyword">private</span> Vector3 direction;

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Awake</span>(<span class="hljs-params"></span>)</span>
    {
        body = GetComponent&lt;Rigidbody&gt;();
        direction = Vector3.zero;
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Update</span>(<span class="hljs-params"></span>)</span>
    {
        direction.x = Input.GetAxis(<span class="hljs-string">"Horizontal"</span>);
        direction.z = Input.GetAxis(<span class="hljs-string">"Vertical"</span>);
        direction.Normalize(); <span class="hljs-comment">//handle diagonal scale</span>
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">FixedUpdate</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-keyword">if</span> (direction == Vector3.zero) <span class="hljs-keyword">return</span>;

        <span class="hljs-comment">// Add code to move the RigidBody</span>
    }
}
</code></pre>
<p>Simple, we use <code>Update</code> to gather input per frame and we apply physics in <code>FixedUpdate</code> which will come from the new service asset. Input is captured in a Vector3 called "direction" which will be "state" that we pass to our service asset.</p>
<h2 id="heading-prefab-approach">Prefab Approach</h2>
<p>A typical approach would be to write the physics code directly in this MonoBehaviour and perhaps expose movement related settings, like "Speed" as a serialized field in the Inspector. We could then prefab it, make variants for different speed cubes, etc. But now we're back into the prefab workflow that I'd like to avoid and which will get more complex with additional components.</p>
<h2 id="heading-service-asset">Service Asset</h2>
<p>Create a new script called <code>MovementServiceAsset</code>, change it to inherit from ScriptableObject and then add a method that should be called from the CubeController to move using the RigidBody. Sticking to our rules, CubeController will pass in the RigidBody as a reference and direction as state.</p>
<pre><code class="lang-c#"><span class="hljs-keyword">using</span> UnityEngine;

[<span class="hljs-meta">CreateAssetMenu(fileName = <span class="hljs-meta-string">"MovementServiceAsset"</span>, menuName = <span class="hljs-meta-string">"ServiceAssets/MovementServiceAsset"</span>, order = 0)</span>]
<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">MovementServiceAsset</span> : <span class="hljs-title">ScriptableObject</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Move</span>(<span class="hljs-params">Rigidbody body, Vector3 direction</span>)</span>
    {
        Debug.Log(<span class="hljs-string">$"Moving <span class="hljs-subst">{body.name}</span> in direction: <span class="hljs-subst">{direction}</span>"</span>);
    }
}
</code></pre>
<h2 id="heading-assemble-parts">Assemble Parts</h2>
<p>Now let's put this together. Add a field to the CubeController for a MovementServiceAsset that we can supply in the inspector. Then call the service asset <code>Move</code> method in <code>FixedUpdate</code>, passing the reference and state as paramaters.</p>
<pre><code class="lang-c#">
<span class="hljs-keyword">using</span> UnityEngine;

[<span class="hljs-meta">RequireComponent(typeof(Rigidbody))</span>]
<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">CubeController</span> : <span class="hljs-title">MonoBehaviour</span>
{

    [<span class="hljs-meta">SerializeField</span>] <span class="hljs-keyword">private</span> MovementServiceAsset movementService;

    <span class="hljs-keyword">private</span> Rigidbody body;
    <span class="hljs-keyword">private</span> Vector3 direction;

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Awake</span>(<span class="hljs-params"></span>)</span>
    {
        body = GetComponent&lt;Rigidbody&gt;();
        direction = Vector3.zero;
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Update</span>(<span class="hljs-params"></span>)</span>
    {
        direction.x = Input.GetAxis(<span class="hljs-string">"Horizontal"</span>);
        direction.z = Input.GetAxis(<span class="hljs-string">"Vertical"</span>);
        direction.Normalize(); <span class="hljs-comment">//handle diagonal scale</span>
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">FixedUpdate</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-keyword">if</span> (direction == Vector3.zero) <span class="hljs-keyword">return</span>;

        movementService.Move(body, direction);

    }
}
</code></pre>
<p>Create a new instance of our service.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661785747312/tOuRcd1mI.png" alt="image.png" /></p>
<p>Name it "BasicMovementService".</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661785864971/NhPoIlZCf.png" alt="image.png" /></p>
<p>Then add the new service asset to the CubeController in the Scene.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661785915231/-Y40D0mdu.png" alt="image.png" /></p>
<p>Fire up play mode and use the WASD keys, which should trigger log messages in the Console window.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661785989970/5oetxTGe7.png" alt="image.png" /></p>
<h2 id="heading-add-settings">Add Settings</h2>
<p>Now let's add some settings to the service asset, so we can change behavior of the movement logic from the editor.  These settings will be saved independently in each asset instance created, such as BasicMovementService.</p>
<pre><code class="lang-c#">
<span class="hljs-keyword">using</span> UnityEngine;

[<span class="hljs-meta">CreateAssetMenu(fileName = <span class="hljs-meta-string">"MovementServiceAsset"</span>, menuName = <span class="hljs-meta-string">"ServiceAssets/MovementServiceAsset"</span>)</span>]
<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">MovementServiceAsset</span> : <span class="hljs-title">ScriptableObject</span>
{
    [<span class="hljs-meta">Header(<span class="hljs-meta-string">"Movement Settings"</span>)</span>]
    [<span class="hljs-meta">Space(10)</span>]

    [<span class="hljs-meta">SerializeField</span>] <span class="hljs-keyword">private</span> <span class="hljs-keyword">float</span> speed;

    [<span class="hljs-meta">Header(<span class="hljs-meta-string">"RigidBody Settings"</span>)</span>]
    [<span class="hljs-meta">Space(10)</span>]

    [<span class="hljs-meta">SerializeField</span>] <span class="hljs-keyword">private</span> ForceMode forceMode;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Move</span>(<span class="hljs-params">Rigidbody body, Vector3 direction</span>)</span>
    {
        Debug.Log(<span class="hljs-string">$"Moving <span class="hljs-subst">{body.name}</span> in direction: <span class="hljs-subst">{direction}</span>"</span>);

        body.AddForce(direction * speed, forceMode);
    }
}
</code></pre>
<p>Select the BasicMovementService asset in the Project window and configure speed. We'll leave ForceMode at the default for now.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661786588091/jyaT3upz_.png" alt="image.png" /></p>
<p>Fire up play mode again and use the WASD keys to move the cube (it should tumble around because we don't have any RigidBody constraints set).</p>
<h2 id="heading-more-settings">More Settings</h2>
<p>To stick to our rules and avoid prefab impact, I don't want to set RigidBody constraints on the component directly. Instead I want to set them via our new service asset. So let's add a method to be called from Awake. </p>
<pre><code class="lang-c#">
<span class="hljs-keyword">using</span> UnityEngine;

[<span class="hljs-meta">CreateAssetMenu(fileName = <span class="hljs-meta-string">"MovementServiceAsset"</span>, menuName = <span class="hljs-meta-string">"ServiceAssets/MovementServiceAsset"</span>)</span>]
<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">MovementServiceAsset</span> : <span class="hljs-title">ScriptableObject</span>
{
    [<span class="hljs-meta">Header(<span class="hljs-meta-string">"Movement Settings"</span>)</span>]
    [<span class="hljs-meta">Space(10)</span>]

    [<span class="hljs-meta">SerializeField</span>] <span class="hljs-keyword">private</span> <span class="hljs-keyword">float</span> speed;

    [<span class="hljs-meta">Header(<span class="hljs-meta-string">"RigidBody Settings"</span>)</span>]
    [<span class="hljs-meta">Space(10)</span>]

    [<span class="hljs-meta">SerializeField</span>] <span class="hljs-keyword">private</span> ForceMode forceMode;
    [<span class="hljs-meta">SerializeField</span>] <span class="hljs-keyword">private</span> <span class="hljs-keyword">bool</span> freezeRotation;

    [<span class="hljs-meta">Header(<span class="hljs-meta-string">"Collider Settings"</span>)</span>]
    [<span class="hljs-meta">Space(10)</span>]

    [<span class="hljs-meta">Tooltip(<span class="hljs-meta-string">"Optional"</span>)</span>]
    [<span class="hljs-meta">SerializeField</span>] <span class="hljs-keyword">private</span> PhysicMaterial material;


    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Initialize</span>(<span class="hljs-params">Rigidbody body, Collider collider</span>)</span>
    {
        <span class="hljs-keyword">if</span> (freezeRotation) body.constraints |= RigidbodyConstraints.FreezeRotation;
        <span class="hljs-keyword">if</span> (material != <span class="hljs-literal">null</span>) collider.material = material;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Move</span>(<span class="hljs-params">Rigidbody body, Vector3 direction</span>)</span>
    {
        Debug.Log(<span class="hljs-string">$"Moving <span class="hljs-subst">{body.name}</span> in direction: <span class="hljs-subst">{direction}</span>"</span>);

        body.AddForce(direction * speed, forceMode);
    }
}
</code></pre>
<p>Another setting related to movement would be the <code>PhysicMaterial</code> which governs friction and bounciness of the <code>Collider</code>.</p>
<p>I'll avoid mass, because that might be something changed depending on the characteristics of a single model and not applied service-wide.</p>
<p>Add the call to Initialize in the CubeController Awake event.</p>
<pre><code class="lang-c#">
<span class="hljs-keyword">using</span> UnityEngine;

[<span class="hljs-meta">RequireComponent(typeof(Rigidbody))</span>]
<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">CubeController</span> : <span class="hljs-title">MonoBehaviour</span>
{

    [<span class="hljs-meta">SerializeField</span>] <span class="hljs-keyword">private</span> MovementServiceAsset movementService;

    <span class="hljs-keyword">private</span> Rigidbody body;
    <span class="hljs-keyword">private</span> Vector3 direction;

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Awake</span>(<span class="hljs-params"></span>)</span>
    {
        body = GetComponent&lt;Rigidbody&gt;();
        direction = Vector3.zero;
        movementService.Initialize(body, GetComponent&lt;Collider&gt;());
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Update</span>(<span class="hljs-params"></span>)</span>
    {
        direction.x = Input.GetAxis(<span class="hljs-string">"Horizontal"</span>);
        direction.z = Input.GetAxis(<span class="hljs-string">"Vertical"</span>);
        direction.Normalize(); <span class="hljs-comment">//handle diagonal scale</span>
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">FixedUpdate</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-keyword">if</span> (direction == Vector3.zero) <span class="hljs-keyword">return</span>;

        movementService.Move(body, direction);

    }
}
</code></pre>
<p>Create a new PhysicMaterial for the cube with default settings, add it to the BasicMovementService, and check to freeze rotation. Now that we've added friction, bump up speed to 20 (or play with this setting during play mode).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661788727988/a5ZJwXWu-.png" alt="image.png" /></p>
<p>Hit play and move around, you'll see that the constraints have been set. You can now change these settings, which will be saved even in play mode and applied to all other objects using this service.</p>
<h2 id="heading-another-service">Another Service</h2>
<p>Ok great, let's prefab our Cube, dragging into the Project. Create a variant called FastCube. Duplicate BasicMovementService asset, call it FastMovementService and set the speed to 25. Assign this to the FastCube variant, and add an instance to the scene.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661789455906/d7WpR9oVZ.png" alt="image.png" /></p>
<p>Start play mode and move around, you should see both cubes move but at different speeds. But what has that solved? We still have a problem.  Let's make each cube a different player with individual input settings. Again typical approach would be fields on the prefab to govern input schemes. Instead we'll use a service asset.</p>
<p>Create a new script called InputServiceAsset which will contain our key mappings.</p>
<pre><code class="lang-c#">
<span class="hljs-keyword">using</span> UnityEngine;

[<span class="hljs-meta">CreateAssetMenu(fileName = <span class="hljs-meta-string">"InputServiceAsset"</span>, menuName = <span class="hljs-meta-string">"ServiceAssets/InputServiceAsset"</span>)</span>]
<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">InputServiceAsset</span> : <span class="hljs-title">ScriptableObject</span>
{
    [<span class="hljs-meta">Header(<span class="hljs-meta-string">"Key Mappings"</span>)</span>]
    [<span class="hljs-meta">Space(10)</span>]

    [<span class="hljs-meta">SerializeField</span>] <span class="hljs-keyword">private</span> KeyCode up;
    [<span class="hljs-meta">SerializeField</span>] <span class="hljs-keyword">private</span> KeyCode left;
    [<span class="hljs-meta">SerializeField</span>] <span class="hljs-keyword">private</span> KeyCode down;
    [<span class="hljs-meta">SerializeField</span>] <span class="hljs-keyword">private</span> KeyCode right;

    <span class="hljs-function"><span class="hljs-keyword">public</span> Vector3 <span class="hljs-title">Poll</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-keyword">var</span> direction = Vector3.zero;

        <span class="hljs-keyword">if</span> (Input.GetKey(up)) direction += Vector3.forward;
        <span class="hljs-keyword">if</span> (Input.GetKey(left)) direction += Vector3.left;
        <span class="hljs-keyword">if</span> (Input.GetKey(down)) direction += Vector3.back;
        <span class="hljs-keyword">if</span> (Input.GetKey(right)) direction += Vector3.right;

        <span class="hljs-keyword">return</span> direction.normalized;
    }
}
</code></pre>
<p>Add a field to the CubeController to hold an InputServiceAsset instance and change Update to use the Poll method to get direction each frame. </p>
<pre><code class="lang-c#">
<span class="hljs-keyword">using</span> UnityEngine;

[<span class="hljs-meta">RequireComponent(typeof(Rigidbody))</span>]
<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">CubeController</span> : <span class="hljs-title">MonoBehaviour</span>
{
    [<span class="hljs-meta">SerializeField</span>] <span class="hljs-keyword">private</span> InputServiceAsset inputService;
    [<span class="hljs-meta">SerializeField</span>] <span class="hljs-keyword">private</span> MovementServiceAsset movementService;

    <span class="hljs-keyword">private</span> Rigidbody body;
    <span class="hljs-keyword">private</span> Vector3 direction;

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Awake</span>(<span class="hljs-params"></span>)</span>
    {
        body = GetComponent&lt;Rigidbody&gt;();
        direction = Vector3.zero;
        movementService.Initialize(body, GetComponent&lt;Collider&gt;());
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Update</span>(<span class="hljs-params"></span>)</span>
    {
        direction = inputService.Poll();
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">FixedUpdate</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-keyword">if</span> (direction == Vector3.zero) <span class="hljs-keyword">return</span>;

        movementService.Move(body, direction);
    }
}
</code></pre>
<p>Now we are better sticking to rules. The MonoBehaviour is coordinating 2 services, storing state and orchestrating different components.</p>
<p>Create an instance of InputServiceAsset called "PlayerOneInputService".</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661790551731/ezaPXhuqI.png" alt="image.png" /></p>
<p>Assign keys for WASD movement. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661790712593/39-xyUWw_.png" alt="image.png" /></p>
<p>Duplicate the asset, name it "PlayerTwoInputService" and change the keys to handle arrow movement.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661790742226/DvGRooONC.png" alt="image.png" /></p>
<p>Assign the "PlayerOneInputService" to one of the cubes in the scene, assign "PlayerTwoInputService" to the other.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661790829968/ZoKh1yTOw.png" alt="image.png" /></p>
<p>Enter play mode and test out each cube with each set of keys.</p>
<h2 id="heading-summary">Summary</h2>
<p>Let's organize and look at our project now.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661791186324/OKs5qB7H8.png" alt="image.png" /></p>
<p>We have 3 scripts, two of which are <code>ScriptableObjects</code>. The <code>CubeController</code>, which is the single <code>MonoBehaviour</code> for our "Player" game objects. It gets injected with services that contain game logic. The <code>CubeController</code> doesn't need to supply any settings for these services, they are self-contained, it simply passes state around to and from services. You can test different player settings simply by switching out the assets on the game object or by changing the values in the referenced assets directly.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661791394749/dKlQYxZEB.png" alt="image.png" /></p>
<p>We have 4 service assets, a couple of variations of movement, and 2 different player input mappings. These can be tested from any script independently and don't require having their own state or references. They can be duplicated and customized easily and have clear responsibilities.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661791482257/3HWhEg3CS.png" alt="image.png" /></p>
<p>We have one prefab with 2 variants. The base prefab references a movement service asset, so that all players share the same movement characteristics. You could extend this to have a reference to both basic movement and fast movement, then switch to fast at runtime when a player has a "boost", for example.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661791598495/OB8qCe9z-.png" alt="image.png" /></p>
<p>Then each player variant references a custom input service asset to define controls.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661791660638/2CU-duqls.png" alt="image.png" /></p>
<p>Now the elements of the project play to their strengths and avoid some of their pain points as complexity scales. You could even distribute new configurations of services via <code>Addressables</code> after install.</p>
<p><strong>Project on Github</strong></p>
<ul>
<li>https://github.com/PlayableDesign/ServiceAssets</li>
</ul>
<p>Please comment, contribute, and let me know what you think about service assets!</p>
]]></content:encoded></item><item><title><![CDATA[Unity Addressables Helper]]></title><description><![CDATA[I've been doing a lot of work with the Addressables package recently and I'm writing up an entire series on how to use and implement clever solutions. 
Addressables is a fantastic framework that can:

Reduce memory pressure
Abstract and simplify the ...]]></description><link>https://playable.design/unity-addressables-helper</link><guid isPermaLink="true">https://playable.design/unity-addressables-helper</guid><category><![CDATA[unity]]></category><category><![CDATA[Unity Engine]]></category><category><![CDATA[Game Development]]></category><category><![CDATA[addressables]]></category><dc:creator><![CDATA[Steve Mcilwain]]></dc:creator><pubDate>Mon, 15 Aug 2022 13:56:14 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1660571334131/y4vrrkOfP.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I've been doing a lot of work with the <a target="_blank" href="https://docs.unity3d.com/Packages/com.unity.addressables@1.1/manual/index.html">Addressables</a> package recently and I'm writing up an entire series on how to use and implement clever solutions. </p>
<p><code>Addressables</code> is a fantastic framework that can:</p>
<ul>
<li>Reduce memory pressure</li>
<li>Abstract and simplify the loading of both local and remote resources</li>
<li>Handle dependency loading and unloading</li>
<li>Help create clean, scalable game architectures</li>
</ul>
<p>The caveat is that using <code>Addressables</code> is complicated, it requires good foundational knowledge of Unity, and introduces the complexities of asynchronous programming.</p>
<p>In this article I am introducing a simple helper class that hides some of this complexity and fits seamlessly into the coroutine pattern.</p>
<p>Let's assume that you've installed <code>Addressables</code> are starting to switch from hard references of <code>GameObject prefab</code> to weak references of <code>AssetReference prefab</code>.  If you're already using and familiar with coroutines then you can use this helper to initiate the instantiation of an Addressable asset and wait for completion.</p>
<pre><code class="lang-C#">
<span class="hljs-keyword">using</span> System;
<span class="hljs-keyword">using</span> UnityEngine;
<span class="hljs-keyword">using</span> UnityEngine.AddressableAssets;
<span class="hljs-keyword">using</span> UnityEngine.ResourceManagement.AsyncOperations;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">AddressablesHelper</span>
{

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> WaitUntil <span class="hljs-title">Instantiate</span>(<span class="hljs-params">AssetReference asset, Action&lt;GameObject&gt; callback</span>)</span>
    {
        <span class="hljs-keyword">return</span> Instantiate(asset, <span class="hljs-literal">null</span>, Vector3.zero, Quaternion.identity, callback);
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> WaitUntil <span class="hljs-title">Instantiate</span>(<span class="hljs-params">AssetReference asset, Transform parent, Action&lt;GameObject&gt; callback</span>)</span>
    {
        <span class="hljs-keyword">return</span> Instantiate(asset, parent, Vector3.zero, Quaternion.identity, callback);
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> WaitUntil <span class="hljs-title">Instantiate</span>(<span class="hljs-params">AssetReference asset,
                                                Transform parent,
                                                Vector3 position,
                                                Quaternion rotation,
                                                Action&lt;GameObject&gt; callback</span>)</span>
    {
        <span class="hljs-keyword">var</span> handle = Addressables.InstantiateAsync(asset, position, rotation, parent);

        handle.Completed += handle =&gt;
        {
            <span class="hljs-keyword">if</span> (handle.Status == AsyncOperationStatus.Succeeded)
            {
                callback?.Invoke(handle.Result);
            }
            <span class="hljs-keyword">else</span>
            {
                callback?.Invoke(<span class="hljs-literal">null</span>);
                Addressables.Release(handle);
            }
        };

        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> WaitUntil(() =&gt; handle.IsDone);
    }

}
</code></pre>
<p>Now you can weave these calls into your coroutines like this:</p>
<pre><code class="lang-C#">
<span class="hljs-function"><span class="hljs-keyword">private</span> IEnumerable <span class="hljs-title">Loading</span>(<span class="hljs-params"></span>)</span>
{
    <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> AddressablesHelper.Instantiate(menuPrefab, transform, (go) =&gt;
        {
            Log.Info(name, <span class="hljs-string">"Loaded menu controller..."</span>);
            _menu = go.GetComponent&lt;MenuController&gt;();
        });

    _menu.ShowLoading();
}
</code></pre>
<p>The helper methods return a <code>WaitUntil</code> that is tied to the <code>AsyncOperationHandle</code> of the Addressables invocation. You pass in an <code>Action</code> or define it inline as I do in the above example.</p>
<p>This hides some of the pain from having to manage handles, the only thing to track is releasing the reference for this instantiated <code>GameObject</code>, which should be done via <code>Addressables.ReleaseInstance()</code> to ensure the asset is unloaded when there are no more references. </p>
<pre><code class="lang-C#">
<span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnDestroy</span>(<span class="hljs-params"></span>)</span>
{
    Addressables.ReleaseInstance(_menu.gameObject);
}
</code></pre>
<p>More to come on Addressables, please subscribe to my newsletter if interested and comment your thoughts on this pattern.</p>
]]></content:encoded></item><item><title><![CDATA[A Simple Event System for Unity]]></title><description><![CDATA[Every game project needs an event system to prevent tight coupling between game systems and objects. 
There are several approaches to implementing events, such as simply adding a UnityEvent to a MonoBehaviour or going all in on the popular trend of S...]]></description><link>https://playable.design/a-simple-event-system-for-unity</link><guid isPermaLink="true">https://playable.design/a-simple-event-system-for-unity</guid><category><![CDATA[unity]]></category><category><![CDATA[Unity Engine]]></category><category><![CDATA[Game Development]]></category><dc:creator><![CDATA[Steve Mcilwain]]></dc:creator><pubDate>Mon, 15 Aug 2022 13:10:20 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1660568779278/3eCe6R-J2.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Every game project needs an event system to prevent tight coupling between game systems and objects. </p>
<p>There are several approaches to implementing events, such as simply adding a <a target="_blank" href="https://docs.unity3d.com/ScriptReference/Events.UnityEvent.html">UnityEvent</a> to a <code>MonoBehaviour</code> or going all in on the popular trend of <a target="_blank" href="https://unity.com/how-to/architect-game-code-scriptable-objects#architect-events">SciptableObject events</a>.</p>
<p>I am a big fan of event systems and have implemented many different variations. As a mostly solo indie developer though, some of these implementations can become time consuming and difficult to manage over time. I like the <code>ScriptableObject</code> approach, but I don't have game designers who need to wire up events in the editor. So for me, with a developer-centric, small-team workflow I tend to favor something simple and effective.</p>
<h2 id="heading-a-simple-event-system">A Simple Event System</h2>
<p>After trying different approaches, these are the goals that this event system satisfies:</p>
<ul>
<li>Developer centric workflow, doesn't require wiring up in the editor</li>
<li>Can quickly define new events of any type</li>
<li>Requires a minimal number of classes, expandable for better team workflow with source control</li>
<li>Follows a pub/sub model and is performant at scale</li>
</ul>
<p>This approach involves creating 3 classes for your event system:</p>
<ul>
<li><code>GameEvent</code> class</li>
<li>Generic <code>GameEvent&lt;T&gt;</code> class</li>
<li><code>Events</code> static class that houses all of the defined events for the game</li>
</ul>
<h2 id="heading-gameevent-class">GameEvent Class</h2>
<p>This is a simple C# class that defines a game event with no parameters and uses an <a target="_blank" href="https://docs.microsoft.com/en-us/dotnet/api/system.action-1?view=net-6.0">Action</a> with the <a target="_blank" href="https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/event">event</a> keyword to represent a multicast delegate. </p>
<p>While you could simply make the <code>Action</code> public and allow subscribers to directly register themselves, I prefer to encapsulate it with simple <code>Add</code> and <code>Remove</code> methods. This gives me room to later add in validation or logging behavior to these methods if needed.</p>
<pre><code class="lang-C#">
<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">GameEvent</span>
{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">event</span> Action action = <span class="hljs-keyword">delegate</span> { };

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Publish</span>(<span class="hljs-params"></span>)</span>
    {
        action?.Invoke();
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Add</span>(<span class="hljs-params">Action subscriber</span>)</span>
    {
        action += subscriber;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Remove</span>(<span class="hljs-params">Action subscriber</span>)</span>
    {
        action -= subscriber;
    }
}
</code></pre>
<h2 id="heading-generic-gameevent-class">Generic GameEvent Class</h2>
<p>Next, add a generic class for events that need to pass a type as a parameter.</p>
<pre><code class="lang-C#">
<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">GameEvent</span>&lt;<span class="hljs-title">T</span>&gt;
{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">event</span> Action&lt;T&gt; action;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Publish</span>(<span class="hljs-params">T param</span>)</span>
    {
        action?.Invoke(param);
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Add</span>(<span class="hljs-params">Action&lt;T&gt; subscriber</span>)</span>
    {
        action += subscriber;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Remove</span>(<span class="hljs-params">Action&lt;T&gt; subscriber</span>)</span>
    {
        action -= subscriber;
    }
}
</code></pre>
<p>You could keep adding classes for multiple types, such as <code>public class GameEvent&lt;S,T&gt;</code> if needed, but I'd only do this when the requirement comes up.</p>
<h2 id="heading-static-events-class">Static Events Class</h2>
<p>Lastly we now need somewhere to define and house events that can be used at runtime. When I need something global I typically ask, can it be <code>static</code> in scope? If I don't need it to be visible in the Scene hierarchy, then I go with static instead of troubling myself with Singleton MonoBehaviours or ScriptableObject instances.</p>
<pre><code class="lang-C#">
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">partial</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Events</span>
    {

        <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">readonly</span> GameEvent onJump = <span class="hljs-keyword">new</span>();
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">readonly</span> GameEvent&lt;<span class="hljs-keyword">float</span>&gt; onDamage = <span class="hljs-keyword">new</span>();
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">readonly</span> GameEvent&lt;GameObject&gt; onPickup = <span class="hljs-keyword">new</span>();

     }
</code></pre>
<p>I marked this class partial, because after you start defining events, it can become a long list and if you have other team members modifying it, then it can introduce the risk of a merge conflict. With a partial class, you can organize your events into a multilpe .cs files, for example:</p>
<ul>
<li>Events_Menu.cs</li>
<li>Events_Player.cs</li>
<li>Events_Enemy.cs</li>
</ul>
<h2 id="heading-subscribing-to-events">Subscribing to Events</h2>
<p>Subscribers will need to add and remove themselves in their <code>OnEnable</code> and <code>OnDisable</code> methods. It can be tedious, but I have snippets setup to quickly frame this out and fill in.</p>
<pre><code class="lang-C#">
<span class="hljs-keyword">using</span> UnityEngine;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Player</span>: <span class="hljs-title">MonoBehaviour</span>
{

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnEnable</span>(<span class="hljs-params"></span>)</span>
    {
        Events.onJump.Add(OnPickup);
        Events.onDamage.Add(OnDamage);
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnDisable</span>(<span class="hljs-params"></span>)</span>
    {
        Events.onJump.Remove(OnPickup);
        Events.onDamage.Remove(OnDamage);
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnPickup</span>(<span class="hljs-params">GameObject item</span>)</span>
    {
        Debug.Log(<span class="hljs-string">$"Picked up item: <span class="hljs-subst">{item.name}</span>"</span>);
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnDamage</span>(<span class="hljs-params"><span class="hljs-keyword">float</span> amount</span>)</span>
    {
        Debug.Log(<span class="hljs-string">$"Damaged: <span class="hljs-subst">{amount}</span>"</span>);
    }
}
</code></pre>
<h2 id="heading-publishing-events">Publishing Events</h2>
<p>Publishing is as easy as a single line of code.</p>
<pre><code class="lang-C#"><span class="hljs-keyword">using</span> UnityEngine;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Pickup</span>: <span class="hljs-title">MonoBehaviour</span>
{
    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnCollisionEnter</span>(<span class="hljs-params">Collision other</span>)</span>
    {
        <span class="hljs-keyword">if</span> (other.CompareTag(<span class="hljs-string">"player"</span>))
        {
            Events.onPickup.Publish(gameObject);
        }
    }
}
</code></pre>
<h2 id="heading-summary">Summary</h2>
<p>This is a simple solution but it can be extended with logging, editor tooling to show subscribers during play mode, and because the Events class is marked <code>partial</code> you can add multiple files to better organize your events or split them out by team members to avoid merge conflicts. Feel free to use this pattern, suggest improvements, and you can view a real world implementation in one of my recent game jam submissions, <a target="_blank" href="https://github.com/stevemcilwain/Moodledy">Moodledy</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Designing Games for Accessibility]]></title><description><![CDATA[Designing games to be playable by everyone is important to consider early in the design process. Review guidelines and challenges that people face to figure out what elements of your game will benefit from accessibility features. Here's a few resourc...]]></description><link>https://playable.design/designing-games-for-accessibility</link><guid isPermaLink="true">https://playable.design/designing-games-for-accessibility</guid><category><![CDATA[Accessibility]]></category><category><![CDATA[unity]]></category><category><![CDATA[Unity Engine]]></category><category><![CDATA[Game Development]]></category><dc:creator><![CDATA[Steve Mcilwain]]></dc:creator><pubDate>Sat, 13 Aug 2022 19:31:29 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1660418711375/n_pg9fRFQ.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Designing games to be playable by everyone is important to consider early in the design process. Review guidelines and challenges that people face to figure out what elements of your game will benefit from accessibility features. Here's a few resources to get your started.</p>
<h2 id="heading-game-accessibility-guidelines">Game Accessibility Guidelines</h2>
<p>This site groups guidelines into Basic, Intermediate and Advanced categories, so that one can choose a level of investment in accessibility features. The guidelines are a collaborative effort from a group of studios, specialists, and academics.</p>
<p>https://gameaccessibilityguidelines.com/</p>
<h2 id="heading-can-i-play-that">Can I Play That</h2>
<p>This site is a treasure trove of reviews and news of real-world games and their adoption of accessibility features. They also offer workshops and already work with some high profile AAA studios.</p>
<p>https://caniplaythat.com/</p>
<h2 id="heading-free-unity-courses">Free Unity Courses</h2>
<p>The training site, <a target="_blank" href="https://www.raywenderlich.com/">raywenderlich.com</a>, offers 2 free training courses on how to practically implement accessibility features in Unity games. </p>
<ul>
<li><a target="_blank" href="https://www.raywenderlich.com/5783444-improving-accessibility-in-unity-games-part-1">Improving Accessibility in Unity Games – Part 1</a></li>
<li><a target="_blank" href="https://www.raywenderlich.com/5783534-improving-accessibility-in-unity-games-part-2">Improving Accessibility in Unity Games – Part 2</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Organizing Components with AddComponentMenu in Unity]]></title><description><![CDATA[Inspector > Add Component
By default, when you create a new MonoBehaviour component, it is added to the menu under Scripts/{namespace}/{component} and gets jumbled in with a lot of other random stuff from packages you have imported.
Default Example
n...]]></description><link>https://playable.design/organizing-components-with-addcomponentmenu-in-unity</link><guid isPermaLink="true">https://playable.design/organizing-components-with-addcomponentmenu-in-unity</guid><category><![CDATA[unity]]></category><category><![CDATA[Unity Engine]]></category><category><![CDATA[Game Development]]></category><dc:creator><![CDATA[Steve Mcilwain]]></dc:creator><pubDate>Tue, 09 Aug 2022 18:56:03 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1660419396140/XspSbNDmX.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-inspector-andgt-add-component">Inspector &gt; Add Component</h2>
<p>By default, when you create a new <code>MonoBehaviour</code> component, it is added to the menu under <em>Scripts/{namespace}/{component}</em> and gets jumbled in with a lot of other random stuff from packages you have imported.</p>
<p><strong>Default Example</strong></p>
<pre><code class="lang-C#"><span class="hljs-keyword">namespace</span> <span class="hljs-title">PlayableDesign.ThriftShop</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">CameraController</span> : <span class="hljs-title">MonoBehaviour</span>
    {
    }
}
</code></pre>
<p><strong>Menu</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1660070867001/lBSD4D7y3.png" alt="image.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1660070529440/p4WT2Sptf.png" alt="image.png" /></p>
<p>Yuck!</p>
<h2 id="heading-addcomponentmenu">AddComponentMenu</h2>
<p>Instead, you can create a custom folder at the top level of the context menu in the editor using the <code>AddComponentMenu</code> attribute.</p>
<p><strong>Better Example</strong></p>
<pre><code class="lang-C#">
[<span class="hljs-meta">AddComponentMenu(<span class="hljs-meta-string">"Custom/"</span> + nameof(CameraController))</span>]
<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">CameraController</span> : <span class="hljs-title">MonoBehaviour</span>
{

}
</code></pre>
<p><strong>Menu</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1660070111959/0SjkfIu5g.png" alt="image.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1660070629737/PG6ZbdPFm.png" alt="image.png" /></p>
<p><strong>Best Example</strong></p>
<p>Even better, I like to use dashes as the folder name so that my components are always on top.</p>
<pre><code class="lang-C#">
[<span class="hljs-meta">AddComponentMenu(<span class="hljs-meta-string">"-----/"</span> + nameof(CameraController))</span>]
<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">CameraController</span> : <span class="hljs-title">MonoBehaviour</span>
{

}
</code></pre>
<p><strong>Menu</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1660070796778/P9tw6GIah.png" alt="image.png" /></p>
<h2 id="heading-enjoy">Enjoy!</h2>
]]></content:encoded></item><item><title><![CDATA[A Steps-Based Progress Slider for Unity]]></title><description><![CDATA[Problem:
I want a loading canvas that will display a loading progress bar that updates based on loading activities within the scene. I always forget the little details about how to make the slider look like a progress bar.
Solution:
Add a Slider to a...]]></description><link>https://playable.design/a-steps-based-progress-slider-for-unity</link><guid isPermaLink="true">https://playable.design/a-steps-based-progress-slider-for-unity</guid><category><![CDATA[unity]]></category><category><![CDATA[Unity Engine]]></category><category><![CDATA[progress bar]]></category><dc:creator><![CDATA[Steve Mcilwain]]></dc:creator><pubDate>Sun, 01 May 2022 19:34:31 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1660419613142/xleA6uEnP.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-problem">Problem:</h2>
<p>I want a loading canvas that will display a loading progress bar that updates based on loading activities within the scene. I always forget the little details about how to make the slider look like a progress bar.</p>
<h2 id="heading-solution">Solution:</h2>
<p>Add a Slider to a UI Canvas:
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1659553587326/m-ya_zPF8.png" alt="image.png" /></p>
<p>Uncheck <em>Interactable</em> and set <em>Transition</em> to <em>None</em>:
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1659553666189/cqRrtvK2X.png" alt="image.png" /></p>
<p>Delete the "Handle Slide Area" <em>GameObject</em> from the hierarchy:
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1659553728633/nf1A7x6BJ.png" alt="image.png" /></p>
<p>Select the "Fill" <em>GameObject</em> and set the <em>Width</em> of the <em>Rect Transform</em> to "20" (or use whatever value you like). Also change the <em>Color</em> of the <em>Image</em> to a value you prefer:
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1659553937849/wFiKyUjRA.png" alt="image.png" /></p>
<p>Select the "Fill Area" <em>GameObject</em> and set the <em>Right</em> value of the <em>Rect Transform</em> to "10" (or use half of the value you set for the Fill):
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1659554153033/GhC6oww6u.png" alt="image.png" /></p>
<p>Select the "Background" <em>GameObject</em> and set the <em>Alpha</em> value of the <em>Image</em> to "1" (or whatever you prefer):
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1659554235739/sCE_r7OyA.png" alt="image.png" /></p>
<p>Adjust the size of the "Slider" <em>Rect Transform</em> to meet your preferences and play with the Value to get a feel for how it will look at runtime. 
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1659554862124/tZsMpRa7I.png" alt="image.png" /></p>
<h3 id="heading-scripting">Scripting</h3>
<p>Attach the script below to the slider and set the number of steps in the scene that have to be completed for loading to be completed. </p>
<p>For example, let's say I have a <strong>SaveManager </strong> object that is loading state from a file, an <strong>ItemSpawner </strong> that is placing prefabs and a <strong>MusicManager </strong> that is loading level music then that would be 3 steps to complete. </p>
<pre><code class="lang-C#">  <span class="hljs-keyword">using</span> UnityEngine;
  <span class="hljs-keyword">using</span> UnityEngine.UI;

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Loading</span> : <span class="hljs-title">MonoBehaviour</span>
    {

        [<span class="hljs-meta">Tooltip(<span class="hljs-meta-string">"Set this value to the number of loading steps"</span>)</span>]
        [<span class="hljs-meta">SerializeField</span>] <span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> steps;

        <span class="hljs-comment">// Private state</span>

        <span class="hljs-keyword">private</span> Slider _slider;
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> _completed;

        <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Awake</span>(<span class="hljs-params"></span>)</span>
        {
            _slider = GetComponent&lt;Slider&gt;();
            _slider.<span class="hljs-keyword">value</span> = <span class="hljs-number">0f</span>;
        }

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Reset</span>(<span class="hljs-params"></span>)</span>
        {
            _completed = <span class="hljs-number">0</span>;
            _slider.<span class="hljs-keyword">value</span> = <span class="hljs-number">0f</span>;
        }

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnStepCompleted</span>(<span class="hljs-params"></span>)</span>
        {
            _completed++;
            _slider.<span class="hljs-keyword">value</span> = (_completed / steps);
        }

    }
</code></pre>
<p>Now you can have other scripts in the scene call the <em>OnStepCompleted</em> method when each has completed its own loading activities. </p>
<p>You can expand on this solution with events and callbacks, but it's a simple start to displaying progress within a scene.</p>
]]></content:encoded></item><item><title><![CDATA[A Colorful Logging Component for Unity]]></title><description><![CDATA[Problem:
I want to add logging to a GameObject that can be called via code or wired up via the inspector. I also want to specify the color of the logs in the inspector so that I can easily distinguish the messages in the debug console.
Solution:
Use ...]]></description><link>https://playable.design/a-colorful-logging-component-for-unity</link><guid isPermaLink="true">https://playable.design/a-colorful-logging-component-for-unity</guid><category><![CDATA[unity]]></category><category><![CDATA[Unity Engine]]></category><category><![CDATA[Game Development]]></category><dc:creator><![CDATA[Steve Mcilwain]]></dc:creator><pubDate>Fri, 01 Apr 2022 19:13:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1660419741330/8toDhOG8R.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-problem">Problem:</h2>
<p>I want to add logging to a <em>GameObject</em> that can be called via code or wired up via the inspector. I also want to specify the color of the logs in the inspector so that I can easily distinguish the messages in the debug console.</p>
<h2 id="heading-solution">Solution:</h2>
<p>Use the code below to create a new component that you can add to <em>GameObjects</em>. You can then access the logging methods via <code>GetComponent&lt;LoggingBehavior&gt;()</code> or wire up logging calls in the inspector. </p>
<pre><code class="lang-c#"><span class="hljs-keyword">using</span> UnityEngine;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">LoggingBehavior</span> : <span class="hljs-title">MonoBehaviour</span>
{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">string</span> FORMAT = <span class="hljs-string">"&lt;color=#{0}&gt;&lt;b&gt;{1}&lt;/b&gt;: {2}&lt;/color&gt;"</span>;

    [<span class="hljs-meta">Header(<span class="hljs-meta-string">"Settings"</span>)</span>]
    [<span class="hljs-meta">Space(10)</span>]

    [<span class="hljs-meta">Tooltip(<span class="hljs-meta-string">"Set the color for log messages"</span>)</span>]
    [<span class="hljs-meta">SerializeField</span>] <span class="hljs-keyword">private</span> Color logColor = Color.white;

    <span class="hljs-comment">// Private State</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">string</span> _htmlColor;

    <span class="hljs-comment">// Unity Lifecycle</span>

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Awake</span>(<span class="hljs-params"></span>)</span>
    {
        _htmlColor = ColorUtility.ToHtmlStringRGB(logColor);
    }

    <span class="hljs-comment">// Public API</span>

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">LogInfo</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> message</span>)</span>
    {
        Debug.LogFormat(gameObject, FORMAT, _htmlColor, gameObject.name, message);
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">LogWarning</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> message</span>)</span>
    {
        Debug.LogWarningFormat(gameObject, FORMAT, _htmlColor, gameObject.name, message);
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">LogError</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> message</span>)</span>
    {
        Debug.LogErrorFormat(gameObject, FORMAT, _htmlColor, gameObject.name, message);
    }

}
</code></pre>
<p>The results, in this example a timer component that invokes UnityEvents is connected to the LoggingBehavior component.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1659467489744/PG-6qSj1y.png" alt="image.png" /></p>
]]></content:encoded></item><item><title><![CDATA[Burst Particles at Runtime with Unity]]></title><description><![CDATA[Problem:
I want to emit a specific number of particles from a ParticleSystem at runtime. For example, in the game CatBoarder, for every spin the player completed during a jump, I wanted to emit a particle for each.
Solution

Unity 2020+

Add a Partic...]]></description><link>https://playable.design/burst-particles-at-runtime-with-unity</link><guid isPermaLink="true">https://playable.design/burst-particles-at-runtime-with-unity</guid><category><![CDATA[unity]]></category><category><![CDATA[Unity Engine]]></category><category><![CDATA[Game Development]]></category><category><![CDATA[particleSystem]]></category><dc:creator><![CDATA[Steve Mcilwain]]></dc:creator><pubDate>Tue, 01 Mar 2022 19:39:44 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1660419867923/DFEyg4_OR.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-problem">Problem:</h2>
<p>I want to emit a specific number of particles from a <em>ParticleSystem</em> at runtime. For example, in the game <a target="_blank" href="https://playable.design/games">CatBoarder</a>, for every spin the player completed during a jump, I wanted to emit a particle for each.</p>
<h2 id="heading-solution">Solution</h2>
<ul>
<li>Unity 2020+</li>
</ul>
<p>Add a <em>ParticleSystem</em> to the <em>GameObject</em> that will be emitting particles. Uncheck <em>Looping</em> and <em>Play on Awake</em>.</p>
<p>Under <em>Emissions</em>, configure <em>Bursts</em> to only emit one particle.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1659464626228/OxIu1sEQ3.png" alt="image.png" /></p>
<p>Add the code below as a component, then call <code>Emit</code> with the number of particles to emit.</p>
<pre><code class="lang-c#"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">BurstEffect</span>: <span class="hljs-title">MonoBehaviour</span>
{
    <span class="hljs-keyword">private</span> ParticleSystem _particles;
    <span class="hljs-keyword">private</span> ParticleSystem.Burst _burst;

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Awake</span>(<span class="hljs-params"></span>)</span>
    {
        _particles = GetComponent&lt;ParticleSystem&gt;();
        _burst = _particles.emission.GetBurst(<span class="hljs-number">0</span>);
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Emit</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> score</span>)</span>
    {
        _burst.count = score;
        _particles.emission.SetBurst(<span class="hljs-number">0</span>, _burst);
        _particles.Play();
    }
}
</code></pre>
<p>The results... a happy cat after 2 spins!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1659465473650/j8FIKgxd8.png" alt="image.png" /></p>
<h2 id="heading-further-reading">Further Reading:</h2>
<ul>
<li>https://docs.unity3d.com/Manual/PartSysEmissionModule.html</li>
</ul>
]]></content:encoded></item></channel></rss>