【Unity】GameCIとGithubActionsを使ってクラウドビルド環境を構築する

# はじめに
この記事は[Unity Advent Calendar 2023](https://qiita.com/advent-calendar/2023/unity) 13日目の記事です

これまでUnityでCICDを実現する記事をいくつか書いてきました

https://qiita.com/broken55/items/9220e86460e320ffefbf

https://qiita.com/broken55/items/3e3908df74e911e17cf1

近年はリモートワークが普及したことによりオンプレミス環境のJenkinsよりクラウドビルド環境が求められることが増えてきました。
今回の記事では[GameCI](https://game.ci/)と[GithubActions](https://docs.github.com/ja/actions/learn-github-actions/understanding-github-actions)を使ってクラウドビルド環境の構築の仕方を紹介します

今回使ったプロジェクトなどはGithubにアップしてあります

https://github.com/tkada/gameci_sample

# GameCIとは

https://game.ci/

GameCIはUnityのクラウドビルド環境を簡単に構築できるライブラリです
GitHub/GitLab/CircleCIなどに対応しています

GameCIでビルドするまでの大まか流れとしては以下の通りです。
1.Unityプロジェクトの準備
2.ライセンスファイルの取得
3.ライセンスファイルをGithubに登録する
4.GithubActionsでビルドできるように設定
5.ビルドを実行する

# Unityプロジェクトの準備
まずUnity Projectを作成し、Githubにプッシュしましょう

とりあえずシーンにはテキストが表示されるようにしておきます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105021/8f7c1324-b126-ff14-e2cf-ff926c3b6cb3.png)

Build Settingsにビルドしたいシーンを入れておきます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105021/4ebf0b87-4751-06d6-2a92-d08f560519ac.png)

とりあえず準備ができました
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105021/ecb9edaa-bdf1-f83b-5698-2482e30e3536.png)

# ライセンスファイルの取得
GithubActions上でUnityを動かすためにはUnityのライセンスファイル(.ulfファイル)が必要となります
Personalライセンスの場合でも取得が必須となっています。


[GameCI](https://game.ci/docs/github/activation/)公式によると下記の場所にulfファイルは存在します。
```
Windows: C:\ProgramData\Unity\Unity_lic.ulf
Mac: /Library/Application Support/Unity/Unity_lic.ulf
Linux: ~/.local/share/unity3d/Unity/Unity_lic.ulf
```
どのOSのファイルであっても問題ないようです。

※少し古い記事だとUnityのManualActivationを使ってulfファイルを取得する方法が掲載されていますが、2023年12月現在 <b>PersonalライセンスはManual Activationを使ってulfファイルを取得することができません</b>

# ライセンスファイルをGithubに登録する
GithubのSettings->Secrets and variables->Actionsを開きます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105021/49381c37-d9b5-ac1f-e50e-00a4783a7be6.png)

Repository secretsのNew repository secretをクリックします
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105021/5bd23d4e-470f-acc1-3545-961a6c9b3c15.png)

入力画面になるのでNameに`UNITY_LICENSE`
Secretに.ulfファイルの中身をコピーしてAdd secretします
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105021/3e31f49c-9af0-5e7b-a4ce-1f75f876e66e.png)

同様に`UNITY_EMAIL`と`UNITY_PASSWORD`にUnityログインに使っているメールとパスワードを入力して保存します
※Secretに入れた文字列はログに表示されたり、他のGithubユーザーに見られることはありません

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105021/267acfcc-5ed6-940f-fd8f-cc448f865a3c.png)


# GithubActionsでビルドできるように設定
GithubのActionsタブを開き、Simple workflowからGithubActions用のファイル(workflow)を作成します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105021/f1e0f8e6-9f05-1725-6789-638064bb59cb.png)

Editorが開いたら下記のコードをコピーしてコミットします
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105021/d06209ee-03f8-0d52-b48b-8fe2d0862369.png)


```build.yml
# 名前を付ける。これがGithubActions上に表示される
name: Run the Windows build
on:
  workflow_dispatch: {}
jobs:
  build:
    name: Run the Windows build
    runs-on: ubuntu-latest
    steps:
      # リポジトリのチェックアウト
      - name: Check out my unity project.
        uses: actions/checkout@v4
      - name: Create LFS file list
        run: git lfs ls-files -l | cut -d' ' -f1 | sort > .lfs-assets-id

      # LFSファイルのキャッシュを取得する
      - name: Restore LFS cache
        uses: actions/cache@v3
        id: lfs-cache
        with:
          path: .git/lfs
          key: ${{ runner.os }}-lfs-${{ hashFiles('.lfs-assets-id') }}

      # LFSファイルをPullする
      - name: Git LFS Pull
        run: |
          git lfs pull
          git add .
          git reset --hard

      # ファイル名にgitのハッシュを含めたいので$GITHUB_OUTPUTに保存しておく
      - name: Set outputs
        id: vars
        run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
        
      # Cache
      - uses: actions/cache@v3
        with:
          path: Library
          key: Library-${{ hashFiles('Assets/**', 'Packages/**', 'ProjectSettings/**') }}
          restore-keys: |
            Library-

      # Unityプロジェクトのビルド
      # 設定しない場合、buildフォルダに出力される
      - name: Run the Windows build
        uses: game-ci/unity-builder@v4
        env:
          UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
          UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
          UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
        with:
          targetPlatform: StandaloneWindows64
          unityVersion: 2022.3.0f1  #ここに使うUnityのバージョンを入れる

      # 成果物をZIPにしてアップロードする
      - name: Upload the Windows Build
        uses: actions/upload-artifact@v3
        with:
          name: Build-${{ steps.vars.outputs.sha_short }}
          path: build
```

