エイバースの中の人

アプリとWEBサービスを作っているABARSのBLOGです。

Firebaseに格納した進行度をBigQueryで集計する

Firebaseのカスタムイベントに対して、カスタムキーでイベントを複数回送信すると、1ユーザに対して複数のイベントがレコードに登録されます。このデータを、FirebaseからBigQueryに転送した後、Redashで解析することを考えます。

このイベントが進行度の場合で、カスタムキーに対してint値を保つ場合に、各進行度に対して、何人のユーザがいるかを計測したい場合があります。その際、そのままカウントしてしまうと、1ユーザが複数回イベントを発行してしまっているため、ユーザ数が実際のユーザ数よりも多くなってしまいます。1ユーザが複数のイベントを発行していた場合、ユーザごとに最大の進行度を計算してから、カウントしたいです。

この目的を達成するには、以下のようなクエリを実行します。GROUP BYで user_dim.app_info.app_instance_idを指定して、MAX(event_dim.params.value.int_value) でまとめた後、COUNTします。

SELECT 
  some_int_value,
  EXACT_COUNT_DISTINCT( user_dim.app_info.app_instance_id ) as users
FROM
(
SELECT user_dim.app_info.app_instance_id,
       MAX(event_dim.params.value.int_value) AS some_int_value
FROM [xxx-xxx:xxx_ANDROID.app_events_xxx]
WHERE event_dim.name = "some_event_name"
  AND event_dim.params.key = "some_event_key"
GROUP BY user_dim.app_info.app_instance_id
)
GROUP BY
  1
ORDER BY 
  some_int_value

また、複数の日付のテーブルを結合して集計する場合は、FROMを以下のように書き換えます。

FROM 
(TABLE_DATE_RANGE([xxx-xxx:xxx_ANDROID.app_events_], 
                    TIMESTAMP('2017-07-04'), 
                    TIMESTAMP('2017-07-09')))

参考:BigQuery と Firebase Analytics によるモバイル アプリのカスタム分析

Gear VR 2016年版と2017年版の比較

Gear VR 2016年版(SM-R323)と2017年版(SM-R324)を比較してみました。

IMG_2323


こちらが2016年版です。アタッチメントが黒です。Galaxy S8は指で押すと、若干ですが、上下に動きます。

IMG_2322


こちらが2017年版です。アタッチメントが灰色です。Galaxy S8は、かっちりと固定され、指で押しても上下に動きません。

IMG_2321


当初は、USB-Cアタッチメントの差だけかと思っていたのですが、2017年版のUSB-Cアタッチメントを2016年版に装着してみても、上下のゆらぎは改善しませんでした。どうやら、スマートフォンを上下から挟み込む3連の出っ張りの高さも違うようです。

結論として、2016年版と2017年版は、筐体もアタッチメントも差分があるため、2017年版を購入した方がよいようです。

Galaxy S8でGear VRの熱問題は解決したか

Gear VRの最大の問題は熱です。Galaxy S6で動画を見ていると、15分程度で熱の警告が表示され、フレームレートが大きく低下します。

この問題は、Galaxy S6をファンで冷却することで改善することができます。具体的には、扇風機の前でプレイするか、サンワダイレクトのスマートフォンクーラーで冷却することで、改善します。

IMG_2300


しかし、サンワダイレクトのスマートフォンクーラーは、ホールド力が弱く、うまくGear VRに固定できず、ストラップを通して無理やり固定したため、視力調整が難しくなってしまいました。また、クーラー自体もMicro USBで充電しないといけないのが不便です。その結果、ほとんど使わず、扇風機で対処していました。

Galaxy S8では、SOCのプロセスの進化の恩恵で、劇的に熱問題が改善しています。以下の記事では、GearVRをサーマルカメラで計測したところ、同条件で、Galaxy S7が50度、Galaxy S8が39度まで改善することが示されています。

Galaxy S8 vs. Galaxy S7: Which is best for VR?

実際、Galaxy S8を使い始めた以降、動画・ゲーム・インターネットのいずれでも、一度も熱警告は発生していません。

Galaxy S6では負荷の低いアプリでも熱警告が頻発するため、Gear VRを使用する場合、Galaxy S6ではなく、Galaxy S8を選択するのがよさそうです。

Gear VRの2016年版をGalaxy S8で使えるか

Gear VRには2016年版と2017年版があります。2016年版の型番はSM-R323、2017年版の型番はSM-R324です。2017年版にはコントローラが付属します。2016年版のGear VRはGalaxy S8でも使用できるのでしょうか。

そもそも、Galaxy S8はUSB-Cです。Gear VRは、USB端子がアタッチメント方式になっており、Micro USBとUSB-Cを切り替えられるようになっているため、USB-Cアタッチメントがあれば接続できそうです。

2016年版には、USB-Cアタッチメントが付属するものと、付属しないものがあります。もともと、USB-CはGalaxy Note 7用のものであり、Galaxy Note 7のバッテリー問題以後、USB-Cのアタッチメントは同梱されなくなりました。そのため、Amazonやメルカリには、Gear VRの2016年版が安価に出品されていますが、USB-Cのアタッチメントが付属するかどうかは、事前に確認が必要です。

USB-Cのアタッチメントがあれば、接続できそうです。ということで、実際に接続してみました。

IMG_2314


接続したところ、Galaxy S8はGear VRを認識し、問題なく動作しました。視野的にも問題は感じず、Galaxy S6と同等の見栄えです。ただ、Gear VR本体とGalaxy S8のホルダーに若干、遊びがあるためか、Gear VRを強く降ると、若干、がたつきがあるように感じます。

海外のフォーラムで調査してみると、2016年版と2017年版では、数ミリcm程度、アタッチメントのサイズが異なるという情報がありました。

比較画像

若干のがたつきは、この数ミリが影響しているのかもしれません。

公式には、Galaxy S8で使用できるのはSM-R324だけとなっているため、基本的には2017年版の新型を使った方が良さそうです。

Galaxy S8の縦長画面におけるuGUIの動作

Galaxy S8では16:9を超える縦長になっています。縦長アプリの場合、uGUIのMatch Width Or Heightに1を設定し、横長になった場合に左右に余白を追加する方向に調整することが多いため、Galaxy S8では逆にマイナスの余白が発生し、見切れてしまうのではないかと心配していました。

しかし、その心配は杞憂で、実際にGalaxy S8でUnityで開発したアプリを動作させたところ、自動的にアプリは16:9の解像度で起動し、上下に黒の余白が入るようになっていました。Galaxy S8側で、アプリの互換性を保つよう、ホワイトリストで16:9表示にフォールバックするようです。

Galaxy S8:18.5:9の縦長ディスプレイはアプリごとに表示を変えられるので安心

Kindle Fire7(2017)とFire HD 8(2017)の比較

Amazonの格安タブレット、Kindle Fire 7とKindle Fire HD 8を両方買ってみました。

IMG_2303

スペック差は以下のようになります。

