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:
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.
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!