Unity Cloud BuildでUnityのビルドやバイナリ配布を自動化しよう

お金を払ってProライセンスを使っている人に朗報です。Unityがベータ版として提供する Unity - Cloud Build を使えば、いわゆる継続的インテグレーション(CI)が簡単に行えます。

弊社では「レポジトリの特定のブランチを監視させ、変更があったらiOS版とAndroid版をビルドして配布」という用途のために使用してします。

そうです、TestFlightのような配布機能まで備えており大変便利です。ProユーザーはJenkinsやTestFlightを個別にセットアップする必要がなくなります。

下準備

iOS用ビルドやAndroid用ビルドをするためにはいくつか下準備が必要となります。

identifierの用意

Bundle ID的なものを用意しておく必要があります。iOS Developer CenterでApp IDを作ったりしてiOS向け、Android向けの設定を終えます。あとで必要になるのでビルド用のProvisioning等を用意しておきましょう。つまるところ、手元でビルドするのに必要な準備と大差はないと思います。

手作業による変更が要らない状態に

Xcodeプロジェクトの書き出し後に手作業による設定変更をしている人もいるかもしれませんが、Cloud Buildではそのようなことはできません。プロジェクトの書き出しからビルドまでを完全に自動で通す必要があります。

そこで前回のエントリーUnityでのXcode設定をUnityEditorのスクリプトだけで自動化する につながります。Xcode のプロジェクトを変更する必要がある人はこちらを参考に色々頑張ってみましょう。

Cloud Buildでプロジェクトの設定

Unity - Cloud Build ←まずここからProライセンスを支払っているアカウントでログインし、上部ナビゲーションバーからAdd Newを選択します。

するとこのような画面になるので、監視したいリポジトリのURLを入力しましょう。弊社ではbitbucketを利用しているのでgit@bitbucket:account/repository.gitのようになりました。

次にSSHキーを設定します。この画面で詳しく案内してくれるので特に問題はないでしょう。

どのブランチを監視するのかを決めます。ここで指定したブランチにあるコードを使ってビルドしてくれるようになります。

これまでに設定した内容の確認と、ビルドするプラットフォームを選びます。iOSAndroidを有効にし、必要であればAuto-buildにもチェックしておきましょう。Auto-buildが有効になっていると、ブランチに対する変更を検知して自動的にビルド→配布を行ってくれます。

最後にAndroidiOSの設定を行います。

予め用意しておいたBundle IDやProvisioning Profile、p12などを入力、アップロードします。

長かったですね。これで自動ビルドのための準備ができました。

次のステップに進むと最初のビルドがスタートします。ビルド完了後は配布URL付きのメールが来ます。

運用例

弊社ではCloud Buildが監視するためだけのブランチを作成しており、区切りのいいタイミングでメインブランチからプルリクエストを作成してビルド用ブランチを更新するようにしています。BitbucketのWebインターフェースでプルリクエストを作ってマージするだけで自動的に実行可能なバイナリを作ってくれるわけです。

もっというとこのプルリクエスト作成→マージの部分はChatOpsな感じで動くようにしています。細かい話は後日書くかもしれませんが、予め登録しておいた命令をbot hogehogeのような感じでSlackに投稿すると、あとはBotが勝手にマージしてくれるようにしています。

「そろそろメンバー全員に見せておくか」と思ったらSlackにちょっと書くだけで配布まで完了できるわけです。革命的ですね。

UnityでのXcode設定をUnityEditorのスクリプトだけで自動化する

Unityが出力したiOS用プロジェクトの手動編集をなんとかしたいと考えていたら、同じような人がやはり多いようでいろいろな方法がブログエントリで紹介されていました。

調べた感じではCodeEditor-for-UnityxcodeprojというRubyのgemを使う方法が多くみられました。前者のライブラリは最近はメンテされていないようですし、後者の場合Rubyが必要になるということで広く導入しにくい(関係者全員にxcodeproj gem入れろとはいえない)ので別の方法がない限りできないかなーと思っていたら、なんとBitbucketにリポジトリを発見しました。

Unity-Technologies / XcodeAPI — Bitbucket https://bitbucket.org/Unity-Technologies/xcodeapi

Unity-Technologiesというアカウント名から察するに公式のツールだと考えて良さそうです。

このコードを使ってXcodeプロジェクト出力後にフレームワークを追加したり設定を変更するような処理を書いてみようと思います。

なお他の手段でできることがこのXcodeAPIではまだできないということがあるかもしれないのでその点は注意してください。

導入

準備はほとんど要らなくて、リポジトリからダウンロードしたファイルをUnityのプロジェクト内、Assets以下の好きなところに突っ込んでおけば大丈夫です。

ビルド後の処理を書く

Assets/Editorあたりのディレクトリに、例えばModifyXcodeProject.csという感じのスクリプトを配置します。

