きっポグ

- kitposition's weblog -

【Unity】unityroom 1週間ゲームジャム出品作「こはくちゃんズのファイアごっこ!!」を解剖してみる~その1

 Unityゲーム投稿サイト「unityroom」さん*1が「1週間ゲームジャム」という企画を立ち上げたということで、1週間でゲームを作って出品してみました。しんどかったーw

 今回主催から提示されたお題は「跳ねる」。すぐに浮かんだのが約30年前に発売され、子供の頃遊んだ「ゲーム&ウオッチ*2の名作「ファイア」です。今回出品した作品はこの「ファイア」をモチーフにしてみました。そもそもファイアというのは…と語りだすと止まらないのでまたの機会にして、知ってるおっさんも知らない人もぜひunityroomに行って遊んでみてください。

「こはくちゃんズのファイアごっこ!!」

f:id:kitposition:20170501202326p:plain

 今回からこのブログで何回かに分けて、このゲームで用いたUnityの機能や得られた知見等について紹介しようと思います。

 

 

Unityのアニメーションでカクカクの動きを実現する

 今回のゲームは「ビルから落ちてくるユニティちゃんを受け止めて救急車まで運ぶ」という冷静に考えると妙なシチュエーションなのですが、モチーフの「ゲーム&ウオッチ」の液晶画面を再現して、ユニティちゃんはコマ送りのようなカクカクした動きにしました。


「こはくちゃんズのファイアごっこ!!」プレイ動画

 Unity、特に3Dゲームでは基本的にヌルヌルと滑らかな動きが多く、カクカクしたコマ送りをどうしたら実現できるのか意外と迷うところです(処理が重くてカクカクになることはあるけど)。コマ送りだから物理演算はなしとして、オブジェクトの座標が段階的に加算される、と捉えればスクリプトでループを回してtransformの座標を変化させることも検討できるでしょう。
 しかし、僕は基本的にコードを書くのが嫌いVisual Studioの画面を見ただけで頭が痛くなってくるので、今回はカクカクのアニメーションを作成しました。

 これが実際のゲームのアニメーションをアニメーションビューで表示したものです。

f:id:kitposition:20170501211448p:plain

 このようにアニメーションのカーブを階段状にすると、「次のキーフレームまでは現在の位置を保ち、キーに達した瞬間に別の地点に飛ぶ」動きにできます。
 では、作り方です。

1 キーフレームを設定

 下図はオブジェクトをただ斜め上に動かすだけのアニメーションをカーブ表示したところです。カクカクさせたいところにキーフレームを追加します。

f:id:kitposition:20170501211608p:plain

2 カーブの接線をConstantに設定

 追加されたキーフレームを右クリックするとカーブの接線を切り替えることができます。これは「キーフレームの前後でカーブのラインをどう変化させるか」ということですが、キーフレームを追加した直後は「Clamped Auto(Unity5.4以前は多分Auto)」に設定されていて、要するに「ええ感じ」なカーブになっています。

f:id:kitposition:20170501222900p:plain


 これを上図のようにして「Constant(「一定」の意味)」に切り替えると…

f:id:kitposition:20170501212310p:plain

 おお!あっさり階段状にできました!
 これをカクカクにしたい全てのキーフレームに適用するのですが、キーフレームはドラッグで一括選択して編集できるので作業としては一瞬です。
 なお、上の図では「Both Tangents」つまりキーフレームの両側を設定していますが、右側や左側だけを設定することも可能です。また、設定は他にも色々あって面白い動きを作ることができるので、興味のある方は試してみてください。

 

アニメーションイベントを活用する

1 アニメーションの分析

 今回のゲームでは前掲の動画の後半部分にある通り、ゲームが進むと何人ものユニティちゃんが落ちてきます。制作にあたって注意したのは、「複数のユニティちゃんが同時に地面に達しないようにする」ことです。そうなったらプレイヤーは絶対受け止められず、強制ゲームオーバーになってしまい興醒めですので…。

 さすがに本家のゲーム&ウオッチ「ファイア」は所有していないのでYouTubeのプレイ動画を観て分析した結果、本家では次のような方法を採用しているようです。

  1. 落下キャラが跳ね返る瞬間のみフレームの時間を短くしてズレが生じやすくする
  2. タイミングがヤバくなったら滞空時間(各コマ間の移動時間)を微妙に延長する
  3. テトリスのスリップ?のように、同時に落下してもプレイヤー(キャッチする人)を素早くずらせば両方跳ね返せるようにしてあるのかも(未確認)

 2や3を実装するのはどうも難しいコードを考えなくてはいけない気がして絶対嫌なので却下し、前述のアニメーションビューでキーフレームの位置を変えるだけで実装できる1を採用しました。
 しかし、これだけではタイミングがズレやすくなる(気がする)だけで同時落下を確実に防ぐことはできません。そこで今回のゲームでは、全てのキャラのアニメーションが共通でタイミングも一緒であることを利用し、「そもそも同時落下するタイミングで次のキャラを落とさない」という実装を行いました。

 頭で考えても埒が明かないので、Googleスプレッドシートでアニメーションを図にしてみました。

