ナノカ技術メモ

ただのエンジニア。何でも屋みたいな扱い受けてます。

Xcodeから実機ビルド時に「App Installation Failed」

はじめに

Unityから書き出したプロジェクトをXcodeで開きiPhoneへインストールしようとした時、以下のエラーが起きた。

`[App Installation Failed] Could not write to the device.`

`[App Installation Failed] This application does not support this device’s CPU type.`

XcodeでRun時にメッセージウィンドウとして表示されます。
頻度としては4回実行して上のエラーが3回に対して、下のエラーは1回くらいです。

動作環境

iPhone7(iOS11 beta)
XCode 9.0 Beta 3
MacBook PromacOS Sierra10.12.5)
UnityPro(5.6.2f1)

そうなった経緯

  1. 朝、問題になっているプロジェクトにて実機インストールができる状態であったことを確認。
  2. デバッグのためUnityのBuild SettingsにてDevelopment BuildとAutoconnect Profilerにチェックを入れる。
  3. その後、以下の記事を参考にIl2cppだったプロジェクトをMono2xに変更する。

www.f-sp.com

すると、エラーが出てインストールができなくなりました。

解決のためにやったこと

Mono2xからIl2cppに戻す

効果なし...

Unity Development Buildを解除

UnityのBuild SettingsにてDevelopment BuildとAutoconnect Profilerにチェックが入っていたので外したが効果なし。

以下は普通にエラー文で検索してみていろいろやってみました

正しいArchitecturesの設定かどうか

teratail.com
Targetの「Build Settings > Architectures > Valid Architectures」にiPhone7のarm64が追加されているか確認したが追加されていた...

容量

gootara.org
iPhoneの容量が足りず入れられない時にエラーが出るとあり、
iPhoneの容量が足りているか確認したが121.6GBも残っていた...

stackoverflow.com
Macの容量が足りないとインストールできずにエラーが出る的なことが英語で書いてあったのでMacの容量が足りているか確認したが67.09GB残っていた...

インストールしてある同じアプリを消す

qiita.com
インストールしているアプリを削除し、再度XcodeでRunしてインストールしようとしたが同じエラーが出た...

CocoaPods

neighborhood.bluz.io
CocoaPodsを使用している場合は.xcodeprojファイルではなく、.xcworkspaceファイルから起動しなくてはいけない。

forums.developer.apple.com
CocoaPodsを更新することで解決すると書かれている。

しかし、このプロジェクトはUnityでCocoaPodsは未使用です...

記事がなくなってきたので汎用的な祈りの儀式を始めます

再起動

iPhone,Macの再起動 & Xcodeのプロジェクトを消してUnityから再度ビルドしたが同じエラーは健在...

Gitから復元

GitからUnityのプロジェクトを復元しました。
そしてバックアップしていたその後の編集内容を手動でマージ....
動いた!

まとめ

  1. いろいろやってみて解決しなかったらGitから復元しよう
  2. 慣れないことをする前にはバックアップを取ろう

おそらく原因は「Il2cpp⇆Mono2x」だとは思いますが戻しても動かなくなるのは厄介ですね...

Unityで作ったXcodeのプロジェクトからTARGETSを消してはならない!

発生した問題

UnityでiOS向けにBuildしてXcodeのプロジェクトに変換したときに以下のエラーが出てビルドできない。

Exception: Deletion of either of the "Unity-iPhone" or "Unity-iPhone Tests" targets is not supported

原因

Xcode側でTARGETSから「Unity-iPhone Tests」を消していた。
※「Unity-iPhone」「Unity-iPhone Tests」は残しておき、名前も変えない。

解決策

UnityでFileからBuild Settingsを選び、BuildかBuild And Runでファイルを選択するときに出てくるWindowで「Replace」を選択する。
f:id:nanokanato:20170728134716p:plain:w300

Append(追記)じゃなくReplace(置換)することでTARGETSを作成してくれる。
しかし、Xcode側から設定した内容が消えているので注意です。

GoogleAppScriptをはじめよう

GoogleAppScriptとは

Google Apps Script(GAS)はGoogleが提供する11のサービスをクラウド上でスクリプトを実行することで操作できるサービスです。
言語はJavaScriptをベースとしています。

