エイバースの中の人

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

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を先に読む必要があり、その依存関係がDependenciesに記載されています。

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などに上げて、ランタイムでロードします。

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で使用する

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版を期待したいです。

AppEngineのデータストアの自動バックアップ

AppEngineのデータストアのバックアップをcronで自動化する方法が公開されていました。 Scheduled Backupsでは、データストアをCloudStorageにバックアップします。

まず、バックアップ先のCloudStorageのバケットを作成します。

cloud_storage

次に、データストアの管理を有効にします。

datastore

AppEngineプロジェクトのcron.yamlにバックアップジョブを設定します。kindにはバックアップ対象のエンティティ名を、gs_bucket_nameに先ほど作成したバケットを登録します。

- description: backup
  url: /_ah/datastore_admin/backup.create?name=BackupToCloud&kind=Bookmark&filesystem=gs&gs_bucket_name=backup-tdnet-search
  schedule: every 12 hours
  target: ah-builtin-python-bundle

cronを実行すると、データストアの管理画面に表示されます。反映までに少し時間がかかります。

datastore_admin

Cloud Storage側にも反映されます。

bucket

今まで、データストアのバックアップは手動でぽちぽちするしかなかったのですが、自動化できるようになって便利です。

SBI証券の投資成績を集計するツールを作りました

SBI証券の譲渡益税明細から取得できるCSVを入力して、投資成績を集計することができるツールを作成しました。

まず、口座管理->取引履歴->譲渡益税明細から、対象期間のCSVをダウンロードします。

sbi


次に、TDnetSearchにアップロードすると、集計結果が表示されます。

csv


集計結果はテキストで取得することもできるため、簡単にBLOGに貼り付けることができます。

2016年の投資結果を分析して、2017年の投資につなげていければと思います。

TOBの貢献が大きかった2016年の株式投資成績

2016年の株式投資の成績(確定利益分)をまとめてみました。TOBされたマルキョウとニフティで65%の利益となっており、残りの銘柄は1銘柄につき5%以下の貢献ということで、TOBされるかどうかにかなり左右される結果となりました。

今年は、ネットネット株ランキングを作ったことで、ランキング外の銘柄は処分したのですが、ここで処分した愛知電機が年末に大きく上がってしまったのが惜しかったところです。

休暇などで時間が出来た時に、ちょこちょこ買った銘柄のパフォーマンスは、全体にほとんど影響を与えていないです。むしろ売り焦ってマイナスです。

来年はあまり売買せず、どれだけ売り焦らずに、TOBを待てるかという握力を意識した方が良さそうです。中部日本放送、日本ユピカ、東北新社、デコラックスに期待しています。

銘柄総利益に対する貢献度
マルキョウ 38%
ニフティ 27%
岡山製紙 5%
日本ユピカ 5%
インベスコ・オフィス・ジェイリート投資法人 投資証券 5%
東京ラヂエーター製造 4%
テクノメディカ 3%
三井住友フィナンシャルグループ 3%
グローバル・ワン不動産投資法人 投資証券 3%
旭化成 3%
東部ネットワーク 3%
中部日本放送 3%
東北新社 2%
三井物産 1%
日本アコモデーションファンド投資法人 投資証券 1%
積水ハウス・リ−ト投資法人 投資証券 1%
ナンシン 0%
ソニーフィナンシャルホールディングス 0%
オリックス 0%
日本BS放送 0%
フジ・メディア・ホールディングス 0%
トップリート投資法人 投資証券 0%
中部電力 0%
りそなホールディングス 0%
フリービット 0%
ダイハツディーゼル 0%
iシェアーズ米国リート不動産株ETFダウジョーンズ米国不動産 0%
リオン 0%
中部鋼鈑 0%
日本ヘルスケア投資法人 投資証券 0%
ビックカメラ 0%
ディー・エヌ・エー 0%
カンダホールディングス 0%
北陸電力 0%
リスクモンスター 0%
クリヤマホールディングス 0%
小田原機器 0%
ジオマテツク 0%
ハークスレイ 0%
新潟放送 0%
フージャースホールディングス 0%
北陸瓦斯 0%
空港施設 0%
iシェアーズ米国高配当株ETFモーニングスター配当フォーカス 0%
ソノコム 0%
キクカワエンタープライズ 0%
阪神内燃機工業 0%
横浜丸魚 0%
東京汽船 0%
名古屋電機工業 -1%
アルプス物流 -1%
U−NEXT -1%
カドカワ -1%
愛知電機 -1%

ちなみに、上記の表は、SBI証券の取引履歴の譲渡益税明細をCSVでダウンロードして、エクセルで銘柄名で並び替えた後、集計して保存、CSVをWEBツールでTABLEタグ変換しました。わりと簡単に今年の成績を振り返れるのでオススメです。

macOS Sierraの画面が真っ暗とカーソルだけになる

