AGS Pointers for Dummies
If you're reading this, then you've come looking for answers. What are pointers? How do I use them? What makes them better than just using integers? These questions, amongst others shall be answered. You're not alone in the confusion caused by pointers. Many people who are new to scripting, or haven't ever used a language that supported pointers don't understand them right away. That's why I'm here to help.
What Are Pointers?
So what exactly are pointers?
To understand what a pointer is, we must first understand the more basic idea of variables. If you already understand variables, you can skip over this section.
Variables
A variable, in the context of scripting, is a way to represent a value. In AGS, there are five different data types which can be used to represent a variable. These types include char, short, int, String[1], and float. A char-type variable is one that can hold only a single character (i.e., 'A', 'B', etc.) or a number within the range 0 to 255. A short-type variable can store integers within the range -32768 to 32767. An int-type variable can store integer values within the range -2147483648 to 2147483647. A String-type variable can hold a string of characters (i.e., "this is some text!") of virtually[2] infinite length. A float-type variable can store floating-point decimals within the range -2147483648.0 to 2147483647.0, and has precision[3] up to approximately 6 decimal places, though this will vary based on the actual number.
For information on defining and assigning values to variables read the entry in the manual data types.
Pointers
Okay, so now that we understand what a variable is, we can begin to understand what a pointer does. The basic idea of a pointer is that instead of creating a new variable, we are simply going to point to a variable that is already stored in the memory. This can have several uses in scripting, and AGS even has some special ones.
Defining A Pointer
And, how do I use them?
In AGS, you can only create pointers to certain data types. These are called the managed types[4].
Managed Types
AGS has certain managed types[4] that you can not create an instance (variable declaration) of, but you can create pointers to[5]. All of the variables of managed types are managed by AGS. These include the types: AudioChannel, AudioClip, Button, Character, DateTime, Dialog, DialogOptionsRenderingInfo, DrawingSurface, DynamicSprite, File, Game, GUI, GUIControl, Hotspot, InventoryItem, InvWindow, Label, ListBox, Maths, Mouse, Object, Overlay, Parser, Region, Room, Slider, TextBox, and ViewFrame.
Working With Managed Types
You can work with these managed types through pointers. You define a pointer by typing the name of the managed data type, then a space, an asterisk (*)[6], and finally the name of the pointer. So, if we want to create a GUI pointer (this is expressed as GUI*) called GUIPointer, we could type the following:
GUI *GUIPointer;
This creates a pointer that can point to any GUI stored in the memory. However, until it is assigned a value, it is an empty, or null pointer. We'll first discuss how to assign pointers a value, then we'll discuss null pointers.
Array of Pointers
It should be noted here that when defining pointers, you can also create an array of pointers. When you create an array you are simply defining a set of variables (or in this case, pointers) which all have the same name. You access each one individually using an index between brackets ([ and ]).
Defining an array of pointers works the same way as defining any other array does, so to define an array of GUI*s called myguis to hold 5 GUI*s, you would type:
GUI *myguis[5];
With arrays you can't assign initial values, and the valid indices are from 0 to the size of the array minus one (in this case, 0 to 4). You treat an array of pointers just like you would ordinary pointers.
Dynamic Array of Pointers
As of AGS 3.0, you can have dynamic arrays of the built-in types, including the managed types. The assignment here works a little differently:
GUI *daguis[] = new GUI[5];
Notice that we don't use an asterisk after the new keyword. Keep that in mind if you plan to use dynamic arrays of pointer types.
Assigning A Pointer A Value
To make a pointer point to a GUI, you assign it the value of the GUI you want it to point to (with the assignment operator, =). So to make GUIPointer point at the GUI named MYGUI, you would type:
GUIPointer = gMygui;
As long as the pointer isn't global (i.e., the pointer is defined inside of a function), then you can also assign it an inital value when you create it, like this:
GUI *GUIPointer = gMygui;
Global pointers can't have an initial value assigned though, so this will only work if you define the pointer inside of a function. When defining more than one pointer of the same type at once, it is necessary to use an asterisk for every pointer. So, if you want MyGUIPointer to point to MYGUI, and OtherGUIPointer to point to OTHERGUI, you can do this:
GUI *MyGUIPOINTER = gMygui, *OtherGUIPointer = gOthergui;
If you forget an asterisk then it will try to create a new instance (create a new variable) of the type GUI. AGS doesn't allow the user to create new instances of managed types, so this would crash your game. So, it's always important to remember your asterisks.
A More Useful Assignment
This type of assignment is rather pointless however, unless you just want a new alias for your GUIs. A more useful assignment makes use of the function GUI.GetAtScreenXY. This function returns a GUI* to the GUI at the specifed coordinates. So, if you wanted to see what GUI the mouse was over, you could do this:
GUI *GUIUnderMouse = GUI.GetAtScreenXY(mouse.x, mouse.y);
Testing A Pointer's Value
If you want to see what a pointer is actually pointing to, you can use the boolean operators == (checks if two things are equal) and != (checks if two things are not equal). So, to see if GUIUnderMouse is MYGUI or not, you could do this:
if (GUIUnderMouse == gMygui) Display("MYGUI is under the mouse!"); else if (GUIUnderMouse != gMygui) Display("MYGUI is not under the mouse!");
Null Pointers
If a pointer isn't pointing to anything, it is known as a null pointer. It will actually hold the value null. Operations on null pointers will cause the game to crash, so you should always be sure that your pointer is non-null before using it.
You check if a pointer is null or not the same way you would normally check a pointer's value:
if (GUIPointer == null) { /* the pointer is null */ } else { /* the pointer is non-null */ }
What Pointers Do
Okay, so we can create pointers, assign them a value, and test their value, but what do pointers do?
We've discussed already that pointers point to variables stored in the memory to prevent having to reproduce the data, but we haven't actually discussed in depth how this can be used to our advantage.
Pointer System Versus Integral System
We've seen how a GUI* (remember that this is a GUI-pointer or pointer-to-GUI) can help us find out what GUI is on the screen at certain coordinates, but we could do this with an integral system, such as:
int gui = GetGUIAt(mouse.x, mouse.y); if (gui == MYGUI) Display("MYGUI is under the mouse!");
So if we could already do this, why change to a pointer system and cause all the confusion? But let's not get ahead of ourselves. Pointers aren't designed with the sole purpose of causing confusion. And they are actually quite useful once you understand them.
The String Type
The String type isn't one of AGS's managed types, nor can you create a pointer to it. So why then am I bringing it up? The fact is, the String type is actually internally defined as a pointer, which is how it is able to have it's virtually infinite maximum length.
Prior to AGS 2.71, AGS used the now deprecated string type. The string type was internally defined as an array of 200 characters (chars). This meant that strings had a maximum length of 200 characters themselves.
With the introduction of AGS 2.71 came the new String type which removed that limit. And how did it do it? It used a pointer. Not an AGS-style pointer, but a pointer nonetheless. In programming languages such as C and C++, a pointer-to-char (char*)[7] creates a special type of pointer. Instead of just pointing to one single variable, a char* can point to a virtually infinite number of chars in the form of what is known as a string-literal (such as "this is some text").
Since AGS uses a special type of pointer for managing the String type, it can still hold the value null (this is what Strings are initialized to), and when used as a function parameter, can be made optional in the same manner (see the section on optional parameters for more information).
Script O-Names
Script o-names are another example of a pointer system versus an integral one. Basically the way a script o-name is defined is like this:
// pseudo-AGS-internal-code GUI *gMygui = gui[MYGUI];
For all we know the gui array itself could be an array of pointers to something stored deeper within the bowels of AGS, but it's not really important as in the end they would still both point to the same GUI, and this is just an example anyway.
Using an integral system you would have to acess the gui array any time you wanted to perform any operations on the GUI[8]. So, if we wanted to move MYGUI to (30, 120), in an integral system we could do this:
gui[MYGUI].SetPosition(30, 120);
In a pointer system we would do this:
gMygui.SetPosition(30, 120);
So it makes our code a bit shorter then, but it's essentially the same. All-in-all not a particularlly convincing example. So let's take a look at another built-in pointer: player.
Player Keyword
The player keyword provides a much simpler method for performing operations directly on the character. In an integral system we could use something like this:
character[GetPlayerCharacter()].Move(20, 100);
With the player keyword we now simply type:
player.Move(20, 100);
This also provides advantages when working with the player's active inventory item.
Player.ActiveInventory
In an integral system to access the player character's active inventory, you would have to do something like this:
character[GetPlayerCharacter()].activeinv
In a pointer system you do this:
player.ActiveInventory
But what about when we actually want to do something with that? Say, for example, changing it's graphic to slot 42:
// integral system inventory[character[GetPlayerCharacter()].activeinv].graphic = 42; // pointer system player.ActiveInventory.Graphic = 42;
Again, it makes the code shorter, but the second snippet is also easier to read, and more obvious what you're trying to do.
File*
Another example can be seen if we look at the File type.
In an integral system, you would access an external file like this:
int handle = FileOpen("temp.tmp", FILE_WRITE); if (handle == 0) Display("Error opening file."); else { FileWrite(handle, "test string"); FileClose(handle); }
You have to store the file's handle when you open it, and you later have to be sure to close that file using the same handle. In-between this time you have to be sure that the value of the handle doesn't change or get lost.
In a system with pointers, you can do this instead:
File *output = File.Open("temp.tmp", eFileWrite); if (output == null) Display("Error opening file."); else { output.WriteString("test string"); output.Close(); }
You create a File* which points to the opened file. You still have to close the file using the File*, but it's simpler since you are using a specifically created File* instead of just a generic int variable.
Pointers as Function Parameters
Pointers can also be used for function parameters. Those who have used versions of AGS prior to 2.7 know that integers used to be passed as parameters for several functions which have now been made into OO (object-oriented) functions, such as MoveCharacter (now known as Character.Move).
The old MoveCharacter function took three parameters: CHARID, int x, and int y. CHARID was an integer parameter which held the character's ID (this is the same as the new Character.ID property). But what if we had the MoveCharacter function in a pointer-implemented, non-OO system? The parameter list would probably be something like this: Character *Char, int x, int y.
The first parameter, a Character* would allow us to pass a Character* instead of just an int, which helps make clearer what the code is trying to accomplish. It also ensures that the parameter is valid (to an extent). An integer parameter could have any value passed into it, which the function would then have to check. A Character* helps to ensure the value is valid, though since it is a pointer it could still be null.
Optional Parameters
As of AGS 2.7 you can make function parameters optional by assigning them a default value when you import the function. For example, to make a function with an optional int parameter, you can define the import like this:
import myfunc(int param1, int param2=5);
That would make PARAM2 optional, with the default value of 5. This import doesn't necessarily have to be placed in a script header (which is where most of your imports will be). If you don't want the function to be globally accessible but you want an optional parameter, you can just put this import in your script before you define the function and it will allow you to have a non-global function with an optional parameter.
Optional Pointer Parameters
Okay, that's nice, but how does it apply to pointers? I tried assigning my Character* parameter a default value and it didn't work.
AGS doesn't currently allow you to assign non-integer default values. This means that to make a parameter optional we will have to give it an integer value. But what integer value can we use with pointers? Though it is not normally recommended (and in most cases won't work), we can substitute the value 0 for null in this case.
This does of course mean that you would have to have some means of handling null values for that parameter. Perhaps, for a Character*, it could default to the player character. You could do this by checking your parameter, and if it is null, reassigning it to the player character:
if (Char == null) Char = player;
Also, you may remember my mentioning that the String type uses pointers? You can make a String parameter optional in the same way you make any other pointer parameter optional, by assigning it the value of 0. This will cause the parameter to default to a null String.
Extending The Built-In (Managed) Types
Now that we've seen what pointers are, how they are used, how they relate to AGS, and some basic uses of them, let's take a look at a different kind of usage. In AGS we can create our own custom-defined datatypes using struct.
You define a struct like this:
struct MyStruct { int IntMember; String StringMember; import int do_something(); };
That would create a new datatype called MyStruct which would have two data members and one member function. You could then create a variable of this type, and do all sorts of fun things with it. Though it's uses don't end there.
You can also make a pointer a member of a struct[9], which provides some interesting possibilities. With a pointer as a member, you can essentially extend built-in datatypes (i.e., the managed types).
Extending the Character Type
We can extend the built-in Character type using a Character* as a member of one of our structs. So, let's look at how we can do this:
struct CharStats { int Health; int Mana; Character* Char; };
We've given this new datatype CharStats three members: Health, Mana, and Char. So how does this help us to extend the built-in datatypes then? By assigning a value to Char, we can access all the properties of that Character through our new datatype. First we have to assign the pointer a value, so let's look at that:
// global script CharStats stEgo; // cEgo with Health and Mana properties export stEgo; // this makes stEgo global to all scripts, requires an import in the header // game_start function stEgo.Health = 100; // set Ego's Health stEgo.Mana = 80; // set Ego's Mana stEgo.Char = cEgo; // set Ego's Char
And now for the remainder of your game you can use stEgo.Char any place you would normally use cEgo. This way you can put all of your properties and functions for working with Ego into one convenient place!
You can extend any of the managed types that you can create pointers to in this manner.
Extending the Character Type for AGS 3.0+
The above example was originally written and designed around the 2.7x branch of AGS. As of AGS 3.0 however, we have the ability to use extender methods. For this particular example, I would recommend adding global extenders such as Character.GetHealth and Character.SetHealth instead of using a separate structure globally. You could still use the structure inside of your script (perhaps in a different script, where the extenders would actually be defined), but it would make it simpler to integrate your extensions into existing scripts by using extenders instead. Check them out if you're using a 3.x version of AGS!
Dynamic Arrays Are Pointers Too
In addition to the managed types, there are another type of pointer you should be aware of: dynamic arrays. You can create a dynamic array of the base types (such as int) or of pointers to a managed type (such as Character*). For creating a dynamic array of pointers, see Dynamic Array of Pointers.
Unlike the managed types, you use the new keyword to create a new array dynamically. The name you give it is treated as a pointer. The manual gives us this example:
int characterHealth[]; characterHealth = new int[Game.CharacterCount];
Initially, characterHealth, just as with other pointers, holds the value of null. When you assign its value on the second line, you are telling it, as with other pointers, to point to the array that you've newly created.
This is particularly important if you pass a dynamic array as a function parameter. If you change the value of a dynamic array passed as a function parameter, it will change the value of the array itself. Keep in mind that the parameter is pointing to the same array as what you passed into the function. Very useful if it's what you want, but it can be confusing if you're not aware why it's happening.
Closing
So you came to me with questions, and I hope I've answered some of them at least. In any case I hope I answered the ones you had about pointers and their usage in AGS. If you have any questions or comments you can PM me on the AGS forums, or email me at monkey.05.06@gmail.com any time. Thanks for reading my article, and I hope you've enjoyed it as much as I enjoyed writing it.
monkey_05_06
Notes
1. ^ The String type is only defined as of AGS v2.71 and higher. Older versions use the now deprecated string type.
2. ^ The length for Strings is limited by your computer's physical memory. A String will take up 4 bytes of memory, plus 1 byte for each character it contains.
3. ^ {{{2}}}
4. ^ AGS's managed types are those listed here. You cannot create a new managed type within AGS's scripts; to create a new managed type you would need to write a plugin. Some module writers may use the keyword managed to prevent users from creating instances of structs that are meant to be used statically. This does not however make the type managed. Only AGS's built-in managed types and any managed types created via plugins are actually managed, and therefore are the only types that can have pointers to them.
5. ^ Not all of the managed types are meant to have pointers to them. Game, Maths, Parser, and Room do not need pointers (you can't even assign them a value).
6. ^ The asterisk doesn't necessarily have to be attached to the name of the pointer, such as "GUI *MyGUIPointer", it can also be attached to the data type itself, such as "GUI* MyGUIPointer". However, it will still be compiled as if it is attached to the name of the pointer, not the data type, so if you define multiple pointers at once, you will still need an asterisk for each pointer.
7. ^ In AGS you can't create a char*, as char isn't one of AGS's managed types. This type of pointer is used in scripting languages like C and C++. For storing string-literals AGS uses the String type (or the string type for AGS versions prior to 2.71).
8. ^ I have taken the liberty here of envisioning an integral system set up much as AGS 2.7+ is set up, only since it is an integral system it uses integers instead of pointers. In this example AGS structs still have member functions, and all other non-pointer-related functionality of AGS is the same.
9. ^ Structs can only have pointers as members in AGS 2.71 and later.