はじめに
ということで、九九のうたをManimとMusicXmlで作成しました。
九九の表
Manimのコード
画面は以下を利用して作成しました。Youtube Shorts用のアスペクト比を設定しています。
%%writefile MultiplicationTable.py
from manim import *
import numpy as np
# ==========================================
# Youtube Shorts用のアスペクト比・解像度設定
# ==========================================
config.pixel_width = 1080
config.pixel_height = 1920
config.frame_width = 8.0
config.frame_height = 8.0 * (1920 / 1080) # 約14.22
# ==========================================
# 定数・データ定義
# ==========================================
# カラー設定
BG_COLOR = "#FFF8E1"
TITLE_COLOR = "#FF6F00"
EQ_TEXT_COLOR = "#5D4037"
READ_TEXT_COLOR = "#039BE5"
ANS_TEXT_COLOR = "#FF1744"
# グリッド用カラー設定
HEADER_BG = "#FFE082"
HEADER_TEXT_COLOR = "#E65100"
GRID_STROKE_COLOR = "#BCAAA4"
ROW_COLORS = [
WHITE, # ヘッダー用
"#FFCDD2", # 1のだん
"#F8BBD0", # 2のだん
"#E1BEE7", # 3のだん
"#D1C4E9", # 4のだん
"#C5CAE9", # 5のだん
"#BBDEFB", # 6のだん
"#B2EBF2", # 7のだん
"#C8E6C9", # 8のだん
"#FFF9C4" # 9のだん
]
# ==========================================
# アニメーション速度・タイミング設定
# ==========================================
BPM = 117
BEAT = 60 / BPM
SPEED_RATE = 1.0 # 微調整用
# 各フェーズにかかる時間 (単位: ビート数)
INTRO_IN_BEATS = 1.0
INTRO_WAIT_BEATS = 5.2
INTRO_OUT_BEATS = 1.0
EQ_IN_BEATS = 1.0
EQ_WAIT_BEATS = 1.0
ANS_IN_BEATS = 1.0
ANS_WAIT_BEATS = 0.5
STEP_CLEAR_BEATS = 0.5
TABLE_CLEAR_BEATS = 2.0
SUMMARY_IN_BEATS = 2.0
SUMMARY_WAIT_BEATS = 4.0
SUMMARY_OUT_BEATS = 2.0
def sec(beats):
return beats * BEAT * SPEED_RATE
# ==========================================
# テキスト・ふりがなデータ
# ==========================================
kuku_titles = {
1: "1のだん", 2: "2のだん", 3: "3のだん",
4: "4のだん", 5: "5のだん", 6: "6のだん",
7: "7のだん", 8: "8のだん", 9: "9のだん"
}
kuku_readings = {
1: [("いん", "いちが", "いち"), ("いん", "にが", "に"), ("いん", "さんが", "さん"), ("いん", "しが", "し"), ("いん", "ごが", "ご"), ("いん", "ろくが", "ろく"), ("いん", "しちが", "しち"), ("いん", "はちが", "はち"), ("いん", "くが", "く")],
2: [("に", "いちが", "に"), ("に", "にんが", "し"), ("に", "さんが", "ろく"), ("に", "しが", "はち"), ("に", "ご", "じゅう"), ("に", "ろく", "じゅうに"), ("に", "しち", "じゅうし"), ("に", "はち", "じゅうろく"), ("に", "く", "じゅうはち")],
3: [("さん", "いちが", "さん"), ("さん", "にが", "ろく"), ("さ", "ざんが", "く"), ("さん", "し", "じゅうに"), ("さん", "ご", "じゅうご"), ("さぶ", "ろく", "じゅうはち"), ("さん", "しち", "にじゅういち"), ("さん", "ぱ", "にじゅうし"), ("さん", "く", "にじゅうしち")],
4: [("し", "いちが", "し"), ("し", "にが", "はち"), ("し", "さん", "じゅうに"), ("し", "し", "じゅうろく"), ("し", "ご", "にじゅう"), ("し", "ろく", "にじゅうし"), ("し", "しち", "にじゅうはち"), ("し", "は", "さんじゅうに"), ("し", "く", "さんじゅうろく")],
5: [("ご", "いちが", "ご"), ("ご", "に", "じゅう"), ("ご", "さん", "じゅうご"), ("ご", "し", "にじゅう"), ("ご", "ご", "にじゅうご"), ("ご", "ろく", "さんじゅう"), ("ご", "しち", "さんじゅうご"), ("ご", "は", "しじゅう"), ("ごっ", "く", "しじゅうご")],
6: [("ろく", "いちが", "ろく"), ("ろく", "に", "じゅうに"), ("ろく", "さん", "じゅうはち"), ("ろく", "し", "にじゅうし"), ("ろく", "ご", "さんじゅう"), ("ろく", "ろく", "さんじゅうろく"), ("ろく", "しち", "しじゅうに"), ("ろく", "は", "しじゅうはち"), ("ろっ", "く", "ごじゅうし")],
7: [("しち", "いちが", "しち"), ("しち", "に", "じゅうし"), ("しち", "さん", "にじゅういち"), ("しち", "し", "にじゅうはち"), ("しち", "ご", "さんじゅうご"), ("しち", "ろく", "しじゅうに"), ("しち", "しち", "しじゅうく"), ("しち", "は", "ごじゅうろく"), ("しち", "く", "ろくじゅうさん")],
8: [("はち", "いちが", "はち"), ("はち", "に", "じゅうろく"), ("はち", "さん", "にじゅうし"), ("はち", "し", "さんじゅうに"), ("はち", "ご", "しじゅう"), ("はち", "ろく", "しじゅうはち"), ("はち", "しち", "ごじゅうろく"), ("はっ", "ぱ", "ろくじゅうし"), ("はっ", "く", "しちじゅうに")],
9: [("く", "いちが", "く"), ("く", "に", "じゅうはち"), ("く", "さん", "にじゅうしち"), ("く", "し", "さんじゅうろく"), ("く", "ご", "しじゅうご"), ("く", "ろく", "ごじゅうし"), ("く", "しち", "ろくじゅうさん"), ("く", "は", "しちじゅうに"), ("く", "く", "はちじゅういち")]
}
# ==========================================
# 各段のイラストを描画するクラス群
# ==========================================
class Pencil(VGroup):
def __init__(self, **kwargs):
super().__init__(**kwargs)
body = Rectangle(width=0.3, height=0.8, color=BLACK, fill_color=YELLOW, fill_opacity=1)
tip = Polygon([-0.15, -0.4, 0], [0.15, -0.4, 0], [0, -0.7, 0], color=BLACK, fill_color='#D2B48C', fill_opacity=1)
core = Polygon([-0.05, -0.6, 0], [0.05, -0.6, 0], [0, -0.7, 0], color=BLACK, fill_color=BLACK, fill_opacity=1)
self.add(body, tip, core)
self.scale(0.8)
class CherryPair(VGroup):
def __init__(self, **kwargs):
super().__init__(**kwargs)
left_cherry = Circle(radius=0.25, color=BLACK, fill_color=RED, fill_opacity=1).shift(LEFT * 0.3 + DOWN * 0.3)
right_cherry = Circle(radius=0.25, color=BLACK, fill_color=RED, fill_opacity=1).shift(RIGHT * 0.3 + DOWN * 0.3)
stem_center = UP * 0.4
left_stem = Line(left_cherry.get_top(), stem_center, color="#006400", stroke_width=4)
right_stem = Line(right_cherry.get_top(), stem_center, color="#006400", stroke_width=4)
leaf = Ellipse(width=0.3, height=0.15, color=BLACK, fill_color=GREEN, fill_opacity=1).rotate(PI / 6).move_to(stem_center + RIGHT * 0.15 + UP * 0.1)
self.add(left_stem, right_stem, left_cherry, right_cherry, leaf)
self.scale(0.8)
class Dango(VGroup):
def __init__(self, **kwargs):
super().__init__(**kwargs)
stick = Line(UP*0.4, DOWN*0.8, color='#8B4513', stroke_width=6)
pink = Circle(radius=0.25, color=BLACK, fill_color='#FFB6C1', fill_opacity=1).shift(UP*0.4)
white = Circle(radius=0.25, color=BLACK, fill_color=WHITE, fill_opacity=1)
green = Circle(radius=0.25, color=BLACK, fill_color='#98FB98', fill_opacity=1).shift(DOWN*0.4)
self.add(stick, green, white, pink)
self.scale(0.8)
class Clover(VGroup):
def __init__(self, **kwargs):
super().__init__(**kwargs)
stem = Line(ORIGIN, DOWN*0.7, color="#006400", stroke_width=5)
leaves = VGroup(*[Circle(radius=0.2, color=BLACK, fill_color=GREEN, fill_opacity=1).shift(RIGHT*0.25).rotate(a, about_point=ORIGIN) for a in [PI/4, 3*PI/4, 5*PI/4, 7*PI/4]])
self.add(stem, leaves)
self.scale(0.8)
class Hand(VGroup):
def __init__(self, **kwargs):
super().__init__(**kwargs)
wrist = Rectangle(width=0.25, height=0.4).shift(DOWN * 0.45)
palm = RoundedRectangle(corner_radius=0.2, width=0.55, height=0.6).shift(DOWN * 0.1)
parts = [wrist, palm]
finger_params = [(0.12, 0.35, -15, 0.22, 0.02), (0.13, 0.45, -5, 0.10, 0.10), (0.14, 0.50, 0, -0.02, 0.15), (0.14, 0.45, 8, -0.14, 0.10), (0.16, 0.35, 40, -0.26, -0.05)]
for w, h, angle, dx, dy in finger_params:
parts.append(RoundedRectangle(corner_radius=w/2, width=w, height=h).shift(UP * (h / 2)).rotate(angle * DEGREES, about_point=ORIGIN).shift(RIGHT * dx + UP * dy))
hand_shape = Union(*parts, color='#C99056', stroke_width=2, fill_color='#FAD6B1', fill_opacity=1)
creases = VGroup(
ArcBetweenPoints(start=LEFT*0.1+UP*0.1, end=LEFT*0.05+DOWN*0.2, angle=PI/6, color='#E5B887', stroke_width=2),
ArcBetweenPoints(start=LEFT*0.1+UP*0.08, end=RIGHT*0.15+DOWN*0.05, angle=PI/8, color='#E5B887', stroke_width=2)
).shift(DOWN * 0.1)
self.add(hand_shape, creases)
self.scale(0.8)
class Insect(VGroup):
def __init__(self, **kwargs):
super().__init__(**kwargs)
legs = VGroup()
for sy, mx, my, ex, ey in [
(0.15, 0.4, 0.35, 0.6, 0.5),
(0, 0.45, 0, 0.65, 0),
(-0.15, 0.4, -0.35, 0.6, -0.5)
]:
legs.add(VGroup(Line(RIGHT*0.15+UP*sy, RIGHT*mx+UP*my, color=BLACK, stroke_width=6),
Line(RIGHT*mx+UP*my, RIGHT*ex+UP*ey, color=BLACK, stroke_width=6)))
legs.add(VGroup(Line(LEFT*0.15+UP*sy, LEFT*mx+UP*my, color=BLACK, stroke_width=6),
Line(LEFT*mx+UP*my, LEFT*ex+UP*ey, color=BLACK, stroke_width=6)))
body = Ellipse(width=0.45, height=0.6, color=BLACK, fill_color='#8BC34A', fill_opacity=1)
line = Line(UP*0.3, DOWN*0.3, color=BLACK, stroke_width=3)
head = Arc(radius=0.18, start_angle=0, angle=PI, color=BLACK, fill_color=BLACK, fill_opacity=1).shift(UP*0.25)
antennas = VGroup(
Line(UP*0.4, LEFT*0.1+UP*0.5, color=BLACK, stroke_width=3),
Line(UP*0.4, RIGHT*0.1+UP*0.5, color=BLACK, stroke_width=3)
)
self.add(legs, antennas, body, line, head)
self.scale(0.8)
class Keyboard(VGroup):
def __init__(self, **kwargs):
super().__init__(**kwargs)
white_keys = VGroup(*[Rectangle(width=0.2, height=0.8, color=BLACK, stroke_width=1, fill_color=WHITE, fill_opacity=1).shift(RIGHT * i * 0.2) for i in range(7)]).center()
black_keys = VGroup()
for i in [0, 1, 3, 4, 5]:
b_key = Rectangle(width=0.12, height=0.45, color=BLACK, stroke_width=1, fill_color=BLACK, fill_opacity=1).move_to(white_keys[i].get_right()).align_to(white_keys[i], UP)
black_keys.add(b_key)
keys = VGroup(white_keys, black_keys).center()
frame = Rectangle(width=0.2*7+0.1, height=0.9, color=BLACK, stroke_width=2, fill_color='#8B4513', fill_opacity=1).move_to(keys.get_center())
frame_top = Rectangle(width=0.2*7+0.1, height=0.2, color=BLACK, stroke_width=2, fill_color='#8B4513', fill_opacity=1).next_to(frame, UP, buff=0)
self.add(frame_top, frame, keys).center()
self.scale(0.8)
class Octopus(VGroup):
def __init__(self, **kwargs):
super().__init__(**kwargs)
body = Ellipse(width=0.5, height=0.6, color=BLACK, fill_color='#FF4500', fill_opacity=1).shift(UP * 0.1)
legs = VGroup()
for i in range(8):
pos = i - 3.5
p0 = np.array([pos*0.04, -0.1, 0])
p3 = np.array([pos*0.22, -0.7, 0])
vec = p3 - p0
normal = np.array([vec[1], -vec[0], 0])
normal = normal / (np.linalg.norm(normal) + 1e-6)
amp = 0.15 if i % 2 == 0 else -0.15
p1 = p0 + vec * 0.33 + normal * amp
p2 = p0 + vec * 0.66 - normal * amp
legs.add(CubicBezier(p0.tolist(), p1.tolist(), p2.tolist(), p3.tolist(), color=BLACK, stroke_width=11),
CubicBezier(p0.tolist(), p1.tolist(), p2.tolist(), p3.tolist(), color='#FF4500', stroke_width=7))
mouth = Ellipse(width=0.1, height=0.15, color=BLACK, fill_color='#FFB6C1', fill_opacity=1).shift(DOWN * 0.05)
mouth_hole = Circle(radius=0.02, color=BLACK, fill_color=BLACK, fill_opacity=1).move_to(mouth)
hachimaki = Rectangle(width=0.5, height=0.1, color=BLACK, fill_color=WHITE, fill_opacity=1).shift(UP * 0.25)
k1 = Polygon([0,0,0], [0.1,0.08,0], [0.15,-0.05,0], color=BLACK, fill_color=WHITE, fill_opacity=1).shift(RIGHT * 0.25 + UP * 0.25).rotate(-PI/6)
k2 = Polygon([0,0,0], [0.1,0.08,0], [0.15,-0.05,0], color=BLACK, fill_color=WHITE, fill_opacity=1).shift(RIGHT * 0.25 + UP * 0.25).rotate(PI/4)
eyes = VGroup()
for dx in [-0.12, 0.12]:
eye = Circle(radius=0.08, color=BLACK, fill_color=WHITE, fill_opacity=1).shift(RIGHT*dx + UP*0.1)
pupil = Circle(radius=0.04, color=BLACK, fill_color=BLACK, fill_opacity=1).move_to(eye).shift(RIGHT*0.02)
hl = Circle(radius=0.015, color=WHITE, fill_color=WHITE, fill_opacity=1).move_to(pupil).shift(LEFT*0.01 + UP*0.01)
eyes.add(eye, pupil, hl)
self.add(legs, body, mouth, mouth_hole, hachimaki, k1, k2, eyes).center()
self.scale(0.8)
class BaseballPositions(VGroup):
def __init__(self, **kwargs):
super().__init__(**kwargs)
angles = np.linspace(PI/4, 3*PI/4, 20)
field = Polygon(*([[0,0,0]] + [[2.0*np.cos(a), 2.0*np.sin(a), 0] for a in angles]), color=BLACK, stroke_width=2, fill_color='#4CAF50', fill_opacity=1)
infield = Polygon([0,0,0], [0.7,0.7,0], [0,1.4,0], [-0.7,0.7,0], color=WHITE, stroke_width=2, fill_color='#D7CCC8', fill_opacity=1)
home = Polygon([0,-0.05,0], [0.05,0,0], [0.05,0.05,0], [-0.05,0.05,0], [-0.05,0,0], color=WHITE, fill_color=WHITE, fill_opacity=1, stroke_width=1)
positions = [(0, 0), (0, 0.7), (0.75, 0.75), (0.35, 1.15), (-0.75, 0.75), (-0.35, 1.15), (-1.0, 1.7), (0, 1.8), (1.0, 1.7)]
players = VGroup(*[Circle(radius=0.15, color=BLACK, stroke_width=1.5, fill_color='#FF5722', fill_opacity=1).move_to(RIGHT*x + UP*y) for x, y in positions])
self.add(field, infield, home, players).center()
self.scale(0.45)
# ==========================================
# 共通関数・共通クラス群
# ==========================================
def get_item_for_table(table_num):
ITEM_CLASSES = {
1: Pencil, 2: CherryPair, 3: Dango, 4: Clover, 5: Hand,
6: Insect, 7: Keyboard, 8: Octopus, 9: BaseballPositions
}
return ITEM_CLASSES.get(table_num, lambda: Circle(color=BLACK))()
class GridGenerator:
"""まとめ表の生成ロジック (縦長比率に調整)"""
@staticmethod
def create_grid(cell_width=0.9, cell_height=1.1):
grid = VGroup()
for r in range(10):
for c in range(10):
x = (c - 4.5) * cell_width
y = (4.5 - r) * cell_height - 0.25
cell = GridGenerator._create_cell(r, c, x, y, cell_width, cell_height)
grid.add(cell)
return grid
@staticmethod
def _create_cell(r, c, x, y, cw, ch):
if r == 0 and c == 0:
content = Text("×", font_size=24, color=HEADER_TEXT_COLOR)
bg_color = HEADER_BG
opacity = 1
elif r == 0:
num_text = Text(str(c), font_size=20, color=HEADER_TEXT_COLOR)
squares = VGroup(*[
Square(side_length=0.08, fill_color=HEADER_TEXT_COLOR, fill_opacity=1, stroke_width=0)
.move_to(RIGHT * (j % 3) * 0.11 + DOWN * (j // 3) * 0.11)
for j in range(c)
]).center()
content = VGroup(num_text, squares).arrange(DOWN, buff=0.05)
bg_color = HEADER_BG
opacity = 1
elif c == 0:
content = Text(str(r), font_size=24, color=HEADER_TEXT_COLOR)
bg_color = HEADER_BG
opacity = 1
else:
return GridGenerator._create_body_cell(r, c, x, y, cw, ch)
box = Rectangle(width=cw, height=ch, fill_color=bg_color, fill_opacity=opacity, stroke_color=GRID_STROKE_COLOR, stroke_width=1)
return VGroup(box, content).move_to(RIGHT * x + UP * y)
@staticmethod
def _create_body_cell(table_num, i, x, y, cw, ch):
bg_color = ROW_COLORS[table_num]
box = Rectangle(width=cw, height=ch, fill_color=bg_color, fill_opacity=0.5, stroke_color=GRID_STROKE_COLOR, stroke_width=1)
eq_mob = Text(f"{table_num}×{i}={table_num * i}", font_size=16, color="#3E2723")
read_text_left, read_text_right, ans_read_text = kuku_readings[table_num][i - 1]
read_mob = Text(f"{read_text_left}{read_text_right}{ans_read_text}", font_size=12, color="#039BE5")
items = VGroup()
for j in range(i):
item = get_item_for_table(table_num).scale(0.12)
for submob in item.get_family():
if isinstance(submob, VMobject):
submob.stroke_width = submob.get_stroke_width() * 0.05
item.move_to(RIGHT * (j % 3) * 0.18 + DOWN * (j // 3) * 0.18)
items.add(item)
items.center()
content = VGroup(eq_mob, read_mob, items).arrange(DOWN, buff=0.05)
for target_size, actual_size in [(ch * 0.9, content.height), (cw * 0.95, content.width)]:
if actual_size > target_size:
scale_factor = target_size / actual_size
content.scale(scale_factor)
for submob in items.get_family():
if isinstance(submob, VMobject):
submob.stroke_width *= scale_factor
return VGroup(box, content).move_to(RIGHT * x + UP * y)
# ==========================================
# メインのアニメーション(ベースとなる親クラス)
# ==========================================
class BaseMultiplicationTable(Scene):
def setup(self):
self.camera.background_color = BG_COLOR
def play_table(self, table_num):
# --- タイトルと「該当のだん」の大きな表表示 ---
title = Text(kuku_titles[table_num], font_size=80, color=TITLE_COLOR).to_edge(UP, buff=1.0)
row_group = VGroup()
for i in range(1, 10):
# 縦長画面用サイズに調整
cw, ch = 2.4, 2.4
idx = i - 1
x = (idx % 3 - 1) * cw
y = (1 - idx // 3) * ch
bg_color = ROW_COLORS[table_num]
box = Rectangle(width=cw, height=ch, fill_color=bg_color, fill_opacity=0.5, stroke_color=GRID_STROKE_COLOR, stroke_width=2)
eq_mob = Text(f"{table_num}×{i}={table_num * i}", font_size=36, color="#3E2723")
read_left, read_right, read_ans = kuku_readings[table_num][i - 1]
read_mob = Text(f"{read_left}{read_right}{read_ans}", font_size=24, color="#039BE5")
items = VGroup()
for j in range(i):
item = get_item_for_table(table_num).scale(0.35)
for submob in item.get_family():
if isinstance(submob, VMobject):
submob.stroke_width = submob.get_stroke_width() * 0.3
item.move_to(RIGHT * (j % 3) * 0.45 + DOWN * (j // 3) * 0.45)
items.add(item)
items.center()
content = VGroup(eq_mob, read_mob, items).arrange(DOWN, buff=0.15)
for target_size, actual_size in [(ch * 0.9, content.height), (cw * 0.95, content.width)]:
if actual_size > target_size:
scale_factor = target_size / actual_size
content.scale(scale_factor)
for submob in items.get_family():
if isinstance(submob, VMobject):
submob.stroke_width *= scale_factor
cell = VGroup(box, content).move_to(RIGHT * x + UP * y)
row_group.add(cell)
row_group.move_to(DOWN * 0.5)
# 画面幅をはみ出さないようにスケール調整
if row_group.width > config.frame_width * 0.9:
row_group.width = config.frame_width * 0.9
self.play(Write(title), FadeIn(row_group, lag_ratio=0.05), run_time=sec(INTRO_IN_BEATS))
self.wait(sec(INTRO_WAIT_BEATS))
self.play(FadeOut(title), FadeOut(row_group), run_time=sec(INTRO_OUT_BEATS))
# --- 各計算式の表示 ---
drawn_items = VGroup()
# Shorts縦長画面に合わせて配置を上部&中央に寄せる
Y_EQ = 4.5
X_NUM1 = -2.2
X_TIMES = -1.1
X_NUM2 = 0.0
X_EQ = 1.1
num1_mob = Text(str(table_num), font_size=70, color=EQ_TEXT_COLOR).move_to(RIGHT * X_NUM1 + UP * Y_EQ)
times_mob = Text("×", font_size=70, color=EQ_TEXT_COLOR).move_to(RIGHT * X_TIMES + UP * Y_EQ)
eq_sign_mob = Text("=", font_size=70, color=EQ_TEXT_COLOR).move_to(RIGHT * X_EQ + UP * Y_EQ)
last_mobs_to_clear = []
for i in range(1, 10):
num2_mob = Text(str(i), font_size=70, color=EQ_TEXT_COLOR).move_to(RIGHT * X_NUM2 + UP * Y_EQ)
ans_mob = Text(str(table_num * i), font_size=80, color=ANS_TEXT_COLOR)
ans_mob.move_to(RIGHT * (X_EQ + 0.8) + UP * Y_EQ, aligned_edge=LEFT)
read_left, read_right, read_ans = kuku_readings[table_num][i - 1]
read1_mob = Text(read_left, font_size=30, color=READ_TEXT_COLOR).next_to(num1_mob, DOWN, buff=0.3)
read2_mob = Text(read_right, font_size=30, color=READ_TEXT_COLOR).next_to(num2_mob, DOWN, buff=0.3)
ans_read_mob = Text(read_ans, font_size=30, color=ANS_TEXT_COLOR).next_to(ans_mob, DOWN, buff=0.3)
# イラストを縦長スペースに均等配置
new_item = get_item_for_table(table_num)
idx = i - 1
x_offset = (idx % 3 - 1) * 2.2
y_offset = (1 - idx // 3) * 2.5
new_item.move_to(DOWN * 1.5 + RIGHT * x_offset + UP * y_offset)
drawn_items.add(new_item)
anims_to_play = [
Write(num2_mob), Write(read1_mob), Write(read2_mob),
FadeIn(new_item, shift=DOWN * 0.2)
]
if i == 1:
anims_to_play = [Write(num1_mob), Write(times_mob), Write(eq_sign_mob)] + anims_to_play
self.play(*anims_to_play, run_time=sec(EQ_IN_BEATS))
self.wait(sec(EQ_WAIT_BEATS))
self.play(Write(ans_mob), Write(ans_read_mob), run_time=sec(ANS_IN_BEATS))
self.wait(sec(ANS_WAIT_BEATS))
# iが9より小さい場合は先に数字や読み仮名だけフェードアウトさせる
if i < 9:
self.play(
*[FadeOut(m) for m in [num2_mob, read1_mob, read2_mob, ans_mob, ans_read_mob]],
run_time=sec(STEP_CLEAR_BEATS)
)
else:
# i == 9 の場合は最後に全体と同時にフェードアウトさせるため保持しておく
last_mobs_to_clear = [num2_mob, read1_mob, read2_mob, ans_mob, ans_read_mob]
# 最後に数式・文字・図をすべて同時にフェードアウト
self.play(
FadeOut(drawn_items),
FadeOut(num1_mob),
FadeOut(times_mob),
FadeOut(eq_sign_mob),
*[FadeOut(m) for m in last_mobs_to_clear],
run_time=sec(TABLE_CLEAR_BEATS)
)
# 個別レンダリング用のクラス
class Table1(BaseMultiplicationTable):
def construct(self): self.play_table(1)
class Table2(BaseMultiplicationTable):
def construct(self): self.play_table(2)
class Table3(BaseMultiplicationTable):
def construct(self): self.play_table(3)
class Table4(BaseMultiplicationTable):
def construct(self): self.play_table(4)
class Table5(BaseMultiplicationTable):
def construct(self): self.play_table(5)
class Table6(BaseMultiplicationTable):
def construct(self): self.play_table(6)
class Table7(BaseMultiplicationTable):
def construct(self): self.play_table(7)
class Table8(BaseMultiplicationTable):
def construct(self): self.play_table(8)
class Table9(BaseMultiplicationTable):
def construct(self): self.play_table(9)
# ==========================================
# まとめ表・動画の結合クラス
# ==========================================
class MultiplicationTableGrid(Scene):
def construct(self):
self.camera.background_color = BG_COLOR
title = Text("くくのひょう", font_size=60, color=TITLE_COLOR).to_edge(UP, buff=1.0)
grid = GridGenerator.create_grid()
# 画面幅をはみ出さないようにスケール調整
grid.width = config.frame_width * 0.95
self.play(AnimationGroup(FadeIn(title), FadeIn(grid, lag_ratio=0.01), lag_ratio=0.1), run_time=sec(SUMMARY_IN_BEATS))
self.wait(sec(SUMMARY_WAIT_BEATS))
class FullMultiplicationVideo(BaseMultiplicationTable):
def show_summary_table(self, is_start=False):
title = Text("くくのひょう", font_size=60, color=TITLE_COLOR).to_edge(UP, buff=1.0)
grid = GridGenerator.create_grid()
grid.width = config.frame_width * 0.95
self.play(AnimationGroup(FadeIn(title), FadeIn(grid, lag_ratio=0.01), lag_ratio=0.1), run_time=sec(SUMMARY_IN_BEATS))
self.wait(sec(SUMMARY_WAIT_BEATS))
if is_start:
self.play(FadeOut(title), FadeOut(grid), run_time=sec(SUMMARY_OUT_BEATS))
self.clear()
def construct(self):
self.show_summary_table(is_start=True)
for i in range(1, 10):
self.play_table(i)
self.clear()
self.camera.background_color = BG_COLOR
self.show_summary_table(is_start=False)
VoiceVoxに読み込んだMusicXml(メロディライン)
以下に掲載しています。
MuseScoreに読み込んだMusicXml(BGM)
以下に掲載しています。
各九九の段の詳細
それぞれ以下に掲載しています。
