FPS using ViewModels (The improved version) // Parts: 2 out of 3

Great tutorial! I need to ask, will this work if the ViewModel is R6 and the character is R15?

2 Likes

Nice tutorial. This tutorial is really helpful.

1 Like

You could have used table indexing instead of individual if/elseif statements for this or instead just cloning the arms without the rest of the body.

1 Like

The tutorial was mainly directed towards beginners, I know there are more efficient ways to do this, but it will be making the code more complicated. Plus this part only runs once when the script starts so you don’t have to worry about it.

I don’t just clone the arms since I’m playing the character’s animations, meaning I require the torso, and the head (I will use the head movement in the next part). Also this code is supposed to work with both R6 and R15 without the need to change anything in it, you just change the game’s avatar type and your tool animations.

You don’t always need to make your code as small as possible, it will just make your code more complicated and give you some problems later, I have tried doing that before, making stuff like these in tables, now I have to rewrite that whole code and use less tables.

1 Like

If you want that you would have to put a pre-made R6 dummy in, let’s say ReplicatedStorage, and when the script loads you make it clone that dummy instead of the player’s actual character.

This will break the body colors part though, since part names are not the same and you will have to insert their names manually.
You will also not be able to play R15 animations on an R6 rig, you can change the animation part of the code so you have to play R6 animations from your tool code but it will need more animation work.

2 Likes

Part 2: ADS & Recoil (Physically Based)

Hello there! This is part 2 of this tutorial which will be focused on Aim Down Sights and Physically Based Recoil using Springs!

Final result

We will need some stuff from previous tutorial to start on this one, the previous gun script and some changes to the gun model.

Original gun script (Only plays animation)

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://6283727388" -- // 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)

Now in the gun we need to modify 2 things:
1. Make the handle part look forward.
2. Add an AimPart that should also look forward (Where the camera will be placed).

To make the handle part look forward, while keeping the mesh’s direction, we have to use 2 parts, one is an invisible part for the Handle and another is the gun mesh which will be welded to the handle.
We will be using this plugin to weld parts by selecting the first part then holding CTRL and selecting the second part then clicking on this button.

image

Make sure all parts of the gun are Massless and CanCollide off and only the Handle part is anchored!
For the AimPart you can create a new invisible part in the gun and name it AimPart then adjusting it’s position and then welding it, I made a plugin that can help you set the position of the AimPart by letting you place your camera where it is and preview it before testing.

Scripting the ADS

To script the ADS, we will need 2 things, RunService and SpringModule.
To install the spring module, create a ModuleScript in ReplicatedStorage, then copy & paste that script in it.

image

We can now require the module in our script like this

local SpringModule = require(game.ReplicatedStorage.SpringModule)

We also need RunService so you should add this at the start of the gun script.

local SpringModule = require(game.ReplicatedStorage.SpringModule)
local RunService = game:GetService("RunService")

After we installed the spring module, we can start scripting the ADS, we will first need to add an Offset value for the ViewModel which will be applied to the ViewModel after placing it at the camera.
In the ViewModel script add these lines:

local Offset = Instance.new("CFrameValue", script)
Offset.Name = "Offset"

And in the ViewModel’s RunService RenderStepped event change this

ViewModel:SetPrimaryPartCFrame(Camera.CFrame)

To

ViewModel:SetPrimaryPartCFrame(Camera.CFrame:ToWorldSpace(Offset.Value))

Try running the game and setting Offset.Value to CFrame.new(0, 0, 1) to see if it works. (You can use command bar at the buttom for that.)

We will need the player’s mouse to detect right mouse button events.

local Player = game.Players.LocalPlayer
local Mouse = Player:GetMouse()

And

local M2Held = false
Mouse.Button2Down:Connect(function()
	M2Held = true
end)
Mouse.Button2Up:Connect(function()
	M2Held = false
end)

Now to make working ADS, we need the ViewModel and the Offset from before

local Camera = workspace.CurrentCamera
local ViewModel = Camera:WaitForChild("ViewModel")
local Offset = Char:WaitForChild("ViewModel"):WaitForChild("Offset")

local Handle = script.Parent:WaitForChild("Handle")
local AimPart = script.Parent:WaitForChild("AimPart")

