· 5 years ago · Aug 10, 2020, 08:52 AM
1metatables can be added to tables and they hold these things called metamethods which fire when you do certain actions upon tables. metatables can only hold metamethods and nothing else, if you define other stuff in a metatable besides a metamethod, that stuff will get ignored and only the metamethods will register as valid table elements.
2In order to create a table with a metatable we can use a method called `setmetatable`:
3```lua
4-- this method returns the normal table that we pass into it as the first parameter
5local tab = setmetatable({},{})
6```
7the first parameter of the `setmetatable` method is the normal table that we want metamethods to act upon, the second parameter is the metatable in which we can store various metamethods
8```lua
9local tab = setmetatable({},{
10 __index = function(tab,key,val)
11 print(tab,key,val)
12 end
13})
14```
15the first metamethod I will discuss, also the most common, is the \__index metamethod. This method will fire when you try to index a value in a table that doesn't exist or is nil. the \__index metamethod can be set to either a function containing three parameters: the normal table, the key you try to index, and the value of the key. You can do custom things like displaying custom error messages or anything you want really, for example:
16```lua
17local tab = setmetatable({},{
18 __index = function(tab,key,val)
19 warn("Tried to index: "..tostring(key).."; a nil value in "..tostring(tab))
20 end
21})
22print(tab["noexist"]) --> "Tried to index: noexist; a nil value in <table_address>"
23```
24You can also set the \__index metamethod to another table. When you index a nil value in the normal table, it will check if the key you try to index exists in the table the \__index metamethod is set to. If the key exists in the second table it will return that key, if not it will return nil. This is very useful when creating an oop structure for your scripts. I will explain how to setup and oop structure now:
25
26So now we can begin creating an oop structure. In order to do this we need a Module Script, since they can return tables. In Object Oriented Programming, there are classes, which can create new objects, these classes are essentially the blueprint for the objects you create under the class. In lua we can accomplish doing this in a module script. First we will setup a module script as it always looks
27```lua
28local Object = {}
29
30return Object
31```
32We have created a new table called Object and return it at the end so that when we require the ModuleScript we get the table, because that is what is returned.
33In order to implement an OOP style into this we have to remember what \__index was used for. The metamethod fires whenever you try to index a nil value in a table, but how can this possibly be useful, heres how:
34```lua
35local Object = {}
36Object.__index = Object
37
38return Object
39```
40Now this may seem confusing at first but we are basically indexing a new key with the name of the metamethod inside of our Object table, lets continue so I can better explain
41```lua
42local Object = {}
43Object.__index = Object
44
45-- generally "new" is used, even in other languages, when we create new objects
46function Object.new()
47
48end
49
50return Object
51```
52In the code above we are defining a new function in our Object table called "new", this method will be used to create new objects under our class
53```lua
54local Object = {}
55Object.__index = Object
56
57-- generally "new" is used, even in other languages, when we create new objects
58function Object.new()
59 local newObject = setmetatable({},Object)
60
61 newObject.TestVariable = 2
62
63 return newObject
64end
65
66return Object
67```
68
69Now I just added a lot, let me explain how this works. In our new method, (remember how setmetatable returns the normal table) we create a new table with a metatable that is the main "Object" table. Now this may be confusing but remember how we did
70```lua
71Object.__index = Object
72```
73Everything else in the metatable that isnt a metamethod will get ignored, like the new method, so we are left with \__index, this means that whenever we try to index something in our `newObject` table, if that key is nil it will attempt to find it in the main Object table. This is great because we essentially just made a new object (its a table tho) that has the methods of its parent class.
74-
75Now lets use a normal to script to see how we can use this
76```lua
77--normal script
78
79-- define a variable which will be equal to our Module Script Class table
80local ObjectClass = require(path_to_module)
81
82-- now lets get a "new object" by using the new method, remember how the new method returns the new table we create, aka the one returned by setmetatable
83local newObject = ObjectClass.new()
84
85--we defined a new key, or property, in the "newObject" table so lets print that out
86print(newObject.TestVariable) --> 2
87```
88this is great and all but lets see the other wonders that we can do with this
89In the Module Script I am going to add a method to the Object table called "GetVariable"
90```lua
91local Object = {}
92Object.__index = Object
93
94-- generally "new" is used, even in other languages, when we create new objects
95function Object.new()
96 local newObject = setmetatable({},Object)
97
98 newObject.TestVariable = 2
99
100 return newObject
101end
102
103function Object:GetVariable()
104 return self.TestVariable
105end
106
107return Object
108```
109Ok so this is great, but let me explain a bit, first let me explain what "self" is, when we set the \__index metamethod equal to the Object table we basically allowed the new object table to be able to index keys of its parent class
110
111
112GetVariable is a method of the Object class, so when we try to index it in the new object table it returns nil at first but since our \__index metamethod is set to the Object table it finds the method. Now this may be confusing, but in the method you will notice I used a colon instead of a . which you will have to understand it this way:
113-when you use a colon the normal table is automatically assumed and is stored in a keyword variable called self
114-when you use a . you have to define the object table that you are using in order to successfully have that method work on the object
115--------
116The method can be used in both way:
117```lua
118function Object:GetVariable()
119 return self.TestVariable
120end
121
122function Object.GetVariable(self)
123 return self.TestVariable
124end
125```
126You just have to be mindful because you cannot use a . if the function uses a colon
127-
128Anyways lets continue in our normal script
129```lua
130--normal script
131
132-- define a variable which will be equal to our Module Script Class table
133local ObjectClass = require(path_to_module)
134
135-- now lets get a "new object" by using the new method, remember how the new method returns the new table we create, aka the one returned by setmetatable
136local newObject = ObjectClass.new()
137
138--we defined a new key, or property, in the "newObject" table so lets print that out
139print(newObject.TestVariable) --> 2
140-- instead of doing this, we can now call our custom methods on our table
141
142print(newObject:GetVariable()) --> 2
143```
144Alright so this is great and all, but what is an actual usecase of coding like this
145-
146I am going to show an example of how I manage, load, and create data for a player, without the datastore stuff and just the structure:
147
148
149```lua
150--Module Script
151local default_data = {}
152
153local PlayerData = {}
154PlayerData.__index = PlayerData
155
156function PlayerData.new(Player)
157 local self = setmetatable({},PlayerData)
158
159 self.DataKey = Player.UserId
160 self.Session = {
161 Data = default_data
162 {
163
164 return self
165end
166
167function PlayerData:Load()
168 -- load the player data, do anything you need to do like actions upon the player based on the data they have saved
169end
170
171function PlayerData:EnableAutoSave()
172 -- resume an auto save thread
173end
174
175function PlayerData:DisableAutoSave()
176 -- pause an auto save thread
177end
178
179function PlayerData:Save()
180 -- a general save method using datastores
181end
182
183return PlayerData
184```
185this is a very basic structure of my actual methods, but let me show you how i then use this in my game:
186```lua
187local Players = game:GetService("Players")
188local PlayerData = require(path_to_module)
189
190local AllPlayerData = {}
191
192Players.PlayerAdded:Connect(function(Player)
193 local newPlayerData = PlayerData.new(Player)
194 AllPlayerData[Player.UserId] = newPlayerData
195end)
196
197Players.PlayerRemoving:Connect(function(Player)
198 local pData = AllPlayerData[Player.UserId]
199 if pData then
200 -- a little different on how i save it but you get the point
201 pData:Save()
202 AllPlayerData[Player.UserId] = nil
203 pData = nil
204 end
205end
206```
207And there we go, a crash course tutorial on metatables and metamethods and how they can be used in your game, keep in mind its like 4am when i wrote this