LoginSignup
7
3

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-02-08

やりたいこと

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

デモ

こんな感じで触った色が球体に反映されています。
GetColor.gif

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

コード

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

【参考リンク】:Unityで、クリックしたピクセルの色を取得したい

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

適当なオブジェクトにアタッチ
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を利用しています。

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

色を変えたいオブジェクト(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は発行された最初の値を無視できるやつです。

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

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

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

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

【参考リンク】:Unityにおけるコルーチンの省メモリと高速化について、或いはUniRx 5.3.0でのその反映

CustomInput

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

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

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

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

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

7
3
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
7
3