Code Monkey home page Code Monkey logo

godot-lua-pluginscript's Introduction

Godot Lua PluginScript

Lua PluginScript icon

Godot Asset Library Icon Pluginscript Lua

WARNING

This does not work with Godot 4.X, since it relies on GDNative, a Godot 3.X only technology.

GDNative + PluginScript library that adds support for Lua as a scripting language in Godot 3.X

Being a GDNative library, recompiling the engine is not required, so anyone with a built release copied to their project can use it. Being a PluginScript language, Lua can seamlessly communicate with scripts written in GDScript / C# / Visual Script and vice-versa. Since the Godot object model is dynamic at runtime, any Godot objects' properties/methods can be accessed from Lua, including singletons like OS, ClassDB and custom singleton nodes. This way, one can use the language that best suits the implementation for each script and all of them can understand each other.

This plugin is available in the Asset Library as Lua PluginScript.

For some usage examples, check out plugin/lua_repl.lua and plugin/export_plugin.lua.

Currently, only LuaJIT is supported, since the implementation is based on its FFI library.

Installing

Either:

  • In Godot Editor, open the Asset Library tab, search for the Lua PluginScript asset, download and install it.
  • Put a built release of the library into the project folder and restart Godot. Make sure the lua_pluginscript.gdnlib file is located at the res://addons/godot-lua-pluginscript folder.
  • Clone this repository as the project's res://addons/godot-lua-pluginscript folder and build for the desired platforms.

Documentation

Goals

  • Provide support for Lua as a scripting language in Godot in a way that does not require compiling the engine from scratch
  • Be able to seamlessly communicate with any other language supported by Godot, like GDScript, Visual Script and C#, in an idiomatic way. This includes being able to dynamically access any Godot object's properties and methods using Lua's index/method notation
  • Have automatic global access to Godot's singleton objects and custom singleton nodes
  • Simple script description interface that doesn't need requireing anything
  • Support for LuaJIT and Lua 5.2+
  • Support paths relative to res://* and exported game/app executable path for requireing Lua modules
  • Have a simple build process, where anyone with the cloned source code and installed build system + toolchain can build the project in a single step

Non-goals

  • Provide calls to core Godot classes' methods via native method bindings
  • Support multithreading on the Lua side

Articles

  1. Designing Godot Lua PluginScript
  2. Implementing the library's skeleton
  3. Integrating LuaJIT and FFI
  4. Initializing and finalizing scripts (TODO)

Script example

This is an example of how a Lua script looks like.

-- Class definitions are regular Lua tables, to be returned from the script
local MyClass = {}

-- Optional: set class as tool, defaults to false
MyClass.is_tool = true

-- Optional: set base class by name, defaults to 'Reference'
MyClass.extends = Node

-- Optional: give your class a name
MyClass.class_name = 'MyClass'

-- Declare signals
MyClass.something_happened = signal()
MyClass.something_happened_with_args = signal("arg1", "arg2")

-- Values defined in table are registered as properties of the class
-- By default, properties are not exported to the editor
MyClass.some_prop = 42

-- The `property` function adds metadata to defined properties,
-- like setter and getter functions
MyClass.some_prop_with_details = property {
  -- ["default_value"] or ["default"] or [1] = property default value
  5,
  -- ["type"] or [2] = variant type, optional, inferred from default value
  -- All Godot variant type names are defined globally as written in
  -- GDScript, like bool, int, float, String, Array, Vector2, etc...
  -- Notice that Lua <= 5.2 does not differentiate integers from float
  -- numbers, so we should always specify `int` where appropriate
  -- or use `int(5)` in the default value instead
  type = int,
  -- ["get"] or ["getter"] = getter function or method name, optional
  get = function(self)
    return self.some_prop_with_details
  end,
  -- ["set"] or ["setter"] = setter function or method name, optional
  set = 'set_some_prop_with_details',
  -- ["usage"] = property usage, from `enum godot_property_usage_flags`
  -- optional, default to `PropertyUsage.NOEDITOR`
  usage = PropertyUsage.NOEDITOR,
  -- ["hint"] = property hint, from `enum godot_property_hint`
  -- optional, default to `PropertyHint.NONE`
  hint = PropertyHint.RANGE,
  -- ["hint_string"] = property hint text, only required for some hints
  hint_string = '1,10',
  -- ["rset_mode"] = property remote set mode, from `enum godot_method_rpc_mode`
  -- optional, default to `RPCMode.DISABLED`
  rset_mode = RPCMode.MASTER,
}
-- The `export` function is an alias for `property` that always exports
-- properties to the editor
MyClass.exported_prop = export { "This property appears in the editor" }
MyClass.another_exported_prop = export {
  [[This one also appears in the editor,
now with a multiline TextArea for edition]],
  hint = PropertyHint.MULTILINE_TEXT,
}

