Lua Game Development Cookbook

4.5 (4 reviews total)
By Mário Kašuba
  • Instant online access to over 7,500+ books and videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. Basics of the Game Engine

About this book

The Lua language allows developers to create everything from simple to advanced applications and to create the games they want. Creating a good game is an art, and using the right tools and knowledge is essential in making game development easier.

This book will guide you through each part of building your game engine and will help you understand how computer games are built. The book starts with simple game concepts used mainly in 2D side-scroller games, and moves on to advanced 3D games. Plus, the scripting capabilities of the Lua language give you full control over game.

By the end of this book, you will have learned all about the components that go into a game, created a game, and solved the problems that may arise along the way.

Publication date:
July 2015
Publisher
Packt
Pages
360
ISBN
9781849515504

 

Chapter 1. Basics of the Game Engine

In this chapter, we will cover the following recipes:

  • Preparing a basic file structure for the game engine

  • Making a stack

  • Making a queue

  • Making a prioritized queue

  • Extending ipairs for use in sparse arrays

  • Creating Lua modules

  • Handling errors with pcall, xpcall, and assert

  • Using Lua with existing projects written in C/C++

  • Getting LuaSDL for libSDL 1.2

  • Designing the main application loop with LuaSDL

 

Introduction


Almost every game uses a game engine to help developers in video game production. It is usually used as a base platform for the game and manages all important functions from 2D/3D graphics, physics, sound effects, and network communication to artificial intelligence, scripting, and support for various software/hardware platforms. Using the scripting language in games has gained a lot of attention in the last decade mainly because it allows you to create game prototypes faster, easier, and it's an important part of the so-called modding support for the game community.

For instance, you can look at Quake game from the id software company, which uses its own scripting language Quake C and is one of the reasons why there are so many mods for this game. However, the source code for this language must be compiled before using. Depending on the project size this means a significant amount of time spent between feature implementation and testing. The Lua language can be used without prior compilation, which allows developers to test out their code right away.

The first game that used the Lua language for scripting was Grim Fandango from the company LucasArts. It was successfully used in further major game titles and today it can be commonly found in many multiplatform and mobile games.

Modern game engines are one of the most complex applications that are used. This leads to the situation where game developers use game engine as a black box without knowing how it actually works. For certain game titles, this might work out quite well. However, if you want to make a quick game prototype with certain features that are not present in the game engine, you'll most probably have to write your own game engine extension or find a workaround.

Lua language is fast and mature enough to be used as a game engine base language. Time-critical portions of a code can be written in C or C++ language and accessed via a Lua/C language interface. With this approach, you can view the Lua language as a high-level glue for the game engine process design.

This book will use Lua 5.1 version mainly because it's well supported and the existing code can be ported into a newer version with minor changes. Another reason behind this choice is that Lua 5.1 API is used in LuaJIT which is Just-In-Time implementation of Lua language. It's generally regarded as a faster version of the Lua language, which gives you speed comparable to compiled C code.

Lua language itself doesn't provide access to graphical output, sound devices or even input devices except basic I/O file interface. This shortcoming can be overcome with the use of LuaSDL binding to libSDL multimedia library that gives us the power to access all the devices needed to create a game with graphics and sounds. Installation and use of this library binding will be contained in this chapter.

It's always good practice to maintain a consistent file structure in any project. The Lua language doesn't formally specify modular structure and it's often the case when each module uses its own style of module specification. This results in namespace conflicts and unpredictable behavior.

The first part of this chapter will cover the preparation of a modular file structure for your application, implementation of the most common data structures, and error handling.

The second half of this chapter will deal with more advanced stuff such as writing and using the Lua modules and using libSDL multimedia library to develop interactive applications in Lua.

 

Preparing a basic file structure for the game engine


When programming a larger project, it's always important to keep it maintainable. One of the common practices is to keep a modular structure. Modular structure can be achieved by keeping files separated in certain directories.

Lua language uses a require function to include modules in your script files. This function uses a default list of paths where it tries to find the module file. The Lua modules can be written as plain Lua scripts or use a form of binary library, which is OS and CPU architecture dependent. This is especially troublesome if want to include binary libraries for all supported operating systems and CPU architectures in one project.

A default set of paths might not always be appropriate for your project, mainly if you bundle many third-party modules with it.

This recipe shows how to set up the Lua interpreter so that it can find correct files in a systematic and user-definable way. This recipe should be used at the beginning of your main Lua script file so that further calls to the require function in Lua will use your file path structure.

Getting ready

You can use a directory structure as shown in the following diagram. If you intend to implement your application for multiple platforms, always divide platform-specific files into separate directories.

The Lib directory contains all the Lua module files and binary libraries.

However, each operating system uses its own file naming convention for binary libraries. The Lua language doesn't have an easy way to obtain the OS name. For this purpose, you can download and use the Lua script module os_name.lua from https://gist.github.com/soulik/82e9d02a818ce12498d1.

You should copy this file into your project directory so that the Lua interpreter can find it.

How to do it…

The require function in the Lua language uses a set of default paths defined in package.path and package.cpath string variables. With your new directory structure, you'd have to change those two variables manually for each operating system, which could be cumbersome.

Instead, you can define a Lua script to build up these two string variables from a generic list of paths for both Lua script files and binary libraries.

