Scripting, Code & Interaction
Working with variables: the basics
I need help with variables. What I am doing doesn't seem to be working. What do I do?
One of AGS's main advantages is its ability to use variables. A variale is an area of memory storage that contains a value, which you can check and change with scripting. Variables are very powerful and useful things in programming languages. If you have an RPG-style stat system in place, you can use variables to store information such as health, magic, strength, etc. You can name variables anything you'd like, as long as they're not names of functions, AGS commands, or already-declared variables, like EGO or a GUI name.
Due to AGS's emulation of the 'C' language, variable scope must also be considered. If a variable will be used throughout the entire game, it will have 'global' scope. If it to be used only in a single room, its scope will be limited to that room.
There are three major steps to using custom variables.
Step 1. If the variable will have global scope (such as health, money, etc.), go to the global script. If the variable will have room scope (such as light_is_on or guard_random_state), go to the room's main script (the button with the braces - { }). Then, at the very beginning before any functions, declare and set the variable, using the following syntax:
vartype varname; varname = value;
OR
vartype varname = value;
vartype can be string (for strings of alphanumeric text, it's advisible to use the new String type if you're using AGS V2.71 or above), bool (for values that can be true or false, supported from AGS V2.7 onwards), int (for whole numbers), float (for decimal numbers, supported from AGS V2.7 onwards), an enumerated type (supported from AGS V2.7 onwards), or a user defined type (however, structs cannot be imported unless the struct definition is in the script header).
An example of an int declaration would be:
int health; health = 100;
OR
int health = 100;
Initially, an int variable will have a default value of zero (0). Therefore, if a variable starts off with '0' as its value, you do not need to assign a value like above.
A string would be declared and set like so:
string myname; StrCopy(myname,"Richard");
OR, from V2.71 onwards:
String mynewname = "Richard";
If you're using V2.7 or earlier, you may NOT use direct assignment of string. In other words, you CANNOT use the code myname="Richard"; as this will cause an error with AGS. Use the above code to correctly assign strings to string variables.
If you are using global variables you must do the following.
Step 2. At the end of the global script (or just after all the variable declarations), you must export any and all variables you create, like so:
export health; export myname;
Step 3. For any and all global variables (whether int, string, String or float), you must import them into all rooms in which they will be used. So, in each and every room where you will be using global variables, go into the room's main script (the { } button), and at the very top -- before anything else -- import the variable(s) like so:
import int health; import string myname;
The last two steps are necessary with global variables. Remember! ONLY the import command requires the data type as well as the variable name. The export command ONLY needs the variable name.
Running regular code outside dialog
I need to run some code in my dialog script that isn't in the form of a dialog command! I suppose I need to run regular AGS script from within the dialog script. What do I do?
Well, there's this little thing called run-script X. First off, create (or edit, if it's there already) a function within the global script called dialog_request (int X). You can name X whatever you'd like, as it really doesn't matter. Next, inside that function, you will check to see what value X is. When you run the run-script X command, whatever is in place of the variable X will be passed along to the dialog_request function. So, you must then check the value of the integer variable within the dialog_request function, so that the right script will be called. Let's say that "run-script 4" was called. Within the dialog_request function, there will be a set of if...else if...else if... commands. When it comes around to cheecking to see if the variable passed was 4, then whatever is put down afterward until the next "else if" command will run.
Here is an example to get you started. If, in the dialog script, you had the command run-script 5, you would need the following in your Global Script...
function dialog_request(int blorg) { if (blorg==1) { // Put whatever goes here if "run-script 1" is run } else if (blorg==2) { // Put whatever goes here if "run-script 2" is run } else if (blorg==3) { // Put whatever goes here if "run-script 3" is run } else if (blorg==4) { // Put whatever goes here if "run-script 4" is run } else if (blorg==5) { // BINGO! This code will be run when "run-script 5" is called } }
Do you see now? When the function dialog_request() is run, with "5" being passed as the only parameter, only the right code will run, because you added an "if/else if" check within the function. This way, only that code will run if "5" was passed as the parameter.
Get it? Got it? Good! :)
Placing your code: more basics
I'm new to this scripting thing. (It's okay, we all gotta start somewhere!) Where exactly would I put code to move a character around or add an inventory item?
First, you need to think of where and when you want this to happen. Do you want this to happen the minute the game starts, before any rooms are loaded? If so, edit the global script (Game -> Edit global script..., or Ctrl-G) and find the definiton for the "game_start" function. It will probably look like:
function game_start() {
Between the braces ( { } ), insert your code that will run the second the game loads. Do not put animation code there, as no characters have yet been drawn at that point. Only run animations when a room is loaded, and usually only after fade-in.
For things that you want to happen every time a room is loaded, choose that room in the Rooms pane and, under Room/Settings, Choose the “i” button. You will see a list of events that you can use to trigger certain events. If you don't want to use code, most functions and commands are available as interaction commands. Simply double-click the event you wish to use, OR right-click and choose Add action.... Then, in the drop-down list box, choose what you want to have happen. Note: You can indeed have more than one command running when that event occurs. They will run one after the other.
For example, if you want the player to walk to a certain location after the room is loaded (each and every time), double-click on the Player enters room (after fade-in) event. You want after fade-in, because you won't see any animations or anything at all before the room fades in. Now, select the Character - Move character command, and choose the parameters below the drop-down box.
As you get to know AGS a little better, you will see that there are many interactions... some for inventory items, objects, hotspots, even characters!
Fatal error when running "function DoSomething(1,2,3);"
When I write "function DoSomething(1,2,3);", it gives me a fatal error! What am I doing wrong now?
If you intended to use an already-existing function in AGS, then you need only write "FunctionName(1,2,3);". Don't forget the semi-colon!
The function keyword is used only while defining functions, i.e., creating your own. In this case, you would type
function FunctionName (type varname, type varname2, ...) {
followed by your code and an ending brace ( } ), into the global script, outside of any other functions. The word type refers to the variable type, such as int, string, or even char (not supported by AGS, but used in the C++ coding language, so look it up there), while varname, varname2, etc., refer to the names of the variables you want to have passed to your function. Then, go to the script header (under the Game menu), and add "import FunctionName (type,type,...);". You need only list the types of variables used, separated by commas, such as (int,string).
Using "FaceLocation" doesn't work
Whenever I use FaceLocation(...) to face my character, it doesn't work! He doesn't face where I want him to face! Damn him!
FaceLocation() will only allow a character to face directions on which (s)he can actually walk. So if you point to a non-walkable-area, then you're out of luck.
Also, the change in character direction will only be visible when the screen is refreshed (when a gameloop is advanced). If you want the character to change his direction fully and quickly (e.g., making a turn-around movement, place several FaceLocation() commands in a row (or use it wthin a while() loop, etc.) and make sure you put a Wait(1); line after each of these commands to ensure the screen is updated after each turn.
Using "GetMP3posMillis()" always returns "0"
Hey! Why does GetMP3posMillis() always return "0" no matter what? I KNOW my song is playing in AGS.
If the music is disabled, or there is no sound card, GetMP3posMillis() will always return "0". So enable music and buy a damn sound card. :)
Fatal error when using "=" and "==" with strings
Oy! How come AGS gives me hell when I write stringname = "blah" or use the conditional "==" with "if" statements and strings?
It has something to do with the fact that "stringname" is actually a pointer, not a variable, so you'd have to use the AGS command StrCopy(). Look it up in the manual. If you're using V2.71 or above, however, you can use the "=" operator to assign the content of a String object.
If you're checking the contents of a string with "if" statements and the like, don't use the "==" operator. Instead, use StrComp() or StrCaseComp. Look those up in the manual as well. If you're using V2.71 or above, however, you can use the "==" operator to check the content of a String object.
Inserting variables into speech/messages
Hey, how would one go about inserting a variable (either integer or string) into a message (text-boxed or speech) using code? For example, I want to display how many gold coins the player has using the Display() command.
Simple! when using any of the Display...() commands, inside the string that contains the message you must insert either a %d (for integers) or %s (for strings) and %f (for floating point numbers, only available for AGS V2.7 or above). This is a placeholder; it holds the place for the string or number to be inserted. After the string, instead of closing off the command with an ending parenthesis, add a comma, then the variable you want to insert into the message.
For example, to display how many coins (let's say it's inventory item #24) the player has. You would insert the following code:
Display("You currently have %d gold coins in your purse.", player.inv[24]);
This could also be done with strings. For example, if the player's name is stored in the global string #17, use the following code if you want BOB to call your character by name:
string my_name; GetGlobalString(17, my_name); DisplaySpeech(BOB, "Hey, %s! What's goin' down, my man?", my_name);
Or, for AGS V2.7 and above:
string my_name; GetGlobalString(17, my_name); cBob.Say("Hey, %s! What's goin' down, my man?", my_name);
Confused? First, we declare a string and name it my_name. Next, we call the GetGlobalString command to store the string in slot #17 into the string we created. Next, DisplaySpeech (or Say for V2.7+) will be used and the %s is replaced with the string my_name. Got it?
GlobalInts, your friend: what they are and how to use them
What are GlobalInts and how would I use them?
GlobalInts, or Global Integers, are a set of 100 variables that can be accessed anywhere in the game using script. You may set a globalint with SetGlobalint() and get the value of one with GetGlobalInt().
SetGlobalInt(int variablenumber, int value);
Example: SetGlobalInt(47, 20); will set global int #47 with a value of 20.
GetGlobalInt(int variablenumber);
Example: GetGlobalInt(47); will return the value stored in global int #47. Note that this is not a complete command. Treat this like you would with a variable: set it to something, or display it somehow. Use it as the object of a command, in other words.
To alter the value of a globalint, use functions ONLY. Don't use operators like ++ and += to increment or decrement the value of the global int. Use the following:
SetGlobalInt(47, GetGlobalInt(47) + 1); // adds 1 to the global int #47 SetGlobalInt(47, GetGlobalInt(47) - 1); // subtracts 1 from global int #47 SetGlobalInt(47, GetGlobalInt(47) + 12); // adds 12 to the global int #47
Keep in mind that globalints are just like any other int variable. There are also GlobalStrings. Look 'em up. :)
Error: "(line xyz): incorrectly terminated character constant"
Every time I run my game I get the error message "Error (line xyz): incorrectly terminated character constant". I checked line XYZ and I can't see a thing wrong with the script. I don't use any foreign characters using ASCII value above 128. What's wrong?
Check to see that the text in your command on that line (or near that line) does not use more than 200 characters at a time in a string. Examples include Display() commands and derivatives thereof. Split long lines up into multple lines.
AGS 2.71 has support for longer strings using the String datatype, but some parts of AGS still have the 200 character limit, so beware.
Slowing down your loops
I have a loop that I can't seem to get to run any slower than it does. For example, my incrementing variable within the loop only increments by 1, and I can't use a lower value. How else can I slow the loop down?
First off, there should already be a Wait(); statement in your loop during each iteration. And to make the loop run slower, simply make the value inside Wait() higher. The higher it is, the longer it will delay. Simple as that!
You can, however, now use float datatypes, so you can make your loop variable increment by whatever fraction you wish.
Creating your own custom functions
How do I create my own custom function to use in my game's scripting? I keep getting errors. What am I doing wrong?
First, decide on a function name. Perhaps you want a function to display a message that says "The number is", followed by a number that you feed the function. Remember that a function can either:
- do some action, but return nothing, or
- do an action, and return a value.
If the function will return a value, it HAS to be an integer value (note: from AGS V2.7 onwards, you may return stuff other than integers, however, this won't be covered here as they're no long beginner information) . Also, even if you decide not to return anythng, keep in mind that sometimes it is a good idea to return something. For example, the DisplaySpeechBackground() (cEgo.SayBackground() for AGS V2.7 and above) function returns a value, even though it is not needed. But it is useful. (It returns an overlay ID, in case you wanted to know, and for AGS V2.7+, it actually returns an Overlay pointer.)
Next, in the game's global script, add the function by itself like so:
function FunctionName(int parametername) { }
Keep in mind that you can have more than one parameter fed to your function, including strings, ints, chars, and any other data type supported by the AGS script system. Next, add the actions that will run when the function is called.
function DisplayNumber(int number) { Display("The number is %d", number); }
In the above example, when called, DisplayNumber() will display "The number is", followed by the number sent to the function, which is called, interestingly enough, number. You can call parameter variable names anything you want. They are declared by the function inside the parentheses ( () ). Next, you have to import your function into the script header. Make sure that you do this correctly. There are two ways to do this:
import DisplayNumber (int);
OR
import DisplayNumber (int number);
Either way will work just fine (but the second way is more handy since you don't need to guess what the parameters stand for using the auto-complete feature of the script editor). Now, in any room's script, or even in the global script below the newly declared function, you can call up the function like so:
... DisplayNumber(57); ...
Finally, if you want to return a value, use the return command at the very end of the declared function, like so:
function Sum(int numberone, int numbertwo) { return numberone + numbertwo; }
The function will now return the value obtained when numberone and numbertwo are added together. Using the function...
int x = Sum(24,61);
...will set x 's value to 85 (the sum of 24 and 61, as calculated in the function). That's all there is to it!
Note that if you create a function using the keyword function then it can only return integer values. Bools and enumerated values (AGS 2.7+) have integer equivalents, as do chars and shorts. But if you want to return a pointer (such as a Character* or a String) then you have to replace function with the return type! So, to return these types you should set up your functions like this:
return_type func_name([param_type_1 param_1 [, param_type_2 param_2 [, ...]]])
And just in case you're confused about the '[' and ']' in there, that just means that it's something you don't have to include. Your functions don't have to have any parameters, and they can have up to 9.
Defining custom hotkeys and shortcuts
How can I define a hotkey (keyboard shortcut) in my game?
Quite simply, open up the Global Script's on_key_press function (Script -> on_key_press) and insert conditional ("if") statements there to check which key(s) were pressed. Search in the manual for ASCII code table (found under "Reference" category), and you will see a table of all the ASCII key codes that can be detected in AGS.
If you do not know how to use if statements within the function to check for key codes, then please learn the basics of scripting first, also found in the AGS manual.
Having a character continuously animated in the background
I would like to have my character continuously animating in the background, but if I loop it continuously, I get errors and/or strange things happen. Or, I just can't get it to work! Help!
Ooooh, sorry, that wasn't in the form of a question! ;)
Heh just kidding. You can use the character's idle animation to do this. Simply set up his/her idle animation (SetCharacterIdle(), or cEgo.SetIdleView() for AGS V2.7 and above) with the idle delay to "0" so that it plays constantly. Voila! You now have a repeatedly animating background character with only one line of script. If you wanted, for example, a character in the background to do a one-time animation randomly with pauses between, set the idle delay to a higher number. The higher the idle delay, the longer between idle animations AGS will wait.
As an alternative to this method (which destroys the idle view delay) you can do this instead:
// in repeatedly_execute_always // AGS 2.7 and higher if (Character.SpeechView > 0) { Character.LockView(Character.SpeechView); if (!Character.Animating) Character.Animate(Character.Loop, Character.AnimationSpeed, eRepeat, eNoBlock, eForwards); } // AGS 2.62 and lower if (character[CHARACTER].talkview > 0) { SetCharacterView(CHARACTER, character[CHARACTER].talkview); if (!character[CHARACTER].animating) AnimateCharacterEx(CHARACTER, character[CHARACTER].loop, character[CHARACTER].animspeed, 1, 0, 0); }
Of course you would replace:
(AGS 2.7 and higher)
Character with the script o-name of the character, character[ID] where ID is the ID of the character, or a pointer to a Character.
(AGS 2.62 and lower)
CHARACTER with the script name of the character.
So if you use the idle view and have a delay set for the animation, this is the safer alternative.
Taking away score points.
How would I have my character lose points (say, if using the score as money)? Is there a script function for this?
There is a much easier way to do this than through scripting. Just use the "gain points" interaction in the Interaction Editor. Place it wherever you want this action to occur. Just put in a negative number to decrease the score.
However, if you must use script, check out the GiveScore() score, using a negative number as a parameter there as well.
Changing the names of characters, hotspots, objects, and inventory items in the middle of gameplay
How would I go about changing the names of characters, hotspots, objects, and inventory items in the middle of gameplay?
For those who don't know, this is a very useful way of altering what the player character "knows". For example, if you meet a character for the first time, in your status bar it might just say "maintenance worker". Then, once you talk to him and find out his name, you may want the status text to say "Crewman Johnson".
To change the character's name, you will have to alter the global character[CHARNAME].name variable. Since this is a string, you cannot simply set its value using the equals ("=") sign. You will have to use the StrCopy() function like so:
StrCopy(character[WORKER].name, "Crewman Johnson");
in AGS 2.71 or later, you CAN use =, so:
cWorker.Name="Crewman Johnson";
For inventory names, just use the SetInvItemName() (or iCup.SetName() for AGS V2.7 and above) function like so:
SetInvItemName(4, "cup of tea");
OR, if you're using V2.7:
iCup.SetName("cup of tea");
OR in 2.71+:
iCup.Name="cup of tea";
As for hotspots and objects, it isn't currently possible to change their names during gameplay. That may change in future versions, however.
How to play movies and video files (AVI, MPG, WMV, etc.)
How do I play movies (video files) in my AGS game?
If you look up the PlayMovie() function in the AGS manual, all will be revealed.
AGS Pointers for Dummies (A reference for the rest of us!)
So what exactly are pointers, and how do I use them?
The basic idea of a pointer is that instead of creating a new variable in the memory, you are just going to point to one that is already stored there. This can have several uses, and in AGS even has some special ones.
AGS has certain managed types that you can not create an instance (variable declaration) of. 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.
Note that not 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).
However, you can work with these managed types through pointers. You define a pointer by typing the variable type, then a space, an asterik (*), and finally the name of the pointer. So, to create a pointer named GUIPointer to point to the GUI object named gMygui, you could type:
GUI *GUIPointer = gMygui;
Note that if the pointer is global (outside of all functions), then you cannot assign it an initial value.
Also note that if you are defining multiple pointers at once, you have to place an asterik before the name of each pointer.
Furthermore, you should take note that the asterik can actually be placed beside the variable type (i.e., GUI* GUIPointer instead of GUI *GUIPointer), but it will always be parsed as being attached to the variable (i.e., GUI *GUIPointer).
Of course this would be a rather pointless assignment, unless you just wanted a new alias for your GUI. A more useful assignment makes use of the function GUI.GetAtScreenXY:
GUI *GUIUnderMouse = GUI.GetAtScreenXY(mouse.x, mouse.y);
As implied by the name of the pointer, this pointer will point to the GUI that the mouse is over. This brings to point an interesting question though. What if there is no GUI under the mouse? Well, in that case, GUIUnderMouse would be set to "null", which means that it isn't pointing to anything.
If a pointer is null, then basically all you can do with the pointer is make it point to something (assign it a value as in the examples), and test it against other pointers (or objects) of the same type. We've already seen how to assign a value to a pointer, so let's see how we can compare two pointers. Let's take the following example:
GUI *GUIUnderMouse = GUI.GetAtScreenXY(mouse.x, mouse.y); if (GUIUnderMouse != null) { if (GUIUnderMouse == gMygui) { Display("MYGUI is under the mouse!"); } }
First we assign the pointer to hold the value of the GUI under the mouse as we did before. Then we test whether it is null with the statement "if (GUIUnderMouse != null)" which reads as "GUIUnderMouse is not equal to null." If GUIUnderMouse was equal to null, then it wouldn't be pointing to anything, so we don't want to work with it. Next we test if the GUI is MYGUI with the statement "if (GUIUnderMouse == gMygui)". If the GUI under the mouse was gMygui, then they will be equal, and the statement will pass as true and the statement will be displayed (avoid doing this repeatedly or else it could be a hassle to return back to the game).
Okay, so we can create, assign, and test pointers, but what do they DO?
Well, we've already discussed that they point to variables stored in the memory, but it's an interesting question as to how this can be useful. Let's take for example a built-in pointer, InventoryItem* Character.ActiveInventory. This is a pointer to an InventoryItem; the active inventory item for the Character who owns the property.
What it does is allows the user to operate on the active item without having to know it's integral value, or even what it is for that matter. For example, if you wanted to change the item's graphic, with an integral system (with no pointers) you would have to do something like this:
inventory[character[GetPlayerCharacter()].activeinv].Graphic = 42;
However, in a system with pointers, you can type this instead:
player.ActiveInventory.Graphic = 42;
So in addition to shortening the code, it also makes it easier to read. The player keyword is a pointer to the player character (Character*) and ActiveInventory is a pointer to the player's active inventory item (InventoryItem*).
Wait...what's with all this script o-name stuff?
If you're reading this, you probably didn't ask that, so I asked it for you, because it pertains to this...
Script o-names are essentially just pointers. So, if you create GUI #5 and name it INVENTORY, then AGS automatically assigns it the script o-name, gInventory. Basically gInventory will be defined internally as this:
// pseudo-internal-AGS-code GUI *gInventory = gui[5];
It is a pointer to GUI #5, which is accessed globally by the gui array. Now for all I know the gui array could be just an array of pointers to something which is managed further inside the bowels of AGS, but it wouldn't really make a difference as they would still both be pointing to the same variable in the end.
Okay, did you just say something about an array of pointers?
Well, I'm not going to go into what an array is (look it up in the manual), but yes, you can create an array of pointers. You do this the same way you would create any other array. So, if you wanted to create your own array of pointers to the first five GUI objects, you could do something like this:
GUI* MyGUIArray[5]; // game_start int i = 0; while (i < 5) { MyGUIArray[i] = gui[i]; i++; }
Note that if you have less than 5 GUIs in your game, this WILL crash it.
*** NEW AS OF 3 JANUARY 2006 ***
I forgot to mention, that you can even place a pointer inside of a struct. This can be another GREAT use for pointers in AGS. You define it the same way you define any other variable in a struct:
struct StructNameHere { vartype1 var1; vartype2 var2; TypeToPointTo *PointerToType; };
Then, you use it like a normal pointer (just as a member of a variable of the new type). For example, suppose you want to store some new information about a character, say RPG statistics. You could use a pointer like this:
struct CharStats { int HP; int MP; int MaxHP; int MaxMP; Character* Char; }; CharStats stEgo; // game_start stEgo.MaxHP = 100; stEgo.MaxMP = 50; stEgo.HP = 100; stEgo.MP = 50; stEgo.Char = cEgo;
If you know how to use structs, then you will already understand what the integer values are for (if you don't check the manual), but what about this Character* (pointer to Character)? By storing this information, you can easily use stEgo in place of cEgo. Where you would normally type something like cEgo.Animate, you would simply type stEgo.Char.Animate.
This is one of the most useful reasons for pointers as, to an extent, it can allow you to extend built-in types.
And just to bring it all together, yes, you can even combine arrays, pointers, and structs all in one:
struct CharStats { int HP[50]; int MP[50]; int MaxHP[50]; int MaxMP[50]; Character* Char[50]; };
Then, just by creating a variable of this type, you can create 50 new character-statistic variables at once!
Now then, I hope this helps everyone to understand pointers in AGS a little bit better.