Implementing user interfaces

Every game will need some kind of user interface, even if it is just a button that can be pressed to start a new game. In this section we will take a look at how a user interface can be implemented for your own game.

The IwUI API

The Marmalade SDK ships with an API called IwUI, which allows us to create user interfaces for our projects consisting of buttons, labels, and other common controls.

This API is very feature-rich and allows interfaces to be created not just for games, but also for more serious applications. Marmalade used to ship with a tool called the Marmalade Studio UI Builder, but this is sadly no longer a supported part of the SDK. However, it is still possible to access this tool by either installing an older version of Marmalade (one of the v5.2.x releases is probably best) or by downloading its source code from https://github.com/marmalade/UI-Builder.

It is also possible to use IwUI without using the UI creation tool by constructing ITX files that describe our interface layouts, by hand. These layout files can end up being quite verbose and therefore hard to maintain, so the Marmalade Studio UI Builder made editing layouts a bit more manageable.

The Marmalade documentation states that the reason for dropping the UI Builder from the SDK was to allow a standardized UI markup system to be used that is supported by a number of other third party tools. At the time of writing this book, no further announcement had been made regarding exactly what form this will take.

There seems no doubt that the IwUI API will remain a part of Marmalade for the foreseeable future. However, we won't be delving any deeper into the API itself in this book as it seems likely that a new UI system will be making its way into Marmalade soon. If you are interested in what IwUI can do, take a look at the Marmalade documentation and the plethora of sample code that ships with the SDK.

The IwNUI API

Marmalade provides a second user interface API called IwNUI. The "N" stands for Native, as this API allows you to construct user interfaces using the standard UI controls for the platform that your application runs on.

This may sound like a good idea but the main drawback is that it is only supported on iOS and Android. All other platforms will use a default style implemented using the previously mentioned IwUI API.

At any rate most games tend to implement their own UI that is in keeping with the style of the game, and this normally means we don't want to use standard OS user interface controls, but IwNUI is a good choice if you happen to want to develop a utility or other application type.

Implementing our own user interface solution

Given that we're effectively starting from square one with our user interface implementation, let's consider how we could go about creating our own solution.

The following sections highlight some of the issues to be aware of when developing user interface code. One of the example projects accompanying this chapter implements a user interface library that tries to take most of the following into account.

Using a generic approach

It really is worth taking the time to develop as generic a solution as possible when dealing with user interface code. While implementing the frontend of a game isn't particularly difficult to code, it is far too easy to find yourself writing UI code from scratch for each project.

By investing in a generic approach, you can quickly put together a functional UI for all your projects. Frontend menu systems actually tend to be little more than a collection of buttons and labels; so why write this code multiple times? Implement these types of controls once and you can then spend more time creating customized controls when your game demands it.

It is recommended that you implement your UI code by creating a separate subproject, as this will help ensure that your solution is as generic and self-contained as possible.

Note

Marmalade makes it easy for us to create our own library modules by using the same system the SDK uses for including its component parts. Simply create an MKB file referencing all the source files that are part of the library, but save it with the extension .mkf instead of .mkb. You can then reference this module by adding the name of the MKF file (minus the extension) to the subprojects section of the main project MKB file. Library module directories should be placed in the same place as the main project directory so that they can be located when creating the project from the MKB file.

Making good use of class inheritance

A good class hierarchy can make implementing your UI a much more pleasant experience and it is well worth taking a look at existing systems to see how they have been constructed.

Most modern UI implementations will normally start with a base level class from which all other control types are derived, which for discussion purposes in this chapter we will call an element. An element will take care of things such as the positioning of a control and internal naming so that the handling of UI events can be standardized.

When implementing a class representing an element, we should make use of virtual methods that can be overridden by child classes to change default behavior. At the very least this normally means that we should have methods that can be called to update and render the control.

Another extremely useful concept is that of a frame, which has the ability to group several elements together so they can be moved, enabled, or hidden at the same time.