using UnityEngine;
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEditor.iOS.Xcode; // ←さっきいれたXcodeAPI
using System.Collections;
using System.IO;

public class XcodeProjectMod : MonoBehaviour
{

    // ちょっとしたユーティリティ関数(http://goo.gl/fzYig8を参考)
    internal static void CopyAndReplaceDirectory(string srcPath, string dstPath)
    {
        if (Directory.Exists(dstPath))
            Directory.Delete(dstPath);
        if (File.Exists(dstPath))
            File.Delete(dstPath);

        Directory.CreateDirectory(dstPath);

        foreach (var file in Directory.GetFiles(srcPath))
            File.Copy(file, Path.Combine(dstPath, Path.GetFileName(file)));

        foreach (var dir in Directory.GetDirectories(srcPath))
            CopyAndReplaceDirectory(dir, Path.Combine(dstPath, Path.GetFileName(dir)));
    }

    [PostProcessBuild]
    public static void OnPostprocessBuild(BuildTarget buildTarget, string path)
    {
        if (buildTarget == BuildTarget.iPhone)
        {
            string projPath = PBXProject.GetPBXProjectPath(path);
            PBXProject proj = new PBXProject();
            
            proj.ReadFromString(File.ReadAllText(projPath));
            string target = proj.TargetGuidByName("Unity-iPhone");

            // システムのフレームワークを追加
            proj.AddFrameworkToProject(target, "AssetsLibrary.framework", false);
            
            // 自前のフレームワークを追加
            CopyAndReplaceDirectory("Assets/Lib/mylib.framework", Path.Combine(path, "Frameworks/mylib.framework"));
            proj.AddFileToBuild(target, proj.AddFile("Frameworks/mylib.framework", "Frameworks/mylib.framework", PBXSourceTree.Source));

            // ファイルを追加
            var fileName = "my_file.xml";
            var filePath = Path.Combine("Assets/Lib", fileName);
            File.Copy(filePath, Path.Combine(path, fileName));
            proj.AddFileToBuild(target, proj.AddFile(fileName, fileName, PBXSourceTree.Source));

            // Yosemiteでipaが書き出せないエラーに対応するための設定
            proj.SetBuildProperty(target, "CODE_SIGN_RESOURCE_RULES_PATH", "$(SDKROOT)/ResourceRules.plist");

            // フレームワークの検索パスを設定・追加
            proj.SetBuildProperty(target, "FRAMEWORK_SEARCH_PATHS", "$(inherited)");
            proj.AddBuildProperty(target, "FRAMEWORK_SEARCH_PATHS", "$(PROJECT_DIR)/Frameworks");
            
            // 書き出し
            File.WriteAllText(projPath, proj.WriteToString());
        }
    }
}

iOSプロジェクト書き出し後に[PostProcessBuild]属性を付けたメソッドが呼ばれます。そこでXcodeプロジェクトの本体である.pbxprojを編集するためのコードを書けばOKです。

上のコードでは

これらのことをやっています。

このコードもXcodeAPIもソース管理に入れてしまえば誰でも使うことができるので他の方法よりも良いかもしれません。

UnityのTranslateにご注意

UnityのTransform.Translate関数には、じつはちょっと注意が必要です。 簡単に言うと、transform.Translate(Vector3.right)とtransform.position += Vector3.rightは異なるというお話です。

手順1

まず、原点に球体を用意します。

f:id:cflat-inc:20141028105357p:plain

手順2

次にInspector上でPositionをX方向に+2移動させると、当然赤い球体のようになります。

f:id:cflat-inc:20141028105412p:plain

手順3

Inspector上でRotationをY軸中心に90度回転させると、向きは変われど、位置は変わりません。 これは、手順2と手順3を入れ替えても同じ結果になります。

f:id:cflat-inc:20141028105421p:plain

手順4

さて、ここでもう一つ緑の球体を用意させます。 Inspector上でRotationをY軸中心に90度回転させます。

f:id:cflat-inc:20141028105450p:plain

手順5

さあ、ここでソース上で下記のようにTranslateさせてみましょう。

   void Start () {
        transform.Translate(2.0f * Vector3.right);
    }

f:id:cflat-inc:20141028105632p:plain

あれ、手順3と結果が異なりますね。 Translate(Vector3)は、ローカル座標系をベースとした関数であるということがわかります。 ちなみに、Position.Xは-2.384186e-07という値になっております。

手順6

ここでソースを下記のように変更してみます。

   void Start () {
        //transform.Translate(2.0f * Vector3.right);
        transform.position += 2.0f * Vector3.right;
    }

結果は、

f:id:cflat-inc:20141028110745p:plain

完全に手順3と一致しましたね。 こちらはInspectorでの操作と同じ結果ということですね。

