I have a fairly standard architecture like this:
GameObjects
have Transform
, and can have Renderer
, Rigidbody
, Camera
, LightSource
, etc, and all of these classes have their corresponding methods.
GameObject
hasGetName
,SetName
, etc. and its ownTransform
.Transform
hasGetPosition
,SetPosition
, etc.Renderer
hasSetTexture
,SetMaterial
, etc. and references aTransform
/GameObject
.
If I want to implement Lua scripting using LuaBridge, and I would like to achieve an API similar to this...
function update()
local gameObject = this()
local renderer = gameObject:GetRenderer()
renderer:SetTexture("Brick")
local rigidbody = gameObject:GetRigidbody()
rigidbody:SetMass(10)
local transform = gameObject:GetTransform()
transform:SetPosition(1, 10, -5)
end
...then which of these should I choose?
Solution A:
I bind every C++ class directly to Lua classes.
In this way I have to implement some extra scripting quality-of-life methods for the classes, so no big deal.
But it's not so easy to see whether a method is callable in Lua script, or is only available in native code. (for example I need to name method
XYZ
asLuaXYZ
)Methods which are only created so that scripting is made easier, are mixed with core functionality.
Solution B:
I create a wrapper class for every required class, like:
Renderer->RendererHandler
,Transform->Transform Handler
,GameObject->GameObjectHandler
, etc.Lua and core functionality is separated, much easier to see what is the scripting API and what is not.
For every class I have to create another similar class which got a lot of methods, which are just calling the calling the same method in the wrapped class. Like
GameObjectHandler->GetName()
just callsGameObject->GetName()
Thanks in advance.
Summary:
In my opinion, Solution B is the best. That is the easiest to handle, the most transparent, and the most separated.
But I got lots of new classes and new dependencies, which messes up my clean architecture. Solution A makes expanding functionality much easier to handle (I dont have to create a lua interface class for new components).
But error handling is worse, because I don't have to care about if a scripter uses a
NULL
pointer (like a non existent component)But if that happens due to my engine implementation I'll have to handle it seriously. (And how can I signal whether a
NULL
pointer in a method is "don't care type" or "serious problem" type?)So currently I think that the best solution is this:
class GameObject { ... GetName() SetName() Lua_getName() Lua_setName() ...
I don't have to create "Lua interface" classes, and handle that mess that comes with it.
I directly bind the Lua functions to the classes, but I create new methods in that class for scripting purposes.
For example I implement
GetName()
for "InEngine" functionality, and aLua_getName()
which is bound to Lua functions and used only in scripts.The problem here is that, yeah you can't call InEngine methods from Lua because it's not bound. But what about calling
Lua_getName()
instead ofGetName()
?- There is no compile check for that, and if the developer is tired and makes this mistake, it will be hell to debug that.