When is a metatable 'really' required?

I’m trying to get my head around metatables, but all the tutorials I’ve seen haven’t gone into detail as to HOW and WHY they can be used in game. What I’m looking for is real case examples for using them. Probs the best use case I’ve seen is ‘to easily make parts with different colors’ or something like that. I want real real use cases. Are there any games that actually utilize them.

3 Likes

You’re not alone with this question. I’ve failed to see the real application of metatables.

They look interesting in concept, but I’m not sure how they can be applied, which is something I need to learn new things.

2 Likes

Well considering most examples are just mathematical based, or how to ‘print’ things, there’s no real ‘How to make a game using OOP or metatables’ tutorials out there.

1 Like

I use them a lot for backup/default tables

local defaultTable = {test = "Hello"}
defaultTable.__index = defaultTable 

local regularTable = {}
setmetatable(regularTable, defaultTable)

print(regularTable.test) -->> "Hello"

and I agree with the guy below, metatables are more powerful tables, they aren’t needed but they can be pretty useful

7 Likes

The word meta itself is greek that means “beyond”… and you probably know what a table is. This roughly defines Metatables as being beyond “normal” tables. Metatables allow developers to store what is called “metadata” about data and alter the behavior of data using functions. Metatables also come with metamethods which are functions. They can go from calling a table, adding a table, etc. (examples include __index, __add).

Metatables and metamethods allow you to create various OOP-based features such as:
Properties, Inheritance, Immutability, Caching, and using mathematical properties.

Metatables aren’t essential to programming and are only really used when developers need to use OOP when developing their games. Some game developers out there do use metatables for OOP, or for other reasons they see fit.

2 Likes

Metatables have a lot of use cases on Roblox. Essentially they allow you to add extra functionality to tables, such as providing default values when an index doesn’t exist and perhaps validating the value is the correct data type when creating a new index. A fairly common use case for metatables would be to adapt an Object-Orientated approach to programming which would include object inheritance, a good example and explanation of this can be found on the lua documentation (and I believe the developer hub, although I can’t find the link)

http://www.lua.org/pil/16.html
http://www.lua.org/pil/16.1.html
http://www.lua.org/pil/16.2.html

I’ve made a system in the past which essentially allows me to add custom methods to Roblox instances, feel free to check it out and even use it if you wish. I could probably improve it some more when I have free time.

Custom methods on Instances
local Register = {}
local Object = {}

local register_meta = {
	__mode = 'k',

	__newindex = function(self, key, value)		
		if type(key) ~= "userdata" then
			return error('key is not userdata')
		end

		if type(value) ~= "table" then
			return
		end
		
		rawset(self, key, value)
	end,
	
	__call = function(self, obj)
		
		-- if we already have the object in the register
		-- then return it
		if self[obj] then
			return self[obj]
		end
		
		local newRegisterObject = Object.new(obj)
		self[obj] = newRegisterObject
		
		return self[obj]
	end
}

Object.__index = function(self, key)
	-- First, check if it's a pre-defined method
	-- on the Object, **not the current object (self)**
	if Object[key] then
		return Object[key]
	end
	
	local RblxRef = self.RblxInstance[key]
	if not RblxRef then
		return
	end
	
	if type(RblxRef) == "function" then
		return function(g, v)
			local success, returnValue = pcall(RblxRef, self.RblxInstance, v)
			return returnValue
		end
	end
	
	return RblxRef
end

Object.__newindex = function(self, key, value)
	if type(key) ~= "string" then
		return error('key must be a string')
	end
	
	if type(value) == "function" then
		return rawset(self, key, value)
	end
	
	if type(value) == "table" and value.RblxInstance then
		value = value.RblxInstance
	end
	
	self.RblxInstance[key] = value
end

Object.AddMethod = function(self, name, func)
	rawset(self, name, func)
end

Object.new = function(obj)
	local newObject = {}
	newObject.RblxInstance = obj
	
	return setmetatable(newObject, Object)
end


setmetatable(Register, register_meta)

local game = Register(game)

function game:GetDataStore(name, scope)
	local DS = self:GetService('DataStoreService')
	return DS:GetDataStore(name, scope)
end

game:AddMethod('GetFromReplicatedStorage', function(self, name)
	local ReplicatedStorage = self:GetService('ReplicatedStorage')
	return ReplicatedStorage:FindFirstChild(name)
end)

game:AddMethod('GetFromServerStorage', function(self, name)
	local ServerStorage = self:GetService('ServerStorage')
	return ServerStorage:FindFirstChild(name)
end)

