Want to build viewmodel in an easier way, not sure how to do it in this way, need help!

This might be confusing to some, but bear with me here.
So, I want to build a viewmodel for an FPS. Rather than welding, I want to try a different, easier approach; rather than making fake, client side arms and having to play animations on fake and realistic, I want to make it where I can use the regular arms. I found out that it is fairly simple to do this; I just need to make everything but the arms transparent client-side for when you are fully zoomed in. The only issue is, I don’t know how to make it where it doesn’t make the arms transparent when zoomed in. If it didn’t make anything transparent when zoomed in, I could just script it to, but I don’t know how to do this. I am thinking that I might have to go to the camera script and change something. However, I am not sure if it is possible to disable. In a summarized way, I need help making your body parts not transparent when you become zoomed in.

Just in case you don’t know what I am talking about, notice how there are NO ARMS in this photo DESPITE the fact that arms are clearly being held up to hold the laser gun:

I actually am working on an FPS without using viewmodels.

The code I used is

-- in StarterCharacterScripts
local character = script.Parent

game:GetService("RunService").RenderStepped:Connect(function()
    for _, child in ipairs(character:GetChildren()) do
        if child.Name:match("Arm") or child.Name:match("Hand") -- R6/R15 compatibility, and to match Left/RightHand as well as Lower/UpperArm 's
            child.LocalTransparencyModifier = 0
        end
    end
end)

Updates every render frame.

Remember that LocalTransparencyModifier is just a multiplier to the Transparency of a part. Any number multiplied by 0 is just 0.

Here is that in action!

I added some extra code that makes the arms go up and down since that code alone won’t make it so you look up and down. It is for R6 though, if you want to know how to do that too, or in R15 just let me know in a reply.

1 Like

Tell me!

But is there any simpler way, like just a complete disable of the camera doing that?

Pretty sure just setting the local transparency to 0 is the most simplest way of doing this, having to require the camera module, tweak it, and etc would be more complicated.

Yeah, but doing that every frame could hurt performance!

1 Like

If you’re doing it on the client, no, it won’t hurt performance :man_shrugging:

Plus, I might be wrong but I don’t think you have to do it in a loop? You can test though. You will have to for loop through the character though but thats the only looping you’ll do.

Also, to make the camera go up and down, you can just utilize sine waves.

But is the LocalTransparencyModifier an actual property for parts?

Yes, it is a property you can change for parts. Via script that is.

Wait, but is the LocalTransparencyModifier property how they make the parts transparent? Because if so, I could just go take the script, make a tweak, and have it be the overriding camera script, and save performance.

You could but that will require 1:
If a roblox script updates (which it does sometimes) you’ll have to update it yourself
2:
You’re gonna have to find it and tweak it to make it work correctly
3:
Its very hacky and you’ll have to botch things up.

Forking the camera scripts is a lot less simpler. Now you have to maintain that and update it whenever Roblox does.

For R6 you just mess around with the C0 of the Left/RightShoulders inside their torso.

local left_shoulder = torso:WaitForChild("Left Shoulder")
local right_shoulder = torso:WaitForChild("Right Shoulder")

local left_c0 = left_shoulder.C0
local right_c0 = right_shoulder.C0
local NINETY_DEGREES = CFrame.Angles(0, math.pi/2, 0)
local NEG_NINETY_DEGREES = CFrame.Angles(0, -math.pi/2, 0)

From there you want to get the rotation of the camera and torso, multiply the base C0 by -90 degrees, then get the torso rotation relative to the camera’s, then multiply that by 90 degrees.

local camera_rotation = camera.CFrame - camera.CFrame.Position
local torso_rotation = torso.CFrame - torso.Position
right_shoulder.C0 = right_c0*NEG_NINETY_DEGREES*torso_rotation:ToObjectSpace(camera_rotation)*NINETY_DEGREES
right_shoulder.Transform = CFrame.Angles(0, 0, math.pi/2)

left_shoulder.C0 = left_c0*NINETY_DEGREES*torso_rotation:ToObjectSpace(camera_rotation)*NEG_NINETY_DEGREES
left_shoulder.Transform = CFrame.Angles(0, 0, math.pi/2)

You do basically the same on the left shoulder except you multiply everything else by positive 90 degrees otherwise the arm will be backwards.

Do note that this does not replicate to the server so you will need a remote event. You would be doing this code in the same RenderStepped listener you had for making the arms visible.

