最近又重新回去做一些有關於 Unity 的 Continuous integration 有關的研究,之前卡住的點是在於 Unity 產生出來的 Xcode Project 可能會有一些設定沒有弄好,然後要手工去設定。當然這在 CI 裡面是不能接受的。不過當時一忙就沒有研究下去了,剛好最近 Unity Cloud Build 使用的人變多,也有很多人有同樣的需求,所以就把官方的討論整理一下貼上來。

自動設定 Xcode 的好處主要有

  1. 給 CI 系統使用
  2. 避免人為操作失誤造成 build 出去的 binary 有問題

不過麻煩的點就在於 Apple 官方沒有給出操作 Xcode Project 的 API,目前有一些第三方做的 API 像是使用 Ruby 的 CocoaPods/Xcodeproj:

https://github.com/CocoaPods/Xcodeproj

使用 obj-C 的 appsquickly/XcodeEditor:

https://github.com/appsquickly/XcodeEditor

然後 C# 有 Unity 官方做的 Xcode Manipulation API:

https://bitbucket.org/Unity-Technologies/xcodeapi

文件在:

http://docs.unity3d.com/ScriptReference/iOS.Xcode.PBXProject.html

使用 C# 應該是最方便我們使用的,不過可惜的是這個 API 功能並不完全,在處理 Entitlement 的時候我們還是要自己手動做一點字串取代。

安裝 Xcode Manipulation API

如果你是 Unity 5 的使用者的話 Xcode Manipulation API 已經包含在 UnityEditor 裡面了,Unity 4 的使用者需要去上面的 Bitbucket 網址 clone 原始檔,放在自己 Unity 專案底下的某個 Editor 資料夾下。然後要注意 xcodeapi 底下 Utils.cs 的 class UnityEditor.iOS.Xcode.PBX.Utils 會跟 Unity 4 底下的 UnityEditor.Utils 衝突。要把 UnityEditor.iOS.Xcode.PBX.Utils 改成別的名字。

Xcode 專案結構

Xcode Project

Build 完設定完的 Xcode Project 大概會長的像這樣,我們要操作的內容主要是 Info.plist、Unity-iPhone 底下的 .entitlements、還有 Unity-iPhone.xcodeproj。xcodeproj 是一個 package ,右鍵點選選 Show Package Content 可以看到 project.pbxproj。

PBXProject

Info.plist、.entitlements、還有 project.pbxproj 這三個就是我們主要操作的對象。

PostProcessBuild

PostProcessBuild 這個 attribute 修飾的 static function 會在 Unity 建置完之後被呼叫。這個函式需要接受兩個參數,一個是 BuildTarget enum ,代表建置的目標平台。另一個是 string 是建置的目標目錄。

一般的設定會像這樣:

1
2
3
4
5
6
7
public static void _OnPostprocessBuild (BuildTarget target, string pathToBuiltProject)
{
  if (target == BuildTarget.iPhone)
  {
    ...
  }
}

PostProcessBuild 可以帶一個數字參數代表執行順序,但是詭異的是 Unity 沒有 PreProcessBuild 有時候會造成一些不便。

PostProcessBuild 的參數在這裡:

http://docs.unity3d.com/ScriptReference/Callbacks.PostProcessBuildAttribute.html

增加 Required Device Capability

預設的 Unity Xcode 專案會開 Game Center 的 Entitlements 但是沒有把 gamekit 加到 Required device capability 裡面,所以開起來會跳 issue ,解決方法是把它加進 Info.plist。

Entitlements

Required Device Capability

這邊用的是 Unity Manipulation API ,code 大概像是下面這樣:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
static void _AddDeviceCapabilities(string pathToBuiltProject)
{
	string infoPlistPath = Path.Combine (pathToBuiltProject, "./Info.plist");
	PlistDocument plist = new PlistDocument();
	plist.ReadFromString (File.ReadAllText(infoPlistPath));
	PlistElementDict rootDict = plist.root;
	PlistElementArray deviceCapabilityArray = rootDict.CreateArray("UIRequiredDeviceCapabilities");
	deviceCapabilityArray.AddString("armv7");
	deviceCapabilityArray.AddString("gamekit");
	File.WriteAllText(infoPlistPath,plist.WriteToString());
}

大致上就是用 PlistDocument 開啟 Info.plist ,加入 UIRequiredDeviceCapabilities 這個屬性,然後在上面加入 gamekit。因為實驗結果 Xcode Manipulation API 會做一個覆寫的動作,所以原本存在的 armv7 也要加回去。然後如果你的 app 有需要其他的 Required device capability 也都在這裡可以加。

[Xcode 7/iOS9] Require Full Screen

在 Xcode 7 裡面有新增了一個 Require full screen 的選項,意思是宣告你的 app 不想讓使用者在 iOS 9 新的分割視窗裡面被開啟。

Require Full Screen

一般的遊戲應該都會設定成 yes ,這個設定也在 Info.plist ,key 值是 UIRequiresFullScreen

1
infoPlistRootDict.SetBoolean("UIRequiresFullScreen", true);