項目Fire 7Fire HD 8
価格(割引後)4980円7980円
CPUARM Cortex-A7 (quad-core, 1.3 GHz)ARM Cortex-A53 (64-bit quad-core, 1.3 GHz)
メモリ1GB1.5GB
容量8GB16GB
解像度1024 x 600 (171 ppi)1280 x 800 (189ppi)
スピーカーモノラルステレオ
重量295g369g

両方使用してみた印象としては、

・Fire7よりもFire HD8の方が動きが格段に滑らか
・Fire HD8の方がスピーカーの音質がよい
・Fire7の方がコンパクトで軽量

パフォーマンスはHD8の方が格段に快適でした。ただ、Fire 7もブラウザなど使わず、Prime Videoを見る専用機としてはとても優秀です。

PNGイメージ-CA80C2B20578-1

Kindleでコミックを読む場合、縦置きだとFire 7もHD8も十分な解像感で読むことができます。

IMG_2304

横置きだと、Fire 7だと解像度が不足して厳しいです。HD8だと、普通に読めます。

IMG_2305

Fire 7だと、横置きで文字が潰れます。

IMG_2307

Fire HD8だと、横置きでも文字が潰れません。

IMG_2306

写真だとFire HD8の方が黄色っぽく見えますが、実機だと特に違和感のある色ではありません。きちんと白に見えます。iPhone 7のカメラとの相性かもしれません。

IMG_2308

Fire OSはAndroid 5ベースです。初めてのFire OSですが、Kindle、Prime Videoなど、Amazonのサービスをアプリを起動せずに統合されたインタフェースでサクサク使えるのは、とても満足感があります。iOSだとブラウザからしかKindleの書籍が買えないのが、Kindleアプリの中でワンクリックで買えるのはとても快適です。

アプリは、Yahoo、Twitter、TDnetViewを入れたぐらいで、他はAmazonサービスに依存する感じです。iPad Pro 9.7も保有しているのですが、雑誌を読む以外の用途であれば、Fire HD8でも十分そうです。これが7980円で買えるのはすごいですね。

Amazon Primeは従来、年間3900円のプランしか選択できませんでしたが、先月から月額400円プランが出来ました。月額400円プランでも、Kindle Fireの4000円 OFFのクーポンは有効ですので、まずは月額400円プランでもよいかと思います。1ヶ月で退会しようと思っていたのですが、Amazon Prime Videoがあまりに便利なので、しばらく加入してしまいそうです。

個人的な理想は、Fire HD8のスペックのFire7ではあるので、HD7の登場を期待してはいます。それでも、現行世代でFire7とFire HD8を比べると、Fire HD8の方が格段にパフォーマンスがよいため、タブレット1台持ちならFire HD8がベストではないでしょうか。

Fire7は、iPad Proなどを保有していて、軽量なKindle + Prime Video専用端末を求めている場合や、普段と違うガジェットを触ってみたい場合に購入すると良さそうです。iPadを保有している場合、Fire HD8とサイズ感や用途が重複するため、Fire7の方がガジェット的な満足感は高いかと思います。




クーポンコード『PRIMEFIRE7』で4,000円OFF



クーポンコード『PRIMEFIREHD8』で4,000円OFF

以下、Prime Videoのオススメです。クーポンはシンゴジラに使いました。



充電は以下のマグネットケーブルを使っています。


gree/unity-webviewでバウンスを止める

Assets/Plugins/iOS/WebView.mmのinitWithGameObjectNameにおいて、以下のように、bounces = NOを代入すると、iOSのバウンスを止めることができます。

- (id)initWithGameObjectName:(const char *)gameObjectName_ transparent:(BOOL)transparent enableWKWebView:(BOOL)enableWKWebView
{
    self = [super init];

   ...
   //追加

    id subview = [[webView subviews] objectAtIndex:0];
    if([[subview class] isSubclassOfClass:[UIScrollView class]]){
        ((UIScrollView *)subview).bounces = NO; 
    }

    return self;
}

uGUIの上にパーティクルを表示する

UnityのuGUIのデフォルトでは、CanvasのRender ModeがScreen Space - Overlayに設定されています。Overlayの場合、UI要素は常に最前面に表示されるため、3DのオブジェクトはuGUIに隠れることになります。パーティクルも3Dでレンダリングされるため、uGUIの後ろに表示されます。

uGUIの上にパーティクルを表示したい場合、CanvasのRender ModeにScreen Space - Cameraを設定することになります。Screen Space - CameraではuGUIのレンダリング先プレーンのZ値を指定できるため、uGUIよりも大きなZ値でレンダリングすれば、パーティクルが前面に来ます。

しかし、3Dのゲームを作っており、3Dの背景の上にuGUIを表示する場合、Screen Space - Cameraを設定すると、Z値の大きな3D要素もuGUIの上に表示されてしまいます。パーティクルのみを前面に持ってくることができません。

この問題を解決するには、複数のカメラを使用します。まず、シーンに2つのカメラを配置します。1つめのカメラのCulling MaskからUIを外します。2つめのカメラのCulling MaskをUIのみにします。2つめのカメラのClear FlagsをDepth onlyにします。パーティクルのLayerをUIにします。

こうすると、背景の3Dのシーンを1つ目のカメラでレンダリングした後、Z値をクリアして、2つ目のカメラでUIとパーティクルがレンダリングされます。中間でZ値がクリアされるため、UI要素は常に3Dの上に表示することができます。

カメラを分けることで、好きなImage Effectをかけることもできるので便利です。

uGUIのボタンが押せるかどうか判定する

自動検証でランダムにuGUIのボタンを押したいことがあります。シーン内のボタンは、
 Button [] list=GameObject.FindObjectsOfType<Button> ();
で取得できるのですが、Panelなど、他のオブジェクトの下にあるボタンも検出してしまうため、そのままでは使えません。

特定のボタンが押せるかどうか判定するには、ボタンからスクリーン座標を求め、EventSystemからRaycastし、自分自身かどうかで判定します。ボタンからスクリーン座標を求めるには、親キャンバスを取得した上で、RectTransformUtility.WorldToScreenPointを使用する必要があります。

private static bool IsButtonClickable(Button item){
        //親キャンバスを取得
        Transform target=item.transform;
        while(target.GetComponent<Canvas>()==null){
            target=target.parent;
            if(target==null){
                return false;
            }
        }

        //親キャンバスからスクリーン座標を求める
        Vector2 position=RectTransformUtility.WorldToScreenPoint(target.GetComponent<Canvas>().worldCamera,item.gameObject.GetComponent<RectTransform>().position);

        //スクリーン座標からRayを飛ばす
        PointerEventData eventDataCurrentPosition = new PointerEventData(EventSystem.current);
        eventDataCurrentPosition.position = position;
        List<RaycastResult> results = new List<RaycastResult>();
        EventSystem.current.RaycastAll(eventDataCurrentPosition, results);

        //自分自身でない場合は他のUIの下なのでタップできない
        if(results.Count >= 1){
            if(results[0].gameObject.name!=item.gameObject.name){
                return false;
            }
        }else{
            return false;
        }

        //タップできる
        return true;
    }

