NEW VERSION HERE:
Hey there! This is my first tutorial on the devforum, please point out errors / typos if you see any.
This tutorial is basic / intermediate level, you will need to know about these topics.
- modules and metatables
- welding
- joint editing
Images coming soon :’(
PLUGINS USED
I used this plugin to edit welds, you will need it for this tutorial
https://create.roblox.com/marketplace/asset/245496280/Joint-Edit?pageNumber=1&pagePosition=0&keyword=joint+edit
Alright, time to start for real now!
1.0 Setting up
Insert 3 folders
into ReplicatedStorage
, and name them “Functions”, “Tools”, and “Modules”. We will be using these later
Grab a tool model from the toolbox(If it’s a tool, remove all scripts and only keep the parts), or make one (I’m bad at building so I got one from the toolbox lol)
If the tool you got from the toolbox / made doesn’t have an invisible part named handle, make one, and position it near the handle of the tool. Make sure the entire model is UNACHORED AND CANCOLLIDE IS CHECKED OFF. Now, make the primary part of the model the handle part and inside, add a motor6d
Name the motor6d something like “torso”
Set the Part1
of the motor6D
to the handle part, and your tool is set for joint editing!
This tutorial will now be continued based on if you want to implement it using r15 or r6
1.1 Multiple part model
If you are using a model with multiple parts, WELD THE TOOL TOGETHER USING WeldConstraints
TO THE HANDLE BEFORE FOLLOWING THE NEXT STEPS!!!
1.1 r15
Insert an r15 rig into workspace
Drag your tool into the rig.
Look for the motor6D
inside of the handle
part in the tool, and make Part0
UpperTorso or LowerTorso(Your choice lol)
INSTALL THE PLUGIN I LINKED AT THE TOP IF YOU HAVEN’T ALREADY!!
In the explorer, select the motor6D
in the part named handle
, and open the Joint Editor plugin.
Use the rotation and movement tools to edit the weld
After you are satisfied with the result, click save and click on the Joint Editor plugin in the plugins tab to close it.
MAKE THE PART0 OF THE MOTOR6D NIL
Drag the tool in ReplicatedStorage > Tools
(One of three folders we made.)
1.2 R6
Do the exact same thing except instead of changing the Part0
of the Motor6D to UpperTorso
or LowerTorso
, change it to Torso
2.0 Modules!
Insert a module script into ReplicatedStorage > modules
, and name it whatever you want, in this case, I named it combat (Because I am using a sword as my model)
Type this out in the module(It’s ok if you don’t understand, I never understood it before too)
local module = {}
local meta = {__index = module}
function module.new(toolName)
local self = {} -- creating a table to hold all our values
self.tool = toolName -- tool name
self.equipped = false -- if its equipped, you will be needing this if you want the tool to do stuff
self.canUse = false -- debounce
return setmetatable(self, meta) -- metatable stuff (makes it so you can access "self" from any function in the module
end
function module:equip()
if not self.tool and not self.equipped and not self.canUse then return end -- checks
local tool = game.ReplicatedStorage.Tools:WaitForChild(self.tool)
local check = game.ReplicatedStorage.Functions.equip:InvokeServer(tool)
if check.returning then -- if the tool was equipped
self.equipped = true
self.canUse = true
warn(check.error)
else -- if the tool wasn't equipped and an error occured
warn(check.error)
end
end
return module
2.1 Server sided code
Insert a script
into ServerScriptService
and type this
R15 VERSION
local equipFunc = game.ReplicatedStorage.Functions.equip
equipFunc.OnServerInvoke = function(plr, tool)
local char = plr.Character or plr.CharacterAdded:Wait() -- character of player
if not char then return {returning = false, error = "Could not find character"} -- if no character, returns an error table
tool = tool:Clone()
local main = tool.PrimaryPart
local motor = main:FindFirstChild("torso") or main:FindFirstChildWhichIsA("Motor6D")
if not main and motor then return {returning = false, error = "Could not find tool handle and/or torso motor"} -- error table 2
if not char["UpperTorso"] then return {returning = false, error = "Could not find character torso"} -- error table 3
motor.Part1 = main
motor.Part0 = char["UpperTorso"] -- OR char["LowerTorso"]! -- connects tool to body
return {returning = true, error = "successful!"} -- if succesful
end
R6 VERSION
local equipFunc = game.ReplicatedStorage.Functions.equip
equipFunc.OnServerInvoke = function(plr, tool)
local char = plr.Character or plr.CharacterAdded:Wait()
if not char then return {returning = false, error = "Could not find character"} -- if no character, sends back error table
tool = tool:Clone()
local main = tool.PrimaryPart
local motor = main:FindFirstChild("torso") or main:FindFirstChildWhichIsA("Motor6D")
if not main and motor then return {returning = false, error = "Could not find tool handle and/or torso motor"} -- error table 2
if not char["Torso"] then return {returning = false, error = "Could not find character torso"} -- error table 3
motor.Part1 = main
motor.Part0 = char["Torso"] -- connects tool to body
return {returning = true, error = "successful!"} -- if succesful
end
Now, you may be wondering, why aren’t you just calling the RemoteFunction
from a separate script instead of using a module?
Good question!
The reason for this is code management
If we use modules, we can store any other related functions into the module, for a easy and secure place for them to be kept.
2.2 Local sided code and mobile support
Add a LocalScript
into StarterPlayer > StarterCharacterScripts
Now type this code
local plr = game.Players.LocalPlayer
local char = plr.Character or plr.CharacterAdded:Wait()
local ContextActionService = game:GetService("ContextActionService")
local combatModule = require(game.ReplicatedStorage.Modules.combat) -- module we just made
local sword = combatModule.new("sword")-- your weapon name here
--[[Multiple tools
local tools = {
[1] = combatModule.new("sword"),
[2] = combatModule.new("flashlight")
}
local keycodes = {
[1] = Enum.KeyCode.One, -- keyboard button 1
[2] = Enum.KeyCode.Two, -- keyboard button 2
[3] = Enum.KeyCode.Three, -- keyboard button 3
}
]]
local function equip()
sword:equip()
end -- don't know if you can just call sword:equip() through ContextActionService so i made a new function :/
--[[Multiple tools
local function equip(WepName)
tools[WepName]:equip()
end
]]
ContextActionService:BindAction("EquipSword", equip, true, Enum.KeyCode.One) -- binds the equip function to keyboard button 1
--[[Multiple weps(again)
for i, v in pairs(tools) do
ContextActionService:BindAction("Equip"..v, equip(v), true, keycodes[i]) -- binds multiple equip functions with keyboard buttons 1, 2, and 3
end
]]
That should be enough code to make it equip nicely. This should work in mobile, too.
2.3 EXTRAS
Unequipping is easy, just destroy the weapon in the server side using a RemoteFunction
, and if it is usable, unbind the action using ContextActionService:UnbindAction(ActionName)
. Make a function in the module like module:unequip()
, then fire a RemoteFunction
from there to make it easier on you
To make it usable, add some code in the module, bind another action using ContextActionService
, and call the action from the module, OR make a function like this
local function use()
BlahTool:doSomething()
end
For switching, just add more binding and unequip the first tool, equip the second.
Maybe legacy moon animation suite can be used to edit welds instead of Joint Edit, I haven’t tested it yet though, but I know Joint Edit works
2.4 CONCLUSION
Alrighty, I hope you guys enjoyed this tutorial. The best part about the module is its flexibility. You can really just add more to the module for animations and stuff
Remember though, damage HAS TO BE HANDLED IN THE SERVER
Alright, thanks for making it this far and trusting in me. Please point out errors. Thanks and see you hopefully next time
Edit: BETTER VERSION OUT NOW!!!