-- Functions defined in table are public methods
function MyClass:_ready()  -- `function t:f(...)` is an alias for `function t.f(self, ...)`
  -- Singletons are available globally
  local os_name = OS:get_name()
  print("MyClass instance is ready! Running on a " .. os_name .. " system")

  -- There is no `onready` keyword like in GDScript
  -- Just get the needed values on `_ready` method
  -- Also, Lua doesn't have the `$child_node` syntax, use `get_node` instead
  self.some_grandchild_node = self:get_node("some/grandchild_node")
end

function MyClass:set_some_prop_with_details(value)
    self.some_prop_with_details = value
    -- Indexing `self` with keys undefined in script will search base
    -- class for methods and properties
    self:emit_signal("something_happened_with_args", "some_prop_with_details", value)
end

function MyClass:get_some_prop_doubled()
  return self.some_prop * 2
end

-- In the end, table with class declaration must be returned from script
return MyClass

Status

  • LuaJIT support
  • Lua 5.2+ support
  • Useful definitions for all GDNative objects, with methods and metamethods
  • A yield function similar to GDScript's, to resume after a signal is emitted (GD.yield)
  • Working PluginScript language definition
  • PluginScript script validation and template source code
  • PluginScript code editor callbacks
  • PluginScript debug callbacks
  • PluginScript profiling callbacks
  • Package searcher for Lua and C modules that work with paths relative to the res:// folder and/or exported games' executable path
  • Lua REPL
  • API documentation
  • Unit tests
  • Example projects
  • Export plugin to minify Lua scripts
  • Drop-in binary release in GitHub
  • Submit to Asset Library

Third-party software

This project uses the following software:

  • godot-headers: headers for GDNative, distributed under the MIT license
  • LuaJIT: Just-In-Time Compiler (JIT) for the Lua programming language, distributed under the MIT license
  • High Level GDNative (HGDN): higher level GDNative API header, released to the Public Domain
  • LuaSrcDiet: compresses Lua source code by removing unnecessary characters, distributed under the MIT license
  • LuaUnit: unit-testing framework for Lua, distributed under the BSD license
  • debugger.lua: dependency free, single file embeddable debugger for Lua, distributed under the MIT license

Other projects for using Lua in Godot

godot-lua-pluginscript's People

Contributors

bojjenclon avatar gilzoide avatar johndoneth avatar jtbx avatar trainzmarcel avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

godot-lua-pluginscript's Issues

`remote` keyword?

Is there a way to declare functions with behavior of the GDScript remote keyword?

Runtime scripting

Can I use it at runtime to execute lua scripts? (without compromising the rest of the application)

Godot threads from Lua

Is there a proper way to use one of Godot's native threads from a Lua script? I've tried simply doing something like:

thread = Thread:new()
thread:start(self, "_save_game_in_thread", save_data)
self.thread = thread

But this just causes the game to crash with no error reported.

I know one of the stated non-goals is "Support multithreading on the Lua side" but I'm not sure if that's what you meant or if you simply meant native Lua multithreading.

Godot Array Indexing

Currently, an object constructed from the Array wrapper in Lua requires zero-indexing to access its elements. This looks wrong next to Lua's one-indexing, and can lead to some odd logic that requires checking types and adding/subtracting one to avoid OBO errors. Should Array be changed to one based indexing?

Mono: Core API Hash Mismatch Error.

In Godot 3.5.x Mono, this error occurs when a C# solution has been built before running.

Error Message:
E 0:00:00.560 _init_godot_api_hashes: Mono: Core API hash mismatch.
<C++ Source> modules/mono/mono_gd/gd_mono.cpp:515 @ _init_godot_api_hashes()