Unity 5.5で使うAssetBundle

AssetBundleは、UnityのAssetを別ファイルに格納できる仕組みです。GooglePlayで配信できるapkファイルには100MBの制約があるため、アセットをAssetBundleに分離する必要があります。

まず、AssetBundleに格納するアセットをエディタで指定します。指定は、アセット単位でも、フォルダ単位でも行うことができます。フォルダに指定した場合、フォルダ内の全てのアセットが格納されます。尚、AssetBundleには階層構造が存在せず、最終階層のファイル名で識別されます。

assetbundle


AssetBundleをビルドするには、Editorスクリプトで実行する必要があります。AssetBundleにはコンパイル済みのシェーダが含まれるため、iOS向け、Android向けで異なるファイルが生成されます。Unity Cloud Buildでビルドした際のパスと合わせるため、StreamingAssets/AssetBundles/iOS or Androidに格納するのがオススメです。

using UnityEditor;
using UnityEngine;

using System.IO;

public class CreateAssetBundles
{
    [MenuItem("Assets/Build AssetBundles")]
    static void BuildAllAssetBundles()
    {
        var platform = "Standalone";
        #if UNITY_ANDROID
            platform="Android";
        #endif
        #if UNITY_IOS
            platform="iOS";
        #endif

        if (!Directory.Exists(Application.streamingAssetsPath+"/AssetBundles"))
        {
	        Directory.CreateDirectory(Application.streamingAssetsPath+"/AssetBundles");
	    }

        if (!Directory.Exists(Application.streamingAssetsPath+"/AssetBundles/"+platform))
        {
            Directory.CreateDirectory(Application.streamingAssetsPath+"/AssetBundles/"+platform);
        }

        Debug.Log("Build asset bundles for "+EditorUserBuildSettings.activeBuildTarget);
        BuildPipeline.BuildAssetBundles(Application.streamingAssetsPath+"/AssetBundles/"+platform, BuildAssetBundleOptions.None, EditorUserBuildSettings.activeBuildTarget);
    }
}


生成したAssetBundleは、 WWW.LoadFromCacheOrDownloadで読み込むことができます。 WWW.LoadFromCacheOrDownloadを使用した際、キャッシュに存在すればキャッシュから、存在しなければ指定したパスから読み込みます。キャッシュに存在するかどうかは、Caching.IsVersionCached(url,BUNDLE_VERSION)で取得することができ、キャッシュのクリアはCaching.CleanCache();で行うことができます。BUNDLE_VERSIONは固定値を入れることが推奨されています。BUNDLE_VERSIONを上げた場合、旧バージョンのアセットが消去されないため、キャッシュ容量が増加し続けるためです。そのため、crc値を書き換えることで、キャッシュクリアする方法が推奨されています。crc値は、生成したAssetBundleと同じフォルダにあるmanifestファイルに記載されています。

			// ダウンロード処理
			WWW www=null;
			if(CHECK_ASSET_BUNDLE_CRC){
				www = WWW.LoadFromCacheOrDownload(url, BUNDLE_VERSION, crc);
			}else{
				www = WWW.LoadFromCacheOrDownload(url, BUNDLE_VERSION);
			}
			while (!www.isDone)
			{
				progress_cnt=www.progress;
				yield return null;
			}

			// エラー処理
			if(!string.IsNullOrEmpty(www.error))
			{
				load_failed=true;
				errror_detail=url;
				Debug.Log(www.error);
				yield break;
			}

			// Asset Bundleをキャッシュ
			assetBundleCache[bundlename] = www.assetBundle;

			// リクエストは開放
			www.Dispose();


AssetBundleには、高圧縮低速のLZMA形式と、低圧縮高速のLZ4形式があります。WWW.LoadFromCacheOrDownloadでLZMA形式をロードすると、自動的にLZ4形式に変換してキャッシュします。そのため、AssetBundleはLZMA形式で生成して問題ありません。

AssetBundleには依存関係があります。とあるPrefabをAssetBundleに格納した場合、そのPrefabに紐付いたAssetが自動的に検索され、AssetBundleに格納されます。しかし、このままだと、複数のPrefabから参照されるオブジェクトが、複数のAssetBundleに格納されることになり、ファイル容量が増大してしまいます。この問題は、複数のPrefabから参照されるオブジェクトを、別のAssetBundleに格納することで回避できます。その場合、とあるPrefabのAssetBundle.LoadAssetを呼ぶまでに、複数のPrefabから参照されるオブジェクトを格納したAssetBundleが読み込まれている必要があり、その依存関係がDependenciesに記載されています。尚、AssetBundleを読み込む順番は、Dependencies順でなくてもかまいません。あくまで、AssetBundle.LoadAssetを呼ぶまでに依存関係が解決されていればよいです。そのため、WWW.LoadFromCacheOrDownloadをCoroutineで並列化して、高速化することができます。

ManifestFileVersion: 0
CRC: 903982090
Hashes:
  AssetFileHash:
    serializedVersion: 2
    Hash: 378b24330402254e95eb1568de8f4e56
  TypeTreeHash:
    serializedVersion: 2
    Hash: a3429469e80eaecda247646a582aa377
HashAppended: 0
ClassTypes:
- Class: 1
  Script: {instanceID: 0}
Assets:
- Assets/AssersBundleResources/***/***.prefab
Dependencies:
- AssetBundles/iOS/models_***