return Register
2 Likes

That’s not quite true, here’s an absolutely cracking one:

2 Likes

I’ve read that before, doesn’t go into detail on how to make a game with it. Just has a few examples, but that’s about it

1 Like

You don’t make a game with OOP, you write your code with it in mind. It is however the most popular paradigm for game development, as you can consider everything the player sees (and everything they don’t) as objects, with their own properties and functions.
If you look at the Instances in the Roblox engine, you’ll notice this - parts, for example, have properties for size, position, colour etc. and functions that you can call from your scripts.

2 Likes

They’re required when you think they’re required. I use them for debugging, OOP, and emulating the ability to write to locked ROBLOX objects.

Regarding how I use them for debugging:
It is sometimes useful to keep track of table access through the __index method and the __newindex method.

Regarding how I use them for emulating the ability to write to locked objects:
What I do is I create a new table and set that table to index back to my original object. Then I can just set indexes to that table and I can emulate the behavior of writing and calling custom methods of a locked object.

Metatables are to be used carefully. They may have tempting features like overloading operators for convenience of writing less code in the long run but, in actuality, failing to use them with caution will result in code with non-obvious meaning, confusing anyone (including you in a couple weeks time) who attempts to understand what’s going on.

Prefer code that’s obvious and explicit in what it does rather than code with non-standard behaviour defined by some metatables. The Roblox style guide agrees, stating that metatables are a good example of a surprising or dangerous feature that should be used with care.

The only real uses for metatables, I’ve found, also align with the Roblox style guide:

In all useful use cases, you’ll probably see metamethods __index and __newindex being used the most. Most other metamethods are used strictly for operator overloading which, in my opinion, just harms the readability of code.

To answer your question: They’re required in very few situations. Don’t force the use of metatables because you think you should be using them. Use them only when absolutely necessary. I’ve personally not touched metatables at all in my latest project simply because I haven’t made any object-oriented code.

5 Likes

Adding onto what Ben said, I’ve been talking with several programmers and we agree that OOP is kind of overrated. Don’t get me wrong, it’s not “OOP is bad, never do it”, but, don’t use it for everything that you do.

I mostly use metatables for OOP, but it isn’t really used that often, and neither should it. Maybe you add some custom functions under the hood that you forget about, and when you are to add some more features you’re ripping your hair off as to why it doesn’t work. Be careful!

Some valid usecases for OO programming is for example when you’re interacting with the Trello API. It’s actually a pain having to provide the same variables over and over, and keeping track of them.
For example, instead of:

local Trello = require("whatever");
local Board = Trello:GetBoard("adaoijDAW¤");
local List = Board:GetList("adwa");
local Card = List:GetCard("123");

print(Card.Title) --> "123" or anything

you’d have to do

local Trello = require("whatever");
local Board = Trello:GetBoard("adaoijDAW¤");
local List = Trello:GetBoardList(Board.Id, "adwa");
local Card = Trello:GetListCard(Board.Id, "adwa", "123");

print(Card.Title) --> "123" or anything

and it just becomes harder to maintain.

Many people get the hang of OOP, and start using it for everything they do, even when it’s not necessary. What you get in return is increased complexity, less maintainability and in the future you’re going to wonder what in the world you have been doing.

2 Likes

My new game uses OOP in a number of places.

It’s a city builder so the city entity itself has methods to load and save it, as well as a bunch of other features.

It also has AI traffic and I have a vehicle class that each vehicle is an entity of. Allows for really clean programming when it comes to telling the vehicle to start, stop, making decisions, destroy itself, etc.

I also sometimes use it for player data to save loads of messy calls to API in my main code. I’ll have a PlayerData class which has methods like loading and saving, clearing, etc.

Honestly it can be used for anything where you’ll have multiple of a particular type of entity. The metastable let’s you inherit the methods whilst letting you store specific data for that entity in other keys in its table.

You could wrap each player in an entity and create methods like teleport, jail, team swap, all as methods in the metatable. There’s no time you require metatables but they can make your life easier if you know how to use them.

Once you understand them and have played about a bit you’ll spot opportunities where they’d save you time, and others where they would overcomplicate it unnecessarily. You as the programmer decide what tools you use.

2 Likes

Deeply rooted tables, uh Object Oriented Programming, and metamethods are pretty common in hooking stuff (If you’re a exploiter, don’t thanks.),There are some weird situations though too

In my opinion, you can do most stuff without them and you may never have actual need to use them.
On the other hand, they can simplify some tasks and open the doors to more opportunities.

Data Driven Design techniques can be implemented with metatables.