To be frank, I don't know all the details about making your own custom Animation class. I know only a general idea, but the details are fuzzy. I usually end up using a trial-and-error approach trying different methods and reading the source code of similar animations, until it works. But the main idea is:
- An animation receives a
mobject
(it can be a group) on which it will operate. In the__init__
method usually it only stores that object intoself.mobject
, so every animation has that object, and other animation attributes such as the run time, rate function, etc. - When used in a
.play()
, manim will call itsbegin()
method. Usually you don't need to override that. The default implementation creates another inner object calledself.starting_mobject
which stores the initial state of the mobject being animated, and it is simply a deep copy ofself.mobject
(although you can override how that starting mobject is created by overridingcreate_starting_mobject()
method. - At each frame, manim will call the
interpolate()
method of the animation, passing italpha
. This parameter is between 0 and 1 an represents the "intermediate state" of theself.mobject
to render. 0 means the initial state, 1 means the final state. So for example, if you are animating a rotation of 90 degrees, and alpha is 0.5 you have to setself.mobject
to yourstarting_mobject
rotated 45 degrees. Note that alpha can go from 0 to 1 and then back to 0, depending on the rate_func, so usually it is easier to implement the result as some manipulation ofself.starting_mobject
instead of relying on the last state ofself.mobject
- When the animation reaches its end,
finish()
is called. The default implementation callsinterpolate(1)
- Finally
clean_up_from_scene()
is called. The default implementation removes the object from the scene if the animation has the attributeself.remover
toTrue
(examples of these areFadeOut()
,Uncreate()
,...)
But indeed (and here is where things begin to get fuzzy), you don't need to override interpolate()
either, because the default implementation simply calls interpolate_mobject()
, with the same parameter alpha
. So you can override this second one instead. Why is it like this? No idea.
Even more confusing, you don't need to override interpolate_mobject()
either, specially for composed mobjects (ones that have submobjects) because the default implementation of interpolate_mobject()
extracts all submobjects from self.mobject
and calls interpolate_submobject()
passing it three parameters: submobject
(the one to be updated), starting_submobject
(the initial state of that one) and alpha
.
Usually it is interpolate_submobject()
the one that you have to override.
So what I usually do is to inherit from some animations that does something similar to what I want to do, and the override some of those methods.
A pretty generic animation from which inherit is Transform
. This one does the following:
- At
__init__
it receives two mobjects instead of one. It stores them inself.mobject
andself.target_mobject
- At
begin()
it callsself.create_target()
to initializeself.target_mobject
(by default this function simply returns theself.target_mobject
that was stored in__init__
). Then it creates a copy ofself.target_mobject
intoself.target_copy
- It does not override
interpolate
, butinterpolate_submobject()
instead. From this method it simply callssubmobject.interpolate()
passing it the starting submobject, the target copy and the alpha. It is responsability ofsubmobject
to "morph" itself into something between the starting and the target, governed by alpha. - It does not override
finish()
- In
clean_up_from_scene()
it does the replacement of the original mobject by the transformed one, ifreplace_mobject_with_target_in_scene
wasTrue
(it is forReplacementTransform()
).
I want an animation similar to ShrinkToCenter()
but that shrinks into any other given point, instead of the center.
An easy way is to transform the object into that final point, so we can extend Transform
and override the method that provides the target:
class ShrinkToPoint(Transform):
"""Remove an :class:`~.Mobject` by shrinking it to a point.
Parameters
----------
mobject
The mobjects to be introduced.
point
The point to which the mobject shrinks.
"""
def __init__(
self, mobject: Mobject, point: np.ndarray, point_color: str = WHITE, **kwargs
) -> None:
self.point = point
self.point_color = point_color
super().__init__(mobject, remover=True, **kwargs)
def create_target(self) -> Mobject:
super().create_target()
return self.mobject.copy().move_to(self.point).scale(0)