Unexplainable wiggling/jiggling/shaking for my custom VR characters

Short description of the issue: The first players first to join the game everything has always worked works as intended. As new players join, an unexplainable “shaking” occurs in their character.

Videos:
Intended behavior:
e3e23f52baa245492ddf01ae1567d705
Unintended behavior:
8cacf432d10f57d3e785821a7713a817

MY SETUP

  • The Character Model
    When a player joins, the client will verify if they are in VR mode. If so, the server will delete everything in their character. A folder named “VRFolder” is created and parented inside their Character model. I set up a ChildAdded event to delete anything new added that isn’t the VR folder. This is nice, because when the player respawns or leaves, this is automatically cleaned up for me.
    image

  • The Base
    To make each VR character work, I have a “Base” model which is just three unanchored 1x1x1 blocks that represent the position of the headset, right controller, and left controller. They are all unanchored, cancollide is true, and massless is false.
    image

  • The Head
    I have a few different head models that are all set up in the same exact way. They are all unanchored parts that are welded (with a weldconstraint) to the center head part. The “eyes” are the only parts that have cancollide false.
    image

  • The Hands
    I have two different sets of hands: small and large. The small hands are set up in the exact same way the large ones are, just half the size. The “wrist” is welded (with a weldconstraint) to the palm, and each finger is its own model and connected with Motor6D’s, which allows for finger movement.
    image

  • Putting it all together
    When the Player is ready to create their character, the base is cloned, a head is selected and cloned, and a set of hands is cloned. The head and hands are parented to the Base, and the Base is parented to the VR folder. Attachments are created for the HeadCF and Head (the part inside the model) and then an AlignPosition and AlignOrientation are created and parented to the HeadCF. The hands are also set up in the same manner, using the (Right/Left)HandCF and the Palm in the respective hand model. Details of the AlignPosition and AlignOrientation are attached:

Finally, I loop through every part of the Base model (and its descendants, so that includes the hands/head) and set the network ownership of the parts to the Player using SetNetworkOwner. This gives the client full control over the character, and allows me to update the CFrame of the HeadCF/RightHandCF/LeftHandCF on the client and since the AlignPosition and AlignOrientations are set up, when the CFrame is updated all the parts move.

Solutions I have tried:
I have tried creating the hands solely on the server and making the client request to update the positions and while a lot less common, this bug still occurs- and the latency makes it horrifying to play with.

Note: I am using Knit.

