【入門編】世界よ、これが本当の Unity 講座だ(2)

前書き

 みなさん今日は。Japanリージョンゲーム事業本部 技術・編成部 開発基盤グループのどらです。大変ご無沙汰しております(汗)全二回の構成を予定しておりましたこの記事、ついに第二回をお届けする時がやって参りました。早くして下さいと催促されてしまった訳では決してありませんよ、ほ、ほんとに…(震声汗)

 前回 League of Legends にハマっていると書きましたが、私はやっとブロンゴを脱出し、シルバー5になりました!ちなみに Dino Gnar が大好きです。ナ”ー!! 実際によく使うのは Nasus なんですけど(笑)

 余談はそれくらいにして(笑)前回は Unity でゲームを作る上での概念を重点的に学びました。今回はその概念を思い出しながら実際に簡単なゲームを作る事で、自分の血肉としていきましょう。

 この記事では、下記の点を大事にしていこうと思っています。

  • ただ操作を暗記するのではなく「何故そうなのか?」を理解する

  • 全体像を把握し、その中で自分が今どの様な作業をしているのかを理解する

  • インストールなど他に記事がたくさんある部分は説明せず、この記事の価値に集中する

    • ただし、参考になりそうな記事をリンクでご紹介します

  それでは、頑張って学んでいきましょう!

この記事のゴール

 今回は、Unity ならではの「簡単に 3D のゲームが作れる」という所を活かし、既存アセットのユニティちゃんを使って 3D の障害避けゲームを作る、という所をゴールにします。あまり複雑になりすぎず、ある程度ゲームっぽくできるという事でこれにしました。面白いかどうかは今回は二の次です!(笑)なお、Unity 4 の記事はよくありますが Unity 5 を使った記事は少ない様なので今回は最新の Unity 5 を使用しようと思います。

 ゲームはざっと下記の様なイメージです。なんとなく Flappy Bird っぽいですね(笑)

  • ユニティちゃんが走り続けている

  • 一定時間ごとにユニティちゃんの前に障害物が現れて

    • ユニティちゃんをタップしてジャンプして避けられれば続行

    • ジャンプのタイミングが合わず障害物に衝突してしまったらゲームオーバー

  • 制限時間はなく、衝突してしまうまでずっとゲームは続く

 

 おそらく、実際のスマートフォンのゲームだとするとユニティちゃんをタップするという仕様にしてしまうと指でせっかくのかわいいユニティちゃんが隠れてしまうので微妙だと思いますが、今回は PC 上での再生しか想定していないのでまぁよしとしておきましょう。

ゴールへの道筋

 さて、ではこのゲームを作るにあたって必要になる事を軽くブレークダウンして書き出してみましょう。おそらく、一度作った事のある方でないと予めブレークダウンする事は難しいと思うので、今さっとできなくても気にしないで下さい。後々できる様になるはずです。

  • Unity のインストール

  • このゲームを作るプロジェクトの作成

  • ユニティちゃん 3D モデルの入手

  • ユニティちゃんをシーンに配置する

  • 描画の設定をする

    • カメラの設定

    • ライトの設定

  • ユニティちゃんを走らせる

  • ユニティちゃんをタップしたらジャンプさせる

  • 障害物を動かす

  • 障害物がユニティちゃんに衝突したらゲームオーバーにする

 ブレークダウンしてみると、何となく、どうやって作ればよいか見えてきた気がしませんか? ちょっと行ける気になってきた所で早速作ってみましょう!

Unity のインストール

 さて、では早速 Unity のインストールですが、これは色々な所で解説されていますのであえてこの記事ではあまり深く解説しない事にします。こちらの URL などを参考にインストールしてみて下さい。インストールはインストーラーの指示に従うだけですので特に難しい所はないかと思います。

このゲームを作るプロジェクトの作成

 Unity はインストールできたとして、次はこのゲームを作るためのプロジェクトを作成してみましょう。ゲーム内で使用されるテクスチャやスクリプト(プログラム)をひとまとめにしたものをプロジェクトと呼んでいます。基本的には1ゲーム1プロジェクトと考えておけばよいでしょう。なお、テクスチャなどの用語が分からない方は、この記事の第一弾を見直してみて下さいね。

 Unity を起動すると、下記の様なダイアログが表示されます。「New Project」を押してプロジェクト作成の手順に進みます。

 Project Name は何でもよいのですが、今回はゲームに合わせて「RunUnityChan」を指定します。Location はプロジェクトを作成する場所ですが、どこでも大丈夫です。左下に「3D 2D」という設定がありますが、今回は 3D メインのゲームなので 3D に設定にしておきます。

 新規にプロジェクトを作ると RunUnityChan プロジェクトフォルダ配下に、下記の様なフォルダが自動的に作成されます。

  • Assets
  • Library
  • ProjectSettings
  • Temp

 ゲームで使うテクスチャやスクリプトなどは Assets 配下に配置します。Asset、アセットという言葉はよく出てきますが、テクスチャ、モデルやスクリプトなどゲームに使用するデータを総称してそう呼んでいます。Assets 以下のフォルダは自由に構成できますが、第一階層は自プロジェクトの名前にしておく事をオススメします。何故かと言うと、アセットストアなどからダウンロードしてきたアセットもここに配置される事になるので、自プロジェクトのファイルを簡単に判別できる様にしておいた方が便利だからです。例えば、今回の場合は下記の様なフォルダ構成にしておくとよいでしょう。

  • RunUnityChan/Assets/RunUnityChan/

 ここに、アセットストアからダウンロードしてきた何かが入ってくると、下記の様な構成になるという事です。

  • RunUnityChan/Assets/RunUnityChan/

  • RunUnityChan/Assets/SomeAssets/

 これで少し見やすく整理できそうですね。少し advenced な内容ですが、プロジェクトを git 管理する場合には .gitignore を追加すると思いますが、こちらのサイトが参考になります。

 

 Mac OS X で使う場合には、Unity, OSX, Xamarin Studio をオンにした下記の様な設定が参考になります。Xamarin Studio については、後述しますが、簡単に言うとプログラムを編集しやすくする統合開発環境(IDE とも呼ばれる)です。git 管理する方は .gitignore ファイルも置いておくとよいでしょう。

 さて、プロジェクトの作成ができたら、この様な画面が表示されたはずです。これが Unity のエディタとしての画面です。エディタについては第一弾でも少し触れましたが、レベルエディタとも呼ばれる、アセットを作成・管理する、ゲームを作るメインの画面です。

 画面の簡単な説明や操作方法などは、こちらを参考にしてみて下さい。

 

 少しだけ、このあたりの記事に加えて説明しておきたい事を書いておきます。シーンについての解説は第一弾の記事でも少し書きましたが、ゲームで言う所の「面」に近いものだと思っておけばよいでしょう。1面、2面などの面、小さな世界、ステージです。読み込みの単位になりますので、例えば「1面を読み込む」、「2面を読み込む」などのプログラムを書く事になります。

 シーンビューはこのシーンの内容を確認するためのもので、ここに様々な Component を持った GameObject を配置していく事で世界を構築していく、というイメージです。シーンビューはあくまで製作者の視点で見た画面ですので、実際にゲームがどういう画面になるのかはゲームビューの方で確認する事になります。シーンビューとゲームビューでカメラが違うためですが、この辺りは後ほどもう少し説明します。

 とりあえず現段階では、「インターフェイスについての学習」をご覧頂いて、シーンビューのカメラの移動、拡大・縮小、回転、オブジェクトの移動といった基本的な操作はできる様になっておいて下さい。

