Animation on ViewportFrame

I managed to create a script that clones the player’s model in game, and plays an animation and puts it into a ViewportFrame. I thought the closest I could get to creating an animated ViewportFrame was to have still animations.(as seen below)
image

However I had stumbled across a game that had an animated model on a ViewportFrame. Dissecting the idea down, my plan was to do as following:

hide dupe model
v
while true do
v
play anim on og model
v
clone model to viewport
v
wait(animationTime)
v
destroy vf model

6 Likes

Is this plan correct? And if it isn’t what is the correct way, and how does it work?

You’ll want to play the animation on the cloned model, not the original model. A corrected version may look like this:

Clone original model into viewport
v
Hide original model
v
Play the animation on the model clone in the viewport
v
wait(animationTime) then destroy viewport model
v
Show original model again

Let me know what happens

I would like to have a model that is playing an animation constantly, like /edance for reference. So would this just be the same thing as if I were to loop it with this idea?

Yes, you could either loop the existing animation by repeatedly playing it after it stops, or you can create a looped animation in the animation editor.

Oh so, I can’t play animations on the cloned figure, even though ever single instance that was in the original player’s model is within the cloned model?

You should be able to play the animation on the cloned figure. Are you asking if you’re able to or are you saying you tried and it didn’t work?

If you tried and it didn’t work, were there any errors?

EDIT: Viewports don’t support animations. Read @colbert2677’s posts for solutions.

I have not tried that yet. Let me get back to you on that one. (asking if I could)

Yeah, I recommend cloning a figure specifically for playing the animation. That figure will have its Parent be set as the Viewport. The original figure will not have the animation played, because then you won’t see it on the cloned figure. The cloned figure should have the animation played so you can see it in the viewport.

ViewportFrames do not support animations. What you will need to do specifically is to play the animations on either a proxy model or your actual character model. Then, include a loop in RenderStepped (or bind to it, it is appropriate for this use case) that updates each Motor6D’s Transform property in the ViewportFrame character to your model.

I’ve got you in terms of flow.

Setup:
  Model (Actual character or a dummy character)
    Dummy characters need to be placed where players can't see them
  ViewportFrame Character

Workflow:
  Use script below as a template
  Play animation on Model

Script:

local RunService = game:GetService("RunService")
local RenderPriority = Enum.RenderPriority.Last.Value

RunService:BindToRenderStep("UpdateVPCharacter", RenderPriority, function()
    -- Update Motor6Ds here
end)
7 Likes

I have no clue on what RenderStepped or what Motor6Ds are, may I get a link or breakdown?

RenderStepped allows you to bind a function to be ran before a frame renders. In this instance, you’re rendering a ViewportFrame model with animations, therefore it’s appropriate to put it in RenderStep. That being said, my code sets the function to be hooked with the Last priority, meaning it’s one of the final operations to run before a frame is rendered.

Motor6Ds are the welds that connect characters together and what animations modify in order to make characters or assemblies (models) move the way they do. You can get all of them with an iterative filter through your character:

for _, descendant in pairs(character:GetDescendants()) do
    if descendant:IsA("Motor6D") then
        print("Motor6D")
    end
end
1 Like

Okay, that makes much more sense now, thank you.

So for the code that you posted, I play the animation before, and then clone the model within the :BindToRenderStep? This isn’t clicking 100%, but I am pretty close.

No. You clone the model into the ViewportFrame then bind a function to RenderStep. If the model is intended to reflect your character’s current movements, then you play the animation as you normally would on the character and it’ll automatically reflect in the ViewportFrame model.

-- Example script
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")

local LocalPlayer = Players.LocalPlayer
local RenderPriority = Enum.RenderPriority.Last.Value

local character = clonedViewportFrameCharacterGoesHere

RunService:BindToRenderStep("UpdateVPCharacter", RenderPriority, function()
    local realCharacter = LocalPlayer.Character -- Do NOT yield
    if realCharacter then
        for _, descendant in pairs(character:GetDescendants()) do
            if descendant:IsA("Motor6D") then
                local realMotor = realCharacter:FindFirstChild(descendant.Name, true)
                if realMotor then
                    descendant.Transform = realMotor.Transform
                end
            end
        end
    end
end)
3 Likes

I’ll just post my code here, I feel like I am only confusing myself with this.

