Files
documentation/docs/LIBRARY_MANAGEMENT_SYSTEM.md
2025-10-23 09:41:21 -05:00

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

  1. Separation of Concerns: Libraries and game projects exist in separate Git repositories
  2. Modular Design: Each library serves a specific purpose and can be developed independently
  3. Version Control: Libraries can be versioned and updated independently of game projects
  4. Code Reuse: Multiple projects can share the same libraries without code duplication
  5. 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 repositories
  • project/ - Contains all game project repositories

Repository Examples:

  • https://git.ein-softworks.com/library/utilities.git
  • https://git.ein-softworks.com/library/audio-manager.git
  • https://git.ein-softworks.com/project/test-game.git
  • https://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:

  1. Library repositories contain reusable C# code and Godot scenes
  2. Game project repositories include libraries as submodules in their Libraries/ directory
  3. Submodules point to specific commits in the library repositories, ensuring reproducible builds
  4. 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 library and project organizations 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

  1. Launch Godot: ~/GameDev/Godot/Godot.app/Contents/MacOS/Godot
  2. In Project Manager, click "Import"
  3. Navigate to ~/GameDev/projects/test-game/ and select project.godot
  4. Click "Import & Edit"
  5. In the Godot editor, go to Project → Tools → C# → Create C# solution
  6. Click the "Build" button in the toolbar (or Project → Tools → C# → Build Solution)
  7. 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

  1. Keep libraries focused: Each library should have a single, well-defined purpose
  2. Use proper namespacing: Follow the pattern CompanyName.LibraryName
  3. Document your code: Use XML documentation comments for public APIs
  4. Version your releases: Use semantic versioning (major.minor.patch)
  5. Test thoroughly: Create test projects for each library

Project Organization

  1. Consistent naming: Use kebab-case for repository names, PascalCase for namespaces
  2. Clear dependencies: Only include libraries you actually use
  3. Regular updates: Keep libraries updated, but test thoroughly after updates
  4. Backup strategy: Ensure all repositories are backed up on your Gitea instance

Git Workflow

  1. Feature branches: Use feature branches for library development
  2. Descriptive commits: Write clear commit messages
  3. Regular pushes: Push changes regularly to avoid data loss
  4. Clean history: Squash commits when merging features

Development Environment

  1. Consistent paths: Always use the ~/GameDev/ structure
  2. IDE setup: Configure your IDE to recognize the library references
  3. Build automation: Consider setting up CI/CD for library testing
  4. 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

  1. Check Godot's C# documentation
  2. Review Git submodule documentation
  3. Examine this document for reference patterns
  4. Test changes in isolation before applying to important projects

This documentation should be updated as your library system evolves and new patterns emerge.