Android端末にapkをインストールする

Unityで開発しているとたいていBuild And RunでAndroid端末にapkを流し込むと思うのですが、たまに既にできあがったapkを端末にインストールしたいことがあります。

そういうときはコマンドラインから、

adb install 〜.apk

とすることでインストールできます。

しかし、既にインストールしているapkだと、

INSTALL_FAILED_ALREADY_EXISTS

と言われてインストールに失敗してしまいます。

そういうときは、-r をつけて

adb install -r 〜.apk

とすると古いバージョンを上書きしてインストールしてくれます。

AdMobのpluginをバージョンアップしたらAndroid版がビルドできなくなったときの対応

AdMobのプラグインバージョンをアップデートした時に、

CommandInvokationFailure: Unable to convert classes into dex format.
C:\Program Files\Java\jdk1.8.0_92\bin\java.exe -Xmx2048M -Dcom.android.sdkmanager.toolsdir="C:/Users/MyUserName/AppData/Local/Android/sdk\tools" -Dfile.encoding=UTF8 -jar "C:\Program Files\Unity\Editor\Data\PlaybackEngines\AndroidPlayer/Tools\sdktools.jar" -

stderr[
Uncaught translation error: java.lang.IllegalArgumentException: already added: Lcom/google/android/gms/internal/zzut;
...

などと言われてAndroid版がビルドできないことがありました。

Assets -> Play Services Resolver -> Android Resolver -> Force Resolve

することでビルドできるようになりました。

f:id:wkpn:20180601115738p:plain

参考
github.com

Resolving Android Dependencies... が何度も発生する問題

AdMobを組み込んでいるアプリで、他の人と作業していて微妙に環境が違うと、

Resolving Android Dependencies...

っていうのが何度も発生するときがあります。
これがうざいときは、一度ResolveしたあとにResolveを自動でしないように設定すると良いみたいです。

Assets -> Play Serveces Resolver -> Android Resolver -> Settings

にある"Enable Auto-Resolution"をOFFに設定できますので、お試しあれ。

f:id:wkpn:20180531173445p:plainf:id:wkpn:20180531173451p:plain

AdMobのリワード広告のプリロードってどうするのがベストプラクティスか?

AdMobのReward広告のPreloadってどうするのがベストプラクティスなのか分からなかったのですが、Google JapanのAdMob担当の方に質問して教えてもらったのでまとめてみます。

自分の理解が間違っている可能性もあるので組み込む際は自己責任でお願いします。

また、自分はUnityを使っているので今回はUnity上での話になります。

問題点

AdMobでリワード広告を再生するときは当然プリロードしておきたいですよね。

しかし、RewardBasedVideoAdをリクエストすると環境によっては失敗するときがあります。

例えばネットワークにアクセスできない状況だったり、広告在庫がなかったりしたときです。広告在庫に関しては日本ではなくなるってことはあんまりないけど、発展途上国などでは結構あるらしいです。

広告取得に失敗すると、OnAdFailedToLoadに登録したコールバックに処理が回ってきます。

取得に失敗したら、再度プリロードしたいところですが、すぐにまた広告をリクエストすると、

リクエスト -> 失敗 -> リクエスト -> 失敗 -> ....

っとリクエストが怒涛の勢いで積み重なり、負荷がかかっちゃいます。またダッシュボードで確認するとフィルレートがすごい低いことになってしまって、見ていて気持ち良いものではありません。

解決策

そんなこんなで、どうするのが良いのかなぁとずっと思っていたのですが、たまたまGoogleの方に直接質問できる機会があったので質問したところ、

ちゃんとエラーメッセージをみて、それに応じて処理を分けたほうが良いです。

とのこと。まあ、当たり前といえば当たり前ですね^^;

ただ、エラー毎に処理を分けるのがめんどくさいですし、そもそもどんなタイプのエラーがあるのかよく分かりません(-_-;)
(レファレンスあるのか聞けばよかった...)

そんな人に、めんどくさがりにオススメなのは、

失敗したら数分待ってリクエストする

らしいです。

注意点

ただ、数分待ってリクエストするということで、Invokeでも使ってサクッと実装っと思ったのですが、少し注意が必要です。

OnAdFailedToLoadに登録したコールバックはメインスレッドから呼ばれないようなのです。

Unityの関数の中にはメインスレッドで呼んではいけないものというのがいくつかあり、気をつけないといけません。

とりあえず、自分が今までハマったもので言うと、

  • Debug.isDebugBuild
  • Application.internetReachability
  • 音を出す関数(関数名忘れた)
  • Invoke

の4つです。これに違反すると、

UnityException: ○○○○(関数名) can only be called from the main thread.
Constructors and field initializers will be executed from the loading thread when loading a scene.
Don't use this function in the constructor or field initializers, instead move initialization code to the Awake or Start function.

などというメッセージが出ます。(ただ、Androidでしかこのメッセージは見たことないのでAndroid版限定の話なのかも。)

Invokeが使えないので、数分待ってリクエストしようねっというフラグだけ立てて、実際数分後にリクエストする処理はUpdateの中などでするのが吉です。

さらに改善

さらに、たまたま一回だけリクエストに失敗したというパターンもあると思うので、徐々に待ち時間を長くするのが良いと思います。例えば、

5秒後に再リクエスト -> 20秒後に再リクエスト -> 80秒後に再リクエスト -> 5分後に再リクエスト -> 5分後に再リクエスト(5分程度で頭打ちにしておく) -> ...

のような感じです。

参考までに、そんなこんなで実装してみたのが以下になります。

using UnityEngine;
using UnityEngine.UI;
using System;
using UnityEngine.Assertions;
using GoogleMobileAds.Api;

public class AdListener : MonoBehaviour {
    //android
    public string adAppIdAndroid;
    public string adIdRewardAndroid;
    //iOS
    public string adAppIdIOS;
    public string adIdRewardIOS;

    private RewardBasedVideoAd rewardBasedVideo          = null;
    public  Action             userCallbackOnReward      = null;
    public  Action             userCallbackOnRewardClose = null;

    protected bool  isShowingRewardVideo      = false;
    protected float delayedCallTimer          = 0.0f;
    protected int   delayedCallTimeTableIndex = 0;

    //0秒後、5秒後、20秒後、80秒後、300秒後にリクエストをする
    protected float[] delayedCallTimeTable = new float[] {
        0.0f,
        5.0f,
        20.0f,
        80.0f,
        300.0f,
    };

	void Awake () {
#if UNITY_ANDROID
        MobileAds.Initialize(adAppIdAndroid);
#elif UNITY_IPHONE
        MobileAds.Initialize(adAppIdIOS);
#else
#endif
        rewardBasedVideo                        =  RewardBasedVideoAd.Instance;
        // has rewarded the user.
        rewardBasedVideo.OnAdLoaded             += HandleRewardBasedVideoLoaded;
        rewardBasedVideo.OnAdRewarded           += HandleRewardBasedVideoRewarded;
        rewardBasedVideo.OnAdClosed             += HandleRewardBasedVideoRewardedClosed;
        rewardBasedVideo.OnAdFailedToLoad       += HandleRewardBasedVideoRewardedFailed;
        rewardBasedVideo.OnAdLeavingApplication += HandleRewardBasedVideoLeavingApplication;
        initDelayedCallTime();
	}

    void Update()
    {
        //リワード動画再生中にリクエストするとコールバックが呼ばれないことがあったので
        //isShowingRewardVideoで弾いている
        if( !IsLoadedRewardBasedVideo() && !isShowingRewardVideo )
        {
            delayedCallTimer -= Time.deltaTime;
            bool is_request_reward_video = false;
            if( delayedCallTimer < 0 )
            {
                is_request_reward_video = true;
                incrementDelayedCallTime();
            }

            if( is_request_reward_video )
            {
                requestRewardBasedVideoImpl();
            }
        }
    }
    
    void requestRewardBasedVideoImpl()
    {
        if( Application.internetReachability == NetworkReachability.NotReachable ) return;

        string ad_id;
#if UNITY_EDITOR
        ad_id = "unused";
#elif UNITY_ANDROID
        ad_id = adIdRewardAndroid;
#elif UNITY_IOS
        ad_id = adIdRewardIOS;
#else
        ad_id = "unexpected_platform";
#endif
        AdRequest request = new AdRequest.Builder().Build();
        rewardBasedVideo.LoadAd( request, ad_id );
    }

    //リクエスト待ち時間を初期値に戻す
    private void initDelayedCallTime()
    {
        delayedCallTimeTableIndex = 0;
        delayedCallTimer = delayedCallTimeTable[delayedCallTimeTableIndex];
    }

    //次に失敗したときに再度リクエストをするまでの待ち時間を設定
    private void incrementDelayedCallTime()
    {
        delayedCallTimeTableIndex = UnityEngine.Mathf.Min( delayedCallTimeTableIndex + 1, delayedCallTimeTable.Length - 1 );
        delayedCallTimer = delayedCallTimeTable[delayedCallTimeTableIndex];
    }

    public bool IsLoadedRewardBasedVideo()
    {
#if UNITY_EDITOR
        return true;
#else
        return rewardBasedVideo.IsLoaded();
#endif
    }

    public bool ShowRewardBasedVideo()
    {
#if UNITY_EDITOR
        HandleRewardBasedVideoRewarded(null,null);
        HandleRewardBasedVideoRewardedClosed(null,null);
        return true;
#else
        if (rewardBasedVideo.IsLoaded())
        {
            isShowingRewardVideo = true;
            rewardBasedVideo.Show();
            return true;
        }
        else
        {
            return false;
        }
#endif
    }

    public void HandleRewardBasedVideoLoaded(object sender, EventArgs args)
    {
        Debug.Log( "HandleRewardBasedVideoLoaded" );
#if UNITY_EDITOR
        HandleRewardBasedVideoRewarded(null,null);
        HandleRewardBasedVideoRewardedClosed(null,null);
#endif
    }

    // 報酬受け渡し処理
    public void HandleRewardBasedVideoRewarded(object sender, Reward args)
    {
        if( userCallbackOnReward != null )
        {
            userCallbackOnReward();
        }
    }
 
    public void HandleRewardBasedVideoRewardedClosed(object sender, System.EventArgs args)
    {
        if( userCallbackOnRewardClose != null )
        {
            userCallbackOnRewardClose();
        }

        isShowingRewardVideo = false;
        //リクエスト待ち時間を初期値に戻す
        initDelayedCallTime();
    }
 
    // ロード失敗時
    public void HandleRewardBasedVideoRewardedFailed(object sender, AdFailedToLoadEventArgs args)
    {
        Debug.Log( "Failed to load video reward : " + args.Message );
    }

    protected void HandleRewardBasedVideoLeavingApplication(object sender, System.EventArgs args)
    {
        Debug.Log("HandleRewardBasedVideoLeavingApplication");
    }
}

スプラトゥーンでダメージを受けた時みたいな、でろでろエフェクト

スプラトゥーンでダメージを受けた時みたいな画面エフェクトを作りたくて作りました。

スプラトゥーンには及びませんが、だいたいこんな感じになりました。

でろでろエフェクト
f:id:wkpn:20180316174436g:plain

テクスチャを調整したら、もっとスプラ感が出るんじゃないかと思います。

まずはShader、

Shader "Damage"
{ 
    Properties 
    {
        _MainTex ("Base (RGB)", 2D) = "white" {}
        _NoiseTex ("Damage Tex", 2D) = "white" {}
        _VignetteTex ("Vignette Tex", 2D) = "white" {}
    }
    SubShader
    {
        Pass
        {
            Cull Off ZWrite Off ZTest Always
            CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma fragmentoption ARB_precision_hint_fastest
#pragma target 3.0
#pragma glsl
#include "UnityCG.cginc"
            uniform sampler2D _MainTex;
            uniform sampler2D _NoiseTex;
            uniform sampler2D _VignetteTex;
            uniform float     _Timer;
            uniform float     _DamageMag;
            uniform float     _Darkness;
            uniform float     _Sharpness;
            uniform float     _AspectRatio;
            struct appdata_t
            {
                float4 vertex   : POSITION;
                float4 color    : COLOR;
                float2 texcoord : TEXCOORD0;
            };
            struct v2f
            {
                float2 texcoord  : TEXCOORD0;
                float4 vertex   : SV_POSITION;
                float4 color    : COLOR;
            };
            v2f vert(appdata_t IN)
            {
                v2f OUT;
                OUT.vertex = UnityObjectToClipPos(IN.vertex);
                OUT.texcoord = IN.texcoord;
                OUT.color = IN.color;
                return OUT;
            }
            half4 _MainTex_ST;
            float4 frag(v2f i) : COLOR
            {
                float2 org_uv = UnityStereoScreenSpaceUVAdjust(i.texcoord, _MainTex_ST);
                float2 adjusted_uv = org_uv.xy;//アスペクト比を考慮した uv
                adjusted_uv.x *= _AspectRatio;//アスペクト比の考慮

                float noise0 = tex2D(_NoiseTex, float2( adjusted_uv.x + _Timer, adjusted_uv.y )).x;
                float noise1 = tex2D(_NoiseTex, float2( adjusted_uv.x, adjusted_uv.y + _Timer )).x;
                float vignette = clamp( ( tex2D(_VignetteTex, org_uv.xy).r * noise0 * noise1 + _DamageMag ) * _Sharpness, 0.0f, 1.0f );
                vignette = 1.0f - vignette * _Darkness;
                float4 color = tex2D(_MainTex, org_uv.xy) * vignette;

                return color; 
            }
            ENDCG
        }
    }
}

仕組みとしては、周辺を暗くするテクスチャにノイズ用のテクスチャを重ね合わせています。

ノイズ用のテクスチャは、X方向に移動するのとY方向に移動するのを重ね合わせて見た目のランダム性が高まるようにしています。

ノイズ用のテクスチャは、こういう感じのを使っています。

f:id:wkpn:20180316174741p:plain

このテクスチャはフォトショップのフィルターで、ノイズを加える -> 雲模様 -> 波型の歪み として作ったものを、レベル補正してコントラスト上げて、ボカして、端でちゃんとループするようにしたものです。

周辺を暗くする用のテクスチャは、こういう感じです。

f:id:wkpn:20180316174844p:plain

次にc#スクリプト側はこんな感じです↓

using UnityEngine;
using System.Collections;
public class DamageEffect : MonoBehaviour {
    [SerializeField]
    [Range(0.0f, 1.0f)]
    private float timer = 0.5f;

    [Range(0f, 1f)]
    public float speed = 1f;

    [SerializeField]
    [Range(0f, 1f)]
    protected float damage = 0.5f;

    [SerializeField]
    [Range(0f, 1f)]
    protected float darkness = 0.5f;

    [SerializeField]
    [Range(0f, 3f)]
    protected float sharpness = 1.0f;

    [SerializeField]
    protected Material material;

    void OnRenderImage (RenderTexture sourceTexture, RenderTexture destTexture)
    {
        material.SetFloat("_Timer", timer );
        material.SetFloat("_DamageMag", ( damage - 1.0f ) );
        material.SetFloat("_Darkness", darkness );
        material.SetFloat("_Sharpness", Mathf.Pow( 10.0f, sharpness ) );
        material.SetFloat("_AspectRatio", sourceTexture.width/(float)sourceTexture.height );
        Graphics.Blit(sourceTexture, destTexture, material);
    }
    void Update ()
    {
        timer += Time.deltaTime * speed;
        timer = Mathf.Repeat( timer, 1.0f );
    }
}

特に変わったところはないです。

使い方は、

  • c#スクリプトをカメラに引っ付ける
  • 先のシェーダーを元にMaterialを作る
  • MaterialのTableTexにテクスチャを当てる
  • カメラにひっつけたスクリプトにMaterialをassignする

という感じです。

説明が雑くてすいません。質問がありましたらコメント欄でお願いしますm(_ _)M

Shockwave エフェクトを作る

ぽわ~んって感じの画面エフェクトを出したくてCamera filter packというアセットを買いました。

assetstore.unity.com

しかし、個人的に使いづらかったり、バグなのか使い方が悪かったのかアスペクト比が考慮されておらずエフェクトが細長くなってたりしたので自作しました。

ぽわ~んエフェクト
f:id:wkpn:20180316133548g:plain


まずはShader、

Shader "ShockWave"
{ 
    Properties 
    {
        _MainTex ("Base (RGB)", 2D) = "white" {}
        _TableTex ("DistortionTable", 2D) = "white" {}
    }
    SubShader
    {
        Pass
        {
            Cull Off ZWrite Off ZTest Always
            CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma fragmentoption ARB_precision_hint_fastest
#pragma target 3.0
#pragma glsl
#include "UnityCG.cginc"
            uniform sampler2D _MainTex;
            uniform sampler2D _TableTex;
            uniform float     _TimeOffset;
            uniform float     _PosX;
            uniform float     _PosY;
            uniform float     _ShineMag;
            uniform float     _DistortionMag;
            uniform float     _WidthRev;
            uniform float     _AspectRatio;
            struct appdata_t
            {
                float4 vertex   : POSITION;
                float4 color    : COLOR;
                float2 texcoord : TEXCOORD0;
            };
            struct v2f
            {
                float2 texcoord  : TEXCOORD0;
                float4 vertex   : SV_POSITION;
                float4 color    : COLOR;
            };
            v2f vert(appdata_t IN)
            {
                v2f OUT;
                OUT.vertex = UnityObjectToClipPos(IN.vertex);
                OUT.texcoord = IN.texcoord;
                OUT.color = IN.color;
                return OUT;
            }
            half4 _MainTex_ST;
            float4 frag(v2f i) : COLOR
            {
                float2 org_uv = UnityStereoScreenSpaceUVAdjust(i.texcoord, _MainTex_ST);
                float2 adjusted_uv = org_uv.xy;//距離を算出するため、アスペクト比を考慮したuv
                float2 wave_center = float2( _PosX, _PosY );

                //アスペクト比の考慮
                adjusted_uv.x *= _AspectRatio;
                wave_center.x *= _AspectRatio;

                float2 distortion_dir = adjusted_uv - wave_center;//歪み方向。より外側のテクスチャをpickする
                float dist = distance(adjusted_uv, wave_center);
                distortion_dir /= dist;//正規化
                float offset = dist * _WidthRev - _TimeOffset;

                float distortion = tex2D(_TableTex, float2( offset, 0.5 ) ).r;
                org_uv += distortion_dir * distortion * _DistortionMag;//歪ませる
                float4 color = tex2D(_MainTex, org_uv.xy) + _ShineMag * float4( distortion, distortion, distortion, 0.0 );//歪んだ元画像に光をプラス

                return color; 
            }
            ENDCG
        }
    }
}

指定した位置からの距離に応じて、画面を歪ませつつちょっと色を飛ばしています。
距離に応じてどの程度歪ませるかは _TableTex のテクスチャで指定します。

私は、こんな感じのテクスチャを使いました。
f:id:wkpn:20180316132823p:plain

次にC#スクリプト

using UnityEngine;
using System.Collections;
public class ShockWaveEffect : MonoBehaviour {
    [SerializeField]
    [Range(0.0f, 1.0f)]
    private float timer = 0.5f;

    [Range(-1.5f, 1.5f)]
    public float posX = 0.5f;

    [Range(-1.5f, 1.5f)]
    public float posY = 0.5f;

    [Range(0f, 10f)]
    public float duration = 1f;

    [SerializeField]
    [Range(0f, 2f)]
    protected float shine = 0.5f;

    [SerializeField]
    [Range(0f, 1f)]
    protected float distortion = 0.1f;

    [SerializeField]
    [Range(0f, 10f)]
    protected float width = 0.5f;

    [SerializeField]
    protected Material material;

    void OnRenderImage (RenderTexture sourceTexture, RenderTexture destTexture)
    {
        material.SetFloat("_TimeOffset", ( 4.0f * timer - 2.0f ) ); // -2 〜 2 まで動けば画面端で切れることもなかろう
        material.SetFloat("_PosX", posX);
        material.SetFloat("_PosY", posY);
        material.SetFloat("_ShineMag", shine);
        material.SetFloat("_DistortionMag", distortion);
        material.SetFloat("_WidthRev", 1.0f / width);
        material.SetFloat("_AspectRatio", sourceTexture.width/(float)sourceTexture.height );
        Graphics.Blit(sourceTexture, destTexture, material);
    }
    void Update ()
    {
        timer += Time.deltaTime * ( 1.0f / duration );
        if ( timer > 1.0f ){
            enabled=false;
        }
    }

    public void StartEffect(Vector3 world_pos)
    {
        var camera = GetComponent<Camera>();
        var screen_pos = camera.WorldToScreenPoint(world_pos);
        enabled=true;
        timer=0.0f;
        posX = ( screen_pos.x )/camera.pixelWidth;
        posY = ( screen_pos.y )/camera.pixelHeight;
    }

    [ContextMenu("startEffectTest")]
    private void startEffectTest()
    {
        StartEffect(Vector3.zero);
    }
}

使い方は、

  • c#スクリプトをカメラに引っ付ける
  • 先のシェーダーを元にMaterialを作る
  • MaterialのTableTexにテクスチャを当てる
  • カメラにひっつけたスクリプトにMaterialをassignする

あとは、StartEffect( ポジション )を呼べばぽわ~んっていうエフェクトが発生します。

はてブされたかどうかををSlackで通知するようにする

以下のサイトを参考にすることで簡単にできました。

www.komaroku.com

yatta47.hateblo.jp

基本的には、

http://b.hatena.ne.jp/entrylist?sort=eid&url=あなたのチェックしたいURL&mode=rss

で、いけるんですが、自分のサイトの場合だとhttps://を除いた形じゃないと駄目みたいで、

http://b.hatena.ne.jp/entrylist?sort=eid&url=dancingprogrammer.blogspot.com&mode=rss

としたらいけました。Slackへの登録は、

 /feed http://b.hatena.ne.jp/entrylist?sort=eid&url=dancingprogrammer.blogspot.com&mode=rss

っと打ち込むだけで登録されました。