"undefined symbol: lua_gettop" when attempting to load luasocket

When I attempt to load luasocket via local socket = require "socket", Godot outputs the following error:

require: Error loading script metadata: error loading module "socket.core" from file "/home/jeremy/Godot/mygame/rocks/lib/lua/5.1/socket/core.so":
	/home/jeremy/Godot/mygame/rocks/lib/lua/5.1/socket/core.so: undefined symbol: lua_gettop
stack traceback:
	[string "res://script.lua"]:3: in main chunk
	in script_load @ "res://script.lua"
  <C++ Source>  ?:-1 @ require()

Setter not called in self

Me again! In GDScript if you have a setter on a property and change it via self.property the setter will be used. This doesn't apply to Lua, so I'm wondering how to trigger the setter from within a class method?

NodePath property is inaccessible in Lua

Plugin Version: r1
Godot Version: v3.3.3

I'm trying to use a NodePath property and access it from Lua.

The code appears to register the property with Godot as it shows up and works in the inspector but is inaccessible via self in Lua.
image

Properties are working for other primitive types I've used so far but not when the type is NodePath. When I inspect self inside of _ready with the following code, I get the subsequent outcome.

local inspect = require("inspect")

local function ready(self)
  print(inspect.inspect(self))
  print(self.player_path)
end

return {
	_ready = ready,
	player_path = property({type = NodePath})
}
{
  __owner = [Node2D:1622],
  __script = {
    __getter = {},
    __path = "res://enemy.lua",
    __setter = {},
    _ready = <function 1>
  },
  <metatable> = {
    __concat = <function 2>,
    __index = <function 3>,
    __tostring = <function 4>
  }
}
nil

As you can see, the property is not on self and printing the field directly shows that it doesn't exist, as it's nil.


I've been loving this project by the way. It's great! Thank you for working on it.

get_overlapping_areas() Gets all Areas Regardless of Mask.

Hi, me again! I also noticed that the Area2D:get_overlapping_areas() appears to get all overlapping areas regardless of mask.

I have the player on Layer 1, and masked to see layer 6. I also have an object on Layer 7.

It is picking up the object on layer 7, even know I do not have it checked. I have tested the same code in GDScript and it appears to get only the objects on layer 6. Whilst the Lua code gets objects on all layers.

Any ideas?

Thank you and have a good one!

Self Converts Into a Table When Called From Function

Hi, this is my first time ever posting an issue to GitHub, so I apologize if its not in the correct manner. I was recently working on some pathfinding and realized that "self" gets converted into a table if you reference it from a function that is being called that is not one of the built in functions.

Test.extends = "Navigation2D"

function Test:_ready()
	print(self)
	Test:Foo()
end

function Test:Foo()
	print(self)
end

return Test

In the example above, printing self from inside the _ready() function prints the correct game object, but if you print it from within the Foo() function it instead prints a table.

Thank you and have a wonderful day!

Array:sort_custom Usage

I've been trying to sort an array using the sort_custom method, but I can't figure out the proper way to use it. It expects a Godot object and a method to call within it. But everything in Lua is a table. What am I supposed to pass in?

Wrong y coord reported from Lua scripts

I'm having a weird issue where self.position from a KinematicBody2D is always giving a y-coord of 0. I tested the exact same setup with GDscript and the position was reported as expected, so its definitely something on the Lua side.

Do you know what the problem could be loading a multithreading library called effil?

I have a simple script in lua:

local Nodeee = {
extends = "Node",
}

effil = require("effil")

function foo(name)
print(name .. " another thread!")
end

thr = effil.thread(foo)("Thr")

thr:wait()

return Nodeee

Which produces the following error:

E 0:00:00.435 Error loading script metadata: Thr
stack traceback:
[string "res://Nodeee.lua"]:14: in main chunk
in script_load @ "res://Nodeee.lua"
<C++ Source> ?:-1

The effil.dll file is located inside the res:// folder. Do you mind taking a guess at what the problem could be?

Edit: wrong error

Unable to execute text from textEdit's text property with loadstring()

local Executor = {
	extends = "Node",
}

