github actionsでunityビルドしてiOSとAndroidのストアに自動的に提出する

こんにちは。[virapture株式会社](https://virapture.com)の[もぐめっと](https://twitter.com/mogmet)です。

![mogmet](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42234/a7a01e3a-d281-9b12-f3ec-9882a8dae412.jpeg)

この記事を書いてるときは冬なんですけど、もう自分の写真がなさすぎて夏までさかのぼってしまいました。

本日は[CI/CD with Unity, GitHub Actions, and Fastlane](https://medium.com/@davidmfinol/ci-cd-with-unity-github-actions-and-fastlane-e7aaa337784)という記事を参考にUnityでの自動ビルドとストア提出を作ってみたのでそのメモ書きを残しておきます。

## 概要

UnityにはUnity Cloud Buildという公式のCIがあるのですが、いかんせんfastlaneとの連携ができないので、証明書を引っ張ってきたり、ストアにアップロードというのができません。

そこで探してみたところ、[GameCI](https://game.ci/)が出している[unity-builder](https://github.com/marketplace/actions/unity-builder)というgithub actionsを使ってビルドを行い、デプロイにはfastlaneを使用することでうまくできました。

このunity-builderが結構優秀で、ライセンスの認証とかもやってくれる。そして、ビルドが終わった後はライセンスをリターンしてくれるという優れもの。
もはやUnity Cloud Build使わなくてもCIができちゃいます。


## 構築手順

### github actions workflowの設置

[記事の人のリポジトリのファイル](https://github.com/finol-digital/Card-Game-Simulator/blob/develop/.github/workflows/main.yml)をベースにしながら作りました。

ios,android以外にもmac, windowsなどもあったのですが、邪魔だったのでそのへんは抹消しました。

こんなワークフローになってます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42234/fc895ea7-6bb4-6738-9beb-1f216ed850d1.png)

iOSのビルド長すぎぃぃ。。
お待ちかね実体ファイルはこんな感じになってます。

```yml
name: Test, Build, and Release CGS

on:
  # push: { branches: [master] } #masterにpushされたときにやりたい人はこちらをお使いください
  release: { types: [published] }

env:
  UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
  UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
  UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}
  BUILD_NUMBER: ${{ github.run_number }}

jobs:
  tests:
    name: Test Code Quality
    runs-on: ubuntu-latest
    timeout-minutes: 60
    steps:
      - name: Checkout Repository
        uses: actions/checkout@v2
        with:
          fetch-depth: 0
      - name: Cache Library
        uses: actions/cache@v2
        with:
          path: Library
          key: Library
      - name: Run EditMode and PlayMode Tests
        uses: game-ci/unity-test-runner@main
      - name: Publish Test Results
        if: ${{ always() }} # Avoid skipping on failed tests
        uses: davidmfinol/unity-test-publisher@main
        with:
          githubToken: ${{ secrets.GITHUB_TOKEN }}

  buildWithLinux:
    name: Build for ${{ matrix.targetPlatform }} by Unity
    runs-on: ubuntu-latest
    timeout-minutes: 90
    needs: tests
    strategy:
      fail-fast: false
      matrix:
        targetPlatform:
          - Android
          - iOS
    steps:
      - name: Checkout Repository
        uses: actions/checkout@v2
        with:
          fetch-depth: 0
          lfs: true
      - name: Cache Library
        uses: actions/cache@v2
        with:
          path: |
            Library
            build/${{ matrix.targetPlatform }}
          key: Library-${{ matrix.targetPlatform }}-
          restore-keys: Library-
      - name: Free Disk Space for Android
        if: matrix.targetPlatform == 'Android'
        run: |
          sudo swapoff -a
          sudo rm -f /swapfile
          sudo apt clean
          docker rmi $(docker image ls -aq)
          df -h
      - name: Build Unity Project
        uses: game-ci/unity-builder@main
        with:
          customParameters: -buildNumber ${{ github.run_number }}
          targetPlatform: ${{ matrix.targetPlatform }}
          buildMethod: Cgs.Editor.BuildCgs.BuildOptions
          androidAppBundle: true
          androidKeystoreName: keystore.keystore
          androidKeystoreBase64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
          androidKeystorePass: ${{ secrets.ANDROID_KEYSTORE_PASS }}
          androidKeyaliasName: ${{ secrets.ANDROID_KEYALIAS_NAME }}
          androidKeyaliasPass: ${{ secrets.ANDROID_KEYALIAS_PASS }}
      - name: Upload Build
        uses: actions/upload-artifact@v2
        if: github.event.ref != 'refs/heads/develop'
        with:
          name: cgs-${{ matrix.targetPlatform }}
          path: build/${{ matrix.targetPlatform }}

  releaseToGooglePlay:
    name: Release to the Google Play Store
    runs-on: ubuntu-latest
    timeout-minutes: 60
    needs: buildWithLinux
    if: github.event.action == 'published'
    env:
      GOOGLE_PLAY_KEY_FILE: ${{ secrets.GOOGLE_PLAY_KEY_FILE }}
      GOOGLE_PLAY_KEY_FILE_PATH: ${{ format('{0}/fastlane/api-finoldigital.json', github.workspace) }}
      ANDROID_BUILD_FILE_PATH: ${{ format('{0}/build/Android/Android.aab', github.workspace) }}
      ANDROID_PACKAGE_NAME: com.sample.app # 自分のアプリのパッケージ名に合わせよう！
      RELEASE_NOTES: ${{ github.event.release.body }}
    steps:
      - name: Checkout Repository
        uses: actions/checkout@v2
      - name: Download Android Artifact
        uses: actions/download-artifact@v2
        with:
          name: cgs-Android
          path: build/Android
      - name: Prepare for Upload
        run: |
          echo "$GOOGLE_PLAY_KEY_FILE" > $GOOGLE_PLAY_KEY_FILE_PATH
          echo "$RELEASE_NOTES" > fastlane/metadata/android/en-US/changelogs/default.txt
      - name: Upload to Google Play
        uses: maierj/fastlane-action@v1.4.0
        with:
          lane: 'android playstore'

  buildIOS:
    name: Build Archive for iOS
    runs-on: macos-latest
    timeout-minutes: 60
    needs: buildWithLinux
    if: github.event.action == 'published'
    env:
      APPLE_CONNECT_EMAIL: ${{ secrets.APPLE_CONNECT_EMAIL }}
      APPLE_DEVELOPER_EMAIL: ${{ secrets.APPLE_DEVELOPER_EMAIL }}
      APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
      APPLE_TEAM_NAME: ${{ secrets.APPLE_TEAM_NAME }}
      APPLE_ITC_TEAM_ID: ${{ secrets.APPLE_ITC_TEAM_ID }}
      MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
      MATCH_PERSONAL_ACCESS_TOKEN: ${{ secrets.MATCH_PERSONAL_ACCESS_TOKEN }}
      IOS_APP_ID: com.sample.app # bundle idを指定します
      IOS_BUILD_PATH: ${{ format('{0}/build/iOS', github.workspace) }}
      PROJECT_NAME: sampleapp
      RELEASE_NOTES: ${{ github.event.release.body }}
      MATCH_REPOSITORY_ACCOUNT: ${{ secrets.MATCH_REPOSITORY_ACCOUNT }}
      USYM_UPLOAD_AUTH_TOKEN: 'fake' # ビルドが途中でこけるのでfake用に環境変数を追加。
    steps:
      - name: Checkout Repository
        uses: actions/checkout@v2
      - name: Cache restore for debug
        uses: actions/cache@v2
        with:
          path: |
            Library
            build/iOS
          key: Library-iOS-
          restore-keys: Library-
      - name: Download iOS Artifact
        uses: actions/download-artifact@v2
        with:
          name: cgs-iOS
          path: build/iOS
      - name: Cache restore cocoapods # firebase使ってるとcocoapodsを使うのですが、cocoapodsのキャッシュとらないと毎回時間かかるのでキャッシュしておきます。
        uses: actions/cache@v2
        if: ${{ always() }}
        with:
          path: |
            build/iOS/iOS/Pods
            ~/.cocoapods/repos
          key: Pods-${{ hashFiles('**/Podfile') }}
          restore-keys: Pods-
      - uses: actions/setup-ruby@v1
        with:
          ruby-version: 2.7
      - name: Prepare for fastlane # GateKeeper対策
        run: |
          sudo spctl --master-disable
      - name: Archive iOS
        uses: maierj/fastlane-action@v2.0.1
        with:
          lane: 'ios build'
      - name: run if fail_step failed # ビルドがコケた原因がわかるようにcatしておきます
        if: failure()
        run: cat /Users/runner/Library/Logs/gym/*Unity-iPhone.log
      - name: Upload Build
        uses: actions/upload-artifact@v2
        if: github.event.ref != 'refs/heads/develop'
        with:
          name: ipa
          path: |
            ${{ github.workspace }}/*.ipa
            ${{ github.workspace }}/*.dSYM.zip

  releaseToAppStore:
    name: Release to the App Store
    runs-on: macos-latest
    timeout-minutes: 60
    needs: buildIOS
    if: github.event.action == 'published'
    env:
      APPLE_CONNECT_EMAIL: ${{ secrets.APPLE_CONNECT_EMAIL }}
      APPLE_DEVELOPER_EMAIL: ${{ secrets.APPLE_DEVELOPER_EMAIL }}
      APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
      APPLE_TEAM_NAME: ${{ secrets.APPLE_TEAM_NAME }}
      APPLE_ITC_TEAM_ID: ${{ secrets.APPLE_ITC_TEAM_ID }}
      FASTLANE_PASSWORD: ${{ secrets.FASTLANE_PASSWORD }}
      FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD: ${{ secrets.FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD }}
      MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
      MATCH_PERSONAL_ACCESS_TOKEN: ${{ secrets.MATCH_PERSONAL_ACCESS_TOKEN }}
      ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
      ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
      ASC_KEY_CONTENT: ${{ secrets.ASC_KEY_CONTENT }}
      IOS_APP_ID: com.sample.app # bundle idを指定します
      IOS_BUILD_PATH: ${{ format('{0}/build/iOS', github.workspace) }}
      PROJECT_NAME: sampleapp
      RELEASE_NOTES: ${{ github.event.release.body }}
      MATCH_REPOSITORY_ACCOUNT: ${{ secrets.MATCH_REPOSITORY_ACCOUNT }}
    steps:
      - name: Checkout Repository
        uses: actions/checkout@v2
      - name: Download iOS Artifact
        uses: actions/download-artifact@v2
        with:
          name: ipa
          path: |
            ${{ github.workspace }}/*.ipa
            ${{ github.workspace }}/*.dSYM.zip
      - name: Upload to the App Store
        uses: maierj/fastlane-action@v1.4.0
        with:
          lane: 'ios release'
```

このファイルのポイントを話しておくと、linuxでビルドが終わった後、fastlaneを使ってandroid/iosともにビルドもしくはアップロードを行っています。
iosは結構曲者で、UnityでCLIビルドが結構大変なので随所随所にビルドできるような仕組みを置いてます。

https://qiita.com/mogmet/items/4235809351dc6d05694c

### fastlaneの設置

fastlaneで使うファイルを[参考ファイル](https://github.com/finol-digital/Card-Game-Simulator/blob/develop/fastlane/Fastfile)をベースにカスタマイズした下記を設置しています。

```ruby
keychain_name = "temporary_keychain"
keychain_password = SecureRandom.base64

platform :android do

  desc "Upload a new Android version to the Google Play Store"
  lane :playstore do
    upload_to_play_store(
      aab: "#{ENV['ANDROID_BUILD_FILE_PATH']}",
      track: 'internal',
      skip_upload_screenshots: true,
      skip_upload_images: true
    )
  end

end

platform :ios do

  desc "Push a new release build to the App Store"
  lane :release do
    api_key = app_store_connect_api_key(
      key_id: ENV['ASC_KEY_ID'], # your key id
      issuer_id: ENV['ASC_ISSUER_ID'], # your issuer id
      key_content: ENV['ASC_KEY_CONTENT'], # your secret key body
      # ex) key_content: '-----BEGIN PRIVATE KEY-----\nfoobar\n-----END PRIVATE KEY-----'
    )
    upload_to_app_store(
      api_key: api_key, # pass api_key
      force: true,
      skip_screenshots: true,
      skip_metadata: true
    )
  end

  desc "Submit a new Beta Build to Apple TestFlight"
  lane :beta do
    api_key = app_store_connect_api_key(
      key_id: ENV['ASC_KEY_ID'], # your key id
      issuer_id: ENV['ASC_ISSUER_ID'], # your issuer id
      key_content: ENV['ASC_KEY_CONTENT'], # your secret key body
      # ex) key_content: '-----BEGIN PRIVATE KEY-----\nfoobar\n-----END PRIVATE KEY-----'
    )
    upload_to_testflight(
      api_key: api_key, # pass api_key
      skip_waiting_for_build_processing: true
    )
  end

  desc "Create .ipa"
  lane :build do
    cocoapods(podfile: "#{ENV['IOS_BUILD_PATH']}/iOS/Podfile")
    disable_automatic_code_signing(path: "#{ENV['IOS_BUILD_PATH']}/iOS/Unity-iPhone.xcodeproj")
    certificates
    update_project_provisioning(
      xcodeproj: "#{ENV['IOS_BUILD_PATH']}/iOS/Unity-iPhone.xcodeproj",
      target_filter: "Unity-iPhone",
      profile: ENV["sigh_#{ENV['IOS_APP_ID']}_appstore_profile-path"], # より動的にみるようにしています
      code_signing_identity: "Apple Distribution: #{ENV['APPLE_TEAM_NAME']} (#{ENV['APPLE_TEAM_ID']})"
    )
    gym(
      workspace: "#{ENV['IOS_BUILD_PATH']}/iOS/Unity-iPhone.xcworkspace",
      scheme: "Unity-iPhone",
      clean: true,
      #clean: false,
      skip_profile_detection: true,
      codesigning_identity: "Apple Distribution: #{ENV['APPLE_TEAM_NAME']} (#{ENV['APPLE_TEAM_ID']})",
      export_method: "app-store",
      export_options: {
        method: "app-store",
        provisioningProfiles: {
          ENV["IOS_APP_ID"] => "match AppStore #{ENV['IOS_APP_ID']}"
        }
      }
    )
  end

  desc "Synchronize certificates"
  lane :certificates do
    cleanup_keychain
    create_keychain(
      name: keychain_name,
      password: keychain_password,
      default_keychain: true,
      lock_when_sleeps: true,
      timeout: 3600,
      unlock: true
    )
    match(
      type: "appstore",
      readonly: true,
      keychain_name: keychain_name,
      keychain_password: keychain_password
    )
  end

  lane :cleanup_keychain do
    if File.exist?(File.expand_path("~/Library/Keychains/#{keychain_name}-db"))
      delete_keychain(name: keychain_name)
    end
  end

  after_all do
    if File.exist?(File.expand_path("~/Library/Keychains/#{keychain_name}-db"))
      delete_keychain(name: keychain_name)
    end
  end
end
```

元ファイルからcocoapodsのインストールや、api keyまわりの設定、provisioning profileの指定方法などが若干違います。

また、問題を切り分けやすくするためにビルドとapp storeにリリースする処理はわけたりしています。

参考元ファイルでは現在、apikey周りは[証明書をおいていい感じにやる](https://github.com/finol-digital/Card-Game-Simulator/blob/develop/fastlane/Fastfile#L31)ようにしているみたいです。

他にfastlaneに付随するファイルも置いていきます

- Appfile

```ruby
for_platform :android do
  package_name(ENV["ANDROID_PACKAGE_NAME"])
  json_key_file(ENV["GOOGLE_PLAY_KEY_FILE_PATH"])
end

for_platform :ios do
  app_identifier(ENV["IOS_APP_ID"])

  apple_dev_portal_id(ENV["APPLE_DEVELOPER_EMAIL"])  # Apple Developer Account
  itunes_connect_id(ENV["APPLE_CONNECT_EMAIL"])     # App Store Connect Account

  team_id(ENV["APPLE_TEAM_ID"]) # Developer Portal Team ID
  itc_team_id(ENV["APPLE_ITC_TEAM_ID"]) # App Store Connect Team ID
end
```

- Deliverfile

```ruby

submit_for_review false
automatic_release true
force true
skip_screenshots true
release_notes({
  'default' => ENV["RELEASE_NOTES"],
  'en-US' => ENV["RELEASE_NOTES"]
})
run_precheck_before_submit false

submission_information({
  add_id_info_uses_idfa: false,
  export_compliance_compliance_required: false,
  export_compliance_encryption_updated: false,
  export_compliance_app_type: nil,
  export_compliance_uses_encryption: false,
  export_compliance_is_exempt: false,
  export_compliance_contains_third_party_cryptography: false,
  export_compliance_contains_proprietary_cryptography: false,
  export_compliance_available_on_french_store: false
});
```

- Matchfile

```ruby
git_url("https://github.com/iosのcertificateおいてるgitリポジトリ") # sshのurlではなく、httpsで指定してます。
git_basic_authorization(Base64.strict_encode64("#{ENV['MATCH_REPOSITORY_ACCOUNT']}:#{ENV['MATCH_PERSONAL_ACCESS_TOKEN']}"))
storage_mode("git")
type("appstore") # The default type, can be: appstore, adhoc, enterprise or development

app_identifier(["com.sample.app"]) # bundleIDを指定しよう
username("apple@example.com") # Your Apple Developer Portal username
```

### projectの準備

[BuildCgs.cs](https://github.com/finol-digital/Card-Game-Simulator/blob/develop/Assets/Scripts/Cgs/Editor/BuildCgs.cs)をAssets/Scripts/Cgs/Editorディレクトリに置いておく。
このファイルがビルド番号の設定などをしてくれている。

もぐめっとの場合、CIのビルド番号とアプリのビルド番号を紐付けしたかったので冒頭部分だけbuildNumberを参照するように少し修正しました。

```修正後BuildCgs.cs
            PlayerSettings.macOS.buildNumber = options["buildNumber"];
            PlayerSettings.iOS.buildNumber = options["buildNumber"];
            PlayerSettings.Android.bundleVersionCode = int.Parse(options["buildNumber"]);
            PlayerSettings.WSA.packageVersion = new Version(options["buildVersion"]);
```

#### metadataの準備

metadataがないとfastlane途中でこけるため準備しておく。

androidの準備

```
fastlane run download_from_play_store json_key:<json path>
```

jsonファイルはストアにアクセスするために必要なものになるが、こちらの記事に解説は委ねます。

https://dev-yakuza.posstree.com/react-native/fastlane/#api-access%E3%81%AE%E3%81%9F%E3%82%81service-account%E7%94%9F%E6%88%90


iosの準備

```
fastlane deliver download_metadata
```

上記で生成されたファイルをコミットしておく

### githubのsecrets設定

先術したymlやfastfileなどを見てもらったとおり、環境変数をふんだんに使うので、その準備をしていきます。

#### game-ci/unity-builder周りで使う環境変数

細かいことは[Game CIのドキュメントをご参照](https://game.ci/docs/github/builder)ください

[Unityのアカウント周りの情報](https://game.ci/docs/github/builder#professional-license)は下記の変数になります

- UNITY_EMAIL
- UNITY_PASSWORD
- UNITY_SERIAL

unityのserialについては[unityのwebサイト](https://id.unity.com/ja/subscriptions)にログインして確認します。
![スクリーンショット 2021-03-21 15.40.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42234/134535ba-b8d9-3909-caa6-d48bb0ea4fbd.png)




[Androidの公開設定周り](https://game.ci/docs/github/builder#androidappbundle)はこちらの変数になります

- ANDROID_KEYALIAS_NAME
- ANDROID_KEYALIAS_PASS
- ANDROID_KEYSTORE_BASE64
- ANDROID_KEYSTORE_PASS

上記四点はandroidの公開設定でも使うこの辺の情報ですね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42234/e7e023a7-f3e8-7b2a-f138-4a96c81ec542.png)

keystoreの作り方は公式に委ねます

https://developer.android.com/studio/publish/app-signing?hl=ja

#### fastlaneのアップロード周りで使ってる環境変数

アップロードするときなどに使われるfastlaneのDeliverで使われるAppfileで定義して使ってる一覧です。

- APPLE_CONNECT_EMAIL

App Store Connect Accountにアクセスできるアカウントを指定します

- APPLE_DEVELOPER_EMAIL

Apple Developer Accountにアクセスできるアカウントを指定します。


- APPLE_TEAM_ID
- APPLE_TEAM_NAME

上記2点は[Developer Portalのサイト](https://developer.apple.com/account/#/membership)でアクセスできる項目を確認します

![スクリーンショット 2021-03-21 16.00.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42234/fdb1566b-21d0-a299-9af8-cc32a26475b9.png)

- APPLE_ITC_TEAM_ID

APPLE_TEAM_IDではなくて、app store connectで使うアカウントのほうのIDになるらしい。とりあえずfastlane使えるならspaceship使えば取得できます。

https://enmtknt.hateblo.jp/entry/2019/02/21/175825

- ASC_ISSUER_ID
- ASC_KEY_CONTENT
- ASC_KEY_ID

Appleの２段階認証を突破してアップロードできるようにする用の変数です。解説は下記記事に委ねます。

https://zenn.dev/himara2/articles/6a46c977567615

- FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD
- FASTLANE_PASSWORD

先程のAppStoreConnect API Keyを使った方法でできると思うので、おそらくこの2点は設定しなくてもいいと思いますが、念の為設定しておきます。

設定方法はこちらに。

https://qiita.com/watanave/items/6e1982dfb9631172895d#fastlane_apple_application_specific_password%E3%81%AE%E7%94%9F%E6%88%90%E6%96%B9%E6%B3%95

- GOOGLE_PLAY_KEY_FILE

metadataをダウンロードする時お話した、jsonファイルの中身をはっつけます。

再掲)

https://dev-yakuza.posstree.com/react-native/fastlane/#api-access%E3%81%AE%E3%81%9F%E3%82%81service-account%E7%94%9F%E6%88%90

#### fastlane match周りの環境変数

fastlane matchについてはこちらをご参照ください。証明書を管理するやつです。

https://qiita.com/kotarella1110/items/840af2cf80aaea1fb035

- MATCH_PASSWORD

matchに設定してるパスワードを指定します。

- MATCH_PERSONAL_ACCESS_TOKEN
- MATCH_REPOSITORY_ACCOUNT

上記2つはMatchfileで指定したgit_basic_authorizationに使うものなのですが、repositoryのcloneがgithub action上でできるようにするために使っています。
MATCH_PERSONAL_ACCESS_TOKENはgithubのaccess tokenを指定します。

https://docs.github.com/ja/github/authenticating-to-github/creating-a-personal-access-token

MATCH_REPOSITORY_ACCOUNTはMatchのリポジトリにアクセスできるアカウントを指定します。（access tokenを発行したアカウント名ですね）


## 使い方

ようやく設定がおわりました。

このworkflowはreleaseを切ったときに初めて発火されます。

リポジトリの右側にあるreleasesから
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42234/0629d1fd-3dbc-a532-0541-337714de7ef6.png)

Draft a new releaseボタンからリリースを作りましょう！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/42234/c5e4a3c3-8966-6414-1a8e-569ee13cbd57.png)

成功すればreleaseで書いた内容でappstoreとplay storeにリリースされるはずです！

## まとめ

というわけでgame ciさんのunity-builderを使うことでgithub actionsでunityのCIを回すことができるようになりました！
unityはビルドに時間かかるので結構めんどくさかったのですがこの辺が省略できるようになるのはとても楽になりました。

みなさんの開発にあてる時間がこれで増えたら幸いです！


最後に、[ワンナイト人狼オンライン](https://online.1nite-jinro.com/link/blog)というゲームを作ってます！よかったら遊んでね！
他にも[camecon](https://camecon.me/)や[offcha](http://offcha.com/)といったサービスも作ってるのでよかったら使ってね！