##やりたいこと
やえがしさんのTwitterで
画面に映った色を取得してるのを見かけたので、
どうやればいいか調べてみました。
(ちょっとご本人にヒントももらいました。ありがとうございます)
空間に落ちている色をパレットみたいに使ってコーディネートできたらいいなーと思ったので作ってみた!
— やえがし (@Gassy_kk) October 27, 2019
#withARハッカソン #空間 pic.twitter.com/tOluFOVHfq
##デモ
今回はスマホの外部カメラの映像ではありませんが、
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
を利用しています。
検知の監視側はこんな感じです。
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"が表示されました。
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記事書きました。
中身が微妙なので「だからなんやねん」って感じですが、
継続力だけは自分で自分を評価します。
逆に言うと私のレベルで継続できなくなったらオワリなので
これからもノンストップ牛歩で頑張ります。