# ビルドを実行する
上手く設定ができるとActionsタブに先ほど作ったworkflowが表示されます
`Run workflow`を実行してみましょう
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105021/474c1eda-5b7b-928c-9ba5-b73097a99720.png)

上手くいくと下記のようになりArtifactsからZIPがダウンロードできます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105021/8af0491b-7d45-0b3c-fadc-1a9fadf7b576.png)

ダウンロードしたZIPを展開してStandaloneWindows64.exeを実行するとプロジェクトが正しくビルドされていることが分かります
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105021/2a2ce8a2-8a9e-1478-2588-5503ebcf3b84.png)

## workflowが表示されない場合
GithubActionsはデフォルトブランチで実行されることが前提になっています
そのため、デフォルトブランチ以外でworkflowを作成したときにworkflowとして表示されない場合があります。
この時は一度デフォルトブランチでファイルを作成したのち、別ブランチで作業をするのが良いでしょう

# 応用編
## 自動でビルドするようにする
workflowを少し変更して自動でビルドが走るように変更します
下記のように記述するとmainブランチにpushがあるたびに自動で実行されるようになります
```yaml
name: Run the Windows build
on:
  workflow_dispatch: {}
  # mainブランチにpushがあるたびに自動で実行される
  push:
    branches:
      - main
```

他にもプルリクエストが作られたらトリガーにするなどいろいろな使い方があります
公式ドキュメントを見てみてください

https://docs.github.com/ja/actions/using-workflows/workflow-syntax-for-github-actions#on

## Unityの任意の関数を実行する
JenkinsビルドするときもUnityのビルド用の関数を作成してそちらを叩くことが多かったですがGameCIでも同じことができます

まずUnity側にビルド用のクラスを作成します
今回はカスタムパラメータを使う例を示すために、パラメータからもらった値をStreamingAssetsに保存しています
```c#
using UnityEditor;
using UnityEditor.Build.Reporting;
using UnityEngine;

public class Builder
{
    public static void Build()
    {
        var message = "";
        var args = System.Environment.GetCommandLineArgs();
        for(var i=0;i<args.Length;i++)
        {
            if (args[i] == "-message")
            {
                //-messageの次の引数を入力した文字列とする
                message = args[i + 1];
            }
        }

        if(!string.IsNullOrEmpty(message))
        {
            Debug.Log("Message---->"+message);

            using(var writer = new System.IO.StreamWriter(Application.streamingAssetsPath+ "/message.txt"))
            {
                writer.Write(message);
            }
        }

        var option = new BuildPlayerOptions();
        option.target = BuildTarget.StandaloneWindows64;
        //ビルドシーンのパスを配列で指定
        option.scenes = new[] { "Assets/Scenes/SampleScene.unity" };
        //ビルドプラットフォーム名のフォルダに入れないと上手くいかない
        option.locationPathName = "build/StandaloneWindows64/Sample.exe";
        var result = BuildPipeline.BuildPlayer(option);
        if (result.summary.result == BuildResult.Succeeded)
        {
            Debug.Log("BUILD SUCCEED");
            Debug.Log(result.summary.outputPath);
            Debug.Log($"{result.summary.totalSize:N}bytes");
            Debug.Log($"{result.summary.totalTime.TotalSeconds}seconds");
        }
        else
        {
            Debug.LogError("BUILD FAILED");
        }

        EditorApplication.Exit(result.summary.result == BuildResult.Succeeded ? 0 : 1);
    }
}
```

また、StreamingAssetsに保存したパラメータをゲーム上に表示するためにスクリプトを作っておきます
```c#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.IO;

public class MessageFromStreamingAssets : MonoBehaviour
{
    [SerializeField] Text text;

    private void Start()
    {
        try
        {
            using (StreamReader sr = new StreamReader(Application.streamingAssetsPath + "/message.txt"))
            {
                text.text = sr.ReadToEnd();
            }
        }
        catch (System.Exception e)
        {
            Debug.LogException(e, this);
        }
    }
}

```

そしてworkflowに`buildMethod`と`customParameters`を追加し任意の関数にパラメータを渡すように修正します
```yaml
- name: Run the Windows build
        uses: game-ci/unity-builder@v4
        env:
          UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
          UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
          UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
        with:
          targetPlatform: StandaloneWindows64
          unityVersion: 2022.3.0f1  #ここに使うUnityのバージョンを入れる
          buildMethod: Builder.Build #呼び出すクラス名・関数名を入れる
          customParameters: -message MyCustomParameters #渡したいパラメータをここに入れる
```

この状態でビルドするとMyCustomParametersという文字列がゲーム上に表示されるようになりました
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/105021/a938d22e-2db9-d686-c89c-1c0f1fff8f5a.png)

# まとめ
GameCIとGithubActionsを使ったUnityクラウドビルドの仕方を紹介しました
今回はWindowsビルドの例を紹介しましたが他プラットフォームもビルドすることが可能です
またGithubのワークフローに組み合わせることで、プルリクを出したときにビルドチェックを通したりと様々な応用ができます
是非いろいろな使い方を試してみてください