The Forms object contains all menus and controls that will be displayed in the application. All rendering is accomplished from calls made by this object. It duplicates the Windows interface to a limited extent. How the data-driven aspect affects the execution is most prominent in this component.
A good Forms object will possess the following attributes:
A single form consists of a rectangular boundary with many controls within their own rectangular boundaries. The Forms object should be able to hold any number of these forms, whatever is needed to make your game work.
How these rectangles are rendered depends on how you define their rendering attributes. Are they colored boxes? Tiled bitmaps? Animation loops? How should a button be displayed if it is pressed down? All controls will need their own attributes that define these possibilities. You can make it as complicated or as simple as you want. Just bear in mind that it is a lot of work up front, but it is worth the effort from the fliexibility it offers when you actually start designing your game forms.
All rendering, of course, will be handled by the Graphics object, which will be called once for every control. The scene control is an exception, which will be discussed later.
Please note that there are many ways to express a rectangle. Your first draft may contain absolute values, but consider the fact that the resolution may change during execution, so the actual screen coordinates of the rectangle will need to be modified if you need to resize the control. Some controls are clamped to an edge of the screen, centered, take up a percentage of the screen, etc. For my own engine, the design rectangle is a different piece of data from the rendering rectangle. The design rectangle never changes, but the rendering rectangle is determined by a combination of the screen resolution and the design rectangle. These values are recalculated every time the resolution changes, with a single call to the Forms object.
Any images that the controls require should refer to an entry inside the Resources component. Pointer references to those images would be set in the controls during the initialization of the Forms component, as the form designs are created or loaded in memory.
It might also be a good idea to make the controls out of a base class, but set up some derived classes as well for the various control types, with functions specific to each control type. Don't put any new data members in these dervied classes, because they should only access the base class data. These classes should be used to reference the controls directly while the game is running. That way, the programmer will use the functions to set attributes on the controls instead of referring to the attributes directly. This would avoid unexpected results and makes it clearer to the programmer exactly what is possible with your controls.
Support Multiple Open Forms of the Same Type
Once the forms are designed, a single form can be opened more than once if needed. Your code will drive whether or not a form can be opened more than once, of course. One example I found of a form that is opened multiple times is the bag contents within the online game EverQuest. Being able to open more than one bag at a time makes it easier to move inventory around.
In order to open multiple forms, you will likely need a linked list that
holds all the open forms. These don't refer directly to the designed forms,
they are instead duplicates of them. This way, if a control needs to be resized
for any reason, it only affects the open form, not the designed one.
Avoid depending on the system mouse for mouse values. You are encouraged to hide the system mouse, and every tick, keep it centered in the system screen so that it doesn't accidentally strike the task bar and make it visible in your game screen. Some systems also have the Creative LiveWare menu at the top of the desktop, which will make itself visible if the system mouse strikes it.
You can detect the offsets that the system mouse moved in the last frame, however, and use these values to update the position of your own software mouse that you will render yourself. This allows more control over the movement bounds and behavior of your mouse as well.
The only downside is that if you are running the game in windowed mode, you have no system mouse to access anything else until you minimize the application. This is a minor issue, however.
You should be able to change how the mouse is displayed, whether or not it is visible, etc.
Those of you familiar with Windows style programming are aware of the message queue that is generated as the mouse moves, controls are clicked, etc. The Forms object should have its own system as well. It is basically a linked list of events that occur on controls. This linked list is then submitted to an event parse that will absorb all the events that take place in that list, kicking off game events as necessary.
The kinds of events that can occur are: "On this form, this button was pressed and released", "On this form, this list's selection just changed", "On this form, the mouse just clicked on this control", "On this form, the mouse just moved inside this control". A whole list of these are assembled every tick depending on the mouse and keyboard's behavior.
The event system is comprised of the linked list and two routines. Both routines will be called on each tick.
One routine, the same for every engine, will figure out the actual events that took place on controls and automatically set flags on controls to set their states. States would include: Is this button up or down right now? Is this checkbox filled in? Which list item is highlighted now? These flags affect rendering and provide a way for the program to ask what state these controls have. To determine the events that take place, you need to keep track of the previous state of the mouse. For example, if on the previous tick, the left button was up, and on this tick it is down, then there is a left mouse button down event. If the mouse is currently over a control, this would translate to a "On this form, this control was clicked" message.
The second routine, specific to every engine (and hence a virtual function that will be overridden with your custom Forms object), will look at the events that took place on controls and operate on them accordingly. Some controls may cause another form to open or close, others may throw off a game event, kick a player out of the game, etc. This routine will be a key part in how your game behaves based on form controls. The rest of the game behaves based on controls that are not absorbed by the form system (keys pressed to walk forward, mouse buttons used to fire weapons, etc.). Operational modes that you define will determine what controls are absorbed and where they cause effects.
You will need buttons, lists, edit boxes, progress bars, check boxes, radio buttons, slider bars, scroll boxes, static images, etc. Getting them to render and behave is a LOT of work. But at least you only have to do it ONCE!
All controls need rendering attributes and state attributes. The state attributes drive how the controls are rendered. For instance, a button has an up image and a down image. When rendering takes place, the state of the button (up or down) determines which image is used to render the button.
A default rendering routine for controls should be called to take care of all these basic types.
To give you an idea of how much flexibility this offers, consider the following:
A control can have a foreground image and a background image. The images themselves could be animated loops (each frame of which is an image), solid bitmaps, transparent bitmaps, colored boxes (solid or translucent), or nothing at all (meaning that nothing is rendered). You can also set up images for when the mouse moves over a control, when a control is activated, etc. The possibilities are limited to only what you decide to design them for.
Should you wish to tackle an even tougher challenge, you could make forms fully 3D, with the basic control set represented as models instead of images! This alters the strategy for representing them only as rectangular regions. I haven't attempted this myself, but it will definitely be a consideration for a later version of my engine.
The scene is a special control that exists outside the set of other controls I've just described. Each should have its own custom render routine that will refer to the gamestate in whatever way is necessary to render the scene that should go in that rectangle. This is the essence of the gameview display. In my own designs, I contain this custom routine within a RenderScene class as a virtual function, and the control refers to that class through the use of a pointer.
Up until the time you are rendering scene controls, you are taking up the full screen of your game to render the forms and base controls. But the scene you render must be contained within the rectangle for the scene's control. For all you 3D programming gurus out there, that means you have to set up the rendering rectangle such that it stays within the bounds of the scene control's rectangle. When you are done rendering the scene, you can go back to expanding the rendering rectangle to full screen again to continue rendering the other form controls.
Once the rendering rectangle is established, your customized rendering routine should set up the perspective attributes, or frustum, for the scene. Activate the depth buffer and anything else that is necessary.
Now you can access the gamestate to retrieve all the objects you need to render, in the proper order. This is where any expertise you have in rendering 3D graphics takes over. Took a long time to get here, didn't it? I thought so too.
This one is optional. There may be times you need to create a dynamic control. The biggest challenge is accounting for its use in the event parse routine.
If you know all the possibilities of the controls that might ever be needed, it may be easiest to just design them into the form and make them invisible when the program first starts. Then you can make them visible, move them around, resize them, whatever is necessary.
If you wish to tackle the challenge of true dynamic control creation, more power to you!