Dungeon Ho!: Unity – Dev Blog #11

Welcome back, my long-lost readers!  It’s been way too long.  You’re looking well.  I wish I could say I’ve been hard at work on the ‘Ho!, but that would be a lie.  You know how it is.  Things happen.

Anyway… I’m here now, and that’s all that matters.  When last we met, I promised y’all that we would take a look at the UIManager and its various related classes, which is a convenience that I whipped up in order to manage all of the various menu screens and dialogs needed to make the game work.

But first, let’s talk design.  The UIManager class is basically a collection of methods that tracks, keeps reference to, and handles various functionality – such as hiding and showing – screens and dialogs from within the game.  These various UI components are, as you may well know, Unity Canvas objects with a script attached, appropriately named UIScreen.

Before we carry on with that, I noticed something that’s been happening ever since I started using text blocks to format my code examples.  Specifically, that the left and right angle brackets (< and >) in my code are getting “swallowed” by the HTML parser, and I can’t be assed to go in and correct every single instance of it.  If it affects anything important, like if-then logic, I’ll deal with it.  Otherwise, the bulk of the cases are List object declarations; so if you see something like List myList, assume I’m really trying to say List<Class> myList and roll with it.  You’re smart, you should be able to figure out what the relevant class is supposed to be.  Got it?  Good.  Let’s continue.

In Dungeon Ho! parlance, a UIScreen is basically a Unity Canvas with some additional functionality attached via a script, like so;

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class UIScreen : MonoBehaviour {

    protected Player player;

    // Use this for initialization
	void Start () {
		
	}
	
	// Update is called once per frame
	void Update () {
		
	}

    // Override this method to prepare the screen for viewing.
    public virtual void prepare()
    {
        if (player == null)
            player = GameObject.Find("Dungeon").GetComponent().getPlayer();
    }

    public void show()
    {
        // Yes, this doesn't make sense - but if the prepare(); method
        // alters the contents of the UI, its positions/dimensions/etc. 
        // won't get re-calculated if it's not active.
        gameObject.SetActive(true);

        prepare();
    }

    public void hide()
    {
        gameObject.SetActive(false);
    }

    public void toggle()
    {
        if (gameObject.activeSelf)
            hide();
        else
            show();
    }

    public bool isShowing()
    {
        return gameObject.activeSelf;
    }

    public virtual bool handleHotkey()
    {
        return false;
    }
}

The most interesting bits of the code are in the prepare() method.  This method is called before the UIScreen is about to be shown, and is where each subclass initializes its UI components with the relevant data, as we’ll see in a future post.  It also makes sure that there’s a reference to the game’s Player object, by explicitly caching a reference to one if it can find it.  (ProTip:  If it can’t find it, you broke the game somehow.  There will always be a player object for it to find.)

Why do we do this, rather than simply go into the Unity UI and drag our Player into the relevant slot?  Because it’s one line of code that does the same thing for every UI component and I can’t be bothered to do it the other way.  Maybe if I had a UI guy wiring up these dialogs for me, I’d care.  Rule #1 of solo projects, it’s okay to cut corners and not always follow “best practice” – which is usually only “best” because engineers love to follow the herd.  Mini-rant over.

Also note that the show() method puts what we just described into practice, by calling the prepare() method after setting the gameObject (IE, the UI component this script is attached to) active.  Why don’t we call prepare() first?  Because one of the first “features” I discovered when (logically) implementing it that way is that Canvases don’t update their UI components when they’re not active, so trying to set the data of, say, a ScrollContainer in the Canvas will simply fail.  (Hey, that’s in the code comments – whaddaya know.  Rule #2 of solo programming, always document this stuff when you find it… especially when you’re old and your memory is going, like I am – and mine is.)

OK, enough about UIScreen.  We’ll see how to actually make one in a future blog post.  Let’s look at UIManager next.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class UIManager
{
    public const int INVENTORY_SCREEN   = 0;
    public const int CHARACTER_SHEET    = 1;
    public const int PICK_UP_ITEMS      = 2;
    public const int QUEST_LOG          = 3;

    private List uiComponents;

    public UIManager(GameObject ui)
    {
        uiComponents = new List();

        foreach (Transform canvas in ui.transform)
        {
            uiComponents.Add(canvas.gameObject.GetComponent());
        }

        Debug.Log("UIManager found " + uiComponents.Count + " UI components.");
    }

    public void showUI(int id)
    {
        uiComponents[id].show();
    }

    public void hideAll()
    {
        foreach (UIScreen screen in uiComponents)
        {
            screen.hide();
        }
    }

    public void hideUI(int id)
    {
        uiComponents[id].hide();
    }

    public void toggleUI(int id)
    {
        uiComponents[id].toggle();
    }

    public bool isShowing(int id)
    {
        return uiComponents[id].isShowing();
    }

    public UIScreen getUiComponent(int id)
    {
        return uiComponents[id];
    }

    public UIScreen getTopmostUiComponent()
    {
        UIScreen top = null;

        foreach (UIScreen screen in uiComponents)
        {
            if (screen.isShowing())
                top = screen;
        }

        return top;
    }
}

First thing you’ll notice is a bit of laziness; I loop through the passed-in GameObject (…which is the top-level scene node that I have parented all of my UI canvases to) and grab references to them, putting them into a local List, which is indexed by constants.  (I could also have made those constants Strings and used a Dictionary.  Either-or.)

Now, whenever I want to reference a particular UI component, I just use its index constant.  Like so;

uiManager.hide(UIManager.QUEST_LOG);

…and speaking of, the main Game object instantiates an instance of UIManager in its own Unity-supplied start() method;

uiManager = new UIManager(GameObject.Find("UI").gameObject);

Note that I pass in the GameObject named “UI”, as previously described.

Pretty straightforward, eh?  Tune in next time when we look at the Inventory Screen, how it’s derived from UIScreen, and how it’s wired up to the Player’s inventory.  Once that’s out of the way, we’ll intercept some mouse clicks and be able to pick up and drop items… finally!

– Steve

Categories: Development, Dungeon Ho!, Unity | Leave a comment

Post navigation

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Create a free website or blog at WordPress.com.

%d bloggers like this: