Type-safe, high-performance configuration management for Unity games
Features • Installation • Quick Start • Documentation • Examples • Contributing
Managing game configuration data shouldn't be a hassle. This library solves common Unity config challenges:
✅ Type Safety - No more casting or string-based lookups
✅ Designer Friendly - Edit configs in Unity Inspector with ScriptableObjects
✅ Performance - O(1) lookups with pre-built dictionaries
✅ Flexibility - Support for singletons, collections, and custom types
✅ Backend Ready - Built-in serialization for server sync
✅ Version Control - Track and update configs atomically
Perfect for managing enemy stats, item databases, level configurations, game balance values, and any other design data in your Unity projects.
Lightweight, type-safe configuration storage for Unity that lets you load, query, version, and serialize your game configs (design data, tuning values, asset references, etc.) in a predictable and efficient way.
Core Features:
- 🔍 Single or multiple configs per type - Singleton pattern or id-indexed collections
- 🚀 Fast lookups - In-memory dictionaries for O(1) performance
- 📝 Versioning and atomic updates - Track changes and update safely
- 🔄 JSON serialization/deserialization - Perfect for client/server sync
- 📦 ScriptableObject containers - Designer-friendly key/value pairs
- 🎯 Type-safe queries - No casting or string-based lookups
- 🌐 Backend integration - Optional remote config fetching
- 🎯 Why Use This?
- ✨ Features
- 📦 Installation
- 🚀 Quick Start
- 📋 Core Concepts
- 🛠️ ScriptableObject Workflow
- 💾 Serialization & Versioning
- 🌐 Backend Integration
- 📚 API Reference
- 🎮 Examples
- ⚡ Performance
- 🔧 Troubleshooting
- ❓ FAQ
- 🤝 Contributing
- 📄 License
- Unity 6.0 or newer (
"unity": "6000.0") - Namespace:
GameLovers.ConfigsProvider - For JSON serialization:
Newtonsoft.Json(Unity packagecom.unity.nuget.newtonsoft-json) - Uses
Pair<TKey, TValue>fromGameLovers.DataExtensions(already referenced by the assembly definition)
- Open Unity Package Manager (
Window→Package Manager) - Click the + button →
Add package from git URL... - Enter:
https://github.com/CoderGamester/Unity-ConfigsProvider.git#0.2.0 - Click
Add
Add this line to your Packages/manifest.json:
{
"dependencies": {
"com.gamelovers.configsprovider": "https://github.com/CoderGamester/Unity-ConfigsProvider.git#0.2.0"
}
}This package automatically handles most dependencies, but you may need:
- Newtonsoft.Json (for serialization) - Install via Package Manager:
com.unity.nuget.newtonsoft-json - GameLovers.DataExtensions - Automatically included via assembly definition
After installation, you should see:
- ✅
GameLovers.ConfigsProvidernamespace available - ✅ No compilation errors in Console
- ✅ Runtime scripts accessible in your code
Create a new script GameConfigs.cs:
usingSystem;usingUnityEngine;namespaceMyGame.Configs{[Serializable]publicclassEnemyConfig{publicintId;publicstringName;publicintHealth;publicfloatMoveSpeed;publicGameObjectPrefab;// Unity asset references supported!}[Serializable]publicclassGameSettings{publicfloatMusicVolume=0.8f;publicfloatSfxVolume=1.0f;publicboolShowTutorials=true;}}In your game initialization (e.g., GameManager.cs):
usingGameLovers.ConfigsProvider;usingMyGame.Configs;usingSystem.Collections.Generic;usingUnityEngine;publicclassGameManager:MonoBehaviour{privateIConfigsProvider_configs;voidStart(){// Create providervarprovider=newConfigsProvider();// Add enemy configs (multiple instances mapped by ID)provider.AddConfigs(
enemy =>enemy.Id,// Key selector functionnewList<EnemyConfig>{new(){Id=1,Name="Goblin",Health=50,MoveSpeed=3f},new(){Id=2,Name="Orc",Health=100,MoveSpeed=2f},new(){Id=3,Name="Dragon",Health=500,MoveSpeed=5f}});// Add game settings (singleton)provider.AddSingletonConfig(newGameSettings());_configs=provider;Debug.Log("Configs loaded successfully!");}}usingGameLovers.ConfigsProvider;publicclassEnemySpawner:MonoBehaviour{[SerializeField]privateGameManager_gameManager;publicvoidSpawnEnemy(intenemyId){// Get specific enemy configvarenemyConfig=_gameManager.Configs.GetConfig<EnemyConfig>(enemyId);// Use the config datavarenemy=Instantiate(enemyConfig.Prefab);varhealthComponent=enemy.GetComponent<Health>();healthComponent.SetMaxHealth(enemyConfig.Health);Debug.Log($"Spawned {enemyConfig.Name} with {enemyConfig.Health} HP");}publicvoidShowAllEnemies(){// Get all enemiesforeach(varenemyin_gameManager.Configs.GetConfigsList<EnemyConfig>()){Debug.Log($"Enemy: {enemy.Name} (ID: {enemy.Id}) - {enemy.Health} HP");}}publicvoidApplyGameSettings(){// Get singleton settingsvarsettings=_gameManager.Configs.GetConfig<GameSettings>();AudioManager.SetVolume(settings.MusicVolume,settings.SfxVolume);TutorialManager.SetEnabled(settings.ShowTutorials);}}Singleton Configs - One instance per type:
// Perfect for game settings, global valuesprovider.AddSingletonConfig(newGameSettings());varsettings=provider.GetConfig<GameSettings>();Collection Configs - Multiple instances mapped by ID:
// Perfect for items, enemies, levelsprovider.AddConfigs(item =>item.ItemId,itemList);varsword=provider.GetConfig<ItemConfig>(101);// Exception if not foundvarconfig=provider.GetConfig<EnemyConfig>(999);// Safe version - returns false if not foundif(provider.TryGetConfig<EnemyConfig>(999,outvarconfig)){// Use config safely}For designer-friendly configuration authoring, use ConfigsScriptableObject<TId, TAsset>. This stores key/value pairs as a serializable list and builds a dictionary on load.
usingSystem;usingGameLovers.ConfigsProvider;usingUnityEngine;[Serializable]publicclassEnemyConfig{publicintId;publicstringName;publicintHealth;publicfloatMoveSpeed;publicGameObjectPrefab;}[CreateAssetMenu(fileName="Enemy Configs",menuName="Game/Enemy Configs")]publicclassEnemyConfigs:ConfigsScriptableObject<int,EnemyConfig>{}- Right-click in Project →
Create→Game→Enemy Configs - Select the created asset
- In Inspector, add entries to the
Configslist - Each entry has a
Key(int) andValue(EnemyConfig)
publicclassConfigLoader:MonoBehaviour{[SerializeField]privateEnemyConfigs_enemyConfigs;privateIConfigsProvider_provider;voidStart(){varprovider=newConfigsProvider();// Option 1: Use ScriptableObject dictionary directlyvargoblin=_enemyConfigs.ConfigsDictionary[1];// Option 2: Feed into main provider for unified accessprovider.AddConfigs(
config =>config.Id,_enemyConfigs.Configs.Select(pair =>pair.Value).ToList());_provider=provider;}}- Duplicate keys will throw during deserialization
ConfigsDictionaryis read-only and built inOnAfterDeserialize- Keys must be unique within each ScriptableObject
Use ConfigsSerializer to serialize providers to JSON for storage or server transfer, with automatic version tracking.
usingGameLovers.ConfigsProvider;varserializer=newConfigsSerializer();// Serialize to JSON (e.g., to send to server or save locally)stringjsonData=serializer.Serialize(provider,version:"1.2.3");// Deserialize back into a new provider instancevarrestoredProvider=serializer.Deserialize<ConfigsProvider>(jsonData);Debug.Log($"Restored provider with version: {restoredProvider.Version}");Mark types that should not be sent to clients/servers:
usingSystem;usingGameLovers.ConfigsProvider;[IgnoreServerSerialization][Serializable]publicclassEditorOnlyConfig{publicstringInternalNotes;publicboolDebugMode;}[Serializable]publicclassPlayerVisibleConfig{publicintMaxLevel;publicfloatExpMultiplier;}// Check current versionDebug.Log($"Current version: {provider.Version}");// Serialize with semantic versioningstringv1_0_0=serializer.Serialize(provider,"1.0.0");stringv1_1_0=serializer.Serialize(updatedProvider,"1.1.0");// Version is automatically converted to ulong for comparisonvarrestored=serializer.Deserialize<ConfigsProvider>(v1_1_0);// restored.Version will be a ulong representation of "1.1.0"📋 Requirements:
- Config types must be
[Serializable]unless marked with[IgnoreServerSerialization] - Uses Newtonsoft.Json with
TypeNameHandling.Autoand enum-as-string conversion
Integrate with your backend using IConfigBackendService to poll for remote versions and perform atomic config updates.
usingSystem.Threading.Tasks;usingGameLovers.ConfigsProvider;usingUnityEngine;usingUnityEngine.Networking;publicclassMyBackendService:IConfigBackendService{privateconststringBASE_URL="https://your-game-server.com/api/configs";publicasyncTask<ulong>GetRemoteVersion(){usingvarrequest=UnityWebRequest.Get($"{BASE_URL}/version");awaitrequest.SendWebRequest();if(request.result==UnityWebRequest.Result.Success){varversionData=JsonUtility.FromJson<VersionResponse>(request.downloadHandler.text);returnversionData.version;}thrownewSystem.Exception($"Failed to get remote version: {request.error}");}publicasyncTask<IConfigsProvider>FetchRemoteConfiguration(ulongversion){usingvarrequest=UnityWebRequest.Get($"{BASE_URL}/data/{version}");awaitrequest.SendWebRequest();if(request.result==UnityWebRequest.Result.Success){varserializer=newConfigsSerializer();returnserializer.Deserialize<ConfigsProvider>(request.downloadHandler.text);}thrownewSystem.Exception($"Failed to fetch remote config: {request.error}");}[System.Serializable]privateclassVersionResponse{publiculongversion;}}publicclassConfigSyncManager:MonoBehaviour{[SerializeField]privatefloat_syncIntervalSeconds=300f;// 5 minutesprivateConfigsProvider_localProvider;privateIConfigBackendService_backendService;voidStart(){_localProvider=newConfigsProvider();_backendService=newMyBackendService();// Start periodic syncInvokeRepeating(nameof(SyncWithBackend),0f,_syncIntervalSeconds);}privateasyncvoidSyncWithBackend(){try{varremoteVersion=await_backendService.GetRemoteVersion();if(remoteVersion>_localProvider.Version){Debug.Log($"New config version available: {remoteVersion}");varremoteProvider=await_backendService.FetchRemoteConfiguration(remoteVersion);// Atomic update - copy data and bump version_localProvider.UpdateTo(remoteProvider.Version,remoteProvider.GetAllConfigs());Debug.Log($"Successfully updated to version {_localProvider.Version}");// Notify other systems of config updateOnConfigsUpdated?.Invoke();}}catch(System.Exceptionex){Debug.LogError($"Config sync failed: {ex.Message}");}}publicSystem.ActionOnConfigsUpdated;}Read-only access to configuration data.
| Method | Description | Example |
|---|---|---|
ulong Version { get; } | Current version number | var version = provider.Version; |
T GetConfig<T>() | Get singleton config | var settings = provider.GetConfig<GameSettings>(); |
T GetConfig<T>(int id) | Get config by ID | var enemy = provider.GetConfig<EnemyConfig>(1); |
bool TryGetConfig<T>(int id, out T config) | Safe get by ID | if (provider.TryGetConfig(1, out var enemy)) { } |
List<T> GetConfigsList<T>() | Get all configs of type | var allEnemies = provider.GetConfigsList<EnemyConfig>(); |
IReadOnlyDictionary<int, T> GetConfigsDictionary<T>() | Get dictionary of configs | var enemyDict = provider.GetConfigsDictionary<EnemyConfig>(); |
IReadOnlyDictionary<Type, IEnumerable> GetAllConfigs() | Get all config data | var allConfigs = provider.GetAllConfigs(); |
Write access for building configuration data.
| Method | Description | Example |
|---|---|---|
void AddSingletonConfig<T>(T config) | Add singleton | provider.AddSingletonConfig(settings); |
void AddConfigs<T>(Func<T, int> keySelector, IList<T> configs) | Add collection | provider.AddConfigs(e => e.Id, enemies); |
void AddAllConfigs(IReadOnlyDictionary<Type, IEnumerable> configs) | Add bulk configs | provider.AddAllConfigs(configDict); |
void UpdateTo(ulong version, IReadOnlyDictionary<Type, IEnumerable> configs) | Atomic update | provider.UpdateTo(42, newConfigs); |
Default implementation using in-memory dictionaries.
JSON serialization interface.
| Method | Description | Example |
|---|---|---|
string Serialize(IConfigsProvider provider, string version) | Serialize to JSON | var json = serializer.Serialize(provider, "1.0"); |
T Deserialize<T>(string json) where T : IConfigsAdder | Deserialize from JSON | var provider = serializer.Deserialize<ConfigsProvider>(json); |
Unity-serializable container for designer-authored configs.
| Property | Description | Example |
|---|---|---|
List<Pair<TId, TAsset>> Configs | Editable config pairs | Edit in Inspector |
IReadOnlyDictionary<TId, TAsset> ConfigsDictionary | Runtime lookup dictionary | var item = configs.ConfigsDictionary[itemId]; |
Optional interface for remote config fetching.
| Method | Description | Example |
|---|---|---|
Task<ulong> GetRemoteVersion() | Get latest version from server | var version = await service.GetRemoteVersion(); |
Task<IConfigsProvider> FetchRemoteConfiguration(ulong version) | Fetch config data | var configs = await service.FetchRemoteConfiguration(42); |
IConfig- Simple interface withint ConfigId { get; }IConfigsContainer<T>,ISingleConfigContainer<T>- Container patternsIPairConfigsContainer<TKey, TValue>,IStructPairConfigsContainer<TKey, TValue>- Pair containers
usingSystem;usingUnityEngine;usingGameLovers.ConfigsProvider;[Serializable]publicclassItemConfig{publicintItemId;publicstringItemName;publicItemTypeType;publicintValue;publicSpriteIcon;publicGameObjectPrefab;// Computed propertiespublicboolIsWeapon=>Type==ItemType.Weapon;publicboolIsConsumable=>Type==ItemType.Consumable;}publicenumItemType{Weapon,Armor,Consumable,Quest}[CreateAssetMenu(fileName="Item Database",menuName="Game/Item Database")]publicclassItemDatabase:ConfigsScriptableObject<int,ItemConfig>{}// Usage in gamepublicclassInventoryManager:MonoBehaviour{[SerializeField]privateItemDatabase_itemDatabase;publicvoidAddItemToInventory(intitemId,intquantity){if(_itemDatabase.ConfigsDictionary.TryGetValue(itemId,outvaritemConfig)){Debug.Log($"Added {quantity}x {itemConfig.ItemName} to inventory");// Add to player inventory...}else{Debug.LogError($"Item ID {itemId} not found in database!");}}}[Serializable]publicclassDifficultyConfig{publicDifficultyLevelLevel;publicfloatEnemyHealthMultiplier;publicfloatEnemyDamageMultiplier;publicfloatPlayerExpMultiplier;publicintMaxEnemiesPerWave;}publicenumDifficultyLevel{Easy=1,Normal=2,Hard=3,Nightmare=4}publicclassDifficultyManager:MonoBehaviour{privateIConfigsProvider_configs;privateDifficultyLevel_currentDifficulty=DifficultyLevel.Normal;voidStart(){varprovider=newConfigsProvider();provider.AddConfigs(d =>(int)d.Level,newList<DifficultyConfig>{new(){Level=DifficultyLevel.Easy,EnemyHealthMultiplier=0.7f,EnemyDamageMultiplier=0.8f,PlayerExpMultiplier=0.8f,MaxEnemiesPerWave=3},new(){Level=DifficultyLevel.Normal,EnemyHealthMultiplier=1.0f,EnemyDamageMultiplier=1.0f,PlayerExpMultiplier=1.0f,MaxEnemiesPerWave=5},new(){Level=DifficultyLevel.Hard,EnemyHealthMultiplier=1.5f,EnemyDamageMultiplier=1.3f,PlayerExpMultiplier=1.2f,MaxEnemiesPerWave=7},new(){Level=DifficultyLevel.Nightmare,EnemyHealthMultiplier=2.0f,EnemyDamageMultiplier=1.8f,PlayerExpMultiplier=1.5f,MaxEnemiesPerWave=10}});_configs=provider;}publicvoidChangeDifficulty(DifficultyLevelnewDifficulty){_currentDifficulty=newDifficulty;varconfig=_configs.GetConfig<DifficultyConfig>((int)newDifficulty);// Apply difficulty settingsEnemyManager.SetHealthMultiplier(config.EnemyHealthMultiplier);EnemyManager.SetDamageMultiplier(config.EnemyDamageMultiplier);ExperienceManager.SetExpMultiplier(config.PlayerExpMultiplier);WaveManager.SetMaxEnemies(config.MaxEnemiesPerWave);Debug.Log($"Difficulty changed to {newDifficulty}");}}- Lookup Performance: O(1) for both singleton and ID-based configs
- Memory Usage: ~50 bytes overhead per config + actual config size
- Initialization: ~1ms for 1000 configs on average hardware
- Serialization: ~10ms for 1000 configs to/from JSON
- ✅ Load configs during loading screens - One-time initialization cost
- ✅ Reuse
IConfigsProviderinstances - Don't recreate providers unnecessarily - ✅ Use
TryGetConfigfor optional configs - Avoid exceptions for missing data - ✅ Cache frequently accessed configs - Store references if accessed every frame
- ✅ Use ScriptableObjects for large datasets - Better for authoring and iteration
- ❌ Don't call
GetConfigsListrepeatedly - Cache the list if you need it multiple times - ❌ Don't modify configs at runtime - Treat them as immutable data
// Good: Cache frequently used configspublicclassEnemyAI:MonoBehaviour{privateEnemyConfig_config;// Cached referencevoidStart(){_config=ConfigManager.Instance.GetConfig<EnemyConfig>(enemyId);}voidUpdate(){// Use cached config - no lookup costtransform.Translate(Vector3.forward*_config.MoveSpeed*Time.deltaTime);}}// Bad: Lookup every framepublicclassSlowEnemyAI:MonoBehaviour{voidUpdate(){// DON'T DO THIS - expensive lookup every frame!varconfig=ConfigManager.Instance.GetConfig<EnemyConfig>(enemyId);transform.Translate(Vector3.forward*config.MoveSpeed*Time.deltaTime);}}Problem: Type was not registered as a singleton
Solution: Use GetConfig<T>(id) for collection configs, or register as singleton with AddSingletonConfig<T>
// Wrong - EnemyConfig is a collection, not singletonvarenemy=provider.GetConfig<EnemyConfig>();// ❌ Throws exception// Correct waysvarenemy=provider.GetConfig<EnemyConfig>(1);// ✅ Get by IDvarallEnemies=provider.GetConfigsList<EnemyConfig>();// ✅ Get allProblem: Multiple entries with the same key in ConfigsScriptableObject
Solution: Ensure each key is unique in the Inspector
// In Inspector, make sure you don't have:// Key: 1, Value: Enemy1// Key: 1, Value: Enemy2 // ❌ Duplicate key!// Instead use unique keys:// Key: 1, Value: Goblin // ✅// Key: 2, Value: Orc // ✅Problem: Config type is not marked as [Serializable]
Solution: Add [Serializable] attribute or exclude with [IgnoreServerSerialization]
// WrongpublicclassMyConfig{}// ❌ Not serializable// Correct options[Serializable]publicclassMyConfig{}// ✅ Will be serialized[IgnoreServerSerialization]publicclassEditorOnlyConfig{}// ✅ Will be excludedProblem: ConfigsSerializer requires Newtonsoft.Json
Solution: Install via Package Manager
- Open Package Manager
- Search for
Newtonsoft Json - Install
com.unity.nuget.newtonsoft-json
Problem: ScriptableObject changes not reflected at runtime
Solution:
- Check that you're loading the correct asset reference
- Ensure the asset is saved after changes
- For runtime changes, use
ConfigsProvider.UpdateTo()instead
Problem: Slow initialization with thousands of configs
Solution:
- Use
ConfigsScriptableObjectfor better loading performance - Consider lazy loading patterns for very large datasets
- Split large config sets into multiple smaller ones
Q: What's the minimum Unity version?
A: The package.json specifies Unity 6000.0 (Unity 6), but it may work with earlier versions. The package uses standard C# features available in Unity 2021.3+.
Q: Can I use this with Addressables?
A: Yes! Load your ConfigsScriptableObject via Addressables and feed it to the provider:
varhandle=Addressables.LoadAssetAsync<EnemyConfigs>("enemy-configs");varconfigs=awaithandle.Task;provider.AddConfigs(e =>e.Id,configs.Configs.Select(p =>p.Value).ToList());Q: How do I handle config validation?
A: Implement validation in your config classes or use Unity's OnValidate:
[Serializable]publicclassEnemyConfig{publicintHealth;publicboolIsValid=>Health>0;}// In ScriptableObjectpublicclassEnemyConfigs:ConfigsScriptableObject<int,EnemyConfig>{voidOnValidate(){foreach(varconfiginConfigs){if(!config.Value.IsValid)Debug.LogError($"Invalid config: {config.Key}");}}}Q: Is this thread-safe?
A: No, the current implementation is not thread-safe. Use it from the main thread only or implement your own synchronization.
Q: Can I modify configs at runtime?
A: Configs should be treated as immutable. For dynamic changes, use UpdateTo() to replace the entire config set atomically.
Q: How do I handle missing optional configs?
A: Use TryGetConfig instead of GetConfig:
if(provider.TryGetConfig<BossConfig>(bossId,outvarbossConfig)){// Boss has custom configSpawnBoss(bossConfig);}else{// Use default boss behaviorSpawnDefaultBoss();}Q: Can I nest config objects?
A: Yes, as long as all nested types are [Serializable]:
[Serializable]publicclassEnemyConfig{publicintId;publicStatsBaseStats;// ✅ Nested serializable objectpublicList<Ability>Abilities;// ✅ List of serializable objects}[Serializable]publicclassStats{publicintHealth;publicfloatSpeed;}Q: How do I version my configs for compatibility?
A: Use the version string in serialization and implement migration logic:
varjson=serializer.Serialize(provider,"2.1.0");varrestored=serializer.Deserialize<ConfigsProvider>(json);// Check version and migrate if neededif(restored.Version<expectedVersion){MigrateConfigs(restored);}We welcome contributions! Here's how you can help:
- Use GitHub Issues
- Include Unity version, package version, and minimal reproduction steps
- For performance issues, include profiler data if possible
- Clone the repository
- Open in Unity 6000.0+
- Run tests in
Tests/Editor/ - Make your changes
- Ensure all tests pass
- Submit a pull request
- Follow existing code style
- Add unit tests for new features
- Update documentation for API changes
- Use clear, descriptive commit messages
- Performance optimizations
- Additional serialization formats
- More comprehensive examples
- Documentation improvements
- Unit test coverage
This project is licensed under the MIT License - see the LICENSE.md file for details.
- ✅ Commercial use - Use in commercial projects
- ✅ Modification - Modify the source code
- ✅ Distribution - Share with others
- ✅ Private use - Use for personal projects
- ❗ Liability - No warranty provided
- ❗ Attribution - Must include license notice
Made with ❤️ for the Unity community