How to play animations on fake arms for FPS games

I am working on a gun system. I found this tutorial on how to make a first person view for the guns and I found it pretty useful.

The system is very interesting. In short, it creates fake arms holding a copy of the actual gun model that make the player feels that he is holding the gun. This “fake arms effect” is pretty popular and used in lot of FPS games.

The code is very complete and it has a nice and smooth aiming, that make the gun center the screen when you aim, even without playing any animation! (this part is important)

It uses a spring module and change values related to CFrame to achieve this effect without animations, positioning the arms and the weapon on specific positions.

I’m trying to make a “safety” mode that will lower the player arms. Like this:
image

Actually, I already managed to do that for the actual character model. I used CFrame rotation for the upper arms, but it could be also an animation.

The problem is: I want to make the same effect on the “fake arms”, so the player will see their arms lowered in first person.

However, for the fake arms, changing the CFrame to rotate them didn’t work.

The fake arm’s gun model is not attached to the RightHand (like the actual gun). Instead, it is attached to the viewModel’s Head using a Motor6D.
image

So, with the gun attached to the Head, rotating the arms have no effect on it.

The code also has a part that keeps updating the arms position on every RenderStepped to keep it smooth for the ilusion in first person.

I will not paste the entire script here because it is a bit long. Instead, you can check on how the system works by editing the Open Source Game provided by the post author on the tutorial previously mentioned.

My question is: how can I achieve this “lowered arms” effect for the fake arms in first person?

Edit: Aditionally, how can I play any type of animations on the fake arms? Like reloading animations also? I changed the post title to best describe what I want to achieve.

Edit 2: Since no one responded, I thought it would be better to put the script here, at least the important part of it.

Create Rig Module: to create the fake arms based on the character.
local valid = {};

valid[Enum.HumanoidRigType.R15] = {
	"LeftHand", "LeftWrist", "LeftLowerArm", "LeftElbow", "LeftUpperArm", "LeftShoulder",
	"RightHand", "RightWrist", "RightLowerArm", "RightElbow", "RightUpperArm", "RightShoulder",
	"UpperTorso", "Head", "Neck", "Humanoid"
}

for key, t in next, valid do
	local d = {};
	for i = 1, #t do
		d[t[i]] = true;
	end
	valid[key] = d;
end

local function createRig(realCharacter)	
	local last = realCharacter.Archivable;
	realCharacter.Archivable = true;
	
	local character = realCharacter:Clone();
	local humanoid = character:WaitForChild("Humanoid");
	
	local lookup = valid[humanoid.RigType];
	for key, _ in next, lookup do
		while (not character:FindFirstChild(key, true)) do
			character.DescendantAdded:Wait();
		end
	end
	
	lookup["Body Colors"] = character:FindFirstChild("Body Colors");
	lookup["Shirt"] = character:FindFirstChild("Shirt");
	
	local des = character:GetDescendants();
	for i = 1, #des do
		if (not lookup[des[i].Name] and not des[i]:IsA("CharacterMesh")) then
			des[i]:Destroy();
		elseif (des[i]:IsA("BasePart")) then
			des[i].CanCollide = false;
			des[i].Anchored = false;
		end
	end
	
	character.Head.Anchored = true;
	character.Head.Transparency = 1;
	
	if (humanoid.RigType == Enum.HumanoidRigType.R15) then
		character.UpperTorso.Transparency = 1;
	else
		-- this can be ignored because my game only uses R15
		character.Torso.Transparency = 1;
	end	

	humanoid.DisplayDistanceType = Enum.HumanoidDisplayDistanceType.None;
	character.Name = "viewModel";
	
	realCharacter.Archivable = last;
	return character;
end

return createRig;
Function that calls the Create Rig Module. Runs on character loaded
function fps.new(character)
	local self = {};

	self.viewModel = createRig(character);	
	self.humanoid = character:WaitForChild("Humanoid");
	self.hrp = character:WaitForChild("HumanoidRootPart");
	
	self.isAiming = false;
	self.isEquipped = false;
	self.isReloading = false;
	self.safetyMode = false;
	self.isRunning = false;

	self.isAlive = self.humanoid.Health > 0;
	self.isR15 = self.humanoid.RigType == Enum.HumanoidRigType.R15;
	self.baseFOV = camera.FieldOfView;
	self.baseWalkSpeed = self.humanoid.WalkSpeed;
	self.character = character
	self.originalRightC0 = character.RightUpperArm.RightShoulder.C0
	self.originalLeftC0 = character.LeftUpperArm.LeftShoulder.C0
	
	self.aimLerp = spring.new(0, 0, 0, 20, 1);
	self.armTilt = spring.new(0, 0, 0, 10, 1);
	self.FOV = spring.new(self.baseFOV, 0, self.baseFOV, 20, 1);
	self.sway = spring.new(Vector3.new(), Vector3.new(), Vector3.new(), 15, 1);
	self.recoil = spring.new(Vector3.new(), Vector3.new(), Vector3.new(), 20, 1);
	self.bobbing = spring.new(Vector3.new(), Vector3.new(), Vector3.new(), 10, 1);
	
	init(self);
	return setmetatable(self, fps_mt);
