Table of Contents
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
There are two C++ base classes for GUI support:
-
SimpleGUIComponent
-
GUIShader
There is also one management class:
-
SimpleGUI
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.
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.
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
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.
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 )
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.
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.
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.
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.
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.
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.
Button events are related to mouse buttons input. They are reported to the script objects through the methods handleMouseButtonEvent and handleMouseClickEvent methods.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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
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.
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.
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
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