ユニティちゃん 3D モデルの入手

 さて、下準備として次はユニティちゃんの 3D モデルを入手しておきましょう。下記サイトから、「SDユニティちゃん 3Dモデルデータ」をダウンロードしておきます。なんでこれか、って? 一番かわいかったからです(笑)

 〜.unitypackage というファイルがダウンロードできたと思います。これをダブルクリックすると下記の様なインポート画面が開きます。

 本来はここで必要ないファイルなどを省く事もできるのですが、とりあえず今回は全てインポートしてしまっても特に害はないので全てチェックが入った状態で右下の Import ボタンを押してプロジェクトにインポートします。インポートすると Assets 以下にファイルがコピーされ、プロジェクトで利用可能になります。

 Assets フォルダ以下を見ると UnityChan フォルダが作成され、その下に各種のアセットがある事が分かると思います。この様に、.unitypackage で配布されているアセットは最上位の階層に独自の名前のフォルダを作っている事が多いので、先ほどのフォルダ構成にした方が分かりやすい、という理由がお分かり頂けると思います。

ユニティちゃんをシーンに配置する

 ではいよいよシーンにユニティちゃんを配置してみましょう。配置の前にそもそもシーンを作成しなくてはいけないのでは?という疑問があるかもしれませんが、実はプロジェクトを新規に作成した場合には新規のシーンも作成されていて、それがエディタ上に表示されている状態になります(ただし保存がされていないのでファイルはまだない)。なので最初はシーンを新規に作る必要はありません。

 この自動的に作成されたシーンには Main Camera と Directional Light という二つの GameObject がはじめから配置されています。これはそういう Unity の仕様ですが、何かモデルを置いてもとりあえず表示はされる様に配置されているのでしょう。カメラがなければそもそも何をどう映せばよいのか分かりませんし、ライトがなければ光が当たらないので何も見えない状態になってしまうからです。

 さて、ユニティちゃんをシーンに配置するには、下記のファイルをシーンビューか、もしくはヒエラルキービューにドラッグアンドドロップして下さい。

  • Assets/UnityChan/SD_unitychan/Prefabs/SD_unitychan_humanoid

 よく見るとこのファイルは prefab という拡張子になっているのが分かると思います。第一弾でも触れましたが、これは GameObject が雛形化されたファイルという事です。Prefab の中には様々な GameObject や Component が含まれていて、これをシーン上に配置する事で同じ構造の GameObject を簡単に生成できるという訳です。この Prefab の場合、SD ユニティちゃんを作った人が作り上げた形・構造のユニティちゃんがコピーされてそのままシーン上に出てきている、というイメージです。

 また、SD_unitychan_generic という Prefab もあると思いますが、両者の間には Humanoid なのか Generic なのかというアニメーションシステムの違いがあります。詳しくはこちらの記事などを参考にしてみて下さい。

 参考 URL として挙げたものの、ちょっと理解し辛いですね…(笑)一言で言うと、Generic はより汎用的なアニメーションシステム、Humanoid はアニメーションを人間の型に限定する事で動きの差し替えなどを簡単にしたシステム、というイメージです。例えば A というアニメーションを B という Humanoid、C という Humanoid に適用しやすい、という事です。今回はとりあえず Humanoid を使っておきましょう。

 なお、3D のアニメーションについてはこちらも参考になります。

 実際にドラッグアンドドロップするとこの様な表示になると思います。

 とりあえず表示ができるとだいぶ嬉しいものがありますね!(笑)何となく画面が想像できる様になってきたのではないでしょうか。

 今回はユニティちゃんは画面中心にいる想定ですので、座標は (0, 0, 0) に設定します。設定は、位置、回転などの情報を保持する Transform コンポーネントの値を変更する形になります。Position が位置になりますので、ここの X, Y, Z をそれぞれ 0 にしておきましょう。

 変更するには、ヒエラルキービュー上で先ほどの「SD_unitychan_humanoid」を選択し、インスペクタビューで値を書き換えます。

 また、この状態だと今回のゲームに必要のないコンポーネントがユニティちゃんに付いていますので、以下のコンポーネントは削除しておきましょう(これがあると余計なものが画面に表示されてしまうが、削除しても必要な機能が失われる訳ではない)。

  • Face Update(顔のアニメーションの切り替えを可能にしている)

  • Idle Changer(体全体のアニメーションの切り替えを可能にしている)

  • Random Wind(そよ風が吹いているかの様なアニメーションを可能にしている)

 コンポーネントを削除するには、削除したいコンポーネントの右上の歯車アイコンをクリックしてメニューを出し、「Remove Component」を選択すれば OK です。