local plr = game.Players.LocalPlayer
local plrs = game:GetService("Players")
local char = plr.Character or plr.CharacterAdded:wait()
local hum = char:WaitForChild("Humanoid")
local vf = script.Parent
local vfc = Instance.new("Camera", vf)
local idleAnim = nil
local d = Instance.new("Model", workspace)
local anims = script.animations
local part = workspace.Part
local rs = game:GetService('RunService')
local rp = Enum.RenderPriority.Last.Value 

vf.CurrentCamera = vfc
d.Name = plr.Name
wait(2)
for k,v in pairs(char:GetChildren()) do
	v:Clone().Parent = d
end
d.PrimaryPart = d:FindFirstChild('HumanoidRootPart')
local dHum = d:FindFirstChild('Humanoid')
	idleAnim = anims:WaitForChild('idle')
	idleAnim = dHum:LoadAnimation(idleAnim)
	idleAnim:Play()
	d:MoveTo(part.Position)
	wait(2)
	d.Parent = vf
	
	vfc.CFrame = d.HumanoidRootPart.CFrame*CFrame.new(0,0,-5.5,0,1,0,0)
rs:BindToRenderStep("UpdateVPCharacter", rp, function()
    local realCharacter = plr.Character -- Do NOT yield
    if realCharacter then
        for _, descendant in pairs(char:GetDescendants()) do
            if descendant:IsA("Motor6D") then
                local realMotor = realCharacter:FindFirstChild(descendant.Name, true)
                if realMotor then
                    descendant.Transform = realMotor.Transform
                end
            end
        end
    end
end)

Yeah, there’s a few issues in there that may be driving you towards confusion.

Your code has extremely vague variable names (you should use clear variable names so you’re tracking what you’re doing). This part can help you with readability and tracking what exactly you’re going for. This is more of a side note though and not the main problem.

You aren’t supposed to play the animation on the model you’re parenting to the ViewportFrame; you’re meant to play the animation on the actual character or a proxy model. That means either playing an animation on Player.Character or making a copy of the character twice - one to proxy, one to play animations.

local Players = game:GetService("Players")
local RunService = game:GetService("RunService")

local player = Players.LocalPlayer
local character = player.Character or player.CharacterAdded:wait()
local humanoid = character:WaitForChild("Humanoid")

local viewportFrame = script.Parent
local viewportFrameCamera = Instance.new("Camera")

local viewportFrameCharacter = Instance.new("Model")
local viewportProxyCharacter = Instance.new("Model")

local animations = script.animations

local part = workspace.Part

local idleAnim

viewportFrame.CurrentCamera = viewportFrameCamera
viewportFrameCharacter.Name = LocalPlayer.Name
viewportProxyCharacter.Name = LocalPlayer.Name

wait(2)

for _, object in pairs(char:GetChildren()) do
    object:Clone().Parent = viewportFrameCharacter

    if object:IsA("BasePart") then
        local proxyObject = object:Clone()
        proxyObject.Parent = viewportProxyCharacter
        proxyObject.Anchored = object.Name == "HumanoidRootPart"
        proxyObject.Transparency = 1
    end
end

viewportFrameCharacter.PrimaryPart = viewportFrameCharacter.HumanoidRootPart
viewportProxyCharacter.PrimaryPart = viewportProxyCharacter.HumanoidRootPart

local proxyHumanoid = viewportProxyCharacter.Humanoid

idleAnim = proxyHumanoid:LoadAnimation(anims.idle)
idleAnim:Play()

viewportFrameCharacter:MoveTo(part.Position)
viewportFrameCharacter.Parent = viewportFrame

viewportFrameCamera.CFrame = viewportFrameCharacter.HumanoidRootPart.CFrame * CFrame.new(0, 0, -5.5, 0, 1, 0 0)

RunService:BindToRenderStep("UpdateVPCharacter", Enum.RenderPriority.Last.Value, function()
    for _, descendant in pairs(viewportFrameCharacter:GetDescendants()) do
        if descendant:IsA("Motor6D") then
            local realMotor = realCharacter:FindFirstChild(descendant.Name, true)
            if realMotor then
                descendant.Transform = realMotor.Transform
            end
        end
    end
end)
8 Likes

I figured out that I needed to play it from the player model.

This still gives me issues. I am trying to get the animation to constantly play in the viewport frame.


(Note the dancing figure is animated, this was merely a screenshot.)
@colbert2677

You’re not the only one. I am also having trouble figuring this out as well.

[edit: If you can’t view the video, right-click and copy video address and open it in another tab or browser]

I have used similar code to the ones explained previous, just with my own twist but I can guarantee that it should work like the other ones. If needed, I can share what I wrote.