To make it replicate to the server you would need to use a remote but firing a remote 60 times a second isn’t very fun for the server. So cutting that down to perhaps 10 times a second, would still look good for the other players.

Btw you would pass the camera’s rotation and base C0’s to the server.

here is some code I used

game:GetService("ReplicatedStorage").Tilt.OnServerEvent:Connect(function(player, left_c0, right_c0, camera_rotation)
	local character = player.Character
	local torso = character.Torso
	
	local left_shoulder = torso["Left Shoulder"]
	local right_shoulder = torso["Right Shoulder"]

	local torso_rotation = torso.CFrame - torso.Position
	right_shoulder.C0 = right_c0*NEG_NINETY_DEGREES*torso_rotation(camera_rotation)*NINETY_DEGREES
	right_shoulder.Transform = CFrame.Angles(0, 0, math.pi/2)
		
	left_shoulder.C0 = left_c0*NINETY_DEGREES*torso_rotation:ToObjectSpace(camera_rotation)*NEG_NINETY_DEGREES
	left_shoulder.Transform = CFrame.Angles(0, 0, math.pi/2)
end)

Then every 0.1 seconds you can fire the remote to the server

while true do
	tilt_remote:FireServer(left_c0, right_c0, camera_rotation)
	wait(0.1)
end
2 Likes

I understand the other two, but “hacky?” how is it really “hacky?”

Here let me give you the jist of basically what you’re gonna have to do:

Locate the script by requiring it from the charcater by playing the game then copying the script. This of course means the original script gets deleted aka the roblox script meaning you’ll not get updates. Then after achieveing the script you’ll have to locate the part with it alters the transparecny via camera zoom by doing alot of CTRL + F. Once you’ve found the spot, alter it so that it still works properly but doesn’t break when you zoom in and only makes arm transparent. Then once you’ve done that you would be good to go :+1:

Though if you really wanted to:

--[[
	TransparencyController - Manages transparency of player character at close camera-to-subject distances
	2018 Camera Update - AllYourBlox
--]]

local MAX_TWEEN_RATE = 2.8 -- per second

local Util = require(script.Parent:WaitForChild("CameraUtils"))

--[[ The Module ]]--
local TransparencyController = {}
TransparencyController.__index = TransparencyController

function TransparencyController.new()
	local self = setmetatable({}, TransparencyController)

	self.lastUpdate = tick()
	self.transparencyDirty = false
	self.enabled = false
	self.lastTransparency = nil

	self.descendantAddedConn, self.descendantRemovingConn = nil, nil
	self.toolDescendantAddedConns = {}
	self.toolDescendantRemovingConns = {}
	self.cachedParts = {}

	return self
end


function TransparencyController:HasToolAncestor(object)
	if object.Parent == nil then return false end
	return object.Parent:IsA('Tool') or self:HasToolAncestor(object.Parent)
end

function TransparencyController:IsValidPartToModify(part)
	if part:IsA('BasePart') or part:IsA('Decal') then
		return not self:HasToolAncestor(part)
	end
	return false
end

function TransparencyController:CachePartsRecursive(object)
	if object then
		if self:IsValidPartToModify(object) then
			self.cachedParts[object] = true
			self.transparencyDirty = true
		end
		for _, child in pairs(object:GetChildren()) do
			self:CachePartsRecursive(child)
		end
	end
end

function TransparencyController:TeardownTransparency()
	for child, _ in pairs(self.cachedParts) do
		child.LocalTransparencyModifier = 0
	end
	self.cachedParts = {}
	self.transparencyDirty = true
	self.lastTransparency = nil

	if self.descendantAddedConn then
		self.descendantAddedConn:disconnect()
		self.descendantAddedConn = nil
	end
	if self.descendantRemovingConn then
		self.descendantRemovingConn:disconnect()
		self.descendantRemovingConn = nil
	end
	for object, conn in pairs(self.toolDescendantAddedConns) do
		conn:Disconnect()
		self.toolDescendantAddedConns[object] = nil
	end
	for object, conn in pairs(self.toolDescendantRemovingConns) do
		conn:Disconnect()
		self.toolDescendantRemovingConns[object] = nil
	end
end

