BSL:Introduction
Like any typical scripting or programming language, BSL scripts consist of commented plain-text files which employ branching logic and various operators according to a strict syntax to process data using functions that act upon variables. Click one of those words to jump to the brief explanations of those terms below, but if you are new to programming, you'll also want to read the "Details" pages that are linked to from each section.
For those familiar with C, the syntax is similar and you will feel at home in BSL, but BSL's feature set is stripped down to a degree that is simpler than even most scripting languages. Fortunately, this makes BSL fast to learn.
Comments
Comments are notes from the programmer to explain some code. They are supposed to be placed above or after a line of code:
# The following block of code calculates the meaning of life if [block of code begins here]
var int a = 0; # this global is also modified in my_cutscenes.bsl
In documentation outside of source code or script files, such as this page, they are still sometimes used to tell the reader something in a way that doesn't break the actual code, if the user should type it in exactly as it appears, comments and all.
Files
When Oni loads a level, it also loads and parses all .bsl files in the folder in IGMD which contains that level's scripts (the name of the folder being specified by the level's ONLV resource). Like C, the code automatically begins running with the function called "main", regardless of which .bsl file it is found in (Oni's scripts always place it in a file called *_main.bsl). Only other functions that are called by main() (and the functions it calls) will run on their own, though you can also manually call any of those functions from the developer console -- however, you cannot actually define variables or functions through the console.
Note that the optional global folder is also loaded for all levels, but this code cannot run on its own unless you edit another function that does run automatically so that it calls a function in a global BSL file.
Logic
- See BSL:Statements for details.
In order for game events to react to the player's actions, you need branching logic, which basically boils down to an if statement in this form:
if (counter eq 3) # "if counter's value is equal to three, do this..." { # some code here }
...where "counter" is a variable declared beforehand (see "Variables" below). By reading the BSL:Statements page, you can also learn how to test for multiple conditions at once in an "if" statement, use an "else" condition, etc.
Operators
- See BSL:Operators for details.
We're all familiar with the four basic arithmetical operations: addition, subtraction, multiplication and division. Well, BSL only has the first two :-) They're the "+" and "-" symbols, as you would expect. Example:
counter = counter + 1; # increases counter's value by one
To compare two values, you use relational operators, like this:
if (counter < 3)
Syntax
- See BSL:Syntax for details.
BSL allows two kinds of syntax to be used somewhat interchangeably, which can be called "shell-style" and "C-style". This can make it more complicated to talk about the language, and you can also generate errors with mixed syntax. Using the C style requires a bit more typing, but it creates safer and more readable code.
Braces define blocks of code, whether the block is a function or a conditional:
func void some_function(void) { # some code }
if (condition) { # some code }
In C-style BSL, semicolons end a statement, and parentheses enclose function parameters:
func void some_function(void) { var int a; a = 4; sleep(60); if (a eq 4) { dprint("Hello"); } }
The same code would look like this in shell-style BSL:
func some_function { var int a a = 4 sleep 60 if (a eq 4) { dprint "Hello" # the quotes could be left out too } }
Notice that parentheses are always needed with "if" statements, and also note that the type (see "Types" below) of the function and its parameters can be left out in shell-style BSL (this meaning "void" implicitly).
Types
- See BSL:Types for details.
When declaring a variable or a function, you must specify the type of data it contains or returns:
var int x; func int my_func(void) {...}
You can choose from "bool" (can be true/false or 1/0), "int" (can be any whole number), "float" (a value with a decimal point), or "string" (some text).
Functions
- See BSL:Functions for details.
Unlike C, functions do not need to be declared or defined before they are called. Functions are defined by starting with the keyword "func", then giving the type of data (see "Types" above) that the function returns, then the name of the function. After that, any parameters it receives should be listed within parentheses. Finally, the actual code for the function should be given within curly braces, like so:
func int my_awesome_function(int some_input) { var some_output = some_input + 5; return some_output; }
Now that it is defined, we can call it from anywhere in the BSL for the same level by using this invocation:
var result = my_awesome_function(counter); # sets "result" to the the value of "counter" plus 5
Note that (1) we can set a variable to the result (see "Variables below) and then use that value elsewhere, and (2) the name of the variable we pass in to the function does not have any relation the name that the function uses for that value internally. That's because we might simply desire to pass a constant number to the function:
var result = my_awesome_function(3);
No matter what, the value passed in will be called "some_input" inside the function.
Variables
- See BSL:Variables for details.
You've already seen some example of variable declaration. One more thing to be aware of is that variables can be initialized upon declaration:
var int y = 9;
or not:
var int y;
In the second case, "y" is set automatically to zero.
When declaring variables, it's important to be aware of scope. The scope is the range of the BSL script within which the variable can be referred to. If a variable is declared with global scope...
func some_function {...}
var int y = 0;
func another_function {...}
...then it can be accessed from both some_function and another_function, or any other function in the level's BSL files, for that matter. If it is declared with function scope...
func another_function { var int y = 0; }
...then it can only be accessed within that function. If it is declared with block scope...
func another_function { if (...) { var int y = 0; ... } }
...then only other code inside the "if" block can access it.