---
page: Getting Started
section: Start Here
source: https://2dog.dev/getting-started.md
description: Run Godot inside .NET applications with desktop, web, and test hosts.
---
# Let's take Godot for a walk!
**2dog** embeds **Godot** inside **.NET** applications. Use it to build games, apps, and tools for **desktop**, **web**, and **mobile**. It makes build automation simpler, has templates that connect to **UI** frameworks and **web servers**, and lets you run automated **tests** in your IDE.

*(illustration by [Diego Rago](https://diegorago.com) - Dogbot and Lucky!)*
## Who's a good robot?
Things you can do with 2dog may be complex, so we try to make it developer friendly.
::: tip From stock Godot to the browser
Install the [.NET 10 SDK](https://dotnet.microsoft.com/download). Then, in the
base directory of your Godot project (for example `~/MyGame`), run:
```bash
dnx 2dog add --web
dotnet publish MyGame.web
```
Your app/game and `index.html` are then in `MyGame.web/AppBundle/` - easy!
Upload that folder to itch.io or to your web
space. To test it locally, serve it with one of these commands:
`dnx dotnet-serve -- -d MyGame.web/AppBundle` or `npx serve MyGame.web/AppBundle`
*(replace `MyGame` with the project name from `project.godot`)*
:::
## Developing
Just use Godot or your favorite IDE. 2dog builds with [MSBuild](https://learn.microsoft.com/en-us/visualstudio/msbuild/walkthrough-using-msbuild), which runs on Windows x64, Linux x64, or macOS ARM64.
2dog makes a backup of your `.sln` file as `.sln.old`, then upgrades it to the newer `.slnx` format. You can open the `.slnx` file in your IDE or editor of choice. Godot understands both solution formats.
The hosts you add appear as projects inside the solution, where you can extend and debug them as normal .NET projects. Use your IDE or the `dotnet` CLI to build, run, and debug them.
## Editing & Importing
Keep editing scenes and scripts in Godot as usual! Continue to use the official
[Godot 4.7.2 .NET editor](https://godotengine.org/download) to edit scenes and develop your game. Use `Project→Export...` to make classic Godot builds with the familiar export templates.
2dog hosts import changed resources automatically, so you do not need `godot --import`. (You can still run it, of course.)
See [Hosts](/hosts/) for more .NET app types, and [Testing with xUnit](/testing/xunit) for tests.
*(it's awesome!)*
## Updating & Fixing
The 2dog packages in your project stay at their pinned versions until you update them. Run the latest tool to update them or to check for problems:
```bash
dnx 2dog update
dnx 2dog doctor
```
## Squirrel ?!
2dog can do a lot more. For example, it can run unit tests and embed your game in other applications and UIs. It is safest to try this in a completely fresh project, and *you know you wanted to start a new game project anyway!*
To create a new project from the command line, with its own `project.godot`, run:
```bash
dnx 2dog new NewGame --generic --xunit
cd NewGame
dotnet run --project NewGame.2dog
dotnet test
godot-mono -e . # or open it in the Godot project picker, etc.
```
---
page: Core Concepts
section: Start Here
source: https://2dog.dev/concepts.md
description: How 2dog inverts Godot ownership: your .NET host owns the process and drives embedded libgodot, with GodotSharp API access, the main loop, resource paths, and the single-instance rule.
---
# Core Concepts
Teaching an old robot new tricks! *(and it won't forget any of the older ones)*
2dog adds small "sidecar" projects called [Hosts](/hosts/) to your solution. These hosts embed Godot, and they let you run your own code before, on top of, or after Godot. To do this, 2dog uses `libgodot`, which became an official part of Godot in version 4.6.

*"It's a companion, not a replacement."*
Your original Godot project stays and works the same. Continue to develop it in the official Godot editor as you like.
## 2dog is Godot, backwards!
However, the sidecar projects let us do things quite differently!
In a traditional Godot application, Godot controls the process lifecycle:
```
Godot Process
→ GodotSharp (.NET)
→ SceneTree → Scripts
```
2dog rolls over and reverses the order:
```
.NET Process
→ twodog.Engine → Godot (as libgodot)
→ GodotSharp
→ SceneTree → Scripts
```
Your .NET process controls startup, frames, and shutdown. Godot becomes a
rendering, physics, and audio library that your application drives.
## `libgodot` ... ?!
2dog uses `libgodot`, a shared-library build of Godot Engine. A .NET process loads the library. The library supports direct
P/Invoke calls to native APIs and keeps full access to the managed GodotSharp bindings. It compiles and runs on most platforms.
#### Wait, so Godot could just *do that all this time?*
Almost, but... yay for free and libre open source!
2dog is technically a 'fork' or a 'distro' of Godot. It makes some limited changes to the engine. For example, the engine can stop and start again more cleanly, and it does not have to be the first or primary loader of .NET assemblies. 2dog provides these engine builds as binaries in `.nuget` packages that are easy to install. The [2dog tool](./dnx-2dog) gets all these packages for you. To see what it gets, check its output or use `--dry-run`.
`2dog.engine` adds the native `libgodot` for your platform in three variants:
`debug`, `release` and `editor`. They match the Debug, Release and Editor .NET
configurations. [Build Variants](./build-configurations) is the complete guide.
## Lookin' sharp... GodotSharp ... !
That's the current official C# API for Godot.
::: info ...but what about ❓
🔜 Godot .NET
Made you look! 😃 Godot's shiny new C# integration is going to come in due time, and it will make many things nicer for game developers. Even some of the moving parts of Godot become simpler, but you will still have a reason to keep 2dog around as a faithful fool tool.
It still gets us all the hosts, application embedding, custom imports & exports, and most of all, solid unit and integration testing in popular frameworks. And oh, that REPL Reddit had been talking about for a week or a half in November '26...
:::
Your C# game needs GodotSharp, and 2dog gives it to you. After the engine starts, you can use the full GodotSharp API:
```csharp
using var engine = new Engine("MyGame", args: args);
GodotInstance godot = engine.Start(); // Borrowed compatibility handle.
// Access the scene tree
SceneTree tree = engine.Tree;
// Load and instantiate scenes
var scene = GD.Load("res://my_scene.tscn");
var instance = scene.Instantiate();
tree.Root.AddChild(instance);
// Use any Godot API
var viewport = tree.Root.GetViewport();
var physics = PhysicsServer3D.Singleton;
```
## Wow, I have a Main Loop now?
Unlike traditional Godot, the host explicitly runs Godot frame by frame in its own main loop. The host has a basic loop that works like stock Godot. You can change this loop separately in each host project.
```csharp
while (engine.Update())
{
// Godot processes physics, rendering, input, and your frame logic here.
if (someCondition)
break; // Exit when you decide
}
```
`Engine` owns the running instance. `Update()` returns `false` when Godot
wants to quit, for example when the window closes. `RequestQuit()` asks Godot to stop.
After teardown, `Completion` completes and `Exited` fires on every platform.
Desktop disposal is synchronous; browser disposal is asynchronous.
---
page: Project Layout
section: Start Here
source: https://2dog.dev/project-layout.md
description: The recommended 2dog layout: the directory that contains project.godot is the .NET solution root, and small host projects for desktop, browser, and tests sit in folders that Godot ignores.
---
# Project Layout
2dog recommends this layout: the directory that contains `project.godot` is
the .NET solution root, and each host is a small nested project.

*"So this is where everything is. Easy!"*
This way you keep your familiar Godot-style project layout while 2dog's hosts sit obediently in ~~their baskets~~ your solution.
## The Layout at a Glance
```text
MyGame/ Godot project and solution root
├── project.godot Godot settings and main-scene selection
├── MyGame.csproj Godot C# game assembly
├── MyGame.slnx One solution for the complete project
├── scenes/ Scenes and resources
├── scripts/ C# game scripts
├── MyGame.2dog/
│ ├── .gdignore
│ ├── MyGame.2dog.csproj
│ └── Program.cs Desktop entry point
├── MyGame.xunit/
│ ├── .gdignore
│ ├── MyGame.xunit.csproj
│ └── BasicTests.cs Headless xUnit tests
└── MyGame.web/
├── .gdignore
├── MyGame.web.csproj
└── wwwroot/ Browser shell and static files
```
Organize scenes and scripts however you prefer. Keep game content beside
`project.godot`. Keep executable hosts in subdirectories that Godot ignores.
## One Game Assembly
`MyGame.csproj` remains the Godot C# project. It owns:
- C# game scripts, generated source, and game-assembly configuration
- The assembly Godot loads for your game
- The `Godot.NET.Sdk` configuration
- Browser bootstrap code shared with the web host
Scenes, resources, and assets remain beside `project.godot`. Hosts reference
the game assembly and content; they do not duplicate either.
## Three Ways to Run It
### 2dog (generic .NET)
`MyGame.2dog` starts the embedded engine, points it at the parent Godot project,
and drives the main loop.
```bash
dotnet run --project MyGame.2dog
```
### Test Host
`MyGame.xunit` starts Godot through an xUnit fixture, normally headless, and
loads the real game assembly and resources.
```bash
dotnet test MyGame.xunit
```
The root `global.json` selects Microsoft.Testing.Platform, so plain
`dotnet test` from the root runs every xUnit, NUnit and GdUnit host.
### Web Host
`MyGame.web` is a .NET WebAssembly host. When you publish it, 2dog builds the managed
application, imports and exports the Godot content, and assembles a static
site.
```bash
dotnet publish MyGame.web
```
## Why the Hosts Are Nested
Each host sits one level below the Godot project, so it can find the project.
`.gdignore` tells the editor to ignore host source files, so it does not treat
them as game scripts.
Every host points its `GodotProjectDir` property at the parent directory:
```xml
..
```
The build embeds the path as assembly metadata. The host reads it at runtime,
and the build uses it to import resources automatically. See [Resource Import](/import-tool).
The game project excludes the host folders from its default compile globs, so
the game and the hosts do not compile each other's source files.
## What the Godot Editor Sees
Open `MyGame/` in the Godot .NET editor as usual, or from a terminal:
::: code-group
```powershell [Windows]
Godot_v4.7.2-stable_mono_win64.exe --path MyGame --editor
```
```bash [macOS]
Godot_mono.app/Contents/MacOS/Godot --path MyGame --editor
```
```bash [Linux]
godot-mono --path MyGame --editor
```
:::
The hosts' `.gdignore` files hide them from the editor, importer, and exporter.
## How You Get This Layout
For an existing project, the tool adds the hosts around your current files:
```bash
cd path/to/MyGame && dnx 2dog add
```
For a fresh project, the same tool creates the game and hosts together:
```bash
dnx 2dog new MyGame
```
See [`2dog add`](/cli/add) or [`2dog new`](/cli/new) to get started,
and the [CLI reference](/dnx-2dog) for all options.
## When to Use Another Layout
An existing .NET application can host a Godot project elsewhere. Use the
standard layout when you can. If the structure of an existing repository or
application gives you a concrete reason, use a different layout.
For one minimal alternative, see the
[Parallel xUnit Collections Demo](https://github.com/outfox/2dog/tree/main/demos/xunit/parallel_collections),
which uses a small Godot project stub beside MSBuild projects with native dependencies.
---
page: What's a Host?
section: Hosts
source: https://2dog.dev/hosts/index.md
description: Choose a .NET application that starts Godot for your game or tests.
---
# Hosts
A host is a .NET application that starts Godot for your game or its tests.
Each host has its own entry point and references your base game project.
You can have several hosts of the same type, each with its own project.
The host is what runs your game...

*(but no worries, you can be totally in charge!)*
## Add a Host
Run this command from your Godot project directory, which contains
`project.godot`:
```bash
dnx 2dog add
```
When the command asks, choose the hosts. You can also use the flags below. For
a new project, use `dnx 2dog new MyGame` instead.
If you add `-y` and no host flags, the command adds the generic .NET and Web
hosts.
See [Project Layout](/project-layout) for files and
[MSBuild Configuration](/configuration) for shared settings.
### Console
| Host | Flag | Purpose |
|----------------------------------|-------------|-------------------------------------------------|
| [2dog (generic .NET)](./generic) | `--generic` | .NET console application (desktop or headless) |
| [REPL](./repl) | `--repl` | Interactive C# inside the running game (opt-in) |
### Desktop
| Host | Flag | Purpose |
|------------------------|--------------|---------------------------------------------|
| [Avalonia](./avalonia) | `--avalonia` | Cross-platform desktop UI |
| [Qt Quick](./qt) | `--qt` | QML UI with Qt Bridge for C# (experimental) |
| [WinForms](./winforms) | `--winforms` | Windows Forms UI |
| [WinUI 3](./winui) | `--winui` | Windows App SDK UI |
### Web
| Host | Flag | Purpose |
|--------------------|------------|-------------------------------------------------------------------|
| [Blazor](./blazor) | `--blazor` | Game inside a Blazor page |
| [Web](./web) | `--web` | WASM / HTML5 browser bundle (e.g. for [itch.io](https://itch.io)) |
| [WebXR](./webxr) | `--webxr` | Browser VR and AR |
### Mobile
| Host | Flag | Purpose |
| --- | --- | --- |
| [Android](./android) | `--android` | Android APK (experimental) |
| [iOS](./ios) | `--ios` | iOS app built on a Mac (experimental) |
| [MAUI](./maui) | `--maui` | Windows, Android and iOS UI with an embedded Godot view |
### Testing
| Host | Flag | Purpose |
|--------------------|------------|------------------------------------------------|
| [GdUnit](./gdunit) | `--gdunit` | GDScript suites through embedded Godot and MTP |
| [NUnit](./nunit) | `--nunit` | NUnit tests that use the Godot engine (opt-in) |
| [xUnit](./xunit) | `--xunit` | xUnit tests that use the Godot engine (opt-in) |
---
page: 2dog (generic .NET)
section: Console Hosts
source: https://2dog.dev/hosts/generic.md
description: Run and publish your Godot game as a .NET console application.
---
# 2dog (generic .NET)
The generic .NET host is a console application that runs Godot on Windows,
Linux, or macOS. Godot opens a window unless you run it headless.
It is truly the Swiss Army knife of hosts: it gives you a blank .NET canvas
to build on.

*It's pawesome! Robotastic!*
Edit `MyGame.2dog/Program.cs` to add your own C# code to an existing Godot
game. You can run, test, and debug the game in your IDE or with the `dotnet`
CLI.
## Use It
Run these commands from your Godot project directory:
```bash
dnx 2dog add --generic
dotnet run --project MyGame.2dog
```
Pass Godot arguments after `--`. For a headless run:
```bash
dotnet run --project MyGame.2dog -- --headless --quit-after 300
```
## Publishing
```bash
dotnet publish MyGame.2dog -c Release
```
The publish folder includes the executable, .NET runtime, engine, and game
pack. Copy the whole folder to the target machine. Add `-r win-x64`,
`-r linux-x64`, or `-r osx-arm64` to select a platform.
Release builds hide the Windows console. See
[Desktop Publishing](/configuration#desktop-publishing) for framework-dependent,
single-file, Native AOT and ReadyToRun builds.
See [Engine](/api/engine) to customize the frame loop and
[Build Variants](/build-configurations) to select an engine build. See
[Generic .NET Configuration](/configuration/generic) for build and publish settings.
---
page: C# REPL
section: Console Hosts
source: https://2dog.dev/hosts/repl.md
description: Explore and change a running Godot game with C# and shell-like commands.
---
# C# REPL
Add an interactive C# prompt to your game from its Godot project directory.

*"Be a nice dogbot. Share that assembly load context!"*
::: danger Don't play too rough! :stop_sign:
Give access only to trusted users. Do not call methods that can break the game,
such as `tree.free()`.
This is an experimental, powerful new way to interact with your Godot project or the editor *while it's running!*
:::
Create a REPL host:
```bash
dnx 2dog add --repl
dotnet run --project MyGame.repl
```
For a new project with only a REPL host, use `dnx 2dog new MyGame --repl`.
## Try It
Explore the live scene tree:
```text
ls
cd scene
ls
here.Name
cd /
```
`ls` lists nodes, `cd` selects one, and `pwd` shows its path. `$Child` refers
to a child of the selected node. The expression has the live C# type of that
child.
Use `engine`, `tree`, `root`, `scene` and `here` to access the running game.
Godot and common .NET namespaces are already imported:
```csharp
var node = new Node { Name = "FromRepl" };
root.AddChild(node);
node.Name
node.QueueFree();
```
| Mode | Input |
|-------------------|---------------------------------------------------------------------|
| `:auto` (default) | C# and shell-like commands |
| `:sh` | Shell-like commands, with C# expressions for properties and methods |
| `:cs` | C# statements, declarations and expressions |
When you switch modes, you keep your variables and the selected node. Type
`:help` to see the commands and keybindings. Type `:exit` to stop the prompt
and the game.
- Tab completes commands, node paths and C# members.
- Shift+Enter adds a line.
- Up and Down browse the history.
- Ctrl+C or `:cancel` cancels the running work and the queued work.
Output scrolls above your input. New submissions wait in a queue while earlier
ones run. Pass `ct` to asynchronous waits so that Ctrl+C can cancel them.
Ctrl+C cannot stop a blocking call unless that call supports cancellation.
Call Godot APIs only on the thread that owns the engine.
## Scratch Worlds
`world` is your session's default scratch world. Create others with `worlds`:
```csharp
worlds.Add("lab", "res://other.tscn");
worlds["lab"].Scene.GetChildren()
worlds.Remove("lab");
```
Each world has its own viewport and 2D/3D physics. All worlds share the
SceneTree and the singletons. To create an empty world, omit the scene path.
## Coding Agents
Start the host so that it listens for commands:
```bash
dotnet run --project MyGame.repl -- --repl-listen mygame
```
Then run commands from another terminal or agent:
```bash
2dog repl exec -s agent --timeout 30 'cd scene'
2dog repl exec -s agent --timeout 30 'here.GetChildren()'
```
The named session keeps variables, node selection and worlds between calls.
Sessions share the live game; use scratch worlds for separate experiments.
Each call returns output and an exit code. See [`2dog repl`](/cli/repl) under
Develop for options, sessions and terminal attachment.
[`2dog skill`](/cli/skill) installs the REPL guide for Claude Code. To write
the guide into `AGENTS.md` instead, add `--codex`. To show the guide at the
prompt, type `:help agents`.
See [REPL configuration](/configuration/repl) for native variants, headless
use, local connections and publishing.
---
page: Avalonia
section: Desktop Hosts
source: https://2dog.dev/hosts/avalonia.md
description: Embed Godot in a cross-platform Avalonia window with controls over the game.
---
# Avalonia
The Avalonia host uses `2dog.avalonia` to show Godot inside a cross-platform
desktop window. Avalonia controls can overlap the game, including translucent
controls.

*(get cozy and embed Godot in a real UI framework)*
## Use It
Run these commands from your Godot project directory:
```bash
dnx 2dog add --avalonia
dotnet run --project MyGame.avalonia
```
## Customize It
A `GodotSession` owns the engine and runs frames on the UI thread.
`GodotControl` displays the viewport and forwards input. UI event handlers can
access game state through `session.Engine.Tree`.
See the [showcase host](https://github.com/outfox/2dog/tree/main/demos/showcase/showcase.avalonia)
for a pause button, time-scale slider, and FPS display.
## Publish It
```bash
dotnet publish MyGame.avalonia -c Release
```
See [Avalonia Configuration](/configuration/avalonia) for Native AOT and other
publish settings.
## Requirements
- Avalonia 12.1 or later on Windows, Linux, or macOS.
- IME composition and captured mouse input are not supported yet.
---
page: Qt Quick
section: Desktop Hosts
source: https://2dog.dev/hosts/qt.md
description: Embed Godot in a Qt Quick (QML) app built with Qt Bridge for C#, with QML items over the game.
---
# Qt Quick
The Qt Quick host uses `2dog.qt` and
[Qt Bridge for C#](https://github.com/qt/qtbridge-csharp) to show Godot inside a
QML scene. QML items placed
over the `GodotView` render on top of the game, including translucent items.

*(QML on top, Godot underneath, C# all the way down)*
::: warning Experimental
Qt Bridge for C# is pre-release software. The Qt Quick host is tested on
Windows; Linux and macOS builds are untested.
:::
## Use It
Qt Bridge compiles native code during the build. Install these tools first:
- [CMake](https://cmake.org/download/)
- [Ninja](https://ninja-build.org/)
- A C++ toolchain. On Windows, use Visual Studio with the
[Desktop development with C++](https://learn.microsoft.com/cpp/build/vscpp-step-0-installation)
workload.
See the [Qt Bridge requirements](https://github.com/qt/qtbridge-csharp) for
details.
Run these commands from your Godot project directory:
```bash
dnx 2dog add --qt
dotnet run --project MyGame.qt
```
The Qt Bridge package for Windows x64 includes Qt. On Linux and macOS,
[install Qt 6](https://doc.qt.io/qt-6/get-and-install-qt.html) first. Then
pass its prefix, which is the directory that contains
`lib/cmake/Qt6/Qt6Config.cmake`:
```bash
dotnet run --project MyGame.qt -p:QtDir=/path/to/qt6
```
## Customize It
A `QtGodotSession` owns the engine. Create it in `Main` before you load QML.
Dispose it after the QML engine exits:
```csharp
using Qt.Quick;
using twodog;
using var session = new QtGodotSession("MyGame", args: args);
Qml.LoadFromRootModule("Main");
Qml.WaitForExit();
```
The `TwoDog` QML module provides `GodotView`, which shows the game and
forwards input:
```qml
import QtQuick
import TwoDog
Window {
width: 1280
height: 720
visible: true
GodotView { anchors.fill: parent; focus: true }
}
```
Qt Bridge runs `Main` on its own thread, while Qt's GUI thread pumps the engine
once per Qt Quick frame. The session's `Started`, `FrameAdvanced`,
`ActiveModeChanged` and `Stopped` events run on the GUI thread. C# methods
that QML calls also run on the GUI thread. Both can use the Godot API
directly, for example through `session.Engine.Tree`. See
[C# and QML](https://github.com/qt/qtbridge-csharp/blob/dev/docs/csharp-and-qml.md)
to learn how to expose C# types to QML.
See the [showcase host](https://github.com/outfox/2dog/tree/main/demos/showcase/showcase.qt)
for a pause button, time-scale slider, and FPS display.
## Rendering and Lifecycle
On Windows, Qt Quick's default Direct3D 11 backend shares a GPU texture with
the engine, with no copy through the CPU. Other platforms and graphics
backends use CPU readback, which is also the automatic fallback. Set
[`PresentationMode`](/configuration/qt#session-properties) to force either.
The engine starts when the first `GodotView` renders, and it stops when the
application quits. When Godot quits, the application also quits. Only one
`GodotView` can show the engine at a time.
While the window is minimized or hidden, the engine continues to run at a
background rate but does not render, like a standalone Godot game. This
happens only while Qt's event loop runs. To pause it instead, set `IsPaused` from a QML handler
for [`Qt.application.state`](https://doc.qt.io/qt-6/qml-qtqml-qt.html#application-prop)
or the window's visibility.
`GodotView` forwards mouse, wheel and keyboard input. Physical keycodes assume
a US keyboard layout. IME composition is not supported. Captured mouse modes
hide the cursor but do not confine it.
## Publish It
Qt Bridge deploys Qt next to the app during the build. `dotnet publish` is not
tested for the Qt Quick host yet. Native AOT is unsupported, because Qt Bridge loads the app
through the .NET host.
See [Qt Quick Configuration](/configuration/qt) for build settings.
## Licensing
Qt Bridge for C# and Qt are available under the
[LGPL-3.0 or a commercial license](https://www.qt.io/licensing/), and Qt
Bridge is pre-release software. `2dog.qt` ships no Qt code; like the rest of
2dog, it is MIT licensed.
An app that ships Qt and Qt Bridge binaries must meet their license
obligations. Under the LGPL, you must:
- Include the license texts and notices.
- Make the corresponding Qt source available.
- Let users replace the LGPL libraries.
Qt Bridge compiles its generated native host from LGPL sources, so keep that
host rebuildable. This is not legal advice. Check the
[Qt Bridge repository](https://github.com/qt/qtbridge-csharp) and Qt's
licensing terms for your case.
## Requirements
- Qt Bridge for C# (`QtGroup.Qt.Bridge.CSharp.`) and the Qt 6 version it
requires.
- CMake, Ninja and a C++ toolchain.
- Windows x64, Linux x64 or macOS arm64.
---
page: WinForms
section: Desktop Hosts
source: https://2dog.dev/hosts/winforms.md
description: Embed Godot in a Windows Forms window.
---
# WinForms
The WinForms host embeds Godot in a Windows Forms window with a Pause/Resume
button. It runs on Windows.

Godot reports `Engine.is_embedded_in_editor()` as `true` for this host. If a
script checks this value, make sure that it also works in this host.
## Use It
Run these commands from your Godot project directory:
```bash
dnx 2dog add --winforms
dotnet run --project MyGame.winforms
```
## Customize It
Edit the generated form to add controls. Its UI thread runs Godot frames, so
event handlers can access the scene tree through `_engine.Tree`.
See the [showcase host](https://github.com/outfox/2dog/tree/main/demos/showcase/showcase.winforms)
for an example.
Controls cannot overlap the game window. Use [Avalonia](./avalonia) or
[Qt Quick](./qt) for overlays or a cross-platform UI.
See [WinForms Configuration](/configuration/winforms) for build and publish settings.
---
page: WinUI 3
section: Desktop Hosts
source: https://2dog.dev/hosts/winui.md
description: Embed Godot in a Windows App SDK window.
---
# WinUI 3
The WinUI 3 host embeds Godot in a Windows App SDK window with a Pause/Resume
button. It builds and runs on Windows.

*(get cozy and embed Godot in a real UI framework)*
Godot reports `Engine.is_embedded_in_editor()` as `true` for this host. If a
script checks this value, make sure that it also works in this host.
## Use It
On Windows, run these commands from your Godot project directory:
```bash
dnx 2dog add --winui
dotnet run --project MyGame.winui
```
## Customize It
Edit the generated host to add controls. Its UI thread runs Godot frames, so
event handlers can access the scene tree through `_engine.Tree`.
See the [showcase host](https://github.com/outfox/2dog/tree/main/demos/showcase/showcase.winui)
for an example.
The app runs without an MSIX package. WinUI controls cannot overlap the game
window. Use [Avalonia](./avalonia) or [Qt Quick](./qt) for overlays or a
cross-platform UI.
## Publish It
```bash
dotnet publish MyGame.winui -c Release
```
See [WinUI 3 Configuration](/configuration/winui) for Native AOT and other
publish settings.
---
page: Blazor
section: Web Hosts
source: https://2dog.dev/hosts/blazor.md
description: Embed Godot in a Blazor WebAssembly page with GodotView.
---
# Blazor
The Blazor host embeds Godot in a Blazor WebAssembly page through `GodotView`.
An ASP.NET Core server serves the client. Razor components can access Godot
objects directly.
## Use It
Run these commands from your Godot project directory. If `wasm-tools` is
missing, accept the offer from `add` to install it:
```bash
dnx 2dog add --blazor
dotnet run --project MyGame.blazor
```
Open the URL that the server prints. To publish the server and the client,
run this command:
```bash
dotnet publish MyGame.blazor -c Release
```
## Multithreading Headers
The generated server sends cross-origin isolation headers so `--web-threads`
clients can use `SharedArrayBuffer` in development and after publishing. For an
existing host, add this code to `MyGame.blazor/Program.cs`. Put it immediately
after `builder.Build()` and before the calls that add debugging, static assets,
and endpoints:
```csharp
app.Use(async (context, next) =>
{
context.Response.Headers["Cross-Origin-Opener-Policy"] = "same-origin";
context.Response.Headers["Cross-Origin-Embedder-Policy"] = "require-corp";
await next();
});
```
This sends `Cross-Origin-Opener-Policy: same-origin` and
`Cross-Origin-Embedder-Policy: require-corp`. Resources from other origins must allow
the load through CORS or a Cross-Origin-Resource-Policy header. Use HTTPS or
localhost, because the browser requires a secure context.
## Customize the Page
Edit `MyGame.blazor/Client/Pages/Home.razor`. Keep WebAssembly rendering and
disable prerendering:
```razor
@page "/"
@rendermode @(new InteractiveWebAssemblyRenderMode(prerender: false))
```
- Use `Started` to access the engine.
- Use `OnFrame` for work in each frame.
- Use child content to show HTML over the game.
Only one engine can run at a time.
Godot and Blazor share one thread. The [Web host's limitations](./web#limitations)
apply to the client project. See [Blazor Configuration](/configuration/blazor)
for client build and pack settings.
---
page: Godot C# Web Export
section: Web Hosts
source: https://2dog.dev/hosts/web.md
description: Web export for Godot C#: publish your game as a static browser site, with WebGL or WebGPU, from the browser-wasm host.
---
# Web
The Web host is a `browser-wasm` host that runs Godot and your C# game in a
browser. It publishes a static site that needs no server-side code.
## Build and Serve Locally
Run these commands from your Godot project directory. If `wasm-tools` is
missing, accept the offer from `add` to install it:
```bash
dnx 2dog add --web
dotnet publish MyGame.web
dnx dotnet-serve -- -d MyGame.web/AppBundle
```
Open the URL that the server prints. Game output appears in the browser
console. To customize the page, edit `MyGame.web/wwwroot/`. Then publish again.
## Publishing
Upload the contents of `MyGame.web/AppBundle/` to a static web server.
Serve it compressed for faster downloads; see
[Precompression](/configuration/web#precompression) for the settings that
match your server. Only
[multithreading](#multithreading) needs cross-origin isolation headers.
See [Web Configuration](/configuration/web) for build settings.
## GDExtensions
Extensions need a single-threaded WebAssembly side module listed under a `web`
key in their `.gdextension` file. With the default `TwoDogExportPack=true`,
`dotnet publish` adds it to `godot.pck`. If you disable the export, add the
extension to the pack that you supply.
Side modules cannot use `EM_ASM` or `EM_JS`.
## Multithreading
::: warning Experimental
.NET 10 marks its multithreaded browser runtime as experimental.
:::
Set `true` in `MyGame.web.csproj` to
link the threaded engine. `Main` and Godot's main loop then run on
a Web Worker. `System.Threading` and Godot's `WorkerThreadPool` run in
parallel. Use `TwoDogWebExportPreset` to select an export preset that has
`variant/thread_support=true`. The threaded engine has no WebXR.
The page must be cross-origin isolated, so serve it with these headers:
```bash
dnx dotnet-serve -- -d MyGame.web/AppBundle -h "Cross-Origin-Opener-Policy: same-origin" -h "Cross-Origin-Embedder-Policy: require-corp"
```
GDExtensions need a `-pthread` side module under a `web.wasm32.threads` key.
Threaded side modules cannot export `thread_local` variables and are never
unloaded.
## Limitations
- Godot and .NET run on one thread unless you enable
[multithreading](#multithreading).
- Rendering uses WebGL 2 and Godot's [Compatibility renderer](https://docs.godotengine.org/en/stable/tutorials/rendering/renderers.html).
- Audio, fullscreen, and XR follow browser permission and user-gesture rules.
For VR or AR, use the [WebXR host](./webxr). To embed the game in a Razor page,
use the [Blazor host](./blazor).
---
page: WebXR
section: Web Hosts
source: https://2dog.dev/hosts/webxr.md
description: Run Godot VR and AR in the browser with WebXR and the WebXR Layers polyfill.
---
# WebXR
The WebXR host is a `browser-wasm` host with Godot's WebXR capabilities enabled
and the WebXR Layers polyfill included. It is single-threaded: `--web-threads`
applies only to Web and Blazor hosts.
See [WebXR Configuration](/configuration/webxr) for build and publish settings.
## Use It
Run these commands from your Godot project directory. If `wasm-tools` is
missing, accept the offer from `add` to install it:
```bash
dnx 2dog add --webxr
dotnet publish MyGame.webxr
```
Serve the site:
```bash
dnx dotnet-serve -- -d MyGame.webxr/AppBundle
```
## Set Up Your Scene
Enable web XR shaders in `project.godot`:
```ini
[xr]
shaders/enabled.web=true
```
Add an `XROrigin3D` with an `XRCamera3D`. Start Godot's `WebXRInterface` when the
user presses a button. When the session starts, enable `Viewport.UseXR`.
See the [showcase scene](https://github.com/outfox/2dog/tree/main/demos/showcase)
for an example.
Use an XR-capable browser and device, or the
[Immersive Web Emulator](https://chromewebstore.google.com/detail/immersive-web-emulator/cgffilbpcibhmcfbgggfhfolhkfbhmik).
If you do not serve the site from `localhost`, use HTTPS. The
[Web host's limitations](./web#limitations) also apply.
---
page: Android
section: Mobile Hosts
source: https://2dog.dev/hosts/android.md
description: Package your Godot C# game as an Android APK with the experimental Android host.
---
# Android
The Android host is a .NET for Android app that packages your game as an APK.
Godot's own Activity owns the screen, render loop, input, and lifecycle. The
host registers the .NET runtime before Godot starts.
::: warning Experimental
The host supports only `android-arm64` and `android-x64`.
:::
## Set It Up
```bash
dnx 2dog add --android
```
This creates `MyGame.android/` next to `project.godot`, with the project file, an
`Application` that calls `AndroidHost.Register`, and a launcher activity. It also
adds an `Android` export preset. Set `ApplicationId` in the project file to your
own package name.
You also need the .NET `android` workload (the tool offers to install it), an
Android SDK, and JDK 17.
`2dog new`, `add`, `update`, and `doctor` look for the SDK in these places:
- `AndroidSdkDirectory`
- [`ANDROID_HOME` and `ANDROID_SDK_ROOT`](https://developer.android.com/tools/variables)
- common install locations
If the SDK is missing, they show a warning. See
[Android SDK setup](https://learn.microsoft.com/en-us/dotnet/android/getting-started/installation/dependencies)
to install the required components.
## Build and Install
```bash
dotnet publish MyGame.android -r android-arm64
adb install MyGame.android/bin/Release/net10.0-android/android-arm64/publish/*-Signed.apk
```
`dotnet publish` builds the game, exports its pack with the Android preset,
and packages a signed APK named after the `ApplicationId`. For an x64
emulator, use `-r android-x64`. Start the emulator with `-gpu swangle` or
`-gpu host`.
See [Android Configuration](/configuration/android) for build settings and
[signing](/configuration/android#signing).
## Host Code
Do not call `Engine.Start()`, because Godot runs the loop. To access the scene
from host code, register a listener with
`TwoDogActivity.addMainLoopStartedListener`. The listener runs on Godot's
thread when the main scene is ready.
## GDExtensions
Extensions need an Android library for each ABI, listed under `android.arm64` and
`android.x86_64` keys in their `.gdextension` file. Publishing packages them into
the APK. Every listed library must exist, because the pack export copies them.
## Trimming and AOT
Like any .NET for Android app, Release builds are trimmed and AOT-compiled.
See [Trimming and Mono AOT](/configuration/android#trimming-and-mono-aot) and
the experimental [Native AOT](/configuration/android#native-aot) option.
## Limitations
- Input, pause/resume, and process restarts are not tested yet.
---
page: iOS
section: Mobile Hosts
source: https://2dog.dev/hosts/ios.md
description: Run your Godot C# game as an iOS app with the experimental iOS host, built on a Mac with Xcode.
---
# iOS
The iOS host is a .NET for iOS app that links the Godot engine statically and
shows Godot's own view controller. The display link of this view controller owns
the frame loop, input and lifecycle. The host registers the .NET runtime before
Godot starts.
::: warning Experimental
The host supports only `ios-arm64` (devices) and `iossimulator-arm64` (the
Simulator on Apple silicon Macs). You must build it on a Mac with Xcode. The
host does not support Intel Mac Simulators or Mac Catalyst. It is not tested
on physical devices yet.
:::
## Set It Up
```bash
dnx 2dog add --ios
```
This creates `MyGame.ios/` next to `project.godot`. The folder contains:
- the project file
- an `AppDelegate`
- a `SceneDelegate` that starts Godot through `GodotHost.Start` when the
window scene connects
- an `Info.plist`
- a launch screen and an app icon
It also adds an `iOS` export preset. Set `ApplicationId` in the project file
to your own bundle identifier.
You also need the .NET `ios` workload and Xcode with the iOS platform
installed. Only a Mac can build iOS apps, because .NET for iOS uses Xcode's
clang and iOS SDK to link and `codesign` to sign. Its Mono AOT compiler also
runs only on macOS. On Windows, use Pair to Mac. Linux cannot build iOS apps.
`2dog doctor` reports the workload and Xcode.
Run workload and build commands inside the generated host directory so its
`global.json` selects the .NET 10 SDK. Install an iOS workload compatible with
your installed Xcode; the workload and Xcode SDK must match.
```bash
cd MyGame.ios
dotnet workload install ios
dnx 2dog doctor
```
Ordinary solution builds skip this host. Build it directly with the commands
below.
## Build and Run
Start an iPhone or iPad in Xcode's Simulator first. Simulator builds do not
need a signing identity. From `MyGame.ios/`, run these commands to build,
install and launch the app:
```bash
dotnet build -c Debug -r iossimulator-arm64
xcrun simctl install booted bin/Debug/net10.0-ios/iossimulator-arm64/MyGame.ios.app
xcrun simctl launch --console booted com.companyname.mygame
```
Use the `ApplicationId` from your project file in the launch command.
On a device, with your signing identity and provisioning profile:
```bash
dotnet publish -r ios-arm64 -c Release -p:CodesignKey="Apple Development" -p:CodesignProvision="MyGame Dev"
```
The build imports the game, exports its pack with the `iOS` preset, bundles it
as `game.pck` and force-loads the engine archive into the app. Debug builds run
the Mono interpreter. Release builds are AOT-compiled.
If you boot the Simulator outside a GUI session (over SSH or on CI), pass
`--audio-driver Dummy` as an engine argument to `GodotHost.Start`. Without a
GUI session, the Simulator has no audio service, and CoreAudio aborts the app
when the engine starts. Devices do not need this argument.
See [iOS Configuration](/configuration/ios) for build settings.
## Try a CI Build
To try the showcase without installing .NET or building the engine, use an
Apple silicon Mac with Xcode and an installed iOS Simulator runtime:
1. Open a successful [CI run](https://github.com/outfox/2dog/actions/workflows/ci.yml)
whose **iOS Smoke** jobs ran.
2. Download the `ios-simulator-showcase-double-Release` artifact from the run's
**Artifacts** section. You must sign in to GitHub to download artifacts.
GitHub keeps artifacts for seven days.
3. Extract the downloaded artifact.
4. Start a Simulator.
5. Run these commands in the extracted directory:
```bash
ditto -x -k simulator-app.zip ios-showcase
xcrun simctl install booted ios-showcase/showcase.ios.app
xcrun simctl launch --console booted dev.twodog.showcase
```
The inner ZIP preserves the app bundle's permissions and layout. These are
Simulator apps; they cannot be installed on an iPhone or iPad. Build and sign
your own device app using the command above.
CI builds, inspects and launches three complementary Simulator apps:
| Artifact | Coverage |
| --- | --- |
| `ios-simulator-smoke-single-Debug` | Basic C# game startup with the interpreter |
| `ios-simulator-showcase-double-Release` | Trimmed, AOT-compiled showcase with double precision and a static GDExtension |
| `ios-simulator-maui-single-Debug` | Godot embedded in a MAUI page |
A job passes only when its C# success marker appears in the app's console.
The matching `ios-simulator-log-...` artifact of each job contains the
Simulator log. These jobs do not test input, audio or background handling on
physical devices.
## Host Code
Do not call `Engine.Start()`: Godot's view controller runs the loop. The hosts
use the UIKit scene lifecycle. They start the engine when the window scene
connects, and they access the scene after the engine starts:
```csharp
// SceneDelegate.WillConnect
Window ??= new UIWindow(windowScene)
{
RootViewController = GodotHost.ViewController
?? GodotHost.Start("MyGame", TwoDogWebBoot.PluginsInitializer()),
};
GodotHost.Started += () => GodotHost.Dispatch(tree => tree.Root.PrintTree());
```
Godot reads the interface orientation from the app delegate's window, so the
scene delegate assigns its window there too.
`GodotHost.Start` registers the runtime with `IosHost.Register`, creates the
engine and returns the view controller to present or embed. `Started` and
`QuitRequested` run on the main thread, which Godot shares with UIKit;
`Dispatch` runs between frames. One engine runs per process.
## Rendering
Devices use Godot's Metal driver; the Simulator has no Metal driver in Godot
and runs the Compatibility renderer on GLES3.
## GDExtensions
iOS links extensions into the app. List a static library under the `ios.arm64`
key of the `.gdextension` file. You can also list an `.xcframework` of static
libraries with device and Simulator slices. `[dependencies]` entries link the
same way. The
build force-loads the slice for the `RuntimeIdentifier` and keeps the entry
symbol, which Godot resolves from the app itself. Every listed file must exist,
because the pack export copies it. Opt out with `TwoDogIosGdExtensions=false`.
## Limitations
- Native AOT (`PublishAot`) is not supported; builds use Mono.
- Physical devices, input and background handling are not tested yet.
To embed the game in a MAUI page instead, use the [MAUI host](./maui).
---
page: MAUI
section: Mobile Hosts
source: https://2dog.dev/hosts/maui.md
description: Embed Godot in a .NET MAUI page on Windows, Android and, experimentally, iOS.
---
# MAUI
The MAUI host embeds your game in a MAUI page, with controls beside or over
the Godot view.
Windows, Android and iOS share the same game project. iOS support is
experimental and requires a Mac with Xcode.
## Use It
On Windows, run these commands from your Godot project directory:
```bash
dotnet workload install maui-windows maui-android
dnx 2dog add --maui
dotnet run --project MyGame.maui
```
To select the Windows target, add `-f net10.0-windows10.0.19041.0`. Without
it, `dotnet run` asks you to select a target.
For Android, install `maui-android` on any supported development OS. Then
follow the [SDK and JDK setup](/hosts/android#set-it-up). To build for an x64
emulator, run this command:
```bash
dotnet build MyGame.maui -f net10.0-android -r android-x64
```
Build the host project directly, because ordinary solution builds skip MAUI
projects.
On a Mac, the host also targets `net10.0-ios` (experimental). Install
`maui-ios`. Then build for the Simulator or a device in the same way as the
[iOS host](/hosts/ios). The app delegate registers the game's plugins
initializer with `IosHost.Register` before the page creates the view. Mac
Catalyst is unsupported.
Start an iPhone or iPad Simulator. Then run these commands from `MyGame.maui/`:
```bash
dotnet workload install maui-ios
dotnet build -f net10.0-ios -r iossimulator-arm64
```
To install and launch the `.app`, follow the
[iOS host guide](/hosts/ios#build-and-run). You can also
[try a CI-built MAUI Simulator app](/hosts/ios#try-a-ci-build).
## Customize It
The generated `MauiProgram` registers `GodotView` with `UseTwoDog()`:
```csharp
using twodog.Maui;
var builder = MauiApp.CreateBuilder();
builder.UseMauiApp().UseTwoDog();
```
Set `Project` to your game's assembly name and add the view to a MAUI layout:
```csharp
var game = new GodotView { Project = "MyGame" };
var pause = new Button { Text = "Pause", IsEnabled = false };
game.Started += (_, _) => pause.IsEnabled = true;
pause.Clicked += (_, _) => game.Dispatch(tree => tree.Paused = true);
```
For XAML, map `dog` to `clr-namespace:twodog.Maui;assembly=twodog.maui` and use
``.
After `Started`, access game state through `Dispatch`. `Dispatch` runs your
code on the engine thread. The `Started`, `QuitRequested` and `Failed` events
run on the MAUI UI thread. On Android, do not call `Engine.Start()`, because
Android starts the engine.
See the [MAUI showcase](https://github.com/outfox/2dog/tree/main/demos/showcase/showcase.maui)
for pause and time-scale controls.
## Rendering and Lifecycle
On Windows, the view shares a GPU texture with the engine. If that is not
possible, it uses CPU readback. Android
renders to Godot's native GPU surface, and iOS hosts Godot's own view
controller as a child of the page. See [view properties](/configuration/maui#view-properties)
to choose the Windows presentation mode.
Only one Godot view can be active in each process. On Windows, the engine
stops when you unload the view. On Android and iOS, the engine stays alive when
you detach the view, so you can attach it again. On Android, the engine stops
when the Activity is destroyed. You cannot restart Godot in the same process
on Android or iOS.
Windows supports mouse, touch and basic keyboard input. IME composition and
captured mouse input are not supported yet. Android uses Godot's native input.
On Android, complex transforms and clipping work only if the native surface
supports them.
See [MAUI Configuration](/configuration/maui) for build and publish settings.
---
page: GdUnit
section: Testing Hosts
source: https://2dog.dev/hosts/gdunit.md
description: Run GDScript GdUnit suites through a .NET test host with embedded Godot.
---
# GdUnit
The GdUnit host is a .NET test executable that runs GDScript suites in a
private copy of your game with embedded libgodot. It supports pure GDScript
games and scenes that contain C# components.

To create the host, run this command from your Godot project directory:
```bash
dnx 2dog add --gdunit
```
You can also select GdUnit in the interactive host picker. The generated host
includes a sample GDScript test and its MTP runner configuration.
See the [GdUnit configuration guide](/configuration/gdunit) for customization.
To run the tests, use this command from your Godot project directory:
```bash
dotnet test MyGame.gdunit
```
The root `global.json` that 2dog creates selects MTP for `dotnet test`. If your own
root `global.json` does not select MTP, run the command from `MyGame.gdunit/`.
Use [GdUnit's official documentation](https://godot-gdunit-labs.github.io/gdUnit4/latest/)
to write tests. [Testing with GdUnit](/testing/gdunit) explains
how 2dog changes the way GdUnit runs and reports tests.
---
page: NUnit
section: Testing Hosts
source: https://2dog.dev/hosts/nunit.md
description: Add an NUnit test project alongside your Godot game.
---
# NUnit
The NUnit host is a .NET test project that runs your game in a real headless
Godot engine on Windows, Linux, and macOS.

Run these commands from your Godot project directory:
```bash
dnx 2dog add --nunit
dotnet test MyGame.nunit
```
Start with `MyGame.nunit/BasicTests.cs`, then follow
[Testing with NUnit](/testing/nunit) to write your own tests.
See [NUnit configuration](/configuration/nunit) for build settings.
::: tip Starting a new game?
Use `dnx 2dog new MyGame --nunit`. Choose `--xunit` instead for an xUnit test host.
:::
---
page: xUnit
section: Testing Hosts
source: https://2dog.dev/hosts/xunit.md
description: Add an xUnit test project alongside your Godot game.
---
# xUnit
The xUnit host is a .NET test project that runs your game in a real Godot
engine. The setup uses a headless fixture; rendering is available too.

::: tip It's what we use!
**⤜outfox⤏** tests primarily on xUnit in production, it's great! :thumbsup:
:::
Run these commands from your Godot project directory:
```bash
dnx 2dog add --xunit
dotnet test MyGame.xunit
```
Start with `MyGame.xunit/BasicTests.cs`, then follow
[Testing with xUnit](/testing/xunit) to write your own tests.
See [xUnit configuration](/configuration/xunit) for build settings.
::: info Existing test projects
Older `.tests` hosts and hosts with custom names still work. Use their
existing paths with `dotnet test`. `2dog add` and `2dog update` do not rename
them.
:::
---
page: API Reference
section: API Reference
source: https://2dog.dev/api-reference.md
description: Overview of the deliberately small 2dog API: twodog.Engine, Godot.GodotInstance, the twodog.Testing fixtures, xUnit collections, and object lifetimes at a glance.
---
# API Reference
The 2dog API is deliberately small. Use `twodog.Engine` to own one Godot
instance, or use the fixture classes when xUnit owns the process.
## `twodog`
| Type | Use it to |
|--------------------------|------------------------------------------------|
| [`Engine`](./api/engine) | Configure, start, run, and stop embedded Godot |
`Engine` is the normal entry point from the `2dog.engine` package.
## `Godot`
| Type | Use it to |
|-----------------------------------------|-----------------------------------------------------------------------|
| [`GodotInstance`](./api/godot-instance) | Access the borrowed compatibility handle returned by `Engine.Start()` |
2dog adds `GodotInstance` to the GodotSharp API. Use the
[Godot class reference](https://docs.godotengine.org/en/latest/classes/) for
nodes, resources, scenes, and other engine types.
## `twodog.Testing`
| Type | Use it to |
|---------------------------------------------------------|---------------------------------------------|
| [`FixtureBase`](./api/fixture-base#fixturebase) | Build a fixture with custom Godot arguments |
| [`Fixture`](./api/fixture-base#fixture) | Run tests with rendering enabled |
| [`HeadlessFixture`](./api/fixture-base#headlessfixture) | Run headless tests, including CI |
These fixtures are in the `2dog.engine` package. They do not depend on a
specific test framework.
## `twodog.Testing.Xunit`
| Type | Use it to |
|---------------------------------------------------------|-----------------------------------------------------|
| [`RenderingCollection`](./api/fixture-base#collections) | Share a rendered fixture across an xUnit collection |
| [`HeadlessCollection`](./api/fixture-base#collections) | Share a headless fixture across an xUnit collection |
The collection definitions are in the `2dog.xunit` package. They compile into
your test assembly, so xUnit can discover them.
## Lifetime at a Glance
```csharp
using var engine = new twodog.Engine("MyGame", args: args);
engine.Start();
while (engine.Update())
{
// One frame has completed.
}
```
`Engine` owns the instance. Use `RequestQuit()` for a graceful shutdown.
After teardown, `Completion` completes and `Exited` fires on every platform.
On desktop, `Dispose()` is synchronous. In a browser, await `DisposeAsync()` or
`Completion`. Start a new `Engine` only after completion. See
[Single Godot Instance](./known-issues/single-instance).
---
page: twodog.Engine
section: API Reference
source: https://2dog.dev/api/engine.md
description: API reference for twodog.Engine, which configures, starts, and owns one embedded Godot instance: constructor, properties, Start, Run, Dispose, and common Godot arguments.
---
# `twodog.Engine`
Configures, starts, and owns one embedded Godot instance.
```csharp
public class Engine : IDisposable, IAsyncDisposable
```
**Package:** `2dog.engine`
**Namespace:** `twodog`
## Constructor
```csharp
public Engine(string project, string? path = null, params string[] args)
```
| Parameter | Description |
|-----------|--------------------------------------------------------------------------------|
| `project` | Label passed as Godot's first argument; this is not a content path |
| `path` | Directory containing `project.godot`; omit it for automatic content resolution |
| `args` | Additional Godot command-line arguments, passed unchanged |
When `path` is omitted or `null`, desktop hosts call
[`ResolveContent()`](#resolvecontent). Browser hosts leave the path unset so
Godot loads the web pack. Pass a path only for a nonstandard content location.
2dog resolves relative paths from the working directory of the process.
## Properties
### `CaptureErrors` and `Errors`
```csharp
public bool CaptureErrors { get; init; }
public GodotErrorLog Errors { get; }
```
Set `CaptureErrors = true` before `Start()` to collect Godot's native errors and
warnings in `Errors` (`twodog.GodotErrorLog`). It defaults to `false`.
Capture does not work in a browser. If you enable it there, `Start()` throws
`PlatformNotSupportedException`. The [testing fixtures](./fixture-base#errors)
enable capture automatically and expose the same log.
### `Tree`
```csharp
public SceneTree Tree { get; }
```
Returns the active scene tree. Access it only after `Start()` succeeds.
### `NativePath`
```csharp
public string? NativePath { get; init; }
```
Loads an exact desktop `libgodot` file instead of selecting a packaged variant.
Leave this unset in normal hosts. It is not supported in the browser.
### `ProjectAssemblyDir`
```csharp
public string? ProjectAssemblyDir { get; init; }
```
Sets the preferred directory for the game's C# assembly. The default is
`AppContext.BaseDirectory`; custom isolated hosts may need a different path.
### `LoadedNativePath`
```csharp
public static string? LoadedNativePath { get; }
```
Returns the full path of the loaded desktop `libgodot`, when known.
### `Completion`
```csharp
public Task Completion { get; }
```
Completes when this `Engine` reaches a terminal state. If `Start()` succeeded,
that means the owned native instance has been destroyed and synchronous `Exited`
handlers have returned. `Engine` does not await asynchronous work that those handlers start.
Shutdown restores the host synchronization context before `Exited` fires. As a
result, later awaits in the host do not depend on Godot's stopped frame loop.
### `Exited`
```csharp
public event Action? Exited;
```
Fires once after a successfully started instance is destroyed. `Completion`
also completes for an `Engine` disposed before it starts, but `Exited` does not fire.
## Threading
Make lifecycle calls and access Godot objects only on the thread that called
`Start()`. Calls from another thread fail, so dispatch them to the host's engine
thread.
## Methods
### `Start`
```csharp
public GodotInstance Start()
```
Starts Godot and the project's `run/main_scene`, then returns a borrowed
`GodotInstance` compatibility handle. `Engine` owns the instance; do not dispose
the handle. If you start another instance in the same assembly load context before
completion, `Start()` throws `InvalidOperationException`.
### `Update`
```csharp
public bool Update()
```
Processes one main-loop frame. Returns `true` while Godot keeps running and
`false` once it quits or requests exit. Dispose the engine after the main loop
stops. `Update()` throws `InvalidOperationException` if you call it recursively
or while `Run()` owns the main loop.
### `Iteration`
```csharp
[Obsolete]
public bool Iteration()
```
Deprecated; use `Update()`. `Iteration()` returns the inverse: `true` when Godot
requests exit. Replace `while (!engine.Iteration())` with `while (engine.Update())`.
### `RequestQuit`
```csharp
public void RequestQuit()
```
Requests a graceful quit. Repeated requests are harmless.
### `Run`
```csharp
public void Run(Action? perFrame = null)
```
Runs the main loop after `Start()`.
- On desktop, it blocks until Godot requests exit. `perFrame` runs after each
completed frame that did not request exit.
- In the browser, it registers the Emscripten main loop and returns immediately.
### `Dispose`
```csharp
public void Dispose()
```
Stops and destroys the owned instance synchronously on desktop. In a browser,
`Dispose()` requests shutdown and returns before teardown; use `DisposeAsync()`
or await `Completion`.
### `DisposeAsync`
```csharp
public ValueTask DisposeAsync()
```
Requests shutdown and waits for teardown. Prefer this in browser hosts.
If you dispose from a game callback, 2dog defers the disposal until the native
frame returns. Do not wait synchronously for `Completion` inside that callback.
### `WebExitRuntimeOnQuit`
```csharp
public static bool WebExitRuntimeOnQuit { get; set; }
```
Defaults to `false` in a browser: quitting destroys Godot and keeps .NET alive
so any host can start another engine. Standalone pages may set it to `true`
to terminate the entire WebAssembly runtime on quit. Blazor keeps it `false`.
Browser restarts require a fresh canvas and host configuration before `Start()`;
`GodotView` handles those browser resources automatically.
### `WebLogFirstChanceExceptions`
```csharp
public static bool WebLogFirstChanceExceptions { get; set; }
```
Defaults to `false`. Set it to `true` before browser startup to print every thrown
managed exception to the browser console as an error. This includes caught
exceptions and exceptions stored in unawaited tasks. Set it back to `false` when
you are done, because this diagnostic mode adds logging overhead. On desktop,
the setter throws `PlatformNotSupportedException`.
In a browser, 2dog logs unhandled managed exceptions and unobserved task
exceptions automatically, from the first use of `Engine` and across restarts.
2dog reports unobserved task exceptions only after a garbage collection.
### `ResolveContent`
```csharp
public static string? ResolveContent()
```
Returns `null` when Godot loads a published pack by itself: a `.pck` named
after the executable next to it, a pack
[embedded in the executable](/configuration#pack-inside-the-app), or the pack
in `Contents/Resources` of a macOS `.app`. Otherwise, returns
[`ResolveProjectDir()`](#resolveprojectdir).
The desktop constructor calls this automatically when `path` is omitted.
### `ResolveProjectDir`
```csharp
public static string ResolveProjectDir()
```
Returns the absolute `GodotProjectDir` embedded in loaded assembly metadata.
It throws `InvalidOperationException` when no loaded assembly provides that
metadata.
### `RegisterWebPluginsInitializer`
```csharp
public static void RegisterWebPluginsInitializer(IntPtr initializer)
```
Registers the game assembly's generated plugin initializer in a browser host.
The web host's `Program.cs` calls this before `Start()` with the pointer from
`TwoDogWebBoot.PluginsInitializer()`, which `2dog.godotsharp` generates into
the game assembly. Desktop calls throw `PlatformNotSupportedException`.
## Example
```csharp
using Godot;
using Engine = twodog.Engine;
internal static class Program
{
[STAThread]
private static void Main(string[] args)
{
using var engine = new Engine("MyGame", args: args);
engine.Start();
while (engine.Update())
{
// One frame has completed.
}
}
}
```
`args` accepts ordinary
[Godot command-line arguments](https://docs.godotengine.org/en/stable/tutorials/editor/command_line_tutorial.html),
such as `--headless`, `--verbose`, `--rendering-driver opengl3` and
`--audio-driver Dummy`. It passes them to the engine unchanged.
See [2dog (generic .NET)](../hosts/generic) to run and publish a desktop host.
---
page: Godot.GodotInstance
section: API Reference
source: https://2dog.dev/api/godot-instance.md
description: API reference for Godot.GodotInstance, the borrowed compatibility handle returned by Engine.Start().
---
# `Godot.GodotInstance`
Borrowed compatibility handle returned by [`twodog.Engine.Start()`](./engine#start).
```csharp
public class GodotInstance : IDisposable
```
**Package:** `2dog.engine`
**Namespace:** `Godot`
This class is one of the few differences from stock Godot, which does not expose it.
Its `Engine` owns the native instance and its lifecycle.
Create this object through `twodog.Engine.Start()` rather than its low-level static
factory methods.
## Compatibility Methods
### `Iteration`
```csharp
public bool Iteration()
```
Processes one main-loop frame. Returns `true` when Godot requests exit. In new host
code, call `Engine.Update()` instead; it returns `false` when Godot quits.
```csharp
while (!instance.Iteration())
{
// One frame has completed.
}
```
### `IsStarted`
```csharp
public bool IsStarted()
```
Returns whether this instance has started.
### `FocusIn` and `FocusOut`
```csharp
public void FocusIn()
public void FocusOut()
```
Notify Godot that the host has gained or lost focus. Ordinary generic hosts let
Godot's display server handle focus and do not call these methods directly.
### `Pause` and `Resume`
```csharp
public void Pause()
public void Resume()
```
Notify Godot that the host application has paused or resumed. These lifecycle
hooks are useful when another application framework owns the outer window.
::: warning Choose one loop owner
Call `Engine.Update()` yourself or call `Engine.Run()`. Do not use both for
the same instance. Do not dispose this borrowed handle. For lifecycle control, use
`Engine.RequestQuit()`, `Completion`, `Exited`, and engine disposal.
:::
---
page: GodotSharp
section: API Reference
source: https://2dog.dev/api/godotsharp.md
description: API stub for GodotSharp, which 2dog exposes.
---
# `using Godot;`
2dog exposes the full GodotSharp API that we've grown to love and expect.
Write C# as you always do, in your game code and in your host code.
Of course, you can also use GDScript, but only in game code.
:::info Official Docs
I'm happy to wag my tail and send you on an adventure to godotengine.org!
https://docs.godotengine.org/en/stable/tutorials/scripting/c_sharp
:::
---
page: Fixtures
section: API Reference
source: https://2dog.dev/api/fixture-base.md
description: API reference for 2dog's test fixtures and xUnit collections: FixtureBase, HeadlessFixture, Fixture, HeadlessCollection and RenderingCollection.
---
# Fixtures
Each fixture starts and owns one Godot engine for the tests that use it.
| Type | Use |
|-------------------------------------------------------------|--------------------------------------------------------------------------------------------|
| [`HeadlessFixture`](#headlessfixture) | Starts Godot with `--headless`; the usual fixture for game logic, scenes, resources and CI |
| [`Fixture`](#fixture) | Starts Godot with rendering, for tests that need a display, rendered frames or windows |
| [`FixtureBase`](#fixturebase) | Base class; derive from it to pass other Godot arguments |
| [`HeadlessCollection`, `RenderingCollection`](#collections) | Non-parallel xUnit collections that share one fixture |
## `FixtureBase` {#fixturebase}
```csharp
public abstract class FixtureBase : IDisposable
```
**Package:** `2dog.engine`
**Namespace:** `twodog.Testing`
The constructor resolves the Godot project directory, preloads game assemblies,
and starts the engine without its configured main scene.
### Constructor
```csharp
protected FixtureBase(params string[] cmdLineArgs)
```
Passes each argument to Godot unchanged. One process supports either headless
or rendering fixtures, not both. The constructor rejects a change from one mode
to the other.
### Properties
| Property | Type | Description |
|-----------------|----------------------------|-------------------------------------------------------------------|
| `Engine` | `twodog.Engine` | Engine owned by the fixture |
| `GodotInstance` | `Godot.GodotInstance` | Borrowed compatibility handle |
| `Tree` | `Godot.SceneTree` | Active scene tree |
| `Errors` | `twodog.GodotErrorLog` | Errors and warnings Godot reported and no test consumed yet |
| `Input` | `twodog.Testing.TestInput` | Input server events, viewport routing and control-position clicks |
### `Input`
Use `Input.Click(control)` to aim at a control through its viewport's input
router. Foreground controls and viewport input
suppression keep their normal behavior. `PushToViewport` routes custom events
locally. `Send` uses the input server and also updates the global polling
state. Call these on the engine's owner thread. See
[Simulating Input](/testing/input) for examples and the distinction between modes.
### `Errors`
The fixture starts its engine with `CaptureErrors` enabled. Every error and warning that Godot reports goes to
`Errors`: `push_error`, failed engine checks, and exceptions that Godot catches in C# callbacks. With
`2dog.xunit` or `2dog.nunit`, a test fails if any errors or warnings remain in `Errors` after it. A test that
expects an error consumes it and asserts its text:
```csharp
godot.Errors.Expect("Health must not be negative");
```
`Drain()` returns and clears the log without asserting anything.
### `Dispose`
```csharp
public void Dispose()
```
Disposes the `Engine` that the fixture owns. Let the test framework call this
at the end of the fixture lifetime.
## `HeadlessFixture` {#headlessfixture}
```csharp
public class HeadlessFixture : FixtureBase
```
Starts Godot with `--headless`. Use it for game logic, scenes, resources and CI.
## `Fixture` {#fixture}
```csharp
public class Fixture : FixtureBase
```
Starts Godot with rendering enabled, for tests that need a display, rendered
frames or window behavior.
## xUnit Collections {#collections}
```csharp
public class HeadlessCollection : ICollectionFixture
public class RenderingCollection : ICollectionFixture
```
**Package:** `2dog.xunit`
**Namespace:** `twodog.Testing.Xunit`
Each collection is non-parallel and shares one fixture across its test classes.
The package compiles the definitions into your test assembly so xUnit can
discover them. Remove the nodes and other state that each test adds, so that
they do not affect the next test.
```csharp
using twodog.Testing;
using twodog.Testing.Xunit;
using Xunit;
[Collection]
public class SceneTests(HeadlessFixture godot)
{
[Fact]
public void SceneTree_IsRunning() => Assert.NotNull(godot.Tree.Root);
}
```
### Custom collection
To pass other Godot arguments, derive from `FixtureBase`. Define a non-parallel
collection for the new fixture, because Godot is not thread-safe.
```csharp
using twodog.Testing;
using Xunit;
public class OpenGl3Fixture()
: FixtureBase("--rendering-driver", "opengl3");
[CollectionDefinition(nameof(OpenGl3Collection), DisableParallelization = true)]
public class OpenGl3Collection : ICollectionFixture;
```
---
page: twodog.fixture.AssemblyPreloader
section: API Reference
source: https://2dog.dev/api/assembly-preloader.md
description: API reference for AssemblyPreloader, which preloads game assemblies into the default AssemblyLoadContext before Godot starts.
---
# `twodog.fixture.AssemblyPreloader`
Preloads game assemblies into the default `AssemblyLoadContext` before Godot
starts.
```csharp
public static class AssemblyPreloader
```
**Package:** `2dog.engine`
**Namespace:** `twodog.fixture`
::: info Advanced API
The supplied fixtures call this automatically. Use it only in a custom host
that references game types directly and does not use `FixtureBase`.
:::
## `PreloadGameAssemblies`
```csharp
public static void PreloadGameAssemblies(string projectPath)
```
Finds compiled game assemblies under the project's
`.godot/mono/temp/bin/{Configuration}` directory and loads them into the default
context. This stops Godot's plugin context from loading the same game type again with a
second type identity.
When the host references the game project, the preloader loads the host's own
copy, so it always matches the host's build configuration. It ignores the
outputs of other configurations and the leftovers of renamed projects. If the
host does not reference the game project, the preloader loads the first of the
`Debug`, `Release` and `Editor` folders that contains assemblies.
| Parameter | Description |
|---------------|--------------------------------------|
| `projectPath` | Directory containing `project.godot` |
Call it before `Engine.Start()`. In the browser, it returns and does nothing.
It writes discovery and load failures to the console and does not throw them.
If one assembly fails to load, it still loads the others.
```csharp
var projectDir = twodog.Engine.ResolveProjectDir();
AssemblyPreloader.PreloadGameAssemblies(projectDir);
using var engine = new twodog.Engine("MyGame", args: ["--headless"]);
engine.Start();
```
---
page: dnx 2dog
section: Develop and Configure
source: https://2dog.dev/dnx-2dog.md
description: Reference for the 2dog command-line tool: installation, commands, host flags, global and output options, exit codes, version pinning.
---
# `dotnet tool execute 2dog`
`2dog` sets up .NET host projects around a Godot project, keeps them healthy,
and updates them.

*"Everything at my pawtips!"*
Your project directory stays the solution root; added hosts live in nested
folders that Godot ignores.
## Installation
```bash
dnx 2dog add # run latest from nuget
dotnet tool install -g 2dog # or install it globally
```
`dnx 2dog` creates files. It also edits `*.csproj`, `project.godot` and, if
necessary, the solution and `Directory.Build.props` in place.
Only two changes rename files. The tool asks you before it makes either change:
- `.sln` → `.slnx` migration (renames the `.sln` to `.sln.old`)
- [`--rename`](/known-issues/spaced-project-names), which renames the .NET project, its csproj and
`assembly_name` to a name without spaces
The tool ships as [`2dog`](https://www.nuget.org/packages/2dog/). The matching
`dotnet new` template ships separately as
[`2dog.templates`](https://www.nuget.org/packages/2dog.templates/).
## Commands
| Command | Use it to |
|--------------------------------|---------------------------------------------------------------------------------------------------------------|
| [`2dog new`](/cli/new) | Create a Godot project with 2dog hosts |
| [`2dog add`](/cli/add) | Add hosts to an existing Godot project |
| [`2dog doctor`](/cli/doctor) | Check the project and this machine, fix what can be fixed, explain build failures |
| [`2dog update`](/cli/update) | Bring the project's 2dog packages to this tool's versions |
| [`2dog pack`](/cli/pack) | List a `.pck`'s contents |
| [`2dog repl`](/cli/repl) | Attach to a running REPL host, or run code in it from scripts and agents (`--session`, `--timeout`, `--pipe`) |
| [`2dog skill`](/cli/skill) | Write the REPL working guide for coding agents (`--claude`, `--codex`) |
| [`2dog version`](/cli/version) | Print the tool and package versions |
| [`2dog help`](/cli/help) | Show the usage, or the help for one verb |
`2dog` alone prints the version info and the usage. On a terminal, `new`, `add`
and `doctor` show prompts when no flag gives the answer. Host flags skip the project
wizard. Before the restore, the tool can still offer to install a missing
workload (`wasm-tools`, `android`) for newly selected hosts. `--yes`, pipes, and
CI skip these offers. To install a workload without a prompt, use
`--install-wasm-tools` or `--install-android-workload`. To update workloads,
use `2dog update --update-workloads`.
## Host flags
`new` and `add` take one flag per host. The folder is optional; a repeated
flag adds a second host of the same kind. Test hosts are opt-in: choose
`--xunit`, `--gdunit` or `--nunit`, or select a framework in the interactive picker.
| Flag | Host |
|----------------------------|---------------------------------------------------------------------------|
| `--generic [folder]` | [2dog (generic .NET)](/hosts/generic) with your own `Main()` |
| `--web [folder]` | [Web host](/hosts/web) |
| `--webxr [folder]` | [WebXR host](/hosts/webxr) |
| `--xunit [folder]` | [xUnit test project](/hosts/xunit) |
| `--repl [folder]` | [C# REPL host](/hosts/repl) |
| `--gdunit [folder]` | [GdUnit GDScript test project](/hosts/gdunit) |
| `--nunit [folder]` | [NUnit test project](/hosts/nunit) |
| `--winforms [folder]` | [WinForms host](/hosts/winforms); Windows-only |
| `--winui [folder]` | [WinUI 3 host](/hosts/winui); builds only on Windows |
| `--avalonia [folder]` | [Avalonia host](/hosts/avalonia) |
| `--qt [folder]` | [Qt Quick host](/hosts/qt); needs CMake, Ninja and a C++ toolchain |
| `--blazor [folder]` | [Blazor Web App host](/hosts/blazor); needs wasm-tools |
| `--maui [folder]` | [MAUI host](/hosts/maui); Windows, Android and iOS, needs MAUI workloads |
| `--android [folder]` | [Android host](/hosts/android); experimental, needs the android workload |
| `--ios [folder]` | [iOS host](/hosts/ios); experimental, needs the ios workload and a Mac with Xcode |
| `--no-generic`, `--no-web` | Exclude a host from the default set (every kind has a `--no-` form) |
All hosts except `--generic` and `--web` are opt-in: an unattended run never
adds them.
## Build options
`new` and `add` show build options after the host picker. These flags set the
same options without a prompt:
| Flag | Effect |
|------------------------------------|-------------------------------------------------------------------------------------------|
| `--double-precision [true\|false]` | Use matching `.double` engine, bindings and native packages across the game and all hosts |
| `--web-threads [true\|false]` | Enable experimental multithreading for Web and Blazor clients |
New projects default to single precision and single-threaded web builds.
If you omit these flags, `add` keeps the project's settings. Pass `false`
to disable a setting. Double precision needs a matching double precision Godot
editor. Threaded browser builds need cross-origin isolation headers:
`Cross-Origin-Opener-Policy: same-origin` and
`Cross-Origin-Embedder-Policy: require-corp`.
See [web multithreading](/hosts/web#multithreading) for serving requirements.
## Global options
These options work with every verb. You can also put them before the verb
(`2dog --json add`).
| Option | Effect |
|--------------|-----------------------------------------------------------------------------------------------------------------|
| `-y, --yes` | Do not prompt; take the flags and defaults (also `--non-interactive`, `--no-input`) |
| `-h, --help` | Show help; after a verb, the help for that verb |
| `--version` | Same as [`2dog version`](/cli/version); see [choosing a tool version](/cli/pinning#run-a-specific-tool-version) |
Write a value after its option, with a space or `=`: `--name Foo`,
`--name=Foo`. `--` ends option parsing. If you mistype an option or a verb,
the tool suggests a correction.
## Output
| Option | Effect |
|-----------------|--------------------------------------------------------------------------------------------------|
| `--json` | One JSON document on stdout, nothing on stderr, also on failure (`"ok": false`); implies `--yes` |
| `-q, --quiet` | Results and problems only: no header, plan, progress or next steps |
| `--plain` | No colour, no cursor movement, ASCII markers (also `TERM=dumb`) |
| `--no-color` | No colour; cursor movement stays (also `NO_COLOR`) |
| `--accessible` | Numbered yes/no questions instead of lists, no spinners (also `TWODOG_ACCESSIBLE=1`) |
| `-v, --verbose` | Extra detail on stderr: subprocess command lines and output, stack traces |
The report goes to stdout. Diagnostics (`error:`, `warning:`, `note:`, `hint:`,
`verbose:`, spinners) go to stderr, so `2dog add --json | jq .` and
`2dog doctor 2>errors.log` both work. In a pipe or a CI environment, the tool
shows no prompts or animations and uses ASCII markers. If it needs an answer,
it uses the default and writes a `note:`. See
[CLI environment variables](/configuration/environment-variables#cli-output-and-automation)
for the CI indicators and colour overrides.
## Exit codes
| Code | Meaning |
|-------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `0` | Success, including `--dry-run`, "Nothing to do", and a declined plan |
| `1` | Usage error: unknown option or verb, missing value, wrong positional count; the error names the verb's `--help` |
| `2` | Tool error: project state, I/O, invalid XML, a failed subprocess, a plan that stopped half-way (the report says which steps finished), an unexpected exception (`--verbose` adds the stack trace) |
| `3` | `doctor`: findings remain after the fixes (errors; warnings too under `--strict`), or the `--build` failed; `repl exec`: a submission failed |
| `124` | `repl exec`: `--timeout` cancelled a submission |
| `130` | Cancelled with Ctrl+C |
## Versions
The [Version guide](/cli/pinning) explains how to run a specific tool release,
print its version, and keep your project's package versions fixed.
---
page: 2dog add
section: Develop and Configure
source: https://2dog.dev/cli/add.md
description: Reference for 2dog add: add .NET hosts to an existing Godot project in place - arguments, host flags, options, examples.
---
# `2dog add`
Adds hosts to an existing Godot project, in place. Run it again to add more
hosts, including a second host of the same kind.
Without host flags it asks which hosts to add. Host flags skip that wizard.
On a terminal, the tool can still offer to install a missing workload
(`wasm-tools`, `android`).
`-y` runs unattended.
```bash
2dog add [path] [hosts] [options]
```
| Argument | Meaning |
|----------|-------------------------------------------------------------------------|
| `path` | Directory containing `project.godot`; defaults to the current directory |
## Hosts
`--maui [folder]` adds a [MAUI host](/hosts/maui) for Windows, Android and iOS.
Install `maui-windows` on Windows, `maui-android` for Android builds and
`maui-ios` on a Mac. The host is excluded from plain solution builds.
`--ios [folder]` adds an experimental [iOS host](/hosts/ios). It builds only on a
Mac with Xcode and the `ios` workload. Plain solution builds do not include it.
Use any [host flag](/dnx-2dog#host-flags). Repeat a flag to add another host of
the same kind. Without host flags, an unattended run adds the generic and Web
hosts, except the ones that you exclude with `--no-`. Add tests
explicitly with `--xunit`, `--gdunit` or `--nunit`. 2dog finds existing hosts
and skips them.
If you add a test host and no root `global.json` exists, 2dog creates one that
selects [Microsoft.Testing.Platform](/testing/#test-runners). 2dog never edits
an existing `global.json`, but it warns you if that file does not select
Microsoft.Testing.Platform.
The new hosts use the versions of the running tool. In the same run, 2dog
raises the game project's `Godot.NET.Sdk` and an existing 2dog version block in
`Directory.Build.props` to these versions, as `2dog update` does. It never
lowers them. If this changes the Godot line of the project, 2dog tells you. In
that case, install the matching editor and open the project once.
When 2dog migrates an existing `.sln` to `.slnx`, it keeps the old file as a
numbered `.sln.old` backup.
## Options
| Option | Effect |
|------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------|
| `-n, --name ` | Override the base name of the setup's files; letters, digits, `.` and `_` stay, `-` becomes `_`, and C# keywords and leading digits get a `_` prefix |
| `--rename ` | Rename a .NET project name that [contains spaces](/known-issues/spaced-project-names), then set up; only before any hosts exist |
| `--dry-run` | Print the plan; change nothing |
| `--force` | Overwrite existing files from the setup; never deletes |
| `--double-precision [true\|false]` | Select matching double precision bindings and natives for the game and all hosts; `false` selects single precision |
| `--web-threads [true\|false]` | Enable experimental multithreading for Web and Blazor clients; the web server must send COOP/COEP headers |
| `--no-restore` | Skip the final `dotnet restore` |
| `--install-wasm-tools` | Install missing `wasm-tools` for browser hosts without an installation prompt |
| `--install-android-workload` | Install the missing `android` workload for Android hosts without an installation prompt |
If you omit the build settings, 2dog keeps the project's settings. See
[build options](/dnx-2dog#build-options) for their requirements.
Plus the [global and output options](/dnx-2dog#global-options).
Workload offers apply only to newly selected hosts. `--no-restore` skips them;
the install flags work in CI too. `--dry-run` never checks or installs workloads.
If the restore fails, the tool returns exit code `2` and keeps the created
files. It explains the errors that it recognizes. Fix the cause. Then run
`dotnet restore` again.
## Examples
Install the [.NET 10 SDK](https://dotnet.microsoft.com/download), then add
and run a generic .NET host in your existing Godot project:
```bash
cd path/to/MyGame
dnx 2dog add --generic
dotnet run --project MyGame.2dog
```
2dog creates a nested host project that runs your game. Keep editing scenes
and scripts in Godot as usual. GDScript projects work too.
Choose hosts interactively, or supply host flags:
```bash
2dog add # interactive, here
2dog add --generic MyGame.editor # a second generic host, named
2dog add --android # an Android host
2dog add --ios # an iOS host (builds on a Mac)
2dog add --xunit # an xUnit test host
2dog add --gdunit # a GDScript test host (MTP)
2dog add --nunit # an NUnit test host
2dog add path/to/project --no-web
2dog add --rename MyGame # fix a spaced .NET name first
```
See [Hosts](/hosts/) for host types and [Project Layout](/project-layout) for
generated files. For a new project, use [`2dog new`](/cli/new).
---
page: 2dog new
section: Develop and Configure
source: https://2dog.dev/cli/new.md
description: Reference for 2dog new: create a Godot project with 2dog hosts - arguments, host flags, options, examples.
---
# `2dog new`
Creates a new Godot project with 2dog hosts. Without host flags it asks which
hosts to create. Host flags skip that wizard. On a terminal,
the tool can still offer to install a missing workload (`wasm-tools`,
`android`). `-y` runs unattended.
```bash
2dog new [Name] [dir] [hosts] [options]
```
| Argument | Meaning |
|----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `Name` | Project name, used for folders, assemblies and namespaces; letters, digits, `.` and `_` stay, `-` becomes `_`, and C# keywords and leading digits get a `_` prefix; the tool tells you when it changes the name |
| `dir` | Directory to create; defaults to the sanitized name |
## Hosts
Use any [host flag](/dnx-2dog#host-flags). Repeat a flag to add another host of
the same kind. Without host flags, an unattended run creates the generic and
Web hosts, except the ones that you exclude with `--no-`. Add tests explicitly with
`--xunit`, `--gdunit` or `--nunit`. With a test host, the root `global.json`
selects Microsoft.Testing.Platform, so `dotnet test` runs every test host.
## Options
| Option | Effect |
|------------------------------------|--------------------------------------------------------------------------------------------------------------------|
| `-n, --name ` | Project name; same as `Name` |
| `-o, --output ` | Directory; same as `dir` |
| `--dry-run` | Print the plan; change nothing |
| `--force` | Overwrite existing files from the setup; never deletes |
| `--double-precision [true\|false]` | Select matching double precision bindings and natives for the game and all hosts; `false` selects single precision |
| `--web-threads [true\|false]` | Enable experimental multithreading for Web and Blazor clients; the web server must send COOP/COEP headers |
| `--no-restore` | Skip the final `dotnet restore` |
| `--install-wasm-tools` | Install missing `wasm-tools` for browser hosts without an installation prompt |
| `--install-android-workload` | Install the missing `android` workload for Android hosts without an installation prompt |
See [build options](/dnx-2dog#build-options) for the build settings'
requirements.
Plus the [global and output options](/dnx-2dog#global-options).
`--no-restore` skips the workload offers; the install flags work in CI too.
`--dry-run` never checks or installs workloads. If the restore fails, the tool
returns exit code `2` and keeps the created files. Fix the reported cause. Then
run `dotnet restore` again.
## Examples
Install the [.NET 10 SDK](https://dotnet.microsoft.com/download), then create
and run a project with a generic .NET host:
```bash
dnx 2dog new MyGame --generic
cd MyGame
dotnet run --project MyGame.2dog
```
Open `project.godot` in the Godot .NET editor to edit scenes and scripts.
See [Project Layout](/project-layout) for the generated files and
[Hosts](/hosts/) for host types.
Choose hosts interactively, or supply host flags:
```bash
2dog new MyGame # interactive host choice
2dog new MyGame --generic --xunit # unattended
2dog new MyGame --gdunit # GDScript tests
2dog new MyGame --web --double-precision --web-threads
2dog new "My Game" -o games/mine --no-web # name adjusted to MyGame
```
For an existing project, use [`2dog add`](/cli/add).
## Use the .NET Template
The same template is available through `dotnet new` in the separate
[`2dog.templates`](https://www.nuget.org/packages/2dog.templates/) package:
```bash
dotnet new install 2dog.templates
dotnet new 2dog -n MyGame --web false
```
If you previously installed the template from `2dog`, run
`dotnet new uninstall 2dog` before you install `2dog.templates`. This command
does not remove the `2dog` tool.
This creates a generic .NET host. Omit `--web false` to include Web;
install `wasm-tools` when you want to build or publish that host.
The template also accepts `--double-precision true` and `--web-threads true`.
Test hosts are opt-in. Pass `--xunit true`, `--gdunit true` or `--nunit true` to
`dotnet new 2dog`, or use `2dog new MyGame --xunit`, `--gdunit` or `--nunit`.
## Debugging the Godot project in VS Code
New projects include `.vscode/launch.json`, `tasks.json`, and a recommendation
for Microsoft's C# extension. To debug the Godot project:
1. Open the project root folder in VS Code.
2. Install the recommended C# extension.
3. In Run and Debug, select **Godot: Play (C#)**.
4. Press F5.
The build task builds only the root Godot project, in the Debug configuration.
The debugger must know where Godot is. Do one of these steps:
- Set the `GODOT4` environment variable to the main **.NET-enabled** Godot
executable. Then restart VS Code.
- Replace `program` in `launch.json` with the absolute path of that executable.
On Windows, use the `mono` executable, not the `_console.exe` wrapper. The
editor path setting in Godot Tools is not enough for the C# debugger.
The root project uses the Godot SDK and builds a library that Godot loads.
To debug it, VS Code must start the Godot executable. The `.2dog` host is a
regular .NET executable and supports the usual .NET launch workflow.
**Godot: Attach (C#)** can attach to an already running .NET Godot game or
editor. Select the process that loads your scripts.
---
page: 2dog doctor
section: Develop and Configure
source: https://2dog.dev/cli/doctor.md
description: Reference for 2dog doctor: check a 2dog project and the machine, apply fixes, explain build failures - options, exit codes, every check id.
---
# `2dog doctor`
Checks the project and this machine without a build, and applies the fixes
that it can. Use `--fix` for safe repairs. Use `--fix-all` or the interactive
checklist for announced fixes.
```bash
2dog doctor [path] [options]
```
| Argument | Meaning |
|----------|-------------------------------------------------------------------------|
| `path` | Directory containing `project.godot`; defaults to the current directory |
## Options
| Option | Effect |
|------------------------------|---------------------------------------------------------------------------------------------|
| `--fix` | Apply the safe fixes, then check again |
| `--fix-all` | Also apply announced fixes, including workloads required by the selected build |
| `--install-wasm-tools` | Install missing `wasm-tools` for browser hosts, then re-check |
| `--install-android-workload` | Install the missing `android` workload for Android hosts, then re-check |
| `--build [target]` | Run `dotnet build` of the solution, or a host folder or project, and explain known failures |
| `-c, --configuration ` | Configuration for variant checks and `--build` (default `Debug`) |
| `--log ` | Explain an existing build, restore or runtime log and run no checks; `-` reads stdin |
| `--ignore ` | Drop a finding by [check id](#checks); repeatable |
| `--strict` | Count warnings as findings for the exit code |
| `--offline` | Skip the nuget.org check for a newer tool |
| `--list-checks` | Print every check and build-log signature |
The [global and output options](/dnx-2dog#global-options) also apply. With
`-v`, doctor also lists the passed checks.
Missing browser/Android tooling is informational until `--build` selects a host
that needs it. Hosts excluded from plain solution builds do not need their workloads.
To install a required workload, select it in the checklist or use `--fix-all`.
This is an announced fix. The install flags explicitly request installation for existing hosts
without a build. `--fix` alone does not install workloads.
Variant checks read literal values and simple `Configuration` conditions;
doctor reports anything more complex as info. To evaluate it, use `--build`.
Solution migration keeps the `.sln` as a numbered `.sln.old` backup.
## Exit codes
| Code | Meaning |
|------|-------------------------------------------------------------------------------------------------|
| `0` | No errors remain (warnings allowed unless `--strict`) |
| `2` | Doctor could not run: no `project.godot`, unreadable files |
| `3` | Errors remain (warnings too under `--strict`), the `--build` failed, or `--log` found a problem |
## Examples
```bash
2dog doctor # check, then ask which fixes to apply
2dog doctor --fix # apply the safe fixes unattended
2dog doctor --build MyGame.web -c Release # build one host and explain failures
2dog doctor --build MyGame.web/MyGame.web.csproj # a project path also works
2dog doctor --log build.log # explain an existing log
dotnet build 2>&1 | 2dog doctor --log - # or a piped one
2dog doctor --json --strict | jq '.doctor.summary' # CI: exit 3 on any warning
```
## Checks
Ids are stable; `--ignore ` drops one.
### Environment
| Id | Passes when |
| --- | --- |
| `env.dotnet-sdk` | a .NET 10 SDK is installed |
| `env.global-json` | the root global.json pin is satisfied by an installed SDK |
| `env.wasm-tools` | wasm-tools is available for the selected browser build; otherwise optional |
| `env.maui-workload` | MAUI workloads are available for selected MAUI builds |
| `env.android-workload` | android is available for the selected Android build; otherwise optional |
| `env.android-sdk` | Android SDK paths and components are available for the selected Android build; otherwise optional |
| `env.ios-workload` | ios is available for the selected iOS build; otherwise optional |
| `env.xcode` | Xcode with the iOS platform is installed on this Mac; elsewhere a note that iOS builds need a Mac |
| `env.qt-toolchain` | CMake, Ninja and a C++ compiler are available for the selected Qt build; otherwise optional |
| `env.host-platform` | this OS and architecture have 2dog native packages |
| `env.godot4` | GODOT4 points at an existing Godot executable for IDE debugging |
| `env.overrides` | GODOTSHARP_DIR and the other layout overrides point at what they claim |
| `env.packages-restored` | the engine, tools and native packages are in the NuGet cache |
### Layout
| Id | Passes when |
|-------------------------------|---------------------------------------------------------------------------|
| `layout.load-problems` | every project file parses |
| `layout.game-csproj` | the game csproj exists at the project root |
| `layout.assembly-name` | project.godot names the assembly and the csproj matches it |
| `layout.multiple-root-csproj` | a single csproj at the root, or assembly_name picks one |
| `layout.spaced-name` | the .NET project name contains no whitespace |
| `layout.root-build-targets` | Directory.Build.targets provides the deep-clean target |
| `layout.root-build-props` | Directory.Build.props holds the shared package versions |
| `layout.root-global-json` | global.json pins a wasm-capable SDK when a browser host exists |
| `layout.test-runner` | test hosts and global.json use Microsoft.Testing.Platform for dotnet test |
### Game csproj
| Id | Passes when |
|------------------------------|----------------------------------------------------------------|
| `game.sdk` | the game csproj uses Godot.NET.Sdk |
| `game.sdk-mismatch` | Godot.NET.Sdk matches the Godot line 2dog.engine was built for |
| `game.target-framework` | the game csproj targets net10.0 |
| `game.properties` | EnableDynamicLoading and AllowUnsafeBlocks are set |
| `game.bindings` | the game csproj references 2dog.godotsharp |
| `game.default-item-excludes` | every host folder is excluded from the game's default globs |
### Hosts
| Id | Passes when |
|-------------------------------|----------------------------------------------------------------------------|
| `host.gdignore` | every host folder carries a .gdignore |
| `host.project-reference` | every ProjectReference points at an existing project |
| `host.godot-project-dir` | GodotProjectDir points at the Godot project |
| `host.variant` | TwoDogVariant / TwoDogWebVariant is supported by the host platform |
| `host.buildtype-deprecated` | the deprecated TwoDogBuildType property is gone |
| `host.publish-aot` | no WinForms, REPL, Qt, browser or Blazor host enables PublishAot |
| `host.publish-singlefile` | no REPL host enables PublishSingleFile |
| `host.webxr-threads` | WebXR hosts do not enable WasmEnableThreads |
| `host.duplicate-analyzers` | hosts referencing the game strip the duplicate Godot analyzers |
| `host.app-manifest` | the app.manifest a host declares exists |
| `host.web-props-shim` | browser hosts chain to the root Directory.Build.props |
| `host.web-global-json` | browser hosts carry their own global.json |
| `host.trimmer-root` | browser hosts root the game assembly for the trimmer |
| `host.blazor-client` | the Blazor client project exists and publishes 2dog.engine |
| `host.blazor-launch-settings` | Blazor hosts carry a Development launchSettings.json |
| `host.windows-only` | Windows-only hosts are noted on other platforms |
| `host.qt-dir` | Qt hosts outside win-x64 point QtDir at a Qt 6 install |
### Solution
| Id | Passes when |
|------------------------|--------------------------------------------------------------------------|
| `sln.exists` | a solution exists at the project root |
| `sln.multiple` | exactly one solution contains the game project |
| `sln.legacy-format` | the solution uses the .slnx format |
| `sln.contains-game` | the solution lists the game project |
| `sln.contains-hosts` | the solution lists every host project |
| `sln.build-exclusions` | browser, Android and WinUI hosts are excluded from plain solution builds |
### Versions
| Id | Passes when |
|-----------------------------|-----------------------------------------------------------------------------------|
| `ver.managed-elsewhere` | doctor notes versions that come from your own properties, but does not check them |
| `ver.props-invalid` | the TwoDog* versions in Directory.Build.props are readable versions |
| `ver.literal-versions` | host csprojs use the shared version properties, not literals |
| `ver.twodog-consistent` | every 2dog package uses the same version |
| `ver.twodog-outdated` | the 2dog packages are at this tool's version |
| `ver.twodog-newer` | the project is not ahead of this tool |
| `ver.natives` | the native packages are exact-pinned on the engine's Godot line |
| `ver.godot-line-consistent` | TwoDogGodotVersion matches the game project's Godot.NET.Sdk |
| `ver.godotsharp-editor` | GodotSharpEditor in the test host matches the Godot.NET.Sdk |
| `ver.companions` | Avalonia and the other companion packages are not mixed |
| `ver.tool-latest` | a newer 2dog tool on nuget.org is mentioned |
### Presets
| Id | Passes when |
|------------------|-----------------------------------------------------------------------------------------------------|
| `preset.file` | export_presets.cfg exists |
| `preset.web` | the 'Web' preset exists when a browser host does |
| `preset.android` | the Android hosts' export preset ('Android' by default) exists, unless they ship a pre-exported pck |
| `preset.ios` | the iOS hosts' export preset ('iOS' by default) exists, unless they ship a pre-exported pck; a warning for MAUI hosts off a Mac |
| `preset.desktop` | the per-OS desktop presets exist |
### Godot settings
| Id | Passes when |
|-----------------------|------------------------------------------------------|
| `godot.features-line` | config/features names the Godot line the SDK targets |
| `godot.xr-shaders` | a WebXR host has xr/shaders/enabled.web set |
| `godot.import-stamp` | the project has been imported at least once |
## Build-log signatures
`--build` and `--log` recognize these failures. For each match, doctor shows a
title, the log line with the error and the command that fixes it. Doctor lists
other errors after the matches.
| Id | Failure |
|----------------------------------|--------------------------------------------------------------------------------------|
| `build.variant-invalid` | TwoDogVariant is not release, debug or editor |
| `build.buildtype-deprecated` | the deprecated TwoDogBuildType property is set |
| `build.publish-aot` | PublishAot does not support the editor variant |
| `build.publish-singlefile` | PublishSingleFile does not support the editor variant |
| `build.godot-sdk-mismatch` | Godot.NET.Sdk and 2dog.engine are on different Godot lines |
| `build.no-import-capability` | no import capability (`2dog..editor` and 2dog.tools packages missing) |
| `build.import-required` | the build requires an import (TwoDogRequireImport=true) and has no import capability |
| `build.desktop-preset-missing` | the desktop export preset is missing from export_presets.cfg |
| `build.web-preset-missing` | the 'Web' export preset is missing from export_presets.cfg |
| `build.no-export-capability` | no export capability (`2dog..editor` and 2dog.tools packages missing) |
| `build.web-payload-missing` | the browser natives (2dog.browser-wasm) are not restored |
| `build.native-missing` | the native libgodot for this platform was not found |
| `build.nu1213` | the '2dog' tool package is referenced as a library |
| `build.package-not-found` | a package version is missing from the feeds |
| `build.version-conflict` | conflicting package versions across the solution |
| `build.msb3277-godotsharp` | two GodotSharp versions meet in one build |
| `build.wasm-tools-missing` | the wasm-tools workload is not installed |
| `build.android-workload-missing` | the android workload is not installed |
| `build.sdk-too-old` | the installed .NET SDK is too old for net10.0 |
| `build.global-json-unresolved` | no installed SDK satisfies global.json |
| `build.godot-sdk-not-found` | the Godot.NET.Sdk version could not be downloaded |
| `build.il1035` | the trimmer cannot see a root assembly |
| `build.export-failed` | the Godot pck export failed |
| `build.godotplugins-missing` | GodotPlugins.dll not found at runtime |
| `build.native-variant-missing` | the native library for this TwoDogVariant is missing at runtime |
| `build.variant-fallback` | the engine fell back to another native variant |
| `build.host-leak` | host sources compile into the game assembly |
---
page: 2dog update
section: Develop and Configure
source: https://2dog.dev/cli/update.md
description: Reference for 2dog update: bring a project's 2dog packages to the running tool's versions - arguments, options, examples.
---
# `2dog update`
Sets the project's 2dog package versions to the versions of the running tool.
If this changes the project files, it then restores the packages. It never
downgrades a package. If the project is in a Git
repository and tracked files have uncommitted changes, the command stops. To
continue anyway, pass `--allow-dirty`.
```bash
2dog update [path] [options]
```
| Argument | Meaning |
|----------|-------------------------------------------------------------------------|
| `path` | Directory containing `project.godot`; defaults to the current directory |
## Options
| Option | Effect |
|------------------------------|---------------------------------------------------------------------------------------------------|
| `--dry-run` | Print the plan; change nothing |
| `--no-restore` | Skip the final `dotnet restore` |
| `--allow-dirty` | Continue even if tracked files have uncommitted changes |
| `--install-wasm-tools` | Install missing `wasm-tools` for browser hosts |
| `--install-android-workload` | Install the missing `android` workload for Android hosts |
| `--update-workloads` | Install missing workloads the hosts need, then run `dotnet workload update` for the project's SDK |
Plus the [global and output options](/dnx-2dog#global-options). There is no
`--to`: pin the tool instead.
Package updates need no workloads or Android SDK. For browser or Android
hosts, the tool can offer to update workloads on a terminal. It uses
[`dotnet workload update`](https://learn.microsoft.com/dotnet/core/tools/dotnet-workload-update),
which updates **all installed workloads** for the SDK. `--no-restore` skips the
offer, and `--dry-run` never checks or changes workloads.
## Examples
```bash
2dog update # here, after committing
2dog update path/to/project --dry-run # show what would change
dnx 2dog@4.7.2.139 update # a specific tool version
```
---
page: 2dog pack
section: Develop and Configure
source: https://2dog.dev/cli/pack.md
description: Reference for 2dog pack list: list an exported .pck's contents by size, without an engine or project.
---
# `2dog pack`
Runs file-format operations on Godot `.pck` bundles. It reads the pack
directly, so it needs no engine and no project.
```bash
2dog pack list
```
`list` prints the pack format, the Godot version and every entry, largest
first; `--json` puts the listing in the report. Use it to see why a pck
is 99 MiB, or to confirm that a [web publish](/hosts/web) contains an asset.
It also reads a pack [embedded in an executable](/configuration#pack-inside-the-app):
pass the executable instead of the pck.
```bash
2dog pack list MyGame.web/AppBundle/godot.pck
```
```text
MyGame.web/AppBundle/godot.pck: pack format v4 (Godot 4.7), 19 file(s), 0.0 MiB content
21,582 .godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex
11,593 icon.svg
7,602 .godot/exported/133200997/export-3070c538c03ee49b7677ff960a3f5195-main.scn
656 project.binary
316 spinning_cube.gdc
...
```
---
page: 2dog repl
section: Develop and Configure
source: https://2dog.dev/cli/repl.md
description: Attach to a running REPL host or execute C# and node commands from scripts and agents.
---
# `2dog repl`
Connect to a running [REPL host](/hosts/repl) from a terminal, script or coding
agent:
```bash
2dog repl # attach this terminal
2dog repl exec [code] # run code and exit
2dog repl sessions # list named sessions
2dog repl close # cancel work and close its worlds
```
## Finding the host
A host that runs in a terminal listens for connections automatically. For
scripts and agents, use `--repl-listen`. Then the host does not stop when stdin
ends:
```bash
dotnet run --project MyGame.repl -- --headless --repl-listen mygame
```
`2dog repl` selects the host for the current project. If there is no such host,
it selects the only running host. If several hosts qualify, choose one with
`--pipe NAME`.
## Running code
Use a named session to keep variables and node selection between calls:
```bash
2dog repl exec -s agent --timeout 30 'cd scene'
2dog repl exec -s agent --timeout 30 '$Player.Position'
```
`$Player` refers to a child of the selected node. Its type is the live C# type
of that child.
Use `ls` to find nodes and `cd /root/Main` to select an absolute path.
`ls -d 1` limits the listing depth.
If you give no code, or give `-`, the command reads the code from stdin:
```bash
2dog repl exec -s agent --timeout 30 - <<'EOF'
var start = $Player.Position;
$Player.Position += Vector3.Up;
$Player.Position - start
EOF
```
Consecutive C# statements compile and run as one submission, with no frames
between them. A line without a trailing semicolon ends the submission and
prints its value. Shell commands and `:` commands run separately. If a submission
fails, the later submissions do not run. `#load "file.csx"` runs a file as one
submission. `:exit` ends the request but does not close the session.
Quote code to protect `$` paths from your shell. If the code starts with `-`,
put it after `--`: `2dog repl exec -- -1 + 2`.
| Option | Effect |
|------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------|
| `-s, --session ` | Keep variables, imports, mode, selected node and worlds until you close the session or the host stops. Without this option, each call gets a new session. |
| `--timeout ` | Cancel after this duration, including queued time; preserve the session. |
| `--strict` | Fail if a submission logs a Godot error or warning. |
| `--pipe ` | Choose a host by its `--repl-listen` name. |
| `--json` | Return a structured report with status, values, errors, logs and node listings. |
## Output and cancellation
Values go to stdout; compile errors, exceptions and Godot errors and warnings
go to stderr. The command returns the output that
the submission writes with `Console.WriteLine` and `GD.Print`. Output that
comes later stays on the host console. JSON reports contain
per-submission `results` and top-level `errors`; listings include a `tree` of
`name`, `type` and `children`. `hidden` gives the number of omitted descendants.
| Exit code | Meaning |
|-----------|--------------------------------------------------------|
| `0` | All submissions succeeded |
| `1` | Invalid command line |
| `2` | Host unavailable, connection broken or request refused |
| `3` | Submission failed; later submissions did not run |
| `124` | Timeout expired or the host did not answer |
| `130` | Cancelled by Ctrl+C or session closure |
All sessions share the engine thread; synchronous work pauses the game.
Cancellation is cooperative. Pass `ct` to waits such as `Task.Delay`. If you
do not, they continue to run, and the session stays busy after a timeout
returns 124.
## Sessions
`2dog repl sessions` shows each named session's mode, selected node, worlds
and activity. While the engine thread is busy, node and world details are
unavailable. `2dog repl close agent` cancels that session, closes its worlds
and discards its variables. Work that ignores cancellation finishes before
its worlds close.
## Attaching a terminal
`2dog repl` opens the host's interactive client with completion, highlighting
and multiline editing. Each attached terminal has its own session, which
ends when it disconnects. See [local connections](/configuration/repl#local-connections)
for host options.
---
page: 2dog skill
section: Develop and Configure
source: https://2dog.dev/cli/skill.md
description: Reference for 2dog skill: write the REPL working guide where Claude Code, Codex and other coding agents read project instructions.
---
# `2dog skill`
Writes a guide for coding agents to the place where they look for it. The guide
explains how to work in a live game through [`2dog repl exec`](/cli/repl). It
covers the globals, `$` paths, timing on the shared engine thread, handlers that
run every frame, and how several agents share one game.
```bash
2dog skill # .claude/skills/2dog-repl/SKILL.md for Claude Code
2dog skill --codex # a marked section of AGENTS.md
2dog skill --claude --codex # both
2dog skill path/to/MyGame --dry-run
```
| Option | Effect |
|-------------|-----------------------------------------------------------------------------------------------------------------------------------------------------|
| `--claude` | Write `.claude/skills/2dog-repl/SKILL.md`, which Claude Code loads when a task needs it. This is the default. |
| `--codex` | Write the guide between `` markers in `AGENTS.md`, which Codex and other agents read. Later runs replace only that section. |
| `--dry-run` | Show what would change without writing. |
To refresh the guide after you update the tool, run it again. If the content of
a file does not change, the command does not write that file. `2dog new --repl` and `2dog add --repl` write the Claude skill along
with the host. In a running REPL, `:help agents` prints the same text.
---
page: Pinning
section: Develop and Configure
source: https://2dog.dev/cli/pinning.md
description: Choose which version of the 2dog tool to run and keep your project's package versions fixed.
---
# Pinning a specific Version
2dog already pins your project to a specific version in
`Directory.Build.props`. You can edit this file manually.
You can also choose the version of the **2dog tool** that you run. Separately,
you can choose the version of the **2dog packages** that the tool uses.
## Run a specific tool version
Add `@` and the version number after `2dog`:
```bash
dnx 2dog@4.7.2.139 doctor
```
Replace the number with the version that you want to use. This example runs
`doctor` from that release of the tool. If that release is not on your computer,
`dnx` downloads it.
Use the same syntax with other commands. For example, this command previews a
project update with the selected tool version:
```bash
dnx 2dog@4.7.2.139 update --dry-run
```
Remove `--dry-run` to apply the update. The update command changes the project's
package versions to the tool's versions; it does not downgrade newer versions.
See [update](/cli/update) for details.
## Print the version
Use the `version` command to show the tool's version and its package versions:
```bash
dnx 2dog@4.7.2.139 version
```
`--version` belongs to `dnx` and **chooses** the tool version.
`version` is a 2dog command and **prints** version information.
See the [version command reference](/cli/version) for the output format.
## Keep the project's package versions
Projects created by 2dog store their package versions in `Directory.Build.props`.
For example:
```xml
4.7.2.139
```
This is a shortened example; keep the other version properties in your file.
Projects use `$(TwoDogVersion)` in their package references.
Only `2dog update` changes this value. This value does not select the tool that
`dnx` runs. To select the tool, use `@version`.
---
page: 2dog version
section: Develop and Configure
source: https://2dog.dev/cli/version.md
description: Reference for 2dog version: print the tool version and every package a setup references, checked against nuget.org.
---
# `2dog version`
Prints the tool version and every package that a setup references. It checks
each one against nuget.org. If it cannot reach nuget.org within 2.5 s, the
output shows no mark. `--version` is the same, except
[under dnx](/cli/pinning#print-the-version).
```bash
2dog version
```
```text
2dog 4.7.2.139 https://2dog.dev
tool + packages 4.7.2.139 ✅ 2dog, 2dog.engine, 2dog.avalonia, 2dog.blazor, 2dog.qt, 2dog.xunit, 2dog.nunit, 2dog.gdunit, 2dog.repl
native binaries 4.7.2.52 ✅ 2dog.win-x64, 2dog.linux-x64, 2dog.osx-arm64, 2dog.browser-wasm, 2dog.android, 2dog.android-arm64, 2dog.android-x64, 2dog.tools
GodotSharp 4.7.2.139 ✅ 2dog.godotsharp, 2dog.godotsharp.editor
```
| Mark | Meaning |
|---------------------------|------------------------------------|
| ✅ (`ok` when redirected) | The latest stable release |
| 🔄 (`new`) | A newer stable release exists |
| none | The tool could not reach nuget.org |
---
page: 2dog help
section: Develop and Configure
source: https://2dog.dev/cli/help.md
description: Reference for 2dog help: show the tool's usage, or the help for one verb.
---
# `2dog help`
Shows the usage, or the help for one verb. `2dog --help` shows the
same help. `2dog` alone prints the version info and the usage.
```bash
2dog help [verb]
```
```bash
2dog help new
2dog add --help
```
---
page: Resource Import
section: Develop and Configure
source: https://2dog.dev/import-tool.md
description: How 2dog imports Godot resources and exports game packs during MSBuild: incremental imports driven by GodotProjectDir, the twodog.import helper, pack contents, and troubleshooting.
---
# Resource Import
Godot must import resources before a project can run. The import generates
`.uid` files for C# scripts, processes assets, and builds the `.godot/` cache. Without it, the
engine reports missing UIDs. 2dog runs the import **automatically during
the build**.
## Automatic import (MSBuild)
Any project that sets `` and references `2dog.engine` gets an
incremental import after `Build`:
- Changed project files trigger an import. Otherwise, a shared stamp in
`/.godot/` marks the project up to date.
- The import does not track directories with a `.gdignore` file, so a change
to a nested 2dog host does not trigger an import. Use `TwoDogImportExcludes` for more exclusions.
- No installed Godot editor is required. A helper process uses the
editor-variant libgodot from `2dog..editor`.
### Properties
| Property | Default | Description |
|-------------------------|--------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `TwoDogAutoImport` | `true` | Set `false` to disable the automatic import. |
| `TwoDogRequireImport` | `false` | Set `true` to fail the build when no import capability is available (instead of a warning). |
| `TwoDogForceImport` | `false` | Set `true` to run the import even when the project is up to date. This also catches changes that the file tracking cannot see, such as deleted assets. |
| `TwoDogImportStampFile` | `/.godot/2dog.import.stamp` | Override the stamp file location. |
| `TwoDogImportExcludes` | - | Extra semicolon-separated glob excludes for the import inputs (directories with a `.gdignore` are always excluded). |
| `GodotEditor` | - | Path to an external Godot editor binary for imports and [pack exports](#pack-export). The build uses this editor instead of the in-process helper only when you set this property. The `GODOT_EDITOR` and `GODOT4` environment variables do not affect imports or exports. |
If the build cannot find the helper payload or an external editor, it shows a
warning and skips the import. Set `TwoDogRequireImport` to fail instead.
Deleting `.godot/` forces a full import on the next build.
## The import helper (`twodog.import`)
`2dog.engine` runs the import in a separate helper process. It runs only one
import per project at a time. The helper needs the `editor` variant, because
the `debug` and `release` natives cannot import.
## Pack export
When you publish a host, the same helper also exports the game content
through the host's export preset. The pack holds resources only. The host's own
publish ships the code: the game assembly, the bindings, libgodot and the .NET
runtime. 2dog skips Godot's own .NET export step, which runs `dotnet publish` on the
game project.
- Desktop packs sit beside the host executable, and the export copies the
preset's GDExtension libraries next to them, where the engine loads them.
- Web packs embed their GDExtension side modules.
- A stock Godot editor, also when you set it as `GodotEditor`, exports the
standard way. It runs Godot's .NET publish against the stock bindings, so
2dog-only engine APIs do not compile there. It also does not include the
GDExtension libraries.
Packs exported later, such as patches, DLC or mods loaded with
`ProjectSettings.LoadResourcePack`, are resource-only as well. C# changes ship
with a host update, and a pack can only use script classes that the installed
host already contains.
## Troubleshooting
### No import capability, or GodotTools.dll not found
The build could not find the editor libgodot (`2dog..editor`), the
helper, or the GodotTools assemblies (`2dog.tools`). Restore `2dog.engine`, which depends on
all three, or set `` to an external editor binary.
### No .uid files generated
Look for the `TwoDog: Imported Godot project` build message. Make sure that
the build used an *editor* libgodot or an editor binary.
---
page: Build Variants
section: Develop and Configure
source: https://2dog.dev/build-configurations.md
description: Choosing debug, release and editor variants with single or double precision.
---
# Build Variants
2dog selects the native Godot variant from your .NET build configuration:
| .NET configuration | `TwoDogVariant` | Use |
|--------------------|-----------------|-----------------------------------|
| **Debug** | `debug` | Development, debugging, tests |
| **Release** | `release` | Production |
| **Editor** | `editor` | Editor types and `[Tool]` scripts |
Every variant also comes in [double precision](#double-precision);
`TwoDogVariant` stays the same.
- `debug` includes assertions and debugging support.
- `release` is the optimized production runtime.
- `editor` enables `TOOLS_ENABLED`, including editor types, import plugins,
and `[Tool]` scripts.
## Selecting a Variant
`TwoDogVariant` defaults to `debug` in Debug, `editor` in Editor, and `release`
for Release and other configurations. Set it explicitly to override that mapping:
```xml
Exenet10.0debug
```
The platform meta package includes all three desktop variants, so you do not
need extra package references to select a variant.
Web hosts use `TwoDogWebVariant` with the same configuration mapping. The
`2dog.browser-wasm` package restores both debug and release natives. If the
selected payload is missing, the build fails. Web and Android do not support the editor
variant: use Debug or Release, or explicitly select a supported variant.
Without an explicit variant override, use the configuration in the usual way:
```bash
dotnet run -c Debug
dotnet publish -c Release
dotnet test -c Editor
```
See [Configuration](./configuration) for the property and package reference.
## Double Precision
Reference `2dog.engine.double` in the host and `2dog.godotsharp.double` in
every game or library that uses Godot APIs. Editor builds also use
`2dog.godotsharp.editor.double`. Rebuild those projects against the matching
bindings; single- and double-precision Godot types cannot be mixed.
```xml
```
Keep `2dog.avalonia`, `2dog.blazor`, `2dog.qt`, `2dog.xunit`, `2dog.nunit` and
`2dog.repl` under their ordinary names. `2dog.gdunit` becomes `2dog.gdunit.double`. 2dog selects the native dependencies automatically.
If you reference them explicitly, append `.double` to the entire ID, such as
`2dog.win-x64.release.double`.
::: danger GDExtensions + Double Precision
Use only GDExtensions that are built for the same precision as the engine.
In double precision, vectors and other math types use 64-bit floats, so an
extension built for the other precision reads them wrongly.
:::
## Experimental WebGPU
WebGPU is opt-in. It currently supports the Mobile renderer on these targets:
- Windows, Linux and macOS, in single precision
- single-threaded browser runtimes
2dog does not change your existing renderer defaults. If you use WebGPU with
Android, double precision, threaded browser payloads or Forward+, you get a
clear error. Native threading remains supported.
Headless imports and exports do not require a WebGPU device.
```bash
dotnet run --project MyGame.2dog -- --rendering-driver webgpu --rendering-method mobile
```
For project settings, select both the renderer and the platform driver:
```ini
[rendering]
renderer/rendering_method="mobile"
rendering_device/driver="webgpu"
rendering_device/driver.windows="webgpu"
rendering_device/driver.linuxbsd="webgpu"
rendering_device/driver.macos="webgpu"
```
Configure `.web` and `.mobile` feature overrides explicitly when present.
See [web configuration](/configuration/web#experimental-webgpu) and
[Blazor configuration](/configuration/blazor#experimental-webgpu) for browser boot.
WebXR's immersive rendering is outside this experimental scope.
### Adapter and shader limits
The backend has no image atomics, multiview or ray tracing. Read/write storage
textures require native support for their format; unsupported formats fail
shader creation. Use supported R32 formats or separate read and write resources.
Overlapping writable storage-buffer bindings are rejected instead of redirected
to dummy buffers. GPU timestamp and memory statistics are not implemented.
Translated WGSL is cached; GPU pipeline compilation still happens at runtime.
Native Dawn loads only when selected. WebGPU requires macOS 13 or newer; other
renderers retain their existing runtime requirements. Desktop packages include
Dawn and the compiler sidecars and notices needed by the selected backend.
### Source builds
Normal `uv run poe build-all` enables WebGPU on supported configurations. Keep
the Emscripten SDK pinned to `EmscriptenVersion` from `Directory.Build.props`.
```text
uv run build-godot.py --webgpu auto
uv run build-godot.py --platform web --webgpu yes
```
Use `--webgpu no` to omit the driver. `--webgpu-native-path ` supplies an
existing native Dawn boundary SDK, including its runtime libraries and licenses.
Unsupported configurations reject `--webgpu yes`; `auto` preserves their renderer.
Browser packages supply a prepared port and a writable cache. Override that cache
with `TwoDogWebGpuCachePath` when needed.
## Downloading CI Packages
In a 2dog checkout, you can use the tested packages of the current branch, so
you do not have to compile Godot. Install `uv`, the .NET SDK that
`global.json` selects, and an authenticated GitHub CLI (`gh`). Then run:
```bash
uv run poe pull-packages
dotnet run --project demos/showcase/showcase.2dog
```
The command imports the latest successful CI run with available packages for
the current branch into `packages/`. Each bundle includes Windows x64, Linux
x64, macOS arm64, browser and Android packages. It also does these steps:
- It downloads the matching Godot SDK, managed APIs and source generator.
- It refreshes only stale package versions in the `.packages/` cache of the
checkout.
- It restores `2dog.tests.slnf`.
Use the same command in the checkout on each operating system.
These options change what the command does:
- `--run ` selects the same snapshot on several machines.
- `--precision double` or `--precision both` adds the double-precision packages.
- `--plan` shows the run, sizes and expiry, and does not download anything.
- `--no-restore` imports the feed and does not restore projects.
WebGPU uses single precision.
The downloader checks package versions against `Directory.Build.props` and
the native source against the checkout's Godot submodule commit. It rejects
incomplete feeds and empty native stubs. The selected run and package versions
are recorded in `packages/ci-packages.json`.
CI package artifacts expire after seven days. If no matching artifact remains,
run CI again. Do not run `poe build` after you download packages, unless you want to repack.
Without local native builds, it can replace the packages of other platforms with
empty stubs. `dotnet build`, `dotnet run` and `dotnet test` use the downloaded
feed directly.
For a project outside the checkout, add the absolute path to `packages/` as a
NuGet source in that project's `NuGet.Config`, and pin the versions from the
downloaded feed. If the project previously restored different contents at
those same versions, remove only the affected package/version directories
from its NuGet cache before restoring.
## Editor Variants
::: tip Opening the editor
The editor variant enables editor APIs. To start the interactive editor and
initialize the runtime singletons of the editor, such as `EditorInterface`, pass
`--editor`:
```bash
dotnet run --project MyGame.2dog -c Editor -- --editor
```
The generic host forwards these arguments and pumps the editor's main loop.
A custom host must continue to run the engine loop. It must not assume that a
game scene is active in editor mode.
Resource import also runs separately and automatically at build time; see
[Resource Import](./import-tool).
The editor's export dialog edits the presets that `dotnet publish` uses. It
cannot build a playable game, because the host does that. For this reason,
Export Project, Export All, one-click deploy and
`--export-release`/`--export-debug` are not available inside a host. PCK/ZIP and patch exports remain and contain
resources only; see [Pack export](./import-tool#pack-export).
:::
---
page: Environment Variables
section: Develop and Configure
source: https://2dog.dev/configuration/environment-variables.md
description: Environment variables for 2dog debugging, diagnostics, CLI output, assembly loading, and Android builds.
---
# Environment Variables
Diagnostic switches are off when unset.
## Debugging and Diagnostics
| Variable | Value | Effect |
|------------------------|------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `GODOT4` | Full path to the .NET-enabled Godot executable | The generated VS Code C# launch configuration uses it. On Windows, select the main `mono` executable, not the `_console.exe` wrapper. `2dog doctor` checks it |
| `TWODOG_LIFECYCLE_LOG` | Log file path | Traces window activation, minimize/restore messages, Godot window signals and an independent engine-loop heartbeat. Applies to windowed engines using the Windows display driver |
| `TWODOG_GODOT_LOG_DIR` | Directory path | Makes 2dog's xUnit/NUnit fixtures enable Godot's verbose native log and write a separate `godot--.log` per fixture. Creates the directory if needed; works with headless and windowed fixtures |
| `TWODOG_AVALONIA_DIAG` | Exactly `1` | Prints Avalonia GPU presentation and Linux EGL interop diagnostics to the console, prefixed with `2DOG_GPU:` or `2DOG_EGL:` |
`GODOT4` selects the executable launched by the debugger. Resource import and
pack export use the packaged editor by default. To use an external editor, set
the [`GodotEditor` MSBuild property](/import-tool#properties) explicitly.
`GODOT4` and `GODOT_EDITOR` in the environment do not select the editor for
import and export.
The lifecycle log appends and flushes once per second, even while the engine
thread is stalled. Give each process its own log file. On Windows, Avalonia
diagnostics need `OutputType=Exe`.
```bash
TWODOG_LIFECYCLE_LOG=./window-lifecycle.log dotnet run --project MyGame.2dog
TWODOG_GODOT_LOG_DIR=./godot-logs dotnet test MyGame.xunit
```
## CLI Output and Automation
These variables affect the [`2dog` CLI](/dnx-2dog).
| Variable | Value | Effect |
|---------------------------------|-----------------------------------|----------------------------------------------------------------------------------------------------------------------------------|
| `TWODOG_ACCESSIBLE` | `1` | Uses numbered yes/no questions instead of selection lists, and static progress instead of spinners. Equivalent to `--accessible` |
| `NO_COLOR` | Any nonempty value, including `0` | Disables colors. Equivalent to `--no-color` |
| `CLICOLOR_FORCE`, `FORCE_COLOR` | `1` | Enables colors even when output is redirected; `NO_COLOR`, `--no-color` and plain mode still disable colors |
| `TERM` | `dumb` | Enables plain output without ANSI sequences and uses accessible prompts. Equivalent to `--plain` |
| `CI` | `1` | Disables interactive prompts and animated progress |
The CLI also detects `GITHUB_ACTIONS`, `TF_BUILD`, `GITLAB_CI`,
`TEAMCITY_VERSION`, `BUILD_NUMBER`, `JENKINS_URL`, `BUILDKITE`, `CIRCLECI`,
`TRAVIS` and `APPVEYOR` as CI indicators. For these indicators and for
`TWODOG_ACCESSIBLE`, `CLICOLOR_FORCE` and `FORCE_COLOR`, any value enables the
switch, except an empty value, `0` or `false`. Case does not matter for
`false`.
## Godot Assembly Paths
`GODOTSHARP_DIR` (the directory holding `GodotPlugins.dll`), `GODOT_TOOLS_DIR`
and `GODOT_PROJECT_ASSEMBLY_DIR` locate Godot's managed assemblies. 2dog sets
them for its hosts, so leave them unset. For a game assembly in a custom
location, use [`Engine.ProjectAssemblyDir`](/api/engine#projectassemblydir). [`2dog doctor`](/cli/doctor) reports stray values as
`env.overrides`.
## .NET and Android Tooling
| Variable | Value | Effect |
|-------------------------------------------|--------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------|
| `DOTNET_HOST_PATH`, `DOTNET_ROOT`, `PATH` | See [.NET environment variables](https://learn.microsoft.com/dotnet/core/tools/dotnet-environment-variables) | Where the CLI finds `dotnet` for child commands, in that order |
| `AndroidSdkDirectory` | Android SDK directory | First explicit SDK location checked by `2dog new`, `add` and `doctor` |
| `ANDROID_HOME`, `ANDROID_SDK_ROOT` | Android SDK directory | Additional SDK locations checked in that order; see [Android environment variables](https://developer.android.com/tools/variables) |
| `ANDROID_USER_HOME` | Android user configuration directory | Location of `debug.keystore` for the signing fallback; defaults to `~/.android` |
For Android signing, set all three variables of a row together:
| Build variant | Keystore path | Key alias | Keystore/key password |
|---------------|---------------------------------------|---------------------------------------|-------------------------------------------|
| Debug | `GODOT_ANDROID_KEYSTORE_DEBUG_PATH` | `GODOT_ANDROID_KEYSTORE_DEBUG_USER` | `GODOT_ANDROID_KEYSTORE_DEBUG_PASSWORD` |
| Release | `GODOT_ANDROID_KEYSTORE_RELEASE_PATH` | `GODOT_ANDROID_KEYSTORE_RELEASE_USER` | `GODOT_ANDROID_KEYSTORE_RELEASE_PASSWORD` |
2dog passes the password to the signer through an `env:` reference. See
[Android signing](./android#signing) for the keystore order.
---
page: Godot Unit Testing
section: Testing
source: https://2dog.dev/testing/index.md
description: Test your Godot game with GdUnit, xUnit or NUnit, from a first test to signals and simulated input.
---
# Testing
Load scenes, click buttons, and check signals from a normal .NET test runner.
2dog starts a real Godot engine for your tests, so they use your game's scripts
and resources.

*"Find those bugs, wherever they hide."*
2dog supports several popular test frameworks. It also provides generic
fixtures if you need a different setup.
## Choose your framework
Test hosts are opt-in. To add one, choose `--xunit`, `--gdunit` or `--nunit`.
:::: columns
::: column xUnit
- Headless and rendering collections
- One engine shared across several test classes
- Custom engine arguments
- Combinatorial and pairwise rows through `TestMatrix`
- Windowed tests on macOS through the main-thread runner
:::
::: column NUnit
- Headless fixtures on all desktop platforms
- Rendering fixtures on Windows and Linux
- One engine per test class or parameterized fixture
- Async setup and teardown hooks
- Built-in combinatorial and pairwise test cases
:::
::::
Both integrations provide frame waits, signal expectations, input simulation,
async lifecycle support, and automatic checks for Godot errors and warnings.
::: details Minor differences
| Capability | xUnit | NUnit |
| --- | --- | --- |
| Engine shared across test classes | Collection fixture | One engine per fixture; no cross-class sharing |
| Rendering on macOS | Main-thread runner included | Needs a dedicated runner; use headless tests for now |
| Class-wide configuration matrix | Separate fixture types and collections | `[TestFixture(...)]` |
| Method input combinations | `TestMatrix` with `[MemberData]` | `[Combinatorial]` and `[Pairwise]` |
:::
## Test runners
All three hosts that 2dog sets up run on Microsoft.Testing.Platform (MTP):
| Host | MTP integration | Setup |
|--------|------------------------------------------------------------|----------------------------------------------------------------|
| GdUnit | 2dog's framework drives the embedded GDScript runner | [Configuration](/configuration/gdunit) |
| NUnit | Official NUnit adapter bridge | [Configuration](/configuration/nunit#microsofttestingplatform) |
| xUnit | Upstream xUnit runner with 2dog's owner-thread entry point | [Configuration](/configuration/xunit#microsofttestingplatform) |
When you set up a test host, 2dog creates a root `global.json` that selects
MTP. If the project already has a `global.json`, 2dog does not change it. Run
`dotnet test` from the project root to run every test host. To select MTP in
your own `global.json`, add:
```json
{ "test": { "runner": "Microsoft.Testing.Platform" } }
```
If a host or a `global.json` still uses VSTest, [`2dog doctor`](/cli/doctor)
reports it with the check `layout.test-runner`. See Microsoft's [`dotnet test` guide](https://learn.microsoft.com/dotnet/core/testing/unit-testing-with-dotnet-test)
for runner selection and options such as `--coverage`.
## Find a guide
| Task | Guide |
|-----------------------------------------------------|--------------------------------------------------------------------------------------------|
| Set up a test project | [xUnit](./xunit) or [NUnit](./nunit) |
| Run GDScript suites through `dotnet test` | [GdUnit](./gdunit) |
| Wait for frames, check signals, or verify deletion | [Writing engine tests](./writing-tests) |
| Click controls or send keyboard events | [Simulating input](./input) |
| Change packages, build variants, or import settings | [xUnit configuration](/configuration/xunit) or [NUnit configuration](/configuration/nunit) |
::: tip Keep simple tests simple
Code that does not use Godot can stay in ordinary unit tests. Use an engine
fixture when the behavior depends on scenes, resources, or Godot's main loop.
:::
---
page: Testing GDScript with GdUnit
section: Testing
source: https://2dog.dev/testing/gdunit.md
description: Understand how the 2dog host changes GdUnit execution, resource isolation and test reporting.
---
# Testing GDScript with GdUnit
Use [GdUnit's official documentation](https://godot-gdunit-labs.github.io/gdUnit4/latest/)
for writing suites, assertions, hooks, mocks and scene tests. Its
[test suite guide](https://godot-gdunit-labs.github.io/gdUnit4/latest/testing/test-suite/)
and [parameterized test guide](https://godot-gdunit-labs.github.io/gdUnit4/latest/advanced_testing/paramerized_tests/)
describe the framework APIs. This page covers only the differences introduced
by 2dog. To create the .NET project, follow the [GdUnit host guide](/hosts/gdunit).
## Run through .NET
The 2dog host uses Microsoft.Testing.Platform (MTP) and `dotnet test`. Run
commands from the project root, where the setup's `global.json` selects MTP:
```bash
dotnet test --project MyGame.gdunit
dotnet test --project MyGame.gdunit --list-tests
dotnet test --project MyGame.gdunit --gdunit-filter "*test_answer"
```
Plain `dotnet test` runs the GdUnit host together with the
[xUnit](/hosts/xunit) and [NUnit](/hosts/nunit) hosts. If the root
`global.json` does not select MTP, run the commands from `MyGame.gdunit/`. That
folder has its own `global.json`; see
[Select the runner](/configuration/gdunit#select-the-runner).
Use the host's MTP options for these commands. Do not use GdUnit's
[standalone command-line options](https://godot-gdunit-labs.github.io/gdUnit4/latest/advanced_testing/cmd/);
only GdUnit's own runner accepts them.
`--gdunit-filter` matches the fully qualified test name with case-sensitive
`*` and `?` wildcards. Each parameter row has its own result and a stable ID.
The host derives the ID from the resource path, the method and the row index.
To select a single row, use MTP's
[`--filter-uid`](https://learn.microsoft.com/dotnet/core/testing/microsoft-testing-platform-cli-options).
## Test a private project copy
Each discovery or execution request imports a fresh, writable copy of the
game. The copy keeps the original `res://` paths, game resource UIDs and
autoload settings. You can put test scenes and assets in the host folder, even
though its `.gdignore` hides them from the game. Only the configured test roots
become visible in the private copy. Folders under a nested `.gdignore` stay
hidden.
The source game, settings, sidecars and import cache stay unchanged. Changes
to tests or assets appear in the next request. Tests do not write
files to the source game. If a tool inspects files that the tests create,
configure the tool to read the
[artifact directory](/configuration/gdunit#artifacts).
The host's bridge replaces the game's main scene. Load game scenes explicitly in
tests. Discovery starts the engine and game autoloads, but runs no test bodies
or suite hooks. Execution uses that same engine session and runs only selected
cases.
The package supplies an unmodified, pinned official GdUnit release, with its
license and release provenance. You do not need to download the addon
separately. If the source project has its own `addons/gdUnit4` copy, the host
rejects it, so that two framework versions do not mix.
## Results and lifecycle
Assertions, skips and test errors become MTP outcomes. Suite lifecycle failures
also fail the run, even when every test body passes. Unexpected native Godot
warnings and errors fail the host. In the private project, Godot tracks the
GDScript call stack, so Release builds also report the source line of each
assertion.
The host disables GdUnit's flaky-test retries and fail-fast execution. It
preserves GdUnit session hooks; reports they generate remain in the private
project.
When MTP cancels a run, the host stops an active import process, or it asks
GdUnit to stop execution and finish teardown. The embedded session then gets
five seconds to clean up.
[Host timeouts](/configuration/gdunit#execution-options) limit the import
process and the embedded session. Neither timeout can interrupt a blocked
native call.
## Current host scope
The host executes GDScript suites headlessly and serializes requests within
its load context. Godot starts, runs and stops on its owner thread.
Scenes can load ordinary C# game components, but the host does not load
GdUnit4Net's C# test adapter. For C# test suites, use the [xUnit](/hosts/xunit)
or [NUnit](/hosts/nunit) hosts.
---
page: Godot Unit Testing with NUnit
section: Testing
source: https://2dog.dev/testing/nunit.md
description: Write Godot tests with NUnit's headless or rendering fixture.
---
# Testing with NUnit
Add a test project from your Godot project directory:
```bash
dnx 2dog add --nunit
dotnet test MyGame.nunit
```
The generated `BasicTests.cs` has runnable examples of async work, signals,
and deletion. Extend them with tests for your game.
## Your first test
Derive from `GodotTestFixture`. Its `EngineFixture` property gives your test
class a shared headless engine. This test waits for a real Godot timer:
```csharp
using Godot;
using Timer = Godot.Timer;
using NUnit.Framework;
using twodog.Testing.NUnit;
public class TimerTests : GodotTestFixture
{
[Test]
public async Task TimerEmitsTimeout()
{
var timer = new Timer { OneShot = true, WaitTime = 0.01 };
try
{
EngineFixture.Tree.Root.AddChild(timer);
using var timeout = GodotAssert.ExpectSignal(
timer, Timer.SignalName.Timeout);
timer.Start();
await timeout.WaitAsync(EngineFixture);
timeout.AssertEmitted();
}
finally { timer.Free(); }
}
}
```
`EngineFixture` exposes `Engine`, `GodotInstance`, `Tree`, `Input`, and `Errors`.
Each derived test class gets one engine; clean up the objects each test creates.
Fixtures skip the configured main scene, but project autoloads still
initialize. Load and instantiate scenes explicitly in your tests. If a scene
changes shared state such as `SceneTree.Paused`, restore that state.
::: details Why the Timer alias?
The fixture's `Godot` compatibility property shadows the `Godot` namespace in
derived classes, so alias Godot types such as `Timer`.
:::
## Setup and teardown
Use your own `[SetUp]` and `[TearDown]` methods for per-test work, or
`[OneTimeSetUp]` and `[OneTimeTearDown]` for work shared by the class.
These methods can return `Task`.
::: warning Keep the engine on its thread
Do not change the inherited `SingleThreaded`, `NonParallelizable`, and
`FixtureLifeCycle(LifeCycle.SingleInstance)` settings. Around Godot access, do
not use `Task.Run`, `ConfigureAwait(false)`, or NUnit attributes that switch
threads, such as `Timeout` and `RequiresThread`. Godot access must stay on the
engine's owner thread.
:::
## Rendering tests
Derive from `GodotRenderingTestFixture` to open a window and render frames:
```csharp
public class RenderingTests : GodotRenderingTestFixture
{
[Test]
public void WindowIsVisible()
=> Assert.That(EngineFixture.Tree.Root.Visible, Is.True);
}
```
Each NUnit fixture owns its engine. Unlike an xUnit collection, this
integration does not share an engine across test classes.
| Platform | Requirement |
|----------|----------------------------------------------------------------------------------------------|
| Linux | A display and a compatible renderer |
| Windows | A display; the fixture supplies STA and common-controls activation |
| macOS | Use the headless fixture; the standard NUnit runner does not reserve the process main thread |
::: warning Use a separate rendering test project
Keep rendering fixtures in a separate test project, so that each mode runs in
its own process. The setup uses headless fixtures. Native Godot can crash if
one process switches display modes between engine restarts, so 2dog's fixtures
reject the switch. For windowed macOS tests, use xUnit's rendering collection
for now.
:::
::: details Custom engine arguments
Override `CreateFixture()` in either base class. Define a fixture with the
arguments you need, such as a compatibility renderer:
```csharp
public class OpenGlFixture()
: twodog.Testing.FixtureBase("--rendering-driver", "opengl3");
public class UiTests : GodotRenderingTestFixture
{
protected override twodog.Testing.FixtureBase CreateFixture()
=> new OpenGlFixture();
}
```
Use `GodotRenderingTestFixture` only on Windows or Linux. On macOS, windowed
Godot needs the process main thread, and an override does not change that.
:::
## Parameterized fixtures and test cases
Use NUnit's [parameterized fixtures](https://docs.nunit.org/articles/nunit/writing-tests/attributes/testfixture.html#parameterized-test-fixtures)
to run a test class with several configurations. Each fixture instance gets
its own engine. Pass only plain .NET values, not Godot
types, as constructor arguments. Create Godot objects after the engine starts.
For method-level inputs, `[TestCase]` supplies rows, `[Combinatorial]` combines
parameter values, and `[Pairwise]` covers every pair with fewer cases. See
[NUnit's data attributes](https://docs.nunit.org/articles/nunit/writing-tests/attributes/pairwise.html).
## Waits, signals, and input
Use [Writing engine tests](./writing-tests) for frame waits, signal
expectations, cleanup, and expected errors. Use [Simulating input](./input) for
UI tests. Both guides work with NUnit and xUnit.
See [NUnit configuration](/configuration/nunit) for packages and Editor tests.
---
page: Godot Unit Testing with xUnit
section: Testing
source: https://2dog.dev/testing/xunit.md
description: Write your first Godot test with xUnit and choose a shared engine fixture.
---
# Testing with xUnit
Add a test project from your Godot project directory:
```bash
dnx 2dog add --xunit
dotnet test MyGame.xunit
```
The generated `BasicTests.cs` has runnable examples. Replace or extend them
with tests for your game.
## Your first test
Put engine tests in a `HeadlessCollection`. Take its fixture as a constructor
argument. This test adds a node to the scene tree and checks that it entered:
```csharp
using Godot;
using twodog.Testing;
using twodog.Testing.Xunit;
using Xunit;
[Collection]
public class SceneTests(HeadlessFixture godot)
{
[Fact]
public void NodeEntersTree()
{
var node = new Node();
try
{
godot.Tree.Root.AddChild(node);
Assert.True(node.IsInsideTree());
}
finally { node.Free(); }
}
}
```
The fixture exposes `Engine`, `GodotInstance`, `Tree`, `Input`, and `Errors`.
Tests in the collection share these objects, so clean up what each test creates.
Fixtures skip the configured main scene, but project autoloads still
initialize. Load and instantiate scenes explicitly in your tests. If a scene
changes shared state such as `SceneTree.Paused`, restore that state.
## Choose a fixture
| Collection | Constructor argument | Use |
|-----------------------|-------------------------|--------------------------------------|
| `HeadlessCollection` | `HeadlessFixture godot` | Scene logic and CI, without a window |
| `RenderingCollection` | `Fixture godot` | Tests that need rendering |
Keep Godot collections non-parallel. You can use several collections. xUnit
disposes the engine of one collection before it starts the next collection.
Keep headless and rendering collections in separate test projects. Native
Godot can crash if one process switches display modes between engine restarts,
so 2dog's fixtures reject the switch.
::: details Custom engine arguments
Derive from `FixtureBase` and define a non-parallel collection in your test
project:
```csharp
using twodog.Testing;
using Xunit;
public class OpenGl3Fixture()
: FixtureBase("--rendering-driver", "opengl3");
[CollectionDefinition(DisableParallelization = true)]
public class OpenGl3Collection : ICollectionFixture;
```
The built-in collection definitions compile into your test assembly because
xUnit does not discover them in an ordinary referenced DLL.
:::
## Waits, signals, and input
Use [Writing engine tests](./writing-tests) for frame waits, signal expectations,
cleanup, and expected errors. Use [Simulating input](./input) for UI tests.
Both guides work with xUnit and NUnit.
## Setup and test data
| Scope | Setup | Cleanup |
|---------------|---------------------------------------------------|------------------------------------------------------------|
| Each test | Constructor or `IAsyncLifetime.InitializeAsync()` | `IDisposable.Dispose()` or `IAsyncLifetime.DisposeAsync()` |
| Shared engine | A custom collection fixture | Fixture disposal |
Async lifecycle methods run on the engine's owner thread too. See
[xUnit's shared context guide](https://xunit.net/docs/shared-context).
::: details Combinatorial and pairwise cases
Use `TestMatrix` to build managed rows for a theory's `[MemberData]`:
```csharp
public static IEnumerable