Mac Pro 2013のmacOS Sierraを10.12.2に上げたところ、画面が真っ暗とカーソルだけになりました。macOS 10.12 Sierra - 6 (268)の37さんと同様に、/Library/Preferences/loginwindow.plistを消したら治ったのでメモです。

詳しい手順は、Mac - Mac boots to a black screen with cursorにあります。起動時にcommand (⌘) + Sを押してコマンドラインモードで起動、/sbin/fsck -fyでディスクチェック、/sbin/mount -uw /で書き込み権限を設定、rm /Library/Preferences/com.apple.loginwindow.plist をした後、rebootで治りました。

どきどきします。

Unityのシーン変更でメモリを解放する

iPhone5Sと6は、メモリが1GBしかないため、UIの巨大画像がメモリを圧迫し、シーンの遷移で落ちることがあります。

SceneManager.LoadSceneの第二引数にLoadSceneMode.Singleを設定したとしても、シーン移行のタイミングでは、遷移前のシーンと、遷移後のシーンの両方のメモリを専有するようです。

最初は、テクスチャの参照カウントを0にすれば、LoadScene内でも解放されるはずだと期待して、循環参照になりそうなGameObjectにOnDestroyでnullを代入したりしていたのですが、一向に参照カウントが減少せず、仕様だと諦めました。

2シーンの合計となると、半分のメモリしか使用できないため、ゲームのクオリティが落ちてしまいます。この問題を解消するには、空のシーン、俗に言うローディング画面を間に挟むとよいようです。

遷移開始時は以下のようにします。

SceneManager.LoadScene ("loading");
Resources.UnloadUnusedAssets();
System.GC.Collect();

Updateの中で、loadingに遷移したタイミングで本当に行きたかったシーンに遷移します。

if(SceneManager.GetActiveScene().name=="loading"){
SceneManager.LoadScene("next_scene");
Resources.UnloadUnusedAssets();
System.GC.Collect();
}

これにより、iPhone5Sでも落ちなくなりました。

2016年に買って良かったもの

2016年も年末ということで、今年、買ってよかったものをリストアップしてみました。

iPhone7


IMG_1870

昨年まではiPhone5Sを使っていて、iPhone7は大きすぎるかなと思っていたのですが、Suicaに対応したということで買い替えました。薄くなった分、大きさにもすぐに慣れ、逆にiPhone5Sが小さく見えるから不思議なものです。ホームボタンも慣れると逆にiPhone5Sが深く感じますね。今年、一番、満足度が高い買い物でした。

Apple Watch Series 2


IMG_2023

こちらもSuica対応ということで買い替え。残念ながら、Suicaはロック中に動作しないためほとんど使っていませんが、IDが指紋認証なしで使えて便利です。

Sony LED電球スピーカー


IMG_2018

SonyのLED電球スピーカーの二代目です。音楽を聞くにはモノラルなので厳しいですが、ポッドキャストを聞くには最適です。天井から音が鳴るので、講演会にいるような気分になれます。RebuildFM専用機。



ルンバ870


IMG_2019

ルンバ500のサポートの終了に伴い、安く買い換えられるということで購入。ブラシが新しくなって絡みにくくなったのと、スケジュールで自動的に掃除が始まるのが便利です。



Scan Snap IX100


IMG_2020

M1500からの買い替え。USB給電とScan Snap Cloudで場所を取りません。昔は自炊のためにM1500を使っていたのですが、Kindleのおかげでクレジット明細のスキャンぐらいにしか使わなくなっていたので、コンパクトになって満足です。名刺管理もEightに移行して、ペーパーレス化が進んでいます。



なめこ日めくりカレンダー


IMG_2021

2016年は、よつばとカレンダーがなかったのですが、なめこカレンダーのおかげで精神を健全に保てました。2017年版も、もちろん買いました。来年も出て欲しい!



IMG_2014


無印リュック


IMG_2025

バイヤーも使っているとうわさの無印のリュックです。最近はビジネスシーンでもリュックを使うようになってきたので、軽くてがっつり入る無印のリュックは最適でした。

Bose Quiet Comfort 35


IMG_2022

待望のノイズキャンセリング+ワイヤレス。2個買いました。電源を入れると自動的にペアリングされる、USBで充電できる、残電池量をボイスで教えてくれる、など、完成度がとても高いです。



ヤコブセンの置き時計


IMG_2027

限定色があまりに良かったので。ブラックモデルとくらべて、ムーブメントが静かになりました。



デロンギのケトル


IMG_2026

カッパーカラーがマイブームで、ずんぐりむっくりな形状に惹かれて買ってしまいました。保温性能は低いですが、デザイン優先で。



Apple MagicKeyboard


IMG_2024

Lightningで充電できるようになり、電池交換のストレスが大幅に減りました。来年、Touch Bar付きのモデルが出ないかなと期待しています。



iPad Pro 9.7


IMG_2031

最初に大きい方を買って、大きすぎたので小さいのに買い替えました。社外での打ち合わせは、Mac Book 12ではなく、こちらがメインになりました。軽さと小ささは正義です。キーボードがあると、ビジネス感が出ます。Apple Pencilも買いましたが、あまり使ってないです。



