5
\$\begingroup\$

So, I'm currently learning both about Unity for the first time as well as the 'new' 2D UI system, but I'm having a hard time figuring out how to make windows, modals/dialogs, main menus etc. which are not only functioning, but also modular, reusable and written following proper coding standards. Most how to's, blogs, Q&As or tutorials I find only cover the most basic parts of UI development (e.g. how to drag a button from the toolbar to the scene window in 2 hours).

So basically, I'd like to have a main menu which meets those (very common) requirements:

  • A button which 'starts' the game (e.g. by simply loading another scene)
  • A button which 'quits' the game, but also opens up a confirmation dialog before
  • A button which opens up an options dialog, which again is composed of multiple parts (audio, video...)
  • and finally, it should be possible to go back to the previous screen using a 'back key/button', for example Escape

What I started with is a canvas which I called GameMenu. This canvas has several nested canvases, such as

  • MainMenu
  • OptionsMenu
    • OptionsMenuAudioPart
    • OptionsMenuVideoPart
  • CancelMenuModal

Then I created a menu script which works as some sort of state machine:

public class MenuScript : MonoBehavior {

    private GameObject _currentStateObject;
    private GameObject _previousStateObject;

    public void SetState(GameObject newStateObject) {
        if(newStateObject == null || newStateObject)
            return;

        if(CurrentStateObject != null)
            CurrentStateObject.SetActive(false);
        _previousStateObject = _currentStateObject;
        CurrentStateObject = newStateObject;
        CurrentStateObject.SetActive(true);
    }
}

Every button has now an event trigger attached which calls the SetState(...) method on MenuScript.

However, I've got the following problems:

  • How do I pass multiple parameters to methods invoked by event triggers? As soon as I add another parameter to SetState(...), for example bool isModal = false to tell the method that it shouldn't disable the previous state, then it disappears from the event trigger action selection menu.
  • How to I register key press events within the event triggers? It's easy by script (Input.GetKey(...) etc.), but I didn't find one on the event type selection.
  • How do I make e.g. event triggers conditionally? So that certain events cannot be called when certain states are active?

I know I can solve all those problems within the script and make a fully functional main menu which does exactly what I want, but then...

  • I cannot use it for something else
  • I don't use the new Unity UI system, which makes me wonder what's the purpose of it
  • I got to reference all involed game objects (the canvases, buttons etc.) in script, which makes it not really 'extendable' anymore as this means for every changed requirement, I have to alter the script (e.g. a new state, menu, button etc)

The main goal in the end is a script which I can use for all kinds of UIs without writing the same logic over and over again just because it's another scene, game state etc.

\$\endgroup\$
  • 6
    \$\begingroup\$ This is kind of like a bunch of questions all glommed into one. Ask seperate narrowly-defined questions so we can give definitive answers to each. \$\endgroup\$ – Almo Mar 21 '16 at 22:21
  • \$\begingroup\$ @Almo Kind of. Yes, it's a 'bigger' task where I've got several individual problems with, but in the end I'm trying to implement a simple concept (re-usable main menu script using a state machine and kind-of code/UI components separation) that doesn't make sense when the problems are provided individually. For instance, I already figured out that I can pass e.g. scriptable objects as parameters for event trigger invocations and therefor I can make some sort of property bags (solution for problem 1)... \$\endgroup\$ – artganify Mar 21 '16 at 22:26
  • \$\begingroup\$ @Almo ...but this couples the scene graph to the script event more (general problem) and doesn't make much sense in the case of problem 2 (input) which is related to the 'Escape->go back' requirement. Posting all those things in individual questions would just be 3 times copy-paste the upper 75% of the post with a single sentence as the question. And that would generate just a lot of noise. \$\endgroup\$ – artganify Mar 21 '16 at 22:28
1
\$\begingroup\$

To address your problems:

  • With default UnityEvent you cannot pass multiple parameters in the inspector. There are plugins that allow you to do that.
  • You are probably referring to EventSystem. It has Standalone Input Module which which has settings for input. Event System has First Selected. It's an old input system module, you might want to write your own one to have full control over it. There probably exist ways to extend it, though.
  • That is up to your created system or extended version of event system. I don't believe you can do this by default.

I would recommend to look into Divide-and-conquer. Try to split responsibilities as much as possible as long as it seems reasonable. If you are designing a system for reusable UI - you might want to split things like handling UI objects and how they react to changes of states in your game. Every game in some instances is different and in some it's similar. You can find things that are common for every/multiple games and create a system that uses those features only. And for specific features you would create game's unique behaviours.

