#Is This LocalScript Approach Modular and Efficient for Managing All Tools?

1 messages · Page 1 of 1 (latest)

glacial moss
#

Hey everyone,

I’ve posted a question on the Roblox DevForum regarding a modular and efficient approach to handling tools using a single LocalScript in my game. If you have any insights or suggestions, I’d really appreciate it if you could take a look and share your thoughts!

Here’s the link to the post: https://devforum.roblox.com/t/is-this-localscript-approach-modular-and-efficient-for-managing-all-tools/3592100

Thanks in advance!

carmine geode
# glacial moss Hey everyone, I’ve posted a question on the Roblox DevForum regarding a modular...

Hello. Thanks for the question. Hopefully my opinion will shed some light on your topic.

Using a single LocalScript to manage every client side function of every tool is, albeit capable of functioning, probably not the most modular way, which you’re concerned about, to handle tools.

ModuleScripts are created for purposes like these, and what makes them great is that you can use metatables to create individual tool references that can either clean themselves up, as long as you set up a :Destroy method and call it somewhere in your module, i.e tool deletion etc. This also allows the possibility to access your tools custom methods externally.

An example of this would be having an Equip and Unequip method in your module, whereby this module can be required from any other (same-runtime sided script i.e client) and you can call these methods to equip and unequip your tool even if the user didn’t press the equip or unequip button. This allows for seamless customisability and modularity.

One thing that I like to do with modules is inheritance, whereby I create a base Tool class that handles simple Equip, Unequip, cleanup handling etc, and then I can create a subclass of Tool, for example one for handling ranged weapons, melee weapons etc. These can then become a base class for your ranged and/or melee weapons.

#

Essentially, in short, an “OOP” approach would be much more modular. You don’t have to “create” multiple LocalScripts as you can have ModuleScripts that have one purpose yet can be attached to multiple tools at once using metatables.

#

ModuleScripts don’t run any code unless you require them or call the methods you define in them if there’s no boilerplate code, further allowing modularity because this means you can link tools whenever you want instead of instantly when the client loads in.

#

Albeit your current method is fine, and should have negligible performance impacts, ModuleScripts in my opinion would be the way to go.

#

And if you really want then you can look at Frameworks like Knit by sleitnick and NevermoreEngine by Qwenty, opening up more possibilities for modularity and an easier workflow management system for your game. Once you get the hang of ModuleScripts I’m sure you’ll either find Frameworks useful or you won’t, it’s a matter of personal preference and what your specific needs are.

#

I hope this explanation was sufficient. If you have any other questions, please feel free to shoot.

#

@glacial moss

glacial moss
#

I’m not only aiming for modularity and customizability, but I also want to ensure that performance impacts and sanity checks are handled as efficiently as possible too.

#

Because my tool requires many inputs and effects such as clicks, keycodes, animations, sounds, etc.

thorny bridgeBOT
#

studio** You are now Level 1! **studio

glacial moss
#

I usually use OOP ModuleScripts to run any mechanics related to the server side, so I'm not sure if client-sided mechanics can be run in a ModuleScript.

carmine geode
#

ModuleScripts can be used on either side.

carmine geode
carmine geode
carmine geode
#

Event-driven programming is pretty performant imo.

glacial moss
# carmine geode Event-driven programming is pretty performant imo.

Thank you for the clarification! Regarding event cleanup, what’s the best practice for managing events in the ModuleScript, especially when dealing with tool activation, equip/unequip, and user input? How can I ensure that these events don’t cause memory leaks or performance issues in a game with many tools?
I apologize for my lack of understanding. 🙏

carmine geode
#

Whenever you connect to an event, you can append it to a table that contains connected events, either with keys or just index based, and clean them up when required.

#

If you connect to input functions then you can use ContextActiomService, where you can bind keybinds in circumstances where they can be used, and unbind them where they’re unnecessary.

#

E.g you only need to listen for inputs when the tool is equipped, so bind an action to ContextActiomService in the equip method and Unbind in the unequip method as an example.