end

local function init(self)
	self.joint = Instance.new("Motor6D");
	self.joint.Part0 = self.viewModel.Head;
	self.joint.Parent = self.viewModel.Head;
	
	self.viewModelJoint = Instance.new("Motor6D");
	self.viewModelJoint.Part0 = self.viewModel.RightHand;
	self.viewModelJoint.Parent = self.viewModel.RightHand;
	self.viewModelJoint.Name = 'ToolGrip_ViewModel'
	
	if (self.isR15) then
		local lookAround = self.humanoid.Parent:WaitForChild("Animate"):WaitForChild("idle"):FindFirstChild("Animation2");
		if (lookAround) then lookAround:Destroy(); end
	end
	
	self.humanoid.Died:Connect(function()
		self.isAlive = false;
		self:unequip();
	end);
	
	setCollisionGroupRecursive(self.viewModel:GetChildren(), "viewModel");
end
Equip logic. Actually equip the weapon and attach it to the fake arms with the current weapon equipped.
function fps:equip(repWeapon, mouse)
	if (not repWeapon) then 
		self:unequip();
		return;
	end

	local joint = self.character.RightHand:WaitForChild("ToolGrip")
	joint.Part1 = repWeapon:WaitForChild("BodyAttach");
	
	if (not self.isAlive) then return; end
	if (self.isEquipped) then self:unequip(); end
	remotes.setup:FireServer(self.isR15, repWeapon);

	self.actualWeapon = repWeapon;
	self.weapon = repWeapon:Clone();
	self.weapon:WaitForChild("settings"):Destroy();
	self.settings = require(repWeapon:WaitForChild("settings"))(self.isR15);
	
	local side = {"Left", "Right"};
	local children = self.viewModel:GetChildren();
	for i = 1, #side do
		local part = self.weapon:FindFirstChild(side[i]);
		self[side[i]] = part and true;
		for j = 1, #children do
			if (children[j]:IsA("BasePart") and children[j].Name:match(side[i])) then
				children[j].Transparency = part and 0 or 1;
			end
		end
	end
	
	self.joint.C0 = self.settings.CAMERA_OFFSET;
	self.joint.Part1 = self.weapon:WaitForChild("BodyAttach");
	self.joint.Parent = self.viewModel.Head;

	self.aimCFrame = self.settings.CAMERA_OFFSET * repWeapon.BodyAttach.CFrame:inverse() * repWeapon.Aim.CFrame;

	self.settings.holdAnim =  self.humanoid:LoadAnimation(self.settings.holdAnim);
	self.settings.aimAnim = self.humanoid:LoadAnimation(self.settings.aimAnim);
	self.settings.boltAnim = self.humanoid:LoadAnimation(self.settings.boltAnim);

	self.lastAnim = self.settings.holdAnim;
	self.lastAnim:Play();

	self.weapon.Parent = self.viewModel;
	self.viewModel.Parent = camera;
	
	self.originalViewModelLeftC0 = self.viewModel.LeftUpperArm.LeftShoulder.C0
	self.originalViewModelRightC0 = self.viewModel.RightUpperArm.RightShoulder.C0
	
	shiftlock(self, true)
	inputService.MouseIconEnabled = false
	
	self.isEquipped = true;
	updateAmmo(self)
	if weaponGui then
		weaponGui.Enabled = true
	end
end

Again, these are just some parts of the script so you can understand how it works. To read the full script and full logic, please check this open-source game that the author of this tutorial published. Thank you!

1 Like

You can use Moon Animator to make animations. Simply put your viewModel in MoonAnimator and you should be able to create and export animations using keyframes. Here is an FPS framework tutorial; you can scroll down to the part where they mention using Moon Animator: