Introduction:
Ever wanted to animate an object, but it wasn’t a character? You didn’t want to use tweens, because they didn’t have the styles or directions you were looking for?
You wanted to have easily customizable animations, with multiple keyframes, and all inside of a neatly packed module in script!?
Well, I have just the thing for you!:
CFrame Animator 1.0!
yapping →
One day I was working on a game, and I wanted to make some attack moves. I was going to animate, but I realized I didn’t want to have to animate the character, and then implement it into the attack, and then rescript, and then animate the character, and implement, and rinse and repeat. OVER and OVER again.
So, I started writing. A about an hour later I finished my masterpiece, with just a little over 180 lines of code. It would help me script animations! All I would have to do is put the together the character’s body movement (punching, kicking, etc…), And I could leave the rest of the movement (turning around, dashing, flipping, etc) to easily customizable script.
Here’s the resource:
Using this module, you can use your creativity and imagination to put together any effect you want. Whether it’s teleporting behind the player in a move, doing a dash, or maybe even a cutscene animation! You can implement curves, and since you can use curves, you can also do straight lines (straightened out curve).
Tutorial
To use this module, you will first have to come to a basic understanding of bezier curves, and be well-versed in scripting.
– NOTE: This module is NOT incremental (subject to change). It does NOT increment the CFrame
it manipulates. If you try to assign the CFrame
whilst the animation is playing, the animation will not stop, it will be continued and thus overwritten.
This is an OOP (Object Oriented Programming) module, which means to use it, you must create an object of it’s class (animation) using the .new()
function.
local module = require(path.to.module)
local myAnimation = module.new(arg1, arg2, arg3...)
The .new()
function takes 3 arguments. The BasePart
to be manipulated, the table of keyframes, and an optional callback that fires when the animation completes.
Let’s start with the BasePart
local part = workspace.Part
We create a simple part, nothing special.
local keyframes = {}
Now, we must create the keyframes. This will be a table, and it must have at least one keyframe, of course, or the animation will not have a place to go.
Bezier Curves Explained
Before we continue, you must understand something about bezier curves, and how they work with lerping.
Below is a gif represent how bezier curves work:
Bezier curves are curves created by nested interpolation. What do I mean by that? You take a list of a points (P), and for each point, and the next point after it (P(n) & P(n + 1)) excluding the last one, you lerp between those points with a certain alpha (t). After that, you put that point in a new list (K). You repeat for all the points in P, until just before the last point. For the gif, we would stop at P(2), because it’s second to last.
And then, well… That’s it! That’s how you make a bezier curve, no calculus, linear algebra, just some lerping, we repeat the same step on K (the new list) that we did on P, and generate a new list (K1), which would be only 2 points long, until we reach a list that is only one point long. That’s a bezier curve.
Let’s call that whole process, of taking in a list of points, with an alpha (t) and returning a single point value; B(P, t)
. Where P is the list, and t is the alpha.
Back To Scripting
Now, that we understand bezier curves, we can continue with the script, and create the second argument, a list (P) of keyframes.
-- keyframe one (startCFrame, endCFrame, timeStart, timeEnd, timeFunc, midCFrames?, length)
local keyframes = {
{
CFrame.new(),
CFrame.new(0, 10, 0),
0, math.pi / 2, math.sin, {
}, 1
}
}
— This code segment is a lot to take in. Let’s dissect.
You may be wondering why I used CFrame.new()
for the starting position instead of part.CFrame
. As I’ve mentioned earlier, this animation module is not incremental, it assign values, which means if you change the CFrame
while incrementing, the animation will stay in it’s place. We use a blank CFrame
because that’s stating that it’s offset 0 studs and degrees from where the part is. Every CFrame
value you input will be this way.
The starting position of the BasePart
you’re animating is the base position everything will be animated off of, consider the origin.
If you wanted to move the BasePart
while animating, you can manually change the offset position using animation:Move(CFrame)
.
Let’s explain the code. We create a new variable, keyframes
. This is a table of tables (each keyframe), and will hold all our keyframes for the aforementioned arg2
. In the segment, I gave all the arguments for each keyframe, and they are explained below:
-
startCFrame
: This is aCFrame
value, and it is the startingCFrame
of your keyframe. -
endCFrame
: This is also aCFrame
value, and it is the endingCFrame
of your keyframe. -
timeStart
,timeEnd
andtimeFunc
: (to be explained) -
midCFrames
: This is an optional list, if provided, theseCFrames
will be used as midpoints to create a bezier curve as mentioned previously, if not, the keyframe will play as a straight line betweenstartCFrame
andendCFrame
. -
length
: The length of the keyframe in seconds.
Easing Styles Explained
What is an easing style? in TweenService
, they’re used to control the rate at which the tween happens, whether it starts fast, and slows down, or starts slow, speeds up, and slows back down. You can do any of those in this module, using math!
Wait! Don’t click off yet… This is going to be fun. This system uses three values for "easing", a starting time, the time function, and the end time. Let me give an example
Example:
This is an easing style, specifically catered to function like sine. timeStart
is 0, timeEnd
is pi / 2
, and the timeFunc
is just sine. Those numbers generate this graph. But what do those numbers mean?
timeStart
is where the function starts easing on the x axis, and timeEnd
is where is stops. The function is just the function you’re using the generate the easing style.
You can make sine easing styles, and even bouncy, or back, with the proper configurations. To reverse the easing direction in this case, change *timeEnd*
to 0, and timeStart
to -pi / 2
. It will ease out instead of in!
– NOTE: timeEnd
HAS to be greater than timeStart
, or this may cause undesired behavior, or error.
More Easing Styles
-
timeStart
: -2 -
timeEnd
: 2 -
timeFunc
: x2
-
timeStart
: 0 -
timeEnd
: 1.5 * pi -
timeFunc
: sin(x)
Or… just linear!
-
timeStart
: 0 -
timeEnd
: 1 -
timeFunc
: x
Back To Scripting
Great! Now that we understand how the easing system works, let’s continue.
local keyframes = {
{
CFrame.new(),
CFrame.new(0, 10, 0),
0, math.pi / 2, math.sin,
{ CFrame.new(0, 5, 5) },
1
}
}
I’ve inserted in the midCFrames
table, another CFrame
. Between the start and end, but slightly offset to create a curve.
Now, I will add another keyframe, but just in the reverse order, and with a reverse easing direction, like we showed earlier.
local keyframes = {{
CFrame.new(),
CFrame.new(0, 10, 0),
0, math.pi / 2, math.sin,
{ CFrame.new(0, 5, 5) },
1
}, {
CFrame.new(0, 10, 0),
CFrame.new(),
-math.pi / 2, 0, math.sin,
{ CFrame.new(0, 5, 5) },
1
}
}
Looking nice. We've got the keyframes down, now let's create the third argument, the callback when the animation finishes.
This should be simple, for now, we’ll just print a statement:
local callback = function()
print("Animation finished!")
end
Now for the fun part. Let’s plug everything into the module, and get our animation like we deserve.
local myAnimation = module.new(part, keyframes, callback)
And let’s play it!
myAnimation:Play()
Here's the whole script (for reference):
local module = require(game.ReplicatedStorage.Modules.MovementModule)
local part = workspace.Part
local keyframes = {{
CFrame.new(),
CFrame.new(0, 10, 0),
0, math.pi / 2, math.sin,
{ CFrame.new(0, 5, 5) },
1
}, {
CFrame.new(0, 10, 0),
CFrame.new(),
-math.pi / 2, 0, math.sin,
{ CFrame.new(0, 5, 5) },
1
}
}
local callback = function()
print("Animation finished!")
end
local myAnimation = module.new(part, keyframes, callback)
myAnimation:Play()
Conclusion
I will be continuing to take community advice, whether it’s for performance, security, or any other nitpicks, I will take all of the advice, for any other questions, please feel free to fire away in the discussion below.
Have fun with this project.
Remember this module is in 1.0, so it’s VERY beta