Unity Remote 5を使ってみた

先日、実機確認のためのビルド時間に悩んでいるという話をしたら、Unity Remoteというのを教えてもらったので、使ってみました。

Unity Remoteとは、ゲームの実行はUnity Editor上で行われるのですが入力はUSBで繋いだデバイス側から行えるというものです。

また、デバイス側にはレンダリング結果が圧縮して送られるので、長時間のビルドを待たずに実機での操作感を試すことができます。

入力としては、

が受け取れます。

使い方は、超簡単です。

どれぐらい簡単かって言うと、Unity Remoteってものがあるよって聞いてから、Unity Remoteについて調べるのも含めて3分後には利用ができていました。

詳しくは、以下のサイトにありますが、ここでも簡単に触れておきます。
docs.unity3d.com
Unity Remote 4と書いていますが、Unity Remote 5も同様です。


まずは下記のリンク先からUnity Remoteのアプリをデバイスにインストールします。

インストールしたらアプリを実行し、USBケーブルでPCと接続します。

次に、Unity Editor側で Editor設定( Edit > Project Settings > Editor )を開き、Unity Remote というところから使用するデバイスを選択します。
f:id:wkpn:20160816100238p:plain


あとは実行するだけです。

Unity Remote 4との違いは明確にはわかっていないのですが、iTunesのUnity Remote 5の紹介ページから察するに、3D Touchとか、Apple TVとかに対応したっぽいです。

Added support for MFi gamepads connected to the remote device (requires Unity 5.4)
Added new touch properties such as 3D Touch and Apple Pencil support (requires Unity 5.4)
Added support for Apple TV devices (requires Unity 5.4)


!使用感
数フレームぐらい遅延が発生するので、本当の操作感は分かりませんが、ざっくりとした感覚をみるには良いですね。

画質の差は、だいたい以下のよう感じです。

f:id:wkpn:20160816100417j:plain
Resolutionの項目でNormalを選んだ時の圧縮されたゲーム画面

f:id:wkpn:20160816101026j:plain
Resolutionの項目でDownsizeを選んだ時の圧縮されたゲーム画面

f:id:wkpn:20160816100428j:plain
本当のゲーム画面

NormalもDownsizeの違いはそんなになかったのですが、Downsizeの方が負荷が軽いようでした。スペックが低いPCではDownsizeを選んだほうが良さそうです。

また、どちらも左上のデバッグ用の文字は潰れています。しかしEditor上ではしっかり見えるので、そんなに問題はありません。

これで実機での動作確認の完全な代替になるわけではありませんが、開発効率がグッと向上しました。


そんな、Unity Remoteも使って開発したゲームはこちら↓


1分間たまご - 激ムズ!

1分間たまご - 激ムズ!

開発元:Ken Watanabe
無料

posted with アプリーチ

UnityのAsset「Android Native Plugin」でエラー(AdMobとの相性が悪いようでした)

UnityのAsset「Android Native Plugin」でビルドエラー(最新版だけ?)

UnityでGoogleのGame Service系を簡単に使えるようにすることで有名なアセット「Android Native Plugin」を導入したのですが、ビルドしようとすると下記のようなエラーが出ていました。

