Let us talk about OOP usage in a Player Inventory. If you do not already know the four fundamental principles of Object Oriented Programing, please look at my article explaining OOP in detail. Also note, if you do not have the experience to grasp these core concepts as of this moment, you can always come back with a deeper understanding of Lua. This tutorial is built using Advanced Lua concepts; it is understandable if you need to learn more material before attempting.
However, if you already understand these critical fundamental principles and how OOP/Metatables work, let us continue.
Let us start with the basic system that we will be making. The key components that are needed include:
- Saving and Loading Data
- Dealing with Adding and Removing Items
- Verifying the integrity of Items inside of Inventory
With these goals, we can now move on to the core code to make this system run.
To start, I will be using three modules, as seen in the image listed.
The loader will be inheriting the methods from the Classes folder. This will allow us to have clean and efficient code. Please note when doing this type of scripting, you may run into C-Stack errors if you attempt to index a table that is indexing itself.
For example:
local Table = {}
Table.__index = function(T, Index)
return T[Index]
end
This snippet of code will result in a C-Stack error due to repeated indexes to the table. To offset this, you can make a copy of the table.
As seen here:
local Table = {}
local Table_Copy = {}
Table.__index = function(T, Index)
return Table_Copy [Index]
end
However, I would not suggest doing this as it is overcomplicated and messy; Using Table.__index = function() is only useful for inheritance purposes.
The loader module that will be using inheritance will have to be set up so that we don’t have to require each model in our script. Instead, we can require all of the classes through the loader and return the inherited objects directly to the script controlling the system.
To achieve this functionality, we must do the following steps:
--[[
Sam
2/16/2021
Loads classes for use
]]
local Loader = newproxy(true)
local LoaderMT = getmetatable(Loader)
LoaderMT.__index = LoaderMT
local Classes = script.Enums:GetChildren()
for Index, Value in next, Classes do
local Name = Value.Name
local EnumData = require(Value)
LoaderMT[Name] = EnumData
end
setmetatable(LoaderMT, {__index = {}})
return Loader
This will allow us to simply do the following code to require any class in our system.
local Loader = require(game.ServerStorage.InvSystem.Loader)
print(Loader.DataClass) -- {}, we found our class.
With this, we can now set up our important methods for each class.
Our DataClass will contain all the methods we will need to deal with saving and loading data. It will look like this:
--[[
Sam
2/16/2021
Deals with data saving and loading for player Inv
]]
local DataClass = {}
local DS = game:GetService("DataStoreService")
local Store = DS:GetDataStore("Store")
DataClass.__index = DataClass -- we dont use .__index = function here due to it being messy.
function DataClass.New(Player)
return setmetatable({
Player = Player;
Data = {};
}, DataClass)
end
function DataClass:LoadData() -- make load data method
local Data = Store:GetAsync(self.Player.UserId, {}) -- attempt to load data from store
print(Data)
if not Data then -- Set data inside private method
self.Data = {
["Sword"] = 1;
["Gold"] = 500;
}
else
self.Data = Data -- we set the data inside of the private method
end
end
function DataClass:SaveData() -- make save data method
Store:UpdateAsync(self.Player.UserId, function(Old) -- we save data
return self.Data
end)
end
return DataClass
You can save data however you want; I used this method to save time.
We can also create or InvClass; This class will deal with all Inv methods, adding items, removing items, and verifying items exist. This class will also inherit functions from the DataClass. This will look like this:
--[[
Sam
2/16/2021
Deals with inv functionality
]]
local InvClass = {}
InvClass.__index = InvClass
function InvClass.New(Player, DataClass) -- constructor function.
local MT = setmetatable({}, InvClass)
DataClass:LoadData()
setmetatable(MT, {__index = DataClass}) -- This will wrap the new MT we created.
--[[
Because we wrapped the new MT with a new MT we setup an index call to check MT and DataClass for functions.
return MT
end
function InvClass:AddItem(Item,Name, Amount) -- make the add item method
if not Item or Name then return end
if self.Data[Item] then
self.Data[Item] += Amount or 1
else
self.Data[Item] = Amount or 1
end
end
function InvClass:RemoveItem(Item,Name,Amount) -- remove item method
if not Item or Name then return end
if self.Data[Item] then
self.Data[Item] -= Amount or 1
end
end
return InvClass
With all of these main classes done we can now set up the script that will put them all together.
This script will look something like this:
--[[
Sam
2/16/2021
Deals directly with Inv system
]]
local Loader = require(game.ServerStorage.InvSystem.Loader)
shared.PlayerList = {}
game.Players.PlayerAdded:Connect(function(Player)
shared.PlayerList[Player] = Loader.InvClass.New(Player, Loader.DataClass.New(Player)) -- pass the player as well as the Data_Class through.
end)
game.Players.PlayerRemoving:Connect(function(Player)
shared.PlayerList[Player]:SaveData() -- we can call the save data function from the InvClass due to inheriting the Data class properties.
end)
This is our complete system, You can save data, load data, add items, and remove items. You can also add your own custom methods to alter functionality. But be aware if you do so you will need to use the “:” key instead of the default “.”. The key “:” is used to refer to self or the object that the method is being called from.Preformatted text