When updating or rendering the user interface, the frame is responsible for deciding whether or not to update or render the child elements it contains.

The positioning and sizing of all elements contained within a frame should also be calculated relative to the position and size of the frame itself.

Having implemented classes representing both elements and frames, it is possible to implement most common UI controls very simply. Here are a few examples to illustrate this:

  • A label control simply displays a line of text on screen. It can be derived from the element class and at its simplest all we need to define are member variables to store the text to be drawn, and some font and color information. We can then override the virtual render method to allow the text to be drawn at the position indicated by the element class.
  • A bitmap control is very similar to a label but displays an image instead of text. We just need to store a pointer to the image we want to draw (perhaps as a CIwTexture or CIwMaterial pointer) and then implement the render method to draw it on screen.
  • A button control can be derived from a frame. Most UI systems allow an image or a text string (or both) to be displayed on the button, so we can implement this just by adding label or bitmap controls to the list of elements contained within the frame.
  • A slider control could also use the frame as a basis and could include two bitmap controls, one for the backing of the slider and another for the selection knob. A label could also be included if you want to display the current position of the slider as a numeric value.

Hopefully this gives you an idea of how, with a little initial planning, implementing a diverse range of user interface controls actually becomes very easy.

Implementing a data-driven system

With a good class hierarchy in place, the next step is to ensure that your UI can be created easily from a configuration datafile. While it is perfectly possible to create all your controls in code, this is hard to maintain and, most crucially, can then only be edited by a programmer.

Allowing your UI to be constructed from a datafile means that other members of your team can help with designing the UI. Having a datafile format also makes it easier to develop a user interface layout tool if you want to make the process even easier for people to use.

We've already seen how we can use the ITX file format to construct our own custom classes from a file at runtime, so it makes sense to employ this methodology to our UI code (refer back to Chapter 2, Resource Management and 2D Graphics Rendering, if you want to refresh your memory on this). No point in writing more code than we have to!

Responding to user input events

The user interface of a game must solve two main issues. The first is relaying information to the player, and we've already discussed how this can be done earlier. The second is responding to user inputs.

As discussed earlier in this book, modern mobile devices allow a great many ways of allowing the player to interact with a game. Which of these you support, depends on the devices you are trying to target, but by far the most popular choice is the touch screen. Pressing on-screen buttons is just a very natural way of interacting with a application, so it is pretty much guaranteed you will end up supporting touch screens for your own UI.

Obviously not all controls need to respond to being touched. For example, a label is unlikely to do anything, so it makes sense to provide some mechanism that allows us to indicate which controls should respond to touches and which shouldn't.

While we could just add some virtual methods to the element class that gets called whenever a touch has been detected within its bounding area, this is probably not the best solution as it starts to make the element class a little cluttered.

We really want to encapsulate this sort of functionality somehow, and a good way of doing this is by using an Event system. Such a system works by having a central event manager whose sole job is to receive event messages from one part of the code and pass those messages on to any class instance that has registered itself with the event manager to be notified of a particular event.

To implement such a system, we can introduce two new base classes. An Event class, which is the base class for all event message types, and an EventHandler class, which contains a single virtual method called Execute that will be called in order to respond to an Event.

At its most basic level, the Event class will just contain a single member that is used as a unique identifier for a particular type of event, for example, an enumerated type. We can declare our own event types by deriving them from an Event and adding members for any information we might want to pass along with the message. For example, a touch screen event might contain the screen coordinates of where the touch occurred.

Any class that wants to respond to a particular event can then derive from the EventHandler class and provide an implementation for the virtual method. When a new instance of a class is constructed, it registers its interest in any event by passing the unique identifier of the event and a pointer to itself (cast as an EventHandler pointer) to the event handler.

