miya8060
← back to blog

NOTE · 2026-05-09

Unity Personal license の CI activation が完全廃止された (2026-05): dotnet 姉弟 .csproj に逃がす

Unity Personal を使った GitHub Actions の自動テストが、game-ci の deprecation と Unity 公式 manual activation の Personal 拒否で二重に死んでいた。Pro 移行 ($2,200/年~) も self-hosted runner も避けたかったので、Domain 層の noEngineReferences を活かして姉弟 .csproj を立て、dotnet test に逃がした。ubuntu-latest で 43 秒 6/6 pass。

unitycidotnetgithub-actionstesting

新しく立てた個人 Unity プロジェクト (AtmoTest) で GitHub Actions に Unity Test Framework を載せようとしたら、Unity Personal license の CI activation が 2026-05 時点で完全に閉じている ことに正面衝突した。game-ci の従来手順 (.alf を CI で生成 → license.unity3d.com で .ulf 取得 → UNITY_LICENSE secret 登録) は二重に死んでいて、Pro 移行 (年 $2,200~) も self-hosted runner も個人プロジェクトには重い。結局「Domain 層を Unity ランタイム抜きで dotnet test に流す」姉弟 .csproj 戦略に切り替えた。ubuntu-latest で 43 秒 6/6 pass、無料、Editor 不要。

何が「二重に死んでいる」か

3 層に分けて記録しておく。同じ罠を踏む人が続くはず。

1. game-ci の activation action が deprecated

game-ci/unity-request-activation-file@v2 を CI で実行すると:

This action is no longer supported. Please use the updated activation
instructions found at https://game.ci/docs/github/activation

と出てコケる。replacement の workflow action は提供されておらず、docs ページは「ローカル CLI で .alf を作って license.unity3d.com に出して .ulf を貰え」と人手手順を案内している。

2. ローカル CLI で .alf を作る回避策も死んでいる

Unity Editor を閉じた状態で

/Applications/Unity/Hub/Editor/<ver>/Unity.app/Contents/MacOS/Unity \
  -batchmode -nographics -quit -createManualActivationFile -logFile <path>

を叩くと .alf 自体は生成される。今回 Unity_v6000.0.74f1.alf (786 bytes) ができた。しかし license.unity3d.com/manual のアップロード画面に

Unity no longer supports manual activation of Personal licenses.

と書いてあって、Personal の .alf は受け付けない。

3. Unity 公式が Personal の manual activation 廃止を確認

https://docs.unity3d.com/Manual/ManualActivationGuide.html に "The manual activation method doesn't work with floating license subscriptions or Unity Personal." と明記されている。Personal の活性化は Unity Hub login 経由のみで、CI で再現する公式ルートは存在しない。

つまり Personal license で game-ci unity-test-runner を CI で動かす道は完全に閉じている。

取り得る選択肢の比較

選択肢コストProCon
dotnet 姉弟 .csproj設定のみCI 高速、Unity 不要、無料Domain 層に閉じる (Presentation/Bootstrap は別)
self-hosted runnermacOS 常時起動UTF そのまま使えるデーモン管理、セキュリティ、消費電力
Unity Pro 移行年 $2,200~公式ルート、UNITY_SERIAL で一発個人プロジェクトには重い
Unity Cloud Build別サービス契約Unity 公式別契約、CI 統合別
CI で諦めるゼロ即不通解消regression がすり抜ける

「Domain 層は POCO で純 C# として書く」layered architecture が既に入っているなら、姉弟 .csproj が断然安い。AtmoTest はそれが入っていたのでこれを採用した。

採用案: dotnet 姉弟 .csproj

AtmoTest は AtmoTest.Domain.asmdef"noEngineReferences": true を設定して、Domain 層を UnityEngine 参照禁止の POCO に強制している。この制約のおかげで Domain 層のテストコードは純粋な C# + NUnit のみで、Unity ランタイム抜きでもコンパイル・実行できる。

つまり Unity 側の asmdef ベース構造はそのまま残し、CI 用に並走する .csproj を 1 本足すだけで、ソース二重管理なしに dotnet test が動く。

構成

<repo-root>/
├── Assets/
│   ├── Scripts/Domain/        ← noEngineReferences: true な POCO
│   └── Tests/EditMode/        ← UTF asmdef、Domain のみ参照
└── tests-net/
    └── AtmoTest.Tests.csproj  ← Unity 側の .cs を <Compile Include><Link> で参照

.csproj の要点

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <EnableDefaultCompileItems>false</EnableDefaultCompileItems>
  </PropertyGroup>

  <ItemGroup>
    <Compile Include="../Assets/Scripts/Domain/**/*.cs">
      <Link>Domain/%(RecursiveDir)%(Filename)%(Extension)</Link>
    </Compile>
    <Compile Include="../Assets/Tests/EditMode/**/*.cs">
      <Link>Tests/%(RecursiveDir)%(Filename)%(Extension)</Link>
    </Compile>
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
    <PackageReference Include="NUnit" Version="3.14.0" />
    <PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
  </ItemGroup>