描画の設定-カメラの設定

 さて次はカメラを設定してみましょう。ここで言うカメラとは、実際にゲームを実行した時に描画に利用されるもので、Editor 上でシーンビューを表示しているものとは別になります(シーンビューで自分の視点を移動してもゲームの描画は変わりません)。カメラの位置がちょっと微妙なので、以下の値に調整してみて下さい。実際には手で動かしたりして絵面のいい位置に調節する事になると思いますが、今回は数値の通り設定してみて下さい。

  • Transform / Position = (1.5, 1.2, 1.5)

  • Transform / Rotation = (25, 230, 0)

 色々変更を加えたので、そろそろ保存をしておきたくなってきましたね。「File」メニューから「Save Scene」を選択するか、「コマンド + S」のショートカットを押してみましょう。まだシーンファイルがないので、どこに保存するかを聞くダイアログが表示されたと思います。今回は下記のディレクトリに保存しましょう(ディレクトリもないので作成しておいて下さい)。

  • Assets/RunUnityChan/Scenes

 シーンファイル名はとりあえず Main にしておきます。RunUnityChan フォルダを作るのは先ほど説明した通り、プロジェクト名のフォルダをトップ階層に作っておいた方が色々なアセットを入れた時に見やすいからです。また、Scenes フォルダを作っておくのは他にテクスチャやスクリプトファイルを置きたくなった時に種別で分けられる様にしておきたいからです。

 Scenes や Main といった名前にするのは、Unity で扱うものはアッパーキャメルケースの命名になっているものが多く、Unity で使われるプログラム言語の C# もアッパーキャメルケースが標準的なためです。これでなくてはいけないという訳ではないですが、こういった所のルールは予めプロジェクト開始時に定義しておき、チーム内で認識を合わせておくと様々な命名ルールが混在するカオスを避ける事ができるでしょう。

 保存の仕方はお分り頂けたと思いますので、以降は適宜保存しながら作業して下さい。

 

描画の設定-ライトの設定

 次に、ライトの設定をしてみましょう。本来は自分でライトのコンポーネントを調節していく事になりますが、今回の場合はユニティちゃんのアセットに設定がされた Prefab があるのでそれを使ってみましょう。

 既に配置されている「Directional Light」は必要ないので削除し、以下の Prefab をヒエラルキービューかシーンビューに配置してみましょう。

  • Assets/UnityChan/Prefabs/Directional light for UnityChan

 このままだと若干光が強すぎる様に感じます。なので、インスペクタの Light の欄の Intensity を 0.9 と設定してみて下さい。少し光が抑えられ、いい感じに表示されていると思います。

 ここで、エディタ画面上部の再生ボタンを押してみて下さい。Scene ビューが Game ビューに切り替わり、ユニティちゃんが映し出されると思います。また、アイドル(待機)状態のアニメーションが行われていて、少しだけ動いているかと思います。

 ここで、裏ではどの様な事が行われているのかについて少し理解しておきましょう。第一弾の時に少し触れましたが、Unity では可変フレームのリアルタイム描画が行われています。再生ボタンを押すと、ざっくりと下記の様な処理が毎秒数十回繰り返し行われ、結果として上記の様にユニティちゃんがゆらゆら動いている様に描画される事になります。

  1. 内部物理ロジックの実行(物理法則に則って Transform が更新される)

  2. 各 Component の実行(ゲームロジックの実行)

  3. 内部アニメーションロジックの実行

  4. 描画の実行

  ※ものすごく簡略化していますので詳しくは公式サイトの情報などをご覧下さい。

 例えば仮に腕を上げるという動作があったとして、最初は腕が垂直になっているが、5度上げ、さらに5度上げ、という動作を上記のプロセスで繰り返し行う事によって、徐々に腕が上がっていく様なアニメーションになって見えるといったイメージです。

 上記の様なループを一般的にメインループと呼びます。先ほどの再生ボタンは、「メインループを回しはじめてね!」という意味だったんですね。

ユニティちゃんを走らせる

 では次にユニティちゃんを走らせてみましょう。本来であればアニメーション自体(腕をこう動かして、足をこう動かして…といったデータ、AnimationClip と呼ぶ)を作成しなければならずとても大変な作業となりますが、今回はユニティちゃんにそもそもそういうデータが含まれていますので、それを呼び出すだけです。このあたりが既存のアセットが豊富にある Unity の強みでもあると言えるでしょう。

 アニメーションをさせるには、下記の様な方法があります。

  1. Animation コンポーネントを使って制御する

  2. Animator コンポーネントを使って制御する

  3. スクリプトで全て自前で制御する

 名前が似ているので違いが理解し辛いですが、1はどういった状況でどういったアニメーションをさせるのかの制御をプログラムで自分で管理していく、シンプルな仕組みと言えます。2はステートマシンという仕組みを使ってある程度 GUI で管理できます。3はいつどういうアニメーションをするのか、さらには各フレームでの変化も自分で定義していく事になりますのでよほどの事情がない限りはやらないでしょう。

 ステートマシンという言葉がはじめてできましたので軽く触れておきます。ステートマシンとは、状態の移り変わりとその条件を定義するものです。A 状態の時に α という条件が成立すると B という状態になる、といった定義をします。

 この A の状態に x というアニメーションを割り当て、B という状態に y というアニメーションを設定すると、x アニメーションが行われている時に α の条件が成立すると y というアニメーションに切り替わる、という様にアニメーションもステートマシンを使って管理する事ができます。Animator を使うとこれらを視覚的に設定する事ができるので、アニメーションの全体像を把握しながら設定していく事が可能です。

 今回はせっかくなので Animator でステートマシンを使ってアニメーションの管理をしてみましょう。ざっと、下記の様な作業をする事になります。

  • ステートマシンのデータとなる AnimatorController を作成する

  • AnimatorController を対象のモデルの Animator に設定する

  • ステートマシンを定義する

    • ステートを作成する

    • ステートに AnimationClip を設定する

 それでは早速ですが、ステートマシンを編集するためにステートマシンのデータを表す AnimatorController を作成します。

  • Assets/RunUnityChan/Animations

 上記の様なフォルダを作り、その下で右クリックし、AnimatorController を選択します。名前は「RunUnityChanController」としておきます。

 次に、このステートマシンをユニティちゃんの Animator に適用します。ヒエラルキービューからユニティちゃんを選択し、インスペクタの Animator の欄にある Controller に、今作成した「RunUnityChanController」をドラッグ&ドロップして下さい。

 設定できると、上記な状態になっているはずです。この様に、GameObject が持つコンポーネントには別のコンポーネントやアセットの参照を持つ事ができ、それをインスペクタ上から設定する事ができます。こうする事でコンポーネントはそのコンポーネントの動作に必要になる情報・リソースを得る事ができる様になります。

 ではステートマシンを編集するために、「RunUnityChanController」をダブルクリックして下さい。次の様なビューが表示されたはずです。

 ここで青色の四角で表示されている Entry などが先ほど説明した「状態」になります。今回は、下記の様な設定をしたいと思います。

  • 走っている状態を定義する

  • 走っている状態の時は走っているアニメーションを再生する

 先ずは走っている状態を定義するため、右のビューのどこかを右クリックして「CreateState」から「Empty」を選択しましょう。「New State」という状態が作成されたと思います。ちなみに、Entry から矢印が New State に向かって出ていると思いますが、これが状態変化を意味します。Entry という状態はやや特殊で、再生がはじまった時はここから始まるというもので、今回はこの遷移には条件が指定されていないので再生がはじまると自動的に New State に変化しますよ、という設定になっている事になります。この様なデフォルトの状態はオレンジ色で表示されます。

 この状態を選択し、インスペクタから名前を「Running」に変更しておきます。

 その下に Motion という欄があると思いますが、ここがその状態の時に再生される AnimationClip を指定する欄になりますので、下記の Animation Clip を指定しておきます。

  • Assets/UnityChan/SD_unitychan/Animations/SD_unitychan_motion_humanoid/Running@loop

 設定ができたら、この様な状態になっていると思います。

 これで、ステートマシンの設定ができました。動くかどうか、再生してみましょうか!再生ボタンをポチッ!としてみて下さい。

 おぉ!ユニティちゃんがかわいらしくちょこんちょこんと走っていますね!こうやって動くとちょっとワクワクしてきますよね(笑)この調子で頑張りましょう!