And then we can start working on the actual ADS in a RunService.RenderStepped event.

RunService.RenderStepped:Connect(function()
	-- ADS & Recoil code will be here.
end)

Since our ViewModel is a clone of the character not an arm model, we need to set the CFrame of that ViewModel and not the gun, so we will do some math to put the ViewModel in the correct position.

In the RenderStepped event add

local HandleTransform = ViewModel:GetPrimaryPartCFrame():ToObjectSpace(Handle.CFrame)
local OriginalTransform = HandleTransform * HandleTransform:Inverse()
local AimTransform = AimPart.CFrame:ToObjectSpace(Handle.CFrame) * HandleTransform:Inverse()

How this will work is:

  1. Get the transform of the Handle part to the ViewModel’s primary part (Which is used to set it’s position).
  2. Apply the negative transform to the original transform for the hipfire offset which will result in a neutral CFrame (This will be used later to apply recoil to original transformation).
  3. Apply the offset of the AimPart from Handle to the transform, so that AimPart is always positioned where the camera is.

Now we can see if it works by adding these lines after the previous lines (Also in the RenderStepped event).

if M2Held then
	Offset.Value = AimTransform
else
	Offset.Value = OriginalTransform
end

We should have this now

We can use Springs from the SpringModule to make this ADS smooth and realistic, we first need to create a spring before the RenderStepped event.

local AimSpring = SpringModule.new(0)
AimSpring.Damper = 1
AimSpring.Speed = 16

We will be using it to lerp between the OriginalTransform and AimTransform, so whenever the spring’s position is 0 it will be at original transform and whenever it’s at 1 it will be at aim transform (0.5 is halfway, etc).
To do this we will use CFrame:Lerp(), it works like this CFrame1:Lerp(CFrame2, Position)

Offset.Value = OriginalTransform:Lerp(AimTransform, AimSpring.Position)

Make sure you delete this part!

if M2Held then
	Offset.Value = AimTransform
else
	Offset.Value = OriginalTransform
end

Now we need to set the Spring’s target so it moves, at the start of the RenderStepped event add these lines.

if M2Held then
	AimSpring.Target = 1
else
	AimSpring.Target = 0
end

Spring.Position will always keep gradually changing towards Spring.Target so if we test this code we should have this.

We don’t want the gun to change the offset while it’s unequipped and interfere with other guns in inventory so add this line at the start of the RenderStepped event to end the function if the tool isn’t equipped.

if (script.Parent.Parent ~= Char) then
	return 
end

If you want to reset the transform the the hipfire position when the gun is unequipped add this instead.

if (script.Parent.Parent ~= Char) then
	AimSpring.Position = 0
	return
end

We should also reset the Offset when the tool is unequipped, add this in the script.Parent.Unequipped event.

Offset.Value = CFrame.new()

Adding Recoil

Now that we are finally done with the ADS, we can start with recoil.
We will create a new spring for the recoil.

local Recoil = SpringModule.new(Vector3.new())
Recoil.Speed = 16

Mouse.Button1Down:Connect(function()
	if (script.Parent.Parent ~= Char) then return end
	Recoil.Velocity = Vector3.new(0, 0, 12)
end)

This script should give a velocity to the recoil spring whenever the player left clicks with the tool equipped.
We need to apply the recoil to both transforms.

local HandleTransform = ViewModel:GetPrimaryPartCFrame():ToObjectSpace(Handle.CFrame)
local OriginalTransform = HandleTransform * CFrame.new(Recoil.Position) * HandleTransform:Inverse()
local AimTransform = AimPart.CFrame:ToObjectSpace(Handle.CFrame) * CFrame.new(Recoil.Position) * HandleTransform:Inverse()

This should be the result.


We can create another spring for camera recoil too, but it will be more advanced to adjust it’s rotation.

local Recoil = SpringModule.new(Vector3.new())
Recoil.Speed = 16

local CamRecoil = SpringModule.new(0)
CamRecoil.Speed = 15
CamRecoil.Damper = 0.8

Mouse.Button1Down:Connect(function()
	if (script.Parent.Parent ~= Char) then return end
	Recoil.Velocity = Vector3.new(0, 0, 12)
	CamRecoil.Velocity = 150 * 5
end)