Server Code
function Service.Client:JoinTeam(Player: Player, TeamName: string)
	local VRFolder = Player.Character:FindFirstChild("VRFolder")
	if not VRFolder and not VRFolder:FindFirstChild("Base") or typeof(TeamName) ~= "string" then return end
	
	local Team = Teams:FindFirstChild(TeamName)
	if not Team then return end
	
	Player.Team = Team
	
	local Base = VRAssets.Base:Clone()
	local HeadScale
	local Head
	
	if Team.Name == "Ascended" then
		if Player.UserId == game.CreatorId then HeadScale = 8 Head = VRAssets.Heads.VRBrickGod:Clone() else HeadScale = 4 Head = VRAssets.Heads.VRGoodDemiGod:Clone() end
	else
		if Player.UserId == 114316837 then HeadScale = 8 Head = VRAssets.Heads.VREvilBrickGod:Clone() else HeadScale = 4 Head = VRAssets.Heads.VREvilDemiGod:Clone() end
	end
	
	local RightHand = HeadScale == 8 and VRAssets.Hands.LargeRH:Clone() or VRAssets.Hands.SmallRH:Clone()
	local LeftHand = HeadScale == 8 and VRAssets.Hands.LargeLH:Clone() or VRAssets.Hands.SmallLH:Clone()
	
	local function CreateAttachment(Part: Part)
		local NewAttachment = Instance.new("Attachment")
		NewAttachment.Parent = Part
		return NewAttachment
	end

	local function CreateAlignPosition(Part: Part, Attachment0Part: Part, Attachment1Part: Part)
		local AlignPosition = Instance.new("AlignPosition")
		AlignPosition.MaxForce = 100000
		AlignPosition.MaxVelocity = math.huge
		AlignPosition.Responsiveness = 100
		AlignPosition.Attachment0 = Attachment0Part
		AlignPosition.Attachment1 = Attachment1Part
		AlignPosition.Parent = Part
	end

	local function CreateAlignOrientation(Part: Part, Attachment0Part: Part, Attachment1Part: Part)
		local AlignOrientation = Instance.new("AlignOrientation")
		AlignOrientation.MaxAngularVelocity = math.huge
		AlignOrientation.MaxTorque = 100000
		AlignOrientation.Responsiveness = 50
		AlignOrientation.Attachment0 = Attachment0Part
		AlignOrientation.Attachment1 = Attachment1Part
		AlignOrientation.Parent = Part
	end

	local function SetupAlignments(CFPart: Part, PrimaryPart: Part)
		local CFAttachment = CreateAttachment(CFPart)
		local PrimaryAttachment = CreateAttachment(PrimaryPart)

		CreateAlignPosition(PrimaryPart, PrimaryAttachment, CFAttachment)
		CreateAlignOrientation(PrimaryPart, PrimaryAttachment, CFAttachment)
	end

	local function SetCollisionGroup(Model: Model)
		for _, Part in ipairs(Model:GetDescendants()) do
			if not Part:IsA("BasePart") then continue end
			PhysicsService:SetPartCollisionGroup(Part, 'VRNonCollide')
		end
	end
	
	local function Blink()
		local Eyes = {}
		
		local function TweenEye(Eye, NewSize)
			local Tween = TweenService:Create(Eye, TweenInfo.new(0.2), {Size = NewSize})
			Tween:Play()
			Tween = nil
		end
		
		local function Break()
			local VRFolder = Player.Character and Player.Character:FindFirstChild("VRFolder")
			if not VRFolder and not VRFolder:FindFirstChild("Base") then return true else return false end
		end
		
		for _, Eye in ipairs(Head:GetChildren()) do
			if not Eye.Name:find("Eye") then continue end
			Eyes[Eye] = Eye.Size
		end
		
		task.spawn(function()
			while true do
				if Break() then break end
				
				task.wait(math.random(2, 4))
				for Eye, OriginalSize in pairs(Eyes) do
					TweenEye(Eye, Vector3.new(OriginalSize.X, OriginalSize.Y/2, OriginalSize.Z))
					delay(0.2, function()
						if Break() then return end
						TweenEye(Eye, OriginalSize)
					end)
				end
			end
		end)
	end
	
	local function SetOwnership(Model: Model)
		for _, Part in ipairs(Model:GetDescendants()) do
			if not Part:IsA("Part") or Part.Anchored then continue end
			Part:SetNetworkOwner(Player)
		end
	end
	
	local function SetNameGui(NameGui)
		NameGui.Frame.DisplayName.Text = Player.DisplayName
		NameGui.Frame.Username.Text = "("..Player.Name..")"
	end

	SetupAlignments(Base.RightHandCF, RightHand.Palm)
	SetupAlignments(Base.LeftHandCF, LeftHand.Palm)
	SetupAlignments(Base.HeadCF, Head.Head)
	
	local NameGui = Head.Head:FindFirstChild("NameGui")
	if NameGui then SetNameGui(NameGui) end
	
	RightHand.Name = "RightHand"
	LeftHand.Name = "LeftHand"
	
	Head.Parent = Base
	RightHand.Parent = Base
	LeftHand.Parent = Base
	Base.Parent = VRFolder
	
	task.wait()
	SetCollisionGroup(VRFolder)
	SetOwnership(VRFolder)
	
	Blink()
	
	self.SetupCharacter:Fire(Player, HeadScale, Base) -- This fires to the client to setup client events
