Book Image

Lua Game Development Cookbook

By : Mario Kasuba, Mário Kašuba
Book Image

Lua Game Development Cookbook

By: Mario Kasuba, Mário Kašuba

Overview of this book

Table of Contents (16 chapters)
Lua Game Development Cookbook
Credits
About the Author
About the Reviewer
www.PacktPub.com
Preface
Index

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.