Best OOP Approach for an Open World Game?

So, I recently started working on this new project… and I had an Idea for the Player Mechanics…
I wanted to make a SuperClass called PlayerClass and have Conditions in it such as:

isRunning
isJumping
isVaulting
canTilt
isCameraShaking

etc…

and then I thought I could have classes such as Weapons, Camera (Custom scripted), Movement (Custom) all inherit those conditions and use them operate… but I wasn’t sure if this was the best approach… I’m going for an Apocalypse Rising 2 style game and I was hoping to emulate their Character System but I’m not sure how they handled it… does anyone have any ideas or methods or approaches?

1 Like

Well for one, you can start by searching for specific possibilities on the wiki. (https://developer.roblox.com/en-us)

I think the best place to start for you, would be the Humanoid
(https://developer.roblox.com/en-us/api-reference/class/Humanoid)

2 Likes

No, sorry I guess I didn’t explain well, I’m working with ROBLOX’s Default Character I’m just editing it to my liking and and customizing things for my use, but I want to know how I can do all of this in a good OOP Approach that’s efficient, I’m looking for Organization, Efficiency and Good Practice.

1 Like

Oh my bad, I can’t help with this specific question… Sorry, but good luck!

It’s fine someone said instead of an OOP Approach, I can just have all the functions for each System in seperate modules and interlace them with one script

1 Like

The approach is kind of subjective. The best way is your most comfortable way in my opinion.

I think that In OOP, you should be keeping your code as coupled as possible. In other words, have your code closely related: code for weapons and movement are unrelated so have them setup in different modules.

You can handle what you described by passing in the class as a constructor to another class with either a function to construct your class OR the __call metamethod. Here’s something I wrote up and how I would approach something like what you’re describing from what I understand:

the Player Module, which could be responsible for custom “properties” like isRunning

local player = game.Players.LocalPlayer`

local self = {}

local function init(character) --humanoid listener(s) need to be updated
	local humanoid = character:WaitForChild("Humanoid") --our new humanoid
	
	self.isRunning = humanoid.Running:Wait() >= 1
	humanoid.Running:Connect(function(speed)
		self.isRunning = speed >= 1
	end)
end

local init_connection = player.CharacterAdded:Connect(function(character)
	init(character)
end)

return self

the Weapon Module, which could be responsible for handling the player’s weapon. This will be our ‘class’ with a constructor because I want to implement the Player class to use isRunning from it.

local function construct(PlayerClass) --construct these methods with the implementation of the PlayerClass class.
	local self = {}
	
	self.swing = function()
		local success = PlayerClass.isRunning
		if not(success) then
			return
		end
		
		-- code to swing ...
		
		return true
	end
	
	return self
end

return setmetatable({}, {
	__call = function(self, PlayerClass) --the constructor
		return construct(PlayerClass)
	end
})

The script that requires these ‘classes’, and some usage:

local PlayerClass = require(script:WaitForChild("Player"))
local WeaponClass = require(script:WaitForChild("Weapon"))(PlayerClass)

--example usage: try to swing the sword every second
while true do
	print(
		WeaponClass.swing() and
			"Swinged sword" or
			"Cannot swing sword" --because the player is not running
	)
	
	wait(1)
end

Also, you might be wondering why I didn’t just require Player class inside of Weapon class. It’s because we might want to change something in the table ourselves, which would effect everything else that will construct the classes with. Everything we change in PlayerClass inside our initializing script will also change for our Weapon’s because they’re using the same pointer.

This works well and I do this it is a good approach, however, a lot of things are good approaches. Like said above, imo, just do whatever works and what you’re most comfortable with. Good luck!

5 Likes

Thank you bro I’ll try it out and see the results, bless

I’ve heard some success with using a central CharacterWrapper object. Singletons are generally considered a bad idea, but it would essentially be a service for everything pertaining to the character. You can add custom attributes, such as “IsToolEquipped”, “ToolEquipped”, etc.

Other components of the game, such as tools, can then interface through the wrapper. Make sure not to mingle the wrapper with things that shouldn’t be part of the wrapper. I.e, GUIs shouldn’t be part of the character wrapper, but they can access data to it. And modify, but you should give the wrapper explicit setter functions for other modules to use.

Any examples of how this approach is applied with code? I have no clue how to make a CharacterWrapper object.

CharacterWrapper is just the term I used, it’s not a legit thing or concept. Generally, “wrapper” means it “wraps” something with additional functionality.

There are lots of approaches to it. And unfortunately, you might go down a deep, dark rabbit hole on this topic. I’d say study and practice the following class paradigm though.

local ClassName = {}
ClassName.__index = ClassName

function ClassName.new()
   local self = setmetatable({},ClassName) --create an object from class/template ClassName
   --initialize object's properties
   return self
end

local MyObject = ClassName.new()

(You can also do ClassName.__index = function(key). That goes into metatables, which would be very helpful for OOP)

If you need a working example of the code above, with a little explanation, here:

1 Like