A viewmodel is needed for a fps game. The desired result is that the tool must be in the hands of the viewmodel once the player is in first person.
The issue is that the tool keeps randomly dissapearing every time the tool is welded to the viewmodel. The tool is completely deleted without a trace, destroying event doesn’t fire.
The issue also occurs if zooming in and out, so the Part0 of the RightGrip is changed.
Here is a video demonstrating the bug:
Tricks I have tried that but didn’t work:
Parenting the viewmodel to the workspace instead of the camera.
Using a weldconstraint.
Destroy event in the tool’s script with a warn(), did not output anything.
A trick that Did work is removing the localscript of the ak-47 whilst playtesting locally. However this is very unusual and the ak-47 doesn’t have anything that can influence this bug as has been tested with commenting some lines of code.
If anyone has any idea or rather an alternative please comment. Help is really appreciated!
Viewmodels script
local RunService = game:GetService("RunService")
local PhysicsService = game:GetService("PhysicsService")
local REP = game:GetService("ReplicatedStorage")
local Camera = game:GetService("Workspace").CurrentCamera
if Camera:FindFirstChild("ViewModel") then
Camera.ViewModel:Destroy()
end
local player = game.Players.LocalPlayer;
local character = script.Parent
local Animator = character:WaitForChild("Humanoid"):WaitForChild("Animator")
character.Archivable = true
local Head = character:WaitForChild("Head")
character:WaitForChild("Left Arm")
local RightArm = character:WaitForChild("Right Arm")
local humrp = character:WaitForChild("HumanoidRootPart")
character:WaitForChild("Torso")
local ViewModel = character:Clone()
print(ViewModel)
ViewModel.Parent = Camera
ViewModel.Name = "ViewModel"
character.Archivable = false
local ViewModelAnimator = ViewModel:WaitForChild("Humanoid"):WaitForChild("Animator")
ViewModel.Humanoid.DisplayDistanceType = Enum.HumanoidDisplayDistanceType.None
ViewModel.Humanoid.HealthDisplayType = Enum.HumanoidHealthDisplayType.AlwaysOff
ViewModel.Humanoid.BreakJointsOnDeath = false
ViewModel.PrimaryPart = ViewModel:WaitForChild("Head")
ViewModel.PrimaryPart.Anchored = true
ViewModel:SetPrimaryPartCFrame(CFrame.new(0, 5, 10))
for _, v in pairs(ViewModel:GetDescendants()) do
if v:IsA("BasePart") then
v.Massless = true
v.CastShadow = false
v:AddTag("ForceCanCollide_OFF")
local lowername = v.Name:lower()
if lowername:match("leg") or lowername:match("foot") then
v:Destroy()
elseif not(lowername:match("arm") or lowername:match("hand")) then
v.Transparency = 1
end
elseif v:IsA("Decal") then
v:Destroy()
elseif v:IsA("Accessory") then
v:Destroy()
elseif v:IsA("LocalScript") then
v:Destroy()
end
end
local LoadedAnimations = {}
Animator.AnimationPlayed:Connect(function(AnimationTrack)
if AnimationTrack:GetAttribute("ViewModelAnimation") ~= true then return end -- // Skip animation if it isn't supposed to play on ViewModel.
if LoadedAnimations[AnimationTrack] == nil then -- // Indexing using the animation track.
-- // If this animation was not already laoded then load it.
LoadedAnimations[AnimationTrack] = ViewModelAnimator:LoadAnimation(AnimationTrack.Animation) -- // Load animation on the ViewModel.
LoadedAnimations[AnimationTrack].Priority = AnimationTrack.Priority
end
end)
local function RenderStepped()
--update viewmodel position
ViewModel:SetPrimaryPartCFrame(Camera.CFrame)
local isfirstperson = (Head.CFrame.Position - Camera.CFrame.Position).Magnitude < 0.65 ;
for _, part in pairs({ViewModel:FindFirstChild("Left Arm"), ViewModel:FindFirstChild("Left Arm"):GetDescendants(), ViewModel:FindFirstChild("Right Arm"), ViewModel:FindFirstChild("Right Arm"):GetDescendants()}) do
if part.ClassName == "BasePart" or part.ClassName == "Part" or part.ClassName == "MeshPart" then
part.Transparency = if isfirstperson then 0 else 1
end
end
local RightGrip = RightArm:FindFirstChild("RightGrip") or ViewModel:WaitForChild("Right Arm"):FindFirstChild("RightGrip")
if(RightGrip) then
RightGrip.Parent = if isfirstperson then ViewModel:WaitForChild("Right Arm") else RightArm
RightGrip.Part0 = if isfirstperson then ViewModel["Right Arm"] else character["Right Arm"]
end
--update shirt and body colors
local ViewModelShirt = ViewModel:FindFirstChildWhichIsA("Shirt") or Instance.new("Shirt", ViewModel) -- // Create a shirt if there is no shirt found.
local CharacterShirt = character:FindFirstChildWhichIsA("Shirt")
if CharacterShirt then
-- // If a shirt was found in the player's character, then set the ViewModel's shirt to the same shirt.
ViewModelShirt.ShirtTemplate = CharacterShirt.ShirtTemplate
end
for _, Part in pairs(ViewModel:GetChildren()) do
if Part:IsA("BasePart") then
-- // Set the color of each part of the ViewModel to the color of the part with same name in the character.
Part.Color = character[Part.Name].Color
end
end
--update animations
for CharAnim, Anim in pairs(LoadedAnimations) do
if CharAnim.IsPlaying ~= Anim.IsPlaying then
if CharAnim.IsPlaying then
Anim:Play()
else
Anim:Stop()
end
end
Anim.TimePosition = CharAnim.TimePosition
Anim:AdjustWeight(CharAnim.WeightCurrent, 0) -- // 0 Fade time so it's instantly set.
end
end
RunService.RenderStepped:Connect(RenderStepped)
If the destroying event doesn’t fire, I wonder if it’s somehow being adjusted to NaN bounds. Sometimes if you don’t account for certain edge cases you can end up w/ unexpected behavior such as this. If disabling the AK script fixed the issue, then there could be an edge case that causes it.
Hey, nothing has been found in the Ak-47 localscript. The problem persists even when disabling the shooting. The code is basically just events that do little work:
local function InputEnded(input, processed)
if input ~= userInput then return end
userInput = nil
if connectionInputEnded and connectionInputEnded.Connected then
connectionInputEnded:Disconnect()
end
connnectionLoopBool = false
end
local function InputBegan(input, processed)
if userInput ~= nil then return end
if processed == true then return end
if input.UserInputType ~= Enum.UserInputType.MouseButton1 then return end
userInput = input
connectionInputEnded = UIS.InputEnded:Connect(InputEnded)
connnectionLoopBool = true
--while connnectionLoopBool do
-- OnShoot()
-- task.wait()
--end
end
tool.Equipped:Connect(function()
AnimationTrack:Play()
AmmoText.Parent.Visible = true
AmmoText.Parent.Interactable = true
if reloading then
AmmoText.Text = "RELOADING"
else
AmmoText.Text = tostring(magazine) .. " | " .. tostring(MAGAZINE_MAX)
end
connectionInputBegan = UIS.InputBegan:Connect(InputBegan)
ContextActionService:BindAction("Reload", Reload, false, Enum.KeyCode.R)
mouse.Icon = "rbxassetid://316279304"
end)
tool.Unequipped:Connect(function()
if connectionInputBegan and connectionInputBegan.Connected then
connectionInputBegan:Disconnect()
end
if connectionInputEnded and connectionInputEnded.Connected then
connectionInputEnded:Disconnect()
end
userInput = nil
connnectionLoopBool = false
AmmoText.Parent.Visible = false
AmmoText.Parent.Interactable = false
AnimationTrack:Stop()
mouse.Icon = defaultMouseIcon
end)
It might have to do with the RightGrip’s property called Part0 being changed, which just breaks the internal code of Roblox. However I am ignorant of how to make the tool welded to the viewmodel instead of the character, in a different way.
It could be tbh… I haven’t really done any FPS games so I probably can’t be of any help in regards to this. If this is a local script, it’s possible the client is making these changes and the server is taking an authoritative step and somehow breaking it. Is your tool completely gone in workspace when this happens?
Creating the viewmodel on the server. It works, however because the viewmodel is in the server it causes allot of issues with animation, as well as how the other players see the player’s arms. So Viewmodel on the server is abandoned.
The solution has been found. Thanks everybody for wanting to help!
The solution is:
create the Viewmodel on the server
create your own weld (not a weldconstraint) that replaces RightGrip but has the same function.
delete the RightGrip on the server every RenderStepped
All animations on the client
Feel free to copy-paste!
Here is the client script:
local RunService = game:GetService("RunService")
local PhysicsService = game:GetService("PhysicsService")
local REP = game:GetService("ReplicatedStorage")
local Camera = game:GetService("Workspace").CurrentCamera
local player = game.Players.LocalPlayer;
local character = script.Parent
local Animator = character:WaitForChild("Humanoid"):WaitForChild("Animator")
local Head = character:WaitForChild("Head")
character:WaitForChild("Left Arm")
local RightArm = character:WaitForChild("Right Arm")
local humrp = character:WaitForChild("HumanoidRootPart")
character:WaitForChild("Torso")
local Animator = character:WaitForChild("Humanoid"):WaitForChild("Animator")
--prepare character for being cloned
character.Archivable = true
local Head = character:WaitForChild("Head")
character:WaitForChild("Left Arm")
local RightArm = character:WaitForChild("Right Arm")
local humrp = character:WaitForChild("HumanoidRootPart")
local torso = character:WaitForChild("Torso")
--create and initialize viewmodel
local ViewModel = character:Clone()
ViewModel.Parent = workspace.Camera
ViewModel.Name = "ViewModel"
character.Archivable = false
local ViewModelAnimator = ViewModel:WaitForChild("Humanoid"):WaitForChild("Animator")
ViewModel.Humanoid.DisplayDistanceType = Enum.HumanoidDisplayDistanceType.None
ViewModel.Humanoid.HealthDisplayType = Enum.HumanoidHealthDisplayType.AlwaysOff
ViewModel.Humanoid.BreakJointsOnDeath = false
ViewModel.PrimaryPart = ViewModel:WaitForChild("Head")
ViewModel.PrimaryPart.Anchored = true
ViewModel:SetPrimaryPartCFrame(CFrame.new(0, 5, 10))
ViewModel:WaitForChild("HumanoidRootPart"):WaitForChild("RootJoint"):Destroy()
for _, v in pairs(ViewModel:GetDescendants()) do
if v:IsDescendantOf(ViewModel:WaitForChild("Head")) then
v:Destroy()
end
if v:IsA("BasePart") or v.ClassName == "MeshPart" then
v.Anchored = false
v.Massless = true
v.CastShadow = false
v:AddTag("ForceCanCollide_OFF")
local lowername = v.Name:lower()
if lowername:match("leg") or lowername:match("foot") then
v:Destroy()
elseif not(lowername:match("arm") or lowername:match("hand")) then
v.Transparency = 1
end
elseif v:IsA("Decal") then
v:Destroy()
elseif v:IsA("Accessory") then
v:Destroy()
elseif v:IsA("LocalScript") then
v:Destroy()
end
end
ViewModel:FindFirstChild("Left Arm").Transparency = 1
ViewModel:FindFirstChild("Left Arm").CastShadow = true
ViewModel:FindFirstChild("Right Arm").Transparency = 1
ViewModel:FindFirstChild("Right Arm").CastShadow = true
local ViewModel_MainArm = ViewModel:FindFirstChild("Right Arm")
local ViewModelAnimator = ViewModel:WaitForChild("Humanoid"):WaitForChild("Animator")
local LoadedAnimations = {}
Animator.AnimationPlayed:Connect(function(AnimationTrack)
--if AnimationTrack.Animation:GetAttribute("ViewModelAnimation") ~= true then return end -- // Skip animation if it isn't supposed to play on ViewModel.
if LoadedAnimations[AnimationTrack] == nil then -- // Indexing using the animation track.
-- // If this animation was not already laoded then load it.
LoadedAnimations[AnimationTrack] = ViewModelAnimator:LoadAnimation(AnimationTrack.Animation) -- // Load animation on the ViewModel.
LoadedAnimations[AnimationTrack].Priority = AnimationTrack.Priority
end
end)
RunService.RenderStepped:Connect(function()
--update viewmodel positionv
local cameradirection = humrp.CFrame:toObjectSpace(Camera.CFrame).LookVector
local cf = CFrame.new(Camera.CFrame.Position) * torso:WaitForChild('Neck').C0 * CFrame.Angles(-math.asin(cameradirection.Y), 0, -math.asin(cameradirection.X))
ViewModel:SetPrimaryPartCFrame(Camera.CFrame)
--update transparency
local isfirstperson = (Head.CFrame.Position - Camera.CFrame.Position).Magnitude < 0.80 ;
for _, part in pairs({ViewModel:WaitForChild("Left Arm"), ViewModel:WaitForChild("Left Arm"):GetDescendants(), ViewModel:WaitForChild("Right Arm"), ViewModel:WaitForChild("Right Arm"):GetDescendants()}) do
if part.ClassName == "BasePart" or part.ClassName == "Part" or part.ClassName == "MeshPart" then
part.Transparency = if isfirstperson then 0 else 1
end
end
if isfirstperson then
for _, part in pairs(character:GetChildren()) do
if part.ClassName == "BasePart" or part.ClassName == "Part" or part.ClassName == "MeshPart" then
part.CastShadow = true
end
end
end
local RightGrip = character:WaitForChild("Right Arm"):FindFirstChild("RightGrip")
if RightGrip and RightGrip.Part1 and RightGrip.Part0 == character:WaitForChild("Right Arm") then
local Grip = character:WaitForChild("Right Arm"):FindFirstChild('Grip')
if Grip == nil then
Grip = Instance.new("Weld")
Grip.Name = 'Grip'
Grip.Parent = character:WaitForChild("Right Arm")
end
Grip.Part1 = RightGrip.Part1
Grip.Part0 = ViewModel_MainArm
Grip.C0 = RightGrip.C0
Grip.C1 = RightGrip.C1
game:GetService("ReplicatedStorage").ServerScripts.Test.RemoteEvent:FireServer()
end
--update shirt and body colors
local ViewModelShirt = ViewModel:FindFirstChildWhichIsA("Shirt") or Instance.new("Shirt", ViewModel) -- // Create a shirt if there is no shirt found.
local CharacterShirt = character:FindFirstChildWhichIsA("Shirt")
if CharacterShirt then
ViewModelShirt.ShirtTemplate = CharacterShirt.ShirtTemplate
end
for _, Part in pairs(ViewModel:GetChildren()) do
if Part:IsA("BasePart") then
Part.Color = character[Part.Name].Color
end
end
--Play loaded animations
for CharAnim, Anim in pairs(LoadedAnimations) do
if CharAnim.IsPlaying ~= Anim.IsPlaying then
if CharAnim.IsPlaying then
Anim:Play()
else
Anim:Stop()
end
end
Anim.TimePosition = CharAnim.TimePosition
Anim:AdjustWeight(CharAnim.WeightCurrent, 0) -- // 0 Fade time so it's instantly set.
end
end)
On the server:
script.RemoteEvent.OnServerEvent:Connect(function(player)
if player == nil or player.Parent == nil then return end
local char = player.character
if char == nil or char.Parent == nil then return end
local RightArm = char["Right Arm"]
if RightArm == nil or RightArm.Parent == nil then return end
local RightGrip = RightArm["RightGrip"]
if RightGrip == nil or RightGrip.Parent == nil then return end
RightGrip.Enabled = false
end)
If you are wondering what the ForceCanCollide_OFF tag does, there is a separate client script called “collisions” with this code inside it:
local RunService = game:GetService("RunService")
local CS = game:GetService("CollectionService")
RunService.PreSimulation:Connect(function()
local Parts = CS:GetTagged("ForceCanCollide_OFF")
for _, part in pairs(Parts) do
if part.ClassName == "Part" or part.ClassName == "MeshPart" then
part.CanCollide = false
end
end
end)