でもこれ実はTranslate関数でも同じことができて、

   void Start () {
        transform.Translate(2.0f * Vector3.right, Space.World);
    }

とやれば良い。 デフォルトがローカル座標系になっておるのですね。

勘違いされている方もいるようですので、ご注意あれ。

UnityでOpenCVまとめ

Unityで画像処理と言うと、まだあまり需要がなさそうですが、Oculus Riftの流れ的にも、これからニーズが増えそうですなあ。 さて、弊社でもUnityでOpenCVを利用したくなったわけですが、現在パーフェクトな環境はまだなさそうです。 そこそこ調べたので、まとめておきます。

OpenCVSharp

かなーり苦労されている様子がわかりますが、Unity Editor上で使っている人は下記にいるようです↓

https://warapuri.com/post/70283352060/unity%E3%81%A7opencv%E3%82%92%E5%8B%95%E3%81%8B%E3%81%97%E3%81%A6%E3%81%BF%E3%82%88%E3%81%86%E4%B8%BB%E3%81%ABmac%E5%AF%BE%E5%BF%9C

調べた感じ、iOS, androidで試してる人は見つからなかった。。。スマホ向けに利用するためには、プラグインの作成と、おそらくラッパーのソースもいじらないといけないでしょうな。無料なのは素晴らしいですが、ライセンスがLGPLなので、ソースをいじるとなると商用利用は少し大変でしょう。

Emgu CV

http://www.emgu.com/wiki/index.php/Main_Page

ライセンスがGPLなので、商用利用したければ基本は有償版が必要になるでしょう。 これはスマホ向けもあります。それもそれで別途有償で値段もそこそこする。。。

Unityへの組み込みは開発側がサポートしているわけではなさそうです。 Windowsへの組み込みで、ずいぶん苦労されているみたいです。Macも大変かもしれません。 ちなみにスマホ向けライブラリは、お金を払ってからでないと触れません。

OpenCV for Unity

Unity Asset Store - The Best Assets for Game Making

スマホ対応がばっちりできているので、今のところはっきりこれが一番だと思うわけですが、これはこれで難点があります。 まず、Unity Editorで利用することができません。つまりUnityからスマホに落とさないと動作を確認できない。これははっきり痛い。

また、CVAUX等一部APIが使えないようです。ラッピングの充実度という点では、先に上げた二つに劣ると思います。 CVAUXを使いたい時はどうしましょう。。。ちなみに先日私はこのアセットからCVAUXが利用できる夢を見ました。 下記APIが利用できます。

http://docs.opencv.org/java/

組み込みは割と楽チンです。

自作プラグイン

1関数利用したいだけ等、ごくごく限定された使い方をするのであれば、いっそのこと一からプラグインを作った方がコスパがいいのかもしれませんな。 トライしている人はたくさんいますので、これらを参考にされてはと思います。(あまりやりたくはありませんが。。。)

GitHub - thorikawa/unity-opencv-android: Unity+OpenCV+Android

それでは以上。

開発時に便利なUnityのTips

今週はUnity開発時のTipsをいくつか。

取り込んだ画像の画質が悪い

f:id:cflat-inc:20140905134439p:plain

Aniso Levelの引き上げることで、急な角度でテクスチャを見る場合にテクスチャの品質を向上させる。

ついでにpng等透過画像の場合はAlpha is TransparencyのチェックをONにする。 この項目をチェックすると透過付きのテクスチャの透過と非透過部分との境界に発生するノイズが低減される。

NGUIのレイアウト

f:id:cflat-inc:20140905134456p:plain

NGUIはバージョンアップ毎に仕様が変わって大変ですが。

最初はFlexibleになってたはず。

これをConstrainedに変更しておけば、多少画面サイズが変わってもある程度いい感じにスケーリングされる。 http://www.tasharen.com/forum/index.php?topic=6710.0

GoogleAnalytics連携

ここ記事を参考にunity3d-google-analyticsを採用するのであれば、多少改造する必要ある場合がありそうなので、それについてちらほら。

http://qiita.com/yimajo/items/0861a79e741cbd463179

イベントにカスタムディメンションを追加したければ、CGoogleAnalytics.csを下記のようにコード変更。

public void TrackEvent(string _category,string _action, string _label, int _value){
        WWWForm postParams = new WWWForm();

        AddDefaultFields(ref postParams);

        postParams.AddField("t","event");
        postParams.AddField("ec",_category);
        postParams.AddField("el",_label);
        postParams.AddField("ea",_action);
        postParams.AddField("ev",_value);
        postParams.AddField("cd1",m_customerId);

        MakeRequest(postParams);
}

複数シーンをまたぐならCGoogleAnalytics.csにこれを追加。後は最初のシーンのみにGameObjectをぶちこんでおけばよい。

void Awake()
{
    DontDestroyOnLoad(this);
}

