Most game developers test their Lua scripts from the command prompt before plugging them into a game—to verify that the script runs as expected without bugs. You can use the Lua interpreter to do that. The Lua binaries include lua5.1.exe (the interpreter) and luac5.1.exe (the script bytecode compiler). To simplify running the programs from the command line, I recommend renaming them to lua.exe and luac.exe. lua.exe (as I will refer to it from now on) is the interpreter, and you will use it to run Lua script files. luac.exe (as it will be known from now on) is the compiler, which converts a script into a bytecode file. You do not need to compile a Lua script in order to run it. You may compile it into bytecode if you don’t want anyone to edit or copy your scripts when you release a program to the public.
Let’s run a script from the command prompt. First, open a command prompt. In Windows, click Start, All Programs, Accessories, Command Prompt. Optionally, you may click Start, Run and type cmd into the text field (then hit Enter).
The first thing you need to do is change the current directory to where your project files are or will be stored. All of the script files demonstrated in this chapter are available on the book’s CD-ROM in the .sourcesch12sample scripts folder.
You can use the cd command to change the current directory. You can type in absolute paths (such as C:Program FilesMicrosoft Visual Studio 2005) as well as relative paths (such as .projects). The goal is to get into the directory where you have extracted the Lua interpreter (lua.exe).
AdviceAfter you type in a command, such as cd, when you are about to type in a directory or file name, you may type in the first few letters and hit the Tab key (once or multiple times) to cycle through the files and/or folders that begin with the characters you have entered. |
If you do not know where the files are located, try copying everything from sourcesch12sample scripts into the root folder of C: or in a subfolder called C:Advanced2D (just as an example). Once you have used cd to reach the folder where the files are located, you can proceed.
Command-prompt programming is how users and programmers used to interact with the operating system in the old days, and it was not user friendly or easy to learn. Most Linux gurus still prefer their beloved shell rather than a fancy GUI because typing is usually much faster than clicking and moving a mouse. Here’s a helpful command:
dir
This gives you a listing of the current directory. But you can also pass a folder name to dir to list the contents of any folder on your system. Figure 12.1 illustrates.
Before running the Lua interpreter, we need a script file. You can use Notepad, but while we’re in the command prompt, let’s do it the keyboard way. Type this:
Notepad will open with a new file called hello.lua, ready for your use. If the file already exists, Notepad will open the file. Here’s a sample script you can type into the hello.lua file:
-- My first Lua program print("Hello World!") print("Version: ",_VERSION)
Let’s verify that the script file has been created by using another useful command:
type hello.lua
Figure 12.2 shows the output.
AdviceIf you are serious about Lua script programming, you will need a better editor. I recommend Notepad++, a free editor that provides colored syntax highlighting and a tabbed interface. Notepad++ is provided on the CD-ROM and is also available for download at http://notepad-plus.sourceforge.net. |
The Lua interpreter is so small that you can just copy it into your current project folder so that you can run it from the command line to test your script files. (Just be sure to copy the lua5.1.dll library file with Lua.exe since it’s a dependency.)
AdviceIf you run Lua.exe without a script file, it will go into command mode. Just press Ctrl+C to exit. |
Assuming you’re in the right folder, you have copied lua.exe and lua5.1.dll into that folder, and you have created your first script file (whew!), let’s give it a test run:
lua hello.lua
Figure 12.3 shows the output from the hello.lua program.
Let’s talk about how Lua handles variables. In Lua, everything is an object. But since “object” is such a generic term these days, to be more specific, Lua uses a dictionary-style container for all variables (where a dictionary stores each data item with a lookup value). Here is an example script showing how variables are declared and used.
-- Doing variables in Lua --simple variables myinteger = 100 mydouble = 3.1415926535 mystring = "some string" print("myinteger = ", myinteger) print("mydouble = ", mydouble) print("mystring = ", mystring) --complex variable (table) Person = { age = 30, name = "John" } print("Person's name: ", Person.name) print("Person's age: ", Person.age)
This program produces the following output:
myinteger = 100 mydouble = 3.1415926535 mystring = some string Person's name: John Person's age: 30
As you can see from the output, when using the print() function, multiple parameters can be separated by a comma, which inserts a tab character into the output stream. If you want to just append text, you must use Lua’s unusual text concatenation operator, .. (double dot), like so:
print("mystring = " .. mystring)
Lua has a weak random-number generator because it cannot be easily initialized with a random seed. Although Lua provides a function called math.randomseed(), it does not work properly because Lua does not provide an epoch-based millisecond timer. Lua initializes its timer when the script begins to run, which means the best we can do is send 0 to math.randomseed().We need to mix up the random-number generator (math.random()) with some large numbers and the use of modulus (math.mod()) to produce a pseudo-random result. It’s messy. But it can be put into a reusable function.
function gen_random(max) local temp = math.mod((42 * math.random() + 29573), 139968) local ret = math.mod( ((100 * temp)/139968) * 1000000, max) return round(ret + 0.5) end
This function has a dependency—a function called round. Here is the round function (which supports rounding to any number of decimal places):
function round(num, places) local mult = 10^(places or 0) return math.floor(num * mult + 0.5) / mult end
Those two functions work pretty well to produce a random number up to the passed maximum value parameter. But more often than not, we need to generate a random number within a fixed range (such as 100 to 150). That’s easy enough:
function random_range(min,max) return gen_random(max-min) + min end
We can now create random numbers with pretty good consistency. Here is an example:
math.randomseed( os.time() ) output = "" for n = 1,10 do a = random_range(1,1000) output = output .. tostring(a) .. "," end print(output)
The output from this program is a list of 10 random numbers in the range of 1 to 1,000:
63,52,806,319,700,189,617,981,852,15,
Lua variables can contain complex data types, similar to a C++ struct (and even a class through some convoluted code). To define a table, set a variable name equal to an empty set of brackets:
grades = {}
After defining the table, you can fill it in with data like so:
grades[1] = 90.5 grades[2] = 78.3 grades[3] = 85.8 grades[4] = 76.2 grades[5] = 68.1 grades[100] = 50.3 grades[200] = 100.0
Did you notice that the last two elements in the grades table are out of order (100 and 200)? Lua allows you to do that, but it will not fill in the missing elements leading up to those numbers—they will all be nil unless they are defined (which is true of any variable in Lua). Just remember that every variable is an entry in Lua’s dictionary container. In that case, grades[100] is more of a name than an array element (though that’s a simplification). Let’s examine a more complex type of Lua table—one containing multiple named items:
persons = {} persons[1] = { name = "John", age = 30, weight = 180, IQ = 120 } persons[2] = { name = "John", age = 18, weight = 150, IQ = 113 } persons[3] = { name = "Sue", age = 19, weight = 110, IQ = 125 } persons[4] = { name = "Dave", age = 20, weight = 160, IQ = 110 } persons[5] = { name = "Laura", age = 24, weight = 100, IQ = 118 } persons[6] = { name = "Don", age = 18, weight = 130, IQ = 122 } persons[7] = { name = "Julie", age = 22, weight = 120, IQ = 105 } persons[8] = { name = "Craig", age = 21, weight = 180, IQ = 112 } persons[9] = { name = "Sarah", age = 20, weight = 115, IQ = 130 }
The persons table contains four properties: name, age, weight, and IQ. You can legally violate the syntax of any one element in the table, and Lua will not complain—although you could wind up with a program crash. For instance, I could redefine persons[8] like so:
persons[8] = { something = 1, something_else = "blah" }
and Lua will accept it. But, although you can do this, it makes no sense to do so because the most common use for a table is for iteration.
Lua provides a library called table that you can use to get information about one of your tables. The function table.getn() will tell you how many items are contained in your table. For instance:
print( table.getn( persons ) )
will display the number of elements in the persons table (which is 9). Odd as it may seem to your C++ training, table is a Lua library, and your own custom-defined tables are not objects with methods such as size or length. You must pass the name of your table to table.getn() instead. With this new information, we can print out the data in the persons table. Note in this example that you can access properties in a table using two different formats (interchangeably)—either the property name as an index or the property name with the dot operator.
print("PERSONS") size = table.getn(persons) for n = 1, size do print( "Person #" .. n ) print( " Name = " .. persons[n]["name"] ) print( " Age = " .. persons[n].age ) print( " Weight = " .. persons[n]["weight"] ) print( " IQ = " .. persons[n].IQ ) end
This produces the output:
PERSONS Person #1 Name = John Age = 30 Weight = 180 IQ = 120 Person #2 Name = John Age = 18 Weight = 150 IQ = 113 Person #3 Name = Sue Age = 19 Weight = 110 IQ = 125 Person ...
Lua provides functions to retrieve the current date and time. The os.date() function returns the current date with a default format that includes the time (and you may modify the format if you wish):
Date: 04/12/08 13:04:24
The os.time() function returns the number of seconds that have passed since January 1, 1970 (on most systems—though this start date may vary from system to system):
Time: 1208030664
Dividing this number by 60 produces the minutes since the beginning of the epoch. Dividing by another 60 results in hours. Then, dividing by 24 hours and again by 360 days, you will calculate the number of years. Now let’s look into more specific timing features of the Lua language.
There are times when you may wish to profile or benchmark your script code to see whether it’s slowing down the game much (if at all). We can use Lua’s os library to get the current time in a number of ways. I’ve mentioned already that Lua does a poor job of seeding the random number generator due to its lack of an epoch-based millisecond timer. But once the program starts up, you can retrieve milliseconds in order to profile your script code. Here is a function called Stopwatch() that I find useful for slowing down the output of profiling code (for instance, limiting the print calls to once per second):
start_time = 0 function Stopwatch(ms) if Timer() > start_time + ms then start_time = Timer() return true else return false end end
Stopwatch() just returns true or false depending on whether the desired number of milliseconds has passed. To profile a function in Lua, as in C++, you must call it many times, getting a baseline time value, and then divide that time by the number of iterations. This is the only way to get the actual time taken to call a function since the processor can perform a function call in a matter of microseconds (millionths of a second) or nanoseconds (one billionth of a second), while a millisecond is only one thousandth of a second.
The Stopwatch() function has a dependency on another function we have not yet seen. The Timer() function returns the number of milliseconds since the program started with the help of os.clock(). This function normally returns just seconds, but it also provides a decimal value containing the milliseconds as well. By multiplying os.clock() by 1,000, we can convert floating-point seconds into integer milliseconds.
function Timer() return os.clock() * 1000 end
Here is an example that prints out the raw clock value and the converted millisecond value once every second:
repeat if Stopwatch(1000) then print("os.clock(): " .. os.clock()) print("Timer(): " .. Timer()) end until false
That script program produces the following output:
os.clock(): 1.015 Timer(): 1015 os.clock(): 2.015 Timer(): 2015 os.clock(): 3.031 Timer(): 3031 os.clock(): 4.046 Timer(): 4046
The millisecond timer is indeed returning milliseconds, as you can see in this output, because the data is being printed out once per second.
The last thing I want to go over with you regarding timing is a function profiling program. The purpose of this program is to demonstrate how to test the runtime of a single function. To slow down the function call, we will calculate square root—which is notoriously difficult for most processors to calculate. First, we’ll get a random number, then get the square root of the number using math.sqrt(), and return the value for good measure.
function SlowMathFunction() r = random_range(1,999999) num = math.sqrt(r) return num end
Why do we set the square root value equal to a variable first, before returning it? The Lua interpreter might be smart enough to optimize code like this, so we need to physically store the result of the calculation in a variable to actually use a processor cycle.
This program uses a for loop that runs the function a couple million times in order to calculate the time taken to run the function once, and the results are printed out.
print("Profiling function...") TOTAL = 2000000 start = Timer() for n = 1,TOTAL,1 do var = SlowMathFunction() end finish = Timer() delta = finish-start print("Total run time: " .. delta .. " ms" ) print("Function run time: ") milli = round(delta / TOTAL, 8) micro = round(milli * 1000, 8) nano = round(micro * 1000, 8) print(" milliseconds: " .. milli ) print(" microseconds: " .. micro ) print(" nanoseconds : " .. nano )
Here is some sample output. As you can see, it takes about one and a half microseconds to run the SlowMathFunction() just once.
Profiling function... Total run time: 3218 ms Function run time: milliseconds: 0.001609 microseconds: 1.609 nanoseconds : 1609
Just out of curiosity, let’s see how much of that 1.6 microsecond figure is taken up in the function calls (including the random function). Here’s a revised version of the program that has the math calculation embedded in the for loop:
Total run time: 2796 ms Function run time: milliseconds: 0.001398 microseconds: 1.398 nanoseconds : 1398
Look at that! There’s a difference of 211 nanoseconds. Interestingly, this program calls the SlowMathFunction() two million times, so there seems to be a noticeable but infinitesimally small amount of overhead in each function call. The value will differ from one system to the next and will depend on processor speed, but I calculated one-tenth of a picosecond (which is, for all practical purposes, too small to be relevant).
In general, you will want to code your math functions in C++, rather than in Lua, because despite Lua’s solid performance, it is an interpreted language and it cannot compete with a compiled binary for performance. But there are times when a designer may wish to just perform some range tests or gameplay tests to see whether a game is doing what it’s supposed to do in unusual situations. One common calculation is to derive the distance between two points. A point can represent the position of any game entity. The following program prints out the distance between two points:
function distance(x1,y1,x2,y2) return math.sqrt( (x2-x1)^2 + (y2-y1)^2 ) end x1 = 100 y1 = 150 x2 = 780 y2 = 620 print("Point 1: " .. x1 .. "," .. y1) print("Point 2: " .. x2 .. "," .. y2) print("Distance = " .. distance(x1,y1,x2,y2) )
Feel free to change the point locations. The output using these points is:
Point 1: 100,150 Point 2: 780,620 Distance = 826.6196223173
We explored linear velocity and incorporated velocity calculations in the Math class back in Chapter 10. We can code these functions in Lua as well. Here are the Lua versions of VelocityX() and VelocityY() with some test code:
function VelocityX(angle) return math.cos( (angle-90) * math.pi/180) end function VelocityY(angle) return math.sin( (angle-90) * math.pi/180) end ang = 120 vx = round(VelocityX(ang),2) vy = round(VelocityY(ang),2) print("Velocity(" .. ang .. ") = " .. vx .. "," .. vy)
This example script produces the following output:
Velocity(120) = 0.87,0.5
For good measure, I’ll throw in one last math function that has been converted from its C++ equivalent in the Math class: calculating the angle between two points. Like the C++ atan2() function, Lua’s math.atan2() calculates an angle based on delta Y and delta X for two points.
function target_angle(x1,y1,x2,y2) deltaX = x2-x1 deltaY = y2-y1 return math.atan2( deltaY, deltaX ) end x1 = 100 y1 = 100 x2 = 900 y2 = 600 print("Point 1 = " .. x1 .. "," .. y1 ) print("Point 2= " .. x2 .. "," .. y2 ) rangle = target_angle(x1,y1,x2,y2) rangle = round(rangle,4) dangle = math.deg( target_angle(x1,y1,x2,y2) ) dangle = round(dangle,4) print("Target Angle in radians = " .. rangle ) print("Target Angle in degrees = " .. dangle )
(Just grab a copy of the round() function from one of the other script listings for use in this program—it was covered earlier in the chapter.) Here’s the output:
Point 1 = 100,100 Point 2= 900,600 Target Angle in radians = 0.5586 Target Angle in degrees = 32.0054
Now let’s make a simple game entirely in Lua script! You can run this game from a command prompt using the Lua interpreter. The guessing game script first generates a random number from 1 to 100, and then asks the user to continue guessing until he or she gets the answer right. Each time a number is entered, the game tells the player whether the answer is higher or lower.
Here is the source code for the game. Not shown are the common functions we’ve gone over previously: round(), gen_random(), and random_range().
function GetInput() return io.stdin:read("*l") end print "Try To Guess My Secret Number (1-100)" math.randomseed( os.time() ) answer = random_range(1,100) guess = 0 total = 0 repeat input = GetInput() guess = tonumber(input) if guess > answer then print("THE ANSWER IS LOWER") elseif guess < answer then print("THE ANSWER IS HIGHER") end total = total + 1 until guess == answer print("You got it in " .. total .. " tries.")
Figure 12.4 shows the output of the guessing game script in a command prompt.