Search icon CANCEL
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Learning game AI programming with Lua

You're reading from   Learning game AI programming with Lua Leverage the power of Lua programming to create game AI that focuses on motion, animation, and tactics

Arrow left icon
Product type Paperback
Published in Nov 2014
Publisher Packt
ISBN-13 9781783281336
Length 352 pages
Edition 1st Edition
Languages
Arrow right icon
Toc

Table of Contents (11) Chapters Close

Preface 1. Getting Started with AI Sandbox FREE CHAPTER 2. Creating and Moving Agents 3. Character Animations 4. Mind Body Control 5. Navigation 6. Decision Making 7. Knowledge Representation 8. Perception 9. Tactics Index

Introduction to AI sandbox

AI sandbox is a framework that is designed to do away with the tedious job of application management, resource handling, memory management, and Lua binding, so that you can focus immediately on creating AI in Lua. While the sandbox does the dirty work of a small game engine, none of the internals of the sandbox are hidden. The internal code base is well documented and explained here so that you can expand any additional functionality your AI might require.

The design behind the sandbox is a culmination of open source libraries preassembled in order to rapidly prototype and debug Lua's scripted AIs. While C++ code maintains and manages the data of AI, Lua scripts manage AI's decision-making logic. With a clear separation of data and logic, the logic represented in Lua scripts can be rapidly iterated on without worrying about corrupting or invalidating the current state of AI.

Understanding the sandbox

Before diving head-first into creating AI, this chapter goes over the internals and setup of the sandbox. While the AI scripting will all take place in Lua, it is important to understand how Lua interfaces with the sandbox and what responsibilities remain in C++ compared to Lua.

The project layout

The sandbox is laid out to easily support individual applications while sharing the same media resources between them. A key project, which is demo_framework, provides all the common code used throughout the book. The only difference between each individual chapter's C++ code is the setup of which Lua sandbox script to execute. Even though the entire sandbox framework is available from the beginning of the book, each chapter will introduce new functionality within the sandbox incrementally:

bin
    x32/debug
    x32/release
    x64/debug
    x64/release
build (generated folders)
    projects
    Learning Game AI Programming.sln
decoda
lib (generated folders)
    x32/debug
    x32/release
    x64/debug
    x64/release
media
    animations
    fonts
    materials
    models
    packs
    particle
    programs
    shaders
    textures
premake
    premake
    SandboxDemos
src
    chapter_1_movement (example)
        include
        script
        src
    ogre3d (example)
        include
        src
    ...
tools
    decoda
    premake
vs2008.bat
vs2010.bat
vs2012.bat
vs2013.bat

Tip

Downloading the example code

You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.

Now we'll take a look at what each folder within the sandbox project contains:

  • The bin folder will contain all built executables, based on which build configuration is selected in Visual Studio. Both 32-bit and 64-bit versions of the sandbox can be built side by side without the need to rebuild the solution.

    Note

    While the sandbox can be built as both a 32-bit and 64-bit application, debugging Lua scripts is only supported in Decoda with 32-bit builds.

  • The build folder contains Visual Studio's solution file. The build/projects folder contains each individual Visual Studio project along with the solution. It is safe to delete the build folder at any time and regenerate the solution and project files with the vs2008.bat, vs2010.bat, vs2012.bat, or vs2013.bat batch files. Any changes made directly to the Visual Studio solution should be avoided, as regenerating the solution file will remove all the local changes.
  • The decoda folder contains the individual Decoda IDE project files that correspond to each chapter demo. These project files are not generated from build scripts.
  • The lib folder is an intermediate output folder where static libraries are compiled. It is safe to delete this folder, as Visual Studio will build any missing libraries during the next build of the sandbox.
  • The media folder contains all the shared assets used within the chapter demos. Assets used by the sandbox exist as both loose files and ZIP bundles.
  • The premake folder contains the build script used to configure the sandbox's solution and project files. Any desired changes to the Visual Studio solution or projects should be made in the Premake script.

    Note

    The Premake script will detect any additional C++ or Lua files within the project's folder structure. When adding a new Lua script or C++ files, simply rerun the build scripts to update the Visual Studio solution.

  • The src folder contains the source code for each open source library as well as the sandbox itself. Every project within the sandbox solution file will have a corresponding src folder, with separate folders for header files and source files. Chapter demos have an additional script folder that contains every Lua script for that particular chapter.

    Note

    Each open source library contains both a VERSION.txt and LICENSE.txt file, which states the version number of the open source library used as well as the license agreement that must be followed by all users.

  • The tools folder contains the installer for the Decoda IDE as well as the Premake utility program that is used to create the Visual Studio solution.