例えば一定時間ごとにデータを集計したり、SNSに投稿するBotを作ったり、GoogleSpleatSheetを編集したりなどが可能です。

以下のGoogleの機能と標準で連携することができるのでいろいろな使い方が可能です。

  • カレンダー
  • コンタクト
  • ドライブ
  • ドキュメント
  • スプレッドシート
  • フォーム
  • Gmail
  • グループ
  • マップ
  • サイト
  • Languages

GASを用意しよう

GoogleDriveに入って右クリックで「その他 > Google Apps Script」を選択。
※その他に無い場合はアプリを追加で探してみてください。
f:id:nanokanato:20170725121811p:plain:w300

GASを使ってみよう

上のアプリを追加で作成された無題のプロジェクトです。
左上の「無題のプロジェクト」の部分をクリックすることでプロジェクト名を変更できます。
f:id:nanokanato:20170725122209p:plain:w300

左の「コード.gs」の部分にgsファイルが並びます。
ファイル名の右の▽をクリックすることでファイル名を変更したり、ファイルを削除、コピーできます。
f:id:nanokanato:20170725122915p:plain:w200

とりあえず動かそう

プロジェクト名、ファイル名、メソッド名を「test」に変えてみました。
f:id:nanokanato:20170725130009p:plain:w300

function test() {
    Logger.log("Hello World!");
}

Logger.log(string)でログを表示します。

f:id:nanokanato:20170725131256p:plain:w16ボタンをクリックで実行されます。
実行後「⌘+Enter(Mac)」でログが表示されます。

f:id:nanokanato:20170725130553p:plain:w300


行数の部分をクリックすることでブレークポイントを設定できます。
f:id:nanokanato:20170725131350p:plain:w16ボタンを押すことでブレークポイントで停止してくれます。
f:id:nanokanato:20170725130702p:plain:w300
2017/07/25現在ではブレークポイントで止まってくれるのは実行開始したファイル内だけのようです。
別のファイルのブレークポイントで止めたい場合は、メソッドから別のファイルのメソッドを呼び出しているところで一度止めてから、f:id:nanokanato:20170725131414p:plain:w16ステップインすることで呼び出し先の別ファイルのメソッドへ移動できます。
別のファイルに移動した後であればそのファイルのブレークポイントに止まってくれました。

他に何ができるかは以下を参考にどうぞ
Calendar Service  |  Apps Script  |  Google Developers

トリガーの設定

定期的に集計などをしたい場合、その都度再生ボタンを押すのは面倒です。
トリガーとは再生ボタンを押さなくても一定時間ごとに指定したメソッドを実行してくれます。
f:id:nanokanato:20170725131906p:plain:w16ボタンを押して、トリガーを設定してください。

トリガーを設定するウィンドウが表示されました。
最初は青文字のリンクを押すことでトリガーを追加できます。
f:id:nanokanato:20170725132009p:plain:w300

実行の部分はメソッド名が入ります。ここではtestを選んでいます。
イベントには実行周期を登録します。
「特定の日時」「月タイマー」「週タイマー」「日タイマー」「時タイマー」「分タイマー」から選べます。
また、「新しいトリガーを追加」で新しいトリガーを追加できます。
f:id:nanokanato:20170725132339p:plain:w300

時タイマーで設定して1時間ごとにはなりますが、0:16→1:15のように0分ぴったりでは無いので注意です。

ぴったりの時間に処理したい場合は以下のように毎分に設定し、NowMin(分)が0であることを確認すれば間違いないです。
これで毎時0分ぴったりに処理することになります。

    var Nowymdhms = new Date();
    var NowYear = Nowymdhms.getYear();
    var NowMon = Nowymdhms.getMonth() + 1;
    var NowDay = Nowymdhms.getDate();
    var NowHour = Nowymdhms.getHours();
    var NowMin = Nowymdhms.getMinutes();
    if (NowMin == 0) {

    }

Unityでパズドラ風パズル作ってみた

はじめに

最近いろんなゲームをやりつつ、戦略系などが好きなんだなーと実感しました。
その中でも単純なんだけど考えるようなものは暇つぶしには最適で、それがパズルでした。

パズルと言ったらパズドラということもあり(1週間くらいしかやったことないが...)
作ってみることにしました。

