2014.04.30

Web エンジニアが紹介する Unity 2D、その魅力 (1/2)


はじめまして、次世代システム研究室で Web エンジニアをしております T.Y. と申します。私たちが業務の内外問わずに普段どのようなことに興味・関心をもって取り組んでいるかをご紹介すべくエンジニアブログの執筆をすることになりました。どうぞよろしくお願いいたします。

さて、記念すべき最初のエントリーは『Unity 2D』についてです。Unity というと 3D の CG バリバリなゲームを作るイメージがありますが、昨年の11月にリリースされた最新版(2014/4/29 現在)のバージョン 4.3 から 2D ゲームの開発機能が強化されました。例えば、新しいアセットタイプの『Sprite』をはじめ、2D アニメーション用の物理演算エンジン(Box2D) や編集用のビューなどが導入されています。これまで Unity で 3D のゲームを作っていた人も、今まで Web ばかりでした全然 Unity なんて触ったことありませんという人(私です)も非常に簡単に 2D のカジュアルゲームが作れるようになりましたので、今回はそのポイントについてご紹介できればと思います。

スプライトアニメーション

個人的に Unity 2D でぐっと身近になったと思うのがアニメーションです。3D でアニメーションというと 3D のモデル持ってきてボーンを設定して自然になるように動かして…と身構えてしまうのですが 2D にすると極端に言えばパラパラ漫画でもアニメーションになります。

たとえば、複数の画像ファイルを用意して Unity に読み込ませ、下図のようにまとめてシーンビューにドラッグアンドドロップするだけで新規アニメーションが作られます。

20140430_unity.001

ただこれだと複雑な動きをつけるためにはたくさんの画像を用意しないといけなかったりして大変なケースもあると思います。そこで別のアプローチとしては、パーツ毎に別画像に切り出してしまって個別に動きをつけるというものがあります。このとき、『予め動きをつける部分はレイヤーを分けておいて別画像として書き出す』、『動きによっては可視領域が変わるので輪郭をきっちり書いておく』などしておくのが良いです。

20140430_unity.002

別々の画像として読み込んだ後は再度 Unity 上で一つのスプライトにまとめる必要があります。Unity の Sprite Packer は Pro 用ですが、今回の用途では有志の方が公開してくださっている簡易 Sprite Packer で十分です。スプライトをまとめあげた後は Sprite Editor で各パーツの領域を指定しますが、ある程度は Unity が透過領域をみて勝手にやってくれます。

空の GameObject を作り、その小要素として先ほど分割した各パーツを追加してシーンビュー上でキャラクターを組み立てればアニメーション作成の準備は終わりです。アニメーションビューの左上の赤い丸を押してキーフレームにカーソルを動かして各パーツの位置や角度などを変更すればその操作がキーフレームごとに記録されます。

20140430_unity.003

適当にキーフレームごとにキャラクターにポージングをさせるだけで各キーフレーム間のモーションが補完されてアニメーションが生成されます。このとき、最初のキーフレームをコピーして最後のキーフレームとして入れておくとループしたときの動きが綺麗になりますのでおすすめです。

Mechanim による 2D アニメーション制御

いくつかアニメーションを作成すると複数のアニメーションの切り替えをしたくなります。もちろんスクリプトから個々のアニメーションを指定して実行することもできますが、それだとゲームのロジックとアニメーションの遷移ロジックが混ざってしまってプログラムの見通しが悪くなってしまいます。できれば下図のように、『移動速度 (Spd) が 0 を超えたら歩き出す、10 を超えたら走り始める』『HP が 0 を切ったら死ぬモーションを始める』というようにスクリプト側ではアニメーションを意識せずにロジックを組みたいところです。

20140430_unity.004
Unity では 2D をサポートする以前から Mechanim というアニメーションの状態遷移を記述・管理するためのツールが提供されていました。2D でも Mechanim を使用して状態遷移のルールを記述することができます。

20140430_unity.005

