Adding gameplay logic using Lua

Finally I had the time to finish this blog post (which I started writing quite a while back). This time it’s about adding gameplay logic to the game using Lua.
In an earlier post I explained how we separate our game code into several layers, namely the game framework, the game engine and the game(logic) itself.
Using the framework and the engine, we can build different games, since all artwork and gameplay logic is separated from these two parts. I explained earlier how the game assets (e.g. sprites) and room definitions are loaded into the engine and Jaap talked about loading the dialogue (see here), but we haven’t talked about where we define the gameplay logic, which actually makes the game the game.

Gameplay logic
So what happens when the player chooses to ‘pick up matches‘ for example? Where is that gameplay logic defined? What language is used? How is this separated from the engine? How do these parts communicate?

We’ll be using the example of ‘pickup matches’.

Let’s say the player is in the room where the matches are, which is the office. And we want the gameplay logic to be:
– If Kate is in the room, you cannot pick up the matches because she will prevent you from taking them
– If Kate is not in the room, you can pick them up and add them to the inventory

The game engine itself does not know about matches or Kate. Those concepts are not hard- coded in the engine, however they are loaded into the model of the world (game state). So there – in the game state – the engine can lookup stuff about the office and about Kate.
What we need is some way to code the logic and a way to interact with the game engine and its game state.

Lua
There are several ways to code gameplay logic and for this matter we chose a scripting language called Lua. Lua is well known for its use as a scripting language on top of games and game engines, its interoperability with many programming languages, and its large community. I did look into other scripting languages like Squirrel, Angelscript, HScript and others. But Lua was the easiest to integrate in our Haxe project.

What is Lua?
From lua.org: “Lua is a powerful, efficient, lightweight, embeddable scripting language. It supports procedural programming, object-oriented programming, functional programming, data-driven programming, and data description.”
So, Lua is not just a programming language, but it is also easily embeddable in other languages. For the sake of simplicity we call Lua a ‘virtual machine’ in our game architecture. That means it is an engine itself that runs alongside our game engine, and also has a state.
Let’s visualise:

Lua integration

This is the same image as in the earlier post about the design of our game, only rotated 45 degrees. In this image you can see the game engine with the Lua Virtual Machine next to it.
The Lua virtual machine has all the gameplay logic in it in the Lua programming language.
Lua cannot interact directly with the game world and game state. In the game engine we have exposed several methods to Lua, so Lua can interact with the game engine.
A few examples of exposed methods:

  • getGameState(String)
  • setGameState(key:String, value:String)
  • actorSays(Actor, String)
  • runDialogue(Actor)
  • pickupItem(String)
  • hasItem(String)
  • actorGotoTarget(Actor, Actor)
  • actorPlayAnimation(Actor,String)

This way our Lua code can ask the game engine about things in the game state and even update them. The Lua script can start animations and start dialogues.

On the other side: the game engine can directly execute functions in Lua. So when the player chooses to ‘pickup matches’, the game engine will try to execute a function ‘pickup_matches’ in the Lua code. If that function does not exist, the game engine will execute some default behaviour (in our case it will have the character walk over to the matches, play the pick up character animation and finally add the matches to the player’s inventory). If the function does exist, it will let the Lua code decide what to do, which means we can override the default and instead do whatever we want the game to do.

Pick up matches
So back to the example and some actual Lua code taken from ‘office.lua’:

function pickup_officematches()
	actorGotoTarget("hero", "officematches")
	waitSignal("arrived_hero")
	actorPlayAnimation("hero", "usedown")
	if getActorState("kate", "location") == "office" then
		actorSays("kate", {"Hey don't take my matches!"})
		waitSignal("finishedspeaking_kate")
	else
		pickupItem("inv_matches")
		actorRemove("officematches")
		actorSays("hero", {"This will come in handy!"})
		waitSignal("finishedspeaking_hero")
	end
	actiondone()
end

What this script does is:

  • Move the main target to the positions of the matches
  • Wait for it to arrive there
  • Play the ‘usedown’ animation
  • Then check the gamestate:
    • If Kate is in the office, then let Kate say something about the matches
    • If Kate is not there: pick up the matches; remove the matches from the room; let the main character say something
  • Finally return back to the game engine

Let’s see how the communication between the game engine and Lua works using this sequence diagram:

luasequence_diagram

(diagram drawn using websequencediagrams.com)

This sequence diagram shows us what happens when Kate is indeed in the room not being a sport about giving the player her matches:

The game engine starts by calling the method ‘pickup_matches’ in Lua. The first thing the Lua script does is talk to the game engine and have it move the main character to the position of the matches.
After that, something interesting happens. The script will wait until it receives a signal. This means that all game engine calls from Lua are asynchronous. That is because the Lua virtual machine runs in its own thread. That means that execution of the Lua script will immediately continue after calling the method. But many times we want to wait for something in ‘the world’ to have happened before moving on. Like waiting for the main character to arrive at the position. For this reason the signalling methods are very useful.

