Game engine gui design

Started by
22 comments, last by Juliean 11 years, 8 months ago
Hello,

so I'm currently writing the gui for my 2d game engine, and though everything is working fine for now, I wonder if there is any better way of designing this?

So i think on the basic everyone should agree:

I have a EditorObject-class, and from this class I inherit EditorImage, EditorWindow, EditorButton, EditorScrollbar, EditorToolbar, and more to come.

But, my question is now, how do I put this together the right way?

Right now I'm having a window for the tilesets and tile selection, and one that displays the map and lets me draw tiles on it. I achieved this by inheriting the classes WindowTileset and WindowTilemap from the EditorWindow-class. I just need to overload the constructor and the virtual Draw() and (important) Update()-functions, so there is no doubled code from this. Things are working way more neat than I'd have expected, but..

.. is this the right approach? What would you guys do here? If I keep on doing this I'm going to inherit a hell of a lot from the EditorWindow-class for every window I need (WindowNewProject, WindowDatabase ..), which is working, but again, isn't this a little overuse of OOP? What other concepts are there available, and are they useful in my context?
Advertisement
I'd use an existing library instead of writing my own. Right now you're abusing OOP, I'd recommend you to look into composition. There is a great thread going on about this here.
I agree with Mussi that you should look into 3rd party libraries, as GUI systems can get quite massive, but if you really insist on doing this yourself you're going to have to look into some other programming concepts, because you are in fact abusing OOP quite horribly.

You should look into the Observer pattern and the Model-View-Controller model so you can decouple the actual visual representation of your GUI elements from the data they represent, and so you can create a generic method of accessing and modifying data through a GUI.

Note that this can become a massive amount of work for an entire GUI library, so you'd really be off better with a 3rd party library.

I gets all your texture budgets!

Thanks to both of you,

I indeed insist on writing my own libary, so I'd just like to know more exactly when my OOP-abusion takes place.

Your talking about the derivations like WindowTileset, WindowTilemap, right? Or is my overlaying system bad, too? I've had a quick look at the observer-pattern and it seems like my base components like EditorImage, EditorSrollbar, just meet that criteria. EditorScrollbar e.g. holds three values, a minimum and a maximum, as well as a current one and manipulates that, without knowing what those values actually represent. Is this bad design, too? If yes, what benefits could I get from decoupling this?

Oh, not to forget, this gui right now is just being used for the game editor itself. Thats why I didn't want, though already reading into that other thread, to hijack it, as I thought this is a somewhat different topic. The editor itself is more static, so I quess graphics<->logic decoupling isn't that necessary.. right?

So in case just the "higher-level" derivations like WindowTileset that should be changed, how would I do that? Would I make a struct like this:


struct Tileset
{
EditorWindow* pWindow;
EditorImage* pTilesetImage;
EditorSelectRect* pSelectRect;
Update();
Draw();
}


Would this be sufficient? Or do I really need to go with a component based system, or a true observer-pattern / model-view-controller? What are the benefits of this? I feel like my current approach is really practical to work with, expect the over-use of OOP when inheriting from EditorWindow.
I agree that if you want to learn how to write a GUI system for yourself, apply composition well. Form objects can be usually be broken down into components intuitively. For instance, a clickable menu entry can be an aggregate of a TextEntry and a Button. Meaning, it's just a barebones class that contains objects of those two types. A scrollbar (just the bar itself, not the screen it scrolls) would be a Button and a Draggable entity with vertical/horizontal constraints added to it. Here, I've decoupled the components so that they will do very little (if at all) communication with each other. For a menu entry, the Button may just need to know the dimensions of the TextEntry so it can resize its "hot spot" accordingly. I'm not saying this is the best solution to go with, but it's a great improvement over using a large hierarchy of inherited classes.

I have written a older post in this topic that Mussi was pointing to, but it describes my experience with adding some basic UI elements and how I've gotten around to decompose its functions, so it might be worth a look. I'm writing my own UI because I do not need anything very complex for my game.

New game in progress: Project SeedWorld

My development blog: Electronic Meteor

Definitely take a look at MVC and study it in detail

With MVC there's no need for you to define different classes for different types of windows for example. You would just have your 'EditorWindow' class (which you should maybe generalize to represent any Window, whether it's part of your editor or not) which is only worried about representing itself correctly to the user (hence being a 'View'). The window object absolutely does not care about which data it is representing, that's what the 'Model' behind the window should be taking care of.

