MugHead Studios
Menu

Documentation

Lua Script Object

Put a Lua script on any GameObject and edit it in the inspector, with waits and coroutines handled for you.

  • Version 1.1.0
  • Unity 2021.3 LTS and newer, including Unity 6

Lua Script Object lets you put a Lua script on any GameObject and edit it right in the inspector. It's handy for quick prototyping, for level logic you want to tweak without recompiling, or as a starting point for modding.

The tricky part of Lua in Unity is usually pausing a script and carrying on later. We handle that for you: call wait(2) or wait_until(...) and the script just picks up where it left off.

Lua Script Object runs on MoonSharp 2.0, a Lua 5.2 interpreter written in C#. It's included in the package (see section 8), so there's nothing else to import.

1. Getting started

  1. Add the LuaScriptObject component to a GameObject.
  2. Type a script in the box, or click Edit Script to open a bigger editor window with line numbers.
  3. Press play.
debug("Hello from " .. this.name)

wait(1)
this.position.translate(0, 1, 0)

The demo scene in Tests/Runtime/ScriptableLuaObjectDemo.unity shows a few objects talking to each other.

In C#, everything is in the MugHeadStudios.Lua namespace:

using MugHeadStudios.Lua;

2. The LuaScriptObject component

Run mode

  • Awake runs the script when the object wakes up.
  • Start (the default) runs it on the object's first frame.
  • Manual doesn't run it until you call RunLuaScript() from C#, or this.run() from Lua.
luaScriptObject.runMode = LuaScriptObject.RunMode.Manual;
luaScriptObject.RunLuaScript();

Script source type

  • Inline (the default): write the script in the inspector, or set it from code:
luaScriptObject.luaScript = "debug('Hello, World!')";
  • File: drag in a script file. For anything that needs to work in a build, name it something.lua.txt so Unity imports it as a text asset. Plain .lua files are read from disk, which only works in the editor (the inspector will remind you).

Associated game objects

A list of names and objects (or prefabs) your script can get to with this.associated_object("name"), this.associated_script("name") and this.instantiate_associated_object("name"). Handy for spawning things without hard-coding paths.

3. What a script can do with this

Inside a script, this is the LuaScriptObject component itself.

Properties

Lua What it gives you
this.gameObject The GameObject, with its usual Unity members.
this.name The object's name.
this.creationTime When the object started (in Time.time seconds).
this.position / this.rotation / this.scale Shortcuts to the transform (see below).

Transform shortcuts

position, rotation and scale are small helpers around the transform. Position and rotation are in world space, scale is local.

this.position.x = 5                         -- set one axis
this.position.translate(0, 0.1)             -- move by 0.1 on Y
this.scale.xy = this.sin()                  -- pulse x and y scale together
this.rotation.translate(0, 0, delta_time()) -- spin on Z
this.rotation.set(vector3(0, 90, 0))        -- set the whole thing

local p = this.position.get()               -- full Vector3
local flat = this.position.xy_vector        -- Vector2 of x and y
  • Setters: x, y, z, xy, xz, yz, xyz, xy_vector, xz_vector, yz_vector, set(...), translate(...).
  • Getters: x, y, z, xy_vector, xz_vector, yz_vector, get().

Functions

Lua What it does
this.wait(seconds) Pauses the script for a number of seconds. Same as the global wait.
this.wait_until(function) Pauses until the function returns true.
this.always(function) Runs the function every frame until the object is destroyed or disabled.
this.run() Runs the script again from the top.
this.yield() / this.resume() Low level pause and resume, if you want to drive it yourself.
this.set_parent(obj) Parents to a GameObject, Transform or LuaScriptObject.
this.created() Same as creationTime.
this.active() / this.active(bool) Gets or sets whether the object is active.
this.component("Name") Gets a component by type name, for example "Text".
this.set_data(key, value) / this.get_data(key) Stores values on this object. get_data returns false if nothing is stored.
this.sin(freq, amp) / cos / tan Wave based on time since the object started. Both arguments default to 1.
this.instantiate() Clones this object and returns the clone's LuaScriptObject.
this.associated_object(name) / this.associated_script(name) Looks up an entry in Associated game objects. Returns nil if there's no match.
this.instantiate_associated_object(name) Clones an associated object or prefab.
this.destroy() Destroys this object.

4. Global functions

These can be called from any script without this.