CommandInvokationFailure: Failed to re-package resources.
/Users/wakepon/Library/Android/sdk/build-tools/23.0.2/aapt package --auto-add-overlay -v -f -m -J gen -M AndroidManifest.xml -S "res" -I "/Users/wakepon/Library/Android/sdk/platforms/android-24/android.jar" -F bin/resources.ap_ --extra-packages com.chartboost.sdk.unity:com.google.unity:com.example.an_billing:com.example.an_googleplay:com.example.an_social:com.example.androidnative:android.support.v7.appcompat:com.google.android.gms.ads.impl:com.google.android.gms.ads.impl:com.google.android.gms.ads:com.google.android.gms.ads:com.google.android.gms.analytics:com.google.android.gms.analytics:com.google.android.gms.appinvite:com.google.android.gms.auth.api:com.google.android.gms.auth:com.google.android.gms.base:com.google.android.gms.base:com.google.android.gms:com.google.android.gms:com.google.android.gms.clearcut:com.google.android.gms.drive:com.google.android.gms.games:com.google.android.gms:com.google.android.gms.gcm:com.google.android.gms.iid:com.google.android.gms.plus:com.google.android.gms.tasks:android.support.v4:android.support.v4:com.unity3d.ads.android -S "/Users/wakepon/projects/2357/Temp/StagingArea/android-libraries/AN_Res/res" -S "/Users/wakepon/projects/2357/Temp/StagingArea/android-libraries/an_billing/res" -S "/Users/wakepon/projects/2357/Temp/StagingArea/android-libraries/an_googleplay/res" -S "/Users/wakepon/projects/2357/Temp/StagingArea/android-libraries/an_social/res" -S "/Users/wakepon/projects/2357/Temp/StagingArea/android-libraries/androidnative/res" -S "/Users/wakepon/projects/2357/Temp/StagingArea/android-libraries/appcompat-v7-23.1.1/res" -S "/Users/wakepon/projects/2357/Temp/StagingArea/android-libraries/play-services-ads/res" -S "/Users/wakepon/projects/2357/Temp/StagingArea/android-libraries/play-services-ads-9.4.0/res" -S "/Users/wakepon/projects/2357/Temp/StagingArea/android-libraries/play-services-ads-lite/res" -S "/Users/wakepon/projects/2357/Temp/StagingArea/android-libraries/play-services-ads-lite-9.4.0/res" -S "/Users/wakepon/projects/2357/Temp/StagingArea/android-libraries/play-services-analytics/res" -S "/Users/wakepon/projects/2357/Temp/StagingArea/android-libraries/play-services-analytics-impl/res" -S "/Users/wakepon/projects/2357/Temp/StagingArea/android-libraries/play-services-appinvite/res" -S "/Users/wakepon/projects/2357/Temp/StagingArea/android-libraries/play-services-auth/res" -S "/Users/wakepon/projects/2357/Temp/StagingArea/android-libraries/play-services-auth-base/res" -S "/Users/wakepon/projects/2357/Temp/StagingArea/android-libraries/play-services-base/res" -S "/Users/wakepon/projects/2357/Temp/StagingArea/android-libraries/play-services-base-9.4.0/res" -S "/Users/wakepon/projects/2357/Temp/StagingArea/android-libraries/play-services-basement/res" -S "/Users/wakepon/projects/2357/Temp/StagingArea/android-libraries/play-services-basement-9.4.0/res" -S "/Users/wakepon/projects/2357/Temp/StagingArea/android-libraries/play-services-clearcut-9.4.0/res" -S "/Users/wakepon/projects/2357/Temp/StagingArea/android-libraries/play-services-drive/res" -S "/Users/wakepon/projects/2357/Temp/StagingArea/android-libraries/play-services-games/res" -S "/Users/wakepon/projects/2357/Temp/StagingArea/android-libraries/play-services-gass-9.4.0/res" -S "/Users/wakepon/projects/2357/Temp/StagingArea/android-libraries/play-services-gcm/res" -S "/Users/wakepon/projects/2357/Temp/StagingArea/android-libraries/play-services-iid/res" -S "/Users/wakepon/projects/2357/Temp/StagingArea/android-libraries/play-services-plus/res" -S "/Users/wakepon/projects/2357/Temp/StagingArea/android-libraries/play-services-tasks-9.4.0/res" -S "/Users/wakepon/projects/2357/Temp/StagingArea/android-libraries/support-v4-23.1.1/res" -S "/Users/wakepon/projects/2357/Temp/StagingArea/android-libraries/support-v4-23.4.0/res" -S "/Users/wakepon/projects/2357/Temp/StagingArea/android-libraries/unityads-release/res"