#

There are endless possibilities for managing your code, it truly depends on what you need, and that I’m sure you can figure out.

glacial moss
carmine geode
glacial moss
carmine geode
#

They’re practically the same and are interchangeable. The only difference is that ContextActionService allows you to bind keys in context and when required. You can do the same with UserInputService, but you have to store a reference to that connection, whereas ContextActionService allows you to give an action a name and unbind it using that same name anywhere on the same runtime side.

glacial moss
#

Ahh I see... Thanks for all of your advice!

carmine geode
#

No problem. If you have anything else to ask do let me know.

glacial moss
#

If it is possible to place it in the Script in ServerScriptService then there is a possibility to reduce the risk of script skiddings....

carmine geode
#

ModuleScripts are allocated in memory on client and server individually, the server cannot access the client allocation, and the client not the server.

#

Clients cannot access ServerScriptService, so the Module can go anywhere the client can access, usually ReplicatedStorage.

#

If a client manipulates a modulescript, only the client sees those changes since it’s allocated on their machine only, so security is completely fine in this manner.

#

You should still however use the server as the authority. Though sanitising on the client can prevent events being fired unnecessarily, exploiters can fire their own events by ignoring your event sanitiser on the client, so make sure the server sanitises data too.

#

As always, preventing the running of code that can fail by sanitising beforehand is a performance benefit in and out of itself.

glacial moss
# carmine geode As always, preventing the running of code that can fail by sanitising beforehand...
-- Tool (ModuleScript, ReplicatedStorage)
local ToolModule = {}
ToolModule.__index = ToolModule

local ContextActionService = game:GetService("ContextActionService")

function ToolModule.new(tool)
    local self = setmetatable({}, ToolModule)
    self.tool = tool
    self.isEquipped = false
    self.damage = 10
    self.mouseHold = false
    self.rayDistance = 50
    self.inputBinding = "EquipTool"
    return self
end

function ToolModule:equipped()
    self.isEquipped = true
    ContextActionService:BindAction(self.inputBinding, function(_, inputState, _)
        self:handleInput(inputState)
    end, false, Enum.KeyCode.E)
end

function ToolModule:unequipped()
    self.isEquipped = false
    ContextActionService:UnbindAction(self.inputBinding)
end

function ToolModule:handleInput(inputState)
  -- input checks
            self:performRaycast()
end

function ToolModule:performRaycast()
    -- do raycast and checks here
            self:dealDamage(part)
end

function ToolModule:dealDamage(part)
    -- check raycasts and get variables
        humanoid:TakeDamage(self.damage)
end

function ToolModule:init()
    -- initialize things such as gui, hold animation, etc.
end

return ToolModule

Is that a good example of OOP ModuleScript to handle the entire tool mechanics?

thorny bridgeBOT
#

studio** You are now Level 2! **studio

carmine geode
#

Mhm, looks good on my mobile lmao.

#

For contextactionservice you can also return Enum.ContextActionResult.Pass or Sink

#

Very useful

glacial moss
#

Oh

#

But to call these methods, should I use another LocalScript or just call it on Script?

#

Like :init(), :equipped()

carmine geode
#

You can just use one localscript that initialises the module and attaches it

#

Similar to what you made above

#

But you can remove input bindings etc and just attach the module on onToolAdded

#

And destroy on onToolRemoved

#

Or handle onToolRemoved internally in the module

#

And you can call the methods internally

#

You can bind .Equipped etc in the module itself

#

Because then the module can clean it up itself

#

So it’s self-contained

#

Which is a good implementation for a module

#

Usually making them self-contained is their best use

glacial moss
#

That means, I have to do this on LocalScript?

-- Client (LocalScript, StarterPlayerScripts)
local player = game.Players.LocalPlayer
local toolModule = require(game.ReplicatedStorage:WaitForChild("ToolModule"))

local tools = {}

for _, tool in ipairs(player.Backpack:GetChildren()) do
 -- do checks
        local toolObj = toolModule.new(tool)
        tools[tool.Name] = toolObj
        toolObj:initialize()
