uGUIでイベントを使用する – Unity

投稿者: | 2017年12月30日

uGUIのイベントで少し躓いてしまったので、参考として記録しておきます。

■ やりたかったこと

GameObject上に、Imageを表示

GameObject上に、Imageを表示

uGUIのImageをスクリプトで動的に表示します。これをプレイヤーがクリックすると、Imageと関連づけられたGameObjectを取得します。

■ 躓いた点
シーン上に複数のGameObjectが配置されており、同数のImageを動的に表示します。GameObjectとImageは1対1で対応しており、Imageをクリックすると対応するGameObjectが分かるようにします。これを実現するために、Imageにはスクリプトをつけて、Targetというpublicフィールドで対応するGameObjectが分かるようにしました。
ImageはTargetMaskという名前にしており、スクリプトにはTargetController.csという名前をつけました。

public class TargetMarkController : MonoBehaviour {

    // Public
    public GameObject target; // <-対応するGameObjectを入れる

    // Use this for initialization
    void Start () {

    }

    // Update is called once per frame
    void Update () {

    }
}

ImageにEventTriggerコンポーネントを追加して、OnPointerClickイベントを取得できるようにしました。
作成しているのはRPGの戦闘シーンで、このシーンの進行をBattleController.csというスクリプトで管理しています。このBattleController.csに、OnPointerClickイベントのイベントハンドラーを作成しました。

public class BattleController : MonoBehaviour {
    ・・・
    private int targetEnemy = -1;
    private List orders = new List();
    ・・・
    public void TargetMarkClick(Object sender)
    {
        GameObject btn = sender as GameObject;
        GameObject target = btn.GetComponent<TargetMarkController>().target; // 対応する敵キャラクターを取得

        int i = 0;

        while ((i < orders.Count) && (targetEnemy == -1))
        {
            if (orders[i].name == target.name)
        {
            targetEnemy = i;
        }

        i++;
    }
}

orders変数には、シーンに存在する敵味方の全キャラクターが、行動順に入っています。targetEnemy変数は、プレイヤーが選択した敵キャラクターの、ordersリスト内でのインデックス番号が入ります。
プレイヤーがImageをクリックすると、スクリプト「TargetMaskController.cs」のtargetフィールドを参照して、対応する敵キャラクターを取得します。
whileループでordersリストの各キャラクターの名前と比較し、同じ名前のものを探して、インデックス番号を特定します。
後は、ImageのEventTriggerで取得するOnPointerClickイベントに、このTargetMarkClickを当てるだけ。

これでうまくいくはずだったのですが、実行してみるとordersリストを参照した時点でNullReferenceエラーとなりました。
BattleControllerクラス内の別のメソッドではordersリストにアクセスできるのに、このイベントハンドラー内だけordersが空になっています。原因がすぐに分からず、しばらく試行錯誤してしまいました。
その原因は実に単純なものでした。
シーンを開始した時点でImageはシーン上にありません。インスタンスが作成されるまでは実体のないプレハブです。EventTriggerのOnPointerClickイベントで、BattleController.csのTargetMarkClickメソッドが実行されるよう設定しています。このBattleControllerが間違っていたのです。
Imageが実体のないプレはずであるため、指定したBattleControllerもプレハブを指定しているだけでした。シーン上に存在するBattleControllerの実体を指しているわけではないので、ordersリストの中身がなくなっているという減少が発生したのでした。

■ 解決策
これを解決するのは簡単で、Imageを動的に生成したとき、EventTriggerも動的に設定すればよいのです。
// ターゲットマークの表示

using UnityEngine.EventSystems; // この1行が必要

    void ShowTargetMarks()
    {
        Vector3 pos;
        Vector2 screenPos;
        Vector2 localPos;
        RectTransform markRect;
        GameObject canvasGame = GameObject.Find("gameCanvas");
        RectTransform canvasRect = canvasGame.GetComponent<RectTransform>();
        GameObject[] targets = GameObject.FindGameObjectsWithTag("Enemy");

        int counter = 0;

        foreach(GameObject enemy in targets)
        {
            if (enemy.GetComponent<Ally>().IsDead)
            {
                continue;
            }

            pos = enemy.GetComponent<Transform>().position;
            screenPos = RectTransformUtility.WorldToScreenPoint(Camera.main, pos);
            localPos = Vector2.zero;
            RectTransformUtility.ScreenPointToLocalPointInRectangle(canvasRect, screenPos, Camera.main, out localPos);

            GameObject mark = Instantiate(prefabTargetMark) as GameObject; // Imageを動的に生成
            mark.transform.SetParent(canvasRect, false);
            markRect = mark.GetComponent<RectTransform>();
            markRect.localPosition = new Vector3(localPos.x, localPos.y, 0.0f);
            mark.GetComponent<TargetMarkController>().target = enemy;
            mark.name = "TargetMark" + counter.ToString();
            counter++;
            EventTrigger trigger = mark.GetComponent<EventTrigger>(); // イベントの設定
            EventTrigger.Entry entry = new EventTrigger.Entry();
            entry.eventID = EventTriggerType.PointerClick;
            entry.callback.AddListener((sender) => TargetMarkClick(mark));
            trigger.triggers.Add(entry);
        }
    }

動的にEventTriggerの設定を行っているのは、末尾の5行です。
Imageに追加済みのEventTriggerコンポーネントを取得し、trigger変数に入れておきます。

EventTrigger trigger = mark.GetComponent<EventTrigger>();

新規にエントリーを生成します。

EventTrigger.Entry entry = new EventTrigger.Entry();

eventIDに取得したいイベントを指定します。今回はOnPointerClickとなります。

entry.eventID = EventTriggerType.PointerClick;

entryのcallbackに、実行するイベントハンドラーを登録します。今回はラムダ式を使っていますが、単にAddListener(TargetMarkClick(mark))でもいけるかもしれません。ここはまだあまり理解していませんが、とりあえず下記のように記述したらうまく動いています。

entry.callback.AddListener((sender) => TargetMarkClick(mark));

entryをtriggerに追加します。

trigger.triggers.Add(entry);

これで目的を達成することができました。
 
おすすめの本
Unityの教科書 Unity 2017完全対応版 2D&3Dスマートフォンゲーム入門講座 (Entertainment&IDEA)
Unityの寺子屋 定番スマホゲーム開発入門

ADs
  

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)