21 KiB
Godot C# Library Management System
Overview
This document describes a comprehensive system for managing reusable C# libraries across multiple Godot game projects using Git submodules. This approach enables clean separation of concerns, version control for individual libraries, and easy code reuse across multiple game projects.
System Architecture
Core Principles
- Separation of Concerns: Libraries and game projects exist in separate Git repositories
- Modular Design: Each library serves a specific purpose and can be developed independently
- Version Control: Libraries can be versioned and updated independently of game projects
- Code Reuse: Multiple projects can share the same libraries without code duplication
- Clean Dependencies: C# project references ensure proper compilation and IntelliSense support
Directory Structure
~/GameDev/
├── Godot/ # Godot engine installation
├── libraries/ # Local development copies of libraries
│ ├── utilities/ # Example: utilities library repository
│ ├── audio-manager/ # Example: audio management library
│ └── ui-framework/ # Example: UI framework library
└── projects/ # Game project repositories
├── test-game/ # Example: test game project
│ └── Libraries/ # Git submodules directory
│ ├── utilities/ # Submodule → library/utilities
│ └── audio-manager/# Submodule → library/audio-manager
└── puzzle-game/ # Example: another game project
└── Libraries/ # Git submodules directory
└── utilities/ # Submodule → library/utilities
Git Repository Organization
Gitea Organizations:
library/- Contains all reusable library repositoriesproject/- Contains all game project repositories
Repository Examples:
https://git.ein-softworks.com/library/utilities.githttps://git.ein-softworks.com/library/audio-manager.githttps://git.ein-softworks.com/project/test-game.githttps://git.ein-softworks.com/project/puzzle-game.git
How Git Submodules Work
Git submodules allow you to include one Git repository inside another as a subdirectory. In our case:
- Library repositories contain reusable C# code and Godot scenes
- Game project repositories include libraries as submodules in their
Libraries/directory - Submodules point to specific commits in the library repositories, ensuring reproducible builds
- Updates are explicit - you control when to pull in library changes
Benefits of This Approach
- Independent Development: Work on libraries without affecting game projects
- Version Stability: Games use specific library versions until explicitly updated
- Selective Updates: Choose which libraries to update in each project
- Clean History: Each repository maintains its own commit history
- Collaborative Friendly: Multiple developers can work on different libraries simultaneously
Initial Setup Process
Prerequisites
- macOS with Godot v4.5.1 stable installed at
~/GameDev/Godot/ - .NET 8.0.121 installed
- Git configured with access to your Gitea instance
- Gitea instance with
libraryandprojectorganizations created
Step 1: Create Directory Structure
cd ~/GameDev
mkdir -p libraries projects
Step 2: Create Your First Library
Initialize the Library Repository
cd ~/GameDev/libraries
git init utilities
cd utilities
mkdir -p Scripts
Create Library Project Configuration
Create project.godot:
; Engine configuration file.
[application]
config/name="Utilities Library"
config/features=PackedStringArray("4.3", "C#", "Forward Plus")
[dotnet]
project/assembly_name="EinSoftworks.Utilities"
Create Utilities.csproj:
<Project Sdk="Godot.NET.Sdk/4.3.0">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework Condition=" '$(GodotTargetPlatform)' == 'android' ">net7.0</TargetFramework>
<TargetFramework Condition=" '$(GodotTargetPlatform)' == 'ios' ">net8.0</TargetFramework>
<EnableDynamicLoading>true</EnableDynamicLoading>
<RootNamespace>EinSoftworks.Utilities</RootNamespace>
<AssemblyName>EinSoftworks.Utilities</AssemblyName>
</PropertyGroup>
</Project>
Create Example Library Code
Create Scripts/MathUtils.cs:
using Godot;
namespace EinSoftworks.Utilities
{
public static class MathUtils
{
/// <summary>
/// Clamps a value between 0 and 1.
/// </summary>
public static float Clamp01(float value)
{
return Mathf.Clamp(value, 0f, 1f);
}
/// <summary>
/// Checks if a number is approximately equal to another number.
/// </summary>
public static bool Approximately(float a, float b, float threshold = 0.001f)
{
return Mathf.Abs(a - b) < threshold;
}
/// <summary>
/// Converts degrees to radians.
/// </summary>
public static float DegreesToRadians(float degrees)
{
return degrees * Mathf.Pi / 180f;
}
/// <summary>
/// Converts radians to degrees.
/// </summary>
public static float RadiansToDegrees(float radians)
{
return radians * 180f / Mathf.Pi;
}
}
}
Create .gitignore
# Godot 4+ specific ignores
.godot/
# Godot-specific ignores
.import/
export.cfg
export_presets.cfg
# Imported translations (automatically generated from CSV files)
*.translation
# Mono-specific ignores
.mono/
data_*/
mono_crash.*.json
# .NET specific ignores
bin/
obj/
*.tmp
Commit and Push Library
git add .
git commit -m "Initial utilities library setup with MathUtils"
git remote add origin https://git.ein-softworks.com/library/utilities.git
git branch -M main
git push -u origin main
Step 3: Create Your First Game Project
Initialize the Game Repository
cd ~/GameDev/projects
git init test-game
cd test-game
mkdir -p Scripts Scenes Libraries
Create Game Project Configuration
Create project.godot:
; Engine configuration file.
[application]
config/name="Test Game"
config/features=PackedStringArray("4.3", "C#", "Forward Plus")
run/main_scene="res://Scenes/Main.tscn"
[dotnet]
project/assembly_name="TestGame"
Create TestGame.csproj:
<Project Sdk="Godot.NET.Sdk/4.3.0">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework Condition=" '$(GodotTargetPlatform)' == 'android' ">net7.0</TargetFramework>
<TargetFramework Condition=" '$(GodotTargetPlatform)' == 'ios' ">net8.0</TargetFramework>
<EnableDynamicLoading>true</EnableDynamicLoading>
<RootNamespace>TestGame</RootNamespace>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="Libraries/utilities/Utilities.csproj" />
</ItemGroup>
</Project>
Add Library as Submodule
git submodule add https://git.ein-softworks.com/library/utilities.git Libraries/utilities
git submodule update --init --recursive
Create Game Code
Create Scripts/Main.cs:
using Godot;
using EinSoftworks.Utilities;
namespace TestGame
{
public partial class Main : Node2D
{
public override void _Ready()
{
GD.Print("Test Game Started!");
// Test the utilities library
TestMathUtils();
}
private void TestMathUtils()
{
// Test degree/radian conversion
float testAngle = 90f;
float radians = MathUtils.DegreesToRadians(testAngle);
float backToDegrees = MathUtils.RadiansToDegrees(radians);
GD.Print($"90° → {radians} rad → {backToDegrees}°");
// Test clamping
float clampedValue = MathUtils.Clamp01(1.5f);
GD.Print($"Clamped 1.5 to 0-1 range: {clampedValue}");
// Test approximation
bool isApprox = MathUtils.Approximately(0.1f, 0.100001f);
GD.Print($"0.1 ≈ 0.100001: {isApprox}");
}
}
}
Create Scenes/Main.tscn:
[gd_scene load_steps=2 format=3 uid="uid://b8y1qxqxqxqxq"]
[ext_resource type="Script" path="res://Scripts/Main.cs" id="1"]
[node name="Main" type="Node2D"]
script = ExtResource("1")
[node name="Label" type="Label" parent="."]
offset_right = 400.0
offset_bottom = 100.0
text = "Test Game - Check console for utilities library output"
horizontal_alignment = 1
vertical_alignment = 1
Create .gitignore
# Godot 4+ specific ignores
.godot/
# Godot-specific ignores
.import/
export.cfg
export_presets.cfg
# Imported translations (automatically generated from CSV files)
*.translation
# Mono-specific ignores
.mono/
data_*/
mono_crash.*.json
# .NET specific ignores
bin/
obj/
*.tmp
Commit and Push Game Project
git add .
git commit -m "Initial test game setup with utilities library submodule"
git remote add origin https://git.ein-softworks.com/project/test-game.git
git branch -M main
git push -u origin main
Step 4: Open and Build in Godot
- Launch Godot:
~/GameDev/Godot/Godot.app/Contents/MacOS/Godot - In Project Manager, click "Import"
- Navigate to
~/GameDev/projects/test-game/and selectproject.godot - Click "Import & Edit"
- In the Godot editor, go to Project → Tools → C# → Create C# solution
- Click the "Build" button in the toolbar (or Project → Tools → C# → Build Solution)
- Run the game with the Play button
Expected console output:
Test Game Started!
90° → 1.5708 rad → 90°
Clamped 1.5 to 0-1 range: 1
0.1 ≈ 0.100001: True
Usage Workflows
Working on Libraries
Developing New Features
When you want to add functionality to an existing library:
# Navigate to library
cd ~/GameDev/libraries/utilities
# Create a new branch for your feature
git checkout -b feature/string-utilities
# Add your new code (example)
cat >> Scripts/StringUtils.cs << 'EOF'
using Godot;
namespace EinSoftworks.Utilities
{
public static class StringUtils
{
public static string ToPascalCase(string input)
{
if (string.IsNullOrEmpty(input)) return input;
return char.ToUpper(input[0]) + input.Substring(1).ToLower();
}
public static bool IsValidEmail(string email)
{
return email.Contains("@") && email.Contains(".");
}
}
}
EOF
# Test your changes in Godot
# Open ~/GameDev/libraries/utilities in Godot
# Build and test the library
# Commit your changes
git add .
git commit -m "Add StringUtils with PascalCase and email validation"
# Push the feature branch
git push origin feature/string-utilities
# Create a pull request in Gitea
# After review and merge, switch back to main
git checkout main
git pull origin main
# Clean up feature branch
git branch -d feature/string-utilities
Creating a New Library
# Create new library repository
cd ~/GameDev/libraries
git init audio-manager
cd audio-manager
# Set up library structure
mkdir -p Scripts Resources
cat > project.godot << 'EOF'
; Engine configuration file.
[application]
config/name="Audio Manager Library"
config/features=PackedStringArray("4.3", "C#", "Forward Plus")
[dotnet]
project/assembly_name="EinSoftworks.AudioManager"
EOF
cat > AudioManager.csproj << 'EOF'
<Project Sdk="Godot.NET.Sdk/4.3.0">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework Condition=" '$(GodotTargetPlatform)' == 'android' ">net7.0</TargetFramework>
<TargetFramework Condition=" '$(GodotTargetPlatform)' == 'ios' ">net8.0</TargetFramework>
<EnableDynamicLoading>true</EnableDynamicLoading>
<RootNamespace>EinSoftworks.AudioManager</RootNamespace>
<AssemblyName>EinSoftworks.AudioManager</AssemblyName>
</PropertyGroup>
</Project>
EOF
# Create basic audio manager
cat > Scripts/AudioManager.cs << 'EOF'
using Godot;
namespace EinSoftworks.AudioManager
{
public partial class AudioManager : Node
{
private AudioStreamPlayer _musicPlayer;
private AudioStreamPlayer _sfxPlayer;
public override void _Ready()
{
_musicPlayer = new AudioStreamPlayer();
_sfxPlayer = new AudioStreamPlayer();
AddChild(_musicPlayer);
AddChild(_sfxPlayer);
}
public void PlayMusic(AudioStream music, float volume = 1.0f)
{
_musicPlayer.Stream = music;
_musicPlayer.VolumeDb = Mathf.LinearToDb(volume);
_musicPlayer.Play();
}
public void PlaySFX(AudioStream sfx, float volume = 1.0f)
{
_sfxPlayer.Stream = sfx;
_sfxPlayer.VolumeDb = Mathf.LinearToDb(volume);
_sfxPlayer.Play();
}
}
}
EOF
# Copy standard .gitignore
cp ../utilities/.gitignore .
# Commit and push
git add .
git commit -m "Initial audio manager library"
git remote add origin https://git.ein-softworks.com/library/audio-manager.git
git branch -M main
git push -u origin main
Working with Game Projects
Adding Libraries to Existing Projects
To add a new library to an existing game project:
# Navigate to your game project
cd ~/GameDev/projects/test-game
# Add the new library as a submodule
git submodule add https://git.ein-softworks.com/library/audio-manager.git Libraries/audio-manager
# Update the .csproj file to reference the new library
# Edit TestGame.csproj and add to ItemGroup:
# <ProjectReference Include="Libraries/audio-manager/AudioManager.csproj" />
# Example of the updated .csproj:
cat > TestGame.csproj << 'EOF'
<Project Sdk="Godot.NET.Sdk/4.3.0">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework Condition=" '$(GodotTargetPlatform)' == 'android' ">net7.0</TargetFramework>
<TargetFramework Condition=" '$(GodotTargetPlatform)' == 'ios' ">net8.0</TargetFramework>
<EnableDynamicLoading>true</EnableDynamicLoading>
<RootNamespace>TestGame</RootNamespace>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="Libraries/utilities/Utilities.csproj" />
<ProjectReference Include="Libraries/audio-manager/AudioManager.csproj" />
</ItemGroup>
</Project>
EOF
# Commit the changes
git add .
git commit -m "Add audio-manager library dependency"
git push origin main
# In Godot: Project → Reload Current Project
# Then build the solution
Updating Libraries in Projects
To update a library to the latest version:
# Navigate to your game project
cd ~/GameDev/projects/test-game
# Update specific library to latest
git submodule update --remote Libraries/utilities
# Or update all submodules
git submodule update --remote
# Commit the library updates
git add .
git commit -m "Update utilities library to latest version"
git push origin main
# In Godot: Project → Reload Current Project
# Then rebuild the solution
To update to a specific version/commit:
# Navigate to the submodule
cd ~/GameDev/projects/test-game/Libraries/utilities
# Check available tags/versions
git tag -l
# Checkout specific version
git checkout v1.2.0
# Go back to project root
cd ~/GameDev/projects/test-game
# Commit the specific version
git add Libraries/utilities
git commit -m "Update utilities library to v1.2.0"
git push origin main
Creating New Game Projects
To create a new game project with existing libraries:
# Create new project
cd ~/GameDev/projects
git init puzzle-game
cd puzzle-game
# Set up basic structure
mkdir -p Scripts Scenes Libraries
# Create project.godot
cat > project.godot << 'EOF'
; Engine configuration file.
[application]
config/name="Puzzle Game"
config/features=PackedStringArray("4.3", "C#", "Forward Plus")
run/main_scene="res://Scenes/Main.tscn"
[dotnet]
project/assembly_name="PuzzleGame"
EOF
# Add required libraries
git submodule add https://git.ein-softworks.com/library/utilities.git Libraries/utilities
git submodule add https://git.ein-softworks.com/library/audio-manager.git Libraries/audio-manager
# Create .csproj with all dependencies
cat > PuzzleGame.csproj << 'EOF'
<Project Sdk="Godot.NET.Sdk/4.3.0">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework Condition=" '$(GodotTargetPlatform)' == 'android' ">net7.0</TargetFramework>
<TargetFramework Condition=" '$(GodotTargetPlatform)' == 'ios' ">net8.0</TargetFramework>
<EnableDynamicLoading>true</EnableDynamicLoading>
<RootNamespace>PuzzleGame</RootNamespace>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="Libraries/utilities/Utilities.csproj" />
<ProjectReference Include="Libraries/audio-manager/AudioManager.csproj" />
</ItemGroup>
</Project>
EOF
# Copy .gitignore from another project
cp ../test-game/.gitignore .
# Initialize submodules
git submodule update --init --recursive
# Create basic game code
cat > Scripts/Main.cs << 'EOF'
using Godot;
using EinSoftworks.Utilities;
using EinSoftworks.AudioManager;
namespace PuzzleGame
{
public partial class Main : Node2D
{
private AudioManager _audioManager;
public override void _Ready()
{
GD.Print("Puzzle Game Started!");
// Set up audio manager
_audioManager = new AudioManager();
AddChild(_audioManager);
// Test utilities
float angle = MathUtils.DegreesToRadians(45f);
GD.Print($"45 degrees = {angle} radians");
}
}
}
EOF
# Commit and push
git add .
git commit -m "Initial puzzle game setup with utilities and audio-manager libraries"
git remote add origin https://git.ein-softworks.com/project/puzzle-game.git
git branch -M main
git push -u origin main
Advanced Workflows
Working with Library Dependencies
If one library depends on another:
// In Libraries/ui-framework/UIFramework.csproj
<Project Sdk="Godot.NET.Sdk/4.3.0">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<EnableDynamicLoading>true</EnableDynamicLoading>
<RootNamespace>EinSoftworks.UIFramework</RootNamespace>
<AssemblyName>EinSoftworks.UIFramework</AssemblyName>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../utilities/Utilities.csproj" />
</ItemGroup>
</Project>
Creating Library Releases
For important library milestones:
# In your library repository
cd ~/GameDev/libraries/utilities
# Create and push a tag
git tag -a v1.0.0 -m "Release version 1.0.0 - Initial stable release"
git push origin v1.0.0
# Create release notes in Gitea
# Navigate to your Gitea repository → Releases → New Release
Cloning Projects on New Machines
When setting up on a new development machine:
# Clone project with submodules
git clone --recursive https://git.ein-softworks.com/project/test-game.git
# Or if already cloned without --recursive
cd test-game
git submodule update --init --recursive
Best Practices
Library Design
- Keep libraries focused: Each library should have a single, well-defined purpose
- Use proper namespacing: Follow the pattern
CompanyName.LibraryName - Document your code: Use XML documentation comments for public APIs
- Version your releases: Use semantic versioning (major.minor.patch)
- Test thoroughly: Create test projects for each library
Project Organization
- Consistent naming: Use kebab-case for repository names, PascalCase for namespaces
- Clear dependencies: Only include libraries you actually use
- Regular updates: Keep libraries updated, but test thoroughly after updates
- Backup strategy: Ensure all repositories are backed up on your Gitea instance
Git Workflow
- Feature branches: Use feature branches for library development
- Descriptive commits: Write clear commit messages
- Regular pushes: Push changes regularly to avoid data loss
- Clean history: Squash commits when merging features
Development Environment
- Consistent paths: Always use the
~/GameDev/structure - IDE setup: Configure your IDE to recognize the library references
- Build automation: Consider setting up CI/CD for library testing
- Documentation: Keep this document updated as your system evolves
Troubleshooting
Common Issues
Submodule Not Found
# If Libraries/utilities is empty
cd ~/GameDev/projects/test-game
git submodule update --init --recursive
Build Errors
# In Godot editor
# Project → Reload Current Project
# Project → Tools → C# → Create C# solution
# Build
Missing References
Check that your .csproj file includes all required <ProjectReference> entries for your submodules.
Submodule Update Conflicts
# If you have uncommitted changes in a submodule
cd Libraries/utilities
git stash
git pull origin main
git stash pop
Getting Help
- Check Godot's C# documentation
- Review Git submodule documentation
- Examine this document for reference patterns
- Test changes in isolation before applying to important projects
This documentation should be updated as your library system evolves and new patterns emerge.