Thursday, August 28, 2008

Integrating SDL with Lua

For my next little game project I decided to use SDL, OpenGL and preferably some sort of scripting language to handle the game logic. Not that I couldn't write the game logic in C/C++, but I wanted to try out, how this integration of scripting language into your application works.

The language I decided to try first was Lua. It has been used in many games, so it felt like a natural choice.

SDL skeleton

First we need some SDL/OpenGL skeleton, which will initialize SDL and OpenGL. Basically we need the simplest OpenGL application there is.

#include <windows.h>
#include <SDL/SDL.h>
#include <gl/gl.h>

static int redraw(float theta)
{
 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

 glLoadIdentity();
 glTranslatef(0.0f,0.0f,0.0f);
 glRotatef(theta, 0.0f, 0.0f, 1.0f);

 glBegin(GL_TRIANGLES);
 glColor3f(1.0f, 0.0f, 0.0f);
 glVertex2f(0.0f, 1.0f);
 glColor3f(0.0f, 1.0f, 0.0f);
 glVertex2f(0.87f, -0.5f);
 glColor3f(0.0f, 0.0f, 1.0f);
 glVertex2f(-0.87f, -0.5f);
 glEnd();

 SDL_GL_SwapBuffers();
}

int main(int argc, char*argv[])
{
 SDL_Init(SDL_INIT_VIDEO);
 SDL_Surface *screen = SDL_SetVideoMode(400, 400, 32, SDL_DOUBLEBUF | SDL_HWSURFACE | SDL_OPENGL);

 glViewport(0, 0, 400, 400);
 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
 glClearDepth(1.0);
 glDepthFunc(GL_LESS);
 glEnable(GL_DEPTH_TEST);
 glShadeModel(GL_SMOOTH);
 glMatrixMode(GL_PROJECTION);
 glMatrixMode(GL_MODELVIEW);

 for (float theta = 0; theta < 20000; theta += 0.5)
 {
  redraw(theta);
 }

 return 0;
}

To compile, we have to use

g++ -o sdl main.cpp -lmingw32 -lSDLmain -lSDL -lopengl32 -mwindows

For Linux just remove -lmingw32 and -mwindows.

Please note, that the relative order of libraries and main.cpp is important. SDL overrides the default main function, same as Win32 does, so switching the order will result in a linking error.

After successful compilation, you should get an application looking like this:

OpenGL Triangle

The code itself is quite ridiculous (especially the for loop with floating point number), but the reason will become clear, when we insert the Lua language scripting.

Lua skeleton

Now we need a minimal example of Lua processing, so that we get a feeling for the Lua code. It's actually quite simple, you include the appropriate header files (in my case I'm using the C++ version of header files that comes with Lua, because, I'm planning to use C++ in my project). Afterwords you just initialize Lua and call the appropriate script file. It's pretty self explanatory:

#include <lua/lua.hpp>

int main(int argc, char*argv[])
{
 lua_State *L=lua_open();
 luaL_openlibs(L);

 if (luaL_dofile(L, "skeleton.lua")!=0) fprintf(stderr,"%s\n",lua_tostring(L,-1));

 lua_close(L);

 return 0;
}

We compile this with:

g++ -o lua_skeleton lua_skeleton.cpp -llua

Now we need to create a simple Lua script skeleton.lua:

io.write("Hello from Lua\n")

Upon running we should get a result looking like this

Hello from Lua

Skeletons mixed

And now we need to mix these two programs together. I want to offload the rotation of triangle to Lua, that will allow me to test how to call functions from Lua and how to retrieve variables to Lua.

#include <windows.h>
#include <SDL/SDL.h>
#include <gl/gl.h>
#include <lua/lua.hpp>

static int redraw(lua_State *L)
{
 float theta = lua_tonumber(L, 1);

 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 glLoadIdentity();
 glTranslatef(0.0f,0.0f,0.0f);
 glRotatef(theta, 0.0f, 0.0f, 1.0f);
 glBegin(GL_TRIANGLES);
 glColor3f(1.0f, 0.0f, 0.0f);
 glVertex2f(0.0f, 1.0f);
 glColor3f(0.0f, 1.0f, 0.0f);
 glVertex2f(0.87f, -0.5f);
 glColor3f(0.0f, 0.0f, 1.0f);
 glVertex2f(-0.87f, -0.5f);
 glEnd();

 SDL_GL_SwapBuffers();
}

int main(int argc, char*argv[])
{
 lua_State *L=lua_open();
 luaL_openlibs(L);

 lua_register(L, "redraw", redraw);

 atexit(SDL_Quit);
 if( SDL_Init(SDL_INIT_VIDEO) < 0 ) {
  fprintf(stderr,"Couldn't initialize SDL: %s\n", SDL_GetError());
  exit(1);
 }

 SDL_Surface *screen = SDL_SetVideoMode(400, 400, 32, SDL_DOUBLEBUF | SDL_HWSURFACE | SDL_OPENGL);

 glViewport(0, 0, 400, 400);
 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
 glClearDepth(1.0);
 glDepthFunc(GL_LESS);
 glEnable(GL_DEPTH_TEST);
 glShadeModel(GL_SMOOTH);
 glMatrixMode(GL_PROJECTION);
 glMatrixMode(GL_MODELVIEW);

 if (luaL_dofile(L, "mixed.lua")!=0) fprintf(stderr,"%s\n",lua_tostring(L,-1));
 lua_close(L);

 return 0;
}

The Lua script looks as follows. I chose the increment step 0.5, so that I can see possible speed difference between Lua's processing of floating point and C.

for i = 1.00, 20000, 0.5 do
 redraw(i)
end 

To my tremendous surprise the Lua code performs as fast as the C code. I did a couple of measurements on different computers and I couldn't find a statistically significant difference (Lua was hitting 96 % - 101 % framerate of pure C version). Without performance issues in the way, it looks like I might be using Lua as a scripting language in my project.

To download all the source code and binaries, click here luasdl.zip 440 kB

Next time, we will look at the performance and integration with Python.

Code highlighted with syntaxhighlighter

1 comments:

Ido said...

Neat!

Seems strange though that you'd use something that seems so easy to make platform independent and then go ahead and use windows.h.