Andorra 2D GUI
Part 1 - Using the GUI system
Introduction
http://andorra.sourceforge.net/tutots/guisample.jpg
Virtually all professional games do not use the standard components that Windows offers to build up the user interface. The
GUI system should be adapted to the game after all - a simulation of the middle ages does certainly not benefit from higgledy-piggledy Windows XP buttons. Also, it is difficult to display the standard components in a hardware-accelerated way. To give you the possibility of creating consistent GUIs which are fully integrated into your game, Andorra 2D provides a fairly powerful and in most cases sufficient
GUI system. This can easily be extended with self-made components and is also fully skinnable.
Please note: Writing such a
GUI system is very time-consuming, which is why its development is currently suspended. If you have created new components on your own or wish to extend the system, please send me an e-mail.
Preparations
First you have to create a
GUI in the supplied
GUI editor, which works in a similar way to Delphi. In the process you should save each form as a separate
GUI file. (*.axg, Andorra
XML GUI)
Skins can also be created with the supplied skin editor which, however, is still somewhat complicated at the moment. Anyhow, the supplied skin file "sunna.axs" can be modified to achieve the same result.
Apart from that, an Andorra
GUI needs a cursor definition file. For this, create an image list with the according cursors with the image list editor and write a simple
XML file according to the following example, in which you refer to the individual entries:
Delphi-Quellcode:
<?
xml version="1.0" encoding="iso-8859-1"?>
<
set>
<images source="cursors.ail"/>
<cursor name="default" src="default">
<hotspot x="0" y="0"/>
</cursor>
<cursor name="wait" src="wait">
<hotspot x="6" y="11"/>
<anim start="0" stop="7" speed="25"/>
</cursor>
<cursor name="cross" src="cross">
<hotspot x="8" y="8"/>
</cursor>
</
set>
Integration
To use the
GUI in a programme now, we first need some units:
The base frame of all
GUI components is located in "AdGUI" as well as the implementation of the cursors and the
GUI manager. It is important to include the
unit "AdComponents", if you use the supplied components. The reason for this is that with including the
unit, the corresponding components are registered in the
GUI system. If the file is not included, an error message is displayed because the specified components are not registered and therefore cannot be found.
First of all we need an instance of the class "TAdGUI", which takes care of loading, saving, skins and cursors automatically.
Delphi-Quellcode:
AdGUI := TAdGUI.Create(AdDraw);
//Instantiating the class
AdGUI.Skin.LoadFromFile('
sunna.axs');
//Loading the skin
AdGUI.Cursors.LoadFromFile('
cursors.xml');
//Loading the cursor
AdGUI.LoadFromFile('
gui.axg');
//Loading the GUI
Important: Commonly made mistakes
- Forgetting the
unit "AdComponents"
- Forgetting to include a PNG loader (such as AdPNG, AdDevIL or AdFreeImage), because the standard skin "Sunna" uses the format PNG to save the data.
Of course, "AdGUI" has to be freed in the end. One only has to call the method
AdGUI.Update
in the game loop.
Technically, these steps should suffice to display the
GUI. However, the emphasis lies on "display" - one can hit the mouse button as hard as one likes, nothing happens. Why would it? After all, mouse and keyboard events have to be passed on to the
GUI system first. There are two possibilities for doing this. The first one is the easier one which should be enough in most cases.
We use the class "TAdGUIConnector" from the
unit "AdGUIConnector". This loops the events of the AdDraw parent window system through the
GUI system. For this, the following steps are necessary:
Delphi-Quellcode:
AdConnector := TAdGUIConnector.Create(AdGUI);
//Creating the GUI connector
AdConnector.ConnectEventHandlers(AdDraw.Window);
//Linking it to the parent window system
The
GUI connector should also be freed upon terminating the programme.
But the
GUI connector (still) has a few flaws: One can only link the events to a component of the class "TForm" - if the AdDraw is on a panel, the events have to be passed on manually.
However, this method is not that bad and also has another inherent advantage: Imagine a construction game, in which freely movable windows are on the screen. How can the game know if we clicked on the window or on the game field? Not a problem with the manual linking!
Delphi-Quellcode:
procedure Form1Click(Sender:TObject)
var
p:TPoint;
begin
GetCursorPos(p); //Read current cursor position
p := ScreenToClient(p); //Convert to form-relative coordinates
if not AdGUI.Click(p.X, p.Y) then //Execute event handling
begin
//The game field was clicked
end;
end;
At this, the "Click" method of the
GUI system is simply called, which recursively calls the click methods of all the other elements. When it is detected that the click was indeed on a control element, the "Click" method returns "true".
This works analogously with every other event handler.
Accessing control elements
Now it would be highly convenient if we could
access the elements created in the editor in our programme somehow. In the editor, the elements are assigned a name which is stored in the property "Name" - just like in Delphi. The method "FindComponent" of TAdGUI returns the wanted element when given the corresponding name. To
access component-specific properties, we only have to cast the result to the according component class.
TAdButton(AdGUI.FindComponent('AdButton1')).Caption := 'Hello!';
Control element events
Our
GUI system is still fairly inanimate - we do not react to events such as clicks at all yet.
For that we can simply link procedures to events such as "OnClick". We have to create a procedure that has exactly the same parameters as the event procedure. These can be found out in the code completion. Please note that the declared procedure has to be part of a class. Example:
Delphi-Quellcode:
type
TForm1 =
class(TForm)
[...]
private
procedure AdButtonClick(Sender:TObject);
public
[...]
end;
[...]
procedure TForm1.AdButtonClick(Sender:TObject);
begin
TAdButton(AdGUI.FindComponent('
AdButton1')).Caption := '
You clicked me!!';
end;
procedure TForm1.FormCreate(Sender:TObject);
begin
//Initialise Andorra 2D
[...]
//Load GUI
[...]
//Assign events
TAdButton(AdGUI.FindComponent('
AdButton1')).OnClick := AdButtonClick;
end;
By the way, this works the same way in the
VCL.
Customised components
To create customised components, derive a class from "TAdComponent" and register it in the "initialization" clause of the
unit. Along the lines of "Code is worth a thousand words" you best learn how this works by reading the
unit "AdComponents". To use your creations in the editor, you have to include the corresponding class there, too.
Extend code with a loaded GUI
Calling "LoadFromFile" usually replaces the whole
GUI - this can be avoided with the following code. Please note that you have to include the
unit "AdSimpleXML" additionally.
Delphi-Quellcode:
procedure ExpandGUI(afile:
string);
var
elems:TAdSimpleXMLElems;
xml:TAdSimpleXML;
j:integer;
cref:TPersistentClass;
function SearchFirst(elem:TAdSimpleXMLElem):TAdSimpleXMLElems;
var
i:integer;
begin
result :=
nil;
for i := 0
to elem.Items.Count - 1
do
begin
if elem.Items[i].
Name <> '
TAdGUI'
then
begin
result := elem.Items;
exit;
end
else
begin
result := SearchFirst(elem.Items[i]);
if result <>
nil then
begin
exit;
end;
end;
end;
end;
begin
xml := TAdSimpleXML.Create;
xml.LoadFromFile(afile);
elems := SearchFirst(
xml.Root);
if elems <>
nil then
begin
for j := 0
to elems.Count - 1
do
begin
cref := GetClass(elems.Item[j].
Name);
if cref <>
nil then
begin
with TAdComponent(TAdComponentClass(cref).Create(AdGUI))
do
begin
LoadFromXML(elems.Item[j]);
end;
end;
end;
end;
xml.Free;
end;
Conclusion
This information should help you deal with the
GUI system. It is most important to take a look at the properties of the components. Don't be afraid to take a look at the implementation of a component - then emerging questions usually sort themselves out. After all, this is fairly easy in Delphi but also in Lazarus: If you click on an identifier while pressing the CTRL key, you automatically navigate to its declaration.
Copyright and licence
(c) by Andreas Stöckel November 2007
Translation by 3_of_8 (Manuel Eberl)
The content of this tutorial is subject to the
GNU Licence for Free Documentation