Lua, the Embeddable Game Scripting Language

lua

Tue Jan 27 15:43:33 -0800 2009

Lua is an embeddable scripting language with a syntax similar to Ruby or Python. It was designed to be embedded into games or other multimedia apps that need to compile to a desktop executable and run fast. And since you define all the access points between the script and the main program, it allows you to run untrusted user scripts securely. To compare to web technologies: Lua is to a game engine as Javascript is to a web browser.

Writing complex game logic in C is about as much fun as writing the business logic for a web app in C. Lua seems like it has the capability to fill this gap. Your engine is still in C, so it’s fast, and compiles to the appropriate binary for each platform you wish to support. But all your game logic is scripted in Lua.

Although Lua has plenty of reference documentation, how-tos are extremely scarce. In particular, I could not find an example of a full, working Lua-scripted app, including simple things like the makefile with all the proper library switches. So, I give you: a pong game written in Lua.

I don’t claim that this is good Lua code (or good C, for that matter), but it does work on OS X and Linux and presumably could be made to work on Windows without too much hassle. It assumes you are using Lua 5.0, which can be installed on Ubuntu with apt-get install liblualib50-dev and on OS X with port install lua50.

The C part of the program, engine.c, knows nothing about the game. It sets up the SDL drawing surface, exports some functions to Lua space (draw_rectangle and some keypress functions), and then runs in a loop calling the function pulse() in the Lua script. That function in turn executes all the game logic for each frame, using objects constructed inside the Lua VM. Those objects, at their lowest level, call draw_rectangle to send drawing commands back to the C program.

Here’s a snippet of the Lua code which takes user input (up_pressed() etc are C functions which access SDL’s key functions) and translates that to paddle movements for the player (side 1):

function Paddle:move()
   if self.side == 1 then
      if up_pressed()    then self.y = self.y + 0.01 end
      if down_pressed()  then self.y = self.y - 0.01 end
   end
end

Since I’m a testing zealot, I also wrote a small test framework, which executes via all_tests() when you run “make test”. test.c is nothing more than a stripped down engine.c, which runs the script without doing any video. Here’s the tests:

function all_tests()
   describe("Rect", function()
      r1 = Rect:new{x = 0, y = 0, w = 3, h = 1}
      r2 = Rect:new{x = 1, y = 0, w = 2, h = 2}
      r3 = Rect:new{x = 4, y = 0, w = 2, h = 2}

      it("intersects two rectangles", function()
         return r1:intersects(r2) == true
      end)

      it("doesn't intersect non-intersecting rectangles", function()
         return r1:intersects(r3) == false
      end)
   end)
end