ちなみに、”'SD_unitychan_humanoid' AnimationEvent 'OnCallChangeFace' has no receiver! Are you missing a component?” というエラーメッセージがコンソールにだだっと表示されているかと思いますが、とりあえずこれは無視しておきます。

 何が起きているかと言うと、AnimationClip にはある時点になったら「こんなタイミングがきたよ!」とプログラムに通知する仕組み(イベントという)があるのですが、これを受け取って処理する設定がされていないのでエラーになっているという訳です。対処としては、1)イベントを削除する、2)イベントを受け取る処理を書く、があり得ますが1は少し作業が煩雑なので後ほど2で対処します。

 

ユニティちゃんをタップしたらジャンプさせる

 では次はユニティちゃんをタップしたらジャンプさせてみましょう。ざっと、やる事は下記になります。

  • ステートマシンにジャンプしている状態を定義する

  • ジャンプしている状態にジャンプのアニメーションを設定する

  • 走っている状態からジャンプ状態に遷移する条件を設定する

  • ユニティちゃんがタップされたらそれをステートマシンに通知する

 まずは先ほどの要領でステートマシンにジャンプしている状態を定義してみましょう。今回はアニメーションの都合(ジャンプのアニメーションがループしかない、設定を変えるのは煩雑なのでやりたくない)により、下記の二つのステートを合わせてジャンプを表現します。

  • ステート:ToTop

    • Animation Clip:JumpToTop

  • ステート:ToGround

    • Animation Clip:TopToGround

 これらをステートマシンに設定してみて下さい。Animation Clip は先ほどの「Running@loop」と同じ場所にあります。設定すると、この様になっていると思います。

 さて、では次に Running ステートから ToTop に遷移する定義をします。Running を右クリックし、「Make Transition」を選択します。するとなにやら矢印がカーソル位置まで伸びる様になりますので、ToTop をクリックします。すると下記の様に、遷移が定義されます。

 次に、どういう条件が揃うとこの遷移が行われるのか?という定義をする必要がありますが、それには下記の様な手順を踏みます。

  • ステートマシンに変数を作成する

  • その変数がどうなると遷移が行われるのかの定義を行う

 まずは変数の作成ですが、ビューの左側に Parameters というタブがありますので、それをクリックします。すると変数一覧が表示されますので、「+」ボタンをクリックします。Float, Bool, Int, Trigger の型の中からどれかを選びますが、今回は Trigger にし、名前は「Jump」としておきましょう。ちなみに Trigger は bool の様なものですが、true にしても次のフレームでは勝手に false に戻る様なイメージで、ゲーム内で何かが起きた時に遷移したい、という様な場合に使えます。今回はタップされたら即ジャンプして欲しいので、Trigger が適切かと思います。

 設定ができると下図の様になっているはずです。

 次にこの変数を遷移条件に設定します。先ほどの矢印を選択すると、インスペクタ上に Conditions という欄があると思います。その欄の+ボタンをタップすると、Jump が設定されたと思います。これで OK です。

 ここまでで Running 状態から、Jump 変数がオンになった時に、ToTop 状態になるというところまではできましたが、このままだとユニティちゃんが浮いた状態のまま戻ってこなくなってしまうので、ToTop から ToGround、ToGround から Running にも遷移を追加しましょう。これらの遷移には特に条件は設定しなくて OK です。ジャンプしたらそのまま着地してまた走り出してくれればよいからです。設定ができると下図の様になっているはずです。

 さぁ!これで準備はできたはずです。再生ボタンをポチッとしてみましょう! Unity は再生中に各種パラメータを動的に変更する事ができますので、先ほどステートマシンに設定した Jump トリガーもゲーム実行中にオンにする事ができます。ゲーム実行中に、Parameters タブを押して Jump の右にある◯を押してみて下さい。ユニティちゃんがジャンプ、着地、また走り出すはずです(やや動きがぎこちないのは無視)。

 このままだと着地した際の動きがややぎこちないので、もう少しスムースに繋がる様にしたいですよね。Animator はこの辺りの設定も行える様になっていて、アニメーションの重なりを制御する事ができます。ToGround から Running へのトランジションを選択するとインスペクタに下図の様なビューが表示されると思いますが、Settings の部分がそれに該当します。いつ時点から動きがかさなるのか、どれほど重なっているのかを指定しますが、結構感覚的な部分が多いのでスムースに見える様に適当に設定して下さい(笑)私は下図の設定くらいが適切かなと思いました。

 もしジャンプの開始時の動きがぎこちなく見えてしまう場合は同様に Running から ToTop へのトランジションの設定も変えてみて下さい!今回はそこは省略します。

 ここまでで状態遷移の下地は揃いました。後はタッチされたら Jump トリガーをオンにするだけです。頑張っていきましょう! タッチされたら Jump トリガーをオンにする、という動作を実現するためには、ざっと下記の事をする必要があります。

  • コライダをユニティちゃんに設定する

  • 独自のコンポーネントでタッチされたら Jump トリガーを設定するプログラムを書く

 

 ではまずはコライダの設定ですが、コライダについて少しおさらいしておきます。コライダは物体が他の物体と衝突する範囲を定義するもので、必ずしも物体の見栄え上の形と同じ形をしている訳ではありません。スクリーンをタッチした時に行う逆透視変換の過程で、レイを飛ばした時にレイに当たるかどうかの判定にも利用されます。ものすごく簡単に言うとコライダがないと空気の様な存在で触れる事ができずタップした事にならない、というイメージです。詳しくは第一弾を読み返してみて下さい。

 コライダにも色々種類があります。

  • Box Collier(立方体)

  • Sphere Collider(球体)

  • Capsule Collider(薬のカプセルの様な形)