AssetBundleからGameObjectを取得するには、Resources.Loadと同様に、assetBundle.LoadAssetで取得可能です。その際、階層構造の指定はできず、最終的なファイル名でロードします。

	// Asset BundleからGameObjectを取得
	public GameObject GetObject(string assetbundle_id,string assetName){
		try
		{
			GameObject obj=assetBundleCache[assetbundle_id].LoadAsset(string.Format("{0}", assetName));
			if(obj==null){
				Debug.Log(""+assetbundle_id+" "+assetName+" not found");
			}

	#if UNITY_EDITOR
			if(obj!=null){
				if(obj.GetComponent()==null){
					obj.AddComponent();
				}
			}
	#endif
			return obj;
		}
		catch (NullReferenceException e)
		{
			Debug.Log(e.ToString());
			return null;
		}


作成したAssetBundleは、manifestファイルと一緒に、CDNなどに上げて、ランタイムでロードします。

CDNのデータは配信の過程で破損する可能性があります。アセットバンドルが破損していた場合、本来はstring.IsNullOrEmpty(www.error)でエラーを検出することができるはずです。しかし、実際に破損したファイルを生成して読み込ませたところ、Windows、Android、Macでは正常にエラーを検出できますが、iOSの場合、エラーが返ってきません。そのため、www.assetBundle.GetAllAssetNames()を呼び出すことで、アセットバンドルが正しく読み込めたかどうかの確認が必要です。

// iOSで破損ファイルがエラーチェックを抜けるのでアセットリストを取得して例外が起きないか確認
string[] asset_list;
try{
	asset_list=www.assetBundle.GetAllAssetNames();
	if(asset_list.Length>=1){
		Debug.Log("Asset Load Check Success : "+asset_list[0]);
	}
}catch(Exception e){
	is_error=true;
	Debug.Log("Asset Load Check Failed : "+e);
}

AssetBundleでしか参照しないコードが存在すると例外が起きる

AssetBundleでしか参照しないコードが存在し、iOSビルドの設定のStrip Engine CodeがONの場合、AssetBundleからGameObjectを取得しようとした際に、PersistentManager.cppでEXC_BAD_ACCESSの例外が飛びます。対策としては、Strip Engine CodeをOFFにするとよいようです。

AssetBundleにPrefabを入れるとShaderがMissingになる

AssetBundleに敵のモデルのPrefabを入れてロードした場合、iOSの実機では動きますが、EditorではShaderがMissingになり、黒くなったり、ピンクになったりします。

本質的には、iOS向けに書き出したAssetBundleに含まれるコンパイル済みシェーダが、EDITORに対応していないのが問題なのですが、AssetBundleをEDITOR向けに書き出すのはコストが高いです。

そこで、We're looking for feedback on the artist features in the 2017.1 beta, help us out by filling outを参考に、以下のスクリプトでシェーダを当て直すとよいようです。GetComponentsInChildrenにtrueを入れることで、非アクティブのオブジェクトにも適用するように修正しています。

using UnityEngine;
using System.Collections;
 
 
public class ReApplyShaders : MonoBehaviour
{
    public Renderer[] renderers;
    public Material[] materials;
    public string[] shaders;
 
    void Awake()
    {
        renderers = GetComponentsInChildren(true);
    }
 
    void Start ()
    {
        foreach(var rend in renderers)
        {
            materials = rend.sharedMaterials;
            shaders =  new string[materials.Length];
 
            for( int i = 0; i < materials.Length; i++)
            {
                shaders[i] = materials[i].shader.name;
            }        
 
            for( int i = 0; i < materials.Length; i++)
            {
                materials[i].shader = Shader.Find(shaders[i]);
            }
        }
    }
}

Unity Cloud BuildのAsset Bundleの格納先

Unity Cloud Buildを使用してビルドする際、Asset Bundleを同時にビルドして、Streaming Assetsに格納することができます。

Unity Cloud Buildのビルドタイプのアセットバンドルの設定から、Build Asset BundlesとCopy to Streaming Assetsを有効にします。

asset_bundle_oath


ビルド済のapkファイルとipaファイルの拡張子をzipに変更して展開すると、以下のフォルダにアセットバンドルが格納されます。

iOS : Payload/app/Data/Raw/AssetBundles/iOS
Android : assets/AssetBundles/Android

従って、以下のパスでアクセスすることができます。

iOS : Application.streamingAssetsPath + "/AssetBundles/iOS"
Android : Application.streamingAssetsPath + "/AssetBundles/Android"

Mac Pro 2013でLG Ultrafine 5k displayを使う

Mac Fan 2017年5月号の35ページに記載のとおり、macOS 10.12.3から、Mac Pro 2013でLG Ultrafine 5k displayが5k解像度で使用できるようになりました。Mac Pro 2013はThunderbolt2しか搭載しておらず、5kを出すにはThunderbolt3の帯域が必要なはずなのですが、ハードウェアを限定できるAppleの強みで規格外の信号を流しているのか、なぜか繋がるようになったようです。

ということで、実際につなげてみました。

IMG_2223


必要なものは、Apple アップル純正 Thunderbolt 3(USB-C)- Thunderbolt 2アダプタと、Apple Thunderboltケーブルです。Mac Pro 2013にThunderboltケーブルを刺し、Thunderbolt3 - 2アダプタをLG Ultrafine 5k displayに接続します。それだけで何の問題もなく、5k表示できました。

LG Ultrafine 5k displayはマットブラックの質感がとても良く、まるでApple純正ディスプレイのように完成度が高いです。以前は、DELLの4kディスプレイを使っていたのですが、スリープからの復帰が50%程度の確率で失敗して、ディスプレイの電源をON/OFFして復帰させるのがとても面倒でした。LG Ultrafine 5k displayに変えてからは、スリープからの復帰に失敗することは一度もなく、とても快適につかえています。

一点、ボリュームの1メモリの差がとても大きすぎて、ボリューム調整でAlt + Shift + ボリュームボタンを使わないといけないのが気になるくらいですね。

いいディスプレイです。





株式データをBigQueryとDataStudioで分析してみた

GoogleのBigQueryに企業の時価総額とバランスシートを入力して、GoogleDataStudioで分析してみました。使用した元データは、TDnetSearchで収集したものを、BigQueryのStreamingAPIで書き込んだ後、以下のSQLで最新の四半期を抽出しています。Show OptionsでUse Legacy SQLのチェックを外す必要がありました。

SELECT *
FROM AppEngine.Xbrl AS m
WHERE NOT EXISTS (
    SELECT 1
    FROM AppEngine.Xbrl AS s
    WHERE m.code = s.code
    AND m.sec < s.sec
);

以下、分析結果です。分析対象は3426社です。画像はクリックで大きくなります。
pbr_per

横軸がPBR、縦軸がPERの、東洋経済などでたまに見るグラフです。左下にいくほど、割安です。経営危機にゆれるタカタが目立ちますが、企業数が多すぎてよくわからない感じになりますね。

nnr_roe

横軸がネットネット倍率、縦軸がROEのグラフです。バリュー株を分析するには、こちらの方が見やすいですね。こちらは、右上にいくほど、割安です。図書印刷が特出していますが、リクルート株売却に伴う投資有価証券売却益によるものなので、持続性は低いですね。ただ、凸版印刷によるTOBの可能性もある気はするので、妙味はあります。他、横浜丸魚と丸八ホールディングス、知多鋼、キクカワあたりが割安そうな気がします。小田原機械も安いのですが、開発投資が膨らんでいるのが気になりますね。

marketcap_per

横軸が時価総額、縦軸がPERのグラフです。小型株でないと、極端に低いPERは得られないことがわかります。

分析に使用したデータのCSVを以下に置きましたので、インポートして、Google Big QueryとGoogle Data Studioで使ってみることもできます。使用方法は、”Google Data Studioを使ってGoogle先生の分析エコシステムにいいように取り込まれる”がオススメです。カラム名の日本語対訳はTDnetSearchを参照して下さい。

tdnetsearch_20170418.csv (トヨタなどが取得できていなかったので差し替えました)

合わせて、TDnetSearchでも分析グラフを表示する機能を追加しました。こちらもぜひご利用下さい。

追加で、1年前の時価総額と、今年の時価総額の変化率も計測しました。クエリは以下。

SELECT oldest.*, latest.marketcap/oldest.marketcap AS rate_of_marketcap_increase

FROM 
( SELECT * FROM AppEngine.Xbrl AS m
WHERE NOT EXISTS (
    SELECT 1
    FROM AppEngine.Xbrl AS s
    WHERE m.code = s.code
    AND m.sec < s.sec
) ) AS latest 
JOIN 
(SELECT * FROM AppEngine.Xbrl AS m2
WHERE NOT EXISTS (
    SELECT 1
    FROM AppEngine.Xbrl AS s2
    WHERE m2.code = s2.code
    AND m2.sec > s2.sec
))  AS oldest
ON latest.code = oldest.code

nnr_marketcap

ネットネット倍率に対する、2016年から2017年の時価総額の増加率です。1年という短い期間だと、水準訂正のカタリストは起きにくいようですね。ただ、下値が抑えられているという傾向は見て取れます。

Unity 5.5.3f1でPNGのアルファ値が化ける

Unity 5.5.3f1で、インデックスPNGを使用すると、アルファ値が正しく取得できず、不透過のPNGとして扱われるようです。インデックスPNGは使用しない方がよさそうです。

Unity Cloud Buildでは、テクスチャのキャッシュが使用されるため、Unity 5.5.3fに切り替わった直後には気が付かず、キャッシュクリアされたReimportされた後に、問題が発生するため、Unity 5.5.3fが原因かの切り分けが難しくなっています。

尚、Unity 5.5.3p1で治っているようです。

(891365) - TextureImporter: Fixed a bug with some types of PNG files caused the texture importer to not detect the alpha channel properly. (Please re-import the affected assets to fix them.)

https://unity3d.com/jp/unity/qa/patch-releases

ただし、Unity 5.5.3p1はまだUnity Cloud Buildで使えないようです。

Unity 5.5.2のAndroidにおいてApplication.Quitで例外が起きる

Unity 5.5.2のAndroidでアプリケーションからApplication.Quitを呼ぶと、Galaxyなど特定の機種において、libcで例外が起き、アプリケーションが停止されましたと表示されます。Unity 5.6では起きません。

対策としては、Application.Quitではなく、moveTaskToBackを呼ぶとよいようです。

#if UNITY_ANDROID
if (Input.GetKeyDown(KeyCode.Escape)){
	AndroidJavaObject activity = new AndroidJavaClass("com.unity3d.player.UnityPlayer").
	GetStatic("currentActivity");
	activity.Call("moveTaskToBack" , true);
	return;
}
#endif

海外のフォーラムでは、StartCoroutineで描画終了を待てば改善するとありましたが、試してみても改善しませんでした。

UnityのAndroidビルドでx86のNative Pluginがapkに含まれない

UnityのAssets/Plugins/Android/x86に.soを置いてもUnityのInspectorが認識してくれず、apkに含まれないため、ランタイムエラーになります。

解決方法として、Assets/Plugins/Android/libs/armeabi-v7a、Assets/Plugins/Android/libs/x86に.soを置いた上で、ReimportしないとInspectorに表示されず、apkに含まれないようです。

一度、このパスに配置してmetaファイルが生成されると、自由に移動可能になります。

MacでPaintsChainerを動かす

PaintsChainerがすごすぎたので、ソースコードをダウンロードしてMacで動かしてみました。

paints_chainer


手順としては、PaintsChainerをCloneした後、学習済みデータをcgi-bin/paint_x2_unet/models/に置きます。

次に、Chainerをpip install chainerでインストールします。

PaintsChainerのPython 3.0に対して、MacのデフォルトのPythonは2.7なので、server.pyのhttp.serverをBaseHTTPServerに書き換える必要があります。


diff --git a/server.py b/server.py
index 52b37be..d6622a5 100644
--- a/server.py
+++ b/server.py
@@ -1,7 +1,11 @@
#!/usr/bin/env python

-import http.server
-import socketserver
+#import http.server
+#import socketserver
+#from urllib.parse import parse_qs
+
+import BaseHTTPServer
+import CGIHTTPServer

import os, sys
import base64
@@ -10,7 +14,6 @@ import json
import argparse

from cgi import parse_header, parse_multipart
-from urllib.parse import parse_qs


#sys.path.append('./cgi-bin/wnet')
@@ -19,13 +22,13 @@ import cgi_exe



-class MyHandler(http.server.CGIHTTPRequestHandler):
+class MyHandler(CGIHTTPServer.CGIHTTPRequestHandler):
def __init__(self,req,client_addr,server):
- http.server.CGIHTTPRequestHandler.__init__(self,req,client_addr,server)
+ CGIHTTPServer.CGIHTTPRequestHandler.__init__(self,req,client_addr,server)

def parse_POST(self):
ctype, pdict = parse_header(self.headers['content-type'])
- pdict['boundary'] = bytes(pdict['boundary'], "utf-8")
+ #pdict['boundary'] = bytes(pdict['boundary'], "utf-8")
if ctype == 'multipart/form-data':
postvars = parse_multipart(self.rfile, pdict)
elif ctype == 'application/x-www-form-urlencoded':
@@ -80,7 +83,10 @@ class MyHandler(http.server.CGIHTTPRequestHandler):
else:
paintor.colorize(id_str, blur=blur)

- content = bytes("{ 'message':'The command Completed Successfully' , 'Status':'200 OK','success':true , 'used':"+str(args.gpu)+"}", "UTF-8")
+ #content = bytes("{ 'message':'The command Completed Successfully' , 'Status':'200 OK','success':true , 'used':"+str(args.gpu)+"}", "UTF-8")
+
+ content = "{ 'message':'The command Completed Successfully' , 'Status':'200 OK','success':true , 'used':"+str(args.gpu)+"}"
+
self.send_response(200)
self.send_header("Content-type","application/json")
self.send_header("Content-Length", len(content))
@@ -102,7 +108,9 @@ print('GPU: {}'.format(args.gpu))

paintor = cgi_exe.Paintor( gpu = args.gpu )

-httpd = http.server.HTTPServer(( args.host, args.port ), MyHandler)
+#httpd = http.server.HTTPServer(( args.host, args.port ), MyHandler)
+httpd = BaseHTTPServer.HTTPServer((args.host, args.port ), MyHandler)
+
print('serving at', args.host, ':', args.port, )
httpd.serve_forever()


また、MacはAMDのGPUを搭載しておりCUDAが動かないので、GPUを切ります。cuda.と.get()をコメントアウトします。


diff --git a/cgi-bin/paint_x2_unet/cgi_exe.py b/cgi-bin/paint_x2_unet/cgi_exe.py
index 33c381b..86bc4d7 100755
--- a/cgi-bin/paint_x2_unet/cgi_exe.py
+++ b/cgi-bin/paint_x2_unet/cgi_exe.py
@@ -30,11 +30,11 @@ class Paintor:
self.gpu = gpu

print("load model")
- cuda.get_device(self.gpu).use()
+ #cuda.get_device(self.gpu).use()
self.cnn_128 = unet.UNET()
self.cnn = unet.UNET()
- self.cnn_128.to_gpu()
- self.cnn.to_gpu()
+ #self.cnn_128.to_gpu()
+ #self.cnn.to_gpu()
lnn = lnet.LNET()
#serializers.load_npz("./cgi-bin/wnet/models/model_cnn_128_df_4", cnn_128)
#serializers.load_npz("./cgi-bin/paint_x2_unet/models/model_cnn_128_f3_2", cnn_128)
@@ -56,7 +56,7 @@ class Paintor:


def liner(self, id_str):
- cuda.get_device(self.gpu).use()
+ #cuda.get_device(self.gpu).use()

image1 = cv2.imread(path1, cv2.IMREAD_GRAYSCALE)
image1 = np.asarray(image1,self._dtype)
@@ -64,16 +64,16 @@ class Paintor:
image1 = image1[:, :, np.newaxis]
img = image1.transpose(2, 0, 1)
x = np.zeros((1, 3, img.shape[1], img.shape[2] )).astype('f')
- x = cuda.to_gpu(x)
+ #x = cuda.to_gpu(x)

y = lnn.calc(Variable(x), test=True)
- output = y.data.get()
+ output = y.data#.get()

self.save_as_img( output[0], self.root + "line/"+id_str+".jpg" )


def colorize_s( self, id_str, blur=0, s_size=128):
- cuda.get_device(self.gpu).use()
+ #cuda.get_device(self.gpu).use()

dataset = ImageAndRefDataset([id_str+".png"],self.root+"line/",self.root+"ref/" )
test_in_s, test_in = dataset.get_example(0, minimize=True, blur=blur, s_size=s_size)
@@ -81,30 +81,30 @@ class Paintor:

x[0,:] = test_in_s

- x = cuda.to_gpu(x)
+ #x = cuda.to_gpu(x)
y = self.cnn_128.calc(Variable(x), test=True )
- output = y.data.get()
+ output = y.data#.get()

self.save_as_img( output[0], self.outdir_min + id_str + ".png" )

def colorize_l( self, id_str ):
- cuda.get_device(self.gpu).use()
+ #cuda.get_device(self.gpu).use()

dataset = ImageAndRefDataset([id_str+".png"],self.root+"line/",self.root+"out_min/" )
test_in, test_in_ = dataset.get_example(0,minimize=False)
x = np.zeros((1, 4, test_in.shape[1], test_in.shape[2] )).astype('f')
x[0,:] = test_in

- x = cuda.to_gpu(x)
+ #x = cuda.to_gpu(x)
y = self.cnn.calc(Variable(x), test=True )

- output = y.data.get()
+ output = y.data#.get()

self.save_as_img( output[0], self.outdir + id_str + ".jpg" )


def colorize( self, id_str, blur=0, s_size=128):
- cuda.get_device(self.gpu).use()
+ #cuda.get_device(self.gpu).use()

dataset = ImageAndRefDataset([id_str+".png"],self.root+"line/",self.root+"ref/" )
test_in_s, test_in = dataset.get_example(0,minimize=True)
@@ -117,20 +117,21 @@ class Paintor:
x[0,:] = line
input_bat[0,0,:] = line2

- x = cuda.to_gpu(x)
+ #x = cuda.to_gpu(x)
y = self.cnn_128.calc(Variable(x), test=True )

- output = y.data.get()
+ output = y.data#.get()

self.save_as_img( output[0], self.outdir_min + id_str +"_"+ str(0) + ".png" )

for ch in range(3):
input_bat[0,1+ch,:] = cv2.resize(output[0,ch,:], (test_in.shape[2], test_in.shape[1]), interpolation = cv2.INTER_CUBIC)

- x = cuda.to_gpu(input_bat)
+ #x = cuda.to_gpu(input_bat)
+ x = input_bat
y = self.cnn.calc(Variable(x), test=True )

- output = y.data.get()
+ output = y.data#.get()

self.save_as_img( output[0], self.outdir + id_str +"_"+ str(0) + ".jpg" )

diff --git a/cgi-bin/paint_x2_unet/img2imgDataset.py b/cgi-bin/paint_x2_unet/img2imgDataset.py
index d26f580..e98238e 100755
--- a/cgi-bin/paint_x2_unet/img2imgDataset.py
+++ b/cgi-bin/paint_x2_unet/img2imgDataset.py
@@ -41,11 +41,11 @@ class ImageAndRefDataset(chainer.dataset.DatasetMixin):
if minimize :
if image1.shape[0] < image1.shape[1]:
s0 = s_size
- s1 = int( image1.shape[1]*(s_size/image1.shape[0]) )
+ s1 = int( image1.shape[1]*(1.0*s_size/image1.shape[0]) )
s1 = s1-s1%16
else:
s1 = s_size
- s0 = int( image1.shape[0]*(s_size/image1.shape[1]) )
+ s0 = int( image1.shape[0]*(1.0*s_size/image1.shape[1]) )
s0 = s0-s0%16

_image1 = image1.copy()


後は、python server.pyで動作します。ブラウザからhttp://localhost:8000/static/などでアクセスします。

推論側はGPU無効でも数十秒で返ってくるため、クライアントサイドでも動かせそうな印象です。ただ、学習済みデータが134.3MBもあるので、これをそのままクライアントに転送するのが難しそうですね。負荷自体は低いので、アプリ化などは容易そうです。

テクノロジーは元記事が詳しいです。

学習側は、画像をYUV変換して、Yをベースに線に変換しているようです。また、乱数で2値化したり、左右反転したり、ノイズを足したりして、データ量を増やしていますね。

ネットワークの構造はU-netでした。Batch Normalization重要そうです。評価関数は元画像との二乗誤差+アドバイサリーロスを10:1で混合。アドバイサリーロスについては、生成した画像が偽物であると推定させることで、競わせるようです。ソースコードではtrain.pyでCNNが生成した画像をy_fake、正解画像をy_realとして、アドバイサリーネットに入力し、見分けられない画像を生成させようとしています。

ユーザによる色の入力はどう実装しているか不思議だったのですが、線画に元データからランダムに色付きピクセルを追加したものを学習データとしているようです。

いきなり512x512にしないで、128x128を経由しているのは過学習対策なんですかね。ノウハウがありそうです。

Chainerの係数列はNumPyのnpzフォーマットのようです。ダンプしてみたい。

これだけ少ないコード量で、これほどのサービスが作れるのは驚きです。ただ、ソースコードにうまく学習させるためのノウハウが散りばめられており、一朝一夕ではないなという印象です。

Firebase for UnityをUnity Cloud Buildで使用する

-------------------------------------------
2017/8/26追記
Firebase for Unity 4.1.0を使用することで、特別な設定をすることなくUnity Cloud Buildが使用できるようになりました。以下の記事はFirebase for Unity1.1の情報です。

2017/9/11追記
"-lz}"のリンカエラーが発生するようになったので、結局、同様の手順で手動で入れました。Unity Cloud Buildでエラーになった際の解決法 vol3もとても参考になります。
-------------------------------------------

Firebase Analytics + Push Notificationにおける、ローカル環境でのLinker Errorの解消方法とUnity Cloud Buildでの使用方法をまとめました。

Firebase for Unityのインポート

FirebaseのUnity PackageはDownload the SDKからダウンロードすることができます。今回は、Push Notificationを実装するため、FirebaseAnalyticsとFirebaseMessagingの2つのUnity Packageをプロジェクトにインポートします。

firebase_unity


いきなり本番のプロジェクトに導入するのはリスクがあるので、サンプルプログラムをダウンロードして、そこにUnity Packageをインポートするのがオススメです。

iOSでPush通知を使用するには、APNs の SSL 証明書のプロビジョニングが必要です。AppleのProvision Portalで取得した証明書をp12に変換し、Firebaseのサイトに登録します。

その後、Firebaseのサイトから、プロジェクトのgoogle-services.jsonとGoogleService-Info.plistをダウンロードし、UnityのAssetsの中の好きなフォルダに登録します。

これで、Push通知が使用できるようになります。

ローカル環境でのLinker Error

iOSビルドでLinker Errorが出る場合は、cocoapodsのバージョンを確認します。コマンドラインからpod --versionとすると、バージョンが表示されますが、これが1.0以降でない場合に、Firebaseがうまくリンクされません。

Unityは、Xcodeのプロジェクトファイルを生成する際、Podfileが存在する場合にpodコマンドを叩いてくれます。Firebaseはpodコマンドを使って依存ライブラリをダウンロードするのですが、podコマンドに失敗してエラーが発生した場合もプロジェクトファイルが生成されてしまうため、ビルドエラーが発生するようです。

podのバージョンが古い場合は、gem update cocoapodsでバージョンアップが可能です。

Unity Cloud BuildでのLinker Error

ローカルでビルドするにはこれだけでOKですが、Unity Cloud Buildではcocoapodsが使えないため、以下のエラーが出ます。

Error running cocoapods. Please ensure you have at least version 1.0.0.  You can install cocoapods with the Ruby gem package manager:

この場合、CocoaPods を使用せずに統合するからzipをダウンロードし、手動でFrameworksフォルダのFirebaseMessaging.frameworkなどをPlugin/iOSフォルダに置く必要があります。

copy


コピーする必要のあるファイルです。

## Analytics
  - FirebaseAnalytics.framework
  - FirebaseInstanceID.framework
  - GoogleInterchangeUtilities.framework
  - GoogleSymbolUtilities.framework
  - GoogleUtilities.framework
## AdMob (~> Analytics)
  - GoogleMobileAds.framework
## AppIndexing (~> Analytics)
  - FirebaseAppIndexing.framework
## Auth (~> Analytics)
  - FirebaseAuth.framework
  - GoogleNetworkingUtilities.framework
  - GoogleParsingUtilities.framework
## Crash (~> Analytics)
  - FirebaseCrash.framework
## Database (~> Analytics)
  - FirebaseDatabase.framework
## DynamicLinks (~> Analytics)
  - FirebaseDynamicLinks.framework
## Invites (~> Analytics)
  - FirebaseDynamicLinks.framework
  - FirebaseInvites.framework
  - GoogleAppUtilities.framework
  - GoogleAuthUtilities.framework
  - GoogleNetworkingUtilities.framework
  - GoogleParsingUtilities.framework
  - GooglePlusUtilities.framework
  - GoogleSignIn.framework

  You'll also need to add the resources in the
  Resources directory into your target's main
  bundle.
## Messaging (~> Analytics)
  - FirebaseMessaging.framework
  - GoogleIPhoneUtilities.framework
## RemoteConfig (~> Analytics)
  - FirebaseRemoteConfig.framework
  - GoogleIPhoneUtilities.framework
## Storage (~> Analytics)
  - FirebaseStorage.framework
  - GoogleNetworkingUtilities.framework

その上で、Firebase/Editor/AppDeps.csなど*Deps.csから以下のようにpodの呼び出しをコメントアウトします。

#elif UNITY_IOS
/*
        Type iosResolver = Google.VersionHandler.FindClass(
            "Google.IOSResolver", "Google.IOSResolver");
        if (iosResolver == null) {
            return;
        }
        Google.VersionHandler.InvokeStaticMethod(iosResolver, "AddPod", new object[] { "Firebase/Core" }, new Dictionary() { { "version", "3.10+" }, { "minTargetSdk", "7.0" } });
*/
#endif
また、Firebaseはsqlite3とAddressBook.frameworkを要求するのでEditorフォルダに以下のスクリプトを追加し、自動化します。-ObjCがないと実行時に例外が飛ぶので、これも追加します。
using UnityEngine;
using UnityEditor;
using UnityEditor.Callbacks;
#if UNITY_IOS
using UnityEditor.iOS.Xcode;
using System.Collections.Generic;
#endif
using System.IO;

public class PostBuildProcess {

    [PostProcessBuild]
    public static void OnPostProcessBuild (BuildTarget buildTarget, string path) {
#if UNITY_IOS
        string projPath = Path.Combine (path, "Unity-iPhone.xcodeproj/project.pbxproj");

        PBXProject proj = new PBXProject ();
        proj.ReadFromString (File.ReadAllText (projPath));

        string target = proj.TargetGuidByName ("Unity-iPhone");
        proj.AddBuildProperty (target, "OTHER_LDFLAGS", "-lz");
        proj.AddBuildProperty (target, "OTHER_LDFLAGS", "-lsqlite3");
        proj.AddBuildProperty (target, "OTHER_LDFLAGS", "-ObjC");

        List frameworks = new List () {
            "CoreData.framework",
            "AddressBook.framework"
        };

        foreach (var framework in frameworks) {
            proj.AddFrameworkToProject (target, framework, false);
        }

        File.WriteAllText (projPath, proj.WriteToString ());
#endif
    }
}

この段階で、Firebase Analyticsは動作するのですが、Firebase Messagingは実行時に例外で落ちます。どうやら、Unity Pluginと公式サイトのzipのFrameworkのバージョンが異なることが原因のようです。そのため、Firebase Messagingを使用する場合は、ローカルでcocoapodsを使用してiOSビルドした後、プロジェクトのFrameworksフォルダに含まれるFirebaseのFrameworkで、zipからコピーしたFrameworkを置き換える必要があります。

copy


また、GTMDefines.h、GTMLogger.h、GTMLogger.m、GTMNSData+zlib.h、GTMNSData+zlib.mもPlugins/iOSにコピーした上で、InspectorでCompile Flagsに-fno-objc-arc -fobjc-exceptionsと記載する必要があります。

compile_flag


Androidでは、UnityでAndroid Buildに変更して、Assets -> Play Service Resolver -> Android Resolver -> Resolve Client Jarsを実行しておきます。

これでようやく、UnityでFirebase AnalyticsとNotificationを使用することができます。

firebase


XcodeのCapabilityにPush NotificationとBackground Modes -> Remote notificationを追加すると、Firebaseから発行した通知を受け取ることができます。ただし、Info.plistにFirebaseAppDelegateProxyEnabled=NOを設定しないと、アプリがバックグラウンドに回った後、フォアグラウンドに回ると、100%ハングします。

IMG_1873


XcodeのCapability設定とFirebaseAppDelegateProxyEnabledの設定をUnity Cloud Buildで自動化するには、Unityから自動でPush NotificationsをONにしたいを参考に、Editorスクリプトを作成する必要があります。

//Unity Cloud BuildでiOS Buildにfirebaseの依存ファイルを追加する
//加えて、CapabilityにPush Notificationを追加する

using UnityEngine;
using UnityEditor;
using UnityEditor.Callbacks;
#if UNITY_IOS
using UnityEditor.iOS.Xcode;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml;
#endif

public class PostBuildProcess {

    [PostProcessBuild]
    public static void OnPostProcessBuild (BuildTarget buildTarget, string path) {
#if UNITY_IOS
        string projPath = Path.Combine (path, "Unity-iPhone.xcodeproj/project.pbxproj");

        PBXProject proj = new PBXProject ();
        proj.ReadFromString (File.ReadAllText (projPath));

        string target = proj.TargetGuidByName ("Unity-iPhone");
        proj.AddBuildProperty (target, "OTHER_LDFLAGS", "-lz");
        proj.AddBuildProperty (target, "OTHER_LDFLAGS", "-ObjC");
        proj.AddBuildProperty (target, "OTHER_LDFLAGS", "-lc++");
        proj.AddBuildProperty (target, "OTHER_LDFLAGS", "-lsqlite3");

        List frameworks = new List () {
            "CoreData.framework",
            "AddressBook.framework"
        };

        foreach (var framework in frameworks) {
            proj.AddFrameworkToProject (target, framework, false);
        }

        //Add
        File.WriteAllText (projPath, proj.WriteToString ());

        //Set Push Notification Capability
        CreateEntitlements(path,YourAppName);
        SetCapabilities(path);
        SetBackgroundMode(path);
#endif
    }

#if UNITY_IOS
    private static void SetBackgroundMode(string path)
    {
        var plistPath = Path.Combine(path, "Info.plist");

        PlistDocument plist = new PlistDocument();
        plist.ReadFromFile(plistPath);

        plist.root.SetBoolean("FirebaseAppDelegateProxyEnabled",false);

        PlistElementArray bgModes = plist.root.CreateArray("UIBackgroundModes");
        bgModes.AddString("remote-notification");

        plist.WriteToFile(plistPath);
    }

    private static void CreateEntitlements(string path,string your_appname)
    {
        XmlDocument document = new XmlDocument ();
        XmlDocumentType doctype = document.CreateDocumentType ("plist", "-//Apple//DTD PLIST 1.0//EN", "http://www.apple.com/DTDs/PropertyList-1.0.dtd", null);
        document.AppendChild (doctype);

        XmlElement plist = document.CreateElement ("plist");
        plist.SetAttribute ("version", "1.0");
        XmlElement dict = document.CreateElement ("dict");
        plist.AppendChild (dict);
        document.AppendChild (plist);

        XmlElement e = (XmlElement) document.SelectSingleNode ("/plist/dict");

        XmlElement key = document.CreateElement ("key");
        key.InnerText = "aps-environment";
        e.AppendChild (key);

        XmlElement value = document.CreateElement ("string");
        value.InnerText = "development";
        e.AppendChild (value);

        string entitlementsPath = path + "/Unity-iPhone/"+your_appname+".entitlements";
        document.Save(entitlementsPath);

        string projPath = path + "/Unity-iPhone.xcodeproj/project.pbxproj";
        PBXProject proj = new PBXProject ();
        proj.ReadFromString (File.ReadAllText (projPath));
        string target = proj.TargetGuidByName ("Unity-iPhone");
        string guid = proj.AddFile (entitlementsPath, entitlementsPath);
        proj.SetBuildProperty(target, "CODE_SIGN_ENTITLEMENTS", "Unity-iPhone/"+your_appname+".entitlements");
        proj.AddFileToBuild(target, guid);
        proj.WriteToFile(projPath);
    }

    private static void SetCapabilities(string path)
    {
        string projPath = path + "/Unity-iPhone.xcodeproj/project.pbxproj";
        PBXProject proj = new PBXProject ();
        proj.ReadFromString (File.ReadAllText (projPath));

        string[] lines = proj.WriteToString().Split ('\n');
        List newLines = new List ();
        bool editFinish = false;

        for (int i = 0; i < lines.Length; i++) {

            string line = lines [i];

            if (editFinish) {
                newLines.Add (line);
            } else if (line.IndexOf ("isa = PBXProject;") > -1) {
                do {
                    newLines.Add (line);
                    line = lines [++i];
                } while (line.IndexOf("TargetAttributes = {") == -1);

                // 以下の内容はxcodeprojの内容にあるproject.pbxprojを参照してください
                newLines.Add("TargetAttributes = {");
                newLines.Add("xxxxxxxx = {");
                newLines.Add("DevelopmentTeam = xxxxxxxx;");
                newLines.Add("SystemCapabilities = {");
                newLines.Add("com.apple.BackgroundModes = {");
                newLines.Add("enabled = 1;");
                newLines.Add("};");
                newLines.Add("com.apple.Push = {");
                newLines.Add("enabled = 1;");
                newLines.Add("};");
                newLines.Add("};");
                newLines.Add("};");
                editFinish = true;

            } else {
                newLines.Add (line);
            }
        }

        File.WriteAllText(projPath, string.Join ("\n", newLines.ToArray ()));
    }
#endif
}

FirebaseAppDelegateProxyEnabled=NOに設定したので、個別ユーザへのPushが必要な場合は、didRegisterForRemoteNotificationsWithDeviceTokenでFIRInstanceID.instanceID().setAPNSTokenを呼ぶ必要があります。iOSでプッシュ通知を実装する方法の超詳細まとめ(前編)の図のように、APNから取得したDeviceTokenをFirebaseに送らないと、個別ユーザへのPush通知ができないためです。Unity Cloud Buildで自動化したいので、UnityのiOSでAppDelegateに処理を追加するを参考に、iOSのPluginを書くのが順当ですが、ハングする問題は修正中とのことなので、とりあえずUnity SDK 1.2を待ってもよいかもしれません。(2017/1/19追記 Unity SDK 1.1.1がリリースされ、この問題は修正されました。SDK 1.1.1を使用する場合、上記、PostBuildScriptにUserNotifications.frameworkを追加し、plist.root.SetBoolean("FirebaseAppDelegateProxyEnabled",false)をコメントアウトします)

わりと大変なので、Unity Cloud Buildのcocoapods対応か、Firebase Pluginのzip版を期待したいです。
Search
Profile

abars

アプリとWEBサービスを開発しています。最近はUnityとGAE/pyが主戦場。

ブラウザ向けMMOのメトセライズデストラクタ、イラストSNSのイラストブック、東証の適時開示情報を検索できるTDnetSearchを開発しています。

かつてエンターブレインのTECH Win誌でATULADOを連載しました。

サイト:ABARS
Twitter:abars
Github:abars

Twitter
TopHatenar
HotEntry
Counter

アクセス解析付きカウンター。あなたのBLOGにもどうですか?登録はこちらから。

TOP/ BLOG/ LECTURE/ ONLINE/ RUINA/ ADDON/ THREAD/ METHUSELAYZE/ IPHONE/ MET_IPHONE/ ENGLISH/ RANKING