Unityで忍者アプリを作ってみた

さて、Unityで忍者アプリを作ってみました。 短期開発ですが、今回は欧米狙いの色が強いアプリに仕上げています。

https://itunes.apple.com/app/id891186517?at=10l8JW&ct=hatenablog

Android版はこちら↓

https://play.google.com/store/apps/details?id=com.cflat.ninja

フィールドはアセットを購入して、組み立てました。 同様に忍者も購入。

今回はNGUIも購入しました。 今回NGUIで使ったのはボタン程度ですが、基本的に使いやすいですね。 一つ困ったのは、画面サイズに合わせたボタンレイアウトの調整ですが、 [Unity]NGUIで画面サイズに合わせる(NGUI2.3.0対応版) の神記事に助けてもらいました。 よりデザインにこだわろうとすると、もちろんこれだけでは厳しいですが。

今回何より困ったのは、高速化。 iPhoneだと動作が猛烈に重く、ここに工数がかかりました。 原因はフィールドのアセットにあったのですが、そのせいでDrawcall数がかなり多くなっていた。(150くらいだったか) 「UnityでiPhone向け3Dゲームを作る」レポート - ゲームの花園

最初はアセットのpsdがものすごく重いことが原因だとかいろいろ当たりつけてやったんですが、やっぱりDrawcall数が最も重要。 畳なんかももっとカッチョ良かったのですが、削りまくって削りまくって何とかDrawcall数40くらいまで減らしました。 今回は使えなかったけど、GameObjectをStaticにしてしまうというのは、相当効果が大きい。忍者の動きに合わせてフィールドをどんどん移動させなくてはならんので、Staticにはできないのですが、一時はフィールドをスタティックにして忍者をワープさせようかとも考えたほどです。

あとはスポットライトもなかなかの負荷でした。 パーティクルはほどほどにしないとこれも重さの原因になってしまう。 アセットストアで購入するのも、いろいろ労が伴います。 何とか4Sでもある程度動くようになった。ふうー。 それでもまだ多少重いので、Androidだと機種によってはかなり重いので、人気出そうな感じなら改良かな。

そんなこんなでとにかくリリースしました。 良かったら触ってみてください。

ワールドカップに合わせてUnityでサッカーゲームを作ってみた

AppStoreの審査が開幕のブラジル戦に間に合うのかどうか、これが一番の懸案でしたが、1回リジェクトくらったため間に合いませんでした。コートジボワール戦にも間に合わず、何とかギリシャ戦には間に合ったという惨状です。

さて、会社でUnity Proも購入したし、まずは一つリリースしてみようということで、サッカーゲームを作ってみました。 開発期間はかなり短期間です。

https://itunes.apple.com/app/id885300966?at=10l8JW&ct=hatenablog

Android版はこちら↓

https://play.google.com/store/apps/details?id=com.cflat.ClassicDrivingShot

スタジアムとキーパーはアセットを購入しました。 購入するまで、アニメーションがわからないアセットがあるので、この点ストアを改善してほしいですな。 結局一人目のキーパーは使えないキーパーだったので、二人目を購入しました。 世界中探しても、なかなか理想的なキーパーは見当たらないものです。 あと、サウンドも購入するまで音がわからないというのは、買う勇気が湧かない。。。 時間もないし、サウンドはフリーのものを使いました。

あとは、フリックでドライブシュートを打てるようにして、キーパーのColliderをちまちま作ってやって、ドライブシュートの軌道からキーパーの飛ぶ方向を計算して、飛ばしてやる。 ドライブは、フリックダウンの時間に比例して力を加えています。 距離を使うと、パラメータ調整がかなり難しくなりそうだったので。 ゴールが決まった場所に応じて、1ゴール、2ゴール、4ゴール、8ゴール、16ゴールまで獲得でき、これが一つのインセンティブです。 16ゴールは基本的に幻のゴールで、上隅の限られた領域に限定されます。開発者も3回くらいしか出したことない。

GUIは評判通り、開発しにくい。 NGUI買うべきなんでしょうが、今回はひとまずフリーでやりました。 次は購入かなあ。

あとは実機デバッグがやっぱり問題ですな。 Unity Remoteは素晴らしく、ドライブシュートのフリック部分の開発はこれでかなり助けられました。 が、画質も悪く、GUI周りの確認とか、結局実機出力して確認という作業を何回もやりました。 そんでもってネイティブ出力がとても遅い。

タイトル画像がダサいけど、ダサいからアンインストールするって人はほとんどいないだろうし、他にもいろいろ導入したいものもあったけど、タイムリミットが来たので、ひとまずリリースして、その辺はおいおい。。。 マルチプラットフォームの素晴らしさも享受できたけど、元が取れるかな。

早速、レビューも書いていただきました。 フライングドライブシュートを作ったつもりはなかった。