The Premake build

Premake is a Lua-based build configuration tool. AI sandbox uses Premake in order to support multiple versions of Visual Studio without the need to maintain build configurations across solutions and build configurations.

Executing the vs2008.bat batch file will create a Visual Studio 2008 solution within the build folder. Correspondingly, the vs2010.bat and vs2012.bat batch files will create Visual Studio 2010 and Visual Studio 2012 solutions of AI sandbox.

Compiling the sandbox with Visual Studio 2008/2010/2012/2013

Building the sandbox requires only one dependency, which is the DirectX SDK. Make sure that your system has the DirectX SDK installed, or download the free SDK from Microsoft at http://www.microsoft.com/en-us/download/details.aspx?id=6812.

The sandbox solution file is located at build/Learning Game AI Programming.sln once one of the vs2008.bat, vs2010.bat, vs2012.bat, or vs2013.bat batch files have been executed. The initial build of the sandbox might take several minutes to build all of the libraries the sandbox uses, and further builds will be much faster.

Open source libraries

Lua 5.1.5 is used by the sandbox instead of the latest Lua 5.2.x library. The latest build of the Decoda IDE's debugger only supports up to 5.1.x of Lua. Newer versions of Lua can be substituted into the sandbox, but Lua's debugging support will no longer function.

At the time of writing this, Ogre3D 1.9.0 is the latest stable build of the Ogre3D graphics library. A minimal configuration of Ogre3D is used by the sandbox, which only requires the minimum library dependencies for image handling, font handling, ZIP compression, and DirectX graphics support.

The included dependencies required by Ogre3D are:

  • FreeImage 3.15.4
  • FreeType 2.4.12
  • libjpeg 8d
  • OpenJPEG 1.5.1
  • libpng 1.5.13
  • LibRaw 0.14.7
  • LibTIFF 4.0.3
  • OpenEXR 1.5.0
  • Imbase 0.9.0
  • zlib 1.2.8
  • zzip 0.13.62

The sandbox was created with the DirectX SDK Version 9.29.1962, but any later version of DirectX SDK will work as well. Additional open source libraries providing debug graphics, input handling, physics, steering, and pathfinding are detailed as follows:

  • Ogre3D Procedural 0.2: This is a procedural geometry library that provides easy creation of objects such as spheres, planes, cylinders, and capsules used for debug- and prototyping-level creation within the sandbox.
  • OIS 1.3: This is a platform-agnostic library that is responsible for all the input handling and input device management used by the sandbox.
  • Bullet Physics 2.81-rev2613: This is the physics engine library that drives the AI movement and collision detection within the sandbox.
  • OpenSteer revision 190: This is a local steering library that is used to calculate steering forces of AI agents.
  • Recast 1.4: This provides the sandbox with runtime navigation mesh generation.
  • Detour 1.4: This provides A* pathfinding on top of generated navigation meshes.

Open source tools

Premake dev e7a41f90fb80 is a development build of Premake that is based on the Premake development branch. The sandbox's Premake configuration files utilize the latest features that are only present in the development branch of Premake.

Decoda 1.6 build 1034 provides seamless Lua debugging through an inspection of the sandbox's debug symbol files.

Lua IDE – Decoda

