--- 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. ![Illus. Diego Rago: a white anthro dog in a hacker hoodie and glasses walking their blue godot robot dog](img/2dog-walkies-big.png) *(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. ![Illus. Moritz Voss: a white anthro dog in a hacker hoodie and glasses scouting ahead with their blue godot robot dog](img/2dog-scouting.png) *"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. ![Illus. Moritz Voss: a white anthro dog in a hacker hoodie and their blue robot dog checking a trail map](img/2dog-trail-map.png) *"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... ![Illus. Diego Rago: a white anthro dog in a hacker hoodie getting pulled on the leash by their blue robot dog](../img/2dog-squirrel.png) *(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. ![Illus. Moritz Voss: a white anthro dog in a hacker hoodie and glasses walking their blue godot robot dog](../img/2dog-victory.png) *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. ![Illus. Diego Rago: a white anthro dog in a hacker hoodie playing tug of war with of their robot dog](../img/2dog-tugofwar.png) *"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. ![Illus. Diego Rago: a white anthro dog in a hacker hoodie and glasses walking their blue godot robot dog](../img/2dog-harmony.png) *(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. ![Illus. Diego Rago: a white anthro dog in a hacker hoodie and glasses walking their blue godot robot dog](../img/2dog-harmony.png) *(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. ![Illus. Diego Rago: a white anthro dog in a hacker hoodie and glasses walking their blue godot robot dog](../img/2dog-harmony.png) 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. ![Illus. Diego Rago: a white anthro dog in a hacker hoodie and glasses walking their blue godot robot dog](../img/2dog-harmony.png) *(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. ![Illus. Moritz Voss: a white anthro dog in a hacker hoodie and their robot dog sniffing the ground](../img/2dog-investigation.png) 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. ![Illus. Moritz Voss: a white anthro dog in a hacker hoodie and their robot dog sniffing the ground](../img/2dog-investigation.png) 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. ![Illus. Moritz Voss: a white anthro dog in a hacker hoodie and their robot dog sniffing the ground](../img/2dog-investigation.png) ::: 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. ![Illus. Moritz Voss: a white anthro dog in a hacker hoodie getting pulled on the leash by their blue robot dog](img/2dog-terminal.png) *"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 Exe net10.0 debug ``` 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. ![Illus. Moritz Voss: a white anthro dog in a hacker hoodie and their robot dog sniffing the ground](../img/2dog-investigation.png) *"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 Cases => TestMatrix.Pairwise( ["easy", "hard"], [1, 2, 4], [true, false]); ``` Add `[MemberData(nameof(Cases))]` to a `[Theory]` that takes a `string`, an `int`, and a `bool`. `Pairwise` covers every pair of input values with fewer rows. Use `Combinatorial` for every combination. Both are deterministic, but `Pairwise` does not always produce the smallest possible set. These are method inputs. For different engine configurations across an entire class, use separate fixture types and non-parallel collections. ::: ::: warning Test data runs before the engine Do not put Godot types such as `NodePath` and `StringName` in `MemberData`, because they can crash discovery. Pass primitive values, or set `DisableDiscoveryEnumeration = true`. See [xUnit test discovery](/known-issues/xunit-discovery). ::: ## Runner settings 2dog keeps the tests, async continuations, and fixture disposal on the engine's owner thread. Keep Godot access on that thread; avoid `Task.Run` and `ConfigureAwait(false)` around it. ::: details Custom entry points and test frameworks On Windows, the collection thread is STA. 2dog also supplies the comctl32 v6 manifest unless you set your own `ApplicationManifest`. On macOS, windowed Godot needs the process main thread. 2dog wraps xUnit's generated entry point to run Godot collections there. If your project has its own entry point, run xUnit through `GodotTestThread.RunMain`. To use another xUnit test framework, set `false`. This also removes the entry-point wrapper. For windowed macOS fixtures, your entry point must call `GodotTestThread.RunMain`. Your framework must also run collections through `GodotTestThread.Run`. ::: ::: details Adding packages by hand The setup does this for you. For an existing test project: ```bash dotnet add package 2dog.engine dotnet add package 2dog.xunit dotnet add package xunit.v3.mtp-v2 dotnet add package Microsoft.Testing.Extensions.CodeCoverage ``` Set `OutputType` to `Exe` and `UseMicrosoftTestingPlatformRunner` to `true`. Then select Microsoft.Testing.Platform in `global.json`; see [xUnit configuration](/configuration/xunit#microsofttestingplatform). For double precision, add `2dog.engine.double` instead of `2dog.engine`. Keep `2dog.xunit` as it is. See [xUnit configuration](/configuration/xunit) for game paths and Editor bindings. ::: --- page: Writing Engine Tests section: Testing source: https://2dog.dev/testing/writing-tests.md description: Wait for Godot frames, assert signals, and clean up test state in xUnit and NUnit. --- # Writing engine tests A test that uses Godot often needs more than a method call: a timer needs frames, a signal needs a listener, and a queued deletion needs time to finish. The testing helpers handle these steps on the engine's thread. The examples use `godot` for the shared fixture: | Framework | Fixture | Helpers namespace | |-----------|------------------------------------------------|------------------------| | xUnit | Constructor argument `godot` | `twodog.Testing.Xunit` | | NUnit | Use `var godot = EngineFixture;` inside a test | `twodog.Testing.NUnit` | ## Wait for frames An ordinary `await Task.Delay(...)` does not advance Godot. Choose a helper when the work needs the main loop: | Helper | Waits for | |-----------------------------------------|----------------------------------------------------------------| | `godot.WaitUntilAsync(() => condition)` | A condition to become true | | `godot.AwaitAsync(task)` | A task to finish; returns its result or rethrows its exception | | `signal.WaitAsync(godot)` | A recorded signal emission | | `GodotAssert.FreedAsync(godot, node)` | A native object to be deleted | For example, wrap an operation that awaits a Godot signal: ```csharp async Task NextFrame() { await godot.Tree.ToSignal(godot.Tree, SceneTree.SignalName.ProcessFrame); } await godot.AwaitAsync(NextFrame()); await godot.WaitUntilAsync(() => node.IsNodeReady(), timeout: TimeSpan.FromSeconds(2)); ``` ::: info Timeouts and cancellation By default, a wait times out after five seconds. A wait also stops when your test framework cancels the test. To set a different timeout, pass `timeout:`. To add your own cancellation token, pass `cancellationToken:`. A timeout fails the test, but it cannot cancel a task that you supplied. Cancel or clean up that work yourself. ::: Await only one wait helper at a time. Keep Godot access on the engine's owner thread, also after `await`. Avoid `Task.Run` and `ConfigureAwait(false)`. ## Check a signal Create the signal expectation before you trigger the behavior, so that it also catches synchronous signals. If the signal can arrive later, wait for it. Then assert the count: ```csharp using var timeout = GodotAssert.ExpectSignal(timer, global::Godot.Timer.SignalName.Timeout); timer.Start(); await timeout.WaitAsync(godot); timeout.AssertEmitted(); ``` | Call | Meaning | |-----------------------------------|---------------------------------------------------| | `AssertEmitted()` | Exactly one emission | | `AssertEmitted(0)` | No emissions | | `WaitAsync(godot, count: 2)` | Wait until at least two emissions arrive | | `ExpectSignal(source, signal)` | Record a single argument per emission in `Values` | ```csharp using var entered = GodotAssert.ExpectSignal(parent, Node.SignalName.ChildEnteredTree); parent.AddChild(child); entered.AssertEmitted(); var receivedChild = entered.Values[0]; ``` Expectations disconnect on disposal, timeout, or cancellation. Disposal does not check the emission count; call `AssertEmitted()` for that. A wait fails if the source is freed before the expected signal arrives. For signals with several arguments, connect a typed callable and use `WaitUntilAsync` on the values you record. ## Clean up native objects Free nodes in a `finally` block, so that cleanup also occurs when an assertion fails. If you dispose a node's C# wrapper, Godot does not free the native node. Also dispose the resources and signal expectations that your test owns. `QueueFree()` schedules deletion. To check that it actually happened: ```csharp node.QueueFree(); await GodotAssert.FreedAsync(godot, node); ``` ::: tip Tree exit and deletion are separate Godot emits `TreeExiting` while a node is still in the tree, and `TreeExited` after the node leaves the tree. If you remove a node from its parent, Godot does not free it. ::: ## Expect Godot errors If an engine test does not consume a Godot error or warning, the test fails. This also applies to exceptions that Godot catches in C# callbacks. If a test triggers an error on purpose, consume the error and check its text. `Expect` consumes every pending report, and each report must contain the text: ```csharp player.Health = -1; godot.Errors.Expect("Health must not be negative"); ``` Errors from startup or deferred work between tests fail the next test on that fixture. Both integrations also check remaining reports after engine shutdown. xUnit can report late cleanup errors as collection failures. Tests without a 2dog fixture are not checked. ::: details Opting out of automatic error checks If possible, expect the known error instead of opting out. Then unrelated errors still fail the test. | Framework | Opt out | |-----------|----------------------------------------------------------------------------------------------------------------------------------| | xUnit | Mark a test or class `[AllowGodotErrors]`, or set `false` in the test project | | NUnit | Mark a test or class `[AllowGodotErrors]`, or override `FailOnGodotErrors` to return `false` for the whole fixture | `AllowGodotErrors` clears reports after the allowed test. If you disable automatic checks for the whole project or fixture, the reports stay in the log, so you can inspect them. ::: ## Run locally or in CI ```bash dotnet test MyGame.xunit dotnet test MyGame.nunit -c Release dotnet test MyGame.xunit --filter-class "*SceneTests" dotnet test MyGame.nunit --output Detailed ``` `dotnet test` with no project runs every test host; see [Test runners](/testing/#test-runners). For filters and output options, see [xUnit's](https://xunit.net/docs/getting-started/v3/microsoft-testing-platform) and [NUnit's](https://docs.nunit.org/articles/vs-test-adapter/NUnit-And-Microsoft-Test-Platform.html) MTP documentation. Resource import runs automatically when the test project builds. To use Editor APIs, add `-c Editor`. Then check your [xUnit](/configuration/xunit) or [NUnit](/configuration/nunit) settings. Use a headless fixture in CI; it needs no display or audio device: ```yaml - name: Run tests run: dotnet test MyGame.xunit --configuration Release ``` --- page: Simulating Input section: Testing source: https://2dog.dev/testing/input.md description: Click Godot controls and send keyboard events in xUnit and NUnit tests. --- # Simulating input Use the fixture's `Input` helper to interact with your game through Godot's input system. The examples use `godot`; in NUnit, add `var godot = EngineFixture;` inside your test. ## Click a control Run one engine frame to update the layout. Click the control. Then check the result: ```csharp godot.Engine.Update(); using var pressed = GodotAssert.ExpectSignal(playButton, BaseButton.SignalName.Pressed); godot.Input.Click(playButton); pressed.AssertEmitted(); ``` `Click(control)` aims at the control's center in its owning viewport. Godot handles hit testing and event consumption, so an overlay or input handler can block the click. The helper sends motion, press, and release events; it does not advance frames. | Aim at | Call | |------------------------------|---------------------------------------------------------------------| | A point within a control | `godot.Input.Click(playButton, localPosition: new Vector2(10, 10))` | | A point in the root viewport | `godot.Input.Click(new Vector2(80, 40))` | | A point in another viewport | `godot.Input.Click(new Vector2(80, 40), viewport: gameViewport)` | ::: tip Give headless UI tests enough room In headless mode, the root window can start at its minimum size. This can make layout and hit testing incorrect. Before you load your scene, set the root window to the project's viewport size. Then run one engine iteration to update the layout: ```csharp var root = godot.Tree.Root; var originalSize = root.Size; try { root.Size = new Vector2I( ProjectSettings.GetSettingWithOverride("display/window/size/viewport_width").AsInt32(), ProjectSettings.GetSettingWithOverride("display/window/size/viewport_height").AsInt32()); // Add your scene here. godot.Engine.Update(); // Click controls and assert the results. } finally { root.Size = originalSize; } ``` The fixture is shared, so remove your scene and restore the size afterwards. For isolated controls, use a `SubViewport` with an explicit size: ```csharp var viewport = new SubViewport { Size = new Vector2I(400, 300) }; try { godot.Tree.Root.AddChild(viewport); // Add your controls here, then advance a frame before clicking. } finally { viewport.Free(); } ``` ::: ::: details What can block a click? Godot handles the click normally, so each of these can block it: - Foreground controls - Mouse filters - Clipping - Hidden or disabled controls - Paused processing - Viewport input suppression An `_Input` handler can consume the event before the GUI receives it. To confirm that the click arrived, check a signal or a state change. The control overload sends the click directly to the viewport that owns the control. To test an outer overlay or `SubViewportContainer` forwarding, click a point in the outer viewport's coordinates. To test desktop window focus or OS event delivery, write a windowed integration test. ::: ## Send keyboard or custom events `PushToViewport` sends an `InputEvent` in viewport coordinates. Keyboard events go to the control that has focus. To test how a control gets focus, first click it or press Tab until it has focus. ```csharp using var down = new InputEventKey { Keycode = Key.Space, Pressed = true }; using var up = new InputEventKey { Keycode = Key.Space, Pressed = false }; try { godot.Input.PushToViewport(down, gameViewport); } finally { godot.Input.PushToViewport(up, gameViewport); } ``` Construct touch, drag, or wheel events the same way. Dispose custom events that your test creates. ::: details Hover in a standalone SubViewport For a manually managed `SubViewport`, call `viewport.NotifyMouseEntered()` when the pointer enters and `NotifyMouseExited()` when it leaves. Windows and `SubViewportContainer` normally handle these notifications during forwarding; the helper does not send them with every click. ::: ## Test code that polls Input Choose the delivery method based on what your game reads: | Method | Delivery | Updates global `Godot.Input` state? | |----------------------------|-----------------------------------------|-------------------------------------| | `Click` / `PushToViewport` | Local viewport routing | No | | `Send` | Godot's input server and window routing | Yes | If your game polls `IsKeyPressed`, `IsMouseButtonPressed`, or input actions, use `Send`: ```csharp using var down = new InputEventKey { Keycode = Key.F13, Pressed = true }; using var up = new InputEventKey { Keycode = Key.F13, Pressed = false }; try { godot.Input.Send(down); // Godot.Input.IsKeyPressed(Key.F13) is now true. godot.Engine.Update(); // Let game code react. } finally { godot.Input.Send(up); } ``` ## Spoof mouse movement Send a mouse-motion event to test hover, `_Input`, or code that polls the root viewport's mouse position: ```csharp using var motion = new InputEventMouseMotion { Position = new Vector2(80, 40), GlobalPosition = new Vector2(80, 40), Relative = new Vector2(10, 0), ScreenRelative = new Vector2(10, 0) }; godot.Input.Send(motion); ``` `Send` uses window coordinates. If the project uses stretch scaling, convert a logical viewport point with `godot.Tree.Root.GetFinalTransform() * viewportPosition`. If the code under test uses movement deltas, for example a camera, set `Relative`. `PushToViewport(motion, viewport)` uses viewport coordinates and delivers motion and hover too. It leaves global `Input` state unchanged, including the mouse position used by the headless root's `GetMousePosition()`. A standalone `SubViewport` tracks its local mouse position from the events it receives. Neither method moves the OS cursor; mouse warping is unnecessary for these tests. ::: warning Release held input Always release keys, actions, and mouse buttons in a `finally` block. The fixture is shared between tests, so held input affects the next test. Use a new event for each press and for each release. Do not send one event through both delivery methods, because Godot then receives it twice. ::: ::: details Input-server behavior `Send` calls `Input.ParseInputEvent` and immediately flushes buffered input, including other pending events. The project's input emulation settings and window routing apply. Coordinates use Godot's device and window conventions. If a GUI control consumes the event, Godot does not clear the global polling state. See Godot's [viewport delivery](https://docs.godotengine.org/en/stable/classes/class_viewport.html#class-viewport-method-push-input) and [input server](https://docs.godotengine.org/en/stable/classes/class_input.html#class-input-method-parse-input-event) references for the underlying APIs. ::: --- page: MSBuild Configuration section: MSBuild Configuration source: https://2dog.dev/configuration.md description: Shared 2dog settings, package versions, and configuration references for each host. --- # MSBuild Configuration Set host properties inside a `` in the host's `.csproj`. Set shared settings in the root [`Directory.Build.props`](https://learn.microsoft.com/visualstudio/msbuild/customize-by-directory). ## Global Properties {#properties} | Property | Default | Purpose | | --- | --- | --- | | `GodotProjectDir` | None | Directory containing `project.godot`; enables resource import and is embedded for `Engine.ResolveProjectDir()` | | `TwoDogVariant` | `debug` in Debug, `editor` in Editor, `release` otherwise | Desktop/Android/iOS native variant; explicit settings override the default. Android and iOS do not support `editor`; browser hosts use `TwoDogWebVariant` | | `TwoDogRemoveDuplicateGodotAnalyzers` | `false` | Removes duplicate analyzers from a host that also references a `Godot.NET.Sdk` game project | For a host nested directly inside the Godot project: ```xml .. true ``` The build resolves `GodotProjectDir` relative to the project file and embeds it as an absolute path. The game project keeps its Godot source generator; only the host should remove duplicate analyzers. Blazor clients are nested an extra level and normally use `../..`. See [Build Variants](./build-configurations) for native selection and [Resource Import](./import-tool) for import controls, including `TwoDogAutoImport`, `TwoDogRequireImport`, and `GodotEditor`. ## Packages and Versions Package versions begin with the embedded Godot version. Reference exact versions. Projects that 2dog sets up keep them in one block of the root `Directory.Build.props`: ```xml 4.7.2.139 4.7.2.52 4.7.2 ``` Hosts reference those properties: ```xml ``` `TwoDogAspNetCoreVersion` is a floating version on the patch level (`major.minor.*`). Each restore of a Blazor host takes the newest ASP.NET Core servicing release and its security fixes. [`2dog update`](/cli/update) rewrites the version block and the game project's `Godot.NET.Sdk` version, which cannot come from a property. [`2dog doctor`](/cli/doctor) reports remaining literal versions and mismatched Godot lines. `2dog.engine` references the desktop meta packages `2dog.win-x64`, `2dog.linux-x64`, and `2dog.osx-arm64`. Their targets select the native library for the build or publish platform. Each pins the release, debug, and editor packages. The build copies the selected library as `libgodot-.dll`, `.so`, or `.dylib`. `2dog.browser-wasm` pins both release and debug browser natives. ## Desktop Publishing These properties apply to the generic, Avalonia, WinForms and WinUI hosts: | Property | Default | Purpose | |-----------------------------|---------------------------|-----------------------------------------------------------------------------------------------------------------| | `TwoDogExportPack` | `true` | Export game content to a `.pck` next to the executable; `false` skips export, so supply game content separately | | `TwoDogDesktopExportPreset` | RID-mapped | Preset in `export_presets.cfg`: `Windows Desktop`, `Linux`, or `macOS`, according to the publish target | | `TwoDogEmbedPack` | `false` | Ship the `.pck` [inside the app](#single-file): in the executable, or a `.app` bundle on macOS | | `PublishSelfContained` | `true` in generated hosts | Bundle the .NET runtime; `false` requires an installed runtime | | `PublishSingleFile` | `false` | Pack the .NET runtime and your C# code into the [executable](#single-file) | | `PublishAot` | `false` | Compile the host and managed game code into a [native executable](#native-aot) | | `PublishReadyToRun` | `false` | Precompile the engine and host assemblies for [faster startup](#readytorun) | Generated desktop hosts build as `WinExe`, which hides the Windows console. The generic host does this only in Release. To keep console output, set `OutputType=Exe`. Linux and macOS treat `WinExe` as `Exe`. ## Native AOT Generic, Avalonia, and WinUI hosts support `PublishAot=true` with debug or release natives. The editor variant loads GodotTools at runtime and cannot use Native AOT. WinForms does not support trimming or Native AOT. Desktop Native AOT requires the platform's native toolchain: - Windows: Visual Studio C++ build tools - Linux: `clang` and `zlib1g-dev` - macOS: Xcode command-line tools Publish on the operating system that you target. See the [.NET prerequisites](https://learn.microsoft.com/dotnet/core/deploying/native-aot/#prerequisites). Native AOT trims the app. See [Trimming](#trimming) for assemblies that your code uses only through reflection. Android has [experimental Native AOT](./configuration/android#native-aot) with NDK requirements and different trimmer roots. Browser hosts use Mono's AOT compiler through `RunAOTCompilation`, described in their host references. ## Single-file A [single-file](https://learn.microsoft.com/dotnet/core/deploying/single-file/overview) publish packs the .NET runtime and all of your C# code into the executable. The generic, Avalonia, WinForms and WinUI hosts support it. Add `TwoDogEmbedPack` to put the game pack inside the app as well: ```bash dotnet publish MyGame.2dog -c Release -r win-x64 \ -p:PublishSingleFile=true -p:TwoDogEmbedPack=true ``` The publish folder then holds: | File | Contents | |--------------------------|-------------------------------------------------------------------| | `MyGame.2dog.exe` | The .NET runtime, your code and the game pack | | `libgodot-release.dll` | The Godot engine, with the graphics libraries it loads beside it | | `licenses` | License texts of those libraries; ship them with your game | | GDExtension libraries | Only if your game uses GDExtensions; Godot loads them from here | Copy the whole folder. Linux has the same layout, with `.so` libraries. On macOS you get an app bundle, described below. ### Pack Inside the App `TwoDogEmbedPack=true` moves the game pack (`MyGame.2dog.pck`) into the app: - **Windows and Linux:** into the executable. - **macOS:** the publish creates `MyGame.2dog.app`. The executable and the libraries go to `Contents/MacOS`, the pack goes to `Contents/Resources`. `ApplicationId` sets the bundle identifier; the default is `com.example.MyGame.2dog`. It also works for folder publishes and for [Native AOT](#native-aot). ### Sign After You Publish Sign the executable or the `.app` as the last step, with [SignTool](https://learn.microsoft.com/dotnet/framework/tools/signtool-exe) on Windows or [codesign](https://developer.apple.com/documentation/security/notarizing-macos-software-before-distribution) on macOS. On Windows, the signature also covers the embedded pack. 2dog does not embed a pack into an executable that is already signed, because that would break the signature. ### Pack the whole Kitten Kaboodle ```bash dotnet publish MyGame.2dog -c Release -r win-x64 \ -p:PublishSingleFile=true -p:TwoDogEmbedPack=true -p: IncludeNativeLibrariesForSelfExtract=true ``` `IncludeNativeLibrariesForSelfExtract=true` also packs the Godot libraries and your GDExtension libraries into the executable. Your game is then one executable and the `licenses` folder. It is off by default, because: - On first start, .NET unpacks the libraries, about 110 MB on Windows plus your GDExtensions, to `%TEMP%\.net\MyGame.2dog\` on Windows or `~/.net/MyGame.2dog/` on Linux and macOS. Later starts reuse them. - Each update with new code unpacks again, into a new folder. Nothing deletes the old folders. If a folder is deleted, the next start unpacks it again. - .NET does not check the unpacked files after that. Another program that runs as the same user could change them, and signing the executable does not prevent this. - Some virus scanners distrust programs that unpack libraries and load them. - Only native libraries move. Data files that Godot exports with a GDExtension stay next to the executable. If a GDExtension looks for a library next to the executable instead of next to itself, it fails; test each GDExtension that ships extra libraries. See [native libraries](https://learn.microsoft.com/dotnet/core/deploying/single-file/overview#native-libraries) for how .NET handles them. ### Limits - WinUI always unpacks all of its files on first start, because the Windows App SDK requires that for single-file apps. - The editor variant cannot publish as a single file, because it loads Godot's editor tools from disk. - REPL hosts cannot publish as a single file; see [REPL publishing](/configuration/repl#publishing). ## ReadyToRun Generic, Avalonia, WinForms, and WinUI hosts support [ReadyToRun](https://learn.microsoft.com/dotnet/core/deploying/ready-to-run) with a runtime identifier: ```bash dotnet publish MyGame.2dog -c Release -r win-x64 -p:PublishReadyToRun=true ``` It precompiles the host assembly and the engine assemblies `GodotSharp` and `twodog`. The JIT still compiles game scripts, because Godot loads the game assembly from a stream. Browser and Android hosts run on Mono, which has no ReadyToRun; use `RunAOTCompilation` there. ## Trimming Native AOT, browser, Blazor and Android publishes are trimmed. 2dog keeps `GodotSharp`, `twodog`, the host and your game assembly whole. If your code uses another assembly only through reflection, add it as a trimmer root: ```xml ``` --- page: Generic .NET Configuration section: Console Configuration source: https://2dog.dev/configuration/generic.md description: MSBuild settings for the generic .NET host: console behavior, desktop pack export, and Native AOT. --- # Generic .NET Configuration Set properties in `MyGame.2dog.csproj`, which references `2dog.engine`. The [global settings](/configuration#properties) control the game directory, resource import, analyzers, and native variant. [Desktop publishing](/configuration#desktop-publishing) covers the pack, self-contained, single-file, Native AOT and ReadyToRun properties. ```bash dotnet publish MyGame.2dog -c Release -r win-x64 -p:PublishAot=true ``` See the [generic host guide](/hosts/generic) for running the host and passing Godot arguments. --- page: REPL Configuration section: Console Configuration source: https://2dog.dev/configuration/repl.md description: Configure REPL native variants, headless runs, local connections and publishing. --- # REPL Configuration `MyGame.repl/` references your game, `2dog.repl` and `2dog.engine`. For double precision, use `2dog.engine.double`. Submissions use the bindings that the host has loaded. The [global settings](/configuration#properties) also apply. Debug (the default), Editor and Release select the matching [native variant](/build-configurations). ```bash dotnet run --project MyGame.repl -c Editor dotnet run --project MyGame.repl -- --headless ``` The Editor configuration uses the editor natives, but it does not open the Godot editor. Arguments after `--` go to Godot, except the connection options below. ## Local connections Start a host, then attach from another terminal with [`2dog repl`](/cli/repl): ```bash dotnet run --project MyGame.repl -- --repl-listen mygame ``` ```bash 2dog repl --pipe mygame ``` | Host option | Behavior | |-----------------------|-------------------------------------------------------------------------| | `--repl-listen NAME` | Accept sessions over a local named pipe; keep running after stdin ends. | | `--repl-connect NAME` | Connect to that host without starting another engine. | Use only one of these options at a time. `--repl-connect` accepts no Godot arguments and times out after five seconds. Custom hosts use the `ReplHost.Run(args, createEngine, references)` overload. Each connection has its own variables, node selection, queue and scratch world. All connections share the engine thread and the scene tree. Interactive clients have completion and multiline editing; redirected I/O uses plain UTF-8 text. `NO_COLOR` disables terminal colors. `:cancel` cancels a connection's work. EOF or `:exit` waits for submitted work and disconnects; an abrupt disconnect cancels work and closes its worlds. Local `:exit` or `tree.Quit()` stops the listening host. Pipes accept only the current OS user. A connected client can run any code in the host. ## Publishing The REPL compiles C# at runtime. `PublishAot`, `PublishTrimmed` and `PublishSingleFile` must be `false`: ```bash dotnet publish MyGame.repl -c Release ``` --- page: Avalonia Configuration section: Desktop Configuration source: https://2dog.dev/configuration/avalonia.md description: MSBuild settings for Avalonia hosts: shared engine settings, desktop pack export, and Native AOT. --- # Avalonia Configuration Set properties in `MyGame.avalonia.csproj`. Reference `2dog.avalonia` and explicitly select `2dog.engine` or `2dog.engine.double`. [Global settings](/configuration#properties) control the game directory, resource import, analyzers, and native variant. [Desktop publishing](/configuration#desktop-publishing) covers the pack, self-contained, single-file, Native AOT and ReadyToRun properties. ```bash dotnet publish MyGame.avalonia -c Release -r win-x64 -p:PublishAot=true ``` Keep the generated app manifest for correct DPI scaling. See the [Avalonia host guide](/hosts/avalonia) for UI setup and platform requirements. --- page: Qt Quick Configuration section: Desktop Configuration source: https://2dog.dev/configuration/qt.md description: MSBuild and session settings for Qt Quick hosts: Qt location, the TwoDog QML module, and presentation. --- # Qt Quick Configuration Set properties in `MyGame.qt.csproj`. Reference `2dog.qt` and explicitly select `2dog.engine` or `2dog.engine.double`. Also reference the [Qt Bridge for C#](https://github.com/qt/qtbridge-csharp) package for the build platform, `QtGroup.Qt.Bridge.CSharp.`. [Global settings](/configuration#properties) control the game directory, resource import, analyzers, and native variant. ## Qt Location `QtDir` is the Qt 6 prefix, the directory containing `lib/cmake/Qt6/Qt6Config.cmake`. The `win-x64` Qt Bridge package sets it to its bundled Qt. On Linux and macOS, pass it to every build: ```bash dotnet build MyGame.qt -p:QtDir=/path/to/qt6 ``` The [Qt Bridge documentation](https://github.com/qt/qtbridge-csharp) describes the settings of Qt Bridge, such as its export mode and resource packaging. ## The TwoDog QML Module `GodotView` lives in the `TwoDog` QML module, which `2dog.qt` ships as C++ source. The build compiles it with CMake and Ninja against the Qt in `QtDir`, in the host's `obj` directory. Then the build copies it to `TwoDog/` next to the app, where QML finds it. It rebuilds when its sources or `QtDir` change. On Windows, the build uses the newest Visual Studio installation with C++ tools. Do not use a very long path for the NuGet package folder, because the paths of Qt's CMake files can then exceed the 260-character path limit of Windows. The default `%USERPROFILE%\.nuget\packages` is short enough. ## Session Properties Set startup properties when you create the `QtGodotSession`: | Property | Default | Purpose | |--------------------|---------|---------------------------------------------------------------------------------------------| | `PresentationMode` | `Auto` | Prefer GPU texture sharing with CPU fallback; `Gpu` requires sharing, `Cpu` forces readback | | `ActiveMode` | `null` | Read-only: `Gpu` or `Cpu` while frames are presented | | `IsPaused` | `false` | Gameplay pause; set on the GUI thread, or before startup to start paused | ```csharp using var session = new QtGodotSession("MyGame", args: args) { PresentationMode = QtPresentationMode.Cpu, }; ``` On Windows, GPU sharing needs the Direct3D 11 backend of Qt Quick, which is the default backend there. The session makes the engine render on the same GPU as Qt. CPU readback can reduce the frame rate at larger window sizes. ## Publishing Publishing the Qt Quick host is untested, and Native AOT is unsupported. See the [Qt Quick host guide](/hosts/qt) for setup, threading and licensing. --- page: WinForms Configuration section: Desktop Configuration source: https://2dog.dev/configuration/winforms.md description: MSBuild settings for WinForms hosts: Windows targeting, pack export, runtime bundling, and trimming limitations. --- # WinForms Configuration Set properties in `MyGame.winforms.csproj`, which references `2dog.engine`. The generated host targets `net10.0-windows` with `UseWindowsForms=true`. [Global settings](/configuration#properties) control the game directory, resource import, analyzers, and native variant. [Desktop publishing](/configuration#desktop-publishing) covers the pack, self-contained, single-file and ReadyToRun properties. ```bash dotnet publish MyGame.winforms -c Release -r win-x64 ``` WinForms does not support trimming or Native AOT; ReadyToRun works. Keep the generated `ApplicationHighDpiMode=SystemAware` and app manifest so WinForms and Godot use the same DPI mode. See the [WinForms host guide](/hosts/winforms) for window embedding and UI behavior. --- page: WinUI 3 Configuration section: Desktop Configuration source: https://2dog.dev/configuration/winui.md description: MSBuild settings for WinUI 3 hosts: desktop packs, self-contained Windows App SDK deployment, and Native AOT. --- # WinUI 3 Configuration Set properties in `MyGame.winui.csproj`, which references `2dog.engine` and `Microsoft.WindowsAppSDK`. Build and publish on Windows. [Global settings](/configuration#properties) control the game directory, resource import, analyzers, and native variant. [Desktop publishing](/configuration#desktop-publishing) covers the pack, self-contained, single-file, Native AOT and ReadyToRun properties. The generated host is a `win-x64`, unpackaged and [self-contained](https://learn.microsoft.com/windows/apps/package-and-deploy/self-contained-deploy/deploy-self-contained-apps) Windows App SDK app. ```bash dotnet publish MyGame.winui -c Release -p:PublishAot=true ``` See the [WinUI host guide](/hosts/winui) for window embedding and UI behavior. --- page: Blazor Configuration section: Web Configuration source: https://2dog.dev/configuration/blazor.md description: MSBuild configuration for the Blazor WebAssembly client: game packs, native symbols, memory, and SDK-managed compression. --- # Blazor Configuration Set 2dog properties in `MyGame.blazor/Client/MyGame.blazor.Client.csproj`. The client references `2dog.engine`, `2dog.blazor`, and `2dog.browser-wasm`. It normally sets `GodotProjectDir=../..`; see the [global settings](/configuration#properties). ## Client Properties | Property | Default | Purpose | |------------------------------|----------------------------------------------------|--------------------------------------------------------------------------------------------------------| | `TwoDogWebVariant` | Follows the [configuration](/build-configurations) | Native engine variant: `debug` or `release` | | `TwoDogExportPack` | `true` | Export the game pack as a static web asset; `false` uses an existing `wwwroot/godot.pck` in the client | | `TwoDogWebExportPreset` | `Web` | Preset in `export_presets.cfg` | | `TwoDogWebPackName` | `godot.pck` | Exported pack name; if you change it, set the same name in `GodotView.PackUrl` | | `TwoDogWebSideModuleExports` | `true` | Export native symbols used by GDExtensions | | `WasmEmitSymbolMap` | `false` in Release, `true` otherwise | Include native symbols for stack traces; explicit settings override the default | | `WasmInitialHeapSize` | `256MB` | Initial memory allocation; memory can grow | `2dog.browser-wasm` restores both supported native variants. If the payload for the selected variant is missing, the build fails. The client SDK manages its own bundle, progress, source maps, and compression. `TwoDogWebSizeManifest`, `TwoDogWebStripMaps`, and `TwoDogWebPrecompress` apply only to Web and WebXR hosts. ## Publishing, Trimming, and AOT Publish the server project to include its client: ```bash dotnet publish MyGame.blazor -c Release -p:RunAOTCompilation=true ``` `RunAOTCompilation` enables Mono's browser AOT compiler. See [Trimming](/configuration#trimming) for assemblies accessed only through reflection. Keep the attributes on the client's generated `2dog.engine` and browser package references. See the [Blazor host guide](/hosts/blazor) for `GodotView`. ## Experimental WebGPU Set `RenderingDriver="webgpu"` on `GodotView` to select WebGPU Mobile. The default remains OpenGL/WebGL Compatibility. `--rendering-driver webgpu` in `Args` also selects WebGPU when `RenderingDriver` is unset. WebGPU requires the single-precision, single-threaded browser payload, a secure context and a browser that supports WebGPU. Other payloads fail before 2dog creates a device. Later views reuse the same device. If initialization is cancelled, 2dog releases a device that no view has claimed. See [build configurations](/build-configurations#experimental-webgpu). --- page: Web Configuration section: Web Configuration source: https://2dog.dev/configuration/web.md description: MSBuild properties for browser-wasm hosts: native variants, game packs, symbols, compression, and Mono AOT. --- # Web Configuration Set properties in `MyGame.web.csproj`, which references `2dog.engine` and `2dog.browser-wasm`. The generated host uses `RuntimeIdentifier=browser-wasm` and defaults to Release; pass `-c Debug` for a Debug build. See [global settings](/configuration#properties) for the game directory and import controls. ## Properties | Property | Default | Purpose | |-------------------------------------|----------------------------------------------------|------------------------------------------------------------------------------------------------------------------| | `TwoDogWebVariant` | Follows the [configuration](/build-configurations) | Native engine variant: `debug` or `release` | | `TwoDogExportPack` | `true` | Export game content; `false` uses an existing `wwwroot/godot.pck` | | `TwoDogWebExportPreset` | `Web` | Preset in `export_presets.cfg` | | `TwoDogWebPackName` | `godot.pck` | Deployed pack name | | `TwoDogWebSizeManifest` | `true` | Write `twodog.sizes.json` for loading progress | | `TwoDogWebStripMaps` | `true` with the release engine | Remove JavaScript source maps | | `TwoDogWebPrecompress` | `true` with the dotnet CLI | Write Brotli (`.br`) and gzip (`.gz`) copies of large bundle files | | `TwoDogWebPrecompressLevel` | `Optimal` | `Optimal` (balance speed and size), `Fastest`, `SmallestSize` (slower), or `NoCompression`; case-insensitive | | `TwoDogWebPrecompressFormats` | `br;gz` | Compressed copies to write: `br`, `gz`, or `br;gz` | | `TwoDogWebPrecompressKeepOriginals` | `true` | `false` removes the uncompressed engine, pack and .NET binaries; needs `gz` ([upload limits](#web-hosting-with-upload-limits)) | | `TwoDogWebSideModuleExports` | `true` | Export native symbols used by GDExtensions | | `WasmEnableThreads` | `false` | Link the [multithreaded](/hosts/web#multithreading) runtime and engine (experimental) | | `TwoDogWebSideModuleTableSlots` | `16384` | With threads: function table slots reserved for GDExtension side modules | | `WasmEmitSymbolMap` | `false` in Release, `true` otherwise | Include native symbols for stack traces; explicit settings override the default | | `WasmInitialHeapSize` | `256MB` | Initial memory allocation; memory can grow | | `TwoDogWebGpuCachePath` | System temporary directory | Writable Emscripten port cache for WebGPU | `2dog.browser-wasm` restores both debug and release natives. If the payload for the selected variant is missing, the build fails. If you change `TwoDogWebPackName`, change the pack URL in your boot page to match. ## Experimental WebGPU Single-precision, single-threaded browser packages include WebGPU and WebGL. During bundling, `renderer/rendering_method.web="mobile"` in `project.godot` selects WebGPU when the native payload includes it. Other settings select WebGL. The showcase uses Mobile. A new template project uses Compatibility by default. To request WebGPU explicitly, configure `GODOT_CONFIG` in `wwwroot/index.html` before you create `Engine`: ```javascript 'renderingDriver': 'webgpu', 'args': ['--rendering-driver', 'webgpu', '--rendering-method', 'mobile'], ``` The shell requests a device before it initializes Godot. If one of these conditions occurs, the shell logs a warning and starts with `opengl3` and `gl_compatibility`, with the same runtime and game pack: - WebGPU is unavailable. - No adapter is available. - The adapter or device request fails. This fallback happens before startup; it does not recover from later device loss or native renderer failures. Game content must also work with Compatibility. To always use Compatibility, set `renderer/rendering_method.web` to `"gl_compatibility"`, or override both command-line options: ```javascript 'renderingDriver': 'opengl3', 'args': ['--rendering-driver', 'opengl3', '--rendering-method', 'gl_compatibility'], ``` The `opengl3` driver uses WebGL 2 in browsers (OpenGL ES 3.0), not WebGL 3. WebGPU requires a secure context and browser support. Threaded and double-precision payloads reject explicit WebGPU requests before device creation. The package supplies the Emscripten port, link flags and writable cache. See [build configurations](/build-configurations#experimental-webgpu) for limits. ## Precompression The dotnet CLI writes Brotli (`.br`) and gzip (`.gz`) copies next to the large bundle files. Visual Studio's MSBuild skips this step. Choose the settings that match your web server: | Your server | Settings | |-----------------------------------------------|-------------------------------------------------------------------------------------------| | Serves precompressed files | Defaults, or `TwoDogWebPrecompressFormats` set to the one format it serves (`br` or `gz`) | | Compresses responses itself | `TwoDogWebPrecompress=false` | | Compresses nothing | Defaults, plus [compression in the page](#compression-in-the-page) | | Compresses nothing and limits the upload size | [gzip only](#web-hosting-with-upload-limits) | Servers that serve precompressed files include [Caddy](https://caddyserver.com/docs/caddyfile/directives/file_server) with `precompressed`, and nginx with [`gzip_static`](https://nginx.org/en/docs/http/ngx_http_gzip_static_module.html) or [`brotli_static`](https://github.com/google/ngx_brotli). They send a copy in place of the original when the browser accepts its format, so keep the originals for browsers that do not. [dotnet-serve](https://github.com/natemcmaster/dotnet-serve) with `-b -z` compresses responses itself and ignores the copies. A publish removes copies of formats you no longer select, and with `TwoDogWebPrecompress=false` it removes all of them, so a server never sends an outdated copy. ### Compression in the page On a server that compresses nothing, the page can download the `.gz` copies and inflate them with the browser's built-in gzip decoder, [`DecompressionStream`](https://developer.mozilla.org/docs/Web/API/DecompressionStream). Chrome does not support Brotli there, so the page uses gzip. Set this in `wwwroot/index.html`: ```javascript const TWODOG_PCK_GZ = true; ``` The page then requests `godot.wasm.gz`, `godot.pck.gz` and the `.gz` copies of the .NET binaries in `_framework/`. Scripts and the page itself still load uncompressed. If a `.gz` copy is missing, the page falls back to the original. Do not use this on a server that compresses responses, because the files would be compressed twice. ## Web hosting with upload limits The uncompressed bundle, engine and all, of an empty project is about 70 MiB, plus 10-15 MiB per compressed variant of the Godot engine. If your web hosting service limits how much you can upload, ship only the gzip copies of the engine, the pack and the .NET binaries. That's about 18-20 MiB for an empty game. ```xml gz false ``` ::: warning A minimum-size deploy is not a smaller game download This deploy fits web hosting services that *limit how much you can upload.* It does not make your game smaller for players. Instead, it trades their download experience for upload size. Compared with a full deploy, players: - download gzip, which is larger than Brotli - cannot load the game in browsers without [`DecompressionStream`](https://developer.mozilla.org/docs/Web/API/DecompressionStream), because there are no originals to fall back to Players get the best experience from a full deploy with all variants (Brotli, gzip and the uncompressed originals), where the server sends each browser the best format it accepts. ::: The page has nothing to fall back to, so the publish turns on [compression in the page](#compression-in-the-page) in the bundle's copy of `index.html`. Your `wwwroot/index.html` stays unchanged. The build fails if `gz` is not among the formats. ## Trimming and AOT See [Trimming](/configuration#trimming) for assemblies accessed only through reflection. Browser hosts run on Mono. Enable Mono's [AOT compiler](https://learn.microsoft.com/aspnet/core/blazor/webassembly-build-tools-and-aot) in the host's csproj instead of `PublishAot`. This setting applies to `dotnet publish`: ```xml true ``` `-p:RunAOTCompilation=true` sets it for a single publish. See the [Web host guide](/hosts/web) for serving and deploying `AppBundle/`. --- page: WebXR Configuration section: Web Configuration source: https://2dog.dev/configuration/webxr.md description: MSBuild configuration for WebXR hosts, using the Web host's browser engine, pack, compression, and AOT settings. --- # WebXR Configuration Set properties in `MyGame.webxr.csproj`. WebXR uses the same [browser build properties](./web#properties), [precompression](./web#precompression), and [trimming and Mono AOT settings](./web#trimming-and-aot) as the Web host. The generated host defaults to Release. ```bash dotnet publish MyGame.webxr -c Release -p:RunAOTCompilation=true ``` WebXR has no MSBuild properties of its own. XR shaders and scene setup belong to the Godot project; see the [WebXR host guide](/hosts/webxr). --- page: Android Configuration section: Mobile Configuration source: https://2dog.dev/configuration/android.md description: MSBuild configuration for experimental Android hosts: native variants, game packs, signing, trimming, and Native AOT. --- # Android Configuration Set properties in `MyGame.android.csproj`. The generated host references `2dog.engine`, `2dog.android`, and the native meta package for its selected `android-arm64` or `android-x64` RID. See [global settings](/configuration#properties) for the game directory and import controls. ## Properties | Property | Default | Purpose | |-----------------------------|----------------------------------------------------|---------------------------------------------------------------------------------------| | `TwoDogVariant` | Follows the [configuration](/build-configurations) | Native engine variant: `debug` or `release` | | `TwoDogAndroidGameAssembly` | Set by the generated host | Game assembly that Godot loads; kept whole by the trimmer | | `TwoDogAndroidExportPreset` | `Android` | Preset in `export_presets.cfg` | | `TwoDogAndroidPack` | Exported by the build | Use an existing game pack instead of exporting one | | `TwoDogAndroidGdExtensions` | `true` | Package the Android libraries of the project's GDExtensions | | `TwoDogAndroidSigning` | `true` | Sign using .NET signing properties, Godot's keystore variables, or the debug keystore | | `AndroidPackageFormats` | `apk` in the generated host | `apk` for sideloading, `aab` for app stores | Android exports its pack during the build and puts it inside the APK/AAB; `TwoDogExportPack` does not apply. See the [Android host guide](/hosts/android#build-and-install) for SDK setup and installation. ## Signing When you publish, the build signs the app with the first keystore that it finds: 1. The .NET `AndroidKeyStore` and `AndroidSigning*` properties 2. Godot's `GODOT_ANDROID_KEYSTORE_RELEASE_PATH`, `_USER`, and `_PASSWORD` [environment variables](/configuration/environment-variables#net-and-android-tooling) (the `DEBUG` variables for Debug builds). Set all three variables together. 3. Android's debug keystore, `~/.android/debug.keystore` 4. The .NET debug keystore ## Trimming and Mono AOT Release builds use .NET for Android's default trimming and AOT; see its [build properties](https://learn.microsoft.com/dotnet/android/building-apps/build-properties). 2dog preserves `GodotSharp`, `GodotPlugins`, `twodog`, and the game assembly. If your code uses another assembly only through reflection, add it as a trimmer root: ```xml ``` ## Native AOT .NET for Android's Native AOT is experimental. It supports 2dog's debug and release variants and needs the Android NDK: ```bash dotnet publish MyGame.android -c Release -p:PublishAot=true -p:AndroidNdkDirectory=$ANDROID_HOME/ndk/29.0.14206865 ``` Without `PublishAot`, Release builds use Mono's AOT compiler through `RunAOTCompilation`. --- page: iOS Configuration section: Mobile Configuration source: https://2dog.dev/configuration/ios.md description: MSBuild configuration for experimental iOS hosts: native variants, game packs, signing, trimming, and the interpreter. --- # iOS Configuration Set properties in `MyGame.ios.csproj`. The generated host references `2dog.engine`, `2dog.ios`, and the native meta package for its selected `ios-arm64` or `iossimulator-arm64` RID. See [global settings](/configuration#properties) for the game directory and import controls. ## Properties | Property | Default | Purpose | | --- | --- | --- | | `TwoDogVariant` | `debug` in Debug, `editor` in Editor, `release` otherwise | Native engine variant; explicit settings override the default. The `editor` variant is unsupported | | `TwoDogIosGameAssembly` | Set by the generated host | Game assembly kept whole by the trimmer | | `TwoDogIosExportPreset` | `iOS` | Preset in `export_presets.cfg` | | `TwoDogIosPack` | Exported by the build | Use an existing game pack instead of exporting one | | `TwoDogIosGdExtensions` | `true` | Link the static libraries or xcframework slices the project's `.gdextension` files list for iOS | | `UseInterpreter` | `true` in Debug | Run the Mono interpreter so no code path needs a JIT; Release builds AOT-compile | | `RuntimeIdentifier` | `ios-arm64` in the generated host | `iossimulator-arm64` for the Simulator on Apple silicon | | `SupportedOSPlatformVersion` | `15.0` in the generated host | Minimum iOS version; the Simulator runtime must also meet the installed workload's requirements | | `CodesignKey`, `CodesignProvision` | Unset | Signing identity and provisioning profile for device builds | iOS exports its pack during the build and bundles it as `game.pck`. The iOS targets disable `TwoDogExportPack`, because iOS does not use the desktop publish export. To use an existing pack, set `TwoDogIosPack`. The targets also set `TwoDogSkipGodotApiCopy`, because iOS has no `GodotPlugins.dll`. Instead, the game's generated initializer registers its scripts through `IosHost`. The build links the engine archive with `ForceLoad` and the Swift runtime, because Godot registers classes from static initializers and carries a SwiftUI wrapper. Both native variants restore. If the payload for the selected variant is missing, the build fails with `TDGI003`. Other RIDs fail with `TDGI005`. ## Trimming and AOT Release builds are trimmed and AOT-compiled, like any .NET for iOS app. 2dog preserves `GodotSharp`, `twodog`, `twodog.ios`, and the game assembly. If your code uses another assembly only through reflection, add it as a trimmer root: ```xml ``` The build rejects Native AOT (`PublishAot`) with `TDGI004`, because iOS hosts run on Mono. See the [iOS host guide](/hosts/ios) for building, the Simulator and signing. --- page: MAUI Configuration section: Mobile Configuration source: https://2dog.dev/configuration/maui.md description: Configure MAUI targets, GodotView rendering and publishing. --- # MAUI Configuration Set build properties in `MyGame.maui.csproj`; package versions come from the root `Directory.Build.props`. The setup pins the .NET 10 SDK in the host's `global.json` and in a new root `global.json`. It does not change an existing root `global.json`. To use the host's pin, run commands from `MyGame.maui/`. ## Platform Properties | Property | Default | Purpose | | --- | --- | --- | | `TargetFrameworks` | `net10.0-android`, plus `net10.0-windows10.0.19041.0` on Windows and `net10.0-ios` on a Mac | Select one with `-f`; without `-f`, `dotnet run` asks you to choose | | `GodotProjectDir` | `..` | Game directory for import and pack export | | `RuntimeIdentifier` | `win-x64` on Windows, `android-arm64` on Android, `ios-arm64` on iOS | Native architecture; Android also supports `android-x64`, iOS `iossimulator-arm64` | | `TwoDogVariant` | `debug` in Debug, `release` otherwise | Native engine variant; select it with the Debug or Release configuration | | `WindowsPackageType` | `None` | Unpackaged Windows app | | `WindowsAppSDKSelfContained` | `true` on Windows | Bundle the Windows App SDK runtime | | `PublishSelfContained` | `true` on Windows | Bundle the .NET runtime | | `TwoDogAndroidGameAssembly` | Game assembly name | Assembly extracted before engine startup | | `TwoDogIosGameAssembly` | Game assembly name | Assembly kept whole by the trimmer on iOS | | `AndroidPackageFormats` | `apk` | Use `aab` for store distribution | Windows builds are [unpackaged and self-contained](https://learn.microsoft.com/dotnet/maui/windows/deployment/publish-unpackaged-cli). Android uses the [Android host's export, signing and trimming settings](/configuration/android), and iOS uses the [iOS host's settings](/configuration/ios). Both use Mono AOT in Release. The MAUI host does not support Native AOT. ## View Properties Set startup properties before you add `GodotView` to a page: | Property | Default | Purpose | |--------------------------|-----------------------|-----------------------------------------------------------------------------------------------| | `Project` | Required | Game assembly name | | `ProjectPath` | `null` | Windows project directory; published games use their pack | | `Arguments` | Empty | Godot startup arguments | | `PresentationMode` | `Auto` | Windows: prefer GPU sharing, with CPU fallback; `Gpu` requires sharing, `Cpu` forces readback | | `ActivePresentationMode` | `null` before startup | Read-only active GPU or CPU mode | Windows GPU mode defaults to the Vulkan mobile renderer. `Arguments` can override it. In Auto mode, the Compatibility renderer uses CPU presentation. CPU readback can reduce frame rate at larger resolutions. Android always uses Godot's native GPU surface. ## Publish ```bash dotnet publish MyGame.maui -f net10.0-windows10.0.19041.0 -c Release dotnet publish MyGame.maui -f net10.0-android -c Release -r android-arm64 dotnet publish MyGame.maui -f net10.0-ios -c Release -r ios-arm64 -p:CodesignKey="Apple Development" ``` See the [MAUI host guide](/hosts/maui) for setup, lifecycle and `Dispatch`. --- page: GdUnit Configuration section: Testing Configuration source: https://2dog.dev/configuration/gdunit.md description: Configure GdUnit host packages, private resource roots, execution limits and artifacts. --- # GdUnit Configuration The [GdUnit host](/hosts/gdunit) uses the .NET 10 SDK and MTP runner. Reference `2dog.gdunit` at the same 2dog version as the game. For a double-precision game, choose `2dog.gdunit.double`. The package includes a pinned official GdUnit runtime. It also brings the engine and editor-tool dependencies that private resource imports need. You do not need a GdUnit checkout or a separate addon installation. Debug, Release and Editor select the matching [native variant](/build-configurations). The host always runs headlessly. The host project disables the normal game import and pack export. Discovery and execution import their own private stages. ## Create the host From the directory containing `project.godot`, run: ```bash dnx 2dog add --gdunit ``` Use `--gdunit MyGame.scripts` to choose a custom host folder. For a new project, run `dnx 2dog new MyGame --gdunit`. The template accepts `--gdunit true`. The tool creates this layout, including a sample `tests/example_test.gd`: ```text MyGame.gdunit/ .gdignore global.json MyGame.gdunit.csproj Program.cs tests/ ``` Leave `.gdignore` empty. It keeps the host and its test resources out of the game's normal import and export. When the host runs, it imports a private copy. ## Project and entry point For manual setup, create `MyGame.gdunit/MyGame.gdunit.csproj`: ```xml Exe net10.0 enable enable MyGame.GdUnit .. true ``` For a C# game, add a `ProjectReference` to `../MyGame.csproj` so its built assembly and dependencies reach the host output. Exclude `MyGame.gdunit/**` from the game's C# compile glob. Add `Program.cs`: ```csharp using System.Reflection; using Microsoft.Testing.Platform.Builder; using MyGame.GdUnit; using twodog.GdUnit; var project = Assembly.GetExecutingAssembly() .GetCustomAttributes() .Single(attribute => attribute.Key == "GodotProject").Value!; var builder = await TestApplication.CreateBuilderAsync(args); builder.AddSelfRegisteredExtensions(args); builder.AddGdUnit(new GdUnitOptions { ProjectPath = project, TestRoots = ["MyGame.gdunit/tests"] }); using var application = await builder.BuildAsync(); return await application.RunAsync(); ``` `AddSelfRegisteredExtensions` is generated in the project's `RootNamespace`; `AddGdUnit` registers the GDScript test framework. ## Select the runner Besides the root `global.json` described in [Test runners](/testing/#test-runners), the setup puts this one inside `MyGame.gdunit/`: ```json { "sdk": { "version": "10.0.100", "rollForward": "latestFeature" }, "test": { "runner": "Microsoft.Testing.Platform" } } ``` With the root file in place, run commands from the project root: ```bash dotnet test MyGame.gdunit dotnet test --project MyGame.gdunit --list-tests ``` If your root `global.json` does not select MTP, run from `MyGame.gdunit/` instead. ## Execution options Set these properties on `GdUnitOptions` in `Program.cs`: | Property | Default | Purpose | |----------------------------|----------------------------------|----------------------------------------| | `ProjectPath` | Required | Directory containing `project.godot` | | `TestRoots` | `tests` | Directories relative to the game | | `ArtifactsDirectory` | Host output `TestResults/gdunit` | Parent of fresh stages and logs | | `ProjectAssemblyDirectory` | Host output | Built game assemblies and dependencies | | `ImportTimeout` | Two minutes | Limit for the separate import process | | `SessionTimeout` | Five minutes | Time limit for one embedded session | For tests under a nested host, include that host in each root, for example `MyGame.gdunit/tests`. Roots must remain inside the source game. A root can be in a directory that the game ignores, such as `MyGame.gdunit/`. Nested `.gdignore` trees inside a root stay excluded. Linked files and directories are unsupported. Timeouts must be positive. For a mixed C#/GDScript game, build the game through a `ProjectReference` from the host. If you stage the game assemblies and dependencies in a different directory, set `ProjectAssemblyDirectory` to it. Otherwise, keep the host output. ## Artifacts Each request creates a fresh subdirectory beneath `ArtifactsDirectory`: | Artifact | Contents | |---------------|-----------------------------------------------------------------| | `project/` | Writable game copy, selected tests, pinned framework and bridge | | `import.log` | Resource import helper output | | `godot.log` | Embedded engine's native log | | `events.json` | Serialized discovery, lifecycle and result events | Reports from GdUnit's session hooks live under `project/twodog_test_host/reports/`. The host retains artifacts for inspection. For a deeply nested project on Windows, choose a short artifact path. If you do not, native directory operations can exceed the maximum path length of Windows. For assertions, suites and other framework settings, use [GdUnit's official documentation](https://godot-gdunit-labs.github.io/gdUnit4/latest/). See [Testing with GdUnit](/testing/gdunit) for the host's execution differences. --- page: NUnit Configuration section: Testing Configuration source: https://2dog.dev/configuration/nunit.md description: Configure NUnit hosts, engine variants, Editor bindings and headless fixtures. --- # NUnit Configuration Set properties in `MyGame.nunit/MyGame.nunit.csproj`. Reference `2dog.nunit` and explicitly select `2dog.engine` or `2dog.engine.double`. The setup also references `NUnit`, `NUnit3TestAdapter` and `Microsoft.Testing.Extensions.CodeCoverage` for `dotnet test` and coverage collection. The [global settings](/configuration#properties) control the game directory, resource import, analyzers and native variant. Debug, Release, and Editor select the matching [native variant](/build-configurations). For tests that use Editor APIs: ```bash dotnet test MyGame.nunit -c Editor ``` The generated Editor configuration defines `EDITOR` and references `2dog.godotsharp.editor`. A test project that you create manually needs this managed reference to compile Editor API calls. The editor native variant alone does not add it. See [Testing with NUnit](/testing/nunit) for fixture setup and runner rules, and [Writing engine tests](/testing/writing-tests) for waits and assertions. ## Microsoft.Testing.Platform The host runs on the official NUnit adapter's MTP bridge. Projects that 2dog sets up reference `NUnit3TestAdapter` 6.x for MTP v2 and set these properties: ```xml Exe true ``` Run it from the project root; see [Test runners](/testing/#test-runners) for the `global.json` that selects MTP. ```bash dotnet test MyGame.nunit dotnet test --project MyGame.nunit --coverage ``` Coverage reports go to `TestResults/`. To move an older VSTest host to MTP, add the properties above and update `NUnit3TestAdapter` to 6.x. For the other steps, see Microsoft's [migration guide](https://learn.microsoft.com/dotnet/core/testing/migrating-vstest-microsoft-testing-platform). 2dog leaves discovery, filters and results to NUnit's bridge and keeps Godot setup, tests and teardown on NUnit's fixture thread. See [NUnit's official MTP documentation](https://docs.nunit.org/articles/vs-test-adapter/NUnit-And-Microsoft-Test-Platform.html) for runner options. ### Cancellation NUnit's adapter does not signal the test's cancellation token when the session is cancelled. Its forced stop also skips fixture teardown. To cancel tests reliably: - Use NUnit's [`[CancelAfter]`](https://docs.nunit.org/articles/nunit/writing-tests/attributes/cancelafter.html). - Observe its token in async work and frame waits. - Keep that timeout below any outer CI timeout. --- page: xUnit Configuration section: Testing Configuration source: https://2dog.dev/configuration/xunit.md description: MSBuild configuration for xUnit hosts: engine variants, resource import, and Editor API references. --- # xUnit Configuration Set properties in `MyGame.xunit/MyGame.xunit.csproj`. Reference `2dog.xunit` and explicitly select `2dog.engine` or `2dog.engine.double`. `2dog.xunit` compiles its collection definitions into your test project. The [global settings](/configuration#properties) control the game directory, resource import, analyzers, and native variant. Debug, Release, and Editor select the matching [native variant](/build-configurations). For tests that use Editor APIs: ```bash dotnet test MyGame.xunit -c Editor ``` The generated Editor configuration also defines `EDITOR` and references `2dog.godotsharp.editor`. A test project that you create manually needs this managed reference to compile Editor API calls. The editor native variant alone does not add it. Tests use the game directory and its imported resource cache. See [Resource Import](/import-tool) to require or force an import, and [Testing with xUnit](/testing/xunit) for fixture and runner settings. ## Microsoft.Testing.Platform The host uses xUnit's own runner on MTP v2. Projects that 2dog sets up reference `xunit.v3.mtp-v2` and `Microsoft.Testing.Extensions.CodeCoverage`, and set these properties: ```xml Exe true ``` Run it from the project root; see [Test runners](/testing/#test-runners) for the `global.json` that selects MTP. ```bash dotnet test MyGame.xunit dotnet test --project MyGame.xunit --coverage --coverage-output-format cobertura ``` Coverage reports go to `TestResults/`. To move an older VSTest host to MTP, add the properties above and replace `xunit.v3` with `xunit.v3.mtp-v2`. For the other steps, see Microsoft's [migration guide](https://learn.microsoft.com/dotnet/core/testing/migrating-vstest-microsoft-testing-platform). 2dog delegates discovery, filters, result reporting and cancellation to xUnit. The 2dog entry point keeps Godot on its owner thread. On macOS, that thread is the process main thread. See [xUnit's official MTP documentation](https://xunit.net/docs/getting-started/v3/microsoft-testing-platform) for runner options and IDE support. --- page: Known Issues section: Known Issues source: https://2dog.dev/known-issues/index.md description: Current 2dog limitations and their workarounds: one Godot instance per load context, xUnit discovery crashes with Godot types, GD.Print visibility in tests, and spaced project names breaking publish. --- # Known Issues 2dog is early-stage software. These pages track current limitations and their workarounds. ## Current Issues - **[Single Godot Instance](./single-instance)**: one instance per assembly load context at a time; sequential restart is supported and isolated concurrent hosting is experimental - **[xUnit Test Discovery](./xunit-discovery)**: Godot types in `[MemberData]` crash discovery because the engine has not started yet at that point - **[GD.Print in Tests](./gd-print-output)**: `GD.Print` output is hidden by default - **[Spaced Project Names](./spaced-project-names)**: whitespace in the game's .NET name makes `dotnet publish` silently drop its NuGet dependencies, because of a .NET SDK bug. `2dog add --rename` fixes it If you find an issue that is not listed here, please [open an issue on GitHub](https://github.com/outfox/2dog/issues). --- page: Single Godot Instance section: Known Issues source: https://2dog.dev/known-issues/single-instance.md description: Only one Godot instance may run per assembly load context in 2dog; sequential restart is supported and isolated concurrent hosting is experimental. --- # Single Godot Instance Per Load Context Only one Godot instance may run in an assembly load context at a time. Starting a second instance throws `InvalidOperationException`: ```csharp using var engine1 = new Engine("MyGame"); engine1.Start(); using var engine2 = new Engine("MyGame"); engine2.Start(); // Throws InvalidOperationException. ``` Sequential restart is supported, also in the browser. The [Blazor host](/hosts/blazor) restarts on a fresh canvas, and web hosts keep the runtime alive by default (`Engine.WebExitRuntimeOnQuit = false`). Any host can start a new engine after `Completion` completes and `Exited` fires. `Engine` owns its instance; the `GodotInstance` returned by `Start()` is a borrowed compatibility handle. On desktop, dispose the current engine before creating another: ```csharp using (var engine = new Engine("MyGame")) { engine.Start(); engine.Run(); } // Synchronous teardown. using var nextEngine = new Engine("MyGame"); nextEngine.Start(); ``` In a browser, teardown is asynchronous. Before you create the new `Engine`, call `RequestQuit()` while `Run()` pumps frames, then await `Completion`. You can also call `DisposeAsync()` instead. This allows xUnit collections to use fresh engines sequentially in one test process with the same display mode. Keep headless and rendering fixtures in separate test projects. A mode change across native restarts can crash Godot, so the fixtures reject it. Collections must disable parallelization. See [Testing](/testing/). Before shutdown returns, it releases every Godot object that the engine created. This includes wrappers that the garbage collector dropped but did not finalize yet. As a result, nothing from one engine is released into the next. You do not need to force a garbage collection between engines. A restart does not reload the game assembly. Static fields keep their values across engines, but the previous engine disposed every Godot object that it created when it shut down. As a result, a static that holds a Godot object throws `ObjectDisposedException` in the next engine. Examples are a `Resource` such as `FastNoiseLite`, a `Node` or a `PackedScene`. Create such objects per instance (in `_Ready`, or lazily behind `GodotObject.IsInstanceValid`), or reset them from `Exited`: ```csharp private static FastNoiseLite? _noise; private static FastNoiseLite Noise => GodotObject.IsInstanceValid(_noise) ? _noise : _noise = new FastNoiseLite(); ``` ## Concurrent engines The repository contains experimental `twodog.hosting*` projects and [hosting demos](https://github.com/outfox/2dog/tree/main/demos) for concurrent desktop engines. They are not published NuGet packages: each engine needs its own assembly load context and physical native library copy. CWD, environment variables and signal handlers remain process-global; macOS hosting is unsupported. --- page: xUnit Test Discovery Crash section: Known Issues source: https://2dog.dev/known-issues/xunit-discovery.md description: Godot values in xUnit MemberData can crash the test host during discovery - the affected Godot types and the workarounds. --- # xUnit Test Discovery Crash with Godot Types Godot values in xUnit `[MemberData]` can crash the test host during discovery ([issue #16](https://github.com/outfox/2dog/issues/16)): ```csharp public static IEnumerable Paths = [[new NodePath("/root")]]; [Theory] [MemberData(nameof(Paths))] public void CanLogNodePath(NodePath path) { GD.Print(path); } ``` xUnit enumerates member data before fixtures run. At that point, the engine has not started yet, so constructors that call Godot native code fail. ## Workarounds ### Workaround 1: Disable Discovery Enumeration Set `DisableDiscoveryEnumeration = true` so that xUnit enumerates the member data only when the tests run: ```csharp [Theory] [MemberData(nameof(Paths), DisableDiscoveryEnumeration = true)] public void CanLogNodePath(NodePath path) { GD.Print(path); } ``` The test explorer then shows one theory entry, not one entry for each data row. ### Workaround 2: Use Primitive Values If you need one test explorer entry for each row, pass primitive values. Then create the Godot value inside the test, after its fixture has started: ```csharp public static IEnumerable Paths = [["/root"], ["/root/Main"]]; [Theory] [MemberData(nameof(Paths))] public void CanLogNodePath(string value) { var path = new NodePath(value); GD.Print(path); } ``` Prefer this workaround. ## Affected Types Any GodotSharp type whose constructor calls native code is affected: `NodePath`, `StringName`, `Rid`, `GodotObject` subclasses, and some math value types. Primitive C# values such as strings, numbers, booleans, and their arrays are safe for discovery. --- page: GD.Print in Tests section: Known Issues source: https://2dog.dev/known-issues/gd-print-output.md description: Why GD.Print output is hidden during dotnet test, how to make it visible, and why ITestOutputHelper is the recommended pattern. --- # GD.Print Output Not Visible in Tests `GD.Print` prints nothing under `dotnet test`. The output exists; the runner hides it. ## Why This Happens `GD.Print` writes to stdout through Godot's native `OS::print()`. `dotnet test` hides test-host stdout by default. The stream is global, so the output also mixes with engine and fixture logs and does not belong to a specific test. ## Making GD.Print Visible Test hosts that 2dog sets up run on Microsoft.Testing.Platform (MTP), so each one is an executable. Run it directly to see its stdout in the terminal: ```bash dotnet run --project MyGame.xunit ``` Your messages appear in the test-host stream, mixed with engine and fixture logs. ## Use ITestOutputHelper instead To write output from a test, use xUnit's `ITestOutputHelper`: ```csharp [Collection] public class MyTests(HeadlessFixture godot, ITestOutputHelper output) { [Fact] public void MyTest() { var node = godot.Tree.Root; // Instead of: GD.Print(node.GetPath()); output.WriteLine(node.GetPath()); } } ``` The output then belongs to that test. It appears in the failure report and in the IDE test explorer, without engine logs around it. To watch it while tests run, pass xUnit's `--show-live-output on`: ```bash dotnet test --project MyGame.xunit --show-live-output on ``` `GD.Print` stays what it is, a runtime debugging aid. --- page: Spaced Project Names section: Known Issues source: https://2dog.dev/known-issues/spaced-project-names.md description: Whitespace in the game project's .NET name makes dotnet publish silently drop its NuGet dependencies from 2dog hosts - an upstream .NET SDK bug 2dog guards against. --- # Project Names With Spaces Break Publish A .NET SDK bug makes `dotnet publish` silently drop a *referenced* project's NuGet packages when that project's name contains a space. Every 2dog web publish triggers it. The name comes from `PackageId`, `AssemblyName` or the project file name, so all of them must be space-free. The symptom is that the publish output contains the game's own DLL, but not its NuGet packages. Every package that the game references, such as a JSON library or a logging framework, is missing from `AppBundle/_framework`. The app then crashes at startup with this error: ```text System.IO.FileNotFoundException: Could not load file or assembly 'Newtonsoft.Json, Version=13.0.0.0, ...' ``` Godot often creates such names. For a project named "Fast Dragon", Godot generates `Fast Dragon.csproj` with a matching assembly name. Stock Godot is not affected, because its export publishes the game as the *root* project. 2dog hosts reference the game, which is exactly the broken case. ## What 2dog does `2dog add` does not set up hosts for a name that contains whitespace. It offers to fix the name, either interactively or with `2dog add --rename NewName`. The rename changes only the .NET identity: the csproj file, `[dotnet] project/assembly_name` in `project.godot`, and the solution reference. Godot's display name (`config/name`) keeps its spaces. `2dog new` never creates such names. It removes spaces and shows a notice. ## Fixing an affected project by hand 1. Close the Godot editor. 2. Rename `Fast Dragon.csproj` to `FastDragon.csproj`. 3. Set `project/assembly_name="FastDragon"` in `project.godot`'s `[dotnet]` section. 4. Point the solution's entry at the new csproj name. 5. In each existing host csproj, update the `ProjectReference`, any `TrimmerRootAssembly`, and the `RootNamespace` that carry the old name. Another workaround is to add the game's packages directly to the host csproj, because the SDK never excludes direct references. But then you must keep these references in sync with the game's packages by hand, so prefer the rename. --- page: FAQ section: Optional source: https://2dog.dev/faq.md description: Frequently asked questions about 2dog: how it differs from godot-dotnet, GodotSharp compatibility, separate tool and engine packages, and frame rate caps in the browser. --- # FAQ ## How is 2dog different from godot-dotnet? They solve opposite problems: - **[godot-dotnet](https://github.com/godotengine/godot-dotnet)** puts .NET in Godot through a new bindings layer for native extensions. - **2dog** puts Godot in .NET: your application hosts the engine as a library. 2dog still uses GodotSharp. It changes only the process structure: **Classic Godot with C# (godot-mono):** ``` godot-mono <- the engine process drives everything ├── loads GodotSharp │ └── runs your code └── loads GDExtensions ``` **2dog:** ``` your .NET application <- your process drives the engine └── loads libgodot ├── loads GodotSharp └── loads GDExtensions ``` ### When to use which Use godot-dotnet to write native extensions in .NET. Use 2dog to do these things: - Embed or package Godot in a .NET application. - Run tests with `dotnet test`. - Add .NET testing and tooling around an existing game. ## Will 2dog use godot-dotnet in the future? Probably. 2dog will change when godot-dotnet can replace GodotSharp in practice. In mid-2026, godot-dotnet has only early basics and not the full capabilities of GodotSharp. For this reason, 2dog stays on GodotSharp. ## Is 2dog a replacement for GodotSharp? No. 2dog embeds GodotSharp and Godot's C# source generators. `GD`, `Node`, `[Export]`, signals, and other C# APIs work as usual. Only the owner of the process changes. ## Why is the library a separate package (`2dog.engine`) instead of part of `2dog`? A dotnet tool package cannot also be a `PackageReference` ([`NU1213`](https://learn.microsoft.com/nuget/reference/errors-and-warnings/nu1213)). Therefore `2dog.engine` is the library, `2dog` is the tool, and `2dog.templates` provides the `dotnet new` template. We release all three packages together. The tool and the `dotnet new` template produce the same output. See [`2dog add`](/cli/add). ## Why does the browser build never exceed my display's refresh rate? Browser hosts run Godot's frame loop on `requestAnimationFrame`, which the browser calls once per display refresh. Godot's vsync setting has no effect on the web, and `application/run/max_fps` can only lower the rate. The browser also composites the canvas once per refresh, so extra frames would never reach the screen. To measure uncapped performance, remove the browser's own limit: | Browser | Setting | Notes | |--------------|---------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------| | Chrome, Edge | Start with `--disable-frame-rate-limit --disable-gpu-vsync` | Close every browser window first, or pass a separate `--user-data-dir` | | Firefox | Set `layout.frame_rate` to `0` in `about:config` | `-1` (default) follows vsync, `0` renders as fast as possible, any other value is a fixed rate | | Safari | Turn off Develop > Feature Flags > "Prefer Page Rendering Updates near 60fps", then restart | Raises the 60 fps cap to the display rate, such as 120 Hz on ProMotion screens; there is no uncapped mode | ```bash chrome --disable-frame-rate-limit --disable-gpu-vsync --user-data-dir=/tmp/2dog-bench http://localhost:8080 ``` Use these for benchmarks only, and restore the defaults afterwards: uncapped rendering keeps the CPU and GPU busy and drains batteries. To see the Safari setting, first enable "Show features for web developers" in Safari's Advanced settings. --- Do you have a question that this page does not answer? Ask on [Discord](https://discord.gg/GAXdbZCNGT) or [open an issue on GitHub](https://github.com/outfox/2dog/issues). --- page: Troubleshooting section: Optional source: https://2dog.dev/troubleshooting.md description: Learn what 2dog's exit codes and errors mean, how the tool behaves in CI and pipes, and how to fix restore, build and runtime problems. --- # Troubleshooting What's up, ~~dog~~ doc? ![Illus. Diego Rago: a white anthro dog in a hacker hoodie faceplanting in front of their robot dog](img/2dog-trouble.png) For most problems, the first step is the same: run [`2dog doctor`](/cli/doctor) in the project. It checks the machine and the project, and it applies the fixes that are safe. It also explains build failures that it recognizes. To build the project and explain the failures, run `2dog doctor --build`. For a log that you already have, run `2dog doctor --log `. ## Exit codes | Code | Meaning | What to do | |-------|-------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `1` | Usage error | The `error:` line names the problem and suggests the closest option or verb; `2dog --help` lists what the verb accepts | | `2` | Tool error | Read the `error:` and `hint:` lines. They name the cause: the project state (no `project.godot`, a name with spaces, invalid XML), a failed `dotnet` call, or a plan that stopped half-way. To see the stack trace and every subprocess line, add `--verbose` | | `3` | Doctor findings remain, or the `--build` failed | Read the report. To apply the safe fixes, add `--fix`. To ignore a finding that you accept, add `--ignore `. After `--build`, the build section names the matched failure and the full log | | `130` | Cancelled | Ctrl+C during a prompt or a subprocess; nothing after the cancelled step ran | ## A plan stopped half-way `2dog new`, `add` and `update` apply their steps in order with no rollback. If a step fails, the tool reports `error: step N/M failed: ...`. Then a `note:` lists the completed steps and the number of steps that did not run. Fix the cause, for example a read-only file or a Godot editor that holds a csproj open. Then run the same command again. The tool skips existing files; to overwrite them, add `--force`. ## No terminal, CI, pipes If stdout is not a terminal, or if the tool runs in [CI](/configuration/environment-variables#cli-output-and-automation), the tool never prompts. If you run `new` or `add` without host flags, they apply the default host set and print `note: no terminal to ask on`. To make that choice explicit, pass `--yes` or name the hosts. `doctor` prints the fix commands instead of asking. Output for scripts and pipes: - Markers use ASCII characters. - `--json` writes one document to stdout. - `--plain` and `NO_COLOR` turn styling off. - `CLICOLOR_FORCE` keeps color in a pipe. ## `dotnet restore` failed The tool prints the restore failure, explains recognized errors and returns exit code `2`. Files already written are kept. Common causes: - **`NETSDK1147` / wasm-tools**: the browser hosts need the `wasm-tools` workload. Run `dotnet workload install wasm-tools`. - **`NETSDK1147` / android**: Android hosts need the `android` workload. Run `dotnet workload install android`. To build or publish for Android, you also need the Android SDK and JDK 17. - **`NU1101`/`NU1102`, a `2dog.*` package version not found**: the version that the project pins is not published yet, or your `nuget.config` hides nuget.org. To pin the versions that the running tool ships, run `2dog update`. - **`NU1213`**: a project references the `2dog` tool package; reference `2dog.engine` instead. After you fix the cause, run `dotnet restore` again. `2dog doctor --log` also explains a saved restore log, in the same way as a build log. ## "dotnet not found" The tool cannot find the `dotnet` executable. Add the folder that contains `dotnet` to `PATH`, or set `DOTNET_HOST_PATH` to the full path of the `dotnet` executable. ## Godot cannot load `System.Runtime` (NixOS and pinned .NET runtimes) After `2dog add`, the stock Godot editor may report: ```text System.IO.FileNotFoundException: Could not load file or assembly 'System.Runtime, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. .NET: Failed to load project assembly. ``` `2dog add` upgrades the game project to `net10.0`, which the 2dog binding packages require. If the editor loads an older .NET runtime, it cannot load that assembly even when compilation succeeds. Even if `dnx 2dog` runs and `dotnet --list-sdks` shows .NET 10, the editor can use an older runtime. The editor loads its runtime separately from the build SDK. ### NixOS Nixpkgs' `godot-mono` wrapper selects its own .NET SDK, which can be older than the one in your shell. Replace the existing `pkgs.godot-mono` entry in your NixOS `environment.systemPackages` or Home Manager `home.packages` with: ```nix (pkgs.godot-mono.overrideAttrs { dotnet-sdk = pkgs.dotnet-sdk_10; }) ``` 1. Apply your Nix configuration. 2. Close Godot fully. An editor that is already running keeps its loaded runtime. 3. Open the project with the updated package. 4. Build the project again. If you only install .NET 10 next to the default Godot package, the wrapper still selects its own SDK. See the [Nixpkgs Godot wrapper](https://github.com/NixOS/nixpkgs/blob/master/pkgs/development/tools/godot/common.nix) for the SDK override. ### Other systems and later .NET versions The same issue can occur with any launcher, package or environment that pins Godot to an older .NET runtime. Check which SDK and runtime the launcher selects, including `DOTNET_ROOT` and `PATH`. Configure the launcher to use a runtime that is compatible with the game's target framework. After you change the launcher, fully restart the editor. Keep the game on the target framework required by its 2dog packages. Godot 4.8 moves to .NET 10, so this mismatch is less common for `net10.0` projects. But the problem can occur again when a game targets a later .NET version than the runtime that the editor selects. ## Mixed Godot bindings (TDG001-TDG005) At build time, 2dog replaces the stock GodotSharp assemblies with its own. `2dog update` sets this up in an existing game; `2dog add` and new projects do it already. | Code | Cause | Fix | |----------|-----------------------------------------------------|-----------------------------------------------------------------------------------------------| | `TDG001` | A dependency targets a different Godot version | Change the dependency, or `Godot.NET.Sdk`, to the engine's Godot version | | `TDG002` | `GodotSharpEditor` without `2dog.godotsharp.editor` | Reference `2dog.godotsharp.editor` at the engine version; remove stock packages and HintPaths | | `TDG003` | A referenced GodotSharp assembly is not 2dog's | Remove the conflicting reference or HintPath and rebuild against `2dog.godotsharp` | | `TDG004` | Core and editor binding packages differ in version | Use one 2dog version for all of them | | `TDG005` | An editor export build found no stock GodotSharp | Build the game with `Godot.NET.Sdk` | ## The Godot editor holds a file If the tool reports `could not rename ...` or `the file is in use`, another program has locked a project file. On Windows, the Godot editor locks the game csproj and its build outputs while the project is open. Close the Godot editor and any IDE that has the project open. Then run the command again.