完成した動作が以下です。
レイアウト等はまだまだですが一応パズルできる...という感じです。
これにコンボやスコアを設定すれば完璧、作り上簡単にカスタマイズできるはずです。


実装内容

実装後に違うパズルに改造してしている途中なので確かこうだったなぁという復元になってますが、動かない場合はコメント等にておしらせください。

Line3Block

パズルのピースを用意します。
80x80のPrefabです。
f:id:nanokanato:20170616113854p:plain:w300

階層構造はBlock(CreateEmpty:空のGameObject)に
BackGround(UI.Image)とIcon(UI.Image)を用意します。
f:id:nanokanato:20170616113815p:plain:w300

Blockには以下のScriptを追加します。
主にピースの移動後の座標と種類を管理しています。

InspectorからBlockの色(BlockColors)とキャラアイコン(BlockCharas)を設定します。
BackGroundとIconにはBlockの下層に作った同じ名前のUI.Imageを設定します。
f:id:nanokanato:20170616114234p:plain:w300

設定したBlockの色とキャラアイコンの数だけピースの種類が増えます。

Line3Puzzle

BlockのPrefabを生成したり、ピースが揃っているか確認する部分です。

階層構造はCanvas以下にBoard(CreateEmpty:空のGameObject)を配置しているだけです。
Canvasは640x1136で、Boardは560x760です。
Camera等の設定は省きます。
f:id:nanokanato:20170616114904p:plain:w300

Canvasには以下のScriptを追加します。
Blockの生成と破棄、タップ操作の管理、ピースが揃ったかどうかの確認、アニメーションなどを行なっています。

InspectorのBoardにはCanvas下層のBoardを、BlockPrefabにはBlockを設定してください。
f:id:nanokanato:20170616115225p:plain:w300

ビルド

あとはビルドすることでCanvas > BoardにBlockが生成され、操作待ちになります。

処理の手順は以下になります。

  1. ピースの生成 → 2
  2. 揃っているか確認
    揃っていた場合 → 3
    揃っていない場合 → 6
  3. 揃っていたピースを消す → 4
  4. ピースを下に詰める
    空いている場所には上から新たなピースを落とす → 5
  5. 移動後に再確認 → 2
  6. 移動するピースの選択待ち → 7
  7. ピースを移動して入れ替える
    指が離されたら移動を確定 → 2

まとめ

パズルって単純なゲームなのに内部では色々なロジックが動いており面白いです。
結構出尽くしている感もありますがぜひオリジナルのパズルを作ってみてください。

UnityのAssetBundleを簡単にしてみた

はじめに

AssetBundleを使用するときにAssetBundleを読み込んでローカルに保存、Spriteの取得というのが毎回実装していると手間なので以下のように呼び出せるようにしました。
f:id:nanokanato:20170615115801p:plain:w300

AssetBundleMan


キャッシュ→StreamingAssets→ローカルの保存場所→サーバーの順にAssetBundleを探しに行きます。
一度取得したAssetBundleはキャッシュされます。

サーバーとローカルへの通信はWWWクラスを使用したWWWManを使っています。
madgenius.hateblo.jp

使用方法

Spriteを取得する場合
AssetBundleMan.GetAssetBundleSprite("AssetBundle名", "ファイル名.png", (Sprite sprite) => {
    image.sprite = sprite;
});

取得失敗の可能性もあるのでnullチェックしておくとベストです。
AssetBundleとSpriteの取得後に呼ばれて画像が反映されます。

GameObject,Prefabを取得する場合
AssetBundleMan.GetAssetBundleGameObject("AssetBundle名", "ファイル名.prefab", (GameObject prefab) => {
    GameObject gameObject = Instantiate (prefab, this.transform);
});

取得失敗の可能性もあるのでnullチェックしておくとベストです。
AssetBundleとGameObjectの取得後に呼ばれてPrefabから生成されます。

UnityのWWW通信をCallBackで受けとるクラス

簡単にメモ程度ですが、WWWでの通信を簡単に行うために作成しました。
以下の画像の通り、Coroutineを使わずにCallBackでProgress(進捗度)と通信結果を受け取ることができます。
f:id:nanokanato:20170608185914p:plain:w300