Decoda is a professional Lua integrated development environment (IDE) that was released as open source by Unknown Worlds Entertainment, makers of Natural Selection 2 (http://unknownworlds.com/decoda/).

Decoda takes a unique approach to Lua script debugging, which makes integrating with an application far superior to other Lua debuggers. Instead of using a networked-based approach where an internal Lua virtual machine must be configured to support debugging, Decoda uses the debug symbol files produced by Visual Studio in order to support Lua debugging. The key advantage of this approach is that it supports Lua debugging without requiring any changes to the original application. This key difference in Decoda allows for easy debug support in the sandbox's embedded Lua virtual machines.

Running AI sandbox inside Decoda

Begin by opening this chapter's Decoda project (decoda/chapter_1_movement.deproj). Each sandbox Decoda project is already set up to run the correct corresponding sandbox executable. To run the sandbox within Decoda, press Ctrl + F5, or click on the Start Without Debugging option from the Debug menu.

Setting up a new Decoda project

Setting up a new Decoda project only requires a few initial steps to point Decoda to the correct executable and debug symbols.

A new Decoda project can be configured with the following steps:

  1. Open the Settings menu under the Project menu, as shown in the following screenshot:
    Setting up a new Decoda project

    Accessing the Decoda project settings

  2. Set the Command textbox to point to the new sandbox executable.
  3. Set the Working Directory and Symbols Directory fields to the executable's directory.

    Note

    Decoda can only debug 32-bit applications where debug symbols are present. AI sandbox creates debug symbols in both Release and Debug build configurations.

The Project Settings screen can be seen as follows:

Setting up a new Decoda project

Setting the Decoda debug executable and working directory

Debugging Lua scripts

To begin debugging Lua scripts within the sandbox, press F5 within Decoda. F5 will launch the sandbox application and attach Decoda to the running process. Selecting Break from the Debug menu or setting a breakpoint over a running script will pause the sandbox for debugging.

Debugging Lua scripts

Debugging Lua scripts in Decoda

Decoda Watch window

If you're familiar with the Visual Studio Watch window, the Decoda Watch window is very similar. Type any variable within the Watch window to monitor that variable while debugging. Decoda also allows you to type in any arbitrary Lua statements within the Watch window. The Lua statement will be executed within the current scope of the debugger.

Decoda Watch window

Viewing local Lua variables in the Decoda Watch window

Decoda Call Stack window

The stack window shows you the currently executing Lua call stack. Double-click on any line to jump to the caller. The Watch window will be automatically updated based on the current scope specified by the call stack.

Decoda Call Stack window

Analyzing the Lua call stack in the Decoda Call Stack window

The Decoda Virtual Machines window

The Virtual Machines window shows you each of the Lua virtual machines the sandbox is running. In this case, there is a separate virtual machine for the sandbox and a separate virtual machine for each of the running agents.

The Decoda Virtual Machines window

Debugging multiple Lua virtual machines within Decoda

Simultaneous Lua and C++ debugging

To simultaneously debug both the C++ sandbox and any running Lua scripts, there are a few approaches available.

Visual Studio – Attach to Process

If the sandbox was launched from Decoda, you can always attach it to the running process from Visual Studio through the Debug menu's Attach to Process option.

Visual Studio – Attach to Process

Simultaneously debug the C++ sandbox when launching from Decoda

Decoda – Attach to Process

Decoda can also attach itself to a running process through the Debug menu. If the sandbox is run through Visual Studio, you can attach Decoda at any time in the same fashion that Visual Studio attaches it to the sandbox.

Decoda – Attach to Process

Simultaneously debug Lua scripts when launching the sandbox from Visual Studio

Decoda – Attach System Debugger

To automatically attach both Decoda and Visual Studio when the sandbox launches from Decoda, select the Attach System Debugger option from the Debug menu. Upon running the application from Decoda, Windows will prompt you to immediately attach a Just-In-Time (JIT) debugger.

Note

If your installed version of Visual Studio doesn't show up in the Just-In-Time debugger as a selectable option, enable JIT debugging for native applications from Visual Studio by navigating to Tools | Options | Debugging | Just-In-Time.

The following screenshot shows you the Debug option that we are accessing in order to attach the system debugger:

Decoda – Attach System Debugger

Automatically attach the Decoda debugger when launching the sandbox

Associating Lua scripts from code with Decoda

In order for Decoda to know which Lua file to associate the currently executing Lua script, the luaL_loadbuffer Lua API function must pass in the Lua filename as chunkName during the loading. The luaL_loadbuffer function is an auxiliary Lua helper function provided in lauxlib.h:

lauxlib.h

int luaL_loadbuffer(
    lua_State* luaVM, const char* buffer,
    size_t bufferSize, const char* chunkName);

The Lua virtual machine

The Lua virtual machine is represented by lua_State struct, which is defined in the lstate.h header file. Lua states are self-contained structures without the use for any global data, making them practical for threaded applications:

lstate.h

struct lua_State;

The sandbox runs multiple Lua virtual machines simultaneously. One main virtual machine is assigned to the sandbox itself, while each spawned agent runs its own separate virtual machine. The use of individual virtual machines comes to the sandbox at the cost of performance and memory but allows for iterating Lua scripts in real time on a per-agent basis.

The Lua stack

As Lua is a weakly typed language that supports functions that can receive an arbitrary amount of parameters as well as return an arbitrary number of return values, interfacing with C++ code is a bit tricky.

To get around the strong typing of C++, Lua uses a First In First Out (FIFO) stack to send and receive data from the Lua virtual machine. For example, when C++ wants to call a Lua function, the Lua function as well as the function parameters are pushed onto the stack and are then executed by the virtual machine. Any return values from the function are pushed back to the stack for the calling C++ code to handle.

The same process happens in reverse when the Lua code wants to call the C++ code. First, Lua pushes the C++ function onto the stack, followed by any parameters sent to the function. Once the code has finished executing, any return values are pushed back to the stack for the executing Lua script to handle.

The Lua stack

The Lua call stack indexing scheme

When interfacing with Lua, stack values can either be retrieved bottom-up, or top-down. The top element in the stack can be retrieved with an index of -1, while the bottom element of the stack can be retrieved with an index of 1. Additional elements can be retrieved by indexing -2, -3, 2, 3, and so on.

Note

Lua differs from most programming languages by index values beginning from 1 instead of 0.

Lua primitives

Lua has 8 basic primitives: nil, Boolean, number, string, function, userdata, thread, and table:

  • Nil: Here, a value corresponds to a null value in C.
  • Boolean: Here, values are equivalent to their C++ counterparts and represent either true or false.
  • Number: This internally represent doubles that Lua uses to store integers, longs, floats, and doubles.
  • String: This represents any sequence of characters.
  • Function: Lua also considers functions as a primitive, which allows you to assign functions to variables.
  • Userdata: This a special Lua type that maps a Lua variable to data managed by the C code.
  • Thread: A thread primitive is used by Lua to implement coroutines.
  • Table: This is an associative array that maps an index to a Lua primitive. A Lua table can be indexed by any Lua primitive.

Metatables

A metatable in Lua is a table primitive that allows custom functions to override common operations such as addition, subtraction, assignment, and so on. The sandbox uses metatables extensively in order to provide common functionality to the userdata that C++ manages.

To retrieve a metatable within Lua, use the getmetatable function on any object:

metatable getmetatable(object);

To set a metatable within Lua, use the setmetatable function that passes both the object to set and the metatable:

nil setmetatable(object, metatable);

Tip

As the sandbox heavily uses metatables for userdata, you can always use getmetatable on the userdata to see which operations a specific userdata type supports.

Metamethods

A metamethod is a particular entry within a metatable that is called when an overriden operation is being requested by Lua. Typically, all Lua metamethods will begin with two underscores at the beginning of the function's name.

To add a metamethod to a metatable, assign a function to the metatable indexed by the metamethod's name. For example:

local metatable = getmetatable(table);
metatable.__add = function(left, right)
    return left.value + right.value;
end
setmetatable(table, metatable);

Userdata

Userdata is an arbitrary block of data whose lifetime is managed by Lua's garbage collector. Whenever a code creates userdata to push into Lua, a block of memory managed by Lua is requested from lua_newuserdata:

lua.h

void* lua_newuserdata(lua_State* luaVM, size_t userdataSize);

While the sandbox makes extensive use of userdata, the construction and destruction of the allocated memory is still handled within the sandbox. This allows for Lua scripts to be easily iterated without worrying about Lua's internal memory management. For instance, when an agent is exposed to Lua through userdata, the only memory that Lua manages for the agent is a pointer to the agent. Lua is free to garbage collect the pointer at any time and has no effect on the agent itself.

C/C++ calling Lua functions

The sandbox hooks into the Lua script through three predefined global Lua functions: Sandbox_Initialize, Sandbox_Cleanup, and Sandbox_Update. When the sandbox is first attached to the corresponding Lua script, the Sandbox_Initialize function is called. Each update tick of the sandbox will also invoke the Sandbox_Update function in the Lua script. When the sandbox is being destroyed or reloaded, the Sandbox_Cleanup function will have an opportunity to perform any script-side cleanup.

In order for C++ to call a Lua function, the function must be retrieved from Lua and pushed onto the stack. Function parameters are then pushed on top of the stack, followed by a call to lua_pcall, which executes the Lua function. The lua_pcall function specifies the number of arguments the Lua function receives, the number of expected return values, and specifies how to handle errors:

lua.h

int lua_pcall(
   lua_State* luaVM, int numberOfArguments,
   int numberOfResults, int errorFunction);

For example, the Agent_Initialize Lua script function is called in the AgentUtilities class in the following manner:

Agent.lua

function Agent_Initialize(agent)
    ...
end

First, the Lua function is retrieved from Lua by name and pushed onto the stack. Next, the agent itself is pushed as the only parameter to the Agent_Initialize function. Lastly, lua_pcall executes the function and checks whether it succeeded successfully; otherwise, an assertion is raised by the sandbox:

AgentUtilities.cpp

void AgentUtilities::Initialize(Agent* const agent)
{
    // Retrieves the lua virtual machine the agent script is
    // running on.    lua_State* luaVM = agent->GetLuaVM();

    lua_getglobal(luaVM, "Agent_Initialize");

    // Agent_Initialize accepts one parameter, an Agent.
    AgentUtilities::PushAgent(luaVM, agent);

    // Execute the Agent_Initialize function and check for 
    // success.
    if (lua_pcall(luaVM, 1, 0, 0) != 0)
    {
        assert(false);
    }
}

Lua calling C/C++ functions

Exposing C++ functions to Lua takes place through a process called function binding. Any bound functions exposed to Lua become accessible either as a global function or as a function available through a package. Packages in Lua are similar to namespaces in C++ and are implemented as a global table within Lua.

Function binding

Any function exposed to Lua must fit the lua_CFunction declaration. A lua_CFunction declaration takes in the Lua virtual machine and returns the number of return values pushed onto the Lua stack:

lua.h

typedef int (*lua_CFunction) (lua_State *L);

For example, the C++ GetRadius function exposed in the sandbox is declared in the LuaScriptBindings.h header file in the following manner:

LuaScriptBindings.h

int Lua_Script_AgentGetRadius(lua_State* luaVM);

The actual function implementation is defined within the LuaScriptBindings.cpp file and contains the code for retrieving and pushing values back to the stack. The GetRadius function expects an agent pointer as the first and only parameter from Lua and returns the radius of the agent. The Lua_Script_AgentGetRadius function first checks the stack for the expected parameter count and then retrieves the userdata off the stack through a helper function within the AgentUtilities class. An additional helper function performs the actual work of calculating the agent's radius and pushes the value back onto the stack:

LuaScriptBindings.cpp

int Lua_Script_AgentGetRadius(lua_State* luaVM)
{
    if (lua_gettop(luaVM) == 1)
    {
        Agent* const agent = AgentUtilities::GetAgent(
            luaVM, 1);

        return AgentUtilities::PushRadius(luaVM, agent);
    }
    return 0;
}

To bind the function to Lua, we define a constant array that maps the function's name within Lua to the C function that should be called. The array of function mappings must always end with a null luaL_Reg type struct. Lua uses a null luaL_Reg type struct as a terminator when processing the function map:

AgentUtilities.cpp

const luaL_Reg AgentFunctions[] =
{
    { "GetRadius", Lua_Script_AgentGetRadius },
    { NULL, NULL }
};

The actual function binding to the Lua virtual machine takes place in the luaL_register helper function. The register function binds the table of function names to their corresponding C callback function. The package name is specified at this step and will be associated with each function within the mapping:

AgentUtilities.cpp

void AgentUtilities::BindVMFunctions(lua_State* const luaVM)
{
    luaL_register(luaVM, "Agent", AgentFunctions);
}

Note

If NULL is passed in as the package name, Lua requires that a table be at the top of the Lua stack. Lua will add the C functions to the Lua table at the top of the stack.

Creating custom userdata

While the sandbox uses userdata to pass around agents and the sandbox itself, another use of userdata is to add basic primitives into the sandbox. These primitives are completely controlled by Lua's garbage collector.

The vector primitive added into the sandbox is a good example of using userdata that is completely controlled by Lua. As a vector is essentially a struct that only holds three values, it is a great choice for Lua to completely maintain the creation and destruction of the data. What this means to the C++ code interacting with Lua vectors is that code should never hold on to the memory address returned from Lua. Instead, the code should copy values retrieved from Lua and store them locally.

Looking at the vector data type

Elevating a vector into a basic Lua primitive means supporting all the expected operations users would like to perform on a vector variable in Lua. This means that vectors should support the addition, subtraction, multiplication, indexing, and any other basic operators supported by Lua.

To accomplish this, the vector data type uses metamethods to support basic arithmetic operators, as well as supporting the dot operator for the ".x", ".y", and ".z" style syntax:

LuaScriptUtilities.cpp

const luaL_Reg LuaVector3Metatable[] =
{
    { "__add", Lua_Script_Vector3Add },
    { "__div", Lua_Script_Vector3Divide },
    { "__eq", Lua_Script_Vector3Equal },
    { "__index", Lua_Script_Vector3Index },
    { "__mul", Lua_Script_Vector3Multiply },
    { "__newindex", Lua_Script_Vector3NewIndex },
    { "__sub", Lua_Script_Vector3Subtract },
    { "__tostring", Lua_Script_Vector3ToString },
    { "__towatch", Lua_Script_Vector3ToWatch },
    { "__unm", Lua_Script_Vector3Negation },
    { NULL, NULL }
};

LuaScriptUtilities.h

#define LUA_VECTOR3_METATABLE "Vector3Type"

To support this functionality from the code, we need to let Lua know what type of userdata it is working with when we allocate memory. The LuaScriptUtilities header defines the metatable name of the vector type:

LuaScriptUtilities.cpp

void LuaScriptUtilities::BindVMFunctions(lua_State* const luaVM)
{
    ...

    luaL_newmetatable(luaVM, LUA_VECTOR3_METATABLE);
    luaL_register(luaVM, NULL, LuaVector3Metatable);

    ...
}

When binding C++ functions to the Lua virtual machine, an additional step is added to support vectors. The luaL_newmetatable function creates a new metatable, associating the table with the vector userdata type. Immediately after the metatable is created and pushed onto the Lua stack, a call to luaL_register adds the metamethods listed in LuaVector3Metatable to the metatable:

LuaScriptUtilities.cpp

int LuaScriptUtilities::PushVector3(
    lua_State* const luaVM, const Ogre::Vector3& vector)
{
    const size_t vectorSize = sizeof(Ogre::Vector3);

    Ogre::Vector3* const scriptType =
        static_cast<Ogre::Vector3*>(
            lua_newuserdata(luaVM, vectorSize));

    *scriptType = vector;

    luaL_getmetatable(luaVM, LUA_VECTOR3_METATABLE);
    lua_setmetatable(luaVM, -2);
    return 1;
}

Whenever a vector is created in Lua, memory is allocated from lua_newuserdata and the vector metatable is retrieved from Lua and assigned to the userdata. This allows Lua to know what type of userdata it is dealing with and what functions are supported on the userdata.

The demo framework

The demo framework follows a very simple update, initialization, and cleanup design shared throughout many of the classes within the sandbox.

The following is a class overview of the BaseApplication.h header:

The demo framework

The BaseApplication abstract class overview

The BaseApplication class has the main responsibility to configure the application window, process input commands, as well as configure and interface with Ogre3D. The Cleanup, Draw, Initialize, and Update functions are stub functions with no implementation within the BaseApplication class itself. Classes that inherit from BaseApplication can overload any of these functions in order to plug in their own logic:

  • In a derived class, the Initialize function is called once at the start of the application after Ogre has been initialized.
  • The Cleanup function is called when the application requests to be shut down right before Ogre itself gets cleaned up.
  • The Draw call is executed right before the graphics processing unit (GPU) renders the current application frame.
  • The Update function is called immediately after the GPU has queued up all the rendering calls in order to process the current frame. This allows the GPU to work simultaneously as the CPU begins to prepare the next draw frame.

Ogre

Ogre3D handles the general update loop and window management of the sandbox. BaseApplication implements the Ogre::FrameListener interface in order to implement both the Update and Draw calls of the sandbox:

The following is a class overview of the OgreFrameListener.h header:

Ogre

The Ogre3D FrameListener interface functions

The second interface, which is Ogre::WindowEventListener, enables the sandbox to receive specific callbacks to window events, such as the window movement, resizing, closing, closed, and window focus change:

The following is a class overview of the OgreWindowEventListener.h header:

Ogre

The Ogre3D WindowEventListener interface functions

Note

Both interfaces specify functions that are called from Ogre's main thread, so no race conditions exist to handle events.

Object-Oriented Input System

The Object-Oriented Input System (OIS) library is responsible for all the keyboard and mouse handling within the sandbox. The BaseApplication class implements both OIS interfaces in order to receive calls for key presses, mouse presses, and mouse movements. Once the BaseApplication class receives these events, it sends these events to the sandbox in turn.

The following is a class overview of the OISKeyboard.h header:

Object-Oriented Input System

The Ogre3D KeyListener interface functions

The following is a class overview of the OISMouse.h header:

Object-Oriented Input System

SandboxApplication

The main AI sandbox class, which is SandboxApplication, inherits from the BaseApplication class that implements the Cleanup, Draw, Initialize, and Update functions. The CreateSandbox function creates an instance of a sandbox and hooks up the Lua script specified by the filename parameter.

The following is a class overview of the SandboxApplication.h header:

SandboxApplication

The SandboxApplication implementation of the BaseApplication abstract class

Sandbox

The sandbox class represents the sandbox data as well as handling calls into the Lua sandbox script. The creation of a sandbox requires an Ogre SceneNode instance to situate the sandbox in the world. The sandbox's SceneNode instance acts as the parent to any additional geometry SceneNodes used for rendering; this also includes the AI agents within the sandbox.

The following is a class overview of the Sandbox.h header:

Sandbox

The sandbox class overview

Agent

The agent class encapsulates the agent data and performs function calls to the corresponding Lua script assigned to the agent from the LoadScript function. A SceneNode is required to construct an agent instance to maintain an orient and position the agent within the world.

The following is a class overview of the Agent.h header:

Agent

The agent class overview

Utility classes

AI sandbox heavily uses the utility pattern to separate logic and data. While both the sandbox and agent classes store their own relevant data, any manipulation of the data that interacts with the Lua virtual machine is handled through a utility class. This design separates the need for the agent and sandbox classes to know about the intricacies of interacting with the Lua stack. Instead, the Lua stack manipulation and data manipulation is left as the responsibility of a utility class.

For example, the AgentUtilities class handles all actions that are performed on an AI agent from Lua, while the SandboxUtilities class handles all actions that can be performed on the sandbox from Lua.

Any general purpose or miscellaneous interactions with the Lua virtual machine are handled by the LuaScriptUtilities class.

Lua binding

The LuaScriptBindings.h header file describes every C++ function that is exposed to the Lua virtual machine from the sandbox. You can always reference this file as the AI sandbox application programming interface (API). Each function contains a description, function parameters, return values, and examples of how the function should be called from Lua.

You have been reading a chapter from
Learning game AI programming with Lua
Published in: Nov 2014
Publisher: Packt
ISBN-13: 9781783281336
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at €18.99/month. Cancel anytime