A quick question regarding OOP

Hi all. I’ve determined that I would like to try to use an object-oriented approach for the game I’m developing, which involves NPCs that are capable of fighting both other NPCs and players. A useful object for such a game to have would therefore be an NPC, as I would like to have many instances of different kinds of NPCs that each have their own independent set of data and behavior.

I have read about OOP here and believe that I understand what it is saying. However, I am confused as to how to proceed from where it leaves off.

In particular, here is a section of the tutorial:

--module script called Car in game.ReplicatedStorage
Car = {}
Car.__index = Car

function Car.new(position, driver, model)
    local newcar = {}
    setmetatable(newcar, Car)

    newcar.Position = position
    newcar.Driver = driver
    newcar.Model = model

    return newcar
end

function Car:Boost()
    self.Speed = self.Speed + 5
end

return Car

--main script

Car = require(game.ReplicatedStorage.Car)

newcar = Car.new(Vector3.new(1,0,1), "Guest1892", game.ReplicatedStorage.F1Car)
newcar:Boost()

My confusion lies with how to use the above to actually generate, for example, an F1 car in the physical game world, which is not explained in the tutorial.

If I were creating, for example, a Part to put in the game world, I could do something like

newpart = Instance.new("Part")
newpart.Size = Vector3.new(2,2,4)
newpart.Position = Vector3.new(1,0,1)
newpart.Parent = game.Workspace

which would, of course, result in a part of size 2x2x4 appearing at position (1,0,1) in the game. How do I do this for the car, though? newcar is not an Instance; rather, it is a table with some values; as such, doing something like newcar.Parent = game.Workspace would not yield the desired effect. The “Instance-like” component of newcar is the model F1Car contained in ReplicatedStorage. As such, doing something like

newcar = Car.new(game.ReplicatedStorage.F1Car:Clone(), "Guest1892", Vector3.new(1,0,1))
newcar.Model.Parent = game.Workspace

does result in the model appearing in the game, but this doesn’t seem to me to be the correct procedure to use, since the newcar table (or at least parts of it) appears to now be redundant; (1,0,1) for example, doesn’t actually represent the position of the car in the game – to achieve that, I would need to change the position of the model itself to be (1,0,1), and now I’ve got two copies of this position.

I suppose my overarching point is this: with Instance.new(), there seems to be an intuitive relationship between the creation and manipulation of the object and the physical representation of that object ingame. However, I don’t see how to achieve this relationship with the kinds of custom-made objects and constructor functions as described in the tutorial. I’m sure this is just a result of my poor understanding of the topic at hand, so any and all relevant pointers would be appreciated.

The entire point of OOP is to hide the process from the end user in an easy to work with fashion. You cannot expect the class to move your car to a certain position without coding the process yourself. Lua just understands that you want to insert a certain Vector3 into a table with a “Position” string as its index.

2 ways of achieving this is by using metamethods or more methods.

Metamethods are functions or tables that get called or accessed when a certain operation is attempted. They basically change the behavior of your table. For example, __newproxy will fire whenever you insert a value into a table, allowing you to filter it, or, in your case, move an instance to the inserted Vector3.

The issue with metamethods is that they are confusing for new developers and can represent a major overhead as a function has to be called every time you do a certain operation.

The 2nd way is more obvious and my favorite. You could just add a :SetPosition(pos) method and be done with it.

1 Like

First of all, I would like to emphasize that OOP is a programming style, which means that you still have to do everything you did before (create, move clone, destroy instances), but now you will do it from another point of view. That is, thinking that everything in your game are objects that have properties, perform actions and collaborate with each other to achieve one or several objectives.

The OOP equivalent would be this

local NewPart = {}

NewPart.__index = NewPart

function NewPart.new(position, height, width, thickness)
    local self = {}
    setmetatable(self, NewPart)

    local newpart = Instance.new("Part")
    newpart.Size = Vector3.new(height, width, thickness)
    newpart.Position = position
    newpart.Parent = game.ReplicatedStorage
    self.Instance = newpart

    return self
end

local newPart = NewPart.new(Vector3.new(1,0,1), 2, 2, 4)
newPart.Instance.Parent = workspace

Exactly. Before creating a car class you must think what properties it will have and what actions it will perform. Maybe this makes more sense:

--module script called Car in game.ReplicatedStorage
local Car = {}
Car.__index = Car

function Car.new(instance)
    local newcar = {}
    setmetatable(newcar, Car)

    newcar.Insatnce = instance:Clone()

    return newcar
end

function Car:SetPosition(position)
    self.Instance:SetPrimaryPartCFrame(CFrame.new(position))
end

function Car:SetCarName(name)
    self.Name = name
end

function Car:Spawn()
    self.Instance.Parent = workspace
end

return Car

--main script

local Car = require(game.ReplicatedStorage.Car)

local newcar = Car.new(game.ReplicatedStorage.F1Car)
newcar:SetName("Guest1892")
newcar:Setposition(Vector3.new(1,0,1))
newcar:Spawn()
2 Likes

This sounds like a bad idea. Keep in mind there is a difference between OOP and using classes. OOP is just a buzzword that no one really knows what it means. Everyone else has a different meaning for it.

You’re only partially right on that one. OOP is clearly defined as a programing style that uses classes to define object behavior and objects are the elements on which operations are performed. The issue lies with people who call their code OOP because they made a dictionary or a struct.

I agree with you when it comes to actual use. OOP is only good when you have a large amount of similar objects, where a constructor can simplify the code a lot. But there are a lot of people who create a class because they need only one object. The best way to approach is to mix styles depending on situation.

You don’t need classes to use OOP. You can do OOP without classes. That’s all I disagree with.

that’s the singleton pattern. It seems to me that all services are singleton.

Yep, I just pointed that out as Java devs often have a bad habit of using hundreds of singletons when they aren’t needed.

Ok all, thanks for the replies. They are helpful.