Help us understand the problem. What is going on with this article?

[Unity初心者Tips]オブジェクトがクリックされたか検知する方法、よく見かける?あの方法と比較

More than 3 years have passed since last update.

ゲームを作っているときによくあるのが、物体をクリック(またはタップ)したら何か起こる処理です。ネットで探すと色々な方法が書いてあります。この辺の処理は昔と今で違う方法があり、現在でも両方が機能するで、気付かないまま使うことが多々あります。代表的なものはnGUIとuGUI、それに関連するEventsystemです。Canvas使ってるのにRaycastでマウスの判定を使う、という一見、無駄に見える場合もあります。ここでは、それぞれを簡単に比較してみて、どちらがいいか、考えてみます。

新しい方法が良いので、従来の方法の解説が不要の方は読み飛ばしてください。

それぞれの方法の紹介

まずは、それぞれどういうものなのかを簡単に説明します。今回は、Cubeを沢山、並べて、クリックされるとそのCubeが上昇する、というものを作っています。説明ではCube単体で解説していますが、負荷実験ではそれをPrefabにして多数をSceneに配置し実験しています。

■発射!Raycastでクリックされた物を刺す方法

ネットでよく見かける、従来の方法です。

□クリックされるCubeの準備

まず、Cubeを配置し、"Cube"というタグを入れます。コンポーネントにRigidbodyを追加してください。そして、Cube側に以下のCubeControl.csのコンポーネントをアタッチします。

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()メソッドが呼び出されると、あらかじめ設定されたforceVector3を用いて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毎に実行しています。

  1. マウスが押されたかをInput.GetMouseButtonDown(0)で調べる
  2. 押されていたら、カメラを始点に3Dに変換したマウス座標へのベクトルを取得、Ray
  3. Rayがその先で何かのGameObjectにぶつかるかを検出
  4. 検出したGameObjectのTagがcubeTagの内容と同じ文字列なら、その中のCubeControlコンポーネントのonUserAction()メソッドを実行

 □パフォーマンスを確認

これをCube400個を敷き詰めて実行したのが以下になります。

https://gyazo.com/f5d17f43e81769984d6ddc18be2ef655

高画質版はこちら

何もしない場合、90fps前後で落ち着いています。

■EventSystemを用いた方法

つづいて、Unity4.6で登場しUnity5.xでは標準になっているuGUIのEventSystemを使った方法です。イベントとは、システムで定義した「意味のある出来事」です。クリックされた、画面全体を描き終えた、など、多数あります。そのイベントが起こった時に、呼び出されるのがリスナー、またはコールバック関数と呼ばれる、対応した処理です。それらの結び付けを整理したシステムがEventSystemです。全体を把握しようとすると大きいので、ここは、今回のクリックをもとに考えていきましょう。

□CubeをEventを受ける形で用意

この実装では二つの方法を紹介します。成果物は同じ動作になります。以下の方法1、方法2の何れかで実装してみてください。他にも可能です。

[方法1]EventTriggerコンポーネントをCubeにアタッチ

Event用のコンポーネントが用意されてるので、それを使います。

  1. 前述のCubeControl.csをCubeにアタッチ
  2. Inspectorの下にある[Add Component]ボタンから、[Event]>[Event Trigger]を選択
  3. 受け取るイベントを選択します。配置された[Event Trigger]コンポーネント内の[Add New Event Type]>[PointerClick]を選択。
  4. イベント発生時に通知するObjectを指定します。[PointerClick]内の左下にある○を押して、Cubeを選択します。
  5. イベントを受けて実行するメソッドを指定します。[PointerClick]内の右下にある[No Function]ボタンから、[CubeControl]>[OnUserAction]を選びます。

https://gyazo.com/e685faaac3fc9cc62209d911a0645201

https://gyazo.com/4327da179afcaf0780fa9b0a3e40132a

これでマウスクリックを受け取ることができます。他のイベントも併せて定義したい場合は、上記手順の[Add New Event Type]から変更・追加が可能です。

[方法2]イベントを受け取るIPointerClickHandlerインタフェースを継承したScriptのコンポーネントをアタッチ

Cubeを用意、以下のScriptコンポーネントをアタッチします。

CubeControlEvent.cs
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が動いてくれます。

  1. [GameObject]>[Create Empty]でGameObjectをSceneに配置
  2. [Add Component]ボタンから[Event]>[Event System]を選択
  3. 出てきたコンポーネントの[Add Default Input Modules]ボタンを押す

https://gyazo.com/eabbaa99965c9f1824aeac46a98a17e1

https://gyazo.com/ab09fd224e4f5467c7b393018f13ee7d

特に設定を変える必要はありません。

□イベントを発生させるRaycastをカメラに追加

次に、カメラ(特になにもしていない場合はMain Camera)にPhysics Raycasterコンポーネントを追加します。
https://gyazo.com/20b13d190342102effde3ae913d3ec1c

https://gyazo.com/99bba3dbad9913ca55b458a1bc0f26bc

これによって、カメラからRaycastが飛び、それを拾う設定のあるObjectにEventSystemによって送信され、規定のメソッドが実行されます。

□パフォーマンスの確認

同じくCube400個を並べて実行しています。
https://gyazo.com/31612ceab768070638b809b811e2d15b

何もしない状態で100fps以上出ています、処理が軽くなっているのが推測されます。

比較と検証

それぞれをみると、

  • 新しい方法の方が実装が簡単
  • 処理効率も好さそう

ということになります。数学的な処理だけとらえると、線分と平面(メッシュ)の交差を調べることは、恐らく変わりません。それを多数の物体に対して如何に効率よくこなすようになるかで、差が出ていると思われます。

以上のことから、UIのイベントの処理はEventSystemで実装した方が良さそうです。

まとめ

  • 4.6以降のシステムならEventSystemを積極的に使った方が動作が効率的です。
  • ネットで検索すると、昔の情報の方が永らくネット上に在るために検索ランクが上で、更にそれを見た人がリンクを張るなどしてさらにランクが上がります。そういうことがあるので、新しいUnityを使っている人は、先ず自分のバージョンのリファレンスを参照して、何か変わってないか調べた方が好さそうです。
  • 他方、アプリのバージョンアップを旧Unityで繰り返している、意図的に旧バージョンを開発に用いている場合などで旧いバージョンの情報は有益です。無用なデバッグが不要ということを優先し、また、用いているAssetが新しいUnityに対応しない場合、それに縛られているプロジェクトも多々あります。そうした理由から、旧い情報が無くなることはありません。リファレンスも複数バージョンで記載しているので確認しながら利用しましょう。
  • UnityではScriptでClassとして実装できるものを、Scriptで継承したり、コンポーネントで提供されているものはエディタで設定可能になるなど、多様な実装方法があり、迷うもとになります。自分のやりやすい方法方で実装し、プログラマ側のメンテナンスの効率化の為ならScript、メンテナンス担当がScriptを触らない場合はコンポーネントにするなど、考えていきましょう。

補足事項

  • EventSystemはGUIのボタンなどをSceneに追加すると自動的に入ります。SceneにCanvasが在ったら見てください。二つは要りません。
  • イベント処理なのでUniRxでも記述できます、慣れている方はそちらで。たぶん、慣れている人はこの記事は不要と思われますが。

[参考情報]


こちらにていろいろなUnityに関する情報を提供しています。よろしければご覧ください。
Unityチュートリアルindex

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away