This way you avoid unneeded inheritance for every single use case you might have, and allows for a much simpler, cleaner and easier to maintain GUI system.

Composition is also a crucial point of course when designing a GUI library, but the problem of designing your widgets/elements themselves well is still something different than designing the interaction between widgets and the use cases they occur in and the data they're supposed to represent.

If you want to see some examples of the MVC model you should take a look at Qt, which uses this extensively

I gets all your texture budgets!

@CC Ricers:
For instance, a clickable menu entry can be an aggregate of a TextEntry and a Button[/quote]

A scrollbar (just the bar itself, not the screen it scrolls) would be a Button and a Draggable entity with vertical/horizontal constraints added to it[/quote]

Thats just how I was planning to handle things, except that my scrollbar still inherits from EditorObject, because, after all, isn't this a "base object" too? This drastically reduces the amount of code needed as well. Maybe I should show you what EditorObject looks like:

#pragma once
#include <Windows.h>
#include <vector>
#include "Input.h"
#include "Sprite.h"
using namespace std;
class EditorObject
{
public:
EditorObject(void);
virtual ~EditorObject(void);
void Setup(int iID, int iX, int iY, float fZ, int iWidth, int iHeight, Sprite* pSprite);
virtual bool Over(Vector2 vMousePosition); //mouse is pointing at this object?
virtual void Draw(float fZ);
virtual EditorObject* Check(Vector2 vMousePos); //return object at which the mouse is pointing
virtual bool Update(void);
virtual void UpdateSuper(int iOffX = 0, int iOffY = 0);
void SetX(int iX);
void SetY(int iY);
void SetSuperX(int iX);
void SetSuperY(int iY);
int GetID(void);
int GetX(void);
int GetY(void);
int GetWidth(void);
int GetHeight(void);
Sprite* GetSprite(void);
protected:
bool Activate(bool bActive, int iStateID = 0);
bool IsActive(int iStateID = -1, int iStateException = -1);
bool m_bActive;
int m_iActiveState;
int m_iX, m_iY, m_iOffsetX, m_iOffsetY, m_iSuperX, m_iSuperY;
int m_iWidth, m_iHeight;
float m_fZ;
Sprite* m_pSprite;
int m_iID;

Vector2 ProjectPosition(Vector2 vMousePos, bool bCap=true); //project a given point to object space
};


So EditorObject describes a general object with variables that apply to every gui element. (position,size, a sprite object). The whole Super-thingy is for traversion, it spares me having to pass the holding objects x/y-coordinates all the time. ProjectPosition() is a helper function that outputs the mouse coordinates relative to the objects position, saves me a lot of math.

So I quess everyone would agree that I should at least inherit these classes from that:

EditorImage
EditorButton

If not, why?

If yes, why shouldn't I, example given, also inherit the Scrollbar from this?

The scrollbar, while being basically a composition of three buttons and a background-image, does have its own coordinates, destinctive size, needs to be drawn in relation to other objects, etc.. so whats exactly wrong about that or, better said: What are the benefits of composition here? What is such a huge improvement from having an extern controller handling the scrollbars usage to just having the Update() method do it? This is what, for example, the inherited scrollbar-class looks like:

#pragma once
#include "EditorButton.h"
#include "Timer.h"
class EditorScrollBar :
public EditorObject
{
public:
EditorScrollBar(void);
virtual ~EditorScrollBar(void);
void Setup(Sprite* pSprite, Texture* pFrame, int iID, int iX, int iY, float fZ, int iLength, bool bVertical);
void SetButtons(EditorButton* pUpperButton, EditorButton* pLowerButton, EditorButton* pMidButton);
void SetValues(int iMinVal, int iMaxVal);
void SetNowValue(int iNewValue);
int GetNowValue(void);
void Draw(float fZ);
bool Update(void);
void UpdateSuper(int iOffX = 0, int iOffY = 0);
private:
int m_iMinVal,m_iMaxVal,m_iNowVal;
Vector2 m_vTemp;
Timer m_cTimer;
EditorButton* m_pUpperButton, *m_pLowerButton, *m_pMidButton;
};


Except that Setup() and SetButtons() could/should be moved to the constructor: Again, why would I want to composite instead of inherit here? The scrollbar class uses everything from its super-class, and just extents it. Do I really have such a misunderstanding of OOP-basics or is it some matter of opinion whether you see a scrollbar a base object here?

