AGS Pointers for Dummies

From Adventure Game Studio
Revision as of 18:38, 17 October 2006 by *>Monkey 05 06
Jump to navigation Jump to search
http://img242.imageshack.us/img242/6929/pointerdummieswm0.gif
AGS Pointers for Dummies
A reference for the rest of us!

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, String1, 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 virtually2 infinite length. A float-type variable can store floating-point decimals within the range -2147483648.0 to 2147483647.0, and has precision3 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 here.

1The String type is only defined as of AGS v2.71 and higher. Older versions use the now deprecated string type.

2The 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.

3Floating-point decimals won't always evaluate as you might expect when doing certain mathematical operations (this is due to their precision levels). See the manual entry on data types for more information.

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.

Managed Types

AGS has certain managed types that you can not create an instance (variable declaration) of, but you can create pointers to4. All of the variables of managed types are managed by AGS. These include the types: Room, Game, Parser, File, InventoryItem, Overlay, DynamicSprite, GUI, Label, Button, Slider, TextBox, InvWindow, ListBox, Character, GUIControl, Hotspot, Region, Maths, DateTime, and Object.

4Not all of the managed types are meant to have pointers to them. Room, Game, Parser, and Maths do not need pointers (you can't even assign them a value).

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 asterik (*)5, 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.

5The asterik 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 asterik for each pointer.

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.

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 asterik 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 asterik 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 asteriks.

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 (which is equivalent to 0)6. 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 */ }

6Though null is equivalent to zero, in most cases you should use the keyword null in place of its literal value.

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).

7In 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).

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 GUI8. 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.

8I 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.

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? Remember how I mentioned before that null is equivalent to 0? In most cases you should use null to check for null pointers instead of using 0, however, you can't assign a pointer a default value of null. You can however set a default value of 0.

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 structs.

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 struct9, which provides some interesting possibilities. With a pointer as a member, you can essentially extend built-in datatypes (i.e., the managed types).

9Structs can only have pointers as members in AGS 2.71 and later.

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.

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