end```
Client Code
function Controller:SetupControls(HeadScale: number, Base: Model)
	local function GetDirection(MoveX: number, MoveY: number)
		local DirectionX = math.abs(MoveX)
		local DirectionY = math.abs(MoveY)
		
		if DirectionX > DirectionY then return "X" else return "Y" end
	end
	
	local function MoveFingers(Pressure: number, FingerCode: StringValue)
		local Hand = (FingerCode == "R1" or FingerCode == "R2" or FingerCode == "A") and Base.RightHand or Base.LeftHand
		local FingerGroup = ((FingerCode == "R1" or FingerCode == "L1") and "Grip") or ((FingerCode == "R2" or FingerCode == "L2") and "Trigger") or "Thumb"
		
		
		local function UpdateFingers()
			local function SetFingerM6Ds(FingerName)
				for _, M6D in ipairs(Hand:GetDescendants()) do
					if not M6D:IsA("Motor6D") or (M6D.Name ~= FingerName and M6D.Parent.Parent.Name ~= FingerName) then continue end

					M6D.DesiredAngle = (70 * Pressure) * math.pi/180
				end
			end

			if FingerGroup == "Thumb" then
				SetFingerM6Ds("Thumb")
			elseif FingerGroup == "Trigger" then
				SetFingerM6Ds("Index")
			else
				SetFingerM6Ds("MiddleFinger")
				SetFingerM6Ds("Ring")
				SetFingerM6Ds("Pinky")
			end
		end
		
		UpdateFingers()
	end
	
	local function LeftThumbstick(MoveX: number, MoveY: number, Direction: string)
		-- Direction Y moves forward/back while Direction X moves to the left/right
		local MoveDirection = Direction == "Y" and MoveY or MoveX
		local NewVector = Direction == "Y" and Base.HeadCF.CFrame.LookVector or Base.HeadCF.CFrame.RightVector
		NewVector = Vector3.new(NewVector.X, 0, NewVector.Z)
		
		Camera.CFrame = Camera.CFrame + NewVector * MoveDirection
	end
	
	local function RightThumbstick(MoveX: number, MoveY: number, Direction: string)
		-- Direction Y moves up/down while Direction X turns left/right
		if Direction == "Y" then Camera.CFrame = Camera.CFrame * CFrame.new(0, MoveY, 0) else Camera.CFrame = Camera.CFrame * CFrame.fromEulerAnglesXYZ(0, -MoveX * 0.02, 0) end
	end
	
	local function ThumbstickMoved(Input: InputObject, ThumbstickCode: string)
		local MoveY = Input.Position.Y
		local MoveX = Input.Position.X
		local Direction = GetDirection(MoveX, MoveY)

		if ThumbstickCode == "T1" then LeftThumbstick(MoveX, MoveY, Direction) else RightThumbstick(MoveX, MoveY, Direction) end
	end
	
	Camera.HeadScale = HeadScale
	
	Base.HeadCF.Anchored = true
	Base.RightHandCF.Anchored = true
	Base.LeftHandCF.Anchored = true
	
	for _, Object in ipairs(Base:GetDescendants()) do
		if Object:IsA("BasePart") and Object.Parent:FindFirstChild("Head") then Object.Transparency = 1 end
		if Object:IsA("ParticleEmitter") or Object:IsA("BillboardGui") then Object.Enabled = false end
	end
	
	Camera.HeadScale = HeadScale
	if Player.Team.Name == "Ascended" then Camera.CFrame = workspace.GoodGodSpawn.CFrame else Camera.CFrame = workspace.EvilGodSpawn.CFrame end
	VRService:RecenterUserHeadCFrame()
	
	UserInputService.UserCFrameChanged:Connect(function(BodyPart: UserCFrame, NewCF: CFrame)
		local Part = (BodyPart == Enum.UserCFrame.Head and Base.HeadCF) or (BodyPart == Enum.UserCFrame.RightHand and Base.RightHandCF) or (BodyPart == Enum.UserCFrame.LeftHand and Base.LeftHandCF) or nil
		if not Part then return end
		
		local ScaledCF = Camera.CFrame * CFrame.new(NewCF.Position * HeadScale) * NewCF.Rotation
		
		Part.CFrame = ScaledCF
		
		local PairedPart = (Part == Base.HeadCF and Base:FindFirstChild("Head", true)) or (Part == Base.RightHandCF and Base.RightHand.Palm) or Base.LeftHand.Palm
		if PairedPart and (Part.Position - PairedPart.Position).Magnitude > 30 then PairedPart.CFrame = Part.CFrame end
	end)
	
	UserInputService.InputChanged:Connect(function(Input: InputObject, GameProcessed: boolean)
		local FingerCode = (Input.KeyCode == Enum.KeyCode.ButtonR2 and "R2") or (Input.KeyCode == Enum.KeyCode.ButtonL2 and "L2") or (Input.KeyCode == Enum.KeyCode.ButtonR1 and "R1") or (Input.KeyCode == Enum.KeyCode.ButtonL1 and "L1") or nil
		if FingerCode then local Pressure = Input.Position.Z MoveFingers(Pressure, FingerCode) end
		
		local ThumbstickCode = (Input.KeyCode == Enum.KeyCode.Thumbstick1 and "T1") or (Input.KeyCode == Enum.KeyCode.Thumbstick2 and "T2") or nil
		if ThumbstickCode then ThumbstickMoved(Input, ThumbstickCode) end
	end)
	
	UserInputService.InputBegan:Connect(function(Input: InputObject, GameProcessed: boolean)
		local FingerCode = (Input.KeyCode == Enum.KeyCode.ButtonA and "A") or (Input.KeyCode == Enum.KeyCode.ButtonX and "X") or nil
		if FingerCode then MoveFingers(1, FingerCode) end
		
		if Input.KeyCode ~= Enum.KeyCode.ButtonSelect then return end
		local Head = Base:FindFirstChild("Head", true)
		if Head then Head.CFrame = Base.HeadCF.CFrame end
		Base.RightHand.Palm.CFrame = Base.RightHandCF.CFrame
		Base.LeftHand.Palm.CFrame = Base.LeftHandCF.CFrame
		VRService:RecenterUserHeadCFrame()
	end)
	
	UserInputService.InputEnded:Connect(function(Input: InputObject, GameProcessed: boolean)
		local FingerCode = (Input.KeyCode == Enum.KeyCode.ButtonA and "A") or (Input.KeyCode == Enum.KeyCode.ButtonX and "X") or nil
		if FingerCode then MoveFingers(0, FingerCode) end
	end)
end```

Please let me know if you have any solutions I can try. Thanks for reading :smile:

1 Like

Is there any way this has anything to do with the AlignOrientations? Any help is appreciated. I’m absolutely stuck on this.

It seems more like a modelling problem using AlignOrientations, perhaps ask the people there.

How is this a modeling problem?

I agree with @Memedealer2111 that this is most likely a modelling problem. Try replacing the hands with balls.