Following not-so-hot on the heels of my not-so-hot request for UI design feedback (thanks to none of you who responded!), I wanted to post what I’ve gotten so far and talk a little about the standards that I’m going to try to apply to the UI work going forward.
As stated in the previous post, there’s not so much a “right way” to handle the organization of code and objects, but there are most certainly “wrong ways”. The quickest way to identify when you’ve turned down the wrong alley is to try and get your UI elements to work together, to access each other’s information, and to take actions that the UI is meant to enact. That’s why the hierarchy of UI elements is important.
This fine diagram to my left is what I came up with for my main menu hierarchy. I need to preface this by talking a bit about the data controller though. The data controller is an empty game object — which means you can’t see it in the game — that has one component: Database. Database is a C# script that handles tracking the state data (stuff that changes when you play, like your character and your progress) and main data (stuff that is always the same, like item stats). The empty game object that holds it is set to not be recycled when we switch scenes, so it will persist throughout the game. In addition, the code is set as a singleton, which means it is self-policing so that it ensures that there’s only ever one copy of the data. The data controller isn’t represented in this screencap, but know that it sits above and outside of the Main Menu Controller.
The Main Menu Controller is another empty game object. It is an umbrella beneath which we will find all of the UI elements that make up the main menu, the loading and saving screen, options, and confirmation dialogs like overwrite confirm and quit confirm. The benefit of this is that the empty game object can hold scripts that we can refer to from anywhere in the hierarchy, but more importantly, it allows us to make a prefab of the menu. A prefab is a “master copy” of an object that we can deploy anywhere in the project. When we make a change to the master object, it cascades to all deployed instances. If we make a change to deployed instance, we can either let that change stand just for that instance or have the instance update the master object. I’ve already created a prefab of the main menu hierarchy, which is why the text is in blue.
So what about the organization itself? There are five main levels: MainMenu, GameList, Options, QuitConfirm, and ConfirmOverwrite (yes, OCD folks, I will change the Quit and Overwrite to match naming patterns).
Main Menu is what you’d expect: the main menu. This is what you’d get when you start the game, or when you hit the ESC key while playing the game. It allows you to start a new game, load an existing game, set options, or quit.
This is where the difficulty comes in. Both NEW and LOAD share the same concept: choose a slot from three options. If it’s filled with details, we need to know what option we chose from the main menu: are we starting a new game? If so, we need to make sure the user knows she is going to overwrite an existing file. Are we loading an existing game? Then we need to get the save game data into Database from disk.
And here’s our load/new UI. The first slot is filled in with an existing game. We simply call it “Save game 1” and display the last save date. The other two slots are empty. If the user is starting a new game, she can choose the last two slots and just start the game. If she chooses “Save game 1” then we need to warn her.
If she chooses YES, then we’ll set up a new game and overwrite the slot that’s currently occupied.
What handles all of this? There are two scripts: Main Menu and Game List.
Main menu is attached to the MainMenuController game object because this handles the opening and closing of panels other than the main menu itself. One thing that UI systems need is a way to handle which windows are open and which are not. One way to do this in Unity is through GameObject.SetActive([boolean]). When boolean is false, the window is inactive. Unfortunately, when a game object is inactive, it is unaddressable, meaning we can’t tell it to show itself because it’s just not listening. To get around this, we put a script at a higher level than the object, create a property, and drag that object into the property. Now, when we want to show the windows, we do so from above the window itself…at the controller level. Currently, the main menu itself isn’t represented, which will get fixed later.
What we do not see are methods called event handlers. These are code bits which the buttons use. When someone clicks NEW GAME, the event handler “hears” the activity and takes an action. We point the button’s click event to that method in order to join the two. Here, we have three event handlers: One to display the game list panel, one to display the options panel, and one to display the quit confirmation panel.
The real tricky part was the game list panel.
In this case, we have the script attached to the UI element itself. Why not attach this to the Main Menu Controller? Well, for one reason: we need to take an action when the UI is activated — when the button on the main menu is clicked. If we put this on the Main Menu Controller then as soon as that object is created (at scene load) then those actions would fire. Since the game list panel isn’t active at that point, running that code is pointless.
The activation action runs the code within Database that loads the list of saved games. We use this to update the text of each button on the panel. If there’s a save game in slot one, then we need to change the text of slot one from EMPTY SLOT to the name and last saved date. We need to have references to these buttons inside the script in order to do this, so we create properties to hold those references. We could technically use hierarchy and search methods to find SaveGame1,2,3 buttons, but searching like that is expensive and doesn’t make the assignment visible through the inspector during design time. We also have a reference for Confirm Overwrite Panel because its visibility is controlled based on the state of the button the user presses, and the action she intends to enact (new or load).
If the user is starting a new game, then Database handles the initialization by loading the master data, creating a new Game State Object, merging data where necessary, and then making all of that available through the Database instance. If the user is loading a game, we load the master data and read the appropriate state file from disk. In the case of a new game, we will then send the user to another scene where she can make some limited customization of her character (since the character has no visible presence, it’s mostly just custom name, gender, and paper doll selection). Then, she’d have to decide whether or not to tackle the tutorial. If she’s loading a game, then we would send her to the scene where she left off when the game was saved.
So that’s it! 1300 words to describe a menu system (be thankful I didn’t include all the code!). Needless to say even something as “simple” as a menu can be more complicated than you know, so remember that the next time you’re ripping into a game about animations or physics — there’s a lot of moving parts, and what you see is only the end result of mountains of effort whose structures are really only as good as the organization of its parts.
Read More »
Secret World Legends
Last night a few of us folks from the Combat Wombat Discord and Off-Brand Outlet actually managed to carve out some time to meet up in Secret World Legends to run the first dungeon, “Polaris”. This was a relatively easy mission as we were all above or on the upper end of the suggested level range for the event. I hadn’t done this dungeon in many, many years and it was fun to go back to, although I’m hoping that with the renewed interest in SWL that I can actually see more dungeons in this game this time around.
Master X Master
This is a weird game. A few folks I know had expressed interest, so I jumped in thinking it would be the next Skyforge or Trove: A game I liked just fine, but which would never be my “go to” game.
Thing is, MXM is really a weird game. At first glance, it looks like a MOBA because you have a whole wall of characters that you can unlock and play as. There is a MOBA mode which plays like a traditional MOBA. But that’s not all it does. MXM has a single player element to it; you can run random zones which all end in a boss fight. You can participate in multiplayer arenas. And there’s an active social zone in between.
The game gets its name from your roster, who are referred to as “Masters” (of combat and such), but the duplicate use of the word informs you that every time you enter into a scenario, you get to take two characters with you. At any time (after a short cooldown), you can switch between them at will. Needless to say that if you thought getting good with a single character in a MOBA took practice, you’re in for double the fun.
I’ve been jumping in almost every night to deal with the daily missions for rewards used in upgrading my roster, and to gain the currency used to unlock additional characters. So far I have at least three — the two you get by default (whom I have been using almost exclusively at this point because I like them) and one character I got for free during the tutorial, whom I do not like. Interestingly, because it’s an NCSoft game, there’s crossover from other NCS games like Blade and Soul and Guild Wars 2, a la that “other” MOBA people get all breathless about. And yes, this is the game that pissed some people off by including characters from the ill-treated City of Heroes.
Ready Player One
You can stop here if you don’t want to see me getting ranty, but otherwise…carry on.
I picked up Ready Player One because no, I hadn’t read it up to this point. And going forward, I can solemnly say I will not be reading it because, for me, this book is a terrible experience.
It reads like someone got ahold of the Wikipedia entry for the entire decade between 1980 and 1989 and decided to make a novel out of every single pop-culture bullet point mentioned therein, embedding references as if the novel would turn into a speeding bus that exploded if everything that happened or was produced during those years wasn’t mentioned at least once. Reading this book felt like being at a party and having to listen to that one douche who injects himself into every conversation and does nothing but name-drop every time he opens his mouth so people know how much and who he knows. I call this condition “Crichtonitis” after the late Michael Crichton, an author who creates characters strictly to serve as a mouthpiece for the author’s enthusiastic relay of what he learned while researching his subject. Hell, I’d rather read a textbook.
Whew! OK, that was the sarcastic criticism portion of this entry, so let’s calm down a bit. Earnest Cline is a good writer. I liked his prose; it was easy to read and flows naturally. At this point, the subject is probably kind of dated. It’s somewhere between Wargames (which is mentioned in the book several times, if you see what I’ve done there) and Sword Art Online. Maybe a poor-man’s Snow Crash that aspired to be Neuromancer with a dose of Internet age attitude. That’s not a knock, because I understand that ideas are eternal, and there’s always going to be overlap and levels of engagement in any product.
In a surprise twist, I am kind of interested in the movie treatment. A lot of the grief I am slinging here is due to the fact that the protagonist has to tell us what’s going on, why, and how he’s dealing with it because it’s actually kind of bizarre: a billionaire recluse dies and leaves his fortune and company to anyone who can solve his 1980’s themed adventure mystery, all/most of which happens inside the virtual reality world that he had created. I mean, under other circumstances I could totally get behind something like this, but the wall-to-wall 80s references are just way overkill and really, really put a hard stop to the book for me. I suspect that because of what a movie has to keep and what it has to cut that the live production can let a lot of those overt references fade into the background, which is kind of true to form. Having lived through the 80s, I recognized the majority of references made in RPO, but they were never as in-your-face as they are when someone is trying to make you see them. In reality, they weren’t artifacts or stand alone references we pointed at and identified by name; they just were.
Read More »
One of the cornerstones of the development world is the idea of “best practice”. Since coding is as much about style and flexibility as it is about solving concrete problems and modeling desired outcomes there’s really no one single way to approach a solution…nor should there be! What we have instead is a kind of trend towards certain efficiencies such as least amount of code to get the job done, making it reusable, making sure it doesn’t trip up or over other code, etc. Best practice can be abused when people insist that their way is the only way because anything less is just going to cause your app to sputter and die a wheezing death. But best practices are really the distillation of a whole lot of people’s trial and error, hopefully with a dash of advice from those who created the language.
Finding the “best practice” is often difficult for a few reasons, first and foremost being that as a rule, there is no best practice. Everyone swears by what works best for them. Some people like cars, some like motorcycles, but both will get you to where you need to be so who really cares? The second is that often times developers can be real cagey with their knowledge. There’s some undercurrent that believes divulging experience in development is like a magician telling people how the trick is done, not because it ruins the illusion, but because it suddenly makes the original magician a lot less valuable if everyone can do the same trick.
That’s why I’m throwing this out there in the hopes that someone can at least share some insight into their own experience since it seems like more of what we call a pattern than a practice. A pattern is more of a mindset than an actual flow. It’s how things are arranged so you can get yourself into a practice. In this case, I’m looking for advice on structuring and controlling a user interface in Unity.
And before I get to the question at hand, just a bit of a preamble. The way I’ve been working with Unity UI, I see two methods of wiring up the interaction. The first is to have the UI itself handle interaction with the code that puts and pulls data to and from the game’s data store. The second is to centralize the code that interfaces with the data store, and then have another script on the UI that deals with the UI itself. Then, these scripts communicate among themselves to pass data along.
Option 1: Direct Script Interface
The first option is probably the most direct and easiest to conceptualize. The Unity UI system allows us to draw a box, say, which contains fields for text input (in our example). In order to get this data into the game data store so we can apply it elsewhere, we need to recognize that the textboxes exist.
After we draw our UI, we attach a script to it. This script has properties that are intended to hold references to the UI elements like the textbox (and the button used to submit the data to the data store). When the script receives the command to read the data from the form, it refers to those properties so it can read the contents. It would then take the data from the textboxes and “do something” with it — send it to another part of the game, or put it into the database or something.
The benefit to this is that the UI is completely sorta self-contained. We could copy this and paste it elsewhere and it should work exactly the same, which is one of Unity’s greatest strengths. It’s also easier to manage because we just need to go to the source — the UI elements — and we have access to the visuals and the code.
The downside to this is that we end up with a whole lot of repeated code. Anytime we want similar but slightly different behavior we would need to modify the underlying code and then add a reference to other UI. It also means that we can’t easily take advantage of global patterns such as singletons which allow for persistence of data without having to go “back to the well” of a back-end system. Finally, if we had one script to handle several UI elements, we’d have a whole lot of properties tied to UI elements to deal with, which might make management a nightmare.
Option 2: MVC-esque Interface
This option is more complex, so please bear with me as I try and explain the diagram.
First, MVC is a pattern called “model-view-controller” which puts the display in a “view”, the logic in a “controller” and uses a “model” to represent the data passed between the two. Here, the UI has a controller. This is a game object which centralizes a whole lot of behavior for the express purpose of bundling it together for easy access. Here, a UI controller would be the base of either this or all scene UI elements. We’d have scripts for managing windows in the scene, but also for handling the UI elements (textboxes, buttons, etc).
The difference here is that this script would need to communicate with another script on a more global scene controller. The scene controller is like the MCP from the original TRON: it’s got access to everything you might need, just hanging out in space, easily accessed from whatever and where ever you need it.
The benefit of this method is that the scene controller is all seeing and all knowing. It works for the UI, but it’s also available for other elements of the game. In addition, it can implement the singleton pattern which when combined with telling Unity not to destroy the object on scene transition, offers us a persistent data conduit.
The downside? It’s messy. And complex. Technically, this diagram can be warped a bit. The UI doesn’t need its own controller, for example, and the interface script can live on the top-level element (“canvas”) of each UI we need to communicate with the scene controller. We could also eschew the scene controller itself and make the UI controller the top level way of data store interaction, although we’d lose our data persistence unless we did have some singleton somewhere that wasn’t destroyed when we move between scenes.
So there we have my conundrum. As a hobbyist developer in this realm, I’m not sure what the best option is. I had been using the more complex method previously, but now that I see things in writing I’m thinking of trying the less complex method, although I’m not sure what pitfalls are unaccounted for along that trail.
Are there other methods? This is where I’d love to hear from other developers who have worked with Unity UI and who are willing to offer advice!
Read More »