staticのクラスなのでどこからでも呼び出すことができます。
MonoBehaviourを通信開始時に取得してCoroutineの処理をしているのでその部分のみ指定のMonoBehaviourに差し替えて使用することもできます。
現状、タイムアウトの処理がないので今後実装するかもしれません。

Unityでアプリ容量削減。700MB→179MB

はじめに

先日、Unityで簡単なiOSアプリを作りリリースしたのですが、Archiveでipaを作成したら130MB近くあったのでびっくり...
ちなみにiOSではAppStoreに公開して100MBを超えているとWifiに接続しないとダウンロードできません。
簡単なゲームのアプリなのでWifiに繋いでまで入れるものではないし、このままではインストールの壁が高い...
なんとしても容量削減しないといけないのでそのときやったことを書きます。

ipaのサイズ:130MB
iPhoneでの容量:700MB
iOSではインストール時はipaに圧縮されており、インストール後はappとして展開されます。

検証結果

Textureサイズを適切なものに変更

・容量にはほぼ変化なしだがメモリ削減に効果あり。
docs.unity3d.com


UnityのProjectから画像素材を選択するとInspectorに下のような画面が表示されます。
そこで画像のサイズとクオリティを設定できます。

デフォルトでは2048という無駄に大きいサイズになっているはずなので画像素材のサイズより大きい適切なものに変更しましょう。(下では200x200pxの素材を256に変更)
f:id:nanokanato:20170605142542p:plain:w300

素材の容量を削減

・容量にやや効果ありだが、やりすぎると劣化します
  ・ipaのサイズ:130MB→124MB
  ・iPhoneでの容量:700MB→680MB
tinyjpg.com

今回の場合、容量の原因は主に画像素材でした。
なので画像素材自体を軽くしてみました。

素材によっては大幅に削減されます。
劣化した場合はそのまま使いました。

AssetBundle

iPhoneでの容量に大幅に効果あり
  ・ipaのサイズ:124MB→116MB
  ・iPhoneでの容量:680MB→179MB
docs.unity3d.com

今回の場合、連番画像などが多くなっており、それをResources.Load()で取得しておりました。
しかし、Resourcesフォルダの多用はビルド時に使用している使用していないに関係なく無圧縮でビルドされることがわかりました。
また、画像素材の他にもビルド時に生成されたものがあり容量的によくないようです。
AssetBundleはUnityで使う素材を圧縮したもので、アプリで使用する時だけ解凍されます。

Resourcesをやめ、フォルダ名をResourceに変更しました。(フォルダ名はなんでも良い)
Resourceフォルダを選択すると、下にAssetBundle名を登録できますので自由につけました。(デフォルトはNone)
※グループごとにフォルダを分けている場合でもフォルダ以下をAssetBundleにできますが、個別の方がいいでしょう。
f:id:nanokanato:20170605145759p:plain:w300

以下のコードをUnityのProjectの「Assets/Editor」に追加します。

using UnityEngine;
using System.Collections;
using UnityEditor;
using System.IO;

public class ExportAssetbundle  {

	[MenuItem("Export/AssetBundle/iOS")]
	static void iOS_Export() {
		Directory.CreateDirectory (Application.streamingAssetsPath);
		BuildPipeline.BuildAssetBundles(Application.streamingAssetsPath+"/iOS", BuildAssetBundleOptions.ChunkBasedCompression, BuildTarget.iOS);
	}

	[MenuItem("Export/AssetBundle/Android")]
	static void Android_Export() {
		Directory.CreateDirectory (Application.streamingAssetsPath);
		BuildPipeline.BuildAssetBundles(Application.streamingAssetsPath+"/Android", BuildAssetBundleOptions.ChunkBasedCompression, BuildTarget.Android);
	}
}

追加するとUnityのメニューに「Export/AssetBundle」が追加されます。
f:id:nanokanato:20170605162212p:plain:w300

UnityのProjectのAssetsに「StreamingAssets/iOS」を追加してメニューのiOSを選択するとAssetBundleの作成が開始されます。
作成されたAssetBundleは「Assets/StreamingAssets/iOS」に追加されます。

