Monday, November 26, 2012

On GUI

GUI in Unity can be a pain. GUI anywhere can be pain, but Unity's system for GUI is especially annoying, not to mention grossly inefficient.

A Note on Unity GUI Inefficiency and Why to Use OnGUI as Little as Possible
The OnGUI function is called via reflection for every event that fires. Because of this, you really do not want to put any logic other than the GUI Drawing logic in the GUI functions. Also, if you are only drawing textures and data to the screen, a lot of the calls are a huge waste. All of Unity's GUI is reliant on Events, namely Event.current. If you you are not looking for something with keyboard or mouse (textfields and buttons, for example) then a good deal of the time the GUI functions you call are doing nothing, since the actual drawing, which is all that is used for textures, boxes, labels, etc, only happen when the Event is a Repaint event. Unity's GUI functions wont draw if the event is not a repaint event, but if you are drawing alot of things, then it's a lot of time being spent on nothing. Again, if you are using GUI that reacts to input, like textfields, buttons, scrollbars, etc, then you need other events, since the state of those controls are dependent on said events. As mentioned above OnGUI is called through reflection, so use it as few times as possible(reflection overhead plus being called for every event that goes through can take a considerable toll.)

GUI and MVC
There are many ways to approach GUI in games, and there are different scenarios where different methodologies work (such as in-game vs in-menu).
Personally, I like to approach GUI in the style of Model-View-Controller.
There are many ways to implement this architecture, but I'll try to simplify the normal use in game terms.


Basically, the view is your GUI, which displays a representation of data(the model) in you game. It cares nothing about your game, the state of you game, it just listens to the model and controller, and reacts accordingly. So if we are displaying health, the model tells the view that damage has been dealt, the view gets the new health and start tweening towards the new value.

This data is your model, it doesn't really interact with anything but itself, though it can fire off events. It can have a behavior to itself, for instance, health might recharge if no change has happened, or it could start recharging once the controller has told it to(depending on how you've set it up). The model doesn't care about anything but itself. it doesn't really care about your game, or how it's displayed. It usually doesn't directly manipulate the controller or the view, rather it notifies both that certain things have happened. So if you a player take damage, the model (containing the health) notifies the view, which pulls the new data to display. If the player has no health left, the model notifies the controller, which kills the player. These notifications are usually implemented in either Observers or other types of event systems. Personally, I have found C Sharp's built in event system to be well suited for the task.

The controller is manipulating your model, and is where the input from this system comes from. Note that if you are using the view for input (text fields, buttons) the controller could be listening to your view as well. Going back to our previous example, the controller would see that the player was hit, gets the damage dealt, and calls a method on the model to apply the damage. The controller may also get data from the model(for instance checking energy to see if an attack can be used.)



Again there are many variations of MVC, and some follow rules that others do not, but the important part is separating control from data/behavior from display.

In Unity, you might have 3 behaviors in Unity, to get the example above.
PlayerController - Controller
PlayerStatistics  - Model
PlayerStatisticsDisplay - View

By separating these so explicitly, changes can be made to each without drastically affecting the others. So the way the controller manipulates the model can change without affecting the view at all, and the view can be drastically changed, without having to rework any of the data.


Applying this to Crisis of the Superverse

In the Crisis of the Superverse healthbar, their are three "views", the conflict compass, the tug of war, and the resolve meters at either end. These view classes are called ConflictDisplay, TugOfWarDisplay, and ResolveDisplay, and hook into the model classes named Conflict, TugOfWar, and Resolve, respectively.
TugOfWar contains the other models within itself, and when damage is applied to the TugOfWar, it sends that damage to Conflict, if Conflicts health( depicted by the rotating compass) is on either end, it returns the remainder of the damage to TugOfWar, which moves the bar accordingly. Once the bar is on one side, it applies any remainder of damage to the resolve. From the RoundController, the controller, its as simple as calling a method to apply the damage, the model handles the rest. The views tween their displayed value towards that of the model.
The energy bars, connect to the PlayerStatistics, which are controlled by the PrimaryFighterController, in similar fashion.



No comments:

Post a Comment