Help with creating a base class correctly (oop inheritance)

Hi,

I’ve been having trouble for a week and some days and no one has been able to solve the issue with how I am setting up my base classes.

My problem is that the variables aren’t being shared + the more subclasses that use the base class means the more times it iterates itself…

MAIN CODE:

Subclass Example
--!strict
--[[
	Test sub-class
--]]

--// roblox services
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local TweenService = game:GetService("TweenService")
--// Dependencies
local BaseMovement = require(script.Parent.BaseMovement)
--// class
local Test = {}
Test.__index = Test

export type ClassType = typeof( setmetatable({} ::  {
	
} , Test) ) & BaseMovement.ClassType

--// constructor
function Test.new(): ClassType
	local self = setmetatable(BaseMovement.new() :: any, Test)
	
	return self
end

function Test.Bind(self: ClassType, actionName, inputState, _inputObject): ()
	if not self.character then
		return
	end
	
	local Character = self.character
	local Humanoid = Character:FindFirstChild("Humanoid") :: Humanoid
	
	if inputState == Enum.UserInputState.Begin then
		print("Test start")
	end

	if inputState == Enum.UserInputState.End then
		print("Test end")
	end

end

function Test.GetParameters()
	return {
		actionName = "Test", -- mandatory
		createTouchButton = false, -- optional, will always be false on default
		inputs = { -- mandatory
			Enum.KeyCode.X,
		}
	}
end

return Test.new()
BaseMovement
--!strict

--[[
    The base class for 'movement' // superclass
--]]

--// roblox services
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
--// dependencies
local Trove = require(ReplicatedStorage.Packages._Index["sleitnick_trove@1.1.0"]["trove"])
--// class
local BaseMovement = {}
BaseMovement.__index = BaseMovement

export type ClassType = typeof(setmetatable({} :: {
	BaseConnections: Trove.ClassType;

	character: Model?;
	isMoving: boolean;
	isSprinting: boolean;
}, BaseMovement))

--// constructor
function BaseMovement.new() : ClassType
	local self = {
		BaseConnections = Trove.new(),
		
		character = nil,
		isMoving = false,
		isSprinting = false,
	}

	setmetatable(self, BaseMovement)

	self:_init()

	return self
end

function BaseMovement._init(self: ClassType): ()
	self.BaseConnections:Connect(Players.LocalPlayer.CharacterAdded, function(character: Model)
		if not self.character then
			self.character = character
		end
	end)

	if Players.LocalPlayer.Character then
		self.character = Players.LocalPlayer.Character
	end
end

function BaseMovement.GetCharacter(self: ClassType): Model?
	return self.character
end

function BaseMovement.Destroy(self: ClassType): ()
	self.BaseConnections:Destroy()
end

return BaseMovement

CONTEXT:

Screen Shot 2024-05-04 at 6.45.04 PM

The movement module requires and binds all the subclasses (Test and Test2) that have the bind function

		if type(requiredModule.Bind) ~= "function" or type(requiredModule.GetParameters) ~= "function" then
			continue
		end

then, the subclasses use BaseMovement as the Base Class, which gets the character and the state variables which should be shared.

FILE:

movementmodules.rbxm (2.7 KB)

1 Like

Bump, someone please help me pretty please

1 Like

Here’s an example, you’re gonna have to extract what you need from it.

My base class

local Players = game:GetService("Players")

local Types = script.Parent
local CameraModule = Types.Parent

local Settings = require(CameraModule.Settings)
local Signal = require(CameraModule.Signal)
local Maid = require(script:WaitForChild("Maid"))

local Base = {}
Base.__index = Base

function Base.Start(Data: {})
	local NewType = {}
	
	NewType._running = true
	NewType._player = Players.LocalPlayer
	NewType._camera = workspace.CurrentCamera
	NewType._oldRot = NewType._camera.CFrame.Rotation
	NewType._maid = Maid.new()
	NewType.Stopped = Signal.new()
	
	if Data then
		NewType._oldRot = Data.OldRot or NewType._oldRot
	end
	
	NewType._maid:GiveTask(function()
		NewType.Stopped:Fire()
		NewType.Stopped:DisconnectAll()
	end)
	
	return NewType
end

