Procedural Animation: What it is and how to do it

Nice article, now I feel motivated to continue fleshing out my old custom animator I made months ago, will probably make a Class for it so it’s a lot easier to handle and if it works well, will probably open source it.

The way I handled replication is having a predefined table of animations with corresponding animation IDs. If a player is animating, tell the server what animation it is and fire that information to all the clients. The clients take the animation ID and run the animation locally for every player. I imagine Roblox does something similar to this since it avoids exploiters from tampering with the animations - in this way, any inappropriate players will be walled since the animations are predefined instead of sending CFrame data across the server.

To be clear, I’m still learning what replicates and what doesn’t - all I do is see if it replicates in studio and if it doesn’t, I know I have to do it manually myself. I have only worked with Motor6D and a simple loop - I didn’t know about TweenService at the time I made my animator and I didn’t like :lerp for being too linear. I’m still fairly new to the API but slowly chugging along :wink:

4 Likes

Procedural Animations was something that I needed to learn. I wanted this kind of resources or tutorials for months, thanks.

4 Likes

RootJoint is not a valid member of Part

1 Like

Are you using R6 or R15? The finished example is for R6, I just tested it.

2 Likes

Sounds weird but would honestly be great to see an example of it working/what it is supposed to look like :man_shrugging: .

4 Likes

Default. How can I check or switch?

1 Like
2 Likes

Home<Game Settings<Avatar<Choose R6

2 Likes

This definitely seems pretty cool to use in games, but wouldn’t it be inefficient if you’re using it for a lot of stuff? (Thanks for the article by the way, the way you explained it was simple and straight to the point).

1 Like

All depends on how you’re replicating. Tweening all players might be a bit inefficient (assuming you haven’t removed the default animation scripts), but there are ways of getting around that, such as only tweening when players are in close proximity and using CFrame:lerp on far away players.
Speaking from my own experience, as long as you aren’t tweening on the server, everything should be fine. This is a major reason for why Strife is so laggy, the game sends your positional data to the server every time it changes, instead of telling the server when your goal is and having the server relay that to other clients for them to tween themselves.

3 Likes

Glad to see black magic being in the spotlight for something like this, thank you for the tutorial!

1 Like

Great call out to the black magic series, specifically phantom’s caustaum, that game keeps me going especially for coding

1 Like

Hello there!
Im having problems with the tweening. I dont know how to describe it, but the animations is acting pretty weird. I have a video here:

The code is here

local rad = math.rad
local ti = TweenInfo.new(0.5, Enum.EasingStyle.Sine, Enum.EasingDirection.Out, 0, false, 0)
local leftArm = char.Torso["Left Shoulder"]
local rightArm = char.Torso["Right Shoulder"]
local rightLeg = char.Torso["Right Hip"]
local leftLeg = char.Torso["Left Hip"]
local hrp = char.HumanoidRootPart.RootJoint
local head = char.Torso.Neck
local savedbaseCframe = rightArm.C0
local savedbaseCframe2 = leftArm.C0
local savedbaseCframe3 = rightLeg.C0
local savedbaseCframe4 = leftLeg.C0
local savedbaseCframe5 = hrp.C0
local savedbaseCframe6 = head.C0

local CFrameValue = Instance.new("CFrameValue")
local CFrameValue2 = Instance.new("CFrameValue")
local CFrameValue3 = Instance.new("CFrameValue")
local CFrameValue4 = Instance.new("CFrameValue")
local CFrameValue5 = Instance.new("CFrameValue")
local CFrameValue6 = Instance.new("CFrameValue")

local goal = {}
local goal2 = {}
local goal3 = {}
local goal4 = {}
local goal5 = {}
local goal6 = {}
goal.Value = savedbaseCframe * CFrame.Angles(rad(-33), rad(0), rad(61))
goal2.Value = savedbaseCframe2 * CFrame.Angles(rad(-33), rad(0), rad(-61))
goal3.Value = savedbaseCframe3 * CFrame.Angles(rad(0), rad(0), rad(-29))
goal4.Value = savedbaseCframe4 * CFrame.Angles(rad(0), rad(0), rad(-29))
goal5.Value = savedbaseCframe5 * CFrame.Angles(rad(25), rad(0), rad(0))
goal6.Value = savedbaseCframe6 * CFrame.Angles(rad(-45), rad(0), rad(0))