stderr[
/Users/wakepon/projects/2357/Temp/StagingArea/android-libraries/appcompat-v7-23.1.1/res/layout/abc_alert_dialog_button_bar_material.xml:18: note: removing attribute http://schemas.android.com/apk/res/android:layoutDirection from <android.support.v7.widget.ButtonBarLayout>
/Users/wakepon/projects/2357/Temp/StagingArea/android-libraries/appcompat-v7-23.1.1/res/layout/abc_alert_dialog_button_bar_material.xml: note: using v17 attributes; synthesizing resource com.waken.Divide:layout/abc_alert_dialog_button_bar_material for configuration v17.
/Users/wakepon/projects/2357/Temp/StagingArea/android-libraries/appcompat-v7-23.1.1/res/layout/abc_alert_dialog_material.xml:48: note: removing attribute http://schemas.android.com/apk/res/android:textAlignment from <android.support.v7.widget.DialogTitle>
/Users/wakepon/projects/2357/Temp/StagingArea/android-libraries/appcompat-v7-23.1.1/res/layout/abc_alert_dialog_material.xml: note: using v17 attributes; synthesizing resource com.waken.Divide:layout/abc_alert_dialog_material for configuration v17.
/Users/wakepon/projects/2357/Temp/StagingArea/android-libraries/appcompat-v7-23.1.1/res/layout/abc_dialog_title_material.xml:29: note: removing attribute http://schemas.android.com/apk/res/android:textAlignment from <TextView>
...


色々と調べた結果、Assets/Plugin/Android以下と、Assets/Plugin/Android/lib以下の両方にplay-service系のaarができてしまっていることが問題のようでした。

Assets/Plugin/Android 以下にあるappcompat-v7-23.1.1.aarや、play-services-ads-9.4.0.aarといったaarファイル郡を削除したら、無事にビルドできるようになりました。

ただ、これらのファイルを削除するのも一筋縄では行かず、ProjectSettings/GoogleDependencyAdMobUnity.xmlを削除しないと、aarは何度でも復活してしまいました。

また、GoogleDependencyAdMobUnity.xml自体を削除してもビルド後に復活してしまい、これは未だに謎です。

現在、このエラーに関して「Android Native Plugin」のサポートチームに問い合わせ中。

P.S

GoogleDependencyAdMobUnity.xmlは、AdMobが生成しているようでした。

Assets/PlayServicesResolver/Editor/AdMobDependencies.cs というファイルが、上記xmlを吐くようだったので、全部コメントアウトしたら煩わされることがなくなりました。

コメントアウトしても、Plugin/Android/libs以下にAndroid Native Plugin が追加したaarファイル郡があるので問題なく動きます。

ただ、Unityの人に聞いたところ、ロムに含まれるaarファイルはAndroid直下だけというのが現在の仕様(サブフォルダ以下のaarも含まれている現状はバグ)とのことだったので、今後のUnityのバージョンでは危ういかも。

Unityのシェーダーを書くときに確認しそうなページメモ

  • ビルトインのシェーダー変数

docs.unity3d.com

docs.unity3d.com

  • Shaderのプロパティについて

docs.unity3d.com

  • "UnityCG.cginc"内の便利関数

docs.unity3d.com

docs.unity3d.com

  • Cgのレファレンス

http.developer.nvidia.com

Unityのレンダリング順について

Unityの、ShaderLabにはQueueというレンダリング順をコントロールするためのタグがあります。

詳しくは、以下のページに書かれていますが、自分でも自分の言葉でまとめてみます。

タグ名 内部インデックス 説明
Background 1000 他より前にレンダリングされます。Skyboxなんかもこれらしいが...
Geometry 2000 次に描画されるのはこれ。不透明なマテリアル用。
AlphaTest 2450 3番目。アルファテストするマテリアル用。
Transparent 3000 4番目。半透明マテリアル用。このキューに入るものは奥から順に描かれます。
Overlay 4000 最後。ポストエフェクトとか用。

タグは実は内部的にはインデックスになっています。例えばBackgroundタグは1000というように。
それを利用して、それぞれのタグの間にキューを作って描画することもできます。

Tags { "Queue" = "Geometry+1" }

とすればことで。2001のインデックスが振られ、Geometryの後、AlphaTestの前に描画されます。

インデックスが2500以下のキューは不透明とみなされます。

2500より大きいキューは半透明と見なされて、カメラからの距離でソートされて奥から順に描かれます。

疑問

Backgroundの項目にSkyboxがあり、他のレンダリングより先に行われるとなっていたのに疑問を持ちました。
なぜなら背景になるSkyboxは不透明の一番最後に描いたほうが処理付加的にメリットあるからです。

何故に?って思っていたのですが、別な場所には、

スカイボックスは完全に不透明なオブジェクトと完全に透明なオブジェクトの中間で描画されます。

っと書いてました。どっち?マニュアルの不備?現在問い合わせ中。

P.S
Unityから返信が来ました。どうやら翻訳文の方だけ間違っているようです。以下回答からの引用です。

おっしゃる通り、記述は矛盾したものとなっています。原文を参照すると
Background タグにSkyboxの記述はありません。よって
 スカイボックスは完全に不透明なオブジェクトと完全に透明なオブジェクトの中間で描画されます。
こちらを信用するのが正しいです。

翻訳は間違っている、あるいは原文の変更に対して対応が遅れています。(おそらく後者です)
ご指摘ありがとうございます。順次修正してまいります。

Unityで、Editorではシェーダーがちゃんと実行されているのに実機ではシェーダーがちゃんと実行されなくてハマった件

まず第一に、

Shader.Find( シェーダー名 )

としていたのですが、これが失敗していました。初歩的ですいません。

これはシーンから直接参照されていないリソースは、Resourcesフォルダに入れておかないとアプリに組み込まれないからです。

というわけで、Resourcesフォルダ以下に置いたのに、それでもうまくいかず...

いろいろ調べた結果わかったのは、どうやら、シェーダーの静的なテクスチャ参照は実機では無視される... ようです。(たぶん)

f:id:wkpn:20160822164936p:plain

図のWind Textureというテクスチャを表示するようにしてみると真っ白(デフォルト値)になっていたので、そういうことなのだと思います。

下のようにマテリアルに対して、テクスチャを設定してあげることでうまく表示されるようになりました。

Texture2D texture = Resources.Load<Texture2D>( "Shader/windTexture" );
material.SetTexture( "_WindTex", texture );

トイカメラ風のフィルター

トイカメラ風フィルターです。上下を暈すだけという、ちゃっちいテクニックです。

f:id:wkpn:20160821140621p:plain

Shaderの練習のために作ってみました。

ぼかし画像を作る部分は標準アセットのBlurのほぼコピペです。

ぼかし画像を一枚しか作っていないこともあり、イマイチですね。本当は複数のぼかしレベルの画像を送るべきですが、今回は練習なので手抜きです。

github.com

using UnityEngine;

public class ToyCameraImageEffect : MonoBehaviour
{
    private Material material;
    private Material blurMaterial;

    //ぼかし回数
    [Range(0,10)]
    public int iterations = 3;

    //ブラーのずらし量
    [Range(0.0f,1.0f)]
    public float blurSpread = 0.6f;

    protected virtual void Awake()
    {
        {
            string shaderName = "Hidden/ToyCameraImageEffectShader";
            Shader shader = Shader.Find( shaderName );
            Debug.Assert( shader != null );
            material = new Material( shader );
        }

        {
            string shaderName = "Hidden/BlurEffectConeTap";
            Shader shader = Shader.Find( shaderName );
            Debug.Assert( shader != null );
            blurMaterial = new Material( shader );
        }
    }

    void Update()
    {
    }

    void fourTapCone (RenderTexture src, RenderTexture dest, int iteration)
    {
        float off = 0.5f + iteration*blurSpread;
        Graphics.BlitMultiTap (src, dest, blurMaterial,
                new Vector2(-off, -off),
                new Vector2(-off,  off),
                new Vector2( off,  off),
                new Vector2( off, -off)
                );
    }

    void downSample4x (RenderTexture src, RenderTexture dest)
    {
        float off = 1.0f;
        Graphics.BlitMultiTap (src, dest, blurMaterial,
                new Vector2(-off, -off),
                new Vector2(-off,  off),
                new Vector2( off,  off),
                new Vector2( off, -off)
                );
    }

    void OnRenderImage( RenderTexture src, RenderTexture dest )
    {
        //ぼかしの画を作るために4分の一サイズの小さい一時バッファを作ります。
        int rtW = src.width/4;
        int rtH = src.height/4;
        RenderTexture buffer = RenderTexture.GetTemporary(rtW, rtH, 0);

        downSample4x (src, buffer);

        // Blur the small texture
        for(int i = 0; i < iterations; i++)
        {
            RenderTexture buffer2 = RenderTexture.GetTemporary(rtW, rtH, 0);
            fourTapCone (buffer, buffer2, i);
            RenderTexture.ReleaseTemporary(buffer);
            buffer = buffer2;
        }

        material.SetTexture( "_BlurTex", buffer );

        Graphics.Blit( src, dest, material );

        RenderTexture.ReleaseTemporary(buffer);
    }

}

Shader↓

Shader "Hidden/ToyCameraImageEffectShader"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
		_BlurTex ("Blur Texture", 2D) = "white" {}
		_FilterTex ("Filter Texture", 2D) = "white" {}
	}
	SubShader
	{
		// No culling or depth
		Cull Off ZWrite Off ZTest Always
        Fog { Mode Off }

		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			
			#include "UnityCG.cginc"

			struct appdata
			{
				float4 vertex : POSITION;
				float2 uv : TEXCOORD0;
			};

			struct v2f
			{
				float2 uv : TEXCOORD0;
				float4 vertex : SV_POSITION;
			};

			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
				o.uv = v.uv;
				return o;
			}
			
			sampler2D _MainTex;
			sampler2D _BlurTex;
			sampler2D _FilterTex;

			fixed4 frag (v2f i) : SV_Target
			{
				fixed4 org = tex2D(_MainTex, i.uv);
				fixed4 blur = tex2D(_BlurTex, i.uv);
				fixed4 filter = tex2D(_FilterTex, i.uv);
                return lerp( org, blur, filter.r );
			}
			ENDCG
		}
	}
}