function Executor:execute(code)
	if code == nil or code == "" then
		return false
	end
	-- Code to execute
	local temp_code = loadstring(code)
	
	-- Execute the code and capture the results
	local success, result = pcall(temp_code)
	
	-- Check if there was an error and print it if necessary
	if not success then
		print("Error executing code:", result)
		return false
	end
	
	-- Print the output of the code if there is any
	if result ~= nil then
		print("Output:", result)
	end
	
	return true
end

function Executor:_process()
    local value = self:get_parent():get_parent().play
	if value == true then
		local code_to_run = self:get_parent():get_parent():get_node("CoreUI/ScriptEditor/TextEdit").text
		print(type(code_to_run))
		
		self:execute(code_to_run)
	end
end

return Executor```
it keeps on giving me error of:

E 0:00:13.272 loadstring: bad argument #1 to 'loadstring' (function expected, got cdata)
stack traceback:
[string "res://Scripts/Core/Scripting/Executor.lua"]:10: in function 'execute'
[string "res://Scripts/Core/Scripting/Executor.lua"]:35: in function <[string "res://Scripts/Core/Scripting/Executor.lua"]:29>
in call _process @ "res://Scripts/Core/Scripting/Executor.lua"

Executor.lua:10 @ loadstring()

Define prop without exporting it

Currently, all variables defined in a class are exported to the editor. There doesn't seem to be a value one can set to keep them from being exported.

Scripts Inherited By Scenes All Share Variables

Hi its me again, sorry to bother! I noticed today that when a script is tied to a scene, when you instance that scene all of those instanced scenes share variables. So for example if I change a variable for one of the scripts it changes for every scene that has that script.

Is this because I am declaring my variables this way?

Is there a different way I should be declaring variables?

**EDIT
Upon doing more reading I realize that this is an issue with godot. I need to make each scene unique from one another. Still researching how to do that.

Thank you and have a wonderful day!

local Test = {}

Test.extends = "KinematicBody2D"

Test.Variable = 60

Get Lua script instances while unboxing Object Variants

Using Godot Object pointers provide unexpected results regarding the access to properties that are not declared (see #46).

Using Lua tables, it doesn't matter if a property is declared or not, so it should be possible to set new values to Lua instances from Lua regardless of property declaration. It won't work from GDScript or other languages, but people might be writing all scripts in Lua, so it's more useful to have script instances automatically unboxed, if available.

Using GD.get_lua_instance from Variant.unbox should be enough, falling back to the original Object pointer if there is not Lua script attached to it. The cost of this operation is a single table index, so it shouldn't be a problem regarding performance.

'self' argument not passed to GDScript automatically

Hi @gilzoide.

Firstly, I would like to thank you for your work.

While trying to call a GDScript method from a Lua script, the owner of the method is not passed as first argument and need to be given manually.

Sample Code:

Test.gd:

extends Node

func _ready():
var lua_script = load("res://LuaTest.lua")
lua_script.ltest(self)

func test():
print('It worked !')`

LuaTest.lua:

`local LuaTest = {
extends = "Node",
}

function LuaTest:ltest(parent)
parent.test() -- The code work if we pass parent as argument here
end

return LuaTest`

Corresponding error:
E 0:00:00.490 test: attempt to index local 'o' (a nil value)

Is there something I missed on how it's supposed to work ?
Thanks by advance,

load plugin first time has some errors.

    a new bug, but not very confusing.
  1. create a project
  2. close godot
  3. copy the addons
  4. open the project
  5. now the plugin is not enable in the setting,
  6. click the Enable and some error log is emitted.
  7. close the godot and reopen
  8. now you can click Enable correctly, and lua plugin works well.

I use windows 11.

first click "Enable" error log:

Godot Engine v3.5.1.stable.official (c) 2007-2022 Juan Linietsky, Ariel Manzur & Godot Contributors.
--- GDScript language server started ---
 No loader found for resource: res://addons/godot-lua-pluginscript/plugin/lua_repl.lua.
 No loader found for resource: res://addons/godot-lua-pluginscript/plugin/lua_repl.lua.
 scene/resources/resource_format_text.cpp:157 - Couldn't load external resource: res://addons/godot-lua-pluginscript/plugin/lua_repl.lua
 No loader found for resource: res://addons/godot-lua-pluginscript/plugin/export_plugin.lua.
 res://addons/godot-lua-pluginscript/plugin/plugin.gd:33 - Parse Error: Can't preload resource at path: res://addons/godot-lua-pluginscript/plugin/export_plugin.lua
 core/project_settings.cpp:239 - Property not found: editor_plugins/enabled

