Porting a project from Unity to Godot as a solo developer introduced a need I hadn’t encountered before:
How do I retain the same project architecture and utility layer, while respecting the different debug output conventions of each engine?
Rather than littering the codebase with #if UNITY or #if GODOT directives or duplicating systems, I created a simple debug abstraction to keep my game code engine-agnostic.
The Goal
As part of my effort to reuse as much code as possible across engines, I wanted to:
- Preserve the same GameDebug.Log(), 
GameDebug.Warning(), andGameDebug.Error()calls from the Unity project. - Swap out the underlying implementation depending on the engine.
 - Keep engine-specific code decoupled from game systems and shared utilities.
 
The Solution
I created a debug routing layer using a simple interface-based approach:
- A pure C# static class 
GameDebugused by all systems. - An interface 
IDebugWrapperrepresenting the debug message handler. - An engine-specific implementation (
GodotDebugin this case) hooked into Godot’sGD.Print(),GD.PushWarning(), etc. 
IDebugWrapper Interface
public interface IDebugWrapper  
{  
    string Name { get; }  
    void Log(string message);  
    void LogWarning(string message);  
    void LogError(string message);  
}
This interface defines the core debug methods used by GameDebug, allowing any backend to plug in.
GameDebug Static Class
public static partial class GameDebug  
{  
    private static List<IDebugWrapper> debugWrappers = new List<IDebugWrapper>();  
    
    public static void Log(string message)  
    {       
        foreach (var wrapper in debugWrappers)  
            wrapper.Log(message);  
    }    
    
    public static void LogWarning(string message)  
    {       
        foreach (var wrapper in debugWrappers)  
          wrapper.LogWarning(message);  
    }    
    
    public static void LogError(string message)  
    {       
        foreach (var wrapper in debugWrappers)  
          wrapper.LogError(message);  
    }  
   
    public static void RegisterDebugWrapper(IDebugWrapper wrapper)  
    {       
        if (!debugWrappers.Contains(wrapper))  
        {          
            debugWrappers.Add(wrapper);  
            Log($"Registered: {wrapper.Name}.");  
        }       
        else  
        {  
            LogError($"{wrapper.Name} already registered.");  
        }
    }
}
This class serves as the entry point for all debug messages in the game code. It’s engine-agnostic and delegates to the underlying implementation.
GodotDebug Implementation
using Godot; 
  
public partial class GodotDebug : Node, IDebugWrapper  
{  
    // Called when the node enters the scene tree for the first time.  
    public override void _Ready()  
    {       
        GameDebug.RegisterDebugWrapper(this);  
    }  
    
    public new string Name => "Godot Debug";  
  
    public void Log(string message)  
    {       
        GD.Print(message);  
    }  
    public void LogWarning(string message)  
    {      
        GD.PushWarning($"WARNING: {message}");  
    }  
    public void LogError(string message)  
    {      
        GD.PushError($"ERROR: {message}");  
    }
}
In Godot, this class binds GameDebug to Godot’s GD.Print(), GD.PrintErr(), etc., ensuring native behavior for debugging and logging.
Why This Matters
This wrapper gives me:
- Consistent code between Unity and Godot projects.
 - Engine independence in my core systems and utilities.
 - Simple, swappable logging behavior that can be extended or suppressed (e.g., during tests).
 
It’s a small but powerful architectural pattern that supports clean layering, long-term reuse, and ease of porting all of which are essential when working solo or across multiple engines.