Scripting, Code & Interaction

From Adventure Game Studio | Wiki
Jump to navigation Jump to search

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

Creating your own variables is easy. At the top of the script, or anywhere BEFORE the variable is first used, declare the variable, using the following syntax:

 vartype varname;


vartype can be String (for strings of alphanumeric text), bool (for values that can be true or false), int (for whole numbers), float (for decimal numbers), an enumerated type, or a user defined type (however, structs cannot be imported unless the struct definition is in the script header).

An example of variable declaration would be:

 int health;
 String MyName;
 bool HasItem;


Initially, an int variable will have a default value of zero (0), a float will have a default value of 0.0, a String will have a null default value, and a bool defaults to false. If these values are OK, then you've successfully declared your first variable. However, you might need your variables to start with a value assigned to them - and that's pretty easy too.

An example of declaring and setting a variable would be:

 int heath = 50;
 float Pi = 3.14;
 String MyName = "Richard";
 bool WearingPants = true;


Note:

  • String values must be inside quotation marks - the following code WILL NOT work:
 String MyName = Richard;
  • Strings that're declared outside a function (e.g. at the top of the Global, or Room, script) CANNOT be declared with a value. If they need to start off as someting other than null, you need to set them in a function (e.g. game_start, or one of the ;Player enters room' interactions)
  • float values must have a decimal place, even if you want it to start as a whole number. The code to use there would be:
 float MyFloat = 3.0;


As seen above, variables are set using =. There are a few other common operators you'll use in handling variables, which are described in the Manual. For example, == means 'is equal to' and is used to compare (not set) values, such as:

 if (health == 0) Display("Oh no! You are dead!");


Or

 if (MyName == "Richard") cNpc.Say("Hey, Ricky!");


!= means 'is not equal to', e.g.:

 if (MyName != "Richard") cNpc.Say("Hey, you're not Ricky!");


And so on. String variables have some dedicated functions, to make handling them easier.


Another important thing to be aware of is the 'scope' of variables. At declaration, variables are local variables. This means they can only be used in the script they were declared in - a variable declared in a Room script can only be used in that room; if it's declared in a Module script it can only be used in that module; if it's declared in a function, or interaction event, it can only be used by that function or interaction. Variables declared inside functions or interactions will be reset everytime the function or interaction is run - however, variables declared in Room, Global or Module scripts stay the same until you change them (e.g. a Room-based variable will still be stored if you leave the room and come back again).

A variable that can be used in all rooms, modules, etc, is said to be a global variable, and here's how you make them.

Firstly, global variables can only be made in the Global script, or in Module scripts. They're declared the same way shown above, but then have to be exported, using the export keyword:

 vartype varname;
 export varname


The manual says the export line should go at the very end of the script - but as long as it comes AFTER the variable declaration, that's not really important.

Next, you have to import the variable to all the rooms you want to use it in. Open the Room script (the '{}' button in the Room Editor) and paste the import at the very top of the script. If you want to use the variable in all rooms, put the import in the Global Headrer (Ctrl-H):

 import vartype varname;


So, if you wanted to make a global variable called 'health', you'd put this at the top of your Global script:

 int health;
 export health;


And this in the Global Header (or Room script):

 import int health;


Note that the import command needs the variable name AND VARIABLE TYPE: export only needs the variable name. Global variables declared in Module scripts should always be imported in the Module Header.

Final Note: This is based on V2.72, users of older versions need to keep a few things in mind.

  • The String type was introduced in V2.71. Before that, you need to use string. (Note the capitalisation.) The string type cannot be set with = - you need to use the StrFormat command, and as with the String type it has to be inside a function (e.g. game_start). Also, strings can't be made global. Use the GlobalString array, or declare them as a char array instead (e.g. char MyName[200]).
  • float, bool and enumerated types were introduced in V2.7.

Running regular code inside 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 within 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 = Game.GlobalString[17];
 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 use Game.GlobalString to store the string in slot #17 into the String we created. Next, Character.Say will be used and the %s is replaced with the string my_name. Got it? You can also do this with int variables, e.g.:

 int my_int = 3;
 String my_string = String.Format("%d", my_int);

Or:

 int my_int = player.InventoryQuantity[iCheese.ID];
 player.Say("I have %d pieces of cheese.", my_int);

(The last one could be shortened to: player.Say("I have %d pieces of cheese.", player.InventoryQuantity[iCheese.ID]);)

NOTE:

  • The Game.GlobalString array was introduced in V2.71. In earlier versions, you'll need to use GetGlobalString
  • Character.Say was introduced in V2.7, earlier versions use DisplaySpeech
  • For more on String formatting codes (like %d and %s), read the manual.

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.

