【Unity(C#)】タップした画面の色を取得する方法(UniRx使用)

##やりたいこと

やえがしさんのTwitterで
画面に映った色を取得してるのを見かけたので、
どうやればいいか調べてみました。
(ちょっとご本人にヒントももらいました。ありがとうございます)

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">空間に落ちている色をパレットみたいに使ってコーディネートできたらいいなーと思ったので作ってみた！<br> <a href="https://twitter.com/hashtag/withAR%E3%83%8F%E3%83%83%E3%82%AB%E3%82%BD%E3%83%B3?src=hash&amp;ref_src=twsrc%5Etfw">#withARハッカソン</a> <a href="https://twitter.com/hashtag/%E7%A9%BA%E9%96%93?src=hash&amp;ref_src=twsrc%5Etfw">#空間</a> <a href="https://t.co/tOluFOVHfq">pic.twitter.com/tOluFOVHfq</a></p>&mdash; やえがし (@Gassy_kk) <a href="https://twitter.com/Gassy_kk/status/1188404372448768000?ref_src=twsrc%5Etfw">October 27, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

##デモ

こんな感じで触った色が球体に反映されています。
![GetColor.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244071/70a50f40-cc80-85cd-7f36-857c1b45e964.gif)


今回はスマホの外部カメラの映像ではありませんが、
ARCoreなどのカメラをシーンに置けば
スマホの画面の映像から色を抽出できます。


##コード

もうほぼ答えですが、下記リンクを参考にしました。

【参考リンク】：[Unityで、クリックしたピクセルの色を取得したい](https://teratail.com/questions/221307)

リンク先にもありますが、描画処理が終わってからピクセルを取得する必要があるので、
`WaitForEndOfFrame`でそのフレームの最後に取得するようにしてます。

```C#:適当なオブジェクトにアタッチ
using UnityEngine;
using UniRx;
using UniRx.Triggers;
using CustomInput; //自分で定義したやつ
using System.Collections;

public class GetPixelColor : MonoBehaviour
{
    Coroutine runCoroutine;
    Texture2D screenTex;

    readonly public ColorReactiveProperty touchPosColorProperty = new ColorReactiveProperty();

    void Start()
    {
        screenTex = new Texture2D(1,1, TextureFormat.RGB24, false);

        this.UpdateAsObservable()
            .Where(_ => SimpleInput.GetTouchDown())
            .Subscribe(_ => 
            {
                if(runCoroutine == null)
                {
                    runCoroutine = StartCoroutine(GetColorTouchPos());
                }
            })
            .AddTo(this);
    }

    IEnumerator GetColorTouchPos()
    {
        yield return new WaitForEndOfFrame();
        Vector2 touchPos = SimpleInput.GetTouchDownPos();
        screenTex.ReadPixels(new Rect(touchPos.x, touchPos.y,1,1), 0, 0);
        touchPosColorProperty .Value= screenTex.GetPixel(0,0);
        Debug.Log(touchPosColorProperty.Value);

        runCoroutine = null;
    }
}
```

色が変化したことを検知するために
`ReactiveProperty`を利用しています。

検知の監視側はこんな感じです。

```C#:色を変えたいオブジェクト(MeshRenderer有り)にアタッチ
using UnityEngine;
using UniRx;

public class ChangeColor : MonoBehaviour
{
    [SerializeField]
    GetPixelColor getPixelColor;

    MeshRenderer thisObjMeshRenderer;

    void Start()
    {
        thisObjMeshRenderer = this.gameObject.GetComponent<MeshRenderer>();

        getPixelColor.touchPosColorProperty
            .SkipLatestValueOnSubscribe()
            .Subscribe(_ =>
            {
                thisObjMeshRenderer.material.color = getPixelColor.touchPosColorProperty.Value;
            })
            .AddTo(this);
    }
}

```

##SkipLatestValueOnSubscribe

SkipLatestValueOnSubscribeは発行された最初の値を無視できるやつです。

`ObserveEveryValueChanged`や`ReactiveProperty`を利用した際、
Subscribe(実行したい関数を登録)した時点で初期値が発行されてしまうようで、
始まっていきなりSubscribe内の登録した処理が走ってしまっていました。

例として下記のようなコードで`ObserveEveryValueChanged`で
Color型の変数を監視した場合、
Start関数内で初期値が発行されてしまい、
まだ色が変化してないにもかかわらず"Color Change"が表示されました。

```C#:例）touchPosColorはただのColor型
        GetColor
            .ObserveEveryValueChanged(_ => _.touchPosColor)
            .Subscribe(_ =>
            {
                Debug.Log("Color Change");
            })
            .AddTo(this);
```

`SkipLatestValueOnSubscribe`は
`ReactiveProperty`のオペレータとして用意されているので、
`ObserveEveryValueChanged`ではなく、
Color型の変数を`ReactiveProperty`に変えて利用しました。

【参考リンク】：[Unityにおけるコルーチンの省メモリと高速化について、或いはUniRx 5.3.0でのその反映](http://neue.cc/2016/05/14_529.html)

##CustomInput

今回はInputがタッチなのでオレオレInputを定義してます。

タッチ系は毎回書くのが面倒なので、一回作って使い回すか
EventSystemを利用するのがセオリーっぽいです。
(もっと楽なのあったら教えてください)

```C#
using UnityEngine;

namespace CustomInput
{
    /// <summary>
    /// Custom InputKey
    /// </summary>
    public static class SimpleInput
    {
        /// <summary>
        /// Being touched return true
        /// </summary>
        public static bool GetTouch()
        {
            if (0 < Input.touchCount)
            {
                Touch touch = Input.GetTouch(0);

                if (touch.phase == TouchPhase.Moved && touch.phase == TouchPhase.Stationary)
                {
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// Touching return true only 1 frame
        /// </summary>
        public static bool GetTouchDown()
        {
            if (0 < Input.touchCount)
            {
                Touch touch = Input.GetTouch(0);

                if (touch.phase == TouchPhase.Began)
                {
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// Releasing return true only 1 frame
        /// </summary>
        public static bool GetTouchUp()
        {
            if (0 < Input.touchCount)
            {
                Touch touch = Input.GetTouch(0);

                if (touch.phase == TouchPhase.Ended)
                {
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// Return touching position only 1 frame. Not being touched return Vector.zero
        /// </summary>
        public static Vector2 GetTouchDownPos()
        {
            if (0 < Input.touchCount)
            {
                Touch touch = Input.GetTouch(0);

                if (touch.phase == TouchPhase.Began)
                {
                    return touch.position;
                }
            }
            return Vector3.zero;
        }
    }
}

```

##１００
やっと100記事書きました。
中身が微妙なので「だからなんやねん」って感じですが、
継続力だけは自分で自分を評価します。

逆に言うと私のレベルで継続できなくなったらオワリなので
これからもノンストップ牛歩で頑張ります。