In the first step, you need to create a list of directories:

-- A list of paths to lua script modules
local paths = {
  '{root}/{module}',
  '{root}/lib/{module}',
  '{root}/lib/external/{module}',
  '{root}/lib/external/platform-specific/{platform}/{module}',
}
-- A list of paths to binary Lua modules
local module_paths = {
  '?.{extension}',
  '?/init.{extension}',
  '?/core.{extension}',
}

Strings enclosed with curly brackets will be substituted with the following values:

Name

Value

root

This is the application's root directory

platform

This is the current platform

module

This is the module's file path

extension

This is the module's filename extension, which is platform dependent

Binary module filename extensions that are platform dependent are also set in a table:

-- List of supported OS paired with binary file extension name
local extensions = {
  Windows = 'dll',
  Linux = 'so',
  Mac = 'dylib',
}

Now, you need to set root_dir which is the current working directory of the application and the current platform name as follows:

-- os_name is a supplemental module for
-- OS and CPU architecture detection
local os_name = require 'os_name'

-- A dot character represent current working directory
local root_dir = '.'
local current_platform, current_architecture = os_name.getOS()

local cpaths, lpaths = {}, {}
local current_clib_extension = extensions[current_platform]

Before you start building the path list, you need to check whether the current platform has defined binary module extensions as follows:

if current_clib_extension then
  -- now you can process each defined path for module.
  for _, path in ipairs(paths) do
    local path = path:gsub("{(%w+)}", {
      root = root_dir,
      platform = current_platform,
    })
    -- skip empty path entries
    if #path>0 then
      -- make a substitution for each module file path.
      for _, raw_module_path in ipairs(module_paths) do
        local module_path = path:gsub("{(%w+)}", {
          module = raw_module_path
        })
        -- add path for binary module
        cpaths[#cpaths+1] = module_path:gsub("{(%w+)}", {
          extension = current_clib_extension
        })
        -- add paths for platform independent lua and luac modules
        lpaths[#lpaths+1] = module_path:gsub("{(%w+)}", {
          extension = 'lua'
        })
        lpaths[#lpaths+1] = module_path:gsub("{(%w+)}", {
          extension = 'luac'
        })
      end
    end
  end
  -- build module path list delimited with semicolon.
  package.path = table.concat(lpaths, ";")
  package.cpath = table.concat(cpaths, ";")
end

With this design, you can easily manage your module paths just by editing paths and module_paths tables.

Keep in mind that you need to execute this code before any require command.

How it works…

This recipe builds content for two variables that are used in the require function—package.path and package.cpath.

Both variables use a semicolon as a delimiter for individual paths. There's also a special character—the question mark which is substituted with the module name. Note that the path order might not be as important in this case as with our default list of paths. The path order might cause problems if you expect to use a module out of the project directory structure. Therefore, a customized set of paths from this recipe should always be used before the default set of paths.

The Lua language allows the use of hierarchical structure of modules. You can specify a submodule with package names delimited by a dot.

require 'main_module.submodule'

A dot is always replaced with the correct directory separator.

 

Making a stack


Stack data structure can be defined in the Lua language as a closure that always returns a new table. This table contains two functions defined by keys, push and pop. Both operations run in constant time.

Getting ready

Code from this recipe will be probably used more than once in your project so that it can be moved into the Lua module file with similar algorithms. The module file can use the following structure:

-- algoritms.lua

-- Placeholder for a stack data structure code 

return {
  stack = stack,
}

This module structure can be used with algorithms from other recipes as well to keep everything organized.

How to do it…

The following code contains a local definition of the stack function. You can remove the local statement to make this function global or include it as part of the module:

