Wednesday, April 13, 2016

Too Much Clutter - or how I finally decided to make a dialog box

Sub-Zero Squirrel Games
Wednesday, April 14, 2016 - Little Rock, Arkansas, USA

Man this canvas is getting a little cluttered. The hardest thing we're working on right now is the Graphical User Interface, which I will just call GUI like a good nerd from here on out. With all the various settings, options, requests, information, feedback, and other stuff we the games needs to let you know what's going on and what's next and how you prefer the experience, the various parts are starting to get a little crazy. There's a bunch of stuff you don't see sitting off screen waiting for it's moment of glory. Check out this comparison:


On the left you see the exploded canvas waiting to be brought onscreen, which you see in the right. The canvas is an overlay on the screen, separate of the camera, which is why you don't see the game objects (ship. moon, etc. ) on the left. There's a lot of stuff hanging over the edges. But wait, you can't even see all the various layers beneath stuff. Believe me, it's a lot of stuff.

You don't believe me? Well, let's just prove it, here's a picture of the animation tree...



It's only going to get worse as I go. I need a better solution.

My AHA moment

In my years of writing software for people to use, I've learned that you have to be careful about when you let people click buttons in your application. In general, users will find amazingly unique ways to mess up your carefully crafted logical program. So, you have to ask them if they really mean to do what they just asked your program to do. Now, in any .Net situation, I'd simply just pop open a dialog box, and ask them a question, "ARE YOU SURE YOU'RE NOT AN IDIOT?" (Yes, I've actually used that question before in  program at work.) 

But, in Unity, there's no such easy call just built in, and besides, anything we put on the screen should reflect the design on the rest of the game, and not be some generic OS provided message box. 

I know I'm not the first one to stumble across this simple solution, but I'm going to share mine anyway. It's short. sweet, to-the-point, and could save someone else a lot of work. 

The rest of this post is all geek speak, if you're interested in that part, keep reading. Otherwise, play the game already:





I created this dialog box starting with an Image on the main canvas, which has been tagged "MainCanvas" and is a prefab which is used in every gameplay scene. It has an invisible full screen image behind it, so that it becomes modal. I attached a script to it, made a new prefab, dropped the whole group of canvas objects into it, then deleted it from the scene. 

Here's the code attached to this new prefab: 

using UnityEngine;

using System.Collections;
using UnityEngine.UI;

public class DialogBox : MonoBehaviour {

//this is a modal doalog box for TFOS
    //this string right here is the name of the function to call when the
    //player has responded:
    public string CallBack;
    
    //a list of character images to choose from, and a way to set that choice
    public Sprite[] Characters;
    public int CharaterIndex;

    //options for the dialog
    public string Message;
    public int Alignment;
    public string ChoiceOne;
    public string ChoiceTwo;
    
    //references to the parts of the dialog that need to change
    public Text ButtonOneText;
    public Text ButtonTwoText;
    public Text MessageText;
    public Image CharacterImage;

    void Start()
    {

        CharacterImage.sprite = Characters[CharaterIndex];
        MessageText.text = Message;

        switch (Alignment)
        {
            case -1:
                MessageText.alignment = TextAnchor.UpperLeft;
                break;
            case 0:
                MessageText.alignment = TextAnchor.UpperCenter;
                break;
            case 1:
                MessageText.alignment = TextAnchor.UpperRight;
                break;
        }


        ButtonOneText.text = ChoiceOne;
        ButtonTwoText.text = ChoiceTwo;

    }

    public void OnButtonOne()
    {
        Debug.Log("Sending Choice: " + ChoiceOne);
        GameObject.FindGameObjectWithTag("MainCanvas").SendMessage(CallBack, (int)1);
        Destroy(gameObject);
    }

    public void OnButtonTwo() 
    {
        GetComponentInParent<CanvasManager>().SendMessage(CallBack, (int)2);
        GameObject.FindGameObjectWithTag("MainCanvas").SendMessage(CallBack, (int)1);
        Destroy(gameObject);
    }


}

Now I just need to add a reference to this new prefab into my main canvas manager script, and instantiate a new one whenever I need to ask the player a question. It could be any question, but let's ask them about Facebook:

//the prefab is a UI Image, with attached components
public Image PrefabDialogBox;

public void OnFBInteract()
    {
        if (FB.IsLoggedIn)
        {
            Image _DialogBox = Instantiate(PrefabDialogBox);
            _DialogBox.transform.parent = transform;
            _DialogBox.transform.position = new Vector3(Screen.width / 2f, Screen.height / 2f, 0);


            DialogBox ThisDialog = _DialogBox.GetComponent<DialogBox>();
            ThisDialog.Message = "Do you want to log out of Facebook and remove the information?";
            ThisDialog.ChoiceOne = "yes";
            ThisDialog.ChoiceTwo = "no";
            ThisDialog.CharaterIndex = 3;
            ThisDialog.Alignment = 1;
            ThisDialog.CallBack = "FaceBookLogoutreply";


        }
        else
        {
            Image _DialogBox = Instantiate(PrefabDialogBox);
            _DialogBox.transform.parent = transform;
            _DialogBox.transform.position = new Vector3(Screen.width / 2f, Screen.height / 2f, 0);


            DialogBox ThisDialog = _DialogBox.GetComponent<DialogBox>();
            ThisDialog.Message = "Can we access your public Facebook profile? We need to be able to notify your next of kin.";
            ThisDialog.ChoiceOne = "yes";
            ThisDialog.ChoiceTwo = "no";
            ThisDialog.CharaterIndex = 2;
            ThisDialog.Alignment = -1;
            ThisDialog.CallBack = "FacebookLoginReply";


        }


    }

    public void FaceBookLogoutreply(int Choice)
    {
        if (Choice == 1)
        {
            FB.LogOut();
            //TODO: clear any FB data we've requested
        }
        else
        {
            //player chose not to log out, or something else
            //Do.Nothing();
        }

    }

    public void FacebookLoginReply(int Choice)
    {
        if (Choice == 1)
        {
            //TODO: log into facebook

        }


    }

So there you have it. No more creating permanent instances and cluttering up EVEN MORE of my canvas with simple yes/no questions. I still need to leave in those parts which will be animated in and out, but that's just what it is, and I'm not going to complain.

Until next time, Stay Frosty,
~Sub-Zero Chuck

1 comment:

  1. Hey, just so you know that no good deed is completely useless, I came across your Reddit post linking to this subject (dialog boxes) three years later, and it helped me. Thank you :)

    ReplyDelete