This error can also be thrown if you wrap your string in single-quotes instead of double-quotes. You must use double-quotes in functions like player.Say("Hello World.");

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:

  1. do some action, but return nothing, or
  2. 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.

Moving functions to separate script files

As a game becomes more complex, the code in GlobalScript becomes harder and harder to maintain. So it makes sense to place certain functions into separate script files.

A script consists of two parts: A header file (ending on .ash) and the script code file (ending on .asc). When you add a new script via the Script section of the project tree (right-click -> new script), a pair of these files will automatically be generated.

As an example, let us try move a function doStuff() from GlobalScript to a new script MyScript.

 function doStuff() {
 //doing stuff here
 }

When moving the code of the function from GlobalScript.asc to MyScript.asc, you will most likely get an error when you try to run it. This is due to the fact that the function has not yet been made public to the project. In order to do so, you have to import it in the header file MyScript.ash:

 import function doStuff();
 

If you have some experience in other programming languages, this may be a bit confusing, since the import does not work like #include in C/C++ or import in python. It does not make code visible to your script, but rather makes the function from the script visible to the whole project. Every function not imported that way will only be usable within the script where it is defined.

Note by the way that unlike global variables, functions do not need to be exported (using the export command) in order to work.

So now my function can be accessed from anywhere?

Well, almost. In the project tree, all scripts are organized as a list. Each script can only see functions defined in scripts above them in that list (and of course its own functions). GlobalScript is usually at the bottom of the list, so it can access all other functions, but no other script can access functions in GlobalScript. Room scripts are an exception, as they do not appear in the Scripts list but have access to all other scripts, including GlobalScript.

In our example, doStuff() can now be accessed from GlobalScript, any room script and any script below MyScript in the Scripts list.

So what to do if my script has to call a function defined in GlobalScript?

It cannot call a function in GlobalScript directly. Therefore it would be advisable to move that function to MyScript as well, or place it in a new script file that lies above MyScript.

I tried that, and it works, but when I move a character action to my own script it stops working.

Certain built-in functions (e.g. repeatedly_execute()) can be used in custom scripts and will be called before their counterpart in GlobalScript is called. Most other built-in functions can only be called from GlobalScript, and that includes any character or item-related actions. If you still want to move these functions to separate scripts, you have to use helper functions. As an example, let us try to create a working character interact function for a bartender character in MyScript:

  1. Create a character interact action cBartender_Interact from the character's action menu in the project tree. The corresponding function will be generated in GlobalScript.asc.
  2. Write a new function bartenderInteractHandler() in MyScript.asc to contain the actual action code
  3. Import the function in MyScript.ash to make it visible to the project
  4. Call bartenderInteractHandler() from the cBartender_Interact() function defined in GlobalScript.asc

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.

error: (line xyz): Parse error: unexpected (whatever)

What does this mean, and how can I get rid of it?

That error most usually means you've placed a command outside a function - as such, AGS doesn't know when to run it, and so it's 'unexpected'. In general, the only code allowed outside of a function declaration (e.g. at the very top of a Room script) are variable declarations, e.g.:

 //Room script file
 int DoneStuff;
 String MyString;
 // etc

All other commands must be inside a function - be it one you've created yourself, or a room' Interaction function ('Player enters room before fadein', 'Player leaves room', etc). See also the earlier entries on placing code and creating functions.

Turning Objects/Hotspots/Regions On or Off in another room.

I can't seem to find in the manual how to turn objects etc, on and off from another room. Is there just not a command for this or am I going insane?

No, you're not going insane - there's no direct way to do this. You'll have to use variables (GlobalInts or your own - see the earlier entry on working with variables), and check them in the 'Player enters Room' interaction of the room the Object (or whatever) is in. If you're likely to need to do it a lot you might want to try the OtherRoom module, which handles everything for you.

But what about character actions? They are not tied to a room and I can't use variables here.

If you know that the character is in a specific room, you can access the room's objects/hotspots/regions with appropriate variables and the corresponding index of the item you want to access:

 if (player.Room == 3)
 {
   object[0].Visible = true; //make object with ID 0 visible
   hotspot[4].Enabled = false; // enable hotspot with ID 4
   region[7].Enabled = true; // enable region with ID 7
 }

Note that you are entering dangerous terrain by doing this. If you remove an object from a room, all subsequent objects will move up in the list, causing their ID to decrease by 1. If you don't adapt your ID in the object[] call, you will get some weird bugs.

Avoid another trap by making absolutely sure that the character is really in that room when you access the objects. If not and there is no hotspot with ID 4 in the current room, the game will crash.