A powerful C# Source Generator for Unity that automatically generates optimized code for Remote Config management with Firebase integration and flexible storage options.
- π Zero Reflection Overhead - 50-90% faster than dictionary-based approaches
- π Type-Safe - Compile-time type checking for all config fields
- π― Auto-Scan - Automatically detects all public static fields (no manual attribute tagging required)
- πΎ Flexible Storage - Pluggable storage with support for PlayerPrefs, GameData, or custom implementations
- π Zero Dependencies - Generated code has no dependencies on specific storage systems
- π₯ Firebase Ready - Built-in support for Firebase Remote Config syncing
- π οΈ Easy to Use - Minimal setup, maximum productivity
- Installation
- Quick Start
- Building the DLL
- Storage Implementation
- Usage Examples
- Advanced Features
- Documentation
- Troubleshooting
Dependencies: CΓ i NuGet For Unity => Install Microsoft.CodeAnalysis.CSharp
LαΊ₯y SourceGenerator.dll ΔΓnh kΓ¨m trong release
-
Copy file
SourceGenerator.dllvΓ o thΖ° mα»₯cAssets/Plugins/trong Unity project -
Chα»n DLL trong Unity Inspector
-
CαΊ₯u hΓ¬nh nhΖ° sau:
- Bα» chα»n "Any Platform"
- Chα» chα»n "Editor"
- ThΓͺm Label: NhαΊ₯n vΓ o dropdown "Asset Labels" vΓ thΓͺm label
RoslynAnalyzer
-
Apply changes
using RemoteConfigGenerator;
[RemoteConfigData(PrefsPrefix = "rc_")]
public static partial class RemoteData
{
// All public static fields are automatically scanned
public static int GoldReward = 100;
public static string WelcomeMessage = "Welcome to the game!";
public static float SpawnRate = 2.5f;
public static bool EnableNewFeature = false;
public static long UserId = 123456789;
// Arrays are supported
public static int[] RewardLevels = { 10, 20, 30, 50, 100 };
public static float[] Multipliers = { 1.0f, 1.5f, 2.0f };
}Important:
- Class must be
static partial - Add
[RemoteConfigData]attribute - Source Generator automatically scans all public static fields
Create a storage adapter (see Storage Implementation for examples):
using UnityEngine;
using RemoteConfigGenerator;
public class PlayerPrefsStorage : IRemoteConfigStorage
{
public void SetInt(string key, int value) => PlayerPrefs.SetInt(key, value);
public int GetInt(string key, int defaultValue) => PlayerPrefs.GetInt(key, defaultValue);
public void SetFloat(string key, float value) => PlayerPrefs.SetFloat(key, value);
public float GetFloat(string key, float defaultValue) => PlayerPrefs.GetFloat(key, defaultValue);
public void SetString(string key, string value) => PlayerPrefs.SetString(key, value);
public string GetString(string key, string defaultValue) => PlayerPrefs.GetString(key, defaultValue);
public void SetBool(string key, bool value) => PlayerPrefs.SetInt(key, value ? 1 : 0);
public bool GetBool(string key, bool defaultValue) => PlayerPrefs.GetInt(key, defaultValue ? 1 : 0) != 0;
public void SetLong(string key, long value) => PlayerPrefs.SetString(key, value.ToString());
public long GetLong(string key, long defaultValue) =>
long.TryParse(PlayerPrefs.GetString(key, defaultValue.ToString()), out var v) ? v : defaultValue;
public void Save() => PlayerPrefs.Save();
}using UnityEngine;
public class GameBootstrap : MonoBehaviour
{
void Awake()
{
// 1. Assign storage implementation
RemoteDataExtensions.Storage = new PlayerPrefsStorage();
// 2. Load saved values
RemoteDataExtensions.LoadFromPrefs_Generated();
// 3. Use your config
Debug.Log($"Gold Reward: {RemoteData.GoldReward}");
Debug.Log($"Welcome: {RemoteData.WelcomeMessage}");
}
void Start()
{
// Modify values
RemoteData.GoldReward = 200;
// Save changes
RemoteDataExtensions.SaveToPrefs_Generated();
}
}NαΊΏu bαΊ‘n khΓ΄ng dΓΉng SourceGenerator.dll ΔΓnh kΓ¨m trong bαΊ£n release thΓ¬ bαΊ‘n cΓ³ thα» tα»± build hoαΊ·c sα»a lαΊ‘i code theo Γ½ muα»n rα»i build lαΊ‘i.
- .NET SDK 6.0+
- Visual Studio 2022 or Rider (optional)
# Navigate to the SourceGenerator directory
cd SourceGenerator
# Build in Debug mode
dotnet build -c Debug
# Build in Release mode (recommended for production)
dotnet build -c Release
# Output location:
# Debug: SourceGenerator/bin/Debug/netstandard2.0/RemoteConfigGenerator.dll
# Release: SourceGenerator/bin/Release/netstandard2.0/RemoteConfigGenerator.dll- Open
RemoteConfigGenerator.sln - Right-click on
RemoteConfigGeneratorproject - Select Build or Rebuild
- Find DLL in
SourceGenerator\bin\Debug\netstandard2.0\orRelease
- Open
RemoteConfigGenerator.sln - Select Build β Build Solution (Ctrl+Shift+B)
- Find DLL in
SourceGenerator\bin\Debug\netstandard2.0\orRelease
| Configuration | Use Case | Optimizations |
|---|---|---|
| Debug | Development, debugging | No optimizations, includes debug symbols |
| Release | Production | Full optimizations, smaller file size |
# Check if DLL was created
ls SourceGenerator/bin/Release/netstandard2.0/RemoteConfigGenerator.dll
# View DLL info (Windows)
dotnet --info# Clean all build artifacts
dotnet clean
# Clean and rebuild
dotnet clean && dotnet build -c ReleaseThe generator uses a Storage Wrapper Pattern - you provide the storage implementation by implementing IRemoteConfigStorage.
namespace RemoteConfigGenerator
{
public interface IRemoteConfigStorage
{
void SetInt(string key, int value);
int GetInt(string key, int defaultValue);
void SetFloat(string key, float value);
float GetFloat(string key, float defaultValue);
void SetString(string key, string value);
string GetString(string key, string defaultValue);
void SetBool(string key, bool value);
bool GetBool(string key, bool defaultValue);
void SetLong(string key, long value);
long GetLong(string key, long defaultValue);
void Save();
}
}Simple, works out of the box:
public class PlayerPrefsStorage : IRemoteConfigStorage
{
public void SetInt(string key, int value) => PlayerPrefs.SetInt(key, value);
public int GetInt(string key, int defaultValue) => PlayerPrefs.GetInt(key, defaultValue);
// ... implement other methods
public void Save() => PlayerPrefs.Save();
}Pros: No dependencies, works everywhere
Cons: Limited to simple types, slower for large data
High-performance binary serialization:
using VirtueSky.DataStorage;
public class GameDataStorage : IRemoteConfigStorage
{
public void SetInt(string key, int value) => GameData.Set(key, value);
public int GetInt(string key, int defaultValue) => GameData.Get(key, defaultValue);
// ... implement other methods
public void Save() => GameData.Save();
}Pros: Fast, supports complex types, better performance
Cons: Requires game-data-unity package
Implement any storage you want:
public class CloudStorage : IRemoteConfigStorage
{
public async void Save()
{
// Upload to cloud
await UploadToCloudAsync();
}
// ... implement other methods
}See STORAGE_WRAPPER_VI.md for detailed examples.
// Read values
int gold = RemoteData.GoldReward;
string msg = RemoteData.WelcomeMessage;
bool enabled = RemoteData.EnableNewFeature;
// Modify values
RemoteData.GoldReward = 500;
RemoteData.EnableNewFeature = true;
// Save to storage
RemoteDataExtensions.SaveToPrefs_Generated();
// Load from storage
RemoteDataExtensions.LoadFromPrefs_Generated();using Firebase.RemoteConfig;
using System.Threading.Tasks;
public class RemoteConfigManager : MonoBehaviour
{
async void Start()
{
// Initialize storage
RemoteDataExtensions.Storage = new PlayerPrefsStorage();
// Load local cached values
RemoteDataExtensions.LoadFromPrefs_Generated();
// Sync from Firebase
await SyncFromFirebase();
}
async Task SyncFromFirebase()
{
try
{
// Fetch latest config from Firebase
await FirebaseRemoteConfig.DefaultInstance.FetchAndActivateAsync();
// Sync to static class using generated lookup
var allKeys = FirebaseRemoteConfig.DefaultInstance.AllKeys;
foreach (var key in allKeys)
{
var configValue = FirebaseRemoteConfig.DefaultInstance.GetValue(key);
RemoteDataExtensions.SetFieldValue_Generated(key, configValue);
}
// Save to local storage
RemoteDataExtensions.SaveToPrefs_Generated();
Debug.Log("β
Synced from Firebase");
}
catch (Exception ex)
{
Debug.LogError($"β Sync failed: {ex.Message}");
}
}
}// Export all values to string (useful for debug UI)
string debugInfo = RemoteDataExtensions.ExportToString_Generated();
Debug.Log(debugInfo);
// Output:
// GoldReward: 100
// WelcomeMessage: Welcome to the game!
// SpawnRate: 2.5
// EnableNewFeature: False[RemoteConfigData(PrefsPrefix = "game_")]
public static partial class GameConfig
{
public static int MaxLevel = 100;
public static float DifficultyMultiplier = 1.0f;
}
[RemoteConfigData(PrefsPrefix = "shop_")]
public static partial class ShopConfig
{
public static int DiamondPrice = 99;
public static string[] ProductIds = { "com.game.coins", "com.game.gems" };
}
// Setup each independently
GameConfigExtensions.Storage = new PlayerPrefsStorage();
ShopConfigExtensions.Storage = new PlayerPrefsStorage();
GameConfigExtensions.LoadFromPrefs_Generated();
ShopConfigExtensions.LoadFromPrefs_Generated();Control individual field behavior:
[RemoteConfigData]
public static partial class RemoteData
{
// Custom Firebase key name
[RemoteConfigField(Key = "reward_gold_amount")]
public static int GoldReward = 100;
// Don't save to storage (runtime only)
[RemoteConfigField(PersistToPrefs = false)]
public static int TempValue = 0;
// Don't sync from Firebase (local only)
[RemoteConfigField(SyncFromRemote = false)]
public static int LocalOnlyValue = 0;
}[RemoteConfigData(PrefsPrefix = "myapp_config_")]
public static partial class RemoteData
{
// Keys will be: myapp_config_GoldReward, myapp_config_WelcomeMessage, etc.
}Auto-Scan (Default): Automatically includes all public static fields
[RemoteConfigData]
public static partial class RemoteData
{
public static int Field1 = 1; // β
Included
public static int Field2 = 2; // β
Included
private static int Field3 = 3; // β Not included (private)
}Manual Mode: Only includes fields with [RemoteConfigField]
[RemoteConfigData]
public static partial class RemoteData
{
[RemoteConfigField]
public static int Field1 = 1; // β
Included
public static int Field2 = 2; // β Not included (no attribute)
}- β
int,float,string,bool,long - β
int[],float[](stored as comma-separated strings) - β Complex types (use GameData storage with custom serialization)
- QUICK_START_VI.md - Vietnamese quick start guide
- STORAGE_WRAPPER_VI.md - Storage wrapper pattern details
- TECHNICAL_DETAILS_VI.md - Technical implementation details
Solution: Assign storage before calling Save/Load:
RemoteDataExtensions.Storage = new PlayerPrefsStorage();Check:
- β
Class is
static partial - β
Has
[RemoteConfigData]attribute - β Rebuild project (Clean + Build)
- β Restart IDE
Cause: Unsupported field type
Solution: Only use supported types (int, float, string, bool, long, int[], float[])
Check:
- β Storage is assigned
- β
Called
SaveToPrefs_Generated()after modifying values - β
Called
LoadFromPrefs_Generated()before reading values
Visual Studio:
- Solution Explorer β Dependencies β Analyzers β RemoteConfigGenerator β
YourClass_Generated.g.cs
File System:
obj/Debug/generated/RemoteConfigGenerator/RemoteConfigGenerator.RemoteConfigSourceGenerator/
1. You write: 2. Source Generator creates:
[RemoteConfigData] public static partial class RemoteDataExtensions
public static partial class {
RemoteData public static IRemoteConfigStorage Storage { get; set; }
{
public static int Gold = 100; public static void SaveToPrefs_Generated()
} {
Storage.SetInt("rc_Gold", RemoteData.Gold);
Storage.Save();
}
public static void LoadFromPrefs_Generated()
{
RemoteData.Gold = Storage.GetInt("rc_Gold", RemoteData.Gold);
}
// + Dictionary lookups for Firebase
// + Export to string
// + SetFieldValue / GetFieldValue
}
Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT License - see the LICENSE file for details.
- Built with Roslyn Source Generators
- Inspired by Firebase Remote Config best practices
- Storage wrapper pattern for maximum flexibility
- Create an issue on GitHub
- Check the Documentation folder
- Review Example folder for working code
Made with β€οΈ for VirtueSky