f:id:kitposition:20170501215445p:plain

 画面上のコマ送りの1コマに2フレーム(跳ね返る瞬間のみ1フレーム)消費しているため、左のビルから右の救急車までは意外と道程が長く、多少適当にキャラを落としてもそうそうダブることはありません。が、一番下の3人目のように、1人目が11フレーム目にいるタイミングで落とすと、1人目が32フレーム目に達した時点で同時に落ちてくることがわかります。このような悪いタイミングが、全体を通して3か所くらいありました。

 つまり、例えば「落下中のいずれかのキャラが11フレーム目にある時は新しいキャラを落とさない」ようにできればいいのですが、Unityで実行中のアニメーションがどのフレームにあるかを外部から取得する方法があるのかよくわかりません。スタートからの経過時間を知ることはできたような気がしますが、ゲームが進むとだんだん速くなるのでフレームへの換算処理が色々めんどくさそうです。そこで、外部からわからないのであればキャラ自身に教えてもらおう、というわけで、こういう時にとても役に立つのが「アニメーションイベント」です。

2 アニメーションイベントの設定

f:id:kitposition:20170501220221p:plain

 アニメーションイベントは、アニメーションビューのタイムライン上の好きな地点に設定し、アニメーションの再生がその地点に達した時に何かのメソッドを発動する機能です。設定方法は上図の通りです。

  1. 設定した位置に再生ヘッドを移動させてイベント追加ボタン(①)を押す
  2. 追加された楔形みたいなやつにメソッドを登録する(②)

(なお、登録できるの多分そのアニメーションが登録されたAnimatorをアタッチしたオブジェクトにくっついているクラスのメソッドだけなので、メソッドは名前だけでも先に書いておく必要があります。また、引数を渡すこともできますが、渡せる引数は型に縛りがあるほか、数も1つだけです。条件にマッチしないメソッドは登録自体ができません。)

 あとは、死ぬほど嫌いなコードをしぶしぶ書くのですが、今回は新しいキャラを生成して落とす役割のオブジェクト(シューター)を別に用意していたので、例えば以下のように生成を許可するbool型の変数でも公開して、そいつに通知してやるだけです。

f:id:kitposition:20170501220945p:plain

 こうすれば、通知のタイミングを設定するためにifうんたらとかyieldなんたらとか書かなくても実際の通知部分を1行書いてやれば事足ります。コードが少なくて嬉しいですね!

 前掲した今回のゲームのタイムラインには多数のイベントが追加されているのがわかると思いますが、ここで説明した落下禁止コマ以外のコマでも何かと処理すべきことは多い(例えば最後のコマではキャラを消すとか)ので、今回はコマを数種類に分けてそれぞれの処理を担うメソッドを作成し、イベントで発火させるようにしています。*3

 

次回予告

 今回はゲームの目玉であるコマ送りをUnityの標準機能のアニメーションを使って実装する方法をご紹介しました。が、1週間という制約が設けられたゲームジャムでは、やはりアセットを上手く活用する必要がありました。実はこのゲームでは「おいおいこんなゲームのどこにそれ使うんだよw」みたいなアセットをいくつか投入していますので、次回(GW中のいつか)はその辺をご紹介する予定です。

*1:運営しているのはあの人気スマホアプリ「太陽人間」のプログラマーで、天才カエルとして知られるnaichiさんです

*2:すごくどうでもいいけどゲーム&ウオッチの「オ」は大きい「オ」です。ググると「もしかしてゲーム&ウォッチ?」とか言われることがありますがGoogleのたわ言に惑わされないように。ちなみに妖怪ウォッチは小さい「ォ」です。

*3:今回説明は割愛しますが、実際はイベントの発火タイミング(Unity内部処理フロー内での発火位置)等との兼ね合いで必ずしも理屈通りの位置に置けば上手くいくわけでもなく、調整を行いました。また長々書いた割には本当に成功しているかわかりません。プレイしてみてもし一緒に落ちてきた場合は見なかったことにしてください。