ゲームを作っているときによくあるのが、物体をクリック(またはタップ)したら何か起こる処理です。ネットで探すと色々な方法が書いてあります。この辺の処理は昔と今で違う方法があり、現在でも両方が機能するで、気付かないまま使うことが多々あります。代表的なものはnGUIとuGUI、それに関連するEventsystemです。Canvas使ってるのにRaycastでマウスの判定を使う、という一見、無駄に見える場合もあります。ここでは、それぞれを簡単に比較してみて、どちらがいいか、考えてみます。
新しい方法が良いので、従来の方法の解説が不要の方は読み飛ばしてください。
それぞれの方法の紹介
まずは、それぞれどういうものなのかを簡単に説明します。今回は、Cubeを沢山、並べて、クリックされるとそのCubeが上昇する、というものを作っています。説明ではCube単体で解説していますが、負荷実験ではそれをPrefabにして多数をSceneに配置し実験しています。
■発射!Raycastでクリックされた物を刺す方法
ネットでよく見かける、従来の方法です。
□クリックされるCubeの準備
まず、Cubeを配置し、"Cube"というタグを入れます。コンポーネントにRigidbodyを追加してください。そして、Cube側に以下のCubeControl.csのコンポーネントをアタッチします。
using UnityEngine;
using System.Collections;
[RequireComponent(typeof(Rigidbody))]
public class CubeControl : MonoBehaviour {
Rigidbody rigidBody;
public Vector3 force = new Vector3(0, 10, 0);
public ForceMode forceMode = ForceMode.VelocityChange;
// Use this for initialization
void Start () {
rigidBody = gameObject.GetComponent<Rigidbody>();
}
public void OnUserAction()
{
rigidBody.AddForce(force, forceMode);
}
}
OnUserAction()
メソッドが呼び出されると、あらかじめ設定されたforce
のVector3
を用いてAddForce
によってCubeが動きます。
### □マウス入力を扱うObjectを用意
マウスを検知して呼び出す側は今回、別のオブジェクトで配置します。このCubeを沢山、配置した場合、すべてのCubeでマウスの検知をそれぞれで実行するのは無駄になる処理が多くなりうるからです。
という訳で、別のオブジェクトを[GameObject]>[Create Empty]で作り、それに以下を当てはめます。
using UnityEngine;
using System.Collections;
public class SceneManager : MonoBehaviour
{
public string cubeTag="Cube";
// Update is called once per frame
void Update()
{
if (Input.GetMouseButtonDown(0))
{
Ray ray = new Ray();
RaycastHit hit = new RaycastHit();
ray = Camera.main.ScreenPointToRay(Input.mousePosition);
//マウスクリックした場所からRayを飛ばし、オブジェクトがあればtrue
if (Physics.Raycast(ray.origin, ray.direction, out hit, Mathf.Infinity))
{
if(hit.collider.gameObject.CompareTag(cubeTag))
{
hit.collider.gameObject.GetComponent<CubeControl>().OnUserAction();
}
}
}
}
}
この処理で以下をUpdate毎に実行しています。
- マウスが押されたかを
Input.GetMouseButtonDown(0)
で調べる - 押されていたら、カメラを始点に3Dに変換したマウス座標へのベクトルを取得、
Ray
へ -
Ray
がその先で何かのGameObject
にぶつかるかを検出 - 検出した
GameObject
のTagがcubeTag
の内容と同じ文字列なら、その中のCubeControl
コンポーネントのonUserAction()
メソッドを実行
### □パフォーマンスを確認
これをCube400個を敷き詰めて実行したのが以下になります。
何もしない場合、90fps前後で落ち着いています。
■EventSystemを用いた方法
つづいて、Unity4.6で登場しUnity5.xでは標準になっているuGUIのEventSystem
を使った方法です。イベントとは、システムで定義した「意味のある出来事」です。クリックされた、画面全体を描き終えた、など、多数あります。そのイベントが起こった時に、呼び出されるのがリスナー、またはコールバック関数と呼ばれる、対応した処理です。それらの結び付けを整理したシステムがEventSystem
です。全体を把握しようとすると大きいので、ここは、今回のクリックをもとに考えていきましょう。
###□CubeをEventを受ける形で用意
この実装では二つの方法を紹介します。成果物は同じ動作になります。以下の方法1、方法2の何れかで実装してみてください。他にも可能です。
####[方法1]EventTrigger
コンポーネントをCubeにアタッチ
Event用のコンポーネントが用意されてるので、それを使います。
- 前述のCubeControl.csをCubeにアタッチ
- Inspectorの下にある[Add Component]ボタンから、[Event]>[Event Trigger]を選択
- 受け取るイベントを選択します。配置された[Event Trigger]コンポーネント内の[Add New Event Type]>[PointerClick]を選択。
- イベント発生時に通知するObjectを指定します。[PointerClick]内の左下にある○を押して、Cubeを選択します。
- イベントを受けて実行するメソッドを指定します。[PointerClick]内の右下にある[No Function]ボタンから、[CubeControl]>[OnUserAction]を選びます。
これでマウスクリックを受け取ることができます。他のイベントも併せて定義したい場合は、上記手順の[Add New Event Type]から変更・追加が可能です。
####[方法2]イベントを受け取るIPointerClickHandler
インタフェースを継承したScriptのコンポーネントをアタッチ
Cubeを用意、以下のScriptコンポーネントをアタッチします。
using UnityEngine;
using UnityEngine.EventSystems;
using System.Collections;
[RequireComponent(typeof(Rigidbody))]
public class CubeControlEvent: MonoBehaviour , IPointerClickHandler
{
Rigidbody rigidBody;
public Vector3 force = new Vector3(0, 10, 0);
public ForceMode forceMode = ForceMode.VelocityChange;
// Use this for initialization
void Start () {
rigidBody = gameObject.GetComponent<Rigidbody>();
}
public void OnPointerClick(PointerEventData eventData)
{
rigidBody.AddForce(force, forceMode);
}
}
上記Scriptの、前述のCubeControl.csとの違いは、以下になります。
- 継承に
IPointerClickHandler
が追加されている - メソッドが
OnPointerClick(PointerEventData eventData)
になってる
その他は変わりません。
前述のコンポーネントの追加する方法と内部的には同じ処理になりますが、どのインタフェースを継承するかで拾うイベントが決まるので、複数を用いる場合はそれぞれを継承し、イベントで通知される関数も固定されている(インタフェースで定義されている)ので、合わせた名前で定義します。
AddListenerを用いる記述の方法もあると思います。
詳しくは、こちらを参照してください。
Unity-スクリプトリファレンス:EventTrigger
https://docs.unity3d.com/jp/current/ScriptReference/EventSystems.EventTrigger.html
###□Eventの仕組みを提供するEventsystemをSceneに配置
Unityでは、入力で何かが起こったかをオブジェクト間で通知する仕組みをEventSystem
で用意しています。これを用意することで、上記のIPointerClickHandler
が動いてくれます。
- [GameObject]>[Create Empty]でGameObjectをSceneに配置
- [Add Component]ボタンから[Event]>[Event System]を選択
- 出てきたコンポーネントの[Add Default Input Modules]ボタンを押す
特に設定を変える必要はありません。
###□イベントを発生させるRaycastをカメラに追加
次に、カメラ(特になにもしていない場合はMain Camera
)にPhysics Raycaster
コンポーネントを追加します。
これによって、カメラからRaycast
が飛び、それを拾う設定のあるObjectにEventSystemによって送信され、規定のメソッドが実行されます。
###□パフォーマンスの確認
何もしない状態で100fps以上出ています、処理が軽くなっているのが推測されます。
比較と検証
それぞれをみると、
- 新しい方法の方が実装が簡単
- 処理効率も好さそう
ということになります。数学的な処理だけとらえると、線分と平面(メッシュ)の交差を調べることは、恐らく変わりません。それを多数の物体に対して如何に効率よくこなすようになるかで、差が出ていると思われます。
以上のことから、UIのイベントの処理はEventSystemで実装した方が良さそうです。
まとめ
- 4.6以降のシステムなら
EventSystem
を積極的に使った方が動作が効率的です。 - ネットで検索すると、昔の情報の方が永らくネット上に在るために検索ランクが上で、更にそれを見た人がリンクを張るなどしてさらにランクが上がります。そういうことがあるので、新しいUnityを使っている人は、先ず自分のバージョンのリファレンスを参照して、何か変わってないか調べた方が好さそうです。
- 他方、アプリのバージョンアップを旧Unityで繰り返している、意図的に旧バージョンを開発に用いている場合などで旧いバージョンの情報は有益です。無用なデバッグが不要ということを優先し、また、用いているAssetが新しいUnityに対応しない場合、それに縛られているプロジェクトも多々あります。そうした理由から、旧い情報が無くなることはありません。リファレンスも複数バージョンで記載しているので確認しながら利用しましょう。
- UnityではScriptでClassとして実装できるものを、Scriptで継承したり、コンポーネントで提供されているものはエディタで設定可能になるなど、多様な実装方法があり、迷うもとになります。自分のやりやすい方法方で実装し、プログラマ側のメンテナンスの効率化の為ならScript、メンテナンス担当がScriptを触らない場合はコンポーネントにするなど、考えていきましょう。
#補足事項
- EventSystemはGUIのボタンなどをSceneに追加すると自動的に入ります。SceneにCanvasが在ったら見てください。二つは要りません。
- イベント処理なのでUniRxでも記述できます、慣れている方はそちらで。たぶん、慣れている人はこの記事は不要と思われますが。
[参考情報]
- Unity-マニュアル:イベントシステム
https://docs.unity3d.com/ja/current/Manual/EventSystem.html - [Unity-スクリプトリファレンス:EventSystem
https://docs.unity3d.com/ja/current/ScriptReference/EventSystems.EventSystem.html]
(https://docs.unity3d.com/ja/current/ScriptReference/EventSystems.EventSystem.html) - [Unity-スクリプトリファレンス:EventTrigger
https://docs.unity3d.com/ja/current/ScriptReference/EventSystems.EventTrigger.html]
(https://docs.unity3d.com/ja/current/ScriptReference/EventSystems.EventTrigger.html) - Unityの新GUI、UGUIのイベント制御について/テラシュールブログ
http://tsubakit1.hateblo.jp/entry/2014/12/23/233000
こちらにていろいろなUnityに関する情報を提供しています。よろしければご覧ください。
[Unityチュートリアルindex]
(http://qiita.com/JunShimura/items/f87c599b3738b804f605)