今年は年末にいろいろな機器を買い替えました。来年もよい製品がいろいろ出て欲しいです。

OneSignalを使ってUnityにPush通知を実装する

UnityでPush通知を実装する場合、OneSignalを使うと便利です。無料で任意のセグメントのユーザに通知を送ることができます。

iOSの設定

(1) アプリケーションフォルダにあるキーチェーンアクセスを開きます
(2) メニューの「証明書アシスタント」から「認証局に証明書を要求」してディスクに保存します
(3) iOS Dev CenterのProvision Portalへ行き、Certificatesの項目で、Apple Push Notification Authentication Key (Sandbox & Production)を作成します
(4) aps.cerをダウンロードします
(5) キーチェーンアクセスに取り込んで、右クリックし、p12で書き出します(左のタブをログインに合わせておかないとp12で書き出せません)
(6) OneSignalに登録します

証明書は毎年更新が必要です。

Androidの設定

(1) Firebaseのプロジェクトを作成してFCMのサーバーキーを取得します
(2) OneSignalに登録します

Unityの設定

(1) OneSignalのUnity SDKをインポートします
(2) 以下のコールバックを任意のスクリプトに登録します(ONE_SIGNAL_API_KEYは自分のものに書き換えます)
(3) OneSignalのサイトからPush通知を発行すると、アプリを起動している場合、ダイアログが表示されたあと、HandleNotificationOpenedが呼ばれます

using System.Collections.Generic;  
void Start () {
    // Enable line below to enable logging if you are having issues setting up OneSignal. (logLevel, visualLogLevel)
    // OneSignal.SetLogLevel(OneSignal.LOG_LEVEL.INFO, OneSignal.LOG_LEVEL.INFO);
  
  OneSignal.StartInit(ONE_SIGNAL_API_KEY)
    .HandleNotificationOpened(HandleNotificationOpened)
    .EndInit();
  
  // Sync hashed email if you have a login system or collect it.
  //   Will be used to reach the user at the most optimal time of day.
  // OneSignal.syncHashedEmail(userEmail);
}

// Gets called when the player opens the notification.
private static void HandleNotificationOpened(OSNotificationOpenedResult result) {
}

尚、Unityからアプリを書き出した後、XcodeのCapabilityでPushNotificationとBackground ModesのRemote notificationsを有効化する必要があります。従って、Unity Cloud Buildではビルドは通りますが、動作しません。

UnityのAndroid向けビルドにおいて、Google.JarResolver.ResolutionException: Cannot resolve com.google.android.gms:play-services-gcm:9+() というエラーが出る場合は、Android SDK managerでAndroid Support RepositoryとGoogle Play servicesとGoogle Repositoryをアップデートして下さい。 また、JAVA_HOMEにJREへのパスを設定する必要があります。

Apple Watch Series 2を買いました

Apple Watchのステンレススチール+ミラネーゼループを使用していたのですが、Apple Watch Series 2のNikeモデルを買いました。

IMG_1985

今回もステンレスモデルにするか迷ったのですが、結局、2年に一回は買い替えそうなのと、軽さ優先ということで、アルミニウムモデルにしました。心配していたミラネーゼループとの質感の違いは、思ったよりも気になりませんでした。それよりも軽くなったメリットの方が大きかったです。

ただ、当初の購入目的であったSuicaの使用は、iPhone7と異なり、ロック状態では改札を通れないという問題から、実用性は微妙でした。対して、コンビニでのID支払いは、iPhone7で必要な指紋認証が不要な分、便利でした。なので、コンビニでのID支払い専用機というのが、現在の評価です。

Apple Watchそのものの機能としては、時計・アクティビティトラッカー・電話の着信に気づかない問題の解消・当日のスケジュールの確認、あたりを便利に使っています。外出や打ち合わせが増えてくると、時計があった方が便利ですね。

結局、Nikeモデルにした意味はありませんでしたので、通常モデルでも十分かと思います。

UnityのuGUIの座標をスクリプトで指定する

UnityでuGUIを使用する場合、複数の解像度に対応するため、Canvas Scalerを割り当てます。

canvas_scaler

しかし、Canvas Scalerを与えたCanvasにおいて、子要素の座標をスクリプトから制御しようとした場合、transform.positionにCanvas Scalerの拡大率が考慮されないため、うまく制御できません。これは、Unity Editor上では、Canvas Scalerで指定した座標系で設定できるのが、スクリプトでは実座標系で設定することになるのが原因です。

スクリプトからも、Canvas Scalerで指定した座標系で値を設定するには、anchoredPosition3Dを使用します。

具体的に、
 gameObject.transform.position=v;
ではなく、
 gameObject.GetComponent<RectTransform>().anchoredPosition3D=v;
と記述することで、Canvas Scalerを適用しても、適切な位置に子要素を設定することができます。
Search
Profile
Twitter
TopHatenar
HotEntry
Counter

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

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