アニメータービューを開くと上図のようなダイアグラムが表示されますので、先に定義したアニメーションを呼び出してどのような条件で遷移するのかを直感的に記述することができます。例えば上記の例では左下の Parameters で『Spd』という float 型の変数を追加し、『bear@idle』と『bear@walk』を接続するコネクタ (Transition) の Conditions で 『Spd Greater 0』という条件を設定しています。一方、攻撃したり (attack) されたり (attacked) するのはキャラクターの任意の状態で発火するため『Any State』につないでいます。

さて、Mechanim で定義したパラメーターはスクリプト側からは (Float 型の場合は) Animator.SetFloat() などとして値を入れます。例えば下のコードでは transform.Translate() でキャラクターを動かしつつ animator.SetFloat() で『Spd』パラメーターにも速度を入れることでアニメーションを変化させています。更に下のブロックでは animator.SetTrigger() を呼んでいますが、この Trigger 型は発火直後に false に変化する真偽値のようなもので今回の『攻撃モーション』のように一回のみアニメーションを呼び出したいときに便利です。

20140430_unity.006

上記のコードではスクリプトからアニメーション遷移を制御していますが、逆にアニメーションの中からスクリプト(で定義されたメソッド)を呼び出すこともできます。例えば下図では animator.SetTrigger() でキャラクターが死ぬアニメーションを呼び出し、さらにアニメーションが終了したタイミングで予めキャラクターにアタッチしておいた die() メソッドを呼び出しています。

20140430_unity.007

die() メソッドの中では Destroy 処理を実行し、アニメーションが再生しきった後に GameObject を破棄するようにしています。

このように Mechanim を使うことでアニメーションとゲームのロジックを分離し、連携させることが可能ですが注意点もいくつかあります。例えばアニメーションからのメソッド呼び出しを多用してしまうと、ゲームロジック側では意図しないところでスクリプトが実行されてしまいます。通常であればコードのチェックだけで気づけることも、アニメーションの一部として実装されているときちんと設計を共有していないとバグの発見に手間取ることが考えられます。

また、アニメーションの実行には一定の時間が必要ですので、それを遮るタイミングでイベントを発火させてしまうとアニメーションが中断したり、逆に上述の die() などアニメーション中で呼び出すはずのメソッドが呼ばれないといった問題が起きてしまいます。

物理演算エンジン Box2D

Unity 4.3 では 2D オブジェクト専用の物理演算エンジンとして Box2D が組み込まれています。これに伴い、Rigidbody や各種 Collider、イベントハンドラなどの API は『Rigidbody 2D』『Circle Collider 2D』『OnCollisionEnter2D』のように全て 『2D』 という接尾辞が付くようになります。2D と従来の 3D 用の物理演算エンジンとは共存することができますが、互いに干渉しないので例えば Circle Collider 2D と Circle Collider それぞれをアタッチした GameObject が衝突しても何もイベントは発火しません。

Collider 2D では Circle Collider 2D, Box Collider 2D, Edge Collider 2D, Polygon Collider 2D と4種類の Collider が提供されています。面白いのは Polygon Collider 2D で例えば下図の地面のようなギザギザしたスプライトを読ませると自動的に(透過領域の)形状に沿った境界線を設定してくれます。

20140430_unity.008

しかしながら複雑な形状の衝突検知が必要でない場合は Edge Collider 2D をアタッチして手動で設定(shift + クリックで頂点の追加・移動)する方が余計な負荷をかけずに良いと思います。

Unity 2D で新たに追加された機能要素についてざっと紹介させていただきましたが、いかがでしたでしょうか。次世代システム研究室ではスマホアプリエンジニアを絶賛募集中ですので、Unity や Cocos2d-x を始めとしたスマホアプリの開発経験をお持ちでかつ弊社で働くことに興味があるという方は 募集一覧 > スマホアプリエンジニア からどしどしご応募していただけると幸いです。

次回は簡単な性能評価の結果について紹介させていただく予定です。