Multiplayer Play Mode & instance commands
Commands to drive Multiplayer Play Mode (MPPM) Play Mode Scenarios — the main Editor plus one or more virtual player (clone) Editors — and to discover those instances so you can drive each one.
These are distinct from editor_play / editor_stop / editor_pause
(see Editor lifecycle & observability), which control the
main Editor only and never launch virtual players.
See Connectivity → Multiple instances
for the clone topology (each clone is a separate Unity project under Library/VP/<vpId> with its
own Library, port, and Pipeline server).
Availability
The editor.playmode scenario commands require Unity 6000.3 or newer and the
com.unity.multiplayer.playmode package. Both conditions have to hold:
- On 6000.5 and newer, scenario control runs on the public
Unity.PlayMode.Editor.PlayModeScenarioManagerAPI inUnityEditor.PlayModeModule, a built-in editor module that carries no public types before 6000.5. - On 6000.3 and 6000.4 (MPPM 2.0.x), the same commands, arguments and return shapes are implemented by
reflection over MPPM's internal scenario types. Two differences: a player's
idis its name (6000.3 players have no GUID), andeditor.playmode create_scenarioalways writes toAssets/Settings/PlayMode/, usingpathonly for the file name. - Virtual players come from the MPPM package. Without it there is nothing to orchestrate, so the
commands are compiled out rather than registered and answering
available: false. The package presence is detected by anasmdefversion define (MPPM_AVAILABLE), so removing MPPM removes the commands on the next recompile.
Where either condition fails, these commands are absent from /api/commands and calling one
fails as an unknown command. Probe /api/commands rather than branching on an available field.
The package as a whole still supports 6000.0 — only this command group is gated.
editor.playmode list_instances is available on every supported version and needs no MPPM: it works off the
port descriptor files, so it lists whatever clones happen to be running.
Scenario authoring (editor.playmode create_scenario, editor.playmode add_player,
editor.playmode remove_player) still reflects within that gate: the OrchestratedScenario asset type,
its m_Settings struct, and the instance controller types have no public surface. Those three
report a "not found. Is Multiplayer Play Mode installed?" error if the types are missing at
runtime even though the package is installed — an MPPM version whose internals moved.
Two things to know before you start
Scenario assets must end in .asset. Unity imports a PlayModeScenario only from a .asset
file. Give editor.playmode create_scenario any other extension and Unity writes the YAML to disk but
imports it as a DefaultAsset — the file exists, and every other scenario command reports it
missing. editor.playmode create_scenario rejects a non-.asset path up front, and verifies the asset
imported before reporting success.
There is always an "active scenario", even when there isn't one.
PlayModeScenarioManager.ActiveScenario never returns null: with nothing assigned it hands back a
non-persistent DefaultScenario that runs the main Editor alone, no clones. The commands report
that case as active: null with isDefaultScenario: true, and editor.playmode start_scenario refuses
to run rather than silently entering ordinary single-Editor Play Mode.
Scenario control
editor.playmode list_scenarios
List all Play Mode Scenario assets in the project.
No parameters.
Returns: { count, scenarios: [{ name, path, type }] }
editor.playmode set_active_scenario
Set the active Play Mode Scenario by asset path.
| Parameter | Required | Default | Description |
|---|---|---|---|
path |
yes | – |
Asset path of the scenario, e.g. Assets/Settings/PlayMode/2Players.asset |
Returns: { active: { name, path }, state }
Notes: Only valid while state is Idle — stop a running scenario first with
editor.playmode stop_scenario. There is no get_active counterpart: read the current scenario from
editor.playmode get_scenario_status's active.
editor.playmode start_scenario
Start the active Play Mode Scenario (enters Play Mode with its players).
No parameters.
Returns: { started, active: { name, path }, state, message }
Notes: The scenario manager enters Play Mode itself — do not call editor_play. Player
editors launch asynchronously; poll editor.playmode get_scenario_status or editor.playmode list_instances. Fails
if no scenario asset is active — the implicit DefaultScenario does not count.
editor.playmode stop_scenario
Stop the active / running Play Mode Scenario.
No parameters.
Returns: { stopped, state, note }
Notes: State settles asynchronously (Running → Stopping → Idle); poll
editor.playmode get_scenario_status for the final state.
editor.playmode get_scenario_status
Live state of the active / running Play Mode Scenario and of the Editor instances it runs.
No parameters.
Returns: { state, playMode, active: { name, path } | null, isDefaultScenario, players: [{ id, name, type, state, reachable, vpId, port, projectPath }], playerError, note }
Notes: MainThreadRequired = true. state is the scenario manager's state (Idle / Starting
/ Running / Stopping); playMode is playing / paused / stopped. active is null and
isDefaultScenario is true when no scenario asset is assigned.
players is one entry per player the active scenario declares — never MPPM's fixed four slots,
so a two-player scenario reports two players and no phantom NotLaunched rows. Each entry joins
three sources:
| Field | Source | Meaning |
|---|---|---|
id, name, type |
the scenario asset | identity; type is main / clone / local. id is what editor.playmode remove_player takes. |
state |
MPPM | NotLaunched / Launching / Launched / UnexpectedlyStopped. null if MPPM has no slot under that name. |
reachable, vpId, port, projectPath |
the player's port descriptor | present once that Editor's Pipeline server is up; reachable is false and the rest null until then. |
state and reachable answer different questions and you generally want both. A player mid-launch
reads Launching with reachable: false; one that died reads UnexpectedlyStopped, which a
descriptor alone cannot distinguish from never having started. Once reachable is true, drive
that player directly with unity command --project-path <its projectPath>.
Each player is matched to a descriptor in this order: by vpId when MPPM reports one (a vpId with
no descriptor yet reports nothing rather than matching another player); the main player by role;
otherwise a clone by its name, against clone descriptors only. A local (built) player has no Editor
descriptor, so it never reports reachable. playerError
carries a scenario-settings or MPPM reflection failure; the whole command is compiled out when the
package is absent, so an error here never means "not installed".
For the declared roster on its own — including a scenario that is not the active one — call
editor.playmode list_players.
editor.playmode list_players
List the players a Play Mode Scenario asset declares.
| Parameter | Required | Default | Description |
|---|---|---|---|
path |
no | active scenario | Scenario asset path. |
Returns: { path, count, players: [{ id, name, type, controller, runMode }], playerError, note }
Notes: One entry per declared player, with the id you pass to editor.playmode remove_player and a
type of main / clone / local. players is empty and note explains why when no scenario
asset is active. This is the declared roster and cannot change while a scenario runs — it is not
something to poll. Unlike editor.playmode get_scenario_status it takes a path, so it can
read a scenario that is not the active one; it reports no run state or reachability.
Scenario authoring (Edit Mode)
editor.playmode create_scenario
Create a new orchestrated Play Mode Scenario asset.
| Parameter | Required | Default | Description |
|---|---|---|---|
name |
yes | – |
Scenario name (also the asset file name). |
path |
no | <authoring root>/Settings/PlayMode/<name>.asset |
Asset path for the new scenario. Must be inside the authoring root (Assets/ unless changed in Project Settings) and end in .asset. On 6000.3/6000.4 the folder is always Assets/Settings/PlayMode/. |
Returns: { created, name, path }
Notes: The Main Editor player is added automatically by the scenario itself; only add clones
and local players. A path outside Assets/ or with an extension other than .asset is rejected
before anything is written, and the command re-loads the asset after creating it so a file Unity
declined to import is reported as a failure instead of a success.
editor.playmode add_player
Add a player to a scenario asset.
| Parameter | Required | Default | Description |
|---|---|---|---|
path |
no | active scenario | Scenario asset path. |
type |
yes | – |
Player type: clone (virtual player) or local (built player). |
name |
no | – |
Name for the new player. |
Returns: { added, id, type, path } — path is the scenario that was actually modified.
Notes: The Main Editor is added automatically and cannot be added as a player.
editor.playmode remove_player
Remove a scenario player by its id.
| Parameter | Required | Default | Description |
|---|---|---|---|
id |
yes | – |
Player id to remove: a GUID on 6000.5+, the player's name on 6000.3/6000.4. Read it from editor.playmode list_players. |
path |
no | active scenario | Scenario asset path. |
confirm |
no | false |
Must be true to remove the player; without it the command refuses. |
dry_run |
no | false |
Report the player that would be removed without changing the scenario. |
Returns: { removed, id, path }, or { status: "dry_run", wouldRemove: { id, name, type }, path } on a dry run.
Notes: Destructive: it rewrites the scenario asset and is not undoable via Unity's Undo, so it follows the
confirm/dry_run convention in Safety and mutations.
Reachable instances
editor.playmode list_instances
List running Editor instances reachable over Pipeline (this Editor + its MPPM virtual player clones).
No parameters.
Returns: { count, instances: [{ name, role, port, pid, vpId, projectPath }] }
Notes: MainThreadRequired = false. Only instances whose PID is still alive are listed; the
main Editor sorts first. projectPath is what you pass to unity command --project-path to talk to
that instance — see Driving a clone below.
Driving a clone
Talk to a clone directly. Each one runs its own Pipeline server and publishes its own descriptor, so pointing the CLI at the clone's project directory gives you the whole command surface against it:
unity command --project-path <mainProject>/Library/VP/<vpId> editor_status
unity command --project-path <mainProject>/Library/VP/<vpId> run_tests --mode playmode
Get the path from editor.playmode list_instances (its projectPath) or from unity pipeline list, which
reports clones by player name against the project they are playing.
unity command with no --project-path still resolves to the main Editor: the CLI collapses a
project's own clones into it, so starting a scenario never turns a working unity command into an
ambiguity error.
There is no command that relays through the main Editor, deliberately. A direct connection doesn't
queue behind the main Editor's command gate (or stall when a modal dialog blocks it), it honours
--timeout, it reports the clone's own status rather than nesting one response envelope inside
another, and it reaches endpoints a relay over /api/exec never could — job polling, /api/status,
/api/commands.