一键导入
unity-build
Build Unity APK/IPA on macOS with Unity 6.x — pre-flight checks, version bump, iOS signing params, post-build verification, buildlog, and commit.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Build Unity APK/IPA on macOS with Unity 6.x — pre-flight checks, version bump, iOS signing params, post-build verification, buildlog, and commit.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Build Flutter APK/IPA, generate changelog, update buildlog, and commit version bump. Handles platform selection, build mode, and artifact renaming.
Evidence-driven bug investigation and fix pipeline. Use when something is broken, not working, or the root cause is ambiguous.
Two independent reviewers must both approve high-stakes output before it ships, breaking the single-reviewer blind-spot problem.
Team-orchestrated feature development with parallel specialist agents. Use when building a feature that benefits from multiple agents working concurrently. You act as Product Director coordinating the team.
Interactive agent picker for composing and dispatching parallel teams. Browse available agents across domains, pick a roster, dispatch in parallel.
ASO optimization pipeline for an app store listing. Use when optimizing an App Store or Play Store listing.
| name | unity-build |
| description | Build Unity APK/IPA on macOS with Unity 6.x — pre-flight checks, version bump, iOS signing params, post-build verification, buildlog, and commit. |
| when_to_use | Use when building a Unity game, cutting a release, or producing a test build. Triggers: "build the game", "Unity build", "ship Unity build", "make a release", "test build", "/unity-build". |
| user-invocable | true |
| disable-model-invocation | true |
| allowed-tools | Bash Read Edit Write Glob Grep Agent |
| paths | ["**/Assets/**","**/ProjectSettings/ProjectVersion.txt","**/Packages/manifest.json"] |
Interactive Unity build pipeline: gather params, pre-flight, invoke Unity in
batch mode, archive + export for iOS, verify artifacts, log, commit. Companion
to /flutter-build — same UX + buildlog format.
Threading rule. Run Unity + xcodebuild on the skill's main thread; sub-agents only scaffold and draft the buildlog. Spawn those helpers in the foreground (
run_in_background: false, the default) so they return and terminate — never as background teammates. If you ever do background a helper, you MUSTshutdown_requestit and awaitshutdown_responseonce its result is consumed, or it parks as a zombie idle process. (Unlike/flutter-build, this skill has no parallel background builders — Unity can't build two targets on one project at once.)
Run in parallel:
ProjectSettings/ProjectSettings.asset — extract:
productName: → {appname} (used verbatim for artifact filenames — no transformation)bundleVersion: → {current_version}AndroidBundleVersionCode: → {current_android_build} (int)buildNumber: nested map → iPhone: → {current_ios_build} (string; treat as int)appleDeveloperTeamID: → {team_id} (may be empty; required for iOS)ProjectSettings/ProjectVersion.txt — extract m_EditorVersion: → {editor_version}/Applications/Unity/Hub/Editor/{editor_version}/Unity.app/Contents/MacOS/Unityxcodebuild -version | head -1 | awk '{print $2}' | cut -d. -f1 → {xcode_major}xcodebuild is not installed, {xcode_major} is empty — iOS unavailableAssets/Editor/BuildScript.cs exists (agent will scaffold if missing)git log --oneline -5 — for context display{skill_start_epoch} via date +%s — used for post-build mtime verificationDerive:
next_android_build = current_android_build + 1next_ios_build = current_ios_build + 1{xcode_major} >= 15 → release-testing (renamed from ad-hoc in Xcode 15+){xcode_major} <= 14 → ad-hocUnity uses separate per-platform build numbers (Android int, iOS string). The skill keeps them in lockstep by default — user can override.
The nextc fastlane setup (generated by setup-ios-signing.sh) supports Unity as a sign-only
path: fastlane lives at the project root (fastlane/Fastfile, Gemfile at root — NOT ios/)
and its lanes sign + export the Builds/iOS/Unity-iPhone.xcodeproj Unity regenerates each build.
test -f fastlane/Fastfile && echo "fastlane" || echo "no-fastlane"
ios_fastlane = true when fastlane/Fastfile exists at the project root, else false. iOS only
(Android always uses the Unity/Gradle path).true: the fastlane lane replaces the xcodebuild archive/exportArchive/ExportOptions.plist
steps (Step 6) — Unity still generates the Xcode project first, then fastlane signs (match) and runs
gym. See references/fastlane-signing.md. When false: the xcodebuild path is unchanged.Show current state + build configuration prompt:
App name : {appname}
Version : {current_version}
Android build #: {current_android_build}
iOS build # : {current_ios_build}
Unity editor : {editor_version}
Xcode : {xcode_major} (iOS only)
Team ID : {team_id or "<not set>"} (iOS only)
Recent commits :
{last 5 commits}
Build configuration:
1. Platform: android / ios / both (default: both)
2. Mode: release / development (default: release)
3. Version: {current_version} (press enter to keep)
4. Android build #: {next_android_build} (press enter to auto-increment)
5. iOS build #: {next_ios_build} (press enter to auto-increment)
If iOS is in platforms, also:
6. iOS export method: {default_method} | app-store-connect | debugging | enterprise
(default: {default_method})
7. Strip Swift symbols: yes / no (default: yes)
8. Compile bitcode: yes / no (default: no)
Please provide choices (e.g., "android, release" or press enter for defaults).
Parse response — use defaults for anything unspecified.
ios_fastlane = true AND iOS is being built)When fastlane was detected and iOS is in the platform selection, the fastlane lane controls
signing + export, so questions 6–8 above do not apply (the lane and match own the export
method and signing; no ExportOptions.plist). Ask instead:
This project has fastlane. How should the iOS build be signed + exported?
1. ad-hoc — signed Ad Hoc IPA, installable on registered devices (default)
2. app-store — signed App Store IPA (no upload)
3. testflight — build the App Store IPA AND upload to TestFlight
Record as ios_lane ∈ {ad-hoc, app-store, testflight}; default ad-hoc. The lane always
builds Release internally, so the release/development "Mode" applies to Android only on the
fastlane path. When ios_fastlane = false, skip this question — iOS uses questions 6–8 (xcodebuild).
Show summary (only include iOS rows if iOS is in platforms):
Build plan:
App name : {appname}
Platforms : {platforms}
Mode : {mode}
Version : {version}
Android # : {android_build}
iOS # : {ios_build}
Unity : {editor_version}
Xcode : {xcode_major}
Team ID : {team_id}
iOS method : {fastlane ({ios_lane}) | xcodebuild} ← only show when iOS is being built
Export : {export_method} ← xcodebuild path only
Swift strip : {strip_swift_symbols} ← xcodebuild path only
Bitcode : {compile_bitcode} ← xcodebuild path only
- `ios_fastlane = true` → `iOS method` = `fastlane ({ios_lane})`, and the Export / Swift strip /
Bitcode rows are omitted (lane + match own signing/export). Otherwise `xcodebuild` + those rows.
Note: Unity may re-serialize scene/meta files when opening. Only
ProjectSettings.asset, BuildScript.cs (+.meta), and docs/buildlog.md are
committed; other changes are reverted.
Proceed?
Wait for confirmation. If user says no, loop to Step 2.
ios_fastlane = false, and {team_id} is empty, STOP: "iOS builds require a Team
ID. Set appleDeveloperTeamID: XXXXXXXXXX (the 10-char identifier from your Apple
developer account) in ProjectSettings/ProjectSettings.asset first." On the fastlane
path this guard is relaxed — the team comes from fastlane/Appfile (team_id) and
match handles signing, so an empty appleDeveloperTeamID is fine.{xcode_major} is
empty, STOP: "iOS builds require Xcode. Install Xcode from the App Store
and re-run xcode-select --install." (Applies to both paths — the fastlane
lane runs gym, which drives xcodebuild internally.)ios_fastlane = true, run the signing preflight from references/fastlane-signing.md:
cfg="${IOS_SIGNING_CONFIG:-$HOME/.fastlane-nextc/config/teams.json}"; if it does not exist,
STOP with the setup-ios-signing.sh <team> <bundle-id> instructions.Run BEFORE any build work — each failure aborts:
No Unity Editor already running. Run:
pgrep -fl 'Unity\.app/Contents/MacOS/Unity' || true
If any PID is returned, abort: "The Unity Editor appears to be open on this system. Batch mode will connect to the running editor's license client and exit 0 without building (silent false success). Close the editor and re-run."
No stale lockfile. If Temp/UnityLockfile exists, abort: "Lockfile at
Temp/UnityLockfile — a prior editor session didn't close cleanly. Close
the editor (or rm Temp/UnityLockfile) before re-running."
Git status. Run git status --porcelain. If any uncommitted changes
exist, show the list and ask: "You have uncommitted changes. Commit them
first, or proceed (anything Unity re-serializes will be reset, but your
pre-existing changes remain)?" Do NOT proceed without confirmation.
Scaffold BuildScript.cs + .meta if missing. If
Assets/Editor/BuildScript.cs does not exist, spawn the unity-builder agent
to generate both the .cs and a matching .meta (deterministic GUID). Stage
both for the Phase 10 commit.
Agent(
subagent_type: "unity-builder",
model: "haiku",
description: "Scaffold BuildScript.cs",
prompt: """
MODE: scaffold
Project root: {project_root}
Write Assets/Editor/BuildScript.cs and Assets/Editor/BuildScript.cs.meta
using your scaffold templates. Do not run any builds. Report the file
paths you wrote.
"""
)
Secrets-in-bundle guard (SECURITY). Assets/ (esp. Resources/, StreamingAssets/) packs verbatim into the APK/IPA and is trivially extractable, so scan for secret-bearing files (.env*, secrets.json, *.local.*, service-account*.json, *.pem, *.p12, *.keystore, *.jks — the full runnable find Assets ... command lives in unity-builder Phase F1) and STOP if any match: list the paths, explain they would ship in the binary, and have the user move the secret out of Assets/ before building.
The "What's new" content comes from the git commit range, not from artifacts, so draft and approve it now — before the version bump and the build. A cancel at the review gate then aborts before anything is modified on disk (no slow Unity build wasted), and the approved text is available to feed the iOS testflight lane as FL_CHANGELOG — the upload happens during Step 6, so the notes must exist before it.
Resolve the last tag, then delegate the draft + review to the unity-builder in whats-new mode (foreground — the agent owns date/tag sanity checks, full commit range, --stat reading, vague-subject diff reading, and the Approve / Edit / Cancel gate):
last_tag=$(git describe --tags --abbrev=0 --match 'build/*' 2>/dev/null || echo "")
Agent(
subagent_type: "nextc-unity:unity-builder",
model: "haiku",
prompt: """
Mode: whats-new
Project root: {project_root}
Last build tag: {last_tag or empty}
Version: {version}
Android build: {android_build} ← include when android in platforms
iOS build: {ios_build} ← include when ios in platforms
Platforms: {android|ios|both}
Status: pending
"""
)
The agent returns either:
STATUS: APPROVED with the approved content between ===WHATSNEW_START=== / ===WHATSNEW_END=== delimiters — hold this as approved_whatsnew. Do NOT write it yet; Step 9 assembles and writes the full entry after the build. For the iOS testflight lane, pass this text as FL_CHANGELOG in Step 6 (see references/fastlane-signing.md).STATUS: CANCELLED — STOP before the version bump. Do NOT bump, build, commit, or tag. Nothing has changed on disk. Report the cancellation; the user can re-run after fixing their concern.Edit ProjectSettings/ProjectSettings.asset in-place via the Edit tool
(never sed — it's structured YAML):
bundleVersion: <old> → bundleVersion: {version}AndroidBundleVersionCode: <old> → AndroidBundleVersionCode: {android_build}
(only if Android is in platforms)buildNumber: block: iPhone: <old> → iPhone: "{ios_build}"
(only if iOS is in platforms; preserve existing quoting)Unity batch mode cannot run concurrently on the same project — both Android
and iOS lock Library/ + Temp/UnityLockfile. The iOS export step (xcodebuild
OR the fastlane lane) does NOT touch Library/ and may parallelize with Unity
Android. For both: Unity iOS (exclusive) → parallel { Unity Android, iOS export
on the generated project } → Phase 7. Single-platform: just the one Unity run,
then the iOS export. Run mkdir -p Builds/logs first.
iOS export branch: when ios_fastlane = true, the iOS export is the fastlane
lane (see "iOS: fastlane lane" below) and the ExportOptions.plist + xcodebuild archive + xcodebuild exportArchive steps are skipped. When false, those
xcodebuild steps run as before. Either way the fastlane lane and xcodebuild run on
the skill's main thread (per the threading rule) — never in a sub-agent.
"/Applications/Unity/Hub/Editor/{editor_version}/Unity.app/Contents/MacOS/Unity" \
-batchmode -quit -nographics \
-projectPath "{project_root}" \
-buildTarget Android \
-executeMethod BuildScript.BuildAndroid \
-logFile "{project_root}/Builds/logs/android.log" \
-appname "{appname}" \
-buildVersion "{version}" \
-buildNumber "{android_build}" \
{developmentBuildFlag}
{developmentBuildFlag} is -developmentBuild in development mode, empty in release.
The scaffolded BuildScript.cs reads the -appname / -buildVersion / -buildNumber custom args and writes
Builds/Android/{appname}_{version}_{android_build}.apk directly (correct name, no
rename needed) — Step 8 then moves it up to Builds/ root, the standard location.
"/Applications/Unity/Hub/Editor/{editor_version}/Unity.app/Contents/MacOS/Unity" \
-batchmode -quit -nographics \
-projectPath "{project_root}" \
-buildTarget iOS \
-executeMethod BuildScript.BuildIOS \
-logFile "{project_root}/Builds/logs/ios-unity.log" \
{developmentBuildFlag}
Expected output: Builds/iOS/Unity-iPhone.xcodeproj (plus the rest of the Xcode
project tree). This is required for both iOS paths — the fastlane lane's
require_export! also expects this project to exist.
The per-method iOS export command blocks + failure handling live in
references/ios-export.md. Pick the branch by ios_fastlane: true → the fastlane
match+gym lane from the project root (signing/{env_exports} in
references/fastlane-signing.md), skipping xcodebuild; false → the xcodebuild
ExportOptions.plist → archive → exportArchive path.
Unity CLI exit 0 is necessary but not sufficient — a no-op run (editor already open, sandbox-blocked writes) also exits 0. For EACH platform built, assert all of the following:
[ -f "$ARTIFACT" ][ "$(stat -f %m "$ARTIFACT")" -gt "{skill_start_epoch}" ][ "$(stat -f %z "$ARTIFACT")" -gt 10485760 ] (> 10 MiB —
prevents passing on 0-byte placeholders)Where $ARTIFACT is:
Builds/Android/{appname}_{version}_{android_build}.apkBuilds/iOS/ipa/<scheme>.ipa (pre-rename)gym printed — default project-root Unity-iPhone.ipa (pre-rename).
For the testflight lane the build is also uploaded; still verify the local IPA if one was produced.If ANY check fails, treat as build failure even if exit code was 0. Surface the log tail and the check that failed.
Builds/ root)After Step 7 verification, move each final artifact to the root of Builds/ (the standard
drop location) with mv (never cp) — one canonical file per platform:
# Android — BuildScript.cs already named it; move up from Builds/Android/
mv "{project_root}/Builds/Android/{appname}_{version}_{android_build}.apk" \
"{project_root}/Builds/{appname}_{version}_{android_build}.apk"
# iOS — rename + move from wherever it landed (xcodebuild → Builds/iOS/ipa/ ; fastlane → project root)
ipa=$(ls -t "{project_root}/Builds/iOS/ipa/"*.ipa "{project_root}/"*.ipa 2>/dev/null | head -1)
mv "$ipa" "{project_root}/Builds/{appname}_{version}_{ios_build}.ipa"
Both finals end at Builds/{appname}_{version}_{build}.{apk,ipa} (Step 11 reports Builds/).
This supersedes the earlier "rename in place, never move" instruction.
The "What's new" content was already drafted and approved in Step 4b (before the build). Here you only wrap it in the metadata that exists once the build ran (status, artifact sizes) and write it — do NOT re-draft or re-run the review gate.
Stamp the write timestamp: today=$(date +%Y-%m-%d), time=$(date +%H:%M).
On success: assemble the entry and prepend it below the # Build Log header in docs/buildlog.md (create with the header if missing; newest-first):
## Build — {version} (android {android_build}, ios {ios_build}) ({today} {time})
- **Platforms:** {android, ios, or both}
- **Mode:** {release or development}
- **Unity:** {editor_version}
- **Xcode:** {xcode_version} (iOS only)
- **Signing method:** {export_method or `fastlane match ({lane})`} (iOS only)
- **Artifact sizes:** Android {N} MiB, iOS {N} MiB
- **Status:** success
{approved_whatsnew — the block between the Step 4b delimiters, verbatim}
On failure: the pre-approved "What's new" describes changes that did not ship — do NOT write it. Use the same header + metadata with **Status:** failed and replace the "What's new" block with a one-line error summary.
Post-write lint (both cases):
# Build Log header presentgit checkout -- docs/buildlog.md to revert if any fail, then abort)Never delete or modify past entries.
Precondition: the "What's new" was approved in Step 4b and the Step 9 buildlog lint passed. If the user cancelled at the Step 4b review gate, the build never ran; if the Step 9 lint failed, skip Step 10 entirely — no commit, no tag.
Unity may have touched scenes/meta/settings that we don't want in this commit. Reset anything dirty that isn't in the commit whitelist:
git status --porcelain | awk '{print $2}' | while read -r f; do
case "$f" in
ProjectSettings/ProjectSettings.asset) ;;
docs/buildlog.md) ;;
Assets/Editor/BuildScript.cs) ;;
Assets/Editor/BuildScript.cs.meta) ;;
*) git checkout -- "$f" 2>/dev/null || true ;;
esac
done
git add ProjectSettings/ProjectSettings.asset \
docs/buildlog.md \
Assets/Editor/BuildScript.cs \
Assets/Editor/BuildScript.cs.meta
Commit message:
chore: bump version to {version} (android {android_build}, ios {ios_build})
On single-platform builds, drop the unused side: (android {android_build}) or (ios {ios_build}).
git tag build/{version}+{max(android_build, ios_build)}
Do NOT tag partially-failed builds. Do NOT push.
Present a table plus diagnostics:
| Platform | Status | Size | Artifact | Path |
|----------|---------|---------|---------------------------------------------|-------------------|
| Android | success | {N MiB} | {appname}_{version}_{android_build}.apk | Builds/ |
| iOS | success | {N MiB} | {appname}_{version}_{ios_build}.ipa | Builds/ |
Builds/ root in Step 8failed status, one-line error, and the log pathThen append:
Signing : {export_method} (Team {team_id}) ← iOS only, xcodebuild path
Signing : fastlane match ({ios_lane}) ← iOS only, fastlane path; add "→ uploaded to TestFlight" for the testflight lane
Logs : {project_root}/Builds/logs/{android,ios-unity,ios-archive,ios-export}.log ← xcodebuild path; fastlane prints its own log to the console
Phase timings : pre-flight {N}s / Unity iOS {N}s / Unity Android {N}s (parallel w/ xcodebuild) / xcodebuild {N}s / verify {N}s
Committed : {version} (android {android_build}, ios {ios_build})
Tagged : build/{version}+{max_build}
Reminder : `git push && git push --tags` when ready
Omit iOS-specific rows when only Android was built, and vice versa.
If unity-builder is unavailable, inline its content tasks (scaffold templates
for BuildScript.cs/.meta, walk git log directly). Build invocations are
already main-thread.
ProjectSettings.asset and the
scaffolded BuildScript.csProjectSettings.asset with sed — use the Edit toolgit log — always curateread only / licensing mutex / permission denied, it's a sandbox signal.env*, secrets.json, *.local.*, service-account*.json, *.pem, *.p12, *.keystore, *.jks) sit under Assets/ at build time — they get packed into the shipped binary. The Step 4 secrets-in-bundle guard must pass before building~/.fastlane-nextc/ (the teams.json match_password, the .p8 key) — NEVER read those values into the report, log, or commit, and NEVER set MATCH_PASSWORD yourself. They authenticate signing/upload and never enter the app binary — separate from the Assets/ bundle guard abovematch provides the signing identity non-interactively — the "NEVER guess iOS signing identity" rule still holds: if the lane stalls on an interactive Apple prompt, STOP rather than guessingrun_in_background: true with log tailing, or
timeout: 1800000 (30 min) on Bash