Unityで自作ポストエフェクトを試してみた。

fspace.hatenablog.com

上記のサイトを参考にしてUnityで初めてシェーダーを触ってみました。

UnityのシェーダーはShaderLabと言って、頭の部分にシェーダーに値を渡すための記述を書き、下の方にCgかHLSLでシェーダーを書くみたいです。

一応GLSLでも書けるみたいですけど、ターゲットがMac OS XOpenGL ESをサポートしたモバイルデバイスかLinuxの時だけにしとき、っと公式サイトに書いてます。

今回は、作っているゲームで風が吹いている方向を分かるようにしたかったので、そんな感じのものを作りました。単純にテクスチャを流しているだけです。

f:id:wkpn:20160820174604p:plain

githubにサンプルをプロジェクトごと上げてます。
github.com
※一時、プロジェクトにコミットし忘れてました。ごめんなさい。

githubにコードありますが、こっちにも載せておきます。

using UnityEngine;

public class WindImageEffect : CustomImageEffect
{
    [SerializeField]
    [Range(0, 360)]
    private float rotation;

    [SerializeField]
    private float translateSpeed = 2.0f;

    [SerializeField]
    [Range(0, 2.0f)]
    private float density = 0.4f;

    [SerializeField]
    private Color windColor = Color.white;