To apply it we will need to substract the last applied rotation from the current rotation before applying it to camera, since we also want to camera to go a little higher after the recoil is over we will let 2% of the recoil be applied permanently.

local CamTransform = CFrame.fromEulerAnglesXYZ(math.rad(CamRecoil.Position), 0, 0)
Camera.CFrame = Camera.CFrame * (CamTransform * PreviousCamTransform:Lerp(CFrame.new(), 0.02):Inverse())
PreviousCamTransform = CamTransform

These lines should be inside the RenderStepped event.
Make sure to add this line before the event aswell.

local PreviousCamTransform = CFrame.new()

Here is the uncopylocked place incase you need it!

21 Likes

Really good tutorial, what would I do if I wanted to use a custom viewmodel?

If you want to use custom arms that means you would also use custom animations, you can have a model of that ViewModel in ReplicatedStorage and whenever the script loads it will clone that model instead of the player’s character, you would also have to make your script play animations on both the player’s character and the ViewModel because the automatic animation replication in the current script won’t work since they are different models, if you want to use R6 ViewModels on R15 players though (Like arsenal does) you can also have an arms model in ReplicatedStorage, but instead of setting the CFrame of the ViewModel in your script you set the CFrame of the gun and place the arms in their grip positions.

1 Like

Appreciate you making this awesome tutorial! Was just wondering when we can expect part 3?

Honestly I thought the tutorial has died for a while but since it’s coming back up I’ll probably start working on part 3.

Very cool, will look forward to part 3!

Is part 3 still in the works, when can we expect it?

1 Like

The tutorial is really nice! I really like how concise it was. It was really easy for me to understand it! Nice Job!

Sorry I’ve just been inactive on the devforum and after thinking about it, the tutorial is already not that good since the ViewModels don’t look quite good and have bad coding, plus Aim down sights will even make it worse.

At this point you should’ve understood the basics of a viewmodel and all I can suggest is looking for a more advanced tutorial, I suggest learning about springs, humanoids, animations, etc…

I don’t think I’ll ever make part 3.

You could also use the HumanoidDescription | Roblox Creator Documentation for body colors

Hi there, thanks for the tutorial. Just need a bit of help though, the actual characters arms won’t be transparent(not the viewmodels arms) when equipping the tool. It shows both the actual arms and the view models arms.

put a local script in startergui or smthn and add this code

local player = game.Players.LocalPlayer
local char = player.Character
local RunService = game:GetService("RunService")

char:WaitForChild("Humanoid").CameraOffset = Vector3.new(0, -.2, -1.1)

for i, v in pairs(char:GetChildren()) do
	if v:IsA("BasePart") and v.Name ~= "Head" and v.Name ~= "Left Arm" and v.Name ~= "Right Arm" then

		v:GetPropertyChangedSignal("LocalTransparencyModifier"):Connect(function()
			v.LocalTransparencyModifier = v.Transparency
		end)

		v.LocalTransparencyModifier = v.Transparency

	end
end

RunService.RenderStepped:Connect(function(step)
	local ray = Ray.new(char.Head.Position, ((char.Head.CFrame + char.Head.CFrame.LookVector * 2) - char.Head.Position).Position.Unit)
	local ignoreList = char:GetChildren()

	local hit, pos = game.Workspace:FindPartOnRayWithIgnoreList(ray, ignoreList)

	if hit then
		char:WaitForChild("Humanoid").CameraOffset = Vector3.new(0, 0, -(char.Head.Position - pos).magnitude)
	else
		char:WaitForChild("Humanoid").CameraOffset = Vector3.new(0, -.2, -1.1)
	end
end)

Thanks. I found out that it was a viewable arms local script that I had inputed earlier. Thank you though for the kind script. I although am facing another issue, where the tool with the ViewModel will be randomly removed. I can’t seem to find the problem, but shortly after equipping it will despawn.

if you have meshes or somethings like that, add weld constraints to the handle and link it to every single part on the tool and turn off collisions and anchors

I know you dont awnser questions anymore but for anyone else in the Comments the ViewModel isnt showing up for me whenever I Clone it. I even tried changing from script.Parent to the LocalPlayers Character and nothing