Pointers in AGS

Various commands in the scripting language will require you to use pointers. This section has been split into three separate topics to introduce you to pointers depending on your previous programming experience.


Pointers for programming newbies

Pointers can be quite a daunting prospect, and in languages like C and C++ they certainly are; but AGS tries to make things as simple as possible.

Basically, a pointer is a variable that points to something of a particular type. For example, a Character pointer would point to a character.

Think of a pointer as of a roadsign with arrow that tells you direction to another place. The pointer itself is not the object, but it lets you use an object through itself. You may reassign a pointer anytime and make it point to another object. This would be like turning roadsign and making it direct to another place: the sign will be the same, but the place it shows is different. Similarily, the pointer variable stays, but referring to a different object.

What's the point of having pointers? There are few common reasons why they are used.

The main purpose of the pointer could sound a bit too technical, but it's still good to know. In programming languages, when you use a value somewhere, would it be assigning to a variable, or passing into a function, or returning it from function, that value gets copied. If it's something simple, like few integers, that's not a big deal. But if it's an object with many properties stored inside, many such operations could slow program down. Furthermore, because it's a copy, it becomes impossible to do work on an object inside a function, and then use modified object further. Any changes applied in a function would be done over a copy rather than the original object. There are various methods to overcome this problem, and pointers could be the most straightforward of them. With pointers only "direction to object" is copied, but not the object itself.

Then, pointers are more direct way to use objects, read and set their properties. For example, if there's an array of objects, like character[] is an array of all Characters in your game, then to address a particular character you'd have to use this array and a character index, like this:

character[4].Walk(100, 100);

Because using numbers like this may be inconvenient for various reasons, and easier to forget what they mean, you could create an integer variable to store that index there int MY_FRIEND = 4;, or define a named constant #define MY_FRIEND 4, then use it like:

character[MY_FRIEND].Walk(100, 100);

That seem more explicit, but still looks bit complicated. On another hand you could create a pointer Character* cMyFriend; and assign it to character from array at the start of the game: cMyFriend = character[4];, and then use like:

cMyFriend.Walk(100, 100);

Not only you do not need to refer to array anymore, but you now only use one "symbol" to refer to the character (the pointer) instead of two or more symbols (array, index, and so on).

NOTE: the above is just an example of concept, and in practice AGS already generates such pointer for each character and other object you create for your game. The script names you give to your characters are used to name these pointers, for example if your character is called cPlayerChar, then there will be a pointer Character* cPlayerChar autogenerated for you that you can use in scripts.

Another reason is that pointers have the type of the object they are pointing to, and type checking in AGS script makes their use more reliable than use of an integer index, for instance, which does not tell what kind of object its referring to. For example, if you have int door_id; containing a hotspot's index, you could by mistake use it in a function expecting room object's index, and program won't complain. Finding such mistakes could be difficult. On the other hand, if you have Hotspot* hDoor; and you try to pass it into a function expecting Object*, script compiler will display and error, letting you notice your mistake early.

How to use pointers?

Let's look at an example. If you want to write a string to a file you do this:

File* file = File.Open("temp.txt", eFileWrite);
file.WriteString("Test!");
file.Close();

Here File.Open returns a pointer to a "file object", which is then used to do file operations. Looks fairly simple, the only slightly confusing part is getting used to declaring the variable as File* with an asterisk; but that's something you'll get used to quite quickly, and all the examples in the manual should point you in the right direction.

Let's look at another example. Suppose you want a variable that contains the current hotspot that the mouse is over:

// top of global script
Hotspot *mouseOverHotspot;

// repeatedly_execute
mouseOverHotspot = Hotspot.GetAtScreenXY(mouse.x, mouse.y);

Above script declares a pointer to Hotspot and assigns it to a hotspot found under mouse on each game tick.

Now, what if you want to know whether the mouse is over your Door hotspot:

if (mouseOverHotspot == hDoor) {
    Display("Mouse over the door");
}

Above compares values of two pointers, this will only be successful if both point to same object.

If you'd like or have to use an index from hotspots array instead, you can still use it like this:

if (mouseOverHotspot == hotspot[2]) {
    Display("Mouse over the door");
}

