Tutorials
Andorra 2D GUI
Part 1 - Using the GUI system
Introduction
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:
<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.
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
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:
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!
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.
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:
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.
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.Name <> 'TAdGUI' then
begin
result := elem.Items;
exit;
end
else
begin
result := SearchFirst(elem.Items);
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
This page was generated with the help of the following PHP-Scripts: GeSHi a free PHP Syntax highlighter and StringParser_BBCode a free BBCode Parser.