Hello! This tutorial will introduce you to a helpful concept of OOP. First, I’m going to define what an object will be in this context so that I can use the term without confusion. An object can be thought of as a box to hold simpler forms of data, which means that tables, classes, structs, and instances are all objects. Notice how I said tables? You’ve probably used them as objects before, either unknowingly or knowingly; take this pizza constructor for instance.
local function NewPizza(toppingName, hungerValue)
return {name = toppingName .. " Pizza", hungerValue = hungerValue}
end
local pizza = NewPizza("Sausage", 5)
print(pizza .name .. " will fill " .. pizza.hungerValue .. " hunger points.")
As you can see, “pizza” is a table tasked with holding data, and is an object in this sense. We also know some other things about pizza. For instance, it’s a food and in a game, a food would most likely be an item, so if food is an item then pizza is also an item. Finally, an item is an Object; Object being the highest form of abstraction. Let us implement “Object” below.
local Object = {}
Object.Name = "Object"
Object.__index = function(t, key)
return t.Parent[key]
end
function Object:New(name)
return setmetatable({Name = name, Parent = self}, Object)
end
So we’ve now made our master object and created a constructor method on it for creating more objects with ease. What the constructor method does is it creates a new table containing its name and a reference to its parent, which is the object that the method was called on. It then sets its metatable to our master object, before returning our newly created object for us to play with. Since we’re using the index metamethod to look in the parent in case nil is indexed, it will travel up the hierarchy until it finds something or runs out of parents to search in.
Usage would work like so:
local Item = Object:New("Item")
Item.maxStackSize = 64
local Food = Item:New("Food")
local Pizza = Food:New("Pizza")
print(Pizza.maxStackSize) --Prints 64 since it found the property in Item.
Now let’s say the player is holding an item in their hand (think Minecraft). We don’t know what it is other than an item, so it could be a pizza, a sword, or a bone, among other things. But we only want them eating it if it’s food, which pizza is since it’s a subclass of food, meaning it inherits from our Food object. So how do we find out if pizza is actually a food? Well, if you recall the IsA method that can be used on instances in Roblox, we’ll need something exactly like that, except we need to make our own.
function Object:Is(other)
while self ~= other and self ~= nil do
self = self.Parent
end
return self == other
end
This method will traverse up the hierarchy until it finds an object that matches the one being checked in which case it will return true, or runs out of parents to check in, AKA, it hit Object, which doesn’t have a parent, at which point the method will return false.
print(Object:Is(Pizza)) --false
print(Food:Is(Item)) --true
print(Pizza:Is(Item)) -- true
Putting this into use, the pseudo-code would look like this:
if player.itemInHand:Is(Food) then
player.hunger = player.hunger + itemInHand.foodValue
player.itemInHand = nil
end
And of course, this has many more uses, such as checking if an item can be equipped, checking if an animal is a mammal, or checking if an orc is a humanoid.
Notes:
*I was initially going to use some “clever” recursion to do the “is” check in one line, but it ended up being slower due to all the function calls.