AssetBundleの読み込みはAssetBundle.LoadFromFile()メソッドで読み込めます。
しかし、AssetBundleは1度しか読み込めません(読み込むとエラーが出ます)
なので自分は以下のようにstaticのメソッドで一度読み込んだらキャッシュから読み出すようにしています。
AssetBundleが読み込めない時は、上の手順では「Assets/StreamingAssets/iOS」にAssetBundleを作成したので問題はないはずですがLoadFromFile()内のパスを確認してください。

private static Dictionary<string, AssetBundle> assetBundleCache = new Dictionary<string, AssetBundle>();
private static AssetBundle readAssetBundleAssetBundle(string key) {
	AssetBundle assetBundle = null;
	if (!string.IsNullOrEmpty(key)) {
		if (assetBundleCache != null) {
			if (assetBundleCache.ContainsKey (key)) {
				assetBundle = assetBundleCache [key];
			}
		}
		if (assetBundle == null) {
			assetBundle = AssetBundle.LoadFromFile(Application.streamingAssetsPath+"/iOS/"+key);
			if (assetBundleCache.ContainsKey(key)) {
				assetBundleCache[key] = assetBundle;
			} else {
				assetBundleCache.Add(key, assetBundle);
			}
		}
	}
	return assetBundle;
}

読み込んだAssetBundleから素材を取り出すにはLoadAsset()を使用します。
上のreadAssetBundleAssetBundleメソッドを使ってAssetBundleを取得した後、LoadAssetでSpriteを取得しています。
Resouces.Loadでは「Resouces/Chara/chara_1.png」を読み込む場合、"Chara/chara_1.png"を引数としていましたが、
LoadAssetではフォルダ階層を無視し、拡張子が必要です。"chara_1.png"が引数になります。

AssetBundle assetBundle = readAssetBundleAssetBundle(AssetBundle名);
if (assetBundle != null) {
	sprite = assetBundle.LoadAsset<Sprite>(素材ファイル名);
}

AssetBundleで素材を読み込むのならAssetBundle化した素材たちはプロジェクトの外で管理しておきましょう。

ダウンロードで素材を取得

・ダウンロード時のサイズに効果あり
  ・ipaのサイズ:124MB→116MB
  ・iPhoneでの容量:680MB→179MB

上の3つの対策をしてiPhoneでの容量は大幅に減らせましたが、ipaのサイズが100MBを超えているため素材は初回にダウンロードすることにしました。
このアプリでは一応、設定値などを起動時に通信していたのでその通信にAssetBundleも含めたいと思います。
サーバーにはAssetBundleのパスと最終更新日を用意し、最終更新日がアプリ内の最終更新日付より新しくなったら再取得するようにしました。

作成したAssetBundleにはフォルダに登録したAssetBundle名のファイル以外に「.manifest」や「iOS」「iOS.manifest」などが作成されますが削除してビルドしても実機で動かす際には問題なかったので使用しませんでした。

AssetBundleの取得はWWWクラスで行います。
WWWクラスの引数assetBundleがあれば取得成功なので通信結果であるByte配列をローカルに保存します。
※引数assetBundleはAssetBundle.LoadFromFile()と同じく1度しか取得されないようです。
 取得したらキャッシュとしてどこかに保持しましょう。

using (WWW data_www = new WWW (サーバーに設置したAssetBundleのURL)) {
	yield return data_www;
	
	//通信結果を取得
	if (string.IsNullOrEmpty (data_www.error)) {
		//通信成功
		AssetBundle assetBundle = data_www.assetBundle;
		if (assetBundle != null) {
			//AssetBundleの取得成功、保存
			System.IO.File.Delete(AssetBundleを保存するローカルのパス);
			System.IO.File.WriteAllBytes(AssetBundleを保存するローカルのパス, data_www.bytes);
		}
	}
}

前回起動時にサーバー通信でByte配列を保存した後はローカルパスをWWWで通信することで取得できます。
サーバー通信後にローカルパスからも取得しようとすると同じAssetBundleを2回取得しようとしていることになるためエラーになります。

結論

・画像は256色にする
・Resourcesフォルダの多用はしない
・それでも重い時はAssetBundle化
ipaの容量が100MB超えたらサーバーからダウンロードさせる

AssetBundleはPrefabなども可能なのでサーバーがあればアプリを更新せずにレイアウトの変更も可能に!