Skip to main content
Version: Next

Unity

A Unity project keeps its version in ProjectSettings/ProjectSettings.asset and its dependencies in Packages/manifest.json. dispat reads and writes both. A Unity game releases the same way an npm package does: one command, one version, one tag.

The short version​

dispat.json
{
"scripts": {
"build": "Unity -quit -batchmode -projectPath game -executeMethod Builder.Build",
"publish": "butler push game/Build acme/game:windows --userversion $DISPAT_NEW_VERSION"
},
"packages": {
"game": {
"path": "game",
"flow": {"build": "build", "publish": "publish"},
"autoVersion": {"enabled": true, "manifests": "all"}
}
},
"initials": {"game": "0.1.0"}
}

Use manifests: all to reach ProjectSettings/ProjectSettings.asset. The file sits one folder down from the package rather than directly in it. Commit a feature and run dispat. The settings file, the git tag, and the changelog will all show the same version.

$ git commit -m "feat(game): co-op lobby"
$ dispat
12:50:45 INF ● changed bump=minor package=game version="0.1.0 -> 0.2.0"
12:50:45 INF manifest reconciled manifest=ProjectSettings/ProjectSettings.asset package=game ranges=0 stage=version version=0.2.0 versionWritten=true
12:50:45 INF published package=game tag=game@0.2.0 version=0.2.0

What dispat reads​

ProjectSettings/ProjectSettings.asset​

FieldRead asWritten by
productNamethe package's namenever
bundleVersionthe version--set-version, and auto-versioning
AndroidBundleVersionCodethe build counter--set-build
buildNumber (per platform)the build counter, when there is no Android one--set-build

The file is YAML, but standard libraries cannot parse it. Unity writes its own tag directive and tags the document with a class id.

%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!129 &1
PlayerSettings:
productName: Acme Game
bundleVersion: 1.2.0
buildNumber:
Standalone: 7
iPhone: 7
AndroidBundleVersionCode: 7

A conforming YAML parser refuses !u!129. dispat reads the file by line instead, so it can parse any real Unity project. It only reads settings one level deep, so nested mappings cannot leak a version into the answer.

Packages/manifest.json​

Every entry is a dependency. dispat accepts all three forms Unity uses:

{
"dependencies": {
"com.unity.textmeshpro": "3.0.6",
"com.acme.core": "file:../../packages/core",
"com.acme.tools": "https://github.com/acme/tools.git#v1.2.3"
}
}

dispat keeps all three exactly as written. The file: form tells dispat which folder it points at. This makes an embedded package part of the workspace graph. Release com.acme.core, and the project consuming it releases next.

The manifest declares no name and no version of its own. It lists what the project consumes.

Build numbers for the stores​

Google Play and the App Store order uploads by an integer counter. They ignore the version players see. Run --set-build to write that counter and nothing else:

$ dispat writer --set-build "$GITHUB_RUN_NUMBER" game/ProjectSettings/ProjectSettings.asset

Every counter in the file moves. A project shipping to Steam, the App Store, and Google Play from one settings file has three counters. Stamping only one of them would upload two builds with the wrong store order.

Pass an integer for the counter. The stores require it, so dispat enforces it. Passing a version string fails before the file opens, saving you from an upload error later.

A version write never touches a counter. A build write never touches the version. They move for different reasons, so you use separate commands for each.

Unity packages​

A UPM package is an npm package. It uses the same package.json, the same fields, and the same registry protocol. dispat reads it with its npm reader. A repository of Unity packages needs no special setup.

Set the range policy to exact. UPM resolves an exact version and nothing else. A caret range leaves a project that fails to open:

dispat.json
{
"packages": {
"core": {
"path": "packages/core",
"autoVersion": {"enabled": true, "range": "exact"}
}
}
}

Ranges written into Packages/manifest.json are always pinned. dispat ignores the policy here to keep the project working.

What dispat leaves alone​

ProjectSettings/ProjectVersion.txt pins the editor version. This is a toolchain choice. dispat never reads or writes it.

Packages/packages-lock.json is a lock file. dispat does not write lock files in any ecosystem. Run the tool that owns yours in the syncLock hook.

The folders dispat stays out of​

Library/ is Unity's node_modules. Its PackageCache holds a real package.json for every resolved package, which would flood your workspace with third-party packages if scanned. dispat never descends into Library/, Temp, Logs, UserSettings, MemoryCaptures, or Builds.

Rename any source folder called Library or Builds. dispat will not find manifests inside them.

Where to go next​

Validate every distribution format​

A UPM package, a Unity package export, and a NuGet package can carry the same source but have different publication steps. Keep the native UPM manifest, test each artifact as a consumer would, and reconcile successful destinations before retrying. See integration findings.

Check identity at all three boundaries: the package.json version, the release tag, and the version embedded in each download name or registry record. A project may publish one source version as a Git-installable UPM package, a .unitypackage, and a NuGet package. Success at one destination says nothing about the others, so reconcile each publication before a retry and install each fresh artifact in a clean consumer project. A NuGet integration tool does not necessarily publish itself to NuGet; follow the declared distribution policy.

Android exports can include native libraries from packages, plugins and middleware. Apply the engine-independent Android alignment check to every 64-bit .so in the final artifact, including wrappers. Keep ELF, APK ZIP and device checks separate.