Scripting, Code & Interaction: Difference between revisions

mNo edit summary
 
(48 intermediate revisions by 16 users not shown)
Line 1: Line 1:
==Working with variables: the basics==
==Working with variables: the basics==
''I need help with variables. What I am doing doesn't seem to be working. What do I do?''
''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.
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;


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.
'''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 [http://www.bigbluecup.com/manual/enum.htm enumerated type], or a [http://www.bigbluecup.com/manual/struct.htm user defined type] (however, structs cannot be imported unless the struct definition is in the script header).


'''''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:
An example of variable declaration would be:


   '''vartype''' varname;
   '''int''' health;
   varname '''=''' value;
   '''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 [http://www.bigbluecup.com/manual/Operators.htm 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.:


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


  '''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), '''int''' (for whole numbers), or '''float''' (for decimal numbers, supported from AGS V2.7 onwards).
And so on. String variables have some [http://www.bigbluecup.com/manual/String%20functions.htm dedicated functions], to make handling them easier.
An example of an '''int''' declaration would be:


  '''int''' health;
  health '''=''' 100;


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


  '''int''' health '''=''' 100;
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.


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


A '''string''' would be declared and set like so:
  '''vartype''' varname;
  '''export''' varname


  '''string''' myname;
  '''StrCopy('''myname,"Richard"''')''';


OR, from V2.71 onwards:
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.


  '''String''' mynewname '''=''' "Richard";
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):


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.
  '''import vartype''' varname;


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:
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;
   '''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:
 
And this in the Global Header (or Room script):


   '''import int''' health;
   '''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==
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?''
''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?''


Line 110: Line 151:
'''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.
'''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.
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"==
==Using "GetMP3posMillis()" always returns "0"==
Line 116: Line 157:


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. :)
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. :)
WARNING ! This function is now deprecated.
Use '''AudioChannel.Seek''' , instead


==Fatal error when using "=" and "==" with strings==
==Fatal error when using "=" and "==" with strings==
Line 135: Line 180:
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:
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;
   String my_name = Game.GlobalString[17];
  GetGlobalString(17, my_name);
   cBob.Say("Hey, '''%s'''! What's goin' down, my man?", my_name);
   DisplaySpeech(BOB, "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, for AGS V2.7 and above:
Or:


   string my_name;
   int my_int = player.InventoryQuantity[iCheese.ID];
   GetGlobalString(17, my_name);
   player.Say("I have '''%d''' pieces of cheese.", my_int);
  cBob.Say("Hey, '''%s'''! What's goin' down, my man?", my_name);
 
(The last one could be shortened to: ''player.Say("I have '''%d''' pieces of cheese.", player.InventoryQuantity[iCheese.ID]);'')


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?
'''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''), [http://www.bigbluecup.com/manual/StringFormats.htm read the manual].


==GlobalInts, your friend: what they are and how to use them==
==GlobalInts, your friend: what they are and how to use them==
Line 174: Line 228:


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.
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==
==Slowing down your loops==
Line 226: Line 282:


...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!
...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:
# 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.
# Write a new function bartenderInteractHandler() in MyScript.asc to contain the actual action code
# Import the function in MyScript.ash to make it visible to the project
# Call bartenderInteractHandler() from the cBartender_Interact() function defined in GlobalScript.asc


==Defining custom hotkeys and shortcuts==
==Defining custom hotkeys and shortcuts==
Line 239: Line 343:
Ooooh, sorry, that wasn't in the form of a question! ;)
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.
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.==
==Taking away score points.==
Line 280: Line 415:
If you look up the '''PlayMovie()''' function in the AGS manual, all will be revealed.
If you look up the '''PlayMovie()''' function in the AGS manual, all will be revealed.


[[Category:AGS Beginners' FAQ]]
==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 [[Scripting%2C_Code_%26_Interaction#Placing_your_code:_more_basics|placing code]] and [[Scripting%2C_Code_%26_Interaction#Creating_your_own_custom_functions|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 [[Scripting%2C_Code_%26_Interaction#Working_with_variables:_the_basics|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 {{Thread|OtherRoom module|20650}}, 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.
 
[[Category:Beginner Tutorials]]
[[Category:Scripting]]
1

edit