Let's break down what each UI has in common or could have in most instances:

  • Appear/Diappear.
  • Animations [Explained in "Most instances"].
  • Data to display. [This one is a bit tricky, there way too many ways to put data in required forms. But you can choose your own approach to displaying data which in your own opinions suits your needs the best, or is the most performant, whatever you choose].
  • Fields to output data to. [Same story with data].
  • Data communication. [How to send data to display/How to get required data to display].

(Most instances - because really what UI system needs to do is display information. I could just be Text, Open, Close. Not every UI needs animations, but well they feel nice :))


For this example, during game jam, I have created a project with reusable Views which is open to anyone on Github. In View I have tried to put everything that would usually be required for UI in Unity. Project has a little Main Menu demo. Like:

  • Openning and closing.
  • Playing animations.
  • Generating animations for specific view.
  • Makes use of Canvas Group which is commonly used in UI.
  • Automatically creates/changes a few things on GameObject when script is applied.

You can modify it to your needs. But from my personal experience for every game that I have been using this system on I haven't removed any features, only added new ones or fixed some things. It does't really cover Data • because it is designed for every view to have it's own type of data communication route [to put it simply - just creation of new method to display data with strong type. Or any other way, if you develop one, but this one is the easiest].

Project is created in LWRP with Unity 2019.2.2f1 - Unity UI View System.


Small example with animations from the project

View System Example Animations


For other game specific things like:

  • Inventory.
  • Dialogues.
  • User information.

Those things are specific which require its own approach to how UI will work for them. But they still have those common things, like Open/Close, etc. If you want those UI's to be reusable as well - then make the systems[Inv, Dialogue...] reusable as well. One should adapt to the other in some way. (If someone knows some magic how to avoid that - I will gladly listen to theirs solution)


P.S. I've put this in another answer because it describes a different approach to solving the problem. I still use MVC alike pattern for personal projects which is a more complicated system to be described or developed.

\$\endgroup\$
1
\$\begingroup\$

General

There are many solutions for this approach. Mainly it would be just learning how to make every system reusable and modular. There are already a lot of solutions/patterns on the internet like: Micro-Services, Module Pattern...

After a bit of time and experience you would understand how to make everything modular and come up with your own solutions that work for your needs the best.


Specific

I am going to describe the main system that I am using for UI.

I have a system that makes use of MVC pattern. I call it OMCVO - Object Model Controller View Output.

In Objects[aka Components] I have data objects [Models] that are sent to a Controller which decides to what view to assign the data. Some views that I have are Singletons, I usually don't use them through Controller.

[Some of the method naming isn't the actual names of methods in system, they are just to show an example].

Basically, I've split the Data, Output and Behaviour. The workflow is very simple:

  • I create some DataType which I load with information and send this data to controller [Controller.Take(data)].
  • Some View that accepts this DataType through Controller does calculations and displays them to Output.
  • Some Data is linked to views for real-time changes.
  • Views have standard API like Open, Close, Show, Hide, animations, relations to one another and other similar stuff.

Technical implementation is very tedious and complicated, so I won't go into it.


Part of old asnwer that some people might find useful

But for this purpose I would have different Views that are Models and Controllers of itself.

For example, Confirmation Window - it has a method public void ApplyConfirmation(UnityAction buttonConfirmationAction, string text); - and when you click on exit button wich has some method applied to if from MenuManager or whatever which has a call to Confirmation Window.

ConfirmationWindow.Instance.ApplyConfirmation(delegate { this.ExitGame(this.SaveDataBeforeExit) }, "Do you really want to exit?");

Now confirmation window pops up. ApplyConfirmation in my case has an "Yes" button which is asigned any method to be called when it's pressed and a text that changes the text on pop up window.

If you are sure that you are going to have only 1 UI element of that behavior and you can reuse it - you can go ahead and make it a Singleton to be able to call it easily from any class like UIElementClassName.Instance.PublicMethodToCall();

Hope you got the idea from it.


For a simpler and better described solution look at my other answer.

\$\endgroup\$
0
\$\begingroup\$

If you want a modular UI you can use and implement different fonts/texts, I'd write my own gui texture's fonts ect and have a OnGui holding all the options you want then simply either create functions regarding each option or have multiple scripts. Regarding 'noise' if it works it works, having a simple:

audioMenu = false;
mainMenu = true;

Can simply revert u back to the main screen.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.