When I picked up the development of Office Management 101 again, I first decided to rework the whole AI system, which had become cumbersome and a bit limited for what I had planned for the future. While I was figuring out the approach, I thought that since I'm changing it all anyway, I might as well make it more flexible in terms of coding too.
This led me to look into scripting languages that I could integrate into my game engine, which runs on the LibGDX Java framework. I considered several different ones, but finally settled on Lua, which is fairly simple to write and a popular choice for games.
To my surprise it turned out there's a number of Lua libraries for Java and it wasn't immediately clear to me which would be the best one. The final choice for me came down to picking the one which seemed to be updated most often - LuaJ, although I found the documentation of it somewhat lacking.
I did find a simple example how to use it with LibGDX though. The example is outdated, since LibGDX has gone through some major changes this year, but at least it was a start.
So here I'll give an overview how I set it up. I won't claim it's the best way to do it or even a good one, but if you're reading this and find anything I could improve in my approach or code, feel free to let me know. It's not meant to be much of a tutorial, but rather just an explanation of how I implemented it, especially since I have no real Java experience out of this project.
I'm using the Gradle setup that LibGDX ships with these days, so to add any 3rd party Java libraries I just drop them in the libs directory in my project root. In my build.gradle file I have defined the core dependencies as:
apply plugin: "java"
compile fileTree(dir: '../libs', include: '*.jar', exclude: '*-sources*.jar')
This includes any .jar files that may be in the libs directory, but excludes the source files of those libraries.
The other dependencies include the core as well, so there would be no need to define that row again. The desktop for example:
apply plugin: "java"
After adding a new .jar file, you need to refresh Gradle dependencies and I've noticed that sometimes I also have to restart my Eclipse client.
Based on the example I linked earlier, I created a base interface for the scripts (Script.java), so if needed, I could even support different scripting languages. Maybe not a realistic need, but I do like to keep things flexible if possible.
The implementing LuaScript class makes use of JsePlatform.standardGlobals() of LuaJ that loads the script file from disk. It then defines the functions that take the parameters passed to it and converts them to a form that Lua can understand with CoerceJavaToLua.coerce(object) and runs a function in the loaded Lua file with globals.get(functionName).invoke(parameters).
You can download the full file here: LuaScript.java
Then I have something like a global class called ScriptManager that manages the Lua scripts cache by loading them upon first request (so scripts are only loaded when required) from list in a .json file and has the methods to call the functions defined in Lua scripts. Note that it expects the files to be in assets/data/scripts directory.
The full class is here: ScriptManager.java
This could still use some improvements like removing rarely used scripts from the cache after some time, so I'll work on it some more when time permits.
In addition I have a singleton ExecuteScript that is made available in Lua scripts to call other script functions, which can be in separate files.
The class itself is here: ExecuteScript.java
Okay, so how does it all come together? So say I have the scripts defined in the scripts.json file like this (just simple key-value pairs):
And say the character.lua file looks like this:
-- UPDATE --
function update(character, delta)
To call the Lua init function in my Java Character class, I just need to add the following line in the constructor:
The ScriptManager will load the character.lua file based on the scripts.json key and call the function init while passing the Character object that calls the function as a parameter to the function. To call the update function I could do either:
And that's it.
To call the same function from Lua itself I'd have to do:
Now all this doesn't mean that 3rd party modding of the game would be possible yet. It would need to check for files in external directories first, since right now it loads all the files from the Java package. There would also need to be better error checking, lots of security improvements and more integration to the rest of the game classes, not to mention proper documentation. But this means that should I ever decide that adding full modding support is a good use of my time, I at least have a base to start from instead of having to rewrite half of the code.
The system could use some performance testing though and I definitely want to add a console where Lua command input is possible for easier debugging purposes, so those are my main concerns for now.
That's all for now, thanks for reading and I hope it was useful or at least interesting for at least some of you. Next time I'll continue with the behavior tree based AI that I implemented and which will refer to this article as well in some parts. If there's any questions you want to ask or advice you want to give, feel free to leave a comment here, email me at firstname.lastname@example.org or contact me on Twitter (@tulevikEU).