Now, whenever an event occurs we create an instance of the event type in question, populate its members with information about that event, and pass it to the event manager. The event manager will compare the unique identifier of the event against its list of registered instances and then call the Execute method of EventHandler for any of registered instance that wanted to be notified about the type of event that has just occurred. The event message will be passed into the Execute method of the instance so that its data can then be acted upon accordingly.

Screen resolution and orientation

Chances are that your game could well be executed on a number of different devices that have different screen resolutions and aspect ratios, which can make creating a nice looking user interface a real chore.

It is therefore important to provide a very flexible way in which the position and size of UI controls can be specified.

When specifying screen coordinates, widths, and heights for controls, consider allowing both exact pixel sizes and ratios of the width and height of the containing frame to be used.

It is also good to allow a control to be conformed to a particular aspect ratio when using ratios to define sizes. Being able to ensure that the control has a particular aspect ratio makes it much easier to keep a consistent layout of any child control and is particularly important when drawing bitmapped images that will look strange if they end up stretched. When fixing a control to a particular aspect ratio, you will want to be able to indicate whether the width or the height should change to keep the control in the correct shape.

Being able to lay out controls relative to each other is also a useful thing to be able to do. One way of doing this is by specifying that a control should take in its position by adding an offset to the position of another control.

Another thing that can throw a spanner in the works is when the user rotates the device and the screen changes between portrait and landscape orientations. For most games we will want screen orientation changes to be ignored, since most games are designed to either be played in portrait or landscape and not both.

Ignoring screen orientation changes is made simple by adding the DispFixRot setting to the application's ICF file, as follows:

[S3E]
DispFixRot=Landscape

This setting can take the following values:

Value

Description

Free

Screen will rotate when the user rotates the device. This is the default value if DispFixRot is not used.

Portrait

Screen will always be kept in portrait aspect but can rotate when the device is held in either of the possible portrait orientations. It is easy to miss the fact that there are two possible portrait orientations since the phone could be held upside down!

Landscape

Screen will always be kept in landscape aspect but can rotate when the device is held in either of the possible landscape orientations. Again, note that there are two possible landscape orientations depending on which direction you rotate the phone from its normal portrait position.

FixedPortrait

Screen will be fixed in the device's default portrait orientation and will not rotate at all.

FixedLandscape

Screen will be fixed in the device's default landscape orientation and will not rotate at all.

If we do choose to support screen orientation changes, we need some way of detecting when the orientation has changed. We can do this by setting up a callback function as follows:

// This is the callback function
int32 OnOrientationChanged(s3eSurfaceOrientation* apOrientation,
void* apUserData)
{
  if (apOrientation->m_OrientationChanged)
  {
    if (apOrientation->m_Width > apOrientation->m_Height)
    {
      // Switch to landscape
    }
    else
    {
      // Switch to portrait
    }
  }
  return 0;
}

// Call this somewhere in our set up code
s3eSurfaceRegister(S3E_SURFACE_SCREENSIZE, (s3eCallback)
   OnOrientationChanged, NULL);

It is highly recommended, if you are supporting both portrait and landscape in your game, that you define specific layouts of your controls for each orientation and switch between them when the device is rotated. Trying to accommodate both orientations with a single layout is possible but tends to yield uninspiring results in both orientations, so make the most of the available screen space by providing custom layouts for each.

Adding template functionality

Consistency is an important part of the user interface design. We expect controls of a similar type to look the same. If they don't, the design starts to look sloppy and unprofessional. It's therefore useful to be able to provide a way of defining certain aspects of our UI once, and Template definitions allow us to do just that.

A relatively easy way of implementing templates is to be able to copy the settings of one UI control into another. We can create a control that will never actually be displayed, but will act as a template for other controls. When creating a new control we can copy all member settings from the template and then proceed to make changes to the settings so that the control displays whatever we need it to.

One way of implementing this is to add a virtual method to the element class, which is given a pointer to the template control. Each class can override this method to set its member variables based on the values contained in the template. By calling the virtual method in the parent class, we can copy all member variable settings from the template right down to the base element class.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.178.151