等々

 Mesh Collider というメッシュの形をそのままコライダにできるコンポーネントもあるのですが、処理が重くなるので Box Collider などの原始的な形で処理が軽いコライダで簡略化したりします。今回の場合、そこまで正確な判定が必要な訳でもないので Box Collider を使ってみます。

 コライダを追加するには、ヒエラルキービューから下記の GameObject を選択して下さい。追加する GameObject はジャンプした時に追従する様なもので、体の重心になっていそうなところという事で今回は下記にしました(ユニティちゃんの最上位の GameObject はジャンプしても位置は変わらないので)。

  • SD_unitychan_humanoid/Character1_Reference/Character1_Hips/Locator_Hips

 選択したら、インスペクタから「Add Component」を選択し、「Physics」から「Box Collider」を選択します。コライダが追加され、シーンビューで緑の枠が表示されていると思います。これが、このコライダが他のコライダと衝突する範囲になります。

 このままだと大分ユニティちゃんよりも大きくなってしまっていますので、以下の設定にして調整してみましょう。

  • Center = (0, 0.2, 0)

  • Size = (0.3, 1.2, 0.25)

 それっぽい形になりましたね!(笑)だいたい同じくらいの形になっていればとりあえず OK です。

 コライダはこれで OK として、次は実際にタップされたらステートマシンの Jump 変数をトリガーするための設定です。この設定はプログラムで行う必要があり、独自のコンポーネントを作ってその中で記述する事になります。

 と、その前にやっておくべき大切な事があります。プログラムは、書く前に「どういった構造にするのか」を考えておく事が極めて重要です。すなわち、

  • やりたい事の本質を見極める

  • 各要素の責任範囲を明確にする

 というプロセスを経る事により、

  • 短い期間で

  • 分かりやすく

  • 変更に強く

  • バグを生みにくい

 プログラムを書く事ができるからです。よく、「あっ こうしておけばよかった…」「何でこんな形にしてしまったんだろう…」という事がありますよね。そういった事も、まずはどういう構造にするのかを少しでも考えるだけで違ってくるはずです。

 たいそうなものではないですが、今回はやりたい事を考え、下図の形にする事にしました。

 四角で表現されているものは独自コンポーネント、その下の説明がそのコンポーネントの責任範囲で、どこまでを制御してどこからは他に任せるのか、という意味合いです。矢印は参照関係で、この場合は RunUnityChanController が UnityChanController と ObstacleController の参照を持っている、という関係です。こういった場合には、より大きな概念がその下の概念を参照し、下の概念が上の概念を参照しない様に配慮すると疎結合な関係を築きやすくなります。

 3つコンポーネントがありますが、先ずは UnityChanController から作ってみます。ヒエラルキービューでユニティちゃんを選択し、インスペクタのパネル下部に表示されている「Add Component」を押します。どのコンポーネントを追加するのか、というポップアップが表示されますので、最下部にある「New Script」を選択します。すると名前と言語を指定する画面になりますので、名前を「UnityChanController」に、言語は「C Sharp」を選択し、「Create and Add」で作成しておきます。

 ここまで操作すると、空のコンポーネントが作成され、それがユニティちゃんにアタッチされた状態になっています。今からやる事は二つあります。

  • スクリプトファイルがデフォルトの場所に置かれているので移動する

  • ユニティちゃんがタップされたら Jump トリガーを引くための記述をする

 今作成したスクリプトは Assets 直下に置かれています。これはゲーム特有のスクリプトなので、RunUnityChan 以下に移動しておくのがよいでしょう。そこで、下記の様なフォルダを作り、その下に移動しておいて下さい。

  • Assets/RunUnityChan/Scripts

 移動したら、UnityChanController をダブルクリックして下さい。MonoDevelop というアプリケーションが別途立ち上がったと思います。これはスクリプトを効率的に編集するためのアプリケーションです。

 開くとこの様な表示がされていると思います。ここに記述を加える事でそのコンポーネントに独自の振る舞いを持たせる事ができる様になります。今回は下記の様な記述を行います。

using UnityEngine;
using System.Collections;
 
public class UnityChanController : MonoBehaviour
{
   public void OnTapped()
   {
       this.GetComponent<Animator>().SetTrigger("Jump");
   }
 
   public void OnCallChangeFace()
   {
   }
}

 C# 自体の構文などが分からない人は今回は言語の解説はスコープ外として省略しますので先ずは書籍などで学んでおいて下さいね。ここで、OnTapped メソッドは後ほど RunUnityChanController が外部から呼び出すためのメソッドで、タップされたのでジャンプの動作を行うために Animator の “Jump” Trigger をセットしています。つまり、タップされると Animator のステートが ToTop に移行し、ジャンプのアニメーションが再生されます。OnCallChangeFace メソッドは先ほど出ていたエラーに対処するためだけに書いているメソッドなので中身は何もありません。

 次に、RunUnityChanController を作成します。これはゲーム全体のフローの制御を行うものになりますので、ユニティちゃんにアタッチするのはやや不自然です(ユニティちゃんの責任範囲を超えている)。そこで、別途ゲーム全体のコントロールを行う空の GameObject を作成してそこにアタッチする事にします。


 ヒエラルキービューを右クリックし、「Create Empty」を選択します。すると、GameObject という名前の空の GameObject ができたと思いますので、名前を RunUnityChanController に変更します。後は先ほどと同じ手順で、新しいコンポーネントとして「RunUnityChanController」を作成し、適切なフォルダに移動し、下記のスクリプトを追記します。

