0. はじめに
本記事はmanim入門講座01「ValueTrackerとupdaterでオブジェクトを自動的に動かそう」の続きとなっておりますが、独立して読むこともできます。
動機
アニメーションを作ろうとmanimの勉強を始めたのですが、公式ドキュメントのチュートリアルを終えた途端、何をしたらよいかわからなくなりました。
そこで、作りたいものを決め、そのために必要な事項を生成AIに質問して、それについて公式ドキュメントで学ぶというサイクルを回しています。
同じ悩みを抱えている方の助けになればと思い、上の過程で学んだことをなるべく丁寧に解説していきます。
本記事について
本記事では公式ドキュメントに掲載のあるコードを詳しく解説していきます。
そのため、英語が問題なく読めたり、コードを見ただけで何をしているか把握できる人は直接公式ドキュメントをご覧ください。
1行1行丁寧に解説していきますが、公式ドキュメントのチュートリアルで解説のある内容は飛ばします。
注意
私はpythonを触れたことがある程度で、一般的でない言い回しをしたり、そもそも誤った解説をしてしまうかもしれませんが、ご容赦ください。
その際はご指摘いただければ幸いです。
1. 本記事で解説する内容
本記事で解説する内容はこちらのalways_redrawについてです。
次に示すコードと出力される動画についてはこちらのドキュメントで直接確認できます。
(ただし一部改変)
本記事で解説するコード
from manim import *
# 画面の幅と高さの設定
config.frame_width = 6
config.frame_height = 4
class TangentAnimation(Scene):
def construct(self):
# 2. オブジェクト
ax = Axes(
x_range=[-4, 4, 1],
y_range=[-2, 2, 1],
x_length=5,
y_length=3)
sine = ax.plot(np.sin, color=RED)
tracker = ValueTracker(0)
# 3. always_redraw
point = always_redraw(
lambda: Dot(
sine.point_from_proportion(tracker.get_value()),
color=BLUE
)
)
tangent = always_redraw(
lambda: TangentLine(
sine,
alpha=tracker.get_value(),
color=YELLOW,
length=4
)
)
# 4. 動かしてみる
self.add(ax, sine, point, tangent)
self.play(tracker.animate.set_value(1), rate_func=linear, run_time=4)
2. オブジェクト
-
ax = Axes():- 軸オブジェクトの生成。デフォルトではxy軸が生成される
- 引数
x_rangeでx軸の範囲を[min, max, step]で指定。stepは軸の刻み幅 - 引数
x_lengthで軸の長さを指定 - これら以外にも様々な引数があるのでこちら参照
-
sine = ax.plot(np.sin, color=RED):- 軸
ax上に位置する曲線オブジェクトを生成 - ここではsin関数を与えている
- 引数
colorで色を指定 - これら以外にも様々な引数があるので[こちら参照](CoordinateSystem - Manim Community v0.19.2)
- 軸
-
tracker = ValueTracker(0):- 前回の記事で解説している
-
Dot:点オブジェクト。後で説明 -
TangentLine:接線オブジェクト。後で説明
3. always_redraw
概要
01ではadd_updaterを用いてオブジェクトにupdaterを与え、ValueTrackerの値を変化させることでオブジェクトを自動的に動かすことができました。
今回紹介するalways_redrawを使用しても同じことが実現できます。
まず大雑把に説明すると,always_redraw(オブジェクト)によってupdaterの付与されたMobjectが返されます。このMobjectはValueTrackerの値が更新されたとき,その中身も更新します。
この機能によって,.animateを使ってValueTrackerの値を連続的に変化させれば、それに伴ってMobjectが連続的に動いているように見えることになります。
add_updater()と何が違うの?と思う方もいらっしゃるかもしれません.
使い方を見ればその違いはよく分かると思いますが,最初に言っておくとadd_updater()ではValueTrackerの値更新のたびに,設定していた関数を呼び出し,その関数によってオブジェクトの移動等の操作を行っていました.
対してalways_redrawは,ValueTrackerの値更新のたびに,オブジェクトの位置などの情報を新たに書き換えて移動等を行います.
コードで解説
引数に指定した関数が返すオブジェクトにupdaterを付与します.
always_redrawの基本構文
オブジェクト名 = always_redraw(引数なしでMobjectを返す関数)
always_redrawの使用例(1)
point = always_redraw(
lambda: Dot(
sine.point_from_proportion(tracker.get_value()),
color=BLUE
)
)
細かいところ
-
tracker.get_value():-
ValueTrackerの値を取得する
-
-
.point_from_proportions():- 曲線の左端を0、右端を1としたとき、指定された数の曲線上の位置を返す
- $0.5$なら真ん中など
- そのため
trackerの値は0から1に制約しておく
01で解説した無名関数lambda m : ~と比べて、mがなくなっています。このmは関数の引数なので、引数なしの関数ということになります。
また,:以下が関数の中身で、今回はDotオブジェクトのみ書かれていますが、これで関数呼び出しのときにDotオブジェクトを返します。
Dotは第一引数に点の位置を指定します。その位置がsine.point_from_proportion()となっています.
したがってpointは曲線sine上に位置する点です.
ValueTrackerの値が更新されるたびDot(sine.point_from_proportion(tracker.get_value()),color=BLUE)の中身が更新されます.
これでtrackerの値を0から1へ.animateで連続的に変化させれば,点pointが曲線sineの左端から右端まで連続的に動きます.
always_redrawの使用例(2)
tangent = always_redraw(
lambda: TangentLine(
sine,
alpha=tracker.get_value(),
color=YELLOW,
length=4
)
)
同様に今度はupdaterの付与された接線オブジェクトを生成しています。
接線オブジェクトTangentLineは第一引数に対象とする曲線オブジェクト、引数alphaに曲線の始点を0、終点を1としたときの接点の位置を指定します.
これで同様にtrackerの値を0から1へ.animateで連続的に変化させれば,接線TangentLineが曲線sineの上を左端から右端まで連続的に動きます.
4. 動かしてみる
まずは軸、曲線、点、接線を画面上に表示します.
self.add(ax, sine, point, tangent)
続いてtrackerの値を0から1に連続的に変化させることで,点pointと接線tangentが曲線sine上を連続的に動きます.
self.play(tracker.animate.set_value(1), rate_func=linear, run_time=4)
-
rate_func:- 動き方を指定する引数で
linearだと等速に動く - デフォルトは
smoothで最初と最後がゆっくりになる
- 動き方を指定する引数で
-
run_time:- 動く時間を決定する