    private float translate = 0.0f;

    public override string ShaderName
    {
        get { return "Hidden/WindImageEffectShader"; }
    }

    void Update()
    {
        translate += translateSpeed * Time.deltaTime;
    }

    protected override void UpdateMaterial()
    {
        float rad = Mathf.Deg2Rad * rotation;
        //shader側に値を渡す
        Material.SetFloat( "_translate", translate );
        Material.SetFloat( "_density", density );
        Material.SetColor( "_windColor", windColor );
        Material.SetFloat( "_rotCos", Mathf.Cos( rad ) );
        Material.SetFloat( "_rotSin", Mathf.Sin( rad ) );
    }
}
Shader "Hidden/WindImageEffectShader"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
		_WindTex ("Wind Texture", 2D) = "white" {}
        _translate ("Translate", Float) = 0
        _density ("Density", Float) = 0
        _windColor ("Wind Color", COLOR) = (1,1,1,1)
        _rotCos ("RotCos", Float) = 0
        _rotSin ("RotSin", Float) = 0
	}
	SubShader
	{
		// No culling or depth
		Cull Off ZWrite Off ZTest Always
        Fog { Mode Off }

		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			
			#include "UnityCG.cginc"

			struct appdata
			{
				float4 vertex : POSITION;
				float2 uv : TEXCOORD0;
			};

			struct v2f
			{
				float2 uv : TEXCOORD0;
				float2 uv_wind : TEXCOORD1;
				float4 vertex : SV_POSITION;
			};

            Float     _rotCos;
            Float     _rotSin;
            Float     _translate;

			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
				o.uv = v.uv;
				o.uv_wind = v.uv;
				o.uv_wind.y *= _ScreenParams.y / _ScreenParams.x;//アスペクト比の分補正
                o.uv_wind = float2( _rotCos * o.uv_wind.x - _rotSin * o.uv_wind.y, _rotSin * o.uv_wind.x + _rotCos * o.uv_wind.y );//回転行列を掛ける
				o.uv_wind.x += _translate;//移動
				return o;
			}
			
			sampler2D _MainTex;
			sampler2D _WindTex;
            Float     _density;
            fixed4    _windColor;

			fixed4 frag (v2f i) : SV_Target
			{
				fixed4 col = tex2D(_MainTex, i.uv);
				fixed4 wind = tex2D(_WindTex, i.uv_wind);
                float ratio = _density * wind.r;
                return lerp( col, _windColor, ratio );
			}
			ENDCG
		}
	}
}


あとUnityではポストエフェクトの類をImage Effectと呼んでいるようです。ポストにやるとは限らないからかな。