IMPORTANT: So far so good, but there's still one thing that must be told, and that one is a bit of a downer. Unlike other variables, which always have some data, pointers may have a state where they do not point to any object. This kind of value is defined by a keyword null, and pointers in this state are called "null pointer". The problem with "null pointers" is that trying to use object's data or functions with them would result in program error. Good news is that you may find this out, comparing them to null. If you have a custom pointer variable, which in theory (according to your script's logic) may happen to not be assigned, then it's best to check if it's null or not before using it.

Suppose you want to find a character under a mouse and display their name on a GUI label:

Character* chUnderMouse = Character.GetAtScreenXY(mouse.x, mouse.y);
lblCharacterName.Text = chUnderMouse.Name;

What would happen if there's no character under mouse? Character.GetAtScreenXY will return "null" value, it gets assigned to chUnderMouse, and as soon as program tries to get character's name, it will throw an error, because it cannot get Name from "nothing". This is how we prevent this:

Character* chUnderMouse = Character.GetAtScreenXY(mouse.x, mouse.y);
if (chUnderMouse != null) {
    lblCharacterName.Text = chUnderMouse.Name;
}

So, that concludes our introduction to pointers. Hopefully you've got an understanding of what they are and what they do; if there's anything you can't work out, feel free to ask on the Technical forums.


Pointers for people who know Java or C#

AGS pointers work in a very similar way to object variables in Java and C#. The main difference is that AGS pointers are declared in the C-style manner with an asterisk t represent the pointer. So:

Hotspot *hs;

would declare a variable hs which points to a Hotspot. This would be equivalent to the following in Java or C#:

Hotspot hs;

In AGS, pointers are used to point to various built-in types, such as Hotspots, Inventory Items, Characters and so on, or custom user structs declared with managed keyword, but not regular structs.

There are no proper constructors in AGS. User managed types are created with new keyword and have all of their members initialized to zero, built-in types are either found in autogenerated variables and arrays, or returned by static member functions, like Overlay.CreateGraphical.

You use pointers in the same way as you would in Java and C#. Pointer memory management in AGS is all automatic -- the memory is freed when there are no longer any variables pointing to the instance. Thus, if you have global pointer variables in your global script, it's a good idea to set them to null when you're no longer using them, to allow the memory to be freed.

If you attempt to call a method on a null pointer, an error will occur (just like you'd get an exception in Java or C#).

Various built-in AGS static methods return a pointer to an instance (for example, File.Open, Hotspot.GetAtScreenXY, and so on). You can save this pointer into a pointer variable, and then call its methods as you would in Java or C#. The following examples are all valid:

File *theFile = File.Open("test.dat", eFileWrite);
if (theFile == null) Display("It's null!");
File *file2 = theFile;
if (theFile == file2) Display("They're the same file!");
theFile = null;
file2.WriteInt(10);
file2.Close();

Pointers for people who know C or C++

For historical reasons pointers in AGS are declared similar to the C/C++ syntax, with an asterisk, but their members are accessed with the dot operator, and not the -> C-style operator. Because AGS didn't support features such as pointers-to-pointers and so forth, there was no need for a separate operator.

Pointers in AGS are more comparable to "smart pointers" in modern C++, not "raw" C-style pointers that would store address to real memory. They may only point to "managed" structs, whether built-in or managed types declared by user. In this way they are more "handles" than pointers.

Pointer memory management is done automatically based on reference counting, so there's no delete keyword. When an object is no longer referenced by any pointer variables, it will be freed. For this reason, if you have any global pointer variables it's advisable to set them to null if you are done with them.

AGS pointers are strongly typed, and you cannot cast between types at will like you can in C and C++. AGS will only allow you to compare and assign pointers of the same type, or of the same base type. There is a special keyword null (similar to nullptr in C++) which all pointers can be set to and compared with, which indicates that they are unassigned.

There are no proper constructors in AGS. User managed types are created with new keyword and have all of their members initialized to zero, built-in types are either found in autogenerated variables and arrays, or returned by static member functions, such as File.Open and Hotspot.GetAtScreenXY. See the examples for the functions to get an idea of how to use them.