Google

2012年9月4日火曜日

Unityでコインゲー開発:2Dと3Dの分割

何度も忘れては思い出したりするTipsがあるので、また忘れる前に記事化しておきます。
一応コインゲー絡みの話。

元となったのはこちらの記事。

 ■[Unity][Unity3d]マウスカーソルでオブジェクトを選択

実際こういった処理を現場ではC#で書いていたのですが、これらの中でやってることの意味、ハマり、詳細等を少々。
で、今回のお題はこうです。

 「2DGUIとマウスの判定がしたい」

3Dゲームの場合、大抵2Dは別に処理します。
カメラが動いたりオブジェが飛んできたりする仕様なら、2Dのポリゴンは別にしないとカメラと一緒に動いてしまいます。
HPとかオプションなんかは当然固定にしたいものですよね。

コインゲームなら、例えば弾けるコインに2Dが邪魔されたり、GUIがコイン投擲操作の邪魔になったりしてはいけない訳です。

んで、私がやったやり方がこう。

 ・最初からあるカメラは3D空間を担当
 ・2D用に別のカメラを準備し、専用レイヤを担当

今回はプラグインは不要な方法ですのでご安心を。
3Dは最初からあるDefaultレイヤを、2D用にはGUIレイヤを準備しました。
2D用のカメラはGUI Cameraという名前で追加しただけです。

あ、カメラのレイヤーですが、こうやって追加します。

カメラ選択して、LayerからAdd Layer選択
開いてる場所に追加。今回はGUIがそれ。
各カメラが重ならないように、カリングマスクもかけときます。

Culling Maskから必要なものだけを選択。
マスクは構成を以下のようにして、余計なものは表示しないようにします。

  Main Camera : Defaultのみ
  GUI Camera : GUIのみ

さらにGUIは3D空間の歪みを消しつつ、3Dよりも上に出し、さらにオブジェクトがない部分は3Dが出るようにしなければなりません。
GUI Camera側の設定を以下に表示します。

Clear FlagsをDepth Onlyに設定
Orthographoc設定で歪みを消します。
2Dの場合はカメラの表示範囲になるので少なめに
あ、あと、上のSSで下に見えてるDepthですが、2D優先にするため以下のようにします。

  Main Camera : -1
  GUI Camera : 0

これでカメラの準備は整いました。
今回はサンプルとして、3D側にはカプセルを、2D側には1枚のプレーンをおいて、その重ね合わせを検証してみます。
プレーンはわかりやすいように赤色にしてみました。
カプセルがあるので一応ライトもあります。

まずカプセル。3D側なのでLayerはDefaultにしてます。
次にプレーン。2D側なのでLayerがGUIになってる点に注意。
これを互いに重ならないような位置に配置します。
座標は適当ですが、各々のカメラにカプセルと板が出ているのがわかります。
2Dカメラは歪みを設定で消したため、表示範囲が長方形になっているのがポイントです。

個別に表示されてますね。2Dは範囲が四角形になってます。
で、ゲーム画面では以下のように出るわけです。

はい、見事重なりました。
この手法を使うことで2Dと3Dが干渉せずに出せます。
レイヤが違い、かつカリングマスクをかけているので、2dカメラのそばにオブジェクトが紛れ込んでも映り込むことはありません。
まあ、大抵はカメラの位置を離して影響ないようにするんですが。

じゃあこの2DのGUIだけマウスを反応させるにはどうするか?が実は本題。
まず忘れちゃいけないのが、マウスで判定させたいGUI、今回はこのプレーンに対してコライダーをつけること。
これを忘れると冒頭で紹介したブログの判定も動いてくれません。
私もこれを結構忘れてしまってしばらく悩んだりとか馬鹿な事をしたりします。

コライダーのサイズ等は触らないのが吉。判定がずれます。
そしてマウス判定をするクラスのUpdate等の内部で以下のように書きます。

 // GUIカメラをヒモ付
 public Camera m_gui_camera;
 // マウス押下時
 if (Input.GetMouseButtonDown(0))
 {
  // マウス押下位置からビームを飛ばして判定
  Ray ray = m_gui_camera.ScreenPointToRay(Input.mousePosition);
  // GUIレイヤのみをマスク
  int gui = 1 << LayerMask.NameToLayer("GUI");
  // 無限の軌道でヒット判定
  if (Physics.Raycast(ray, Mathf.Infinity, gui))
  {
   // GUIレイヤをクリックした際の処理をここに記述
  }
 }

m_gui_cameraは今回準備したGUI専用のカメラを紐付けしましょう。
FindでもインスペクタからのD&Dでもおkです。
マウスが押されたら、GUI側のカメラに対してマウス押下位置の情報を作成します。
int型のguiという変数はなんか変なことやってるように見えますが、さっきの画像を再掲しますのでまずは以下をご覧あれ。


GUIというレイヤを追加しましたが、これは番号として8になります。
LayerMask.NameToLayer()に存在する名称を投げるとその番号が帰ってくるのですが、これはビット単位でのマスク処理になっているため、実際は以下の様なコードなのです。

   1 << 8

つまり、この場合はビットシフトされて256という値になります。
もしさらに追加してレイヤが9なら512になる訳ですな。
続いてこのマスクを使って、Physics.Raycast()にこれらの情報を入れると、マウスの入力位置が該当するレイヤのオブジェクトにあたったかどうかを真偽で返してくれるのです。

真ならその中でGUIにあたった処理を書けばよろしい訳でして。
複数のGUIがあってさらに座標計算とか面倒ならレイヤを沢山作ってボタン別に分ければ綺麗に処理も可能です。
注意点としては、コライダーを忘れないこと。
あとカメラが2つあると、AufdioListenerが複数あるという警告が実行時にひたすらでてしまいます。
どちらか一方のコンポーネントを消すといいでしょう。

一応コインゲーム開発ではこの原理を実際に使っていました。
3D側にも当然コイン投擲等の判定は必要ですが、先ほどのif文に掛からなければ3D空間をクリックしたということになりますので、そこで対処できると思います。

なお、今回のデモのソースは以下からどうぞ。

  サンプル置き場所(SampleProject_20120904.zip 782,845 byte)

デモをEditor上で実行し、赤いプレーン上でマウスクリックすると”hit”とログに出るだけの簡単なシロモノです。
判定スクリプトはプレーンに紐付けしてあります。
まあ、大したことはしていませんが、皆様の何かのお役に立てれば幸いです。


記事長くなりすぎたので続きはまた次回。

0 件のコメント:

コメントを投稿