bw logo

Chapter 17. Graphical User Interface (GUI)

The BigWorld GUI can be broken into standalone (menu and options interfaces) and in-game overlays.

The in-game overlays require 3D integration, and thus have a separate design. The unique features of the in-game interface are support for:

  • Linking to in-game objects (specifically models' bounding boxes),

  • Alpha blending, or superimposing over the scene,

  • Special effects like scaling, rotation, colour change, fading in/out, and motion blur.

The most important feature of the in-game interface is the ability to fade away when not in use. This allows the game designer to create a more immersive game world experience. Whilst a GUI is required to relate important information to the player, it should only do so when that information is required. At other times, the player should not be distracted by overlays, but immersed fully in the 3D world.

Some examples of in-game interfaces would be:

  • Targeting

  • Current item display

  • Player health and damage

  • Chat window

17.1. C++ GUI support

There are two C++ base classes for GUI support:

  • SimpleGUIComponent

  • GUIShader

There is also one management class:

  • SimpleGUI

17.1.1. SimpleGUIComponent

The SimpleGUIComponent class is simply a textured rectangle. The derived class TextGUIComponent draws text, and the also derived class FrameGUIComponent draws a resizable frame using three bitmaps (corner, edge, background).

SimpleGUIComponent has many properties, mostly accessed from Python and XML files. For more details, see Python GUI support.

Components are hierarchical only in that parents draw their children; children do not inherit their parents' transform. WindowGUIComponent is an exception to this rule — children are automatically clipped and translated by their parent. Note that this is a feature of WindowGUIComponent, not the GUI system in general.

17.1.2. GUIShader

The GUIShader class alters the way that components are drawn, in an analogous form to vertex shaders (in fact, 99% of GUI shaders operate only on temporary variables instead of vertices, hardware TnL support).

  • ClipGUIShader clips GUIComponents to a proportion of its original length, which is useful for making health bars.

  • ColourGUIShader colours a component.

  • AlphaGUIShader fades a component.

  • MatrixGUIShader transforms a component.

Shaders are applied to all children — so use a MatrixGUIShader to implement windowing, and an AlphaGUIShader to fade in/out a whole tree of components.

17.1.3. SimpleGUI

This is the GUI root, and it is ticked and drawn at every frame.

Use SimpleGUI::addSimpleComponent() and SimpleGUI::removeSimpleComponent() to build your tree of GUI components. Note that you would normally never call these methods from C++, as they are mostly called by scripts.

17.2. Python GUI support

You probably will create GUIs using Python and XML most of the time. There is no BigWorld GUI editor, thus all GUI is currently created using the Python console in game.

The code below shows an example:

import GUI
 
#create a simple GUI component
s=GUI.Simple( "maps/guis/stats_bar.dds" )

#width/height initially in pixels. can use widthRelative/heightRelative
#to designate the component uses clip coordinates ( -1 .. +1 ) instead.
s.width = 64
s.height = 16

#colour attribute is ( r,g,b,a )
s.colour = ( 255, 128, 0, 255 )

#the materialFX is simply the blend mode. can be "BLEND","SOLID","ADD"... 
s.materialFX = "BLEND"

#the position is always in clip coordinates ( -1 .. +1 )
s.position = ( 0, 0.85, 0.5 )

#the anchors determine what the position means with respect to the width 
#and height. in this example, the position of (0,0.85,0.5) and anchors
#"TOP,CENTER" means that the top centre of the component will rest at 
#the given position.
#The component will hang down from y=0.85, and will be centred on x=0.0
s.verticalAnchor = "TOP"
s.horizontalAnchor = "CENTER"
 
#create a clipper for the health amount. clip shaders are used to
#implement stats bars. the constructor parameter "RIGHT" means the 
#shader will clip the component from the right hand side.
c=GUI.ClipShader( "RIGHT" )

#all shaders animate their values. the speed indicates how long the
#internal parameter will change from the old value to the new. This speed
#indicates the health bar will always take 1/2 a second to change.
c.speed = 0.5

#the value is what the game normally changes if player's health changes.
c.value = 1.0

#this line adds the shader. Note that you can call your shader any name
#you like. Internally, the simple GUI component sees you are trying to 
#add a GuiShader to it, under the name of clipper.
#Internally it will call SimpleGUIComponent::addShader()
s.clipper = c
 
#create a colourer for the health amount
c=GUI.ColourShader()

#the start,middle and end simply represent colours to blend to when the
# value parameter is 1.0, 0.5 and 0.0 respectively.
c.start=(255,0,0,192)
c.middle=(255,255,0,192)
c.end=(0,255,0,192)
c.speed=0.5
c.value=1.0
s.colourer=c
 
#and make the health bar be drawn
GUI.addRoot( s )

Example of GUI creation

You can then customise the new health bar simply by setting the appropriate values in the shaders:

# player's health went down to 80%
s.clipper.value = s.colourer.value = 0.8

Finally, you can associate a script with the GUI component, in order to handle input and I/O events, and to build high-level functionality onto it. Associate a script object with the component using the script attribute:

s.script = PyGUI.Button( s )

For more details about GUI scripts, see XML and Python, and Input events. For more details on attributes, see the Client Python API's entry Main Page Client GUI Classes SimpleGUIComponent.

If you are creating a GUI purely in Python, then you must ensure that you correctly drop all references to it when it is to be deleted, or there will be a memory leak. This is because the Python garbage collection is turned off in the BigWorld engine, it uses reference counting instead for speed. You might get a warning message like the following:

---------------------------------------------------------
Some SimpleGUIComponent instances haven't been destroyed.
To debug, in <engine_config>.xml, set:
   <simpleGui>
     <breakOnAllocId> AllocId </breakOnAllocId>
     <breakOnLeak>    true </breakOnLeak>
   </simpleGui>
---------------------------------------------------------

To delete, you must set all references to the GUI component to None, and this includes removing the component from its parent.

if s.parent:
    s.parent.delChild( s )
else:
    GUI.delRoot( s )
s = None

17.3. XML

GUI can also be represented as XML files. They can be saved in the folder <res>/guis once constructed, for example, using the method described above.

The advantage of the Python interface is that once you have created the GUI, simply call:

s.save( "guis/health_bar.gui" )

An XML file will be created encapsulating the GUI component, its shaders, and all of its children. Once you have done this, write:

GUI.delRoot( s )
s = None
s = GUI.load( "guis/health_bar.gui" )
GUI.addRoot( s )

After that, you will have exactly the same component, with all its shaders and children set up.

Advanced users will find creating XML by hand the quickest way to create your GUI. Alternatively, a GUI editor can be entirely created in Python.

17.4. XML and Python

When you have a GUI component with a script saved in XML, your Python script must implement the following methods (at the very least to stub them out):

  • def onLoad( self, section )

  • def onBound( self )

  • def onSave( self, section )

17.4.1. onLoad(self,section)

The onLoad method is called just after the C++ load method has been called, the standard member variables have been setup, and the associated script has already been constructed.

The data section that the GUI component is being loaded from is passed into the method. This allows the definition of custom attributes, especially for loading custom data into the script object.

Note that the method is called before any children components or shaders have been loaded.

17.4.2. onBound(self)

The onBound method is called after the load is complete.

The main difference between this method and onLoad is that by the time onBound is called, the whole GUI component tree has been created. Thus in the onBound method you can write custom script handling to manipulate child components. For example, you could invoke your own custom Layout Manager in this method.

17.4.3. onSave(self,section)

The onSave method is called just after the C++ save method has saved all standard GUI component members, but before the shaders and children are saved.

17.5. Input events

SimpleGUI offers support for keyboard, joystick, and mouse input. To capture events, a component needs to have its property focus set to true, and an associated Python script attached.

When loading a component from a GUI file, the attribute of a SimpleGUIComponent is automatically set if the XML declares a script field. The value of the field is used to instantiate the script object (it must be a callable receiving one parameter — the SimpleGUIComponent being created — , and returning a Python object). The Python object returned will be the one assigned as the script object for the newly created component.

The script attribute can also be set manually for an existing component, like in the example below:

# instantiate a new PyGUI Button class, and make it the component's 
# script attribute. note most scripts are passed the GUI component 
# in their constructor so they can perform operations on them.
s.script = PyGUI.Button( s )

There are separate focus properties for each category of input events, as described below:

  • focus

    Associated with: Keyboard events (including character events), joystick (axis) events, and global mouse button events.

  • mouseButtonFocus

    Associated with: Mouse button events that occur while the mouse position is contained within the region of the component.

  • crossFocus

    Associated with: Mouse enter events, and mouse leave events.

  • moveFocus

    Associated with: Mouse move events.

  • dragFocus

    Associated with: Drag events.

  • dropFocus

    Associated with: Drop events.

To make a component start receiving input events, you must set the appropriate property to True, and to have it no longer receiving events set it to False, as illustrated below:

# start receiving key/axis events
c.focus = True

# stop receiving mouse enter/leave events
c.crossFocus = False

When a component has a script and it has focus enabled, the script will start capturing input events.

The script must define the appropriate methods to handle the events. The example below illustrated a script defining the methods to handle keyboard and joystick (axis) events:

class Button:
   def handleKeyEvent( self, event ):
      # do whatever, and return 1 if 
      # this key event was consumed

   def handleAxisEvent( self, event ):
      # do whatever, and return 1 
      # if this axis event was consumed

The following sub-sections describe the events supported by SimpleGUI. For more details, see the Client Python API's entry Main Page Client GUI Classes SimpleGUIComponent.

17.5.1. Keyboard Events

Keyboard events are related to input from keyboard, mouse, and joystick buttons. They are reported to script objects through the handleKeyEvent method:

def handleKeyEvent( self, event )

The parameters are described below:

  • event

    PyKeyEvent containing information about this event.

A return value of True means that the event has been consumed, thus effectively preventing it from being propagated to other components or game scripts. A return value of False allows further propagation.

To receive key events, a component must have the property focus set to True.

Note that mouse button events reported through the method handleKeyEvent differ from those reported through handleMouseButtonEvent in that the mouse cursor does not have to be inside the area defined by a component for that component to capture the event. The only requirements for the capture are to have the property focus set to True, and not having another component consuming the event earlier in the focus list.

Character events are attached to key events. Check the PyKeyEvent.character parameter, which will be a string containing the fully translated character. It will otherwise it will be None. Keep in mind that due to more complex input methods (e.g. dead-keys) the length of the character string can be greater than 1.

17.5.2. Axis Events

Axis events are related to joystick axis input. They are reported to script objects through the handleAxisEvent method:

def handleAxisEvent( self, axis, value, dTime )

The parameters are described below:

  • event

    PyAxisEvent containing information about this event.

A return value of True means that the event has been consumed, effectively preventing it to be propagated to other components or game scripts. A return value of False allows further propagation.

To receive axis events, a component must have the property focus set to True.

17.5.3. Mouse Events

Mouse events can be grouped into three categories:

  • Button events

  • Cross events

  • Move events.

Mouse events are propagated from the top-most component under the mouse cursor down to the bottom component until it is handled by a component (by returning True in one of its event handling methods).

The following sub-sections describe how to handle input from each category of mouse event.

Note that mouse events are only generated when the object MouseCursor is active. For more details, see Mouse cursor.

17.5.3.1. Button events

Button events are related to mouse buttons input. They are reported to the script objects through the methods handleMouseButtonEvent and handleMouseClickEvent methods.

17.5.3.1.1. handleMouseButtonEvent

This method is called by SimpleGUI on the top most component under the mouse that has mouseButtonFocus set to True:

def handleMouseButtonEvent( self, comp, event )

The parameters are described below:

  • comp

    Component over which the button was pressed or released.

  • event

    PyKeyEvent containing information about this event.

A return value of True means that the event has been consumed, effectively preventing it to be propagated to other components or game scripts. A return value of False allows further propagation.

To receive mouse button events, a component must have the property mouseButtonFocus set to True.

17.5.3.1.2. handleMouseClickEvent

This method is called by SimpleGUI when the left mouse button was pressed and released over the component.

def handleMouseClickEvent( self, comp, pos )

The parameters are described below:

  • comp

    Component over which the button was pressed.

  • pos

    Position of the mouse on the instant of the mouse button click.

    Value is a 2-tuple of floats in clip-space, ranging from -1.0 (leftmost in x-axis, top in y-axis) to 1.0 (rightmost in x-axis, bottom in y-axis).

A return value of True means that the event has been consumed, effectively preventing it to be propagated to other components or game scripts. A return value of False allows further propagation.

To receive mouse click events, a component must have the property focus set to True.

17.5.3.2. Cross events

Cross events are related to the mouse pointer entering or leaving the region defined by the component in the screen. They are reported to the script objects through the methods handleMouseEnterEvent and handleMouseLeaveEvent.

The signature for handleMouseEnterEvent is described below:

def handleMouseEnterEvent( self, comp, pos )

The signature for handleMouseLeaveEvent is described below:

def handleMouseLeaveEvent( self, comp, pos )

The parameters for both methods are described below:

  • comp

    Component that the mouse entered or left.

  • pos

    First position of the mouse when entering or leaving the component.

    Value is a 2-tuple of floats in clip-space, ranging from -1.0 (leftmost in x-axis, top in y-axis) to 1.0 (rightmost in x-axis, bottom in y-axis).

A return value of True means that the event has been consumed, effectively preventing it to be propagated to other components or game scripts. A return value of False allows further propagation.

To receive mouse enter and leave events, a component must have the properties focus and crossFocus set to True.

17.5.3.3. Move events

Move events are related to the mouse pointer hovering over the region defined by the component in the screen. They are reported to the script objects through the handleMouseEvent method:

def handleMouseEvent( self, comp, event )

The parameters are described below:

  • comp

    Component over which the mouse cursor hovered.

  • event

    PyMouseEvent containing information about this event.

A return value of True means that the event has been consumed, effectively preventing it to be propagated to other components or game scripts. A return value of False allows further propagation.

To receive mouse move events, a component must have the property focus and moveFocus set to True.

17.5.4. Drag-and-drop events

SimpleGUI offers support for drag-and-drop functionality. Drag-and-drop events can be grouped into two categories:

  • Drag events

  • Drop events

The following sub-sections describe how to handle input from each category of drag-and-drop event.

Note that drag-and-drop events are only generated when the MouseCursor is active. For more details, see Mouse cursor.

17.5.4.1. Drag events

Drag events are related to a component being dragged by the user. They are always generated on the component being dragged and reported through the methods handleDragStartEvent and handleDragStopEvent.

17.5.4.1.1. handleDragStartEvent

This method is called when the user is trying to drag the component:

def handleDragStartEvent( self, comp, pos )

The parameters are described below:

  • comp

    Component that the user is trying to drag.

  • pos

    Position of the mouse on the instant of the event.

    Value is a 2-tuple of floats in clip-space, ranging from -1.0 (leftmost in x-axis, top in y-axis) to 1.0 (rightmost in x-axis, bottom in y-axis).

A return value of True signals to the GUI manager that this component is willing to be dragged, and consumes the event. A return value of False prevents the component from being dragged, and allows further propagation of the event.

To receive this event, a component must have the property dragFocus set to True.

17.5.4.1.2. handleDragStopEvent

This method is called when the user released the mouse left button, and therefore wants to drop the component:

def handleDragStopEvent( self, comp, pos )

The parameters are described below:

  • comp

    Component being dragged.

  • pos

    Position of the mouse on the instant of the event.

    Value is a 2-tuple of floats in clip-space, ranging from -1.0 (leftmost in x-axis, top in y-axis) to 1.0 (rightmost in x-axis, bottom in y-axis).

The return value from this method is always ignored, and the originating mouse button event is allowed to propagate further.

To receive this event, a component must have the property dragFocus set to True.

17.5.4.2. Drop events

Drop events are related to a component being dropped over another one. They are always generated on the recipient component and reported through the handleDragEnterEvent, handleDragEnterEvent and handleDropEvent methods.

17.5.4.2.1. handleDragEnterEvent

This method is called when the user just dragged another component over this one, but has not dropped it yet:

def handleDragEnterEvent( self, comp, pos, dropped )

The parameters are described below:

  • comp

    Component about to receive the drop.

  • pos

    Position of the mouse on the instant of the event.

    Value is a 2-tuple of floats in clip-space, ranging from -1.0 (leftmost in x-axis, top in y-axis) to 1.0 (rightmost in x-axis, bottom in y-axis).

  • dragged

    Component being dragged.

The return value from this method is used to determine if the recipient component is willing to accept the drop. This event is always considered consumed when first triggered, and the originating mouse move event is propagated no further.

To receive this event, a component must have the property dropFocus set to True. Arguments

17.5.4.2.2. handleDragLeaveEvent

This method is called when the dragged component is no longer over this one:

def handleDragLeaveEvent( self, comp, pos )

The parameters are described below:

  • comp

    Component that was about to receive the drop.

  • pos

    Position of the mouse on the instant of the event.

    Value is a 2-tuple of floats in clip-space, ranging from -1.0 (leftmost in x-axis, top in y-axis) to 1.0 (rightmost in x-axis, bottom in y-axis).

A return value of True means that the event has been consumed, effectively preventing the originating mouse event to be propagated to other components or game scripts. A return value of False allows further propagation.

To receive this event, a component must have the property dropFocus set to True.

17.5.4.2.3. handleDropEvent

This method is called when the user has dropped a component over this one:

def handleDropEvent( self, comp, pos, dropped )

The parameters are described below:

  • comp

    Component receiving the drop.

  • pos

    Position of the mouse on the instant of the event.

    Value is a 2-tuple of floats in clip-space, ranging from -1.0 (leftmost in x-axis, top in y-axis) to 1.0 (rightmost in x-axis, bottom in y-axis).

  • dropped

    Component receiving the drop.

The return value from this method is always ignored, and the originating mouse button event is allowed to propagate further.

To receive this event, a component must have the property dropFocus set to True.

17.5.5. Component PyGUI

You might find it useful to build your own Python module with custom GUI components. To do that, the best starting point is the module PyGUI.

PyGUI is the informal Python module for basic GUI elements, created for an internal project, and defined in fantasydemo/res/scripts/client/Helpers/PyGUI.

Starting from the basic functionality offered by SimpleGUI native components, you can code your own GUI toolkit in Python. Below are some examples of widgets you can create:

  • Page control

  • Drop-down List

  • Edit field

  • Multi-line text field

  • Check box

17.6. Mouse cursor

It is possible to control the behaviour and appearance of the mouse cursor from the game scripts. Mouse cursor-related functions and properties can be accessed though the MouseCursor object.

The MouseCursor object is a singleton, and can be obtained using the method GUI.mcursor.

The properties published by it are described below:

  • position — Read/write

    Mouse cursor position

  • shape — Read/write

    Mouse cursor shape

  • visible — Read/write

    Mouse cursor visibility status

  • active — Read-only

    Mouse activity status

  • clipped — Read/write

    When set to true, the mouse cursor will be clipped to the region of the client window.

The MouseCursor is an instance of the abstract concept InputCursor. There can only be one active InputCursor at any given time.

To activate the MouseCursor, use the method BigWorld.setCursor. To deactivate it, activate another input cursor, or pass None to BigWorld.setCursor.

Mouse and drag-and-drop events are only propagated to GUI components while the MouseCursor is active.

The code below illustrates how to use the MouseCursor:

# access and modify the mouse cursor
import GUI
import BigWorld

mc = GUI.mcursor()
mc.shape = "arrow"
mc.visible = True
if not mc.active:
   lastPosition = mc.position
   mc.position = ( 0.0, 0.0 )
   BigWorld.setCursor( mc )
   # mc.active is now True

Example of how to use MouseCursor object