Originally posted by @codewdy in #31 (comment)

Can't use the extension

When i try to enable it gives Thais erro

Unable to load addon script from path: 'res://addons/godot-lua-pluginscript/plugin/plugin.gd'. This might be due to a code error in that script.
Disabling the addon at 'res://addons/godot-lua-pluginscript/plugin/plugin.cfg' to prevent further errors.

How can i solve this

cannot load this plugin due to "attempt to call a nil value"

I create an empty godot project, and unzip addons to the project.
reopen the project and press F5(Run).
I think there is some lua error.

src/language_gdnative.c:301 - Error in validate: attempt to call a nil value
stack traceback:
	[C]: at 0x7ff98d127030
--- Debugging process started ---
Godot Engine v3.5.1.stable.official.6fed1ffa3 - https://godotengine.org
OpenGL ES 3.0 Renderer: NVIDIA GeForce RTX 3060 Laptop GPU/PCIe/SSE2
Async. shader compilation: OFF
 
Unicode error: invalid skip

v0.5.0 lua_pluginscript.zip md5sum:47c9324b9f15aa2f4009914d7c247e1d

I revert lua plugin to v0.4.0 and all things work well.

Plugin not working in Godot 4.0

I can't enable the plugin on Godot 4.0 as it throws this error:
Unable to load addon script from path: 'res://addons/godot-lua-pluginscript/plugin/plugin.gd'. This might be due to a code error in that script. Disabling the addon at 'res://addons/godot-lua-pluginscript/plugin/plugin.cfg' to prevent further errors.

  • Fedora Linux 37
  • Godot version 4.0 stable
  • Latest luapluginscript release

Array __newindex Setter Broken

E 0:00:04.667   __newindex: attempt to perform arithmetic on global 'index' (a nil value)
stack traceback:
	[string "res://scenes/actors/actor.lua"]:250: in function <[string "res://scenes/actors/actor.lua"]:240>
	in call add_item @ res://scenes/actors/actor.lua
	in call _exit_tree @ res://scenes/objects/pickup_on_free/pickup_on_free.lua
	in call _on_tween_completed @ res://scenes/objects/shrink_away/shrink_away.lua
  <C++ Source>  lps_init_script:1766 @ __newindex()

I was testing the Array indexing changes and it works great for the getter, but it seems the setter is broken. I even tried a very basic repro like below and it errors as well:

inventory[1] = Item:new()

Passing data between GD.yield() and resume()

Discussed in #13

Originally posted by Alikae January 27, 2022
Hi !
I would like to know if there is currently a way to pass data between between GD.yield() and the corresponding resume().
All my tests so far behave as if GD.yield() always return nil, regardless of how I call resume().

Wanted usage:
script a:

b.test()
b.resume('data')

script b:

function test()
a = GD.yield()
print(a) -- print data

Is there something I missed about how to use it ?
Is this maybe not an intended feature ? The wanted behavior is easily reproduced by changing

b.resume('data')

by

b.some_prop = 'data'
b.resume()

Lastly, I came to the sources to try to understand it by myself, but I stumbled upon coroutine.resume and coroutine.yield, that (in my understanding) doesn't seems to be defined anywhere. Does it come from Godot API ? If so, do you know where I can find some documentation about it ? Pretty hard to find Godot documentation that do more than scratching the surface.

Scripts not reloading in editor

Technically it makes a lot more sense to make this an issue in the Godot repo, but thought it'd be worth tracking here as well.

I found this issue as part of the Known Limitations while I was working on #22. I also wonder what the .gdnlib properties called load_once and reloadable have to do with this.

I figured out that the native GDScript solution has a method called reload that we just need to implement in the PluginScript class of the Godot repo (together with some additions to the language descriptors).

Once we have that we just need to make some functions in our language_gdnative.c and we're set!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.