end

player.Backpack.ChildAdded:Connect(function(child)
-- do checks
        local toolObj = toolModule.new(child)
        toolObj:initialize()
end)

player.Backpack.ChildRemoved:Connect(function(child)
  -- do checks
        tools[child.Name]:unequipped()
end)
carmine geode
#

Basically yeah

#

But you can also handle equipped etc internally in the module itself

#

That works too

glacial moss
#

Like this for example:

-- Server (Script, ServerScriptService)
local CollectionService = game:GetService("CollectionService")
local toolModule = require(game.ReplicatedStorage:WaitForChild("ToolModule"))

local tools = {}

local function bindTool(tool)
    local toolObj = toolModule.new(tool)
    toolObj:initialize()
    tools[tool.Name] = toolObj
end

local function unbindTool(tool)
    if tools[tool.Name] then
        tools[tool.Name]:unequipped()
        tools[tool.Name] = nil
    end
end

for _, tool in ipairs(workspace:GetChildren()) do
    if CollectionService:HasTag(tool, "Weapon") then
        bindTool(tool)
    end
end

CollectionService:GetInstanceAddedSignal("Weapon"):Connect(function(tool)
    bindTool(tool)
end)

CollectionService:GetInstanceRemovedSignal("Weapon"):Connect(function(tool)
    unbindTool(tool)
end)
carmine geode
#

Toolmodule.new has to be run outside the module

#

As the code doesn’t run itself

#

Init can be ran inside .new if you prefer

glacial moss
carmine geode
#

Up to you

glacial moss
carmine geode
#

I think it’s nice to do it outside

#

Because you can attach with .new and then wait for a server condition to then :init

glacial moss
#

So I don't need any LocalScripts doing inputs and RemoteEvents? Only a single server Script and ModuleScript?

carmine geode
#

Mhm

#

Well you need to take UIP inputs on the client obviously

glacial moss
#

Wow, that's very simple

carmine geode
#

.Activated can be used on the server though

glacial moss
#

But I'm using ContextActionService for most inputs rather than .Activated

carmine geode
#

Yeah so client sided for that

#

And you can use events for server inquiries

glacial moss
#

So do I actually need LocalScripts and RemoteEvents for inputs or just do it all in ModuleScript like you said?

#

Or a LocalScript doing inputs and then bind it to :Activate()?

carmine geode
#

You can have a modulescript check to determine if the Runtime is client or server

#

If it’s client you can bind inputs in the module

#

You can have a client Tool module and a server Tool module

glacial moss
#

Hmm?

thorny bridgeBOT
#

studio** You are now Level 3! **studio

carmine geode
#

Inputs can only be handled on client side

glacial moss
#

Based on my understanding, is it correct to do this?

  • ContextActionService - localscript
  • raycast, damage - modulescript
  • effects - modulescript
  • .new() - script
  • equip/unequip - script
carmine geode
#

Yes

glacial moss
#

Do I have to check equip and unequip on localscript too?

#

both on localscript and script

#

?

carmine geode
#

If you want to use contextactionservice as intended then yes

#

The Tool module can be used by client and server but you can limit server only and client only methods by checking RunService

glacial moss
#

Inside the ModuleScript?

carmine geode
#

Mhm

glacial moss
#

What about things that requires special condition like Viewmodel and Camera Manipulation, should I do that on LocalScript or ModuleScript?

#

Because I want the tool works on TPS and FPS

carmine geode
#

You can create another modulescript

#

And inherit the Tool base class

#

The new modulescript can be used to handle FPS and TPS

#

Anywho I’m going to have to go as I have college. Good luck with your journey.

glacial moss
#

Oh okay

#

Thank you so much for all the advice, time, and effort you’ve given me. I truly appreciate your help and guidance. I’m sorry if I’ve asked too much, but it’s really been valuable.

#

If you'd like to see the results of the scripts I’ve been working on, feel free to reach out in the next 1-2 days, and I’ll be happy to share them with you.