So I know that my EditorWindow->WindowTileset/WindowTilemap-classes are bad and I'll going to replace that with some composited objects here, but I really see to fail the point for such a basic objects as a scrollbar.

PS: Sry for bad spacing, code-tags killed it..

@Radikalizm:

Ok, that makes more sense to me for the window class, I'll aplly that to replace my WindowTileset etc.. inherited classes. Still, I don't see why I would want to do this to e.g. the scrollbar, or a button. Well maybe things will get more clear if I implement it for those said classes first.
Any GUI element/widget should inherit from a common Widget base class (EditorObject in your case), you're right about that since every widget will hold a set of similar data (position, id, rotation, etc.). So yes, the scrollbar should be inherited from your EditorObject class.



What I don't see here in your implementation is a widget hierarchy. The lack of this hierarchy is causing some of the problems you're having here I believe, and it makes you resort to some quite hacky and non OO implementations. Those "super" functions are some very obvious warning signals here -to be honest with you I have no idea what these are supposed to do, but from your description I can pretty much assume they are a hack-, just like that "check" function you have in your EditorObject class.

With a hierarchy you define a root widget which has all the widgets in your editor as children. Each of these widgets can have children of themselves as well, and the position of these children now becomes relative to their parents. The absolute position of a widget now becomes: relative position to parent + absolute position of parent.

If you look at the scrollbar example now you'll have the Scrollbar class which inherits the EditorObject class, and which is composed of 3 buttons which are all children of the scrollbar.

Sticking all of your behavioral code inside an update function of a widget instead of using MVC for example will force you to create a new class which inherits your scrollbar for each use case you might ever have, which will result in a huge amount of redundant code. I already explained this in my previous post.
Also, say you were to create a new class for every use case you ever encounter, and later on you need to make some changes to your base class declaration (your scrollbar for example). You'll now have to go back to every use case class of your scrollbar and update their declarations as well, which is just a huge time-waster.

Maybe it wouldn't be a bad idea to review your OO basics, since you're making some other violations against proper object-oriented design:

- Violation of the Single Responsibility Principle (SRP). Stick with SRP at all costs and make sure one class always is responsible for doing one and only one task. Returning the widget which the mouse is pointing to is not the responsibility of a widget!
- Improper constructor usage. 2-step initialization like you're applying here can be a dangerous practice as your class invariant is violated until you call the setup function in your EditorObject class. You only need to forget to call the setup class once after creating an EditorObject and you get sent straight to undefined-behaviour-land, which is not the happiest of places.

I gets all your texture budgets!

What I don't see here in your implementation is a widget hierarchy. The lack of this hierarchy is causing some of the problems you're having here I believe, and it makes you resort to some quite hacky and non OO implementations. Those "super" functions are some very obvious warning signals here -to be honest with you I have no idea what these are supposed to do, but from your description I can pretty much assume they are a hack-, just like that "check" function you have in your EditorObject class.[/quote]

Well those "super"-functions are basically my scene hierachy, Take, as example the scrollbar:

The scrollbar, upon drawing, will call SetSuperX() and SetSuperY() for its buttons. On drawing, these variables are taking into account for the position where the button is drawn to. Same applies to the scrollbar itself, probably hold by a window, which is hold by the main frame. I actually adapted this from a project using an rpg-maker, that obviously lacked much of c++ functionality. Still, it is working, and except for having to overload the UpdateSuper()-method for every base object an making sure it is called for the parent-widget, its easy to use. However I'd still be glad to know about any better variante for handling a scene hierachie..?

With a hierarchy you define a root widget which has all the widgets in your editor as children. Each of these widgets can have children of themselves as well, and the position of these children now becomes relative to their parents. The absolute position of a widget now becomes: relative position to parent + absolute position of parent.[/quote]

Like i said, "super" basically does exactely that, but if there is an easier way, I'd like to know..

Sticking all of your behavioral code inside an update function of a widget instead of using MVC for example will force you to create a new class which inherits your scrollbar for each use case you might ever have, which will result in a huge amount of redundant code. I already explained this in my previous post.[/quote]

