8
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Unity】ExecuteEvents.Execute/ExecuteHierarchy でスクリプトからクリック等の操作をおこなう

Posted at

はじめに

モバイルアプリを筆頭に「マウス(タッチ)のみで操作を完結させる」というのはよくあることかと思います。
そこで悩ましいのが、開発用やサブ入力としてキーボードなどでもクリックやタップ相当の操作をしたい!という状況です。

今までは専用メソッドを作るなどゴリ押しで対応してしまっていたのですが、どうにもナンセンスな気がして改めて調べ直したのでここに共有しておこうと思います。

前置き

onClick.Invoke

まず考えるのが、イベントの発火メソッド(Button なら onClick.Invoke())を呼び出すことです。

    _button.onClick.Invoke();

が、これは非アクティブだろうとenable == falseだろうとinteractable == falseだろうと実行してしまうため、その辺りのケアが必要です。

OnPointerClick

とすれば次はイベント受け取りコールバックメソッド(Button なら OnPointerClick())を呼び出す方法です。

    _button.OnPointerClick(new PointerEventData(EventSystem.current));

これは非アクティブ等の状態も見るらしく前述の問題はなく、これで事足りることも多そうです。ただ、イベントを受け取るコンポーネントが複数ある場合があり(音は別のスクリプトで再生している等)、合わせて正しく再現しようと思うと、イベントハンドラを持つコンポーネントすべてで実行しなければなりません。

オブジェクトでイベントを直接実行する ExecuteEvents.Execute

もっと簡単にオブジェクトをクリックしたという状態を起こすようなことはできないのかな、と色々調べていたところ ExecuteEvents.Execute という API があることを知りました。
これはゲームオブジェクトに対してクリック等(IEventSystemHandler 系)のイベントを実行するというものです。

例えば、Button コンポーネントをもつゲームオブジェクトへクリックイベントを送るコードは次のようになります。

    ExecuteEvents.Execute(_button.gameObject, new PointerEventData(EventSystem.current), ExecuteEvents.pointerClickHandler);

これで Button のゲームオブジェクトに対してイベントを送ることができる(=他にイベントを受け取るコンポーネントがあればそれにもイベントが飛ぶ)ので、マウスでのクリックにかなり近い動作になると言えます。

ヒエラルキーも考慮する ExecuteEvents.ExecuteHierarchy

ExecuteEvents.Execute はゲームオブジェクトに直でイベントを送るため、例えばタッチガード1等、他のオブジェクト配置には無関心です。
もしそこまで考えるなら少し手間が必要で、対象の位置へレイキャストを飛ばしてイベントを発生させるゲームオブジェクトを取得する(もう少し具体的にはオブジェクトの位置に Canvas の GraphicRaycaster からレイキャストを飛ばして一番最初に当たったやつでイベントを発生させる)感じになるかと思います。

ところが少し困ったことに、uGUI 特有のイベントのヒエラルキー伝搬(?)で**「一番最初に当たったオブジェクト」が直接のイベント実行対象でない場合2があります。
しかしそこはさすが Unity、それをやってくれる ExecuteEvents.ExecuteHierarchy という API がピンポイントでありました。
例えば次のようなコードを書けば、クリックが最初に当たったゲームオブジェクトがイベントを発生させる対象でなくても
親オブジェクトを辿ってイベントを発生させる対象を探して実行してくれて**、かつ、(タッチガードのような)親子関係にないオブジェクトで遮られていれば反応しないという、ほぼクリックしたのと同じ状態を再現できます。

    // ※_raycaster は Canvas に付いている GraphicRaycaster

    PointerEventData eventDataCurrent = new PointerEventData(EventSystem.current);

    // イベントデータをボタンの位置に移動
    eventDataCurrent.position = RectTransformUtility.WorldToScreenPoint(_raycaster.eventCamera, _button.transform.position);

    // イベント受け取り可能なオブジェクトを取得
    var hits = new List<RaycastResult>();
    _raycaster.Raycast(eventDataCurrent, hits);
    if (hits.Count > 0)
    {
        // 最前面のオブジェクトにクリックイベントを実行
        ExecuteEvents.ExecuteHierarchy(hits[0].gameObject, eventDataCurrent, ExecuteEvents.pointerClickHandler);
    }

おわりに

私は今回この API を初めて知りましたが、かなり昔のバージョンからあったようですね。
本記事が何かのお役に立てば幸いです。

  1. 手前に RaycastTarget を有効にした Image などを置いてイベントをせき止めるやつです。私の周囲ではタッチガードと呼んでいますが正しい名前とかあるんでしょうか…。

  2. デフォルトの Button を作ったときに、手前に Text が付いてきますが、Button の子オブジェクトになっているので問題なく反応してくれるアレです。

8
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?