CFrameValue.Changed:Connect(function()
	rightArm.C0 = CFrameValue.Value
end)

CFrameValue2.Changed:Connect(function()
	leftArm.C0 = CFrameValue2.Value
end)

CFrameValue3.Changed:Connect(function()
	rightLeg.C0 = CFrameValue3.Value
end)

CFrameValue4.Changed:Connect(function()
	leftLeg.C0 = CFrameValue4.Value
end)

CFrameValue5.Changed:Connect(function()
	hrp.C0 = CFrameValue5.Value
end)

CFrameValue6.Changed:Connect(function()
	head.C0 = CFrameValue6.Value
end)

local tween = ts:Create(CFrameValue, ti, goal)
local tween2 = ts:Create(CFrameValue2, ti, goal2)
local tween3 = ts:Create(CFrameValue3, ti, goal3)
local tween4 = ts:Create(CFrameValue4, ti, goal4)
local tween5 = ts:Create(CFrameValue5, ti, goal5)
local tween6 = ts:Create(CFrameValue6, ti, goal6)
wait(1)
tween:Play()
tween2:Play()
tween3:Play()
tween4:Play()
tween5:Play()
tween6:Play()

Is this something with TweenService? Please help me if you can. :slight_smile:

1 Like

Hi there, not sure what the problem here is… Seems like everything is work as intended.
image
If this is the problem (a weird initial keyframe), see if you’re setting savedbaseCframes before your idle animation plays. In general, I usually wait about two seconds after the player’s character has finished loading in before I apply everything.

1 Like

What happens when regular animations try to play over a procedural animation? Will they interpolate or will one take priority over the other?

(With animations playing) https://gyazo.com/3128b4cc384f7b820f4bd2931c49abb8
(Normal) https://gyazo.com/68d8a219c69555b33ba3d0e76d3697d0
I believe that animation track edits to the C0 take priority over tween service, though animation tracks make changes to the current C0 value (EX, rotate thirty degrees from the current point) versus setting from 0 (EX, set rotation to thirty degress). The stock roblox animations look off because this is re-rigged R6.
The reason my procedural idle isn’t completely overwritten is because I simply set the C0 instead of tweening it.

1 Like

Yo, could I do this but without giving specific angles? I mean, can I just use keyframe sequences from RobloxStudio, sort of like what the animations contain? I wanna smoothly transition from one keyframe sequence to another.

If not, would I be able to get the keyframe sequence of each Motor6D using a function that loops through everything in the character and gets all the Motor6Ds in the character, and then sets a base CFrame(reset keyframe sequence), and then just subtracts from the final keyframe sequence to get the change? Like:

final keyframe sequence - reset keyframe sequence(base CFrame)

This would give me the CFrame relative to the base CFrame, and I can just use that CFrame and do:

goal.Value = baseCFrame * relative_cframe_to_base;

If not, how can I achieve this?

1 Like

I’ve been having trouble with this too, and this is what I’m trying. You can just create your keyframes in the animation editor, then save the C1 value of the M6D that’s being changed with the command bar. The animation editor doesn’t change the C0 value at all, only the C1 value. Then you can just tween the player’s C1 value to that. It works perfectly. It’s a little tedious, but I don’t know of any plugins to help with this.


This is the code I pasted into the command bar to test it.

local MainC1 = workspace.TestDummy.Torso["Left Shoulder"].C1 
game:GetService("TweenService"):Create(workspace.Dummy.Torso["Left Shoulder"],TweenInfo.new(1),{ C1 = MainC1 }):Play()
4 Likes

Can you make a tutorial about springs in the character animations(not fps in any case), i mean’t as example if the player fell while he is falling the spring would make the camera go up/once he touchs the ground it bounces back. nice tutorial

Did you do a relative difference or just saved the last C1 values…? Has everything worked out for you?

1 Like