本日の記事は、Qiita Advent Calendar 2024 - Rust の23日目の記事になります!
昨年は、5分で学ぶ RustとWave Function Collapse (波動関数崩壊アルゴリズム)の旅 という内容で、Advent Calendar 2023の記事を書かせて頂きましたが、5分と言いながらそこそこのボリュームとなり記事制作にもそれなりの時間を使ってしまったこともあり、今回は比較的あっさりめでいきたいと思います。
個人的にはなりますが、2022年や2023年くらいはまだRustという言語は上記の記事でもわかるように、プライベート開発や検証などで用いる程度くらいでしたが、2024年あたりではすっかり業務でも用いるようになりました。
言語の素晴らしさ、パフォーマンスなどは他の方が語ってくれているかと思いますので、今回はそういうことをざっくり割愛しつつ、では、どのように用いたのか。その一部をご紹介できればと思っております。
ワークフロー概要
大まかなプロジェクトの内容としては以下の通りです。
Unityで制作されているキャラクターアニメーション(実装)をウェブで再現する。
ということで、そのためのワークフローとして、
- Unity Recorderでアニメーションをキャプチャしスプライト生成
- スプライトをテクスチャパッキング、CSSアニメーションプロパティを生成
- GitHub Actionsのワークフローで プロジェクトに適した形で整形
- Private GitHub Pageを配信
と、大まかにこのようなフローになります。図で表すと大体このような形です。
図で見るとシンプルには見えますが、意外と登場人物が多く、すべてを説明するとそれなりのボリューム感となりますので、詳細に関しては後日どこかで発表できればと思っております。
今回は、Rust使用箇所に着目し共有できればと思っております。
Rustでテキスチャ作成
図の様に、Unity 2D Animationで作成されたアニメーションをUnity Recorderでキャプチャしスプライト生成するのですが、わざわざRustを使用しなくてもTexturePackerなど用いて作成すれば良いのでは?
と思われる方もいらっしゃるかもしれませんが、1キャラクターで10何種類のアニメーションがあり、これが固定でもなく、キャラクター自体も60体以上存在してしまうと、手動ではとても行える作業じゃありません。
そこで、TexturePackerなどのCLIを用いて自動生成したいのですが、TexturePackerの生成されるCSSファイルは基本 BackGround Positionプロパティということがわかり、
.sprite {display:inline-block; overflow:hidden; background-repeat: no-repeat;background-image:url(anime.png);}
.image_070_0000 {width:256px; height:256px; background-position: -0px -0px}
.image_070_0001 {width:256px; height:256px; background-position: -256px -0px}
.image_070_0002 {width:256px; height:256px; background-position: -512px -0px}
.image_070_0003 {width:256px; height:256px; background-position: -768px -0px}
....
これを手動でCSSアニメーションに置換するわけにもいかないので自動化したいのですが、それならばテクスチャ化五の処理などをRustで行いたいのもあり、テクスチャ化とセットでRustのパッケージなど用いて行ったほうが早いのではないかというところに落ち着きRustでテキスチャ作成をおこないました。
以下の様なプロパティが生成されるのが理想。
@keyframes spriteAnimation {
0.000% { background-position: -0px -0px; }
50.000% { background-position: -240px -0px; }
100.000% { background-position: -440px -0px; }
}
.pic {
width: 240px;
height: 240px;
background-image: url('examples/output/texture_sheet_1.png');
background-size: 540px auto;
animation: spriteAnimation 0.05s steps(1) infinite;
}
色々と探したなか、最終的に落ち着いたのが、「open-texture-packer」になります。
Macで利用する際は、Home Brew Installでインストールして使用することができます。
brew tap webcyou-org/tap
brew install open-texture-packer
インストール完了すると、otpコマンドで実行できます。
otp <any option>
Rustプロジェクトでも、cargo installでプロジェクトに追加することが可能です。
cargo install open_texture_packer
[dependencies]
open_texture_packer = "0.2.1"
Github Actionsで実行する際も、プロジェクト整形用のRustプロジェクトでも利用でき便利です。
更に、かゆいところに手が届く機能として、Unity Recorderで出力する際にFPSの設定も行うことができるのですが、生成するCSSアニメーションのFPSも調整することが可能です。
どういうことかと言うと、FPS 20で出力した際(今回限界まで容量抑えたいのもあって、FPS20でも許容としたので、基本FPS20書き出しです)1秒間のアニメーションに対して20枚のスプライトが出力されるのですが、それと同時に書き出しのパラメータもJSONで出力します。
その値を参照し、fpsオプションコマンドに値を渡せば、生成するCSSアニメーションのFPSに合わせたプロパティが生成されます。
otp <input_directory> [output_directory] --fps 20
なので、この時点で以下の様なHTMLと出力されたテクスチャとCSSを読み込めばCSSアニメーションが再生されます。
<div class="pic"></div>
理想としていた形が、「open-texture-packer」を用いれば実装できてしまいます。
どうしてこんなに理想に沿ったRustパッケージが存在するかというと、こちらは当方で自作パッケージとなっているからです。(ネタバレ)
あまり時間かけず、必要最低限のテクスチャパックのRustクレートとなっており、Packing Algorithmも至ってシンプルで指定したサイズに左上から詰めて行く形となっております。
気が向いたら、その他のPacking Algorithmを入れていこうかと思いますが、その他にも色々とすることもありまして...(言い訳)
とにかく、Unity Recorderで出力されたスプライトとJSONファイルをGitHubレポジトリに追加することによって、GitHub Actionsを経由しCSSアニメーションを再生するファイルが自動生成されるフローは「open-texture-packer」を用いれば行える形となりました。
では、もう一つのRustプロジェクトは何を行っているについて見ていきます。
Rustでプロジェクト側の仕様に沿った整形
上記で、CSSアニメーションファイルは自動生成されるのですが、CSSクラスなどは固定です。
@keyframes spriteAnimation {
0.000% { background-position: -0px -0px; }
50.000% { background-position: -240px -0px; }
100.000% { background-position: -440px -0px; }
}
.pic {
width: 240px;
height: 240px;
background-image: url('examples/output/texture_sheet_1.png');
background-size: 540px auto;
animation: spriteAnimation 0.05s steps(1) infinite;
}
このままだと、すべてがpicクラスとなってしまい1キャラクター1アニメーションしか配置できません。
上記でも記述したように、10何種類のアクションを持ったキャラクターが60種類以上存在するので、それぞれ適したクラス名を付与しなければいけません。
そこでどう対応しているかといいますと、以下の様にアクション毎にディレクトリが切られた形となりますので、こちらのディレクトリ名よりそのアクションを判別する形となっております。
1_wait
2_walk
...
アクション毎にUnityで設定しているSerializeFieldの値や、アニメーションクリップに設定しているメソッドのタイミングなども出力されるので、そちらに含む形でも良かったのですが、先に実装したのでそちらの形となりました。CSSクラスの置換のみならず、JSONデータを参照し、SerializeFieldの値や、アニメーションクリップの設定をプロジェクトで利用できる形のTypeScriptファイルも生成していたり、フロントで利用するディレクトリに自動配置などプロジェクトに必要な構成を作るのがこちらのRustプロジェクトの主な役割となります。
なので、先程の「open-texture-packer」をプロジェクト側のRustプロジェクトにimportし、
[lib]
name = "open_texture_packer"
path = "src/lib.rs"
[[bin]]
name = "otp"
path = "src/main.rs"
一連の自動生成処理を行っていることとなります。
最後に
書いてみたらRust使用箇所は意外にシンプルだなと思ったのですが、途中からファイル移動や圧縮作業はシェルスクリプトでいいやと、結構シェルスクリプトが存在していることに気が付きました(笑)
画像圧縮のため、pngquantを用いて圧縮しているのですが、理想としてはimageoptim CLIの方が圧縮率を比較し劣化が少なく感じますので、そちらで実行したいところではありますが処理完了までの時間が長いのもあって、基本はpngquantなのですがオプションでどちらでも選択できるようにはしていたりもします。
この辺りはRust以外のところになりますので、全貌の詳細はいつか機会があればと思っております。
(wasmを用いたプロジェクトなどもあったりします)
今回のフローでRustを用いた訳ですが、振り返ると「Rust以外の言語でも良いのでは?」と思われる方がもしかしたらいらっしゃるかもしれませんが、(すみません。自分もそう思ったりしました。)違います。
Rustが便利とか、パフォーマンスが良いとか、メモリ安全が保たれているとか、そういうは置いといて、
Rustが好きだから。使っているのです。
以上、良いお年を。
来年も宜しくお願いします。