using UnityEngine;

public class RunUnityChanController : MonoBehaviour
{
    [SerializeField]
    private UnityChanController unityChanController;

    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit raycastHit;
            if (Physics.Raycast(ray, out raycastHit))
            {
                if (raycastHit.transform.gameObject.tag.Contains("UnityChan"))
                {
                    this.unityChanController.OnTapped();
                }
            }
        }
    }
}

 ここで、unityChanController には private 修飾子を付けています。これは、外部から変更される事を望んでいない(そうされた場合に動作を保証できない、そういう場合を考えたくない)ためです。ではどうやってこの変数に値を設定するかと言うと、インスペクタから設定します。[SerializeField] という記述があると思いますが、この [xxx] は「属性」と呼ばれ、変数やクラスなどに何らかの設定する時に使います。今回はコンポーネントの変数をインスペクタから設定可能にし、その設定を保存できる様にするために SerializeField という属性を指定しています。また、SerializeField の属性が指定されている場合、private であったとしてもインスペクタ上から指定可能になります。つまり、プログラムでは外部からは変更できないが、インスペクタからは変更できる、という状態になります。

 ちなみに Update メソッドには修飾子がついていません。アクセス修飾子を省略すると private の扱いとなり通常は外部からは呼べないのですが、Update などの Unity がメインループから呼ぶメソッドは例外的に private であっても呼ばれます。

 

 細かいクラスなどの説明はスクリプトリファレンスなどを見て頂くとして、おおまかに全体の流れとしては、

  • Update がメインループから毎フレーム呼ばれる

  • その中でレイキャストを行い、レイが何かにヒットしているかどうかを調べる

  • ヒットした GameObject が UnityChan というタグを持っていたら UnityChanController にタップされたよ、と通知する

となっています。RunUnityChanController はユニティちゃんがタップされたかどうかの判断を行い、タップされていたら UnityChanController にその通知を送るという所までを行って、実際にユニティちゃんがジャンプするという部分は UnityChanController の OnTapped の中に任せる事で責任範囲を超えない様に配慮しています。

 

 さて、今からやる事が二つあります。

  • RunUnityChanController に UnityChanController の参照を持たせる

  • ユニティちゃんに設定したコライダのある GameObject にタグを設定する

 UnityChanController に [SerializeField] の設定をしましたので実際に参照を持たせてあげましょう。ヒエラルキービューから RunUnityChanController を選択してインスペクタを見ると、下図の様に Unity Chan Controller という欄が追加されているのが分かります。ここに、参照を持たせます。

ヒエラルキービューからユニティちゃんの「SD_unitychan_humanoid」をインスペクタ上の Unity Chan Controller にドラッグ&ドロップすると、下図の様に参照を設定する事ができるはずです。

 次に、ユニティちゃんに設定したコライダを持つ GameObject にタグを設定します。GameObject にはタグと呼ばれる「その GameObject がどういう種類のものなのか」を設定する機能があり、今回の場合はレイがヒットした GameObject が UnityChan というタグを持っていればそれはユニティちゃんをタップしたという事である、という判断にしているので、タグを適切に設定しておかなければなりません。

 先ほどコライダを設定した GameObject である「Locator_Hips」をヒエラルキービューで選択し、インスペクタ上部にある Tag から「Add Tag...」を選択します。「Tags」の欄から「+」を選択し、UnityChan を入力し、追加します。

 追加したら、もう一度ヒエラルキービューから「Locator_Hips」を選択し、Tag に UnityChan を設定しましょう。

 これで一通りの設定ができましたので、実行してみましょう! ちゃんとクリックしたらジャンプしますね!d(^_^ )

 

障害物を動かす

 さて。ゲーム作りも大詰めになってきました。次に障害物を動かすという所を作ってみましょう。ルール上、障害物は際限なく何回も現れる事になります。それを予めシーン上に全て配置しておくというのは無理があります。プログラムで毎回障害物を生成する事もできますが、諸々のコンポーネントの設定をしなければならずそれはそれで手間です。

 そんな時に便利なのが Prefab の機能です。Prefab は GameObject やコンポーネントの状態をひっくるめて保存しておき、コピーを容易に作成できるのでまさに今回の用途にピッタリと言えます。

 という訳でこのセクションでは、

  • 障害物の Prefab を作る

    • GameObject を用意する

    • 動かすためのスクリプトを書く

  • RunUnityChanController に Prefab を保持させる

  • RunUnityChanController で Prefab からコピーを作ってシーンに配置する


 といったあたりを作っていこうと思います。まずは障害物の Prefab 作りですが、今回は単純な立方体にします。ヒエラルキービュー上から「3D Object」→「Cube」を選択します。

 選択すると、シーンビュー上に立方体が表示されたと思います。ユニティちゃんが隠れてしまうほど大きいですね(笑)ちょっと大きさが微妙なので下記の様に調整してみて下さい。

  • Position = (0, 0.05, 0)

  • Scale = (0.5, 0.1, 0.1)

 この様に調整すると、下図の様な大きさになっていると思います。これくらいならジャンプで飛び越えられそうですね。

 ついでにヒエラルキービュー上から名前を Obstacle にしておきましょう。また、この GameObject は衝突判定をする必要があります(ユニティちゃんに衝突したらゲームオーバーなので)。衝突判定をしたい場合は Collider と一緒に Rigidbody コンポーネントもアタッチしておく必要がありますので、こちらも追加しておきましょう。GameObject を選択し、「Component」メニューの「Physics」「Rigidbody」を選択し、追加します。

 Rigidbody を追加したら、以下の様な設定にします。

  • Use Gravity = オフ

 Use Gravity はその名の通り、物理エンジンが重力をかけて自動的にオブジェクトが落ちる様な処理を行うかどうかですが、今回はプログラムで動きを制御しますのでこれはオフにしておきます。

 次に、このオブジェクトの動きをコントロールするコンポーネントを作ります。今までの要領で独自スクリプトを作成します。名前は「ObstacleController」としておきましょう。作ったら、下記の様に設定します。

