godot_luaAPI icon indicating copy to clipboard operation
godot_luaAPI copied to clipboard

Godot LuaAPI

Godot Lua Module

Table of contents:

  • About
  • Features
  • Nightly Builds
  • TODO
  • Compiling
  • Examples
  • Contributing And Feature Requests

About

🐧 Linux 🎨 Windows 🍎 MacOS

WARNING!!! this is a alpha version of the module made for Godot v4-alpha. Please see the branch v1.1-stable for a more stable build.

This is a Godot engine module that adds lua support via GDScript. Importantly this is NOT meant to be a replacement for GDScript. The main purpose of this module is to add runtime execution of code for tasks such as modding or in game scripting.

While the original purpose of this module was for my own use I understand more may find it useful. If a feature is missing that you would like to see feel free to create a Feature Request or submit a PR

To use you can either Compile from source or you can download one of the nightly builds.

By default the lua print function is set to print to the GDEditor console. This can be changed by exposing your own print function as it will overwrite the existing one.

Features

  • Run lua directly from a string or a text file.
  • Push any Variant as a global.
  • Call lua functions from GDScript.
  • Choose which libraries you want lua to have access to.
  • Custom LuaCallable type which allows you to get a lua function as a Callable. See examples below.
  • LuaError type which is used to report any errors this module or lua run into.
  • LuaThread type which creates a lua thread. This is not a OS thread but a coroutine.
  • Object passed as userdata. See examples below.
  • Objects can override most of the lua metamethods. I.E. __index by defining a function with the same name.
  • Callables passed as userdata, which allows you to push a Callable as a lua function. see examples below.
  • Basic types are passed as userdata (currently: Vector2, Vector3, Color, Rect2, Plane) with a useful metatable. This means you can do things like:
local v1 = Vector2(1,2)
local v2 = Vector2(100.52,100.83)
v2 = v2.floor()
print(v2.x) -- "100"
print(v1+v2) -- "(101,102)"
change_my_sprite_color(Color(1,0,0,1)) -- if "change_my_sprite_color" was exposed, in GDScript it will receive a Color variant.

Nightly Builds

TODO

  • Finish v2 documentation
  • Stability testing
  • More up to date todo list on the v2 project

Compiling

This build is for godot 4.0.0-alphaX. X being the latest version. Will not be supporting older alpha builds.

  • Start by cloning the Godot 4.0.0-alpha source with this command git clone https://github.com/godotengine/godot

  • Next change directories into the modules folder and clone this repo with this command git clone https://github.com/Trey2k/lua

  • Now you can follow the Godot build instructions on their site.

Examples

Running lua from a string:

extends Node2D

var lua: Lua

func _ready():
	lua = Lua.new()
	lua.do_string("for i=1,10,1 do print('Hello lua!') end")

Running lua from a file:

extends Node2D

var lua: Lua

func _ready():
	lua = Lua.new()
	lua.do_file("user://luaFile.lua")

Pushing a Variant as a global:

extends Node2D

var lua: Lua
var test = "Hello lua!"

func _ready():
	lua = Lua.new()
	lua.push_variant(test, "str")
	lua.do_string("print(str)")

Exposing a GDScript function to lua:

extends Node2D

var lua: Lua

func luaAdd(a, b):
	return a + b

func _ready():
	lua = Lua.new()
	# Functions are passed the same as any other value to lua.
	lua.push_variant(luaAdd, "add")
	lua.push_variant(func(a, b): return a+b, "addLamda")
	lua.do_string("print(add(2, 4), addLamda(3,3))")

Calling a lua function from GDScript:

extends Node2D

var lua: Lua

func _ready():
	lua = Lua.new()
	lua.do_file("user://luaFile.lua")
	if( lua.function_exists("set_colors")):
		# call_function will return a Variant if lua returns nothing the value will be null
		var value = lua.call_function("set_colors", ["red", "blue"])
		if value != null:
			print(value)
		else:
			print("no value returned")	
		
	if( lua.function_exists("set_location")):
		# Assumeing lua defines a function set_location this will return a callable which you can use ot invoke the lua function
		var set_location = lua.pull_variant("set_location")
		var value2 = set_location.call(Vector2(1, 1))
		if value2 != null:
			print(value2)
		else:
			print("no value returned")

Error handling:

extends Node2D

var lua: Lua

func test(n: int):
	if n != 5:
		# This will raise a error in the lua state
		return LuaError.new_err("N is not 5 but is %s" % n, LuaError.ERR_RUNTIME)
	return n+5

func _ready():
	lua = Lua.new()
	lua.push_variant(test, "test")
	# Most methods return a LuaError
	# calling test with a type that is not a int would also raise a error.
	var err = lua.do_string("test(6)")
	# the static method is_err will check that the variant type is LuaError and that the errorType is not LuaError.ERR_NONE
	if LuaError.is_err(err):
		print("ERROR %d: " % err.type + err.msg)


Bind libraries:

extends Node2D

var lua: Lua

func _ready():
	lua = Lua.new()
	#all libraries are avalible. Use OS and IO at your own risk.
	lua.bind_libs(["base", "table", "string"])

Passing objects as userdata:

extends Node2D
var lua: Lua
class Player:
	var pos = Vector2(0, 0)
	#If lua_funcs is not defined or returns a empty array, all functions will be aval
	func lua_funcs():
		return ["move_forward"]
	#lua_fields behaves the same as lua_funcs but for fields.
	func lua_fields():
		return ["pos"]
	func move_forward():
		pos.x+=1

var player2: Player

func _ready():
	lua = Lua.new()
	player2 = Player.new()
	lua.push_variant(func(): return player2, "getPlayer2")
	lua.expose_constructor(Player, "Player")
	lua.do_string("player = Player() player.move_forward() print(player.pos.x)")
	lua.do_string("player2 = getPlayer2() player2.pos = Vector2(50, 1) print(player2.pos)")
	var player = lua.pull_variant("player")
	print(player.pos)
	print(player2.pos)

Object metamethod overrides:

extends Node2D
var lua: Lua
class Player:
	var pos = Vector2(1, 0)
	# Most metamethods can be overriden. The function names are the same as the metamethods.
	func __index(index):
		if index=="pos":
			return pos
		else:
			return LuaError.new_err("Invalid index '%s'" % index)
	func move_forward():
		pos.x+=1

func _ready():
	lua = Lua.new()
	lua.expose_constructor(Player, "Player")
	var err = lua.do_string("player = Player() print(player.pos.x)  player.move_forward() -- this will cause our custom error ")
	if LuaError.is_err(err):
		print(err.msg)
	var player = lua.pull_variant("player")
	print(player.pos)

Using Coroutines:

extends Node2D
var lua: Lua
var thread: LuaThread
	
func _ready():
	lua = Lua.new()
	# Despite the name this is not like a OS thread. It is a coroutine
	thread = LuaThread.new_thread(lua)
	thread.load_string("
	while true do
		-- yield is exposed to lua when the thread is bound.
		yield(1)
		print('Hello world!')
	end
	")
	
var yieldTime = 0
var timeSince = 0;
func _process(delta):
	timeSince += delta
	if thread.is_done() || timeSince <= yieldTime:
		return
	# thread.resume will either return a LuaError or a Array.
	var results = thread.resume()
	if LuaError.is_err(results):
		print("ERROR %d: " % results.type + results.msg)
		return
	yieldTime = results[0]
	timeSince = 0

Contributing And Feature Requests

All contributions are welcome, if you would like to contribute submit a PR.
Additionally if you do not have the time and or the knowledge you can create a Feature Request

lua logo