Unreal Engine 4 Plugin for adding Lua scripting to your projects.
If you want modders to customize your game/project, or you need to allow game designers to script parts of the logic, this plugin is for you.
Contrary to the other Unreal Engine 4 Lua plugins, this one does not try to expose the Unreal Engine 4 api, but completely hides it exposing to the user/scripter only the features the developer decided to include (via Blueprints or C++).
Currently only Windows 64bit and Mac are supported.
In this fast (and useless example) we will print a string generated by a Lua script.
Install the LuaMachine plugin by downloading it from the official releases page (choose the binary release for your Unreal Engine version and operating system): https://github.com/rdeioris/LuaMachine/releases/
Uncompress it in the Plugins/ directory of your project (create it if it does not exist) and restart the Unreal Editor
Open your favourite editor for lua scripting and create a file into the project Content/ directory (call it hello.lua) with the following code:
current_time = os.date()
return "Hello, i am a lua script, current time is: " .. current_time
Now you need to create a new LuaState, it is a special Blueprint representing a Lua Virtual Machine (you can have multple Lua virtual machines in the same project). Just add a new blueprint inheriting from LuaState and name as you want (QuickLuaState is a good candidate).
Finally open your Level Blueprint and add the following nodes:
If you play the game, you will see the Lua string printed on top.
The LuaRunFile node will execute a lua file (relative to the Content/ directory) in the specified LuaState (QuickLuaState in our case) and will return the value returned by the script itself as a LuaValue UStruct. The plugin offers a bunch of operations you can apply to LuaValue's, in this example we simply converted the value to an Unreal String.
Time to something more complex, edit your hello.lua script:
function call_me_from_unreal(number)
return number * 2
end
current_time = os.date()
return "Hello, i am a lua script, current time is: " .. current_time
Now the script will add the call_me_from_unreal function to the global table:
The LuaGlobalCall node calls a function from the global table passing arguments as an array of LuaValue's.
So, now we know how to call lua functions from Unreal, time to do the opposite.
Open the QuickLuaState Blueprint you created before. This Blueprint exposes lot of different fields to configure your Lua Virtual Machine:
The Table 'TMap' allows you to add items to the Lua global table. We will first try by adding a string and a number (float):
Note that we set the "Lua Filename" field to hello.lua, in this way we can avoid to call the LuaRunFile node as hello.lua will be executed during the spawn of the QuickLuaState.
Let's modify our Level Blueprint:
The hello.lua script is loaded automatically, while the LuaGetGlobal nodes, retrieve the specific fields from the Lua global table, and return them as LuaValue's
Time to expose an Unreal UFunction to Lua: just add a Function to QuickLuaState:
and expose it in the Table TMap:
calling get_player_location() from lua, will trigger the QuickLuaState's GetPlayerPawnLocation(). Note that not all of the UFunction's can be exposed: if they take or return values they must be LuaValue's
Let's update hello.lua
function call_me_from_unreal(number)
location = get_player_location()
print(location.x, location.y, location.z)
return number * 2 + location.x + location.y + location.z
end
current_time = os.date()
return "Hello, i am a lua script, current time is: " .. current_time
and the Level Blueprint:
Please check the print() function: it is automatically mapped to the Unreal Output Log by the plugin.
Now you should have an idea of the plugin potential, continue reading for more infos.
A LuaState (ULuaState C++ class) represents a single Lua virtual machine (there can be multiple, each one isolated from the others). This is a "singleton" as each LuaState class can have a single instance for the whole process life. (Note that while in the Editor, LuaState's are constantly recreated whenever you enter PIE mode to simplify development)
Having multiple LuaState's allows clean separation of domains: as an example you may want a state for game configuration, another one for game logic and one for the modders. More complex examples include having states dedicated to specific Actors, GameModes or Levels.
LuaState's are loaded on-demand, so you can create dozens of them but they will not start Lua VMs until you need them.
LuaState's can load and execute scripts automatically if you specify the fields "LuaCodeAsset" and/or "LuaFilename". You can load scripts (both in text and bytecode format) from your filesystem (via "LuaFilename"), or from a specific asset exposed by the plugin, named LuaCode (you can specify it with the "LuaCodeAsset" field). The advantage of using filesystem scripts is that you can change them even after the project has been packaged (unless you package them too), while LuaCode assets are directly built in your pak files (as bytecode by default).
- LuaCodeAsset: specify a LuaCode asset to automatically load an execute on spawn
- LuaFileName: specify a script path (relative to Content/) to load and execute on spawn
- Table: TMap<FString, FLuaValue> allows adding FLuaValue's to the Lua VM global table
- RequireTable: TMap<FString, ULuaCode> allows to map LuaCode assets to specific name, so you can call require('name') from your code
- LuaOpenLibs: if true, automatically load the lua standard library on spawn
- AddProjectContentDirToPackage: if true, when doing require('name') will search for 'name.lua' in the Content/ directory
- AppendProjectContentDirToPackage: TArray allows specifying a list of Content/ subdirectories to search for packages (while doing require('name'))
- OverridePackagePath: (advanced users) allows to modify package.path
- OverridePackageCPath: (advanced users) allows to modify package.cpath
- LogError: enable/disable logging of Lua errors
A single event is exposed by ULuaState: "LuaError"
If defined, it will be triggered whenever the Lua VM generates an error. The Error message is passed as an argument. This is really useful for adding in-game consoles, or to catch specific errors.
You can define your LuaState's as C++ classes, this is handy for exposing functions that would be hard to define with blueprints:
#pragma once
#include "CoreMinimal.h"
#include "LuaState.h"
#include "AdvancedLuaState.generated.h"
/**
*
*/
UCLASS()
class LUATEST420_API UAdvancedLuaState : public ULuaState
{
GENERATED_BODY()
public:
UAdvancedLuaState();
UFUNCTION()
FLuaValue GetPlayerPawnLocation();
UFUNCTION()
FLuaValue GetSimpleString();
};
#include "AdvancedLuaState.h"
#include "Kismet/GameplayStatics.h"
#include "Engine/World.h"
#include "GameFramework/Pawn.h"
UAdvancedLuaState::UAdvancedLuaState()
{
Table.Add("simple_string", FLuaValue("Test string"));
Table.Add("get_player_location", FLuaValue::Function(GET_FUNCTION_NAME_CHECKED(UAdvancedLuaState, GetPlayerPawnLocation)));
Table.Add("get_simple_string", FLuaValue::Function(GET_FUNCTION_NAME_CHECKED(UAdvancedLuaState, GetSimpleString)));
}
FLuaValue UAdvancedLuaState::GetPlayerPawnLocation()
{
FVector Location = UGameplayStatics::GetPlayerPawn(GetWorld(), 0)->GetActorLocation();
FLuaValue Table = CreateLuaTable();
Table.SetField("x", FLuaValue(Location.X));
Table.SetField("y", FLuaValue(Location.X));
Table.SetField("z", FLuaValue(Location.X));
return Table;
}
FLuaValue UAdvancedLuaState::GetSimpleString()
{
return FLuaValue("Hello World");
}
GET_FUNCTION_NAME_CHECKED is an Unreal Macro for sanitizing UFunction names. Albeit not necessary (the argument is the FName of the function) it is highly suggested to use it to avoid silent errors.
You can call LuaState's offered api using the ULuaBlueprintFunctionLibrary class.
Check its docs here: LuaBlueprintFunctionLibrary
LuaValue's are the way Unreal communicates with a specific Lua virtual machine. They contains values that both Lua and your project can use.
This a list of the currently supported values:
- Nil (nil/NULL value)
- Bool (boolean value)
- Integer (integer/int32 value)
- Number (float value)
- String (string/FString value)
- Function (reference to a lua function)
- Table (reference to a lua table)
- UObject (pointer to an Unreal UObject)
- UFunction (pointer to an Unreal UFunction)
Note that tables are passed by reference, so technically you can update the same table from both lua and Unreal.
Check LuaBlueprintFunctionLibrary for infos on how to use the FLuaValue api.
As a shortcut for reaching long paths in tables, you can use the dot as the separator for field names.
As an example the LuaGetGlobal node, can access the package.path item by simply passing it "package.path" as the field name.
ULuaComponent allows to use an Actor as a Lua userdata.
Check its docs here: LuaComponent
You have various way to use Lua scripting facilities in your packaged project:
Transform your scripts (just drag them in the content browser) to LuaCode assets and eventually fix the RequireTable. By default they will be byte-compiled. It is highly suggested to "sign" the pak file to avoid pak file modifications. This method does not permit customization of scripts after the build.
You can include your scripts in the pak file automatically by specifying the directory containing them in the package settings:
Signing the pak file could be a good thing again, in addition to this byte-compiling the scripts could be accomplished with the 'luac' command (included in lua distributions)
This is for allowing easy customization of your scripts after the packaging. Basically you include the scripts directories in your Content directory:
The projects includes various functional tests (all written as blueprints, so they could be useful as examples too).
Just enable the plugin content visualization from the content browser and from the Automator tool execute the Project related tests.
Commercial support is offered by Unbit (based in Rome, Italy), just contact info at unbit dot it or follow/direct message @unbit on twitter
AIV, Accademia Italiana Videogiochi (http://www.aiv01.it). They are using it for Lua lessons in the Game Design classes.