IMPORTANT NOTE
I’ve been generally inactive on roblox and in development recently. I’ve stopped supporting this tutorial further, and any questions will probably not be answered. I recommend seeing a better tutorial such as this one by EgoMoose.
Introduction
I’ve previously made this tutorial about making FPS guns using ViewModels but the tutorial was not well made since it was my first one. I’ve decided to rewrite the whole tutorial in multiple parts this time (On this same topic) with this first part being about creating the ViewModel and playing animations on it only.
This ViewModel works for both R6 and R15 and it doesn’t require you to manually play animations on each of the player’s character and the ViewModel, instead it will copy animations from the player’s character and play them on the ViewModel if these animations had an attribute called ViewModelAnimation
set to true (To prevent playing emote animations and walk animations in first person).
Please note that the animations will not work if they are loaded onto Humanoid instead of Humanoid.Animator, Please use Animator for all your animations!
I’m sorry for the huge delay of this tutorial, I’ll try my best to complete the full tutorial this time!
Also please note that the tutorial is not fully finished yet, it will be consisting of 3 parts, ViewModel, Aim down sights and finally reload animations.
Expected Final Result
Part 1: The ViewModel
The ViewModel is the fake pair of arms that appear on your screen in FPS games, the way we will be creating our ViewModel is by cloning the player’s character and deleting unnecessary parts (Legs, Face and Accessories).
Let’s start by creating a script called ViewModel
in StarterCharacterScripts.
Now we have to load some Services we will be using, RunService and PhysicsService.
local RunService = game:GetService("RunService")
local PhysicsService = game:GetService("PhysicsService")
I’ll explain later why we would need them.
Since the ViewModel should always be placed where the camera is, we will need the player’s camera too, We will also be using it as a parent for the ViewModel.
local Camera = workspace.CurrentCamera
Everytime a player character is loaded, this script is inserted into the character, so it will keep creating new ViewModels everytime and we have to delete old ViewModels.
local Camera = workspace.CurrentCamera
if Camera:FindFirstChild("ViewModel") then
Camera.ViewModel:Destroy()
end
Let’s create a variable for the character and another for it’s Animator.
local Character = script.Parent
local Animator = Character:WaitForChild("Humanoid"):WaitForChild("Animator") -- // We need the animator to replicate animations to the ViewModel.
Now the part where we actually create the ViewModel, we clone the player’s character.
local ViewModel = Character:Clone()
ViewModel.Parent = Camera
ViewModel.Name = "ViewModel"
The code above will not work since the Character’s Archivable property is set to false, meaning we cannot clone it, so we have to set it to true first.
Character.Archivable = true
local ViewModel = Character:Clone()
ViewModel.Parent = Camera
ViewModel.Name = "ViewModel"
Character.Archivable = false
Let’s do some configuration…
local ViewModelAnimator = ViewModel.Humanoid.Animator -- // Used to play animations on the ViewModel.
ViewModel.Humanoid.DisplayDistanceType = Enum.HumanoidDisplayDistanceType.None -- // Disable name display.
ViewModel.Humanoid.HealthDisplayType = Enum.HumanoidHealthDisplayType.AlwaysOff -- // Disable health display.
ViewModel.Humanoid.BreakJointsOnDeath = false
We will place the ViewModel in air for now until we get to the part where we place it at the camera.
ViewModel.Head.Anchored = true
ViewModel.PrimaryPart = ViewModel.Head
ViewModel:SetPrimaryPartCFrame(CFrame.new(0, 5, 10))
Perfect!
We want to delete unnecessary parts, so we will go through all descendants of the ViewModel and change them a little bit.
for _, Part in pairs(ViewModel:GetDescendants()) do
if Part:IsA("BasePart") then
Part.CastShadow = false -- // If it's a part, then disable it's shadow.
local LowerName = Part.Name:lower() -- // Get the lowercase part name
if LowerName:match("leg") or LowerName:match("foot") then
Part:Destroy() -- // If this is a leg/foot part then delete it, since it's useless.
elseif not (LowerName:match("arm") or LowerName:match("hand")) then
Part.Transparency = 1 -- // If this part isn't a part of the arms then it should be invisible.
end
elseif Part:IsA("Decal") then
Part:Destroy() -- // Delete all decals (Face).
elseif Part:IsA("Accessory") then
Part:Destroy() -- // Delete all accessories.
elseif Part:IsA("LocalScript") then
Part:Destroy() -- // Destroy all scripts.
end
end
Now it’s working perfectly, except we still collide with the ViewModel, now that’s where we start using PhysicsService.
Through studio’s Model
tab, click on Collision Groups to open a window where we can configure our collision groups.
Now create a new collision group called ViewModel
and configure it like this
Back to our script, in the same loop that deletes unnecessary parts, we add this line
PhysicsService:SetPartCollisionGroup(Part, "ViewModel")
The full loop should look like this now
for _, Part in pairs(ViewModel:GetDescendants()) do
if Part:IsA("BasePart") then
Part.CastShadow = false -- // If it's a part, then disable it's shadow.
PhysicsService:SetPartCollisionGroup(Part, "ViewModel")
local LowerName = Part.Name:lower() -- // Get the lowercase part name
if LowerName:match("leg") or LowerName:match("foot") then
Part:Destroy() -- // If this is a leg/foot part then delete it, since it's useless.
elseif not (LowerName:match("arm") or LowerName:match("hand")) then
Part.Transparency = 1 -- // If this part isn't a part of the arms then it should be invisible.
end
elseif Part:IsA("Decal") then
Part:Destroy() -- // Delete all decals (Face).
elseif Part:IsA("Accessory") then
Part:Destroy() -- // Delete all accessories.
elseif Part:IsA("LocalScript") then
Part:Destroy() -- // Destroy all scripts.
end
end
NOTE: Please make sure all parts of your tools are CanCollide off and Massless on since if they weren’t they can cause problems such as flinging the player.
Animations
Now we need to start playing animations on our ViewModel, how we will be doing it is by checking current animations running on the player’s character, if they have the Attribute ViewModelAnimation
set to true then the animation will play on the ViewModel.
Now first we will create a simple tool to play animations on the character.
local Player = game.Players.LocalPlayer
local Char = Player.Character or Player.CharacterAdded:Wait()
if not Char.Parent then Char = Player.CharacterAdded:Wait() end -- // Player.Character can be the old character sometimes.
local Animator = Char:WaitForChild("Humanoid"):WaitForChild("Animator")
local Animation = Instance.new("Animation", script.Parent)
Animation.AnimationId = "rbxassetid://YOUR_ANIMATION_ID" -- // Please note that you HAVE TO change this ID to your own animation ID since this animation was made by me and will only work if you are playing in my own place.
Animation = Animator:LoadAnimation(Animation)
Animation:SetAttribute("ViewModelAnimation", true)
script.Parent.Equipped:Connect(function()
Animation:Play()
end)
script.Parent.Unequipped:Connect(function()
Animation:Stop()
end)
When we test this out we should have this
We will watch for every AnimationTrack played on the character using Animator.AnimationPlayed.
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 not LoadedAnimations[AnimationTrack] 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.
end
end)
We will also be manually playing the animation by setting it’s TimePosition every frame, that’s where we will start using RunService.
local function updateAnimations()
end
RunService.RenderStepped:Connect(function(dl)
updateAnimations()
end)
We have to replicate 3 things from the character animations, if the animation is playing, the time position the animation is at and the Weight of it.
local function updateAnimations()
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
If we playtest we should have this result
If it doesn’t look like this for you, review your code and make sure that you have used your own animation ID in the test tool since my own animations will not work in your place.
Welding the tool to the ViewModel
We can finally weld the tool to the ViewModel now, how we will be doing it is whenever a tool is inserted into the character, roblox creates a weld called RightGrip
which connects the player’s arm to the Handle of the tool, we will be changing the Part0 of that weld to be the arm in the ViewModel instead of the arm in the character.
Now since roblox seems to break the tool if we change Part0 right once it’s added to the character, we will have to use a wait()
before setting it, OR we will have to code our own tool system, including welding the tools, backpack UI and etc, since that is not our main focus in this tutorial I will only be using the first method.
Character.DescendantAdded:Connect(function(Obj)
if Obj:IsA("Weld") and Obj.Name == "RightGrip" then
wait()
Obj.Part0 = ViewModel[Obj.Part0.Name]
end
end)
Now the tool welds to the ViewModel, perfect!
Let’s place the ViewModel at the camera now.
RunService.RenderStepped:Connect(function(dl)
updateAnimations()
ViewModel:SetPrimaryPartCFrame(Camera.CFrame)
end)
The ViewModel should be placed where it should be now!
But one more thing before finishing the tutorial, your game might be changing the shirt of the character or it’s BodyColors, so we have to replicate that to the ViewModel too.
RunService.RenderStepped:Connect(function(dl)
updateAnimations()
ViewModel:SetPrimaryPartCFrame(Camera.CFrame)
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
end)
It’s done! Your ViewModel should be perfectly working now, if you have any questions you can send them in the replies, and if you need the uncopylocked place then here is the link.
Next part will be focused around making working Aim Down Sights (ADS for short) and adding recoil effects!