I did not invent those methods myself, but instead I used the Coroutines Lua scripts developed by Jonathan Fischer. Coroutines is a Lua concept as described here. The Coroutines methods from Jonathan Fischer are inspired by this presentation (slides 64 and up and slides 138 and up). This whole presentation is worth reading by the way.
So I’m not diving into the Coroutines because that concept is very well explained by Jonathan Fischer on his website, but the whole asynchronous nature of the Lua virtual machine interaction is good to keep in mind.

Implementation in Haxe
Enough with all the theory, let’s do some code!
To embed Lua in the programming language of your choice you have to ‘bind’ Lua to your code. For Haxe there is a library called ‘hx-lua’ that integrates Lua in your Haxe code. Original created by Matt Tuttle and later forked and improved by Kevin Resol. That version was forked by Andrei Rudenko and he improved the hx-lua library to support higher versions of Lua. This is the version we use right now.

So to start using Lua in Haxe you need the hx-lua library. And to use the Lua coroutines you can use the coroutines methods I described earlier.
Let’s dive into an example I created, which uses hx-lua and the Lua coroutines. It shouldn’t be too much trouble to understand.

The source code can be found here: LuaExample
I’ve also included a README that describes how to install hx-lua and how to run the example.

The example is a simplified game engine with a simple game-state and a very simple game-loop in the game engine. There are no real Actors and all animating is faked for the sake of simplicity. Let’s examine the code:

Class: Main.hx
This only contains the static main function which creates a new game engine.

Class: GameState.hx
This class contains 2 key-value sets ‘_statevalues’ and ‘_actorStates’, and an array ‘inventory’.
The inventory is just a list of strings containing all the names (id’s) of the items currently in the inventory.
The _statevalues can contain states in the world like ‘office_door_open’ = ‘true’, or ‘number_of_balloons_found’ = ‘3’.
The actorStates contains states of specific actors, e.g. the ‘location’ of actor ‘Kate’ can be set to ‘office’.
Moreover, this class contains several methods to access the state sets and the inventory. These methods are used in the LuaUtil class which exposes methods to Lua.

Class: LuaUtil.hx
This class sets up the Lua virtual machine and exposes several methods to interact with the game engine.
In ‘setupLUA()’ a Lua object is created which is the virtual machine. Then some Lua libraries are loaded so we can use all Lua functionality in our Lua scripts. One of the libraries is ‘coroutine’ which I described earlier.
After the libraries are loaded, several methods are created in Lua which can interact with our game engine. The first one is ‘getActorState’, a Lua function which takes ActorID and a key as parameters. Subsequently a call is made to the game engine, which returns the game state of the actor. For demo purposes we also output the call to the console.
In a similar way, several methods are exposed for triggering different things in the game engine.
After all methods are created, the ‘WaitSupport.lua’ file is loaded in the Lua virtual machine. This is the Lua code using the coroutine method as described earlier. This Lua code is created by Jonathan Fischer.
Our own Lua code is loaded after the coroutine code, which for this demo is ‘office.lua’, which contains the ‘pickup matches’ example.
Finally this class also contains the method ‘executeLua’ which can execute Lua code from the game engine into the Lua virtual machine.

Class: GameEngine.hx
The last important class is the game engine, which is a very very very simplified version of our game engine. It has a simple game loop in the method ‘update’. Its just a simple while-loop.
In this loop the methods ‘UpdateLua’ and ‘UpdateWorld’ are called each ‘frame’ or ‘step’. UpdateLua calls the ‘wakeUpWaitingThreads’ in the Lua virtual machine and UpdateWorld does absolute nothing in this example, but normally it would advance all the objects in the world one step/frame.
When the game engine is created it first sets up Lua using LuaUtil.hx and then it executes the initial method ‘enter_room’, which we use in all our Lua files as the initial method for all our rooms in the game. In this case it just let’s the actor say that he is in the office.

After the initial method, the method ‘pickup_matches’ is called and then the game engine steps in to the game-loop.
The Lua code for pickup_matches is run in Lua so now there are 2 loops running at the same time: the game loop in the game engine, and the Lua loop in the Lua virtual machine.
The game engine loop does nothing, until some action is set by Lua.

Running pickup_matches
Let’s see what happens when the Lua code executes ‘pickup_matches’:
First the method ‘actorGotoTarget(“hero”, “officematches”)’ is called:
In LuaUtil.hx the method ‘actorGotoTarget’ is defined and it will just set the variable ‘actorGotoTarget’ in the game engine to ‘walking’.
Then in Lua it will wait for a signal from the engine by using ‘waitSignal(“arrived_hero”)’.

in the meantime, the game loop detects that the ‘fake_action’ variable is set so it will fake ‘walking’ for several frames’. After that, the game engine will signal Lua that it is done with the action using ‘actorArrived(“hero”)’.

Following this, Lua can continue with ‘actorPlayAnimation(“hero”, “usedown”)’ which will start a fake animation in the game engine.
And this is how the interaction between Lua and the game engine continues. You should be able to find this back in the source code.
Let’s see what the final output of the example is:

luaexample

I wouldn’t call this eye candy or visually pleasing in any way, but it should illustrate the 2 separated loops of the game engine and the Lua virtual machine. I hope the visuals are enough to clarify the given example of pick_matches as described in this post.

If you have any question about this topic, or any other point-and-click-adventure topic, don’t hesitate to add a comment.

That’s it for now!

Leave a Reply