</Project>

3 つだけ覚えておく必要がある。

  • EnableDefaultCompileItems=false は必須: これを忘れると tests-net/ 配下の (空の) .cs を漁ろうとした上で、<Compile Include> で取り込んだ Domain ソースと重複して error CS0579 (重複属性) や CS0260 (partial class 不一致) を吐く。
  • NUnit は 3.x 系を選ぶ: NUnit 4 は Assert.AreEqual を Classic から NUnit.Framework.Legacy.ClassicAssert に分離しているので、Unity 同梱 NUnit (3.5.x) で書いた既存テストコードがコンパイルできない。3.14.0 が NUnit 3 系の最新かつ Unity と同じ major で齟齬なし。
  • <Link> メタデータは IDE 表示専用: Domain/ と Tests/ の仮想フォルダを Visual Studio / Rider で見やすくする飾り。実体パスは Unity 側のままで、コンパイル結果には影響しない。

.gitignore の Unity テンプレに *.csproj ブランケット除外があるので !tests-net/*.csproj 例外を入れる。tests-net/{bin,obj}/ の build output も追加 ignore。

CI workflow

name: Tests
on:
  push: { branches: [main] }
  pull_request: { branches: [main] }
  workflow_dispatch:
jobs:
  domain:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-dotnet@v4
        with: { dotnet-version: 8.0.x }
      - run: dotnet restore
        working-directory: tests-net
      - run: dotnet test --no-restore --logger "console;verbosity=normal"
        working-directory: tests-net

実測: ubuntu-latest で 43 秒で 6/6 pass (setup-dotnet 22s + restore 4s + test 9s + その他)。game-ci の Docker pull (3〜5 分) と比べて桁違いに速い。

ローカル TDD ループは無傷

Unity Editor 側は AtmoTest.Tests.EditMode.asmdef を使い続ける。Unity Test Framework + MCP for Unity の mcp__UnityMCP__run_tests で Red → Green を回す日常運用は完全に従来通り。ソースの単一の真実は Unity 側 .cs ファイル、姉弟 .csproj は CI でこれを linking して同じテストを別ランナーで走らせるだけのミラー。同期は不要。

残るトレードオフ

  • Presentation 層と Bootstrap 層は CI 対象外: これらは UnityEngine 参照を持つので姉弟 .csproj に含められない。ロジックを Domain 層に寄せ、Presentation は薄い MonoBehaviour adapter に保つ規律 (= もともとの layered architecture の意図) が CI 安全網の前提になる。
  • PlayMode テストは CI で動かない: 必要になったら self-hosted runner を立てるか、PlayMode に依存しないテスト設計に保つ。
  • Domain 層の境界が曖昧になるとすぐ破綻する: noEngineReferences: true を asmdef で常に強制し続けることが必須。ここが緩むと姉弟 .csproj のコンパイルが落ちる、つまり CI が壊れる形で即時にフィードバックが返る (これは安全機構として機能する)。

おまけ: squash merge の add-then-delete 罠

PR #1 (TDD foundation) に最初から含めていた broken な activate.yml を、戦略変更後に同じ PR 内で git rm した。しかし PR を squash merge すると ブランチ起点 → HEAD の累積 diff が 1 commit に潰れる ので、「PR #1 内では add → delete を打ち消し合って no-op」になる。一方で別 PR (#2) で main に直接 add していた activate.yml は残っており、結果として merge 後の main にファイルが居座った。

教訓: 別 PR で先に main に入った file を削除したい場合、その PR のマージ前に消すのではなく、main から派生した独立 PR (今回 PR #3) で明示削除する。squash merge を使う限り、累積 diff が打ち消し合うパターンは常に注意する。

なぜここで効くか

個人 Unity プロジェクトで「テストはちゃんと CI で回したい、でも Pro は出せない、self-hosted runner も立てたくない」という制約を満たす唯一の現実解は、Domain を POCO に切り出して dotnet test に逃がすこと。Unity ランタイムを CI に持ち込まない設計に倒すと、活性化 / ライセンス / Docker pull の重い依存が全部消えて、43 秒で green が返る世界になる。layered architecture を最初から守っていたことが効いた一例。

(Origin: 2026-05-09。AtmoTest PR #1 / #2 / #3 で実装、final main commit de74dfegame-ci/unity-request-activation-file@v2 deprecated → ローカル CLI で .alf 生成成功 → license.unity3d.com が Personal 拒否 → Unity 公式 manual activation 廃止確認 → 戦略転換、の順で行き詰まりを 1 日で抜けた。)