Home | Scripting adds logic to the engine. |
|
Introduction
Multiple highlevel (or scripting) languages exist in Racer - there are very simple console commands and there is the more complex (object-oriented) Onyx language. This article is about an intermediate language, called Q-script. It lacks the strict looks of Onyx (which looks more like C++).
Most new focus will be on Onyx, since that offers classes, a tight integration with the Racer engine and better type-checking.
Scripting in racer
1.Introduction
Version 0.89 of Racer introduces an intelligent scripting system, using expressions, functions, loops,...
Before we go into the syntax, it is best to understand how and where these scripts are build and executed.
This documentation assumes that you are already familiar with programming in general and the use of variables and functions.
1.1 Script extensions
The coding starts in an ".rsx" ASCII file.
".rsx" scripts are to be compiled before they can be used and loaded into Racer.
You can compile an rsx-script by sending this console command to Racer:
"compile <file>"
On success, this will create a compiled script-file (".rcx" extension) in the same folder as where the ".rsx" file is located. The console will output a message on success or failure, with error-log when needed.
Other usefull console commands when scripting:
"reload scripts" – reloads ALL Q-scripts
"remove scripts" – unloads ALL Q-scripts (does not actually remove them)
"doc scriptfuncs" - creates a text file that lists all available external functions
"doc scripttypes" - creates a text file that lists all available types in racer
1.2 How to use Scripts
Scripts are track-dependent, so each track can have its own batch of scripts running.
Scripts are "ticked" at certain intervals, depending on their use.
These "ticks"are available:
paint | Updates at each frame painted (fps dependent). Script can paint information to the screen. Place your compiled script (".rcx" extension) in the folder "<track folder>/scripts/paint". |
physics | Updates at each physics update (1000 Hz!). Script can not paint information to the screen (can result in unwanted behaviour) Do not use in combination with physics.async=1, this combination will most likely crash Place your compiled script (".rcx" extension) in the folder "<track folder>/scripts/physics". |
Compiled scripts are automatically loaded when placed in the correct folder if "scripts.auto_load" is set to 1 in racer.ini.
2 Syntax
2.1 Variables
2.1.1 Declaration
This is how you declare a variable:
"int $var = 1"
As you can see, the variable name starts with the '$' character.
This is how all variables must be named, that's the only requirement for naming and declaring variables.
2.1.2 Shared variables
Variables can be "shared", this means that if you create a variable with the name "score" and type "integer" in 2 different scripts, they will in fact be used as 1 variable and will always be the same (even works across networks).
Example 1:
- In script1 you initialize the shared variable "score".
- In script2 you also initialize the shared variable "score" and the script paints the variable each frame.
- In script1 you alter the variable "score"
- script2 paints the value for "score", and without changing the value of "score" explicitly in script2, it matches the value of "score" in script1
Example 2:
- In script1 you initialize the shared variable "score".
- You start a network game and obviously the host and client both have script1 running. (because they have the same track running…)
- The host changes the variable and the change is automatically send to the client (also works the other way around)
- Host and client paint the variable "score" on the screen and the values match!
2.1.3 Types
For a list of all basic types, input "doc scripttypes" in the console, and Racer will automatically generate a text file in the racer root folder called "scripttypes.txt".
2.2 Operations
Scripting supports all common operations and operators like
- =
- ==
- !=
- <
- >
- <=
- >=
- || or keyword "or"
- && or keyword "and"
- %
- *
- /
- +
- -
And also "operator assigns" like:
- %=
- *=
- /=
- +=
- -=
Increment ( ++ ) and decrement ( -- ) operations are also supported
Example 1:
int $var1=0
int $var2=1
$var1 = $var1 * $var2
// result in $var1 is 0
Example 2:
int $var1=0
int $var2=1
$var1 *= $var2
// result in $var1 is 0 and the operations is the same as in example 1
Example 3:
int $var1=3.14 * 100 / ((1000 + 15678) % 2)
// result in $var1 is euhm …
// Go crazy !
Example 4:
int $var1=3.14 * 100 / ((1000 + 15678) % 2)
// result in $var1 is euhm …
// Go crazy !
Example 5:
int $var1=0
++$var
// result in $var1 is 1
2.3 Scopes, if-statements and Loops
2.3.1 Scopes
Scopes can be used to declare and use variables locally
{
// create variable
int $var=5
// do stuff with variable
// …..
}
// variable is automatically removed when going out of scope
Note:
shared variables must be declared globally.
2.3.2 If
As you would expect:
int $var=1
if $var>0
{
// do stuff
}
Conditions do not need to be surrounded by parentheses.
"else" might come in handy:
int $var=1
if $var>0 {
// do stuff
} else {
// do other stuff
}
And "else if" obviously
int $var=1
if $var>0 {
// do stuff
} else if $var<0 {
// do other stuff
} else
{
// do other other stuff
// …..
}
2.3.3 Loops
2.3.3.1 While
int $var=100
while $var>0
{
--$var
// do stuff
// …..
}
// the loop will repeat 100 times
2.3.3.2 For
int $var=0
for $var != 100 do ++$var
{
// do stuff
// …..
}
// the loop will repeat 100 times
2.4 Arrays
Arrays are supported since v0.8.15.
Arrays are a list of variables of the same type.
2.4.1 Declaration
They are declared like this:
int $array[2]
This creates a list of 2 variables that can be used in loops or as vectors …
Assigning values to array-members can be done like this:
int $array[2] = int[2]{0,1}
or
int $arrayA[2] = int[2]{0,1}
int $arrayB[2] = $arrayA
A single array member can be addressed like this
float $array[2] = float[2]{10,1}
// address array member:
float $firstElement = $array[0]
float $secondElement = $array[1]
// Result:
// $firstElement equals 10
// $ secondElement equals 1
You can fill the array with a single value without using the int[5]{… expression.
The compiler automatically copy's the single value to fill the entire array.
int $array[3] = 6
// all array elements are filled with 6
2.4.2 Array Operations
Arrays support operations like this:
int $arrayA[2] = int[2]{0,1} + int[2]{2,3}
// result: $arrayA[0] equals 2 and $arrayA[1] equals 4
or
int $arrayA[2] = int[2]{2,3}
int $arrayB[2] = $arrayA + int[2]{0,0}
// result: $arrayB[0] equals 2 and $ arrayB [1] equals 4
2.4.3 Swizzling
You can swizzle arrays, this means you can address multiple members through a single call.
For example:
int $arrayA[2] = int[2]{0,1}
int $arrayB[2] = $arrayA[1][0]
// result: $arrayB[0] equals 1 and $arrayB[1] equals 0
// The order has changed compared to $arrayA !
Tou can also use xyzw, rgba or m00m01m02m03m10m11..m33 etc to address array members, if you use a dot ( . ).
A couple of examples:
int $arrayA[2] = int[2]{0,1}
int $arrayB[2] = $arrayA.yx
// result: $arrayB[0] equals 1 and $arrayB[1] equals 0
// The order has changed compared to $arrayA !
// Note the dot
Or
int $arrayA[3] = int[3]{0,1,2}
int $arrayB[2] = ( $arrayA+ int[3]{0,10,20} ).zy
// result: $arrayB[0] equals 22 and $arrayB[1] equals 11
Or
int $cowSays[3] = int[3]{0,1,2}
int $arrayB[2] = $cowSays.m00
// result: $arrayB[0] equals 0 and $arrayB[1] equals 0
Or
int $arrayA[4] = int[4]{0,1,2,3}
int $arrayB[3] = $cowSays.yyw
// result: $arrayB[0] equals 1
// $arrayB[1] equals 1
// $arrayB[2] equals 3
2.5 Functions
2.5.1 Declaring functions
You can declare functions with parameters and return values.
Important to know is that every parameter is passed by value and not by reference.
The "func" keyword is required before any function declaration.
Order is important, you cannot call a function before it is declared.
Example 1:
func void DoSomething()
{
// Something
}
DoSomething()
Example 2:
func int ReturnSomething()
{
return 1
}
int $var=ReturnSomething ()
// variable $var is now 1
Example 3:
func int AddSomething(int $add1, int $add2)
{
return $add1+$add2
}
int $var= AddSomething (5,3)
// variable $var is now 8
2.5.2 External functions
External functions are constant functions that interface with the Racer engine.
You can generate a text file with all external functions listed by inputting the console command "doc scriptfuncs", this will output to the root folder of Racer ("scriptfuncs.txt").
External functions are build for easy reading, so anybody can write a script without knowing any hardcore syntax.
Example:
// Paint string at given 2D location on screen
paint "Hello Racer" at x 10 y 10
2.6 Importing
To make your scripts more manageable, you can split your script up in different files and use the "import" keyword. When compiled, all code is merged into 1 ".rcx"-file, like always.
Example:
File: Functions.rsx
Location: <track>/scripts/paint/import
Func int Add(int $add1, $add2)
{
Return $add1+$add2
}
File: main.rsx
Location: <track>/scripts/paint/
import "import/Functions.rsx"
int $var=Add(5,5)
// $var is 10
3 Use and tips
3.1 Interrupt statement
Interrupt is an ordinary external function, but it has much greater value than any other function, hence it needs some extra documentation.
When "Interrupting" a script, you tell the script to stop executing and wait for the next "tick".
When interrupting, you keep all stack and variable states, so no data will be lost.
This is particularly useful when you have a script with persistent variables and in large scripts with states and lots of variables that you only want to load once.
Example:
int $framecounter=0
// Loop infinitely !!! scripts are killed when track is destroyed
while 1
{
++$framecounter
// paint framecount
paint $framecounter at x 10 y 10
// interrupt here
interrupt
}
3.2 Tips
- Although the virtual machine running these scripts is reasonably fast, you cannot expect to get C++ performance. The scripts are meant for small applications. Some good applications might be a soccergame, or just plain debugging.
- Standard function calls with parameters are relatively expensive, try to avoid them as much as possible (external functions are reasonably fast though)
- Use the while loop in combination with interrupt.
(last updated November 13, 2012 )