Animating a viewport character?

Hey, and thanks for reading in advance.

I’ve got a character preview in the menu of a class-based fighting game I’m helping to code. Each class has ‘armor’ that defines its aesthetic, plus several unique skins. When the player joins the game, I have the server clone their character, weld the appropriate armor onto the clone, and then stuff it inside a folder in replicatedstorage for the client to make a copy of for insertion into the viewportframe.

I’ve read several posts that confirm you cannot play Animation objects on viewport frame characters, rather - you can use a renderstepped loop to set the Transform property of the viewport model’s joints to that of your actual character, which I did - with no results, as shown above. I made prints in various places to confirm the Transform property of the viewport model’s joints were in fact changing, and they were. I even set the property manually with a command bar, but nothing changed.

Code:

do local fake = RS.FakeCharacters:WaitForChild(Player.Name)
	local fakeCam = Instance.new("Camera")
	
	local hrp = fake:WaitForChild("HumanoidRootPart")
	local armor = fake:WaitForChild("Armor")
	
	fakeCam.CFrame = CFrame.new(hrp.Position + hrp.CFrame.LookVector * 5, hrp.Position)
	fakeCam.Parent = UI.Class.Viewer
	
	fakeChar = fake:Clone()
	fakeChar.Parent = UI.Class.Viewer
	
	UI.Class.Viewer.CurrentCamera = fakeCam
	
	RUN:BindToRenderStep("UpdateFakeCharacter", Enum.RenderPriority.Last.Value, function()
		if Player.Character then
			for _,joint in pairs(Character:GetDescendants()) do
				if joint:IsA("Motor6D") then
					local mirror = fakeChar:FindFirstChild(joint.Name, true)
					if mirror then
						mirror.Transform = joint.Transform
					end
				end
			end
		end
		
		local CF = fakeChar:GetPrimaryPartCFrame()
		fakeChar:SetPrimaryPartCFrame(CF * CFrame.Angles(0, math.rad(0.5), 0))
	end)
end

And on the server:

game.Players.PlayerAdded:Connect(function(player)
	local fake, currentClass, classFolder
	
	local playValue = Instance.new("BoolValue")
	playValue.Name = "Playing"; playValue.Parent = player
	
	local pingValue = Instance.new("NumberValue")
	pingValue.Name = "Ping"; pingValue.Parent = player
	
	player:LoadCharacter(); while not player.Character do wait() end
	player.Character.Archivable = true
	
	--
	Setup(player.Character)
	
	fake = player.Character:Clone()
	
	for _,object in pairs(fake:GetChildren()) do
		if object:IsA("BaseScript") or object:IsA("Folder") then
			object:Destroy()
		end
	end
	
	fake:WaitForChild("HumanoidRootPart").Anchored = true
	fake.Parent = workspace; fake:MoveTo(Vector3.new())
	
	fake.Humanoid:ApplyDescription(game.Players:GetHumanoidDescriptionFromUserId(player.UserId))
	
	while not player:FindFirstChild("SessionData") do
		wait()
	end
	
	currentClass = Data:Check(player, "Game", "Settings", "Class")
	classFolder = Classes:FindFirstChild(currentClass) or Classes["Warrior"]
	
	reSkin(player, classFolder.Skins[Data:Check(player, "Class", currentClass, "Skin")], fake)
	
	Convert(player, classFolder)
	
	delay(1, function()
		fake.HumanoidRootPart.Anchored = false
		fake.Parent = RS.FakeCharacters
	end)
	
	player.CharacterAdded:Connect(function(character)
		Setup(character); Convert(player, Classes[Data:Check(player, "Game", "Settings", "Class")])
	end)
	
	spawn(pingRoutine(player))
end)

I really don’t understand what I’m doing wrong here. Any ideas?

Edit: I’ve repro’d the same system in a blank place. Same issues occur.

These are the only two scripts in the repro:

-- Client
local RUN = game:GetService("RunService")

local UI = script.Parent
local Player = game.Players.LocalPlayer

local fake = game.ReplicatedStorage.FakeCharacters:WaitForChild(Player.Name, 5)

if fake then
	local hrp = fake.HumanoidRootPart
	local camera = Instance.new("Camera")
	
	camera.CFrame = CFrame.new(hrp.Position + hrp.CFrame.LookVector * 5, hrp.Position)
	camera.Parent = UI.ViewportFrame
	UI.ViewportFrame.CurrentCamera = camera
	
	fake = fake:Clone()
	fake.Parent = UI.ViewportFrame
	
	RUN:BindToRenderStep("AnimateFake", Enum.RenderPriority.Last.Value, function()
		if Player.Character then
			for _,object in pairs(Player.Character:GetDescendants()) do
				if object:IsA("Motor6D") then
					local mirror = fake:FindFirstChild(object.Name, true)
					if mirror then
						mirror.Transform = object.Transform
					end
				end
			end
		end
	end)
end
-- Server
game.Players.PlayerAdded:Connect(function(player)
	local character = player.Character or player.CharacterAdded:Wait()
	character.Archivable = true
	
	local fake = character:Clone()
	fake.Parent = game.ReplicatedStorage.FakeCharacters
end)

And a screenshot of the directory:

Is it because I’m making a character from the server? I really don’t get the problem here.

As of recently, roblox introduced a WorldModel object, which allows you to introduce physics into ViewportFrames.
This is the DevForum Post where they introduce this. Hope this helps.

Also, to be clear, they were correct. You could handle all of that manually, but for big parts, it would cause MAJOR performance issues.

Oh. Huh. Figures I’m one of the rare few that missed the memo. Thanks!

1 Like