local function stack()
  local out = {}
  out.push = function(item)
    out[#out+1] = item
  end
  out.pop = function()
    if #out>0 then
      return table.remove(out, #out)
    end
  end
  out.iterator = function()
    return function()
      return out.pop()
    end
  end
  return out
end

This stack data structure can be used in the following way:

local s1 = stack()
-- Place a few elements into stack
for _, element in ipairs {'Lorem','ipsum','dolor','sit','amet'} do
  s1.push(element)
end

-- iterator function can be used to pop and process all elements
for element in s1.iterator() do
     print(element)
end

How it works…

Calling the stack function will create a new empty table with three functions. Push and pop functions use the property of the length operator that returns the integer index of the last element. The iterator function returns a closure that can be used in a for loop to pop all the elements. The out table contains integer indices and no holes (without empty elements). Both the functions are excluded from the total length of the out table.

After you call the push function, the element is appended at the end of the out table. The Pop function removes the last element and returns the removed element.

 

Making a queue


The queue data structure can be constructed in a similar way as a stack with the table.insert and table.remove functions. However, this will add unnecessary overhead because each element insertion at the beginning of the list will need to move other elements as well. A better solution is using two indices that indicate the beginning and the end of the list.

Getting ready

The code from this recipe can be placed into the algorithms.lua file as in the Making a stack recipe.

How to do it…

The queue data structure will consist of a constructor that returns a new table with three functions: a push, a pop, and an iterator. The resulting table uses the modified version of the length operator to get the right length of the queue:

local function queue()
  local out = {}
  local first, last = 0, -1
  out.push = function(item)
    last = last + 1
    out[last] = item
  end
  out.pop = function()
    if first <= last then
      local value = out[first]
      out[first] = nil
      first = first + 1
      return value
    end
  end
  out.iterator = function()
    return function()
      return out.pop()
    end
  end
  setmetatable(out, {
    __len = function()
      return (last-first+1)
    end,
  })
  return out
end

A new queue data structure can be created by calling the queue function:

local q1 = queue()
-- Place a few elements into queue
for _, element in ipairs {'Lorem','ipsum','dolor','sit','amet'} do
  q1.push(element)
end

-- You can use iterator to process all elements in single for loop
for element in q1.iterator() do
  -- each queue element will be printed onto screen
  print(element)
end

How it works…

This algorithm uses a pair of integer indices that represent positions of the first and the last element of the queue. This approach provides element insertion and deletion in constant time. Because the original length operator isn't suitable for this case, a modified one is provided.

The iterator function creates a new closure that is used in a for loop. This closure is called repeatedly until the pop function returns an empty result.

 

Making a prioritized queue


A prioritized queue or simple priority queue extends basic queue with the entry sorting feature. Upon entry insertion, you can set what will be the priority of the entry. This data structure is often used in job queuing where the most important (highest priority) jobs must be processed before the jobs with lower priority. Priority queues are often used in artificial intelligence as well.

This version of the prioritized queue allows you to obtain entries with minimal or maximal priority at constant time. Element priority can be updated. However, priority queue insertion, update, and removal might use linear time complexity in worst case scenarios.

There are two rules that should be noted:

  • Each entry of this queue should be unique

  • The order of retrieving elements with the same priority is not defined

Getting ready

This recipe will use the following shortcuts:

local ti = table.insert
local tr = table.remove

-- removes element from table by its value
local tr2 = function(t, v)
  for i=1,#t do
    if t[i]==v then
      tr(t, i)
      break
    end
  end
end

It's recommended to put it all together in one Lua module file.

How to do it…

The priority queue can be defined as in the following code:

return function pqueue()
  -- interface table
  local t = {}

  -- a set of elements
  local set = {}
  -- a set of priority bags with a elements
  local r_set = {}
  -- sorted list of priorities
  local keys = {}

  -- add element into storage, set its priority and sort keys
  --  k - element
  --  v - priority
  local function addKV(k, v)
    set[k] = v
    -- create a new list for this priority
    if not r_set[v] then
      ti(keys, v)
      table.sort(keys)
      local k0 = {k}
      r_set[v] = k0
      setmetatable(k0, {
        __mode = 'v'
      })
    -- add element into list for this priority
    else
      ti(r_set[v], k)
    end
  end

  -- remove element from storage and sort keys
  local remove = function(k)
    local v = set[k]
    local prioritySet = r_set[v]
    tr2(prioritySet, k)
    if #prioritySet < 1 then
      tr2(keys, v)
      r_set[v] = nil
      table.sort(keys)
      set[k] = nil
    end
  end; t.remove = remove

  -- returns an element with the lowest priority
  t.min = function()
    local priority = keys[1]
    if priority then
      return r_set[priority][1] or {}
    else
      return {}
    end
  end

  -- returns an element with the highest priority
  t.max = function()
    local priority = keys[#keys]
    if priority then
      return r_set[priority][1] or {}
    else
      return {}
    end
  end

  -- is this queue empty?
  t.empty = function()
    return #keys < 1
  end
  -- simple element iterator, retrieves elements with
  -- the highest priority first
  t.iterate = function()
    return function()
      if not t.empty() then
        local element = t.max()
        t.remove(element)
        return element
      end
    end
  end
  -- setup pqueue behavior
  setmetatable(t, {
    __index = set,
    __newindex = function(t, k, v)
      if not set[k] then
        -- new item
        addKV(k, v)
      else
        -- existing item
        remove(k)
        addKV(k, v)
      end
    end,
  })
  return t
end

How it works…

This priority queue algorithm uses three tables: set, r_set, and keys. These tables help to organize elements into so-called priority bags. The first one, set contains elements paired with their priorities. It's also used when you try to obtain element priority from the queue. The second one, r_set contains priority bags. Each bag represents a priority level. The last one keys contains a sorted list of priorities, which is used in the extraction of elements from a minimal or maximal priority bag.

Each element can be inserted in a way similar to the Lua table with the exception that the inserted element is used as a key and priority is stored as a value:

priority_queue[element] = priority

This form of access can be used to update element priority. Elements with minimal or maximal priority can be extracted using the min or max function respectively;

local min_element = priority_queue.min()
local max_element = priority_queue.max()

Note that elements remain in the priority queue until you delete them with the remove function;

priority_queue.remove(element)

Priority queue can be queried with the empty function that returns true if there's no element in the queue;

priority_queue.empty()

You can use the iterator function in for loop to process all queue elements sorted by their priority:

for element in priority_queue.iterator() do
  -- do something with this element
end
 

Extending ipairs for use in sparse arrays


The ipairs function in the Lua language is used to iterate over entries in a sequence. This means every entry must be defined by the pair of key and value, where the key is the integer value. The main limitation of the ipairs function is that the keys must be consecutive numbers.

You can modify the ipairs function so that you can successfully iterate over entries with integer keys that are not consecutive. This is commonly seen in sparse arrays.

Getting ready

In this recipe, you'll need to define our own iterator function, which will return every entry of a sparse array in deterministic order. In this case, the iterator function can be included in your code as a global function to accompany pairs and ipairs functions; or you can put it in a Lua module file not to pollute the global environment space.

How to do it…

This code shows a very simple sparse array iterator without any caching:

function ipairs_sparse(t)
  -- tmpIndex will hold sorted indices, otherwise
  -- this iterator would be no different from pairs iterator
  local tmpIndex = {}
  local index, _ = next(t)
  while index do
    tmpIndex[#tmpIndex+1] = index
    index, _ = next(t, index)
  end
  -- sort table indices
  table.sort(tmpIndex)
  local j = 1

  return function()
    -- get index value
    local i = tmpIndex[j]
    j = j + 1
    if i then
      return i, t[i]
    end
  end
end

The following lines of code show the usage example for iteration over a sparse array;

-- table below contains unsorted sparse array
local t = {
  [10] = 'a', [2] = 'b', [5] = 'c', [100] = 'd', [99] = 'e',
}
-- iterate over entries
for i, v in ipairs_sparse(t) do
  print(i,v)
end

How it works…

The Lua language uses iterator functions in the control structure called the generic for. The generic for calls the iterator function for each new iteration and stops when the iterator function returns nil. The ipairs_sparse function works in the following steps:

  1. It builds a new index of keys from the table.

  2. It sorts the index table.

  3. It returns a closure where each call of the closure returns a consecutive index and a value from the sparse array.

Each call to ipairs_sparse prepares a new index table called index. The index consists of (integer, entry) pairs.

 

Creating Lua modules


The Lua language doesn't impose strict policies on what a module should look like. Instead, it encourages programmers to find their own style depending on the situation.

Getting ready

In this recipe, you will create three versions of a module that contains one local variable, one variable accessible from outside the module, one function that returns a simple value, and a function that uses a value from the current module.

How to do it…

There are three types of modules that are commonly used:

  • A module that returns a table as a module interface

  • A module in the form of an object

  • A module in the form of a singleton object

The first case is used mostly with modules that contain an interface to third-party libraries. The second type of module is used less often, but it's useful if you need multiple instances of the same object, for example, a network stack. The last one uses a similar approach as in the previous case, but this time there's always only one instance of the object. Many games use the singleton object for the resource management system.

A module that returns a table as an interface

In this case, the module uses locally defined variables and functions. Every function intended for external use is put into one table. This common table is used as an interface with the outer world and is returned at the end of the module:

-- module1.lua
local var1 = 'ipsum'
local function local_function1()
  return 'lorem'
end

local function local_function2(self)
  return var1 .. self.var2
end
-- returns module interface
return {
  lorem = local_function1,
  ipsum = local_function2,
  var2 = 'sit',
}

A module in the form of an object

This module type doesn't manipulate the global namespace. Every object you create uses its own local namespace:

-- module2.lua
local M = function()
  local instance
  local var1 = 'ipsum'
  instance = {
    var2 = 'sit',
    lorem = function()
      return 'lorem'
    end,
    ipsum = function(self)
      return var1 .. self.var2
    end,
  }
  return instance
end

return M

A module in the form of a singleton object

This is a special case of object module. There is only one and the same object instance:

-- module3.lua
local instance

local M = function()
  if not instance then
    local var1 = 'ipsum'
    instance = {
      var2 = 'sit',
      lorem = function()
        return 'lorem'
      end,
      ipsum = function(self)
        return var1 .. self.var2
         end,
    }
  end
  return instance
end

return M

How it works…

Modules are used with the require function that registers them in the global table modules.loaded. This table contains the compiled code of every module used and ensures that each module is loaded only once.

Object modules return a local variable M, which contains an object interface. However, you can use any other name for this variable. Choosing between tables or closure as object contained is mostly a matter of application design.

Variable var1 is always hidden from the outside world and can be changed only by the exposed function. Variable var2 is freely accessible and can be modified anytime.

The following lines of code show the usage patterns for all three types of module:

local module1 = require 'module1'
local module2 = require 'module2'
local module3 = require 'module3'
-- create two instances of module2
local module2_A = module2()
local module2_B = module2()
-- try to create an instance of module2 twice
local module3_A = module3()
local module3_B = module3()

-- usage of a module with interface table
print('Module 1 - Before:', module1:lorem() .. module1:ipsum())
module1.var2 = 'amet'
print('Module 1 - After:', module1:lorem() .. module1:ipsum())

-- usage of a module in a form of an object
print('Module 2a - Before:', module2_A:lorem() .. module2_A:ipsum())
module2_A.var2 = 'amet'
print('Module 2a - After:', module2_A:lorem() .. module2_A:ipsum())
print('Module 2b - After:', module2_B:lorem() .. module2_B:ipsum())

-- usage of a module in a form of a singleton object
print('Module 3a - Before:', module3_A:lorem() .. module3_A:ipsum())
module3_A.var2 = 'amet'
print('Module 3a - After:', module3_A:lorem() .. module3_A:ipsum())
print('Module 3b - After:', module3_B:lorem() .. module3_B:ipsum())
 

Handling errors with pcall, xpcall, and assert


By default, the Lua language uses its internal error function. If an error occurs, Lua will usually abort code execution and put the error message with trace back into the standard error output. You can override the standard behavior with the pcall and xpcall functions. The main difference between these two functions is that pcall will return the status code and error message as the second return value, and xpcall will use the user-defined error function.

This way you can catch nonfatal errors and emulate the try and catch block.

Getting ready

This recipe will show error handling on a simple function that can exit prematurely with the error message:

 local function f1(a, b)
  assert((a == 1), "The first parameter must be equal to 1")
  print(b)
  return a+1
end

How to do it…

Here's how you can catch a nonfatal error with pcall by emulating the try and catch block:

function try(fn, catch_fn)
  local status, msg = pcall(fn)
  if not status then
    catch_fn(msg)
  end
end

try(function()
  f1(2, 3) -- this will throw "an exception"
end, function(e)
  print('An exception occured:', e)
  error('Throw exception')
end)

The next recipe shows how to create your own specialized xpcall2 function that can handle input parameters for a function:

local function xpcall2(fn, ...)
  local arg = {...}
  return xpcall(
    -- function wrapper to pass function arguments
    function(...)
      return fn(unpack(arg))
    end,
    -- error function
    function(msg)
      return debug.traceback(msg, 3)
    end
  )
end

print(xpcall2(f1, 2, 'a'))

How it works…

The whole principle of the try and catch block emulation in Lua relies on the pcall function that catches the error message and pushes it into the catch block function.

The only weakness of this approach is that you can't get more information because you're handling errors outside of the scope of where the error occurred.

This issue can be solved with xpcall which handles error before stack unwinding so you can use the debug library to get more information about the error.

Xpcall2 works as a wrapper function that not only passes parameters into protected function calls, but also handles getting the trace back with the debug.traceback function and returns results or a status code with an error message.

 

Using Lua with existing projects written in C/C++


The Lua language provides a set of API functions for communication between C/C++ and the Lua programming language. You can use these functions in your C code after you include the lua.h header file or lua.hpp for C++ source code. You can rely on this set of functions, but sooner or later you'll see that there are certain usage patterns which can be used to simplify your C/C++ code or just simply make it more readable and error prone. This is especially true if you want to expose C++ objects and structures to the Lua language environment.

Fortunately, there is the Lutok2 library to help you with that. It consists of a set of header files and a freely based previous project called Lutok from Julio Merino.

This chapter will cover how to use Lutok2 library as the C++ API library for the Lua language and as a C++ class wrapper so that you can easily manage making your own extensions in the future. You'll also see that this library is used in many other libraries to cover access to multimedia devices from the Lua language.

Getting ready

The first thing you need to do before starting is to get the Lua binary library and the header files. After this step, you can download the Lutok2 header files and use them in your project.

On Windows:

  1. Download the Lua binary files from https://code.google.com/p/luaforwindows/downloads/list.

  2. Download and unzip the Lutok2 source code from https://github.com/soulik/lutok2/archive/master.zip or get a clone of the repository with the git command:

    git clone https://github.com/soulik/lutok2.git
    

On Linux:

  1. Use your package manager to install the Lua developer package or build the Lua binary library from source code at http://www.lua.org/ftp/lua-5.1.5.tar.gz.

  2. Download and unzip the Lutok2 source code from https://github.com/soulik/lutok2/archive/master.zip or get a clone of the repository with the git command:

    git clone https://github.com/soulik/lutok2.git
    

In both cases, you'll need the C++ compiler that can handle C++11 version of the standard C++ language. You can use clang or gcc compiler under the Unix-like environment, or the recent version of Microsoft Visual C++.

How to do it…

In the following steps, you'll see most common scenarios of using the Lua language in your C/C++ project. Most of the Lutok2 code samples are paired with equivalent Lua C API code so that you can see what the equivalent C code looks like without using the Lutok2 library.

Initializing the Lua state

This is how you initialize a Lua state with Lutok2:

#include <lutok2/lutok2.hpp>

int main(int argc, char ** argv){
  lutok2::State state;
  return 0;
}

This is how you do the same with Lua C API:

#include <lua.hpp>

int main(int argc, char ** argv){
  lua_State * state = luaL_newstate();
  return 0;
}

Creating a Lua module in C/C++

With Lutok2:

#include <lutok2/lutok2.hpp>

using namespace lutok2;
   
   /* A function to be exposed should always use following form:
    * Input argument : A reference to Lutok2 State object
    * Output variable: An integral number of return values
    */ 
int lua_example_myfunction(State & state){
  // C/C++ code to be invoked
  return 0;
}

extern "C" LUA_API int luaopen_example(lua_State * current_state){
  State * state = State(current_state);
  Module myModule;
  /* Expose lua_example_myfunction function in
   * Lua language environment.
   * Key value represents a function name in Lua.
   * Value should always be a function pointer.
   */
  myModule["myfunction"] = lua_example_myfunction;
  
  /* This module will return a Lua table
   * that exposes all functions listed in myModule.
   */
  state->stack->newTable();
  state->registerLib(myModule);
  return 1;
}

With Lua C API:

#include <lua.hpp>

static int lua_example_myfunction(lua_State * L){
  return 0;
}

static const struct luaL_Reg module[] = {
  {"myfunction", lua_example_myfunction},
  {NULL, NULL}
};

extern "C" LUA_API int luaopen_example(lua_State * L){
  lua_newtable(L);
  luaL_register (L, NULL, module);
  return 1;
}

Passing variables from C/C++ into the Lua environment

With Lutok:

int lua_example_myfunction(State & state){
  void * userData = (void*)123456789;
  Stack * stack = state->stack;

  stack->push<bool>(true);
  stack->push<int>(12345);
  stack->push<LUA_NUMBER>(12345.6789);
  stack->push<const std::string &>("A text");
  stack->push<void*>(userData);
  stack->newTable();
    stack.setField<bool>("boolean", false);
    stack.setField<int>("integer", (int)12345);
    stack.setField<LUA_NUMBER>("number", (lua_Number)12345.6789);
    stack.setField<const std::string &>("string", "A text");
    stack.setfield<void *>("userData", userData);
  return 6;
}

With Lua C API:

static int lua_example_myfunction(lua_State * L){
  void * userData = (void*)123456789;
  lua_pushboolean(L, (int)true);
  lua_pushinteger(L, 12345);
  lua_pushnumber(L, 12345.6789);
  lua_pushstring(L, "A text");
  lua_pushlightuserdata(L, userData);
  lua_newtable(L);
    lua_pushboolean(L, (int)false);
    lua_setfield(L, -2, "boolean");
    lua_pushinteger(L, 12345);
    lua_setfield(L, -2, "integer");
    lua_pushnumber(L, 12345.6789);
    lua_setfield(L, -2, "number");
    lua_pushstring(L, "A text");
    lua_setfield(L, -2, "A text");
    lua_pushlightuserdata(L, userData);
    lua_setfield(L, -2, "userData");
  return 6;
}

Passing variables from the Lua environment to C/C++

To get a variable from the Lua environment, you need to call the corresponding Lua C API function lua_to*, for example, lua_tointeger(L, index). You can use the corresponding version of the luaL_check*(L, index) function to obtain a value with additional checks for the correct data type.

Lutok2 provides a similar mechanism where you can use a template form of the to function:

state.stack->to<DATA_TYPE_NAME>(index);

The part DATA_TYPE_NAME presents a name of the target data type and the index value is a position of the variable in the registry. The first function parameter is at index 1. The second one is at index 2, and so on.

The example code for Lutok2 C++ API is as follows:

int integerValue = state.stack->to<int>(1);

The example code for the plain Lua C API is as follows:

int integerValue = luaL_checkinteger(L, 1);

Making the C++ class accessible from Lua with Lutok2

Let's assume that your class is defined in the ExampleObject.hpp header file. In this minimal case, the class contains one numerical property and one member function that returns a string value. The header file will contain the following lines of code:

#ifndef EXAMPLE_OBJECT_H
#define EXAMPLE_OBJECT_H

#include <string>

class ExampleObject {
public:
  int x;
  inline const std::string helloWorld(){
    return "Hello world";
  }
};
#endif

Now, you'll need to create a class declaration and implementation of the C++ class wrapper. You should always use a reasonable name for the wrapper class so that it is clear what class is actually handled. This example uses the name LuaExampleObject.

The header file will be called LuaExampleObject.hpp and it will contain a declaration for the LuaExampleObject class. This header file contains the following lines:

#ifndef LUA_EXAMPLE_OBJECT_H
#define LUA_EXAMPLE_OBJECT_H

#include "ExampleObject.hpp"

class LuaExampleObject : public Object<ExampleObject> {
public:
  explicit LuaExampleObject(State * state) :
    Object<ExampleObject>(state){
    /* Properties handle access to member variables
     * with getter and setter functions
     */
    LUTOK_PROPERTY("x", &LuaExampleObject::getX, &LuaExampleObject::setX);
    // Methods allow you to call member functions
    LUTOK_METHOD("helloWorld",
      &LuaExampleObject::helloWorld);
  }
  
  ExampleObject * constructor(State & state, bool & managed);
  void destructor(State & state, ExampleObject * object);
  int getX(State & state, ExampleObject * object);
  int setX(State & state, ExampleObject * object);
  int helloWorld(State & state, ExampleObject * object);
};
#endif

The implementation file will be called LuaExampleObject.cpp and it will consist of the main code that handles access to member variables and functions:

#include <lutok2/lutok2.hpp>
using namespace lutok2;

#include "LuaExampleObject.hpp"

ExampleObject * LuaExampleObject::constructor(State & state, bool & managed){
  return new ExampleObject;
}

void LuaExampleObject::destructor(State & state, ExampleObject * object){
  delete object;
}

int LuaExampleObject::getX(State & state, ExampleObject * object){
  state.stack->push<int>(object->x);
  return 1;
}

int LuaExampleObject::setX(State & state, ExampleObject * object){
  if (state.stack->is<LUA_TNUMBER>(1)){
    object->x = state.stack->to<int>(1);
  }
  return 0;
}

int LuaExampleObject::helloWorld(State & state, ExampleObject * object){
  state.stack->push<const std::string &>(object->helloWorld());
  return 1;
}

Furthermore, to finish while process, you'll need to register a Lua interface. This is usually done during the module initialization routine. The following code sample shows how to prepare a C++ source file with module initialization. This module will return a table that is used in the Lua script as an interface to create an instance of ExampleObject. Note that this module doesn't register itself in the global variable space. This is currently the preferred way of using modules in the Lua language.

The main module file will be called ExampleModule.cpp and will consist of the following lines:

#include <lutok2/lutok2.hpp>
using namespace lutok2;

#include "LuaExampleObject.hpp"

extern "C" LUA_API int luaopen_example(lua_State * current_state){
  // State object is freed automatically after Lua state closes!
  State * state = new State(current_state);
  Stack * stack = state->stack;

  // Prepare main module interface table
  stack->newTable();
  
  /* Object interface registration always returns Lua function
   * with object constructor.
   */
  state->registerInterface<LuaExampleObject>(
    "LuaExample_ExampleObject");
  /* A new instance of ExampleObject can be obtained by
   * calling ExampleObject function from main interface table.
   */
  stack->setField("ExampleObject");
  return 1;
}

Now, you can include all source code in your C++ project in your favorite IDE and compile them into the binary library.

Don't forget to move the resulting binary library with the module to the working directory with your Lua script or anywhere the Lua interpreter can find it.

Finally, your Lua script will look like this:

local exampleModule = require 'Example'

local example_object = exampleModule.ExampleObject()
example_object.x = 5
print(example_object.x)
print(example_object.helloWorld())

This will create a new ExampleObject instance, sets and gets content of member variable; in the final step, it calls the member function that returns a string value.

How it works…

The Lutok2 library contains most of the commonly used Lua C API functions while sanitizing access to class objects.

The core of this library is divided into two sections:

  • Functions that manage Lua states and Lua modules

  • Functions that manage Lua stack content

All Lutok2 classes are encapsulated in Lutok2 namespace, so there should be no naming conflicts with other libraries. It contains automatic management of the Lua state for these use cases:

  • Lua modules

  • A standalone application that creates a Lua state during runtime

Class wrapper objects use the template form of lutok2::Object as a generic base class that handles most of the work in order to register the class interface in the current Lua environment. The template parameter is mandatory and specifies what class will be wrapped.

lutok2::Object<CLASS_NAME>

The constructor of the class wrapper must always be present, as it defines what methods or properties will be available in the Lua environment. It's called automatically during interface registration. The body of the constructor usually consists of several macros that specify the class members. There are two macros you can use, which are as follows:

  • LUTOK_METHOD(LUA_NAME, FUNCTION)

  • LUTOK_PROPERTY(LUA_NAME, FUNCTION, FUNCTION)

The member functions are defined by their name as a string value and a function pointer. The member variables use a similar notation while using two function pointers. The first one points to the getter function that returns a value of the member variable. The second one points to the setter function that sets the value of the member variable. If you don't want to allow the member variable modification, you can use the pointer to the nullMethod function instead of your own setter function. This will effectively block any changes to the member variable from the Lua script.

Another part of the C++ class wrapper is the functions that manage object instance creation and destruction—constructor and destructor. The constructor method is called when you actually call the object constructor in the Lua environment and it gives you space to actually create a new object instance. There's a reference to the managed argument, which you can change to the false value if the object instance is managed elsewhere. This will also cause that destructor method won't be called upon garbage collection in the Lua environment:

CLASS_NAME * constructor(State & state, bool & managed);

The destructor method is called when the object is freed in the Lua environment during garbage collection. This is the place where you can clean up and free up the object's instance:

  void destructor(State & state, CLASS_NAME * object);
 

Getting LuaSDL for libSDL 1.2


LuaSDL is a binding to the famous libSDL 1.2 multimedia library. LuaSDL provides an interface not only to libSDL itself but also to the SDL_image, SDL_mixer, SDL_net, SDL_ttf, and SDL_gfx libraries.

Getting ready

The LuaSDL module depends on many external libraries. To make the module preparation easier, there's a LuaSDL project repository at GitHub with support for the CMake building system. This project repository contains all the necessary external libraries, which make module building process a breeze.

You'll need to have the following software installed to successfully build the LuaSDL module:

  • The git version control system is available at https://git-scm.com/

  • The CMake building system version 3.1 or newer is available at http://www.cmake.org/

  • The C/C++ compiler, Microsoft Visual Studio, GCC, and clang are supported

  • The Netwide Assembler—NASM (optional) used only for CPU-specific optimizations is available at http://www.nasm.us/

How to do it…

The building process of LuaSDL is the same on both Windows and Linux operating systems with minor differences:

  1. Download the LuaSDL source code with the git command:

    git clone --recursive https://github.com/soulik/LuaSDL.git
    
  2. You need to create an empty working directory, which will contain the project and binary files:

    mkdir LuaSDL/build
    cd LuaSDL/build
    
  3. Now, you can run the CMake building tool to prepare the project files or to make further changes to the building configuration:

    cmake ..
    
  4. After successful preparation of the building environment, you can either open the project file in your IDE or simply run the make command to start the compilation depending on the compiler of your choice:

    • For Microsoft Visual Studio—use the luasdl.sln file

    • For Linux users—use the make command

  5. The successful compilation will result in binary files stored in the bin and lib directories.

Note that Microsoft Visual Studio will generate the .dll binary files in the bin directory, whereas, Linux users will find the same binary files in the lib directory. You can copy all the binary files into one directory where your Lua application resides. This will ensure that the Lua interpreter finds all the necessary binary modules.

There's more…

LuaSDL uses libSDL 1.2, which is more than 5 years old. Despite being no longer maintained, the LuaSDL package is considered as stable.

LuaSDL2 is being developed at the time of writing. You can access the source code from GitHub at https://github.com/soulik/LuaSDL-2.0.git.

 

Designing the main application loop with LuaSDL


LuaSDL offers an event-based application design. LibSDL uses a window to capture input events. Without it, you wouldn't be able to detect key strokes, mouse movement and, of course, you will be without any graphical output.

Getting ready

Before running your Lua script with LuaSDL, make sure you've got the LuaSDL module with all the dependencies in the same directory. However, you can always override the PATH environmental variable so that your application can find the necessary libraries. Note that the path environment variable is managed in the operating system and it's not for the Lua module location!

How to do it…

First, you must load the LuaSDL module file. If this step fails, you are either missing the correct libSDL library or one of the dependencies:

require 'LuaSDL'

You can define your own assertion check function with the optional SDL error reporting:

function SDL_assert(condition, msg)
  local msg = (msg and msg .. ": " .. SDL.SDL_GetError())
  return assert(condition, msg)
end

You have to set up the default window parameters as follows:

local window_x_pos = 128
local window_y_pos = 128
local window_width = 640
local window_height = 480
local window_bpp = 0
local window_flags = 0

LuaSDL must be initialized with SDL.SDL_Init before any use:

SDL_assert(SDL.SDL_Init(SDL.SDL_INIT_EVERYTHING) >= 0, "Couldn't initialize SDL")

LuaSDL places the window randomly upon creation. You can override this by setting the environmental variable SDL_VIDEO_WINDOW_POS:

SDL.SDL_putenv(string.format("SDL_VIDEO_WINDOW_POS=%d,%d",window_x_pos, window_y_pos))

To set a window caption, you have to call the SDL.SDL_WM_SetCaption function:

SDL.SDL_WM_SetCaption("LuaSDL Example", "Example")

Now, you can create a window with the SDL.SDL_SetVideoMode function, which returns the SDL_Surface object as a userdata:

local screen = SDL_assert(SDL.SDL_SetVideoMode(window_width, window_height, window_bpp, window_flags))

You can use the event loop invariant or just use a break statement on the application exit:

local running = true

It's more efficient to use the table of event functions than the usual approach with the if, elseif, and else statements. Each table key is an integer constant in this case:

local events = {
  [SDL.SDL_MOUSEMOTION] = function(event_struct)
    local event_struct = event_struct.motion
    -- do something when this event occurs
  end,
  [SDL.SDL_MOUSEBUTTONDOWN] = function(event_struct)
    local event = event_struct.button
  end,
  [SDL.SDL_MOUSEBUTTONUP] = function(event_struct) 
    local event = event_struct.button
  end,
  [SDL.SDL_KEYDOWN] = function(event_struct) 
    local event = event_struct.key
    local key = event_struct.keysym.sym
  end,
  [SDL.SDL_KEYUP] = function(event_struct) 
    local event = event_struct.key
    local key = event_struct.keysym.sym
  end,
  [SDL.SDL_VIDEORESIZE] = function(event_struct) 
      local event = event_struct.resize
  end,
  [SDL.SDL_QUIT] = function(event_struct) 
      running = false
  end,
}
-- prepare local instance of SDL_Event object
local local_event = SDL.SDL_Event_local()

while (running) do
  -- check for events in the poll
  if (SDL.SDL_PollEvent(local_event)~=0) then
    local event_fn = events[local_event.type]
    if type(event_fn)=='function' then
      event_fn(local_event)
    end
  end
end

The SDL.SDL_Quit function ensures that all the temporary objects are freed upon application exit:

SDL.SDL_Quit()

How it works…

Using the LuaSDL library must follow a certain pattern to ensure correct management of resources and events. Events are used as a primary source of information about user interaction with your application. The LuaSDL library can detect mouse movement, keyboard input, change of application window size, and a request to close your application.

The whole process of event processing uses event polling where you use the SDL.SDL_PollEvent function in each step of the main application loop to obtain information about the event that has occurred. This function may return a value 0 if there are no events to process. In this case, you can use this free time to process game logic. This recipe uses a skeleton table for event processing functions called events.

The most important event, SDL.SDL_QUIT, is received when the user closes the application window. You should clean up all the resources and LuaSDL state as well. Don't forget to use the SDL.SDL_Quit function before exiting your application to ensure all the previously used memory is freed.

About the Author

  • Mário Kašuba

    Mário Kašuba achieved a master's degree in applied informatics at Slovak Technical University in Bratislava, where he used the Lua language in 3D robotics simulations and developed various multimedia applications along with a few computer games.

    Currently, he is the co-owner and chief information officer of an IT Academy company, while he also leads courses on C/C++, PHP, Linux, Databases, Typo3, Silverstripe CMS, VMware virtualization, and the Microsoft Windows Server operating system.

    He also works as the head web developer and system administrator for the web portal http://www.rodinka.sk/.

    Browse publications by this author

Latest Reviews

(4 reviews total)
Interested in game programming on lua.
Excellent
It looks good. But IDE required