情報学的バナナの皮

だらだらと自作プログラムについての備忘録

Unityで3Dモデルをドット絵風に描画したい<4>

tkg-lag.hatenablog.com

さて暫定最終回やっていきましょう。

下の画像は記事のサムネ用

f:id:tkg_lag:20210404202916p:plain

今回はずばりドット絵における回転です。

前回のラストでも言及しましたが、ドット絵を用いていた昔のゲームにおいてのオブジェクトの回転とは角度ごとに新規でドット絵を描くことを意味します。

それがスーファミを含むいわゆる16bitマシンとなると、回転をハードウェア側で行ってくれるので新規でドット絵を描く必要がなくなりました。

とはいえキャラ自体は奥行きのないドット絵なので、例えば画面内で「右向きの身体を左向きにする」といったアニメーションを動かしたいときにはハードウェア側の回転機能は何ら意味がありません。そういう処理をしたいとなって生まれたのがポリゴンを使ったゲームですからね。

つまり、3Dモデルのドット絵風描画にとって、回転とはポリゴン的ななめらかな回転ではなくドット絵的な手書きならではのガクガクとした回転を意味します。

今回はそれに手を付けていきましょう。

・ドット絵的回転制御…階調化

考え方は簡単です。

「描画時にオブジェクトの角度を階調化し、描画終了後に本来の角度に戻す」、これに尽きます。

直観的に扱いやすいように角度を度数法に直し、それから階調化します。

階調化の角度をn度ずつとしたときに(n*k-n/2)から(n*k+n/2)までの間に属するオブジェクトの角度を(n*k)に直すようにしましょう。

四捨五入改め(n-n/2)捨(n+n/2)入ですね。

この処理を描画の直前に行います。

描画の直前=プレイする人や物理演算の影響が終わった後、と考えられるのでここではLateUpdate内で行います。

その2でやったチラ付き防止のと考え方は同じです。

・ドット絵的回転制御…値戻し

自分がひたすらに行き詰ったのはここです。

値を戻すといってもどうやればいいんだろうと。

素直に考えると、プレイする人による操作や物理演算が始まる前。つまりUpdateやFixedUpdateの前に実行したいです。

しかしUnityにはそこに動作するメソッドがありません。

そこでLateUpdateの後かつ描画自体が終わった後、1フレームの最後の最後に実行されるOnRenderObjectの中で値戻しをすることにしました。

これはカメラのレンダリングが終わったら実行されるメソッドで、確実に描画後のタイミングに実行されます。

しかしここで問題が。

上手く動かないんです。

それも実行する度にバグの挙動が変わってそもそもキャラが回転しなくなったり、動くけども階調化されなかったり、Scene画面ではうまく行ってるのにGame画面ではうまくいかなかったり。

頭の中がハテナでいっぱいでここで3日ほど停滞しました。

・「描画時だけ適応したい処理」の実装方法とは

結果、OnRenderObjectの挙動に原因がありました。

OnRenderObjectはカメラごとに描画されるたびに呼び出されます。

自分はオブジェクトと背景でカメラを分けたいなと思っていたのでプロジェクト内に2つカメラを置いていました。

そのせいで各カメラの描画される順番の前後によってバグが発生していました。

それさえわかってしまえば簡単で、こんなコードにしてみました。

void OnRenderObject() {

 if (((Camera.current.cullingMask & (1 << transform.gameObject.layer))==1)

&&!(Camera.current == UnityEditor.SceneView.lastActiveSceneView.camera)){

  //ここで描画時の回転から本来の回転に戻す

 }

}

Camera.current.cullingMaskはOnRenderObject()を呼び出しているカメラが描画するレイヤ、transform.gameObject.layerはオブジェクトの属するレイヤです。

ビット演算をしてカリングマスクで指定された描画レイヤーの中にオブジェクトのレイヤーが含まれているかを検出します。

そしてもう一つ、実はこのOnRenderObjectはゲーム画面ではなくシーン画面の描画でも呼び出されてしまいます。

なのでシーン画面とゲーム画面を両方出しながらデバッグ作業をしているとバグるんです。シーン画面ではうまく行ってるのに~っていうのはそれが原因でした。

そこで!(Camera.current == UnityEditor.SceneView.lastActiveSceneView.camera)という文でOnRenderObjectを呼び出しているカメラがシーン画面の描画用カメラでないことを確認しています。UnityEditor.SceneView.lastActiveSceneView.cameraはシーン画面描画用カメラそのものらしいです。

このif文によって、オブジェクトの描画が終了した瞬間にのみ処理を行うことができます。

 

これで回転制御処理も完成です!無事回転もコマ落ちした雰囲気が出せました。

youtu.be

 

 

ついでにこれまでの結果も合わせてみてみましょう。

左から、

ただPixelPerfectCameraを適応しただけ

PixelPerfectCameraと座標のドットスナップ、アニメーションのコマ落ちを適応

上にさらに回転制御もした完成形

となっています。

youtu.be

 

f:id:tkg_lag:20210424114811g:plain

ということでドット絵描画シリーズも終わりです。(何か思いついたことがあれば続くかもしれませんが。)

クロックタワーとかプリンスオブペルシャくらいのドット絵感になればいいかなと思って始めたので個人的にはとりあえず満足のいくものができました。

なにも考えずPixelPerfectCameraとこのスクリプトをアタッチすればいい見た目になるのでこれからのゲーム作りが捗ればいいなと思います。

 

様々なサイトなどを参照しながら書いたのでコード全体については公開しませんのでご了承ください。

 

<追記>

カメラが透視投影のとき、うまく動いてないことに気が付いたので多分続きますはい