using UnityEngine;
using System;

public class ObstacleController : MonoBehaviour
{
    private bool isMoving = true;

    public event Action CollidedWithUnityChan = delegate {};

    void Update()
    {
        if (this.isMoving)
        {
            Vector3 diff = new Vector3(0.0f, 0.0f, 1.0f) * Time.deltaTime;
            this.gameObject.transform.position = this.gameObject.transform.position - diff;
        }

        if (this.gameObject.transform.position.z <= -1.0f)
        {
            Destroy(this.gameObject);
        }
    }

    void OnCollisionEnter(Collision collision)
    {
        this.isMoving = false;

        if (collision.gameObject.tag.Contains("UnityChan"))
        {
            this.CollidedWithUnityChan();
        }
    }
}

 やっている事は大まかに下記の通りです。

  • Update 内で移動し続ける

  • z 位置が一定以上になると、自分自身を削除する

  • 衝突すると動きを止め、衝突した相手が UnityChan のタグを持っていたら、「ユニティちゃんと衝突しましたよー!」というイベントを発する

 少し細部についてご説明します。

 Update 内では isMoving が true の時のみ移動する様になっています。これは、衝突後に false にする事でそれ以上動かなくする事ができる様にするためです。また、Vector3 は * で掛け算ができます。これを利用し、1.0 / sec の速さにしたいので、Time.deltaTime を掛ける事で可変フレームに対応しています。

 z 位置が一定値以上になると、という記述をしていますが、通常はこの値は直接書かずにどこかに定数として切り出して書いておくなどの形にしておくとマジックナンバーになってしまうのを防ぐ事ができます。ただ、今回は簡略化しているのでそのまま書いています。

 また、Collider が設定されている GameObject は別の Collider が設定されている GameObject と衝突すると OnCollisionEnter というメソッドが呼ばれます。今回はそれを検知し、自身の CollisionEntered イベントを発火しています。これにより、外部から衝突したという事が分かる様にしています。なお、衝突判定に関してはコライダーや、第05回 当たり判定とアニメーションイベントとレイヤーなども参考になりますので見てみて下さい。

 ここで、イベントの形を取っているのは他のコンポーネント(例えば RunUnityChanController)の事を知りたくなく、疎な関係を保ちたかったからです。

 さて、動かすための設定はできたのでこの GameObject を Prefab にしたいのですが、まずは Prefab を格納するフォルダを作っておきましょう。

  • Assets/RunUnityChan/Prefabs

 フォルダを作ったら、Obstacle をフォルダにドラッグ&ドロップしてみましょう。

 この様に、Prefab 化する事ができました!意外にあっさりできてしまいましたね。Prefab 化したら、シーン上に存在している Obstacle は必要ないので削除しておきましょう。

 

 後はこれをどう使うかですが、一定時間ごとに RunUnityChanController から生成してシーンに配置したいので、RunUnityChanController にその修正を加えようと思います。

using UnityEngine;

public class RunUnityChanController : MonoBehaviour
{
    [SerializeField]
    private UnityChanController unityChanController;
    [SerializeField]
    private GameObject obstaclePrefab;
    private float elapsedTime = 0.0f;

    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit raycastHit;
            if (Physics.Raycast(ray, out raycastHit))
            {
                if (raycastHit.transform.gameObject.tag.Contains("UnityChan"))
                {
                    this.unityChanController.OnTapped();
                }
            }
        }

        elapsedTime += Time.deltaTime;

        if (1.5f <= elapsedTime)
        {
            GameObject obstacle = Instantiate(this.obstaclePrefab);
            obstacle.transform.position = new Vector3(0.0f, 0.0f, 3.0f); 
            elapsedTime = 0.0f;
        }
    }
}

 ちょっと複雑になってきましたね。細かい言語自体の説明などは省略しますが、やっている事の大枠は下記の様になっています。

  • 1.5 秒に一回、obstaclePrefab からコピーを生成し、シーンに配置している。

  • 配置したら初期位置に移動する

 

 RunUnityChanController は Obstacle の Prefab を使う事になりますので、参照を設定する必要があります。ヒエラルキービュー上から RunUnityChanController を選択し、インスペクタ上で Obstacle Prefab の欄に先ほど作った Prefab をドラッグ&ドロップしましょう。

 この様になるはずです。ここまできたらいったん再生ボタンを押してみましょう。おぉおおおお、障害物が次々と現れるものの、衝突するとどんどん溜まっていってしまい、もはやカオスです!(笑)衝突したらちゃんとゲームオーバーになる様にしないといけませんね。

障害物がユニティちゃんに衝突したらゲームオーバーにする

 ここまでのスクリプトでは、ユニティちゃんが衝突しても特に何もしていないのでどんどん障害物が溜まっていってしまいました。衝突したらきちんとゲームオーバーにしないといけませんね。

 

 ここでは、下記の様な修正を加えてみましょう。

  • ユニティちゃんが障害物に衝突したらゲームオーバーにする

    • ユニティちゃんが後ろに吹き飛ぶアニメーションを再生する

    • それ以上障害物を発生させなくする

 

 ここまで作業をしてきた読者の方ならだいたいどうやればいいのか、何となく分かってくるかもしれませんね。吹き飛ぶアニメーションを再生するにはそれに対応するステートを作成してそこに吹き飛ぶアニメーションを割り当てて、そのに遷移する条件を設定してあげればよいのです。早速作業してみましょう。

 まず Animations 以下にある RunUnityChanController をダブルクリックして Animator のビューを開きます。先ほどと同じ要領で新しいステートを作成し、名前を「GoDown」とします。Animation Clip には GoDown(他の Animation Clip と同じ場所にあります)を指定しておきましょう。

 次に遷移ですが GoDown はどの様な状態からでも遷移する可能性があるので、「Any State」を右クリックし、「Make Transition」を選択、さらに GoDown をクリックする事でどの状態からでも遷移する可能性のあるステートとして定義します。さらに遷移の条件の指定ですが、先ほど Running から  ToTop への遷移を作成した時と同じ様に Parameters から Collision トリガを作成し、これを遷移する条件として設定してみましょう。やり方が分からなくなったら先ほど Jump 変数の作成などを見直してみて下さいね。設定ができると下図の様になっているはずです。