增加引入的 Framework

如果說你的 app 需要加入別的 .framework ,這個需要用 Xcode Manipulation API 的 PBXProject 開啟 project.pbxproj

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
static void _AddFrameWork(string pathToBuiltProject)
{
	string pbxProjectPath = Path.Combine (pathToBuiltProject, "./Unity-iPhone.xcodeproj/project.pbxproj");
	PBXProject pbxProject = new PBXProject ();
	pbxProject.ReadFromFile (pbxProjectPath);
	string targetGUID = pbxProject.TargetGuidByName ("Unity-iPhone");
	pbxProject.AddFrameworkToProject (targetGUID, "CoreData.framework", weak:false);
	pbxProject.AddFrameworkToProject (targetGUID, "Social.framework", weak:false);
	File.WriteAllText(pbxProjectPath,pbxProject.WriteToString ());
}

weak 參數是設定這個 framework 是 required 還是 optional。

iCloud

因為接下來的操作 Xcode Manipulation API 不支援,所以就要搞很髒的字串替換了。

建議將 Unity 剛剛建置完的 Xcode 專案複製一份起來,然後用 Xcode 打開 iCloud 的 Entitlement ,拿打開之前跟打開之後的 project.pbxproj 做 diff。

首先是 Unity-iPhone 底下的 .entitlements 檔。這個建議就直接複製出來,然後在 PostProcessing 腳本裡面複製回 Unity-iPhone 。

接下來就是要把這個 .entitlements 檔案加到 project.pbxproj。因為每個 PBXProject 的元素前面有一組 24 位的 hex code,然後沒有人知道 Xcode 是怎麼產生的。所以只能乖乖的比對開啟跟沒開啟 iCloud 的 PBXProject 然後填回去。

首先是 Unity-iPhone PBXFileReference

搜尋目標是:

1
@"/* Begin PBXFileReference section */"

取代成

1
2
@"/* Begin PBXFileReference section */
FFFFFFFFFFFFFFFFFFFFFFFF /* test.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; name = test.entitlements; path = ""Unity-iPhone/test.entitlements""; sourceTree = ""<group>""; };

你的 hex code 跟 .entitlements 檔案名稱不會一樣,要用你的專案裡面看到的為準。建議在字串前面加 @ 作為 verbatim string,這樣就可以直接從 pbxproj 裡面複製出來把 " 跳脫成 "" 就可以拿來比對了。不用自己再把換行換成 \n tab 換成 \t

然後是 Custom template 要把:

1
2
3
@"/* CustomTemplate */ = {
	isa = PBXGroup;
	children = (";

取代成

1
2
3
4
@"/* CustomTemplate */ = {
	isa = PBXGroup;
	children = (";
		FFFFFFFFFFFFFFFFFFFFFFFF /* test.entitlements */,";

Target Attribute:

1
@"TargetAttributes = {";

取代成

1
2
3
4
5
6
7
8
9
@"TargetAttributes = {
	EEEEEEEEEEEEEEEEEEEEEEEE = {
		DevelopmentTeam = EEEEEEEE;
		SystemCapabilities = {
			com.apple.iCloud = {
				enabled = 1;
			};
		};
	};";

一樣那個 hex code 跟那個 DevelopmentTeam 還是以你在啟用過 iCloud 的 PBXProject 版本為準。

最後是要在 XCBuildConfiguration section 底下的 buildSettings 加入 CODE_SIGN_ENTITLEMENTS。這個我是隨便找一個 build settings 然後直接黏在它後面。

例如選擇用:

1
@"CLANG_CXX_LIBRARY = ""libstdc++"";"

取代成:

1
2
@"CLANG_CXX_LIBRARY = ""libstdc++"";
CODE_SIGN_ENTITLEMENTS = ""Unity-iPhone/test.entitlements"";";

這樣就大功告成了。

P.S. 原來在 Unity 論壇上面原作者還有手動加入 CloudKit.framework ,不過我照做反而 Link fail,不做就編過了。這邊不太確定是怎麼回事。如果在你的機器上會跳 CloudKit 的 Link fail 的話就試著加加看吧。

[Xcode 7] Bitcode

Bitcode 是 Apple 新的 App thinning 機制所需的編譯方式,不過因為絕大多數既有的 Plugin 都還沒有改成使用 Bitcode 編譯,所以我們也沒辦法開啟。

Bitcode

關閉的方法是在 build settings 加入

1
ENABLE_BITCODE = NO;

方法跟 CODE_SIGN_ENTITLEMENTS 一樣。

希望大家也 Happy building 啦

參考資料

PBXProject 格式參考資料(非官方):

http://www.monobjc.net/xcode-project-file-format.html

iCloud 的開啟方式是參考這篇論壇文章:

http://forum.unity3d.com/threads/icloud-with-cloud-build.303844/#post-2266181

這邊是 Unity Cloud Build 的集中討論串,裡面有一區 Xcode 操作的討論。

http://forum.unity3d.com/threads/ucb-demos.306687/