After a preliminary round of research, I’ve learnt that metatables are tables that have custom behaviors when other functions are called on them (ie. metamethods). In my game, I’m using @GollyGreg’s module script filled with metatables, which somehow makes it so that I can call methods that aren’t the metamethods Roblox listed in their guide on metatables, leading me to assume that I can create custom metamethods. However, that aforementioned module script is a proving to be quite tricky for me to replicate. Simply put, how do I write and use custom metamethods like GollyGreg does in that module script?
I do not see these custom metamethods you speak of in @GollyGreg’s code. I see only two proper metatables with a single standard metamethod for OOP emulation. Can you elaborate?
In the Renderer
script, there is a table called Renderer
and a bunch of functions below it that are styled like methods (eg. line 53 with function Renderer:withRenderImpl(implementationFunc)
). I assumed that Renderer
is a metatable because on line 46, there is a setmetatable
global with Renderer
as the metatable argument. Soon after, state
(ie. the table getting turned into a metatable) is returned, which ultimately ends up being the return value of the createRenderer
script, which I then use as if it were a normal Roblox class (eg. renderer:AddToStack(someHighlight)
). Are these not metamethods? What are they called? How do I replicate them?
Renderer
is a metatable, but what you’re referring to are not metamethods. A metatable is no different from a normal table, when when assigned as the metatable to another, Lua(u) will read that table for specific keys. Those keys can be found here. These keys modify how Lua(u) interprets interactions with the table the metatable was attached to. For example, you can use them to redefine what it means to subtract two tables.
What you’re looking for are just plain methods. This is the term given to functions of a class. To create one, you need only use the following syntax:
function Class:Method(--[[parameters]])
-- Function body
end
This enables the implicit “self” parameter, which is a fundamental component of emulating OOP in Lua(u). To learn more about how this works, read this reply to another post.
The driving metamethod in OOP emulation is the __index
metamethod, which can be assigned to either a function or another table. This metamethod is activated whenever Lua(u) detects an index operation on a table where no value is associated with the index. The following happens:
- If
__index
is assigned to a function, the function is called with the accessed table and the key. - If
__index
is assigned to a table, Lua(u) will propagate the index operation onto that table and return the result.
Developers use the latter behaviour to connect the methods of the class with an instance of the class, which by itself is a table solely comprised of properties.
instance:Method(...)
Attempting the above results in the following operation:
instance.Method(instance, ...)
This starts an index operation on instance
for the key “Method”, but since instance
lacks this key, the index operation propagates to the table stored in the attached metatable’s __index
key, which is the class. This results in the following operation:
class.Method(instance, ...)
Since the method does belong to the class, the function is successfully located and executed with the particular instance of the class and any related arguments.
Altogether:
local Class = {}
Class.__index = Class
function Class.new(stringA, stringB)
local self = setmetatable({}, Class)
self.StringA = stringA
self.StringB = stringB
return self
end
function Class:Method()
print(self.StringA .. self.StringB)
end
local instance = Class.new("Hello", ", world!")
instance:Method() --> Hello, world!
One last question. From that post on explaining self
, it gives me the impression of being something akin to this
from C++ (ie. a way to refer to the “class” that we make with module scripts). Therefore, in my head, self
in Class.new
would be referring to Class
itself. However, you’ve put local
before it, which gives me the impression that it will shadow self
with the metatable. So, did you intend to shadow self
, or is there some other reason you’re using “self” as the name of the metatable to return?
You’re correct! self
is intended to act as a reference to the instance of the class. In C++, and other languages, it is called this
. However, unlike these languages, when it comes to the constructor, the implicit parameter is no longer available. I explain why that is in the same reply I mentioned earlier. Because the parameter is no longer available, its assignment in the constructor is the first instance of that variable, meaning no shadowing is occurring. It is conventional to name the variable “self” as it continues to hold up the notion that “self” refers to the instance of the class, which we’re creating in the constructor
This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.