ちなみに、アニメーションのブレンドは下図くらいの設定だと比較的違和感なく見れます。

 これでアニメーションの設定はできましたので、UnityChanController を拡張して衝突が発生した場合に衝突アニメーションを再生する様にします。

using UnityEngine;

public class UnityChanController : MonoBehaviour
{
    public void OnTapped()
    {
        this.GetComponent<Animator>().SetTrigger("Jump");
    }

    public void OnCollidedWithObstacle()
    {
        this.GetComponent<Animator>().SetTrigger("Collision");
    }

    public void OnCallChangeFace()
    {
    }
}

 さらに、RunUnityChanController を拡張して下記の様にします。

using UnityEngine;

public class RunUnityChanController : MonoBehaviour
{
    [SerializeField]
    private UnityChanController unityChanController;
    [SerializeField]
    private GameObject obstaclePrefab;
    private float elapsedTime = 0.0f;
    private bool isGameOver = false;

    void Update()
    {
        if (this.isGameOver)
        {
            return;
        }

        if (Input.GetMouseButtonDown(0))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit raycastHit;
            if (Physics.Raycast(ray, out raycastHit))
            {
                if (raycastHit.transform.gameObject.tag.Contains("UnityChan"))
                {
                    this.unityChanController.OnTapped();
                }
            }
        }

        elapsedTime += Time.deltaTime;

        if (1.5f <= elapsedTime)
        {
            GameObject obstacle = Instantiate(this.obstaclePrefab);
            ObstacleController obstacleController = obstacle.GetComponent<ObstacleController>();
            obstacleController.CollidedWithUnityChan += this.ObstacleCollidedWithUnityChan;
            obstacle.transform.position = new Vector3(0.0f, 0.0f, 3.0f); 
            elapsedTime = 0.0f;
        }
    }

    private void ObstacleCollidedWithUnityChan()
    {
        if (this.isGameOver)
        {
            return;
        }
        this.unityChanController.OnCollidedWithObstacle();
        this.isGameOver = true;  
    }
}

 追加したのは下記の部分です。

  • isGameOver の変数を用意し、true の場合は Update 内ではなにもしない

  • 障害物を生成した時に、障害物がユニティちゃんに衝突した時のイベントをキャッチできる様に CollidedWithUnityChan に、ObstacleCollidedWithUnityChan をアタッチしている

  • 障害物に衝突してしまった場合は UnityChanController にそれを通知するとともに、 isGameOver を true にしてゲームオーバー状態にしている

 

 さてさて。いよいよこれで実行してみましょう。

 おぉおお、ユニティちゃんが走り、タップするとジャンプし、もし障害物に衝突すると後ろに吹き飛ぶ様な動作をしますね!一通り実装ができた様です!やった!!!

 

 ただ、実行してみると分かると思うのですが、ゲームがめちゃめちゃ難しいです(笑)なかなか飛び越える事ができない。。。難しさも Flappy Bird 並みになってしまった様です。この辺りは本当のゲームであればレベルデザインをしっかりしてもう少し飛びやすくしないといけないですね。まぁ、今回は一通り作成できたのでよしとします!

 

 また、今回はユニティちゃんが障害物に衝突したらというよりは障害物がユニティちゃんに衝突したらというコードになっていますが、ユニティちゃん側で衝突を検知しようとすると UnityChanController をユニティちゃんの最上位階層にアタッチできないという気持ち悪い構造になってしまったり、それを回避しようとするとコントローラをさらに別に作ったり複雑になってしまうので、説明を簡略化するためにこの様な形にしました。お許し下さい。

 なお、今回作成した Unity のプロジェクトはこちらからダウンロードできます。

 

最後に

 いかがでしたか。とっっっっっても長い記事になってしまいましたが、一通り Unity を使ってゲームを作るという体験を通して、裏の仕組みを少しでも理解できたとしたら幸いです。今回は本当に最低限の事しか実装していませんが、もっともっと色々な事ができますし、逆にもっともっと配慮しなければいけない事も沢山あります。

  • 高速化

    • モバイルゲームではバッテリー消費が問題になりやすいし、体感的にもサクサク動かしたいが、どうすればよいか

  • アプリ・データサイズの削減

    • iOS で 64bit 対応が必須になり、アプリサイズが肥大化しているがどの様に対処するか

    • キャラクタの追加などでデータサイズが肥大化していくがどの様に対処するか

  • Unity でのゲームの実装方法

    • ロジックと描画の分離といったコーディングのテクニック

    • デザイナとプログラマの作業の境界線をどこに引くか

  • ディープなシェーダーの世界

    • 多様な表現をするためのシェーダーを使ったテクニック

  • サーバーとの連携

    • データをどこにストアするか、ロジックをどこに置くか

  • 開発の効率化

    • Unity で効率的に CI していくための仕組み作り

  • ネイティブブラグインの活用

    • Unity で標準的に提供されている機能以外にもっと色々な事がしたい場合

  • セキュリティ

    • 悪意のあるユーザーに不正をさせないための工夫

                                   などなど

 ここに書き出したのはあくまで一例で、実際に大規模アプリを作ろうとすると様々なトピックがあります。この記事を入り口として色々と作る過程でその様な技術を身につけていって頂ければと思います。そのうち弊社の誰かが記事にしてくれるかもしれません(笑)

 

 それでは、よい Unity ライフを! ggwp

※ ggwp = good game, well played(LoL の試合が終わった時に「いい試合だったね」的な感じで言うお決まりのセリフ)

 

ユニティちゃんライセンス

このアセットは、『ユニティちゃんライセンス』で提供されています。このアセットをご利用される場合は、『キャラクター利用のガイドライン』も併せてご確認ください。

LICENSEファイルを追加しました。
お手数ですが、すでにダウンロードして頂いた方も、当社が作成したプログラムやファイルについては、次の点をご確認の上、アセットをご利用ください。

Copyright (c) 2015 DeNA Co.,Ltd.

THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.