nimanji’s blog

20代の最後は手のひらに楽しいゲームを産み落とす。それができるまでを記した日記。

重なったGameObjectからどのGameObjectがクリックされたかを判別する

普段はサーバエンジニアとして飯を食っていますが、家では「なんか面白いモノ作りたい」と思いつつ、仕事とは逆のクライアント側の開発を独学中にふと思ったこと。
「重なったGameObjectのうち、どのGameObjectがクリックしたか判別したい」
これをやってみました。

今回作るにあたって参考になったサイトさま:
Unityでスクリプトから画像を読み込んで表示する【uGUI】 | 神代のかなた・ラクガキ
naichilab.blogspot.jp

前提条件:

  • 使う画像はすべて同じサイズであること
  • 使う画像をすべて重ねたとき、ほかの画像と干渉していないこと

環境:

  • Unity version 2017.1.1f
  • UniRx

その1:GameObjectを取り込んだSpriteから作成する

はじめにSceneを作成します。今回はプレイ画面で使うため、PlaySceneとして作成しています。
Hierarchyには、右クリックして「UI>Text」を選択し、作成されたCanvas内のTextを削除したものを用意します。
f:id:nimanji:20180310191711p:plain
次に、Cavnasに設定するスクリプトを作成していきます。
今回はUniRxを使用するため、Canvasに設定するPlayScenePresenterにはAwakeStartを使い、AwakeでGameObjectの生成を記述し、Startにはクリックしたときの動作を記述します。

class PlayScenePresenter : MonoBehaviour
{
    private PlaySceneModel _model;

    void Awake ()
    {
        this._model = new PlaySceneModel();
    }

    void Start ()
    {
        // === ここにクリック時の処理とかを書いていく
    }
}

Awake内に生成用のスクリプトを記載する方法もありますが、それだとごちゃごちゃしてしまうので、別にPlaySceneModelを作成し、インスタンス生成時にGameObjectをHierarchyに登録するように作ります。

public class PlaySceneModel
{
    // 読み込んだSpriteを保存する
    private Object[] load_sprite_list;

    /// <summary>
    /// インスタンス
    /// </summary>
    public PlaySceneModel ()
    {
        // ResourcesディレクトリにあるSpriteをすべて取得
        this.load_sprite_list = Resources.LoadAll("", typeof(Sprite));

        // 取得したSprite1個ずつ読み込む
        foreach (Sprite sprite in this.load_sprite_list) {
            // GameObjectを読み込んだSprite名で生成
            GameObject instance_object = new GameObject(sprite.name);

            // GameObjectの親子関係、アンカー位置などを設定
            instance_object.transform.parent = GameObject.Find("Canvas").transform;
            instance_object.AddComponent<RectTransform>().anchoredPosition = new Vector2(0, 0);
            instance_object.GetComponent<RectTransform>().localScale = new Vector2(1, 1);

            // GameObjectSpriteを適用し、アスペクト比と画像サイズの設定を行う
            instance_object.AddComponent<Image>().sprite = sprite;
            instance_object.GetComponent<Image>().preserveAspect = true;
            instance_object.GetComponent<Image>().SetNativeSize();
        }
    }
}

これで、Resourcesディレクトリの中にpng形式の画像を入れて生成されたSpriteからGameObjectを画面中央に生成することができました。
foreach内でいろいろやっていますが、ここではコード内のコメントのみにしています。各行でどういうことをしているかについては、こちらの記事(Unityでスクリプトから画像を読み込んで表示する【uGUI】 | 神代のかなた・ラクガキ)に記載されていますので、気になるかたはこちらも読んでみることをおすすめします。

そして、作成したPlayScenePresenterをHierarchyのCanvasに設定して実行すると、このような表示になります。
f:id:nimanji:20180310194952p:plain
全体が白い画像ですが、今回は以下の画像を使っているため、すべて白く見えます。画像の名前が違いますが、取り込んだあとに変更しています。
f:id:nimanji:20180310195226p:plain

その2:クリックした位置にあるGameObjectの名前をログに出力する

クリックの処理自体はUniRxを使っているのでけっこう簡単にできる。PlaySenePresenterのStartに以下を追記します。

    void Start ()
    {
        var click_handle = Observable.EveryUpdate().Where(_ => Input.GetMouseDown(0));
        clock_handle.Subscribe(_ => this._model.getObjectNameForMousePosition(Input.mousePosition.x, Input.mousePosition.y));
    }

左クリックしたらPlaySceneModelのgetObjectNameForMousePositionを呼び出すという簡単なもの。getObjectNameForMousePositionの中身は以下のとおりです。

    public function getObjectNameForMousePosition (Vector2 position(
    {
        // 保存されているSpriteのテクスチャのうち、引数の座標のアルファ値が1(不透明)のものを探す
        Sprite target_sprite = null;
        foreach (Sprite sprite in this.load_sprite_list) {
            if (1.0f == sprite.texture.GetPixel((int)position.x, (int)position.y).a) {
                target_sprite = sprite;
                break;
            }
        }
        Debug.Log(target_sprite.name);
    }

これを実行してゲーム画面右上あたりをクリックすると、クリックされた箇所のGameObjectの名前が出力できました。
f:id:nimanji:20180311184130p:plain

最後に:これからの課題

今回はクリックで実装しましたが、やっぱりスマホで遊ぶゲームを作るとなるとタッチ動作の確認をしないといけないという課題が残りました。
調べてみると、どうもタッチ操作とGetPixelは相性が悪い(?)ようで、いろいろ苦戦するであろう記事が多かったので、それができたらまた記事にしようと思います。