function Base:Stop(Keep)
	self._running = false
	self._maid:Destroy()
	
	if not Keep then
		self._camera.CameraType = Enum.CameraType.Custom
		self._player.CameraMode = Settings.CameraMode
		self._camera.CFrame = CFrame.new(self._camera.CFrame.Position) * self._oldRot
	end
	
	return self._oldRot
end

return Base

My inheriting class

local RunService = game:GetService("RunService")

local Types = script.Parent
local CameraModule = Types.Parent

local Base = require(Types.Base)
local Enums = require(CameraModule.Enums)

local Attach = {
	Enum = Enums.Attach
}
Attach.__index = setmetatable(Attach, Base)

function Attach.Start(Data: {})
	local NewType = setmetatable(Base.Start(Data), Attach)
	NewType._camera.CameraType = Enum.CameraType.Scriptable
	
	NewType.Delta = 0.0005
	
	if Data then
		NewType.Attached = Data.Attached or nil
		NewType.Delta = Data.Delta or NewType.Delta
	end
	
	NewType._maid:GiveTask(RunService.RenderStepped:Connect(function(dt)
		local Attached = NewType.Attached
		
		if not Attached then return end
		
		local AttachedType = typeof(Attached)
		local AttachCFrame
		
		if AttachedType == "Instance" and Attached:IsA("BasePart") then
			AttachCFrame = Attached.CFrame
		elseif AttachedType == "Attachment" then
			AttachCFrame = Attached.WorldCFrame
		elseif AttachedType == "CFrame" then
			AttachCFrame = Attached
		else
			warn(`Incorrect attached type: {AttachedType}`)
			return
		end
		
		NewType._camera.CFrame = NewType._camera.CFrame:Lerp(AttachCFrame, 1 - NewType.Delta ^ dt)
	end))
	
	return NewType
end

return Attach

2 Likes

I think I changed everything I saw that was different. I added a debug method which I had before in the base class

function BaseMovement.new() : ClassType
	local self = {
		BaseConnections = Trove.new(),
		
		character = nil,
		isMoving = false,
		isSprinting = false,
		
		debuggingEnabled = true,
		instanceId = math.random(),
	}

	setmetatable(self, BaseMovement)
	
	task.spawn(function()
		while self.debuggingEnabled and task.wait(5) do
			print("Instance:", self.instanceId, "isSprinting:", self.isSprinting)
		end
	end)

	self:_init()

	return self
end

In the subclass the only thing I really noticed was

which I think I added

local Test = {}
Test.__index = setmetatable(Test, BaseMovement)

I made 3 test subclasses and one of them changes the isSprinting value.

Screen Shot 2024-05-05 at 3.30.44 PM

DEBUG:

Screen Shot 2024-05-05 at 3.33.52 PM

It created three threads and only one thread has true

Here’s the file: newBase.rbxm (3.1 KB)

BUMP EDIT!@!

1 Like

I’m still a bit confused on what you were trying to do based on the file you sent

Well it’s pretty obvious. I’m trying to use the basemovement as a way to make the states shared through all the subclasses

What would help is if I had an example of base class inheritance with strict mode, then I can see what I did wrong.

What do you mean by shared? If you want the sub classes to update based on the base class that’s different from inheritance

This is what I mean by shared:

local debuggingEnabled = true
local instanceId = math.random()

local BaseCharacterController = {}
BaseCharacterController.__index = BaseCharacterController

function BaseCharacterController.new()
	local self = setmetatable({}, BaseCharacterController)
	self.enabled = false
	self.moveVector = ZERO_VECTOR3
	self.moveVectorIsCameraRelative = true
	self.isJumping = false
	
	task.spawn(function()
		while debuggingEnabled and task.wait(5) do
			print("Instance:", instanceId, "isJumping:", self.isJumping)
		end
	end)
	
	return self
end

I have this base class that works how I want it to work. It shares isJumping and moveVector. When I run the debug, it only prints one true or false depending on if I’m jumping, and multiple subclasses are accessing the isJumping shared property.

That is what I’m trying to achieve

here’s a visual example of it:
https://gyazo.com/2bd014f00788b5b7937489dde3fc9a5e

whereas mine prints multiple threads, as seen on the debug screenshot on the first message

bumpapappapapapaa

1 Like

Yeah, that’s not what inheritance is for. You probably shouldn’t be using OOP for a movement system anyways, it’d be better to have one main module with the data and some submodules or a state machine.