function TransparencyController:SetupTransparency(character)
	self:TeardownTransparency()

	if self.descendantAddedConn then self.descendantAddedConn:disconnect() end
	self.descendantAddedConn = character.DescendantAdded:Connect(function(object)
		-- This is a part we want to invisify
		if self:IsValidPartToModify(object) then
			self.cachedParts[object] = true
			self.transparencyDirty = true
		-- There is now a tool under the character
		elseif object:IsA('Tool') then
			if self.toolDescendantAddedConns[object] then self.toolDescendantAddedConns[object]:Disconnect() end
			self.toolDescendantAddedConns[object] = object.DescendantAdded:Connect(function(toolChild)
				self.cachedParts[toolChild] = nil
				if toolChild:IsA('BasePart') or toolChild:IsA('Decal') then
					-- Reset the transparency
					toolChild.LocalTransparencyModifier = 0
				end
			end)
			if self.toolDescendantRemovingConns[object] then self.toolDescendantRemovingConns[object]:disconnect() end
			self.toolDescendantRemovingConns[object] = object.DescendantRemoving:Connect(function(formerToolChild)
				wait() -- wait for new parent
				if character and formerToolChild and formerToolChild:IsDescendantOf(character) then
					if self:IsValidPartToModify(formerToolChild) then
						self.cachedParts[formerToolChild] = true
						self.transparencyDirty = true
					end
				end
			end)
		end
	end)
	if self.descendantRemovingConn then self.descendantRemovingConn:disconnect() end
	self.descendantRemovingConn = character.DescendantRemoving:connect(function(object)
		if self.cachedParts[object] then
			self.cachedParts[object] = nil
			-- Reset the transparency
			object.LocalTransparencyModifier = 0
		end
	end)
	self:CachePartsRecursive(character)
end


function TransparencyController:Enable(enable)
	if self.enabled ~= enable then
		self.enabled = enable
		self:Update()
	end
end

function TransparencyController:SetSubject(subject)
	local character = nil
	if subject and subject:IsA("Humanoid") then
		character = subject.Parent
	end
	if subject and subject:IsA("VehicleSeat") and subject.Occupant then
		character = subject.Occupant.Parent
	end
	if character then
		self:SetupTransparency(character)
	else
		self:TeardownTransparency()
	end
end

function TransparencyController:Update()
	local instant = false
	local now = tick()
	local currentCamera = workspace.CurrentCamera

	if currentCamera then
		local transparency = 0
		if not self.enabled then
			instant = true
		else
			local distance = (currentCamera.Focus.p - currentCamera.CoordinateFrame.p).magnitude
			transparency = (distance<2) and (1.0-(distance-0.5)/1.5) or 0 --(7 - distance) / 5
			if transparency < 0.5 then
				transparency = 0
			end

			if self.lastTransparency then
				local deltaTransparency = transparency - self.lastTransparency

				-- Don't tween transparency if it is instant or your character was fully invisible last frame
				if not instant and transparency < 1 and self.lastTransparency < 0.95 then
					local maxDelta = MAX_TWEEN_RATE * (now - self.lastUpdate)
					deltaTransparency = math.clamp(deltaTransparency, -maxDelta, maxDelta)
				end
				transparency = self.lastTransparency + deltaTransparency
			else
				self.transparencyDirty = true
			end

			transparency = math.clamp(Util.Round(transparency, 2), 0, 1)
		end

		if self.transparencyDirty or self.lastTransparency ~= transparency then
			for child, _ in pairs(self.cachedParts) do
				child.LocalTransparencyModifier = transparency
			end
			self.transparencyDirty = false
			self.lastTransparency = transparency
		end
	end
	self.lastUpdate = now
end

return TransparencyController

Also lets not mention the other scripts :ok_hand:

Hacky methods use tricks and inelegant solutions to solve problems.

Forking the camera scripts will only be more of a pain for you. All I am doing is updating the transparency every render frame – doesn’t hurt performance at all. It hurt even less because this is in a localscript.

1 Like

Ok, well thanks for the advice!

And you too 30 CHAAAAAARRRRSSSSSS

1 Like

If you are that concerned about performance, cache the arms to avoid doing string manipulation each frame.

-- in StarterCharacterScripts
local character = script.Parent
local armsegments = {}
for _, child in ipairs(character:GetChildren()) do
    if child.Name:match("Arm") or child.Name:match("Hand") -- R6/R15 compatibility, and to match Left/RightHand as well as Lower/UpperArm 's
        armsegments[#armsegments+1] = child
    end
end

game:GetService("RunService").RenderStepped:Connect(function()
    for _, child in ipairs(armsegments ) do
        child.LocalTransparencyModifier = 0
    end
end)
1 Like

Something as simple as this modifying a couple of 2x1x4 bricks per frame is negligible to performance. You can’t get anything better than what’s posted here, in my opinion.