Pyxelのアニメーションの予約管理に便利なクラスを作った

# はじめに
この記事は[SFC Advent Calendar 2020](https://qiita.com/advent-calendar/2020/sfc)の1日目の記事です。

はじめまして、SFCアドベントカレンダー2020を作成した環境1年の[ウラル](https://twitter.com/barley_ural)です。[2019年の版](https://qiita.com/advent-calendar/2019/sfc)があるとは知らずに2020を作ってしまいました。去年はなかなか賑わっていたようですが、今年は授業日数もギリギリで忙しいので仕方ないですね。~~自分も当日の4時半にこれを書いてまして~~

さて、SFCでは情報基礎2という科目があり、2020年現在はPythonのライブラリ「[Pyxel](https://github.com/kitao/pyxel/blob/master/README.ja.md)」を用いてゲーム制作をする、という授業が行われています。

> ![](https://github.com/kitao/pyxel/raw/master/pyxel/examples/screenshots/02_jump_game.gif)
> ▲[PyxelのREADME](https://github.com/kitao/pyxel/blob/master/README.ja.md)より

Pyxelはレトロゲームの制作に便利なPython用ライブラリです。`pyxel.run(update, draw)`と書くと、フレーム更新時にupdate()というユーザ定義関数が、描画時にdraw()というユーザ定義関数が実行されます。更新と描画が分かれていることで、描画が遅れてもフレームの更新は時間通りに進められる、ということのようです。

そんなPyxelは、描画に便利な関数なども一通り揃えられていて、点、直線、円、矩形、三角形などが描ける他、イメージバンクからドット絵を取り出して使ったり、音楽を作って鳴らすこともできます。色数制限があるためSFC(スーパーファミコン)と同程度とまでは言いませんが、ファミリーコンピュータよりは豊かで自在な表現が可能になっています。

しかしながら、描画までPythonで組んでいるため、やはり複雑なものとなるとプログラムを組むのが非常に難しくなります。特に難しいのがアニメーションで、時刻管理が必要になります。何フレーム後に○○したい、といったときに毎回カウンター変数を用意して時刻を測定する必要があり、また例えば「キーを押したとき奇数回目なら10秒後に雨が降り、偶数回目なら15秒後に雪が降る」のような複雑なイベント予約をする場合、連打されることを考えると複数個の不規則な予約が大量に並ぶことになり、非常に難しいプログラムを書かねばなりません。

そこで、（元々自分用に作ったものですが）「Booker」というクラスを作ったので他の人も使えるように公開します。Bookerは予約するという動詞bookにerを付けたものです。

# Bookerクラス
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Pyxelでイベントの予約管理に便利な自作クラスを公開しました。<br>未来に対して値の変化を予約することができ、また値の変化の仕方を4種類から選べます。複雑なアニメーションをしたい人におすすめ<a href="https://t.co/KiWPf88SsV">https://t.co/KiWPf88SsV</a> <a href="https://t.co/XADI7xDl4J">pic.twitter.com/XADI7xDl4J</a></p>&mdash; ウラル (@barley_ural) <a href="https://twitter.com/barley_ural/status/1333437827351146497?ref_src=twsrc%5Etfw">November 30, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

配布場所
https://github.com/namosuke/pyxel_class_booker

詳しい使い方は[README](https://github.com/namosuke/pyxel_class_booker/blob/main/README.md)をお読みください。
ここでは[booker_example.py](https://github.com/namosuke/pyxel_class_booker/blob/main/booker_example.py)というコードを公開していて、その中に含まれるclass Bookerが当該クラスになります。コピペして使ってください。
book_example.pyを実行すると以下のようになります。
![pyxel-201201-001204.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218499/08fd8470-a2e5-e3f6-303b-72c8e9a4d3ab.gif)
4つの円が一定時間で移動しているのがお分かりでしょうか。実は、Pyxelはこのような規則的な動きも苦手です。状態を逐一保存する何かしらの変数(経過フレーム数など)を必要なだけ作成し、それぞれ増加させなければならないからです。
Bookerクラスでは、このような変数は必要ありません。この例の連続再生では、

```py:booker_example.py
self.flag = 0
Booker.add(self, 'flag', 1, 80, 1)
```

のように、80フレーム後にフラグを1増加させるよう指示しています。
3つ目の引数が増加させたい数を表し、4つ目の引数が何フレーム後か、そして5つ目の引数が何フレームかけて遷移するかを表しています。このほうがずっと簡便です。
経過フレーム数を大量の変数に対して加算する処理は必要ないのでしょうか。必要ありません。
`Booker.do()`をupdate()の最後に一度書くだけで、`Booker.add()`された全ての予約が正しく管理されるのです。

4つの円がそれぞれ異なった移動方法をしているのは何なのでしょうか。これは「イージング」といい、数値の変化の仕方が異なっています。
指定時間で指定値まで一定増加させるのが`linear`、一定加速させるのが`ease in`、一定減速させるのが`ease out`、前半がease inで後半がease outなのが`ease in out`です。
イージングは

```py:booker_example.py
Booker.add(self, 'ball2', 160, 10, 60, 'ease in')
```

といったように、6つ目の変数に文字列で指定します。linearはデフォルト値ですので省略してかまいません。

実は、Pyxelは一定減速などもやろうとすると非常にめんどくさいプログラムが必要になります。直線移動に飽きた人は活用してみてはいかがでしょうか。
イージングはこの他にもいくつか処理方法がありますので、ソースを参考にオプションを増やして自分用にカスタマイズするのもおすすめです。[booker_example.py](https://github.com/namosuke/pyxel_class_booker/blob/main/booker_example.py)の33～48行目がイージング処理部分になります。

ところで、先ほどのGIF画像はしばらくするとマウスが現れますが、ピンクの円がマウスを追いかけているのがわかるでしょうか。ここでは

```py:booker_example.py
self.x = self.y = 0
Booker.add(self, 'x', pyxel.mouse_x, 5, 1)
Booker.add(self, 'y', pyxel.mouse_y, 5, 1)
```

というコードで、5フレーム後に`self.x`と`self.y`にマウスの座標を反映させるようにしています。（遷移に0ではなく1フレームと指定していますが、もし0を指定してしまうと0除算エラーが出るので注意してください。森羅万象、0秒で動くことはできません。）

このようなマウスを後から同じように追従する動きも、普通に組もうとすると非常に難しいことがすぐ想像がつくでしょう。それがBookerクラスを使えばたった3行で実装できてしまうのです。パフォーマンスも悪くないと思います。

未来に対する値の変化という点で、Bookerクラスはアニメーションからイベントまで、幅広い使い方が考えられます。
けっこう苦労して作ったので、ぜひ使ってもらえると嬉しいです。疑問点などあればここのコメントやTwitterからお願いします。機能不足はご自身で対応してくださると助かりますが、プルリクを送って頂けたら内容を元に機能追加するかもしれません。

namosuke/pyxel_class_booker
https://github.com/namosuke/pyxel_class_booker

最後までお読みいただきありがとうございました。