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
することでビルドできるようになりました。
参考
github.com
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"); } }
スプラトゥーンでダメージを受けた時みたいな、でろでろエフェクト
スプラトゥーンでダメージを受けた時みたいな画面エフェクトを作りたくて作りました。
スプラトゥーンには及びませんが、だいたいこんな感じになりました。
でろでろエフェクト
テクスチャを調整したら、もっとスプラ感が出るんじゃないかと思います。
まずは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方向に移動するのを重ね合わせて見た目のランダム性が高まるようにしています。
ノイズ用のテクスチャは、こういう感じのを使っています。
このテクスチャはフォトショップのフィルターで、ノイズを加える -> 雲模様 -> 波型の歪み として作ったものを、レベル補正してコントラスト上げて、ボカして、端でちゃんとループするようにしたものです。
周辺を暗くする用のテクスチャは、こういう感じです。
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というアセットを買いました。
しかし、個人的に使いづらかったり、バグなのか使い方が悪かったのかアスペクト比が考慮されておらずエフェクトが細長くなってたりしたので自作しました。
ぽわ~んエフェクト
まずは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 のテクスチャで指定します。
私は、こんな感じのテクスチャを使いました。
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で通知するようにする
以下のサイトを参考にすることで簡単にできました。
基本的には、
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
っと打ち込むだけで登録されました。