Lua What it does
debug(message, withTime) Logs to the Unity console. withTime adds the time.
time() / delta_time() Time.time and Time.deltaTime.
wait(seconds) / wait_until(function) / always(function) See section 5.
vector2(x, y) / vector3(x, y, z) Makes a vector.
distance_between(a, b) Distance between two numbers.
distance_between_v2(a, b) / distance_between_v3(a, b) Distance between two vectors.
distance_between_game_objects(a, b) The offset between two objects, as a Vector3.
find_object(name) / find_script(name) Finds a GameObject (or its LuaScriptObject) by name. Returns nil if not found.
instantiate(obj) / destroy(obj) Clones or destroys a GameObject.
load_scene(name) / scene_name() Loads a scene, or gets the current scene's name.
set_data(key, value) / get_data(key) Data shared by every script. It survives scene loads. get_data returns false if nothing is stored.
get_type(value) The C# type name of a value.
key_down(key) True while a key is held. Uses Unity KeyCode names, like "Space", "A", "LeftShift" or "UpArrow". Works with the old Input Manager and the new Input System.
random_int(min, max) A whole number from min to max, both included.
random_float(min, max) A number between min and max.
sin(freq, amp) / cos / tan Wave based on Time.time.

5. Waiting and running things every frame

-- Wait two seconds, then carry on
wait(2)

-- Wait for the player to press space
wait_until(function()
    return key_down("Space")
end)

-- Bob up and down forever
always(function()
    this.position.y = this.sin(2, 0.5)
end)

wait and wait_until pause just this script. Everything else keeps running. always doesn't pause; it keeps the function running every frame and the script carries on below it.

If a script hits a Lua error inside one of these, the error is logged with the line number and the object's name, and only that one stops. The other scripts carry on.

6. Adding your own functions

Functions on this

Make a static class with the [MoonSharpUserData] attribute and write extension methods for LuaScriptObject:

using MoonSharp.Interpreter;
using MugHeadStudios.Lua;
using UnityEngine;

[MoonSharpUserData]
public static class MyLuaExtensions
{
    public static void shout(this LuaScriptObject luaScriptObject, string message)
    {
        Debug.Log(luaScriptObject.name + " says: " + message.ToUpper());
    }
}
this.shout("hello")

Global functions

Same idea, but extend LuaGlobal:

[MoonSharpUserData]
public static class MyGlobalLuaFunctions
{
    public static int add(this LuaGlobal luaGlobal, int a, int b)
    {
        return a + b;
    }
}
debug(add(2, 3))

Global functions are stored by name, so you can't have two with the same name (no overloads).

These classes can live anywhere in your project, including your own assembly definitions.

7. Registering your own types (LuaSetup)

Classes with [MoonSharpUserData] are registered automatically. We also register the common Unity types for you: GameObject, Transform, Vector2, Vector3, Color, Camera, Light, the 2D and 3D colliders and rigidbodies, SpriteRenderer, MeshRenderer, Canvas, RectTransform, and UI Text, Image and Button.

For anything else (types you can't add an attribute to), write a CustomLuaSetup extension method. We find it and run it once, before the first scene loads:

using MoonSharp.Interpreter;
using MugHeadStudios.Lua;
using UnityEngine;

[MoonSharpUserData]
public static class MyLuaSetup
{
    public static void CustomLuaSetup(this LuaSetup luaSetup)
    {
        luaSetup.RegisterType<AudioSource>();

        // Runs just before every script starts, if you want to add globals of your own
        luaSetup.scriptModifier = (luaScriptObject, script) =>
        {
            script.Globals["game_version"] = Application.version;
        };
    }
}

8. MoonSharp, assemblies and upgrading

MoonSharp is included. It lives in ThirdParty/MoonSharp and is MoonSharp 2.0 under the BSD 3-Clause licence. The licence is in that folder and in Third-Party Notices.txt. The MoonSharp package we used to point people to is no longer on the Asset Store.

If you already have MoonSharp in your project (for example in Assets/Plugins/MoonSharp, or as a DLL), keep only one copy. Two copies give "type exists in both" errors.

Assembly definitions. The package now compiles into three assemblies:

  • MugHeadStudios.LuaScriptObject (runtime)
  • MugHeadStudios.LuaScriptObject.Editor (inspector and editor window)
  • MugHeadStudios.MoonSharp (the interpreter)

Scripts in the default Assembly-CSharp see all of them automatically. If your scripts are in your own assembly definition, add references to MugHeadStudios.LuaScriptObject and MugHeadStudios.MoonSharp.

Enter Play Mode Options. Works with domain reload turned off. Our static state is reset each time you press play.

9. Notes and limits

  • Lua is Lua 5.2 (MoonSharp). Most pure Lua code works. C libraries don't.
  • Scripts are interpreted, so keep heavy per-frame work in C# and use Lua for logic and glue.
  • On IL2CPP platforms (iOS, WebGL, consoles) make sure Unity doesn't strip the types your scripts use. A link.xml that preserves your assemblies is the usual fix.
  • Scripts can call anything you register, so don't run Lua from players you don't trust without limiting what's registered.

10. Contact

Found a bug, or something in here doesn't make sense? Email us at [email protected]. We read everything and we're happy to help.

MugHead Studios · mugheadstudios.com