Thats a point I cannot follow. Ok, I've read up about normal widget/gui design and since most, if not all of them seem to use some sort of event - slot/signal - system, I've made a fundamental flaw right there. But in my application, I can use the scrollbar just as it is without rewriting it for each use case. From what I've experienced every toolbar is the same: You can click on the outer two buttons to scroll the bar up and down, or drag the middle button, or click anywhere else on the bar. And thats what my update-method does. It also sets the m_NowValue-variable between m_MinValue and m_MaxValue. Later on, I can access these values and do whatever it takes, whether its a bad oop-derived window-class, or a composited class using MVC. So while it is not the best oop-use, it still doesn't produce any redundand code or makes it hard to use, does it?

- Violation of the Single Responsibility Principle (SRP). Stick with SRP at all costs and make sure one class always is responsible for doing one and only one task. Returning the widget which the mouse is pointing to is not the responsibility of a widget![/quote]

How else would I handle this? It seemed logical to me that a widget should have the ability to return the widget it was pointing to, only including itself and its childern..

- Improper constructor usage. 2-step initialization like you're applying here can be a dangerous practice as your class invariant is violated until you call the setup function in your EditorObject class. You only need to forget to call the setup class once after creating an EditorObject and you get sent straight to undefined-behaviour-land, which is not the happiest of places.

Ok, this is totally clear to me, its a bad habbit of mine. It does work well too because I'm using a factory class right now which stuffs all that together, but I'll remove it anyway.
If you were to add an addChild() method to your EditorObject class which accepts another EditorObject, and if you were to maintain an actual list of children inside an EditorObject you could easily update your entire gui with only a single update call on the root widget. Within each update call in a widget you would traverse the list of children and do an update call on each of them individually, which gives you a nice recursive way of updating the gui.

Managing your gui in such a tree structure also makes your gui much easier to traverse from a programmer's point of view, as having access to a parent widget means having access to the entire subtree which that parent is root of.
I remain with my statement that using those super-methods is pretty much a hack.


Maybe I'm misinterpreting the way you use your update function in your gui elements, if that's the case just let me know. Right now, since I don't see any other way how you're updating your actual logic to manipulate your back-end data (eg. how does your scrollable window know that your scrollbar has been updated?), I'm assuming you're doing this in your update function.

The scrollbar example might not be the best example to explain the MVC principle, sorry for that. Let me try to explain it with a different example:
Let's take the simple example of a button. A button is both a visual representation of some dataset, and can manipulate a controller. Let's say you have a button on your GUI, and when you click this button a value gets added to an array you defined somewhere in your code for example. This array is of a fixed size however, and when this array reaches its limit the button should not be clickable anymore.

We separate this situation into a model, a view and a controller. The model is the array, which in our case can have 2 states: "Able to accept values" and "Not able to accept values". The view is our button, and depending on which state the model is in it can change state between "Enabled/Clickable" and "Disabled/Not-clickable". The controller is assigned to our button and it can manipulate our model (the array) by telling it that it should add a value to itself.

Now let's look at what happens when our button is clicked:
-The system managing your GUI gets a mouse-click event and checks whether the mouse position at the time of the click event was over a GUI element, and if so it sends an onClick message to said element. In our case we clicked the button, so our button will receive this message.
- Since the button received the onClick message it has to notify all its associated controllers, among them the controller for our array. The controller for the array sends a message to the model that it should be manipulated, and the model adds a value to the array.
- A value is added to the array, and the state of the array is evaluated. If a state change is triggered (ie. the array has reached its limit) it sends a message to all its views informing them of this state change.
- The button receives this state change message from the model and acts out its role as view. The button sets its state to disabled since no more values can be added to our array.


If you wanted to do all of this purely with inheritance you'd have to create a button class which would handle this entire process in its update system. Also, if you wanted your button to be able to control and represent multiple sets of data at once you'd have to create a separate case for that as well, whereas MVC allows a single controller to manipulate and a single view to represent/observe multiple models.
The update function for you GUI elements should only be responsible for making sure it is correctly represented in your GUI at all times, and should refrain from doing any actual logic manipulating your back-end data.



--- End example ---

On to your other questions:

Returning the gui element your mouse is pointing to should be the responsibility of the system managing your GUI. It is perfectly fine for a gui element to have a function which determines whether a point is inside its area (and that point could be the mouse position), but it's up to the system maintaining the GUI to determine which element(s) has/have been clicked, and to return that element to the system which requires it.

I gets all your texture budgets!

This topic is closed to new replies.

Advertisement