Up until now, we have shied away from doing much with scripting, but all of that is about to change. Scripting is essential for even the most basic game because user interaction is handled through scripts. We can also give enemies artificial intelligence, trigger animations, and manage scene resources. In this chapter, we will consider the following:
ShiVa3D uses Lua as the basis of its scripting language, StoneScript , so if you have experience with Lua then you will have a leg up on everyone else, but you also have to keep in mind that the Lua APIs and many of the language features are not implemented.
The first thing to know about StoneScript, especially if you have programmed with C-based languages, is that there is no semicolon line termination. StoneScript uses the end-of-line to signify line terminations.
We'll cover comments first, because it is always handy to document parts of your code that are hard to follow. Of course, you could always refactor code that is hard to understand and make it more self-documenting, but comments can be valuable when used correctly.
In this book, we will always tend toward variable and function names that are descriptive, and we'll break the code into manageable pieces. A good rule of thumb is to have functions perform a single task, that is, creating an enemy, processing user input, and so on. Try to stay away from having one function do something such as, check collisions, get user input, and load a resource—you will have too many reasons to change the code when making modifications and it will be easy to introduce bugs.
To comment a single line, append --
to the line. This can also be used inline with actual code, as shown in the following snippet:
local nRx, nRz = 0, 0 -- Initialize rotation
To comment multiple lines, use --[[comments]]
, as shown in the following code:
--[[ This script controls the loading of the first scene and the game resources. ]]
StoneScript is a dynamically typed language meaning that the type of the variable is declared at runtime. We declare variables using the local
keyword, as shown in the following code snippet:
local nX = 0 local nY, nZ = 0, 0 local hObject = application.getCurrentUserSceneTaggedObject ( "Ship" )
We can see that in each case, we are not explicitly declaring what kind of variable we want. The first three variables will become numbers and the last will be a handle to an object. The other thing to notice is that on the second line we declared and initialized two variables using the comma operator. Lua also doesn't care if we change the type of a variable on the fly, as done with the hObject
variable in the following code:
hObject = 3
Remember that we previously assigned a handle to an object to hObject
, but now we have assigned a number. Because variable types can be a bit ambiguous, ShiVa3D community developers follow a naming convention for variables because it makes code easier to follow. You can find this information at http://www.stonetrip.com/developer/doc/api/introduction#_Toc275785466. We use a prefix to declare what we intend to use the variable for.
The prefixes used—along with their description and an example of each—are explained are follows:
h
: Handle to an object, for example, hShip = this.getObject()
n
: Numerical value, for example, nRx = 7
b
: Boolean value (true
or false
), for example, bRotating = true
s
: String value, for example, sTitle = "Commander"
v
: Mutable variable—if you really need to use one variable for multiple types, for example, vWhoKnows
t
: Tables, for example, tMonsterLocations
ht
: Hash tables, for example, htUserPasswords
k
: StoneScript constants, for example, object.kGlobalSpace
xml
: XML data, for example, xmlGameSettings
Is it mandatory to use these prefixes and will our code run differently if we do? No to both questions, but it will make your code easier to read, especially if you need to post it to the forum when you have questions.
For objects, Lua uses nil
as the keyword to mean no value; it is similar to null
in C.
In ShiVa, functions are declared in the user interface, which we will go over later, but for now we will take a look at the syntax of defining a function and how a function is subsequently called. In ShiVa, functions can be standalone, or an event handler, with the major difference being in how they are called. We call functions directly in our code, whereas event handlers are called in response to an event. The event can be generated automatically in response to user input or some other game mechanic, or we can generate the event in our code. We will talk more about event handlers later in the chapter when we add user interaction, but for now let's look at functions.
Functions are defined by the function
keyword, followed by the name of the function, and finally the function arguments in parentheses, as shown in the following code:
function ShipAI.onKeyboardKeyUp ( kKeyCode ) … end
Multiple arguments are separated by commas. Note that there is no need to declare the function return type or the types of the arguments. The function is closed by an end
statement. If the function had return values, we would simply use the return
keyword to return as many values as needed, separated by commas, as seen in the following code:
function ShipAI.returnsManyValues ( ) return x, y, z end
To call a function, we simply enter the function name followed by parentheses surrounding any arguments being passed. As we begin scripting our game behavior, we will see another aspect of functions within ShiVa that affect how they are called—we precede the function name with the AI module that contains it, but since we can only call a function from within the AI that owns the function, we use the this
keyword. All of our function calls end up looking like the following line of code:
this.GoVertical ( true )
Flow control does exactly what it sounds like—it controls the flow of our scripts. There are several different kinds of flow control from simple if
statements to complex loops. Let's take a look at what we can do.
This is the bread and butter of programming. We use a Boolean expression to determine which block of code gets executed. The basic statement is as follows:
if(boolean expression) then Statements … end
And the else
version is as follows:
if(boolean expression) then statements … else statements … end
ShiVa also has a specific form for elseif
, shown as follows:
if(boolean expression) then statements … elseif(boolean expression) then statements … else statements … end
The hard part for me has been remembering to use then
and end
. So don't forget those!
The return
keyword
is part of the flow control statements, because it can return program flow from a function. In ShiVa, the return
keyword can return up to thirty-two arguments. The following line of code returns multiple arguments:
return "hello", 3, 5.3, "time"
To use these return values, you need to specify a range of values in the assignment expression. If we had a function that returned a set of x, y, and z coordinates, then the assignment would look like the following line of code:
local nCurrentX, nCurrentY, nCurrentZ = object.getTranslation ( this.hShip ( ), object.kGlobalSpace )
The three return values will be assigned to the variables that we have specified. If we don't provide enough variables, the extra return values are simply discarded, but if all you need are the middle or end values, such as the z coordinate, you will have to provide variables for the x and y coordinates even if you only plan to ignore them.
The break
keyword, on the other hand, simply exits the current loop without returning a value.
ShiVa's for
loop is a bit different from C-based languages; it is shown in the following code snippet:
for i = from, to do statements ... end
This version sets the increment to one by default, while the following version declares the increment to be two:
for i = from, to, step do statements ... end
ShiVa uses all the same operators that you would expect; these operators are as follows:
>
<
>=
<=
==
~=
+
-
*
/
..
and
or
When dealing with objects, the comparison is done using the isEqualTo()
method, for example, object.isEqualTo(hObject1, hObject2)
.
3.15.12.34