Here are some adjustemnts i made for people who want it to work with Player-NPC and not just Player-Player
-
1- Take the Client script under the WeldPlayersTogether Module and parent it to StarterPlayer.StarterPlayerScripts instead of inserting it through script
-
2- Edited the module a bit to work with Player-NPC tasks ( The player has to be the movement controller not the NPC)
--[[
'Weld' two characters together.
This is done by welding both characters to an assembly and giving the first player network ownership over both.
A localscript is inserted to playerscripts for all players that join.
This handles the movement for all welds that are owned by that player.
Advantages:
* Smooth movement (similar to vehicle assemblies with seats)
* Server doesn't have to handle updates every frame; clients handle it
Disadvantages:
* Weld 'owner' is given full movement control over the target character, so succeptible to exploits
]]
local TAG = "CustomPlayerWeldAssemblyPart"
local CollectionService = game:GetService("CollectionService")
local Players = game:GetService("Players")
local weldModel = script.WeldModel
-- Update collisions enabled/disabled for the character (char that doesn't have network ownership)
local function updateCollisions(char, canCollide)
if not canCollide then
-- Disable collisions, and keep track of which we disabled using an attribute, so we can re-enable collisions later
for _, v in pairs(char:GetDescendants()) do
if (v:IsA("BasePart") or v:IsA("MeshPart")) and v.CanCollide == true then
v.CanCollide = false
v:SetAttribute("WasCanCollideEnabledBeforeWeld", true)
end
end
else
-- Enable collisions; only for parts that had collisions enabled originally
for _, v in pairs(char:GetDescendants()) do
if v:GetAttribute("WasCanCollideEnabledBeforeWeld") then
v.CanCollide = true
v:SetAttribute("WasCanCollideEnabledBeforeWeld", nil)
end
end
end
end
local module = {}
module.CurrentWelds = {
-- Format: player1 = weldModel, ...
}
module.ConnectionsPerAssembly = {
-- Format: weldModel = { conn1, conn2, ... }, ...
}
-- Weld 'part' and 'to' using a WeldConstraint
local function weld(part, to)
local weld = Instance.new("WeldConstraint")
weld.Name = "PlayerWeldConstraint"
weld.Part0 = to
weld.Part1 = part
weld.Parent = to
end
-- Add weld between two players
-- 'offsetCFrame' is an offset starting from player1's rootpart
-- 'player1' gets network ownership of both characters; 'player2' is the player that initiated the weld
function module.Setup(Character1: Model, Character2: Model, offsetCFrame)
if not Character1 or not Character2 then
return
end
local char1, char2 = Character1, Character2
if not char1 or not char2 then
return
end
local root1, root2 = char1:FindFirstChild("HumanoidRootPart"), char2:FindFirstChild("HumanoidRootPart")
if not root1 or not root2 then
return
end
local humanoid1, humanoid2 = char1:FindFirstChild("Humanoid"), char2:FindFirstChild("Humanoid")
if not humanoid1 or not humanoid2 then
return
end
-- Disable collisions on the char that is moved around by the assembly forces (prevent collision issues and physics flings)
updateCollisions(char2, false)
local model = weldModel:Clone()
-- For some reason we can't change the relative cframes when the weldconstraint is enabled, so we briefly disable it here...
model.BasePart.WeldConstraint.Enabled = false
-- Setup defaults: assembly floats at char1's root position
model.BasePart.CFrame = root1.CFrame
model.BasePart.AlignOrientation.CFrame = model.BasePart.CFrame
model.BasePart.AlignPosition.Position = model.BasePart.Position
-- Set relative cframe of 'BasePart' and 'Part'
model.Part.CFrame = offsetCFrame
model.BasePart.WeldConstraint.Enabled = true
-- Instantly teleport second character towards dest and weld them to assembly
char2:SetPrimaryPartCFrame(model.Part.CFrame)
weld(root2, model.Part)
model.Parent = workspace
local PlayerOwner = game.Players:GetPlayerFromCharacter(Character1)
-- Set NetworkOwner of assembly to be 'char1'
model.BasePart:SetNetworkOwner(PlayerOwner)
-- Set attribute and tag, so 'player1' knows they can control the assembly to their rootpart
model.BasePart:SetAttribute("UserId", PlayerOwner.UserId)
CollectionService:AddTag(model.BasePart, TAG)
-- Save assembly so we can cleanup later (save to 'player2' instead of 'player1'!)
module.CurrentWelds[Character2] = model
-- Cleanup if either character dies (humanoid.Died or removed from workspace)
module.ConnectionsPerAssembly[model] = {}
table.insert(module.ConnectionsPerAssembly[model], humanoid1:GetPropertyChangedSignal("Health"):Connect(function()
if not humanoid1 or humanoid1.Health <= 0 then
module.Cleanup(Character2)
end
end))
table.insert(module.ConnectionsPerAssembly[model], humanoid2:GetPropertyChangedSignal("Health"):Connect(function()
if not humanoid2 or humanoid2.Health <= 0 then
module.Cleanup(Character2)
end
end))
table.insert(module.ConnectionsPerAssembly[model], char1:GetPropertyChangedSignal("Parent"):Connect(function()
if not char1 or not char1.Parent then
module.Cleanup(Character2)
end
end))
table.insert(module.ConnectionsPerAssembly[model], char2:GetPropertyChangedSignal("Parent"):Connect(function()
if not char2 or not char2.Parent then
module.Cleanup(Character2)
end
end))
end
-- Cleanup assembly, and release both players from welds
-- NOTE: Pass 'player2'; NOT the player that has network ownership!
function module.Cleanup(Character2)
if not Character2 then
return
end
updateCollisions(Character2, true)
local model = module.CurrentWelds[Character2]
if model then
-- Cleanup connections for this assembly
if module.ConnectionsPerAssembly[model] then
for _, v in pairs(module.ConnectionsPerAssembly[model]) do
v:Disconnect()
end
module.ConnectionsPerAssembly[model] = nil
end
-- Cleanup assembly model
model:Destroy()
module.CurrentWelds[Character2] = nil
end
end
return module
- 3- Changed the third argument of
module:Setup()
from offsetCFrame
to actual CFrame for more flexible use.
Here’s the server script
local ServerStorage = game:GetService("ServerStorage")
local Players = game:GetService("Players")
local WeldPlayers = require(ServerStorage.WeldPlayersTogether)
local CHARACTER1, CHARACTER2 = workspace:WaitForChild("Musab_YB"), workspace:WaitForChild("Dummy")
task.wait(3)
WeldPlayers.Setup(CHARACTER1, CHARACTER2,
CFrame.lookAt( CHARACTER1.PrimaryPart.CFrame * (CFrame.new(0,0,-5)).Position ,CHARACTER1.PrimaryPart.Position) -- Don't worry about this it's just a CFrame
)
task.wait(3)
WeldPlayers.Cleanup(CHARACTER2)
Thanks @apenzijncoolenleuk1 Great work!! i love the idea behind it