- Setup Dependencies
- Setup Project
- Injection and Logging in Nodes
- Scene Manager
- Registering Service Factories
- Unique Node Bindings
- Components
- Binding a Node to another class
Utility library for implement state of the art application development in Godot 4. Includes:
-
Using .NET Standards:
- Logging Setup using ILogger interface (Microsoft.Extensions.Logging)
- Dependency Injection (Microsoft.Extensions.DependencyInjection)
-
General utilities for Godot:
- Node Extension Methods
Add project reference to your Godot *.csproj project:
<Project Sdk="Godot.NET.Sdk/4.0.2">
<!-- ... -->
<ItemGroup>
<PackageReference Include="Hobart2967.GoDough" Version="1.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.0" />
<!-- ... -->
<Project Sdk="Godot.NET.Sdk/4.0.2">
<!-- ... -->
<ItemGroup>
<ProjectReference Include="../../GoDough/GoDough.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.0" />
</ItemGroup>
-
Create an autoload script in Godot
- Visit
Project
Menu, thenProject Settings..
- Select
Autoload Tab
- Add a new script here.
- Visit
-
Create specialized
AppHost
class which is for tailoring to your needs:using Godot; using GoDough.Composition.Extensions; using GoDough.Runtime; using GoDough.Runtime.LivecycleHooks; using Microsoft.Extensions.DependencyInjection; public class GwenAppHost : AppHost { #region Ctor public GwenAppHost(Node autoLoadNode) : base(autoLoadNode) { } #endregion #region Public Methods public override void ConfigureServices(IServiceCollection services) { base.ConfigureServices(services); services .AddSingleton<MyService>() .AddSingleton<IOnProcess, ServiceDoingSomethingOnProcess>() // Called on each _Process .AddSingleton<IBootstrapper, BootstrapperService>(); // Called on Startup (_Ready) } #endregion }
-
Call AppHost from your AutoloadScript
using Godot; public partial class AppHostLoader : Node { #region Properties public GwenAppHost AppHost { get; private set; } #endregion #region Ctor public AppHostLoader() { this.AppHost = new GwenAppHost(this); } #endregion #region Public Methods public override void _Ready() { this.AppHost.Start(); } public override void _Process(double delta) { this.AppHost.Process(delta); } public override void _PhysicsProcess(double delta) { this.AppHost.PhysicsProcess(delta); } public override void _Input(InputEvent ev) { this.AppHost.PhysicsProcess(delta); } #endregion }
-
Enjoy!
using GoDough.Composition.Attributes;
using GoDough.Composition.Extensions;
public partial class MyNode : Node {
// Add [Inject] property to any injected service
[Inject]
public ILogger<MyNode> Logger { get; set; }
public override void _Ready() {
this.WireNode(); // Always call WireNode to make sure that the dependencies are loaded.
this.Logger.LogDebug("Node is ready.");
}
}
This will result in:
[07.05.2023 18:14:34] [Debug] [MyNode] Node is ready.
-
Create a Scenes Enum with all your scene names.
public enum Scenes { MainMenu, Game, Settings }
-
Add a Bootstrapper to register your scenes on startup
public class SceneRegistrationBootstrapper : IBootstrapper { #region Private Fields private readonly SceneManagementService<Scenes> _sceneManagementService; #endregion #region Ctor public SceneRegistrationBootstrapper( ILogger<SceneRegistrationBootstrapper> logger) => (_sceneManagementService) = (sceneManagementService); #endregion #region Public Methods public void Run() { this._sceneManagementService .RegisterSceneFile(Scenes.MainMenu, "res://scenes/main-menu/main-menu.tscn") .RegisterSceneFile(Scenes.World, "res://scenes/world/world.tscn") .RegisterSceneFile(Scenes.Settings, "res://scenes/settings/settings.tscn"); } #endregion }
See above for registering the bootstrapper.
using GoDough.Composition.Attributes;
using GoDough.Composition.Extensions;
public partial class MyNode : Node {
// Add [Inject] property to any injected service
[Inject]
public SceneManagementService<Scenes> SceneManager { get; set; }
public override void _Ready() {
this.WireNode();
this.SceneManager.LoadScene(Scenes.World);
}
}
You can always request the current scene via the CurrentScene
properties.
If you want to get notified for scene changes, you can subscribe to the OnSceneChanged
event:
this._sceneManager.OnSceneChanged += (s, e) => {
if (e.SceneKey == SceneNames.Main) {
GD.Print("Yay!");
}
};
- Call
services.AddFactory
with the Class Type to created insideAppHost.ConfigureServices
services.AddFactory<ListItem>()
- Inject the factory and use it
using GoDough.Composition.Attributes; using GoDough.Composition.Extensions; public partial class MyNode : Node { // Add [Inject] property to any injected service [Inject] public Factory<ListItem> ListItemFactory { get; set; } public override void _Ready() { this.WireNode(); // Always call WireNode to make sure that the dependencies are loaded. var listItem = this.ListItemFactory.Create(); } }
-
Declare a node as "Unique Node" inside the scene.
-
Attach script to scene
-
Inside the script, create a property with the exact same name as the unique node.
-
Add
[UniqueNode]
attribute to that property -
Inside the
_Ready
callback method, add a call tothis.BindToViewModel()
.using Godot; using GoDough.Visuals; using GoDough.Visuals.Attributes; public partial class MyPopup : Panel { [UniqueNode] public LineEdit MyTextField { get; set; } public override void _Ready() { this.BindToViewModel(); } }
-
Declare a node as "Unique Node" inside the scene.
-
Attach script to scene
-
Inside the script, create a property that should hold the desired node.
-
Add
[UniqueNode("MyUniqueNodeName")]
attribute to that property. The string should contain the name that you gave it in Godot editor. -
Inside the
_Ready
callback method, add a call tothis.BindToViewModel()
.using Godot; using GoDough.Visuals; using GoDough.Visuals.Attributes; public partial class MyPopup : Panel { [UniqueNode("MyUniqueNodeName")] public LineEdit MyTextField { get; set; } public override void _Ready() { this.BindToViewModel(); } }
Inside your IServiceCollection
configuration, add:
using GoDough.Visuals.Components;
// ...
public override void ConfigureServices(IServiceCollection services) {
//...
services
.AddComponentFactory();
}
using Godot;
using GoDough.Visuals.Attributes;
using GoDough.Visuals.Components;
using Gwen.Network.Models.Character;
namespace Gwen.Client.Scenes.CharacterList {
[SceneBinding("res://prefabs/my-component.tscn")]
public class MyComponent : Component<Camera3D> { // Make sure to set the correct root type as generic, otherwise set "Node".
[UniqueNode]
public Button MyButton { get; set; }
}
}
Inside your IServiceCollection
configuration, add:
public override void ConfigureServices(IServiceCollection services) {
services.AddTransient<MyComponent>();
}
Inject ComponentFactory
into your class, then run:
var myComponent = this.ComponentFactory.Create<CharacterComponent>();
var rootNode = characterComponent.SceneRoot; // Access Node
You can use a node to bind it to another class, accessing unique nodes using the attribute.
- Create class containing properties for child nodes with a unique name:
using Godot;
using GoDough.Visuals;
using GoDough.Visuals.Attributes;
public class ViewBindingClass {
[UniqueNode]
public LineEdit MyTextBox { get; set; }
}
- Use
BindToViewModel
extension method at a place where you have access to the desired node, while passing theViewBindingClass
instance as a parameter:
using Godot;
using GoDough.Visuals;
using GoDough.Visuals.Attributes;
public partial class MyPopup : Panel {
public override void _Ready() {
var viewBindings = new ViewBindingClass();
this.BindToViewModel(viewBindings);
viewBindings.MyTextBox.Text = "Blub";
}
}