何故そんな事をするのか?
Manimの使い方を既に心得ている人はこの記事は読まなくていいかもしれません。これは私のようにPyGameの使い方は心得ているけどManimを学ぶ気力が無いのでPyGameの知識をそのまま活かしてアニメーションを作りたいという人向けの提案となります。
また何も動画作成に特化した事をするわけではないので、これから行うことはリアルタイムで動くPyGameプログラムでもそのまま使える知識となります。なので動画とPyGameプログラムの作り方を同時に学べるという利もあります。
描かれたフレームをffmpegに流し込むだけ?
動画生成自体は極論それだけやればできますがそれだけならわざわざ記事に取り上げる程の物ではないでしょう。取り上げた理由は私が作っているasyncライブラリを用いる事でかなり良い感じにコードを書けると思ったからです。
async/await構文の利点については多くの文献で語られているのでここでは触れませんが、とりあえずアニメーションに役立つ機能が豊富なので紹介します。
黄色い正方形が画面の端を周るアニメーション
from typing import Unpack
from functools import partial
import pygame
from pygame.colordict import THECOLORS
import asyncpygame as apg
async def main(**kwargs: Unpack[apg.CommonParams]):
pygame.init()
screen = pygame.display.set_mode((240, 240))
screen_rect = screen.get_rect()
dest = pygame.Rect(0, 0, 40, 40)
r = kwargs["executor"].register
r(partial(screen.fill, THECOLORS["black"]), priority=0)
r(partial(pygame.draw.rect, screen, THECOLORS["yellow"], dest), priority=0x100)
r(pygame.display.flip, priority=0xFFFFFF00)
anim_attrs = kwargs["clock"].anim_attrs
while True:
await anim_attrs(dest, right=screen_rect.right, duration=500)
await anim_attrs(dest, bottom=screen_rect.bottom, duration=500)
await anim_attrs(dest, left=screen_rect.left, duration=500)
await anim_attrs(dest, top=screen_rect.top, duration=500)
if __name__ == "__main__":
apg.run(main)
解説
kwargs["executor"].register
で登録した関数達は毎フレームpriority
の値が小さい順に呼ばれます。なので上記の例ではフレーム毎に
screen.fill
pygame.draw.rect
pygame.display.flip
の順に呼ばれます。register
はコンテキストマネージャを返し、その __exit__()
が呼ばれた時に登録を解除するのでwithブロックを用いて登録の有効範囲を視覚的に分かりやすく書き表せます。
# 小規模ではないアプリケーションではこれが主力となる書き方です
r = kwargs["executor"].register
with (
r(func1, priority=...),
r(func2, priority=...),
):
...
kwargs["clock"].anim_attrs
は任意のオブジェクトの属性を時間をかけて徐々に変化させます。上の例ではwhileループ内で Rect
のインスタンスであるdest
の
- 右端(right)を500msかけて
screen_rect.right
に変化させる - 下端(bottom)を500msかけて
screen_rect.bottom
に変化させる - 左端(left)を500msかけて
screen_rect.left
に変化させる - 上橋(top)を500msかけて
screen_rect.top
に変化させる
としています。
動画生成
上の例では動画生成はしていません。そうしたい時はapg.run()
に代えてapg.run_and_record()
を用いるだけです。
if __name__ == "__main__":
apg.run_and_record(main, outfile_options=r"-codec:v gif -lossless 1 -loop 0".split(), outfile="./output.gif")
おわりに
asyncpygame
の機能はManimほど豊富ではありませんが、PyGame上で使えるものなら何でも使えるため潜在能力は高いと思っています(例えばPymunk
のような物理エンジン)。