Game Config Parsing System
This document explains how game_config.json is parsed into runtime configurations and where those values are used.
Overview
The config system loads game settings from JSON and distributes them to various gameplay managers. The key files are:
| File | Purpose |
|---|---|
StreamingAssets/game_config.json | The JSON source file |
GameConfigData.cs | Root C# container class |
GameConfigManager.cs | Singleton that loads and caches configs |
LevelDefinitionData.cs | Level definition classes with inheritance |
JSON Structure
{ "version": "1.0.0", "globalSettings": { ... }, "itemPrices": { "entries": [...] }, "uniqueCatsConfig": { "entries": [...] }, "levelDefinitions": { "entries": [...] }, "restaurants": [...]}Loading Flow
1. GameConfigManager.Awake() ↓2. LoadConfiguration() ├── Priority 1: RemoteGameConfigManager (if available) └── Priority 2: StreamingAssets/game_config.json (fallback) ↓3. JsonUtility.FromJson<GameConfigData>(jsonText) ↓4. configData.BuildLookups() ├── itemPrices.BuildLookup() ├── uniqueCatsConfig.BuildLookup() └── levelDefinitions.BuildLookup() // Resolves inheritance here! ↓5. OnConfigLoaded?.Invoke()Level Definition Inheritance
The most powerful feature is level inheritance via extendFromLevelId. This allows child levels to inherit all values from a base level, then override only what’s different.
JSON Example
{ "levelId": "BaseLevel", "levelName": "Base Level Template", "scoreRequired": 50, "timeLimit": 120.0, "catSpawnsConfig": [...], "adaptiveSpawnConfig": {...}, "numberOfTables": 3 // ... all other properties},{ "levelId": "Level1Config", "extendFromLevelId": "BaseLevel", // <-- Inherits from BaseLevel "levelName": "Day 1", "scoreRequired": 300, "timeLimit": 45.0 // Only overrides what's different!}Resolution Algorithm
In LevelDefinitionsData.BuildLookup():
// First pass: Add all entries to lookup (unresolved)foreach (var entry in entries){ _lookup[entry.levelId] = entry;}
// Second pass: Resolve inheritanceforeach (var entry in entries){ if (!string.IsNullOrEmpty(entry.extendFromLevelId)) { var resolved = ResolveInheritance(entry); _lookup[entry.levelId] = resolved; }}The ResolveInheritance() method:
- Gets the base level
- Recursively resolves the base’s inheritance first (supports multi-level inheritance)
- Creates a merged
LevelDefinitionwith base values overridden by child values - Uses “child value if set (> 0 or non-empty), otherwise base value” logic
Inheritance Rules
| Property Type | Rule |
|---|---|
| Numeric (int, float) | Use child if > 0, else use base |
| String | Use child if non-empty, else use base |
| Lists | Use child if non-empty (count > 0), else use base |
| Objects (adaptiveSpawnConfig) | Use child if meaningful (adaptivenessLevel > 0), else use base |
Circular Inheritance Protection
The system tracks which levels are currently being resolved in _resolving HashSet to prevent infinite loops:
if (_resolving.Contains(child.levelId)){ Debug.LogError($"Circular inheritance detected for level '{child.levelId}'"); return child;}_resolving.Add(child.levelId);Converting JSON to Runtime LevelConfig
When GameConfigManager.GetLevelConfig(levelId) is called:
public LevelConfig GetLevelConfig(string levelId){ // 1. Check cache if (levelConfigCache.TryGetValue(levelId, out var cached)) return cached;
// 2. Get resolved definition (with inheritance applied) LevelDefinition definition = configData.levelDefinitions.GetDefinition(levelId);
// 3. Convert to runtime LevelConfig LevelConfig config = CreateLevelConfigFromDefinition(definition);
// 4. Cache and return levelConfigCache[levelId] = config; return config;}Key Conversions
| JSON Type | Runtime Type | Conversion |
|---|---|---|
availableCategories: ["CAKE", "DRINK"] | List<CakeCategory> | Enum.TryParse() |
catSpawnsConfig | List<LevelConfigCatSpawnInfo> | catId resolved via CatDatabase |
availableOrderItemTypes | List<OrderItemTypeWeight> | itemType string → enum |
autoInventoryIncrementSpeedPerCategory | Dictionary<string, float> | List → Dictionary |
Where Config Values Are Used
GlobalGameSettings
Location: GameConfigManager.GlobalSettings
| Property | Used By | Purpose |
|---|---|---|
defaultCatPatience | OrderingCat | Base patience time for cats |
defaultPatBoostTime | OrderingCat | Time added when patting |
defaultMaxPats | OrderingCat | Maximum pat count |
maxCakeInventoryCount | InventoryServingCounter | Max items per counter |
drinkHoldToCreateSpeed | Drink machine | Pour speed multiplier |
uniqueCatSpawnChanceMultiplier | LevelSpawnGenerator | Unique cat spawn rate |
snackCookingTime | Cooking system | Base cook time |
bigBakedCookingTimeMultiplier | Cooking system | Large item cook time |
LevelConfig → CatSpawnManager
File: CatSpawnManager.cs
// Configure table count from levelpublic void ConfigureTablesForLevel(LevelConfig level){ catPositionsLayout.SetActiveTableCount(level.numberOfTables);}
// Initialize adaptive spawning with level configpublic void InitializeAdaptiveSpawning(LevelConfig levelConfig, PerfectionManager perfectionMgr){ if (levelConfig.adaptiveSpawnConfig != null && levelConfig.adaptiveSpawnConfig.adaptivenessLevel > 0.0f) { _useAdaptiveSpawning = true; _adaptiveController = new AdaptiveSpawnController( levelConfig.adaptiveSpawnConfig, // Uses adaptiveSpawnConfig from JSON levelConfig, perfectionManager, this, TotalCatPositions ); }}LevelConfig → LevelSpawnGenerator
File: LevelSpawnGenerator.cs
The spawn generator uses these config values:
// Time limit for spawn distributionfloat levelDurationSeconds = levelConfig.timeLimit;
// Cat spawn proportions and timingList<LevelConfig.LevelConfigCatSpawnInfo> catPool = CreateCatPool(levelConfig.catSpawnsConfig, catDatabase);
// Filter available cakes by categoryavailableCakes = cakeDatabase.CakesPerCategory .Where(categoryList => levelConfig.availableCategories.Contains(categoryList.Category)) .SelectMany(categoryList => categoryList.Cakes .Take(levelConfig.maxNumberOfFoodItemsPerCategory)) // Uses maxNumberOfFoodItemsPerCategory .ToList();LevelConfig → Order Generation
File: LevelSpawnGenerator.GenerateCakeOrder()
// Multi-cake order chancesfor (int cakeNum = 2; cakeNum <= config.maxCakesPerOrder; cakeNum++){ if (cakeNum == 2) { cakeChance = Mathf.Lerp( config.secondCakeBaseChance, // From JSON config.secondCakeMaxChance, // From JSON normalizedProgress); } else { // Uses thirdCakeBaseChance, thirdCakeMaxChance, additionalCakeChanceDecrement }}LevelConfig → InventoryManager
File: InventoryManager.cs
private void InitializeInventory(){ LevelConfig levelConfig = _gameManager.CurrentLevel;
// Filter cakes by available categories List<CakeDatabase.CakeListPerCategory> categoryData = cakeDatabase.CakesPerCategory .Where(item => levelConfig.availableCategories.Contains(item.Category)) .ToList();
// Limit items per category List<CakeData> cakesToShow = categoryGroup.Cakes .Take(levelConfig.maxNumberOfFoodItemsPerCategory) // From JSON .ToList();}LevelConfig → Cat Patience (OrderingCat)
File: OrderingCat.cs
// catBaseOrderTimeMultiplier affects how patient cats arefloat adjustedPatience = catData.baseOrderTime * levelConfig.catBaseOrderTimeMultiplier;LevelConfig → Scoring & Prestige
File: GameRunManager / ScoringManager
// After level completionint prestige = levelConfig.basePrestigeGivenUponCompletion;
// Time bonus checkif (timeRemaining / levelConfig.timeLimit >= levelConfig.timeBonusThreshold){ prestige += levelConfig.timeBonusPrestige;}ItemPrices → Order Score
File: OrderData.cs
public float GetPrice(){ float total = 0; foreach (var item in items) { // Uses GameConfigManager.GetItemPrice() total += GameConfigManager.Instance.GetItemPrice(item.cakeId, defaultPrice: 10f); } return total;}AdaptiveSpawnConfig Details
The adaptive spawn system dynamically adjusts cat spawn timing based on player performance:
"adaptiveSpawnConfig": { "adaptivenessLevel": 0.8, // 0 = rigid, 1 = fully adaptive "spawnTiming": { "minSpawnDelay": 2.0, // Minimum seconds between spawns "maxSpawnDelay": 10.0, // Maximum seconds between spawns "medianSpawnDelay": 4.0, // Target delay "spawnDelayStdDev": 1.5 // Randomization spread }, "usePerformanceAdjustment": true, // Adjust based on perfection rank "rankDelayMultipliers": [1.5, 1.2, 1.0, 0.8, 0.6], // D, C, B, A, S rank multipliers "rankInfluence": 0.4, "useSuccessRateAdjustment": true, // Adjust based on recent success "successRateThresholds": [...], "useOccupancyAdjustment": true, // Slow down when tables are full "occupancyThreshold": 0.75, "blockSpawnWhenAllFull": true, "firstSpawnImmediate": true, "warmupPeriod": 10.0 // Reduced adaptiveness at level start}Restaurant & Star Level System
Restaurants can reference level configs and apply multipliers:
{ "restaurantId": "REST_ItalianCafe", "michelinStarLevels": [ { "starLevelNumber": 1, "scoreRequiredMultiplier": 1.0, // Applied to level scoreRequired "timePerLevelMultiplier": 1.0, // Applied to level timeLimit "dayLevels": [ { "dayLevelNumber": 1, "levelConfigId": "Level1Config" }, { "dayLevelNumber": 2, "levelConfigId": "Level2Config" } ] }, { "starLevelNumber": 2, "scoreRequiredMultiplier": 1.2, // 20% harder "dayLevelsUseListFromStarLevel": 1 // Reuse day list from star 1 } ]}Debugging Config Loading
Enable detailed logging in GameConfigManager:
[ContextMenu("Log Current Config Info")]private void LogConfigInfo(){ Debug.Log($"Loaded from: {LoadedFromPath}"); Debug.Log($"Version: {configData.version}"); Debug.Log($"Level Definitions: {configData.levelDefinitions?.Count}"); Debug.Log($"Restaurants: {configData.restaurants?.Count}");}Common Issues
Cat Not Spawning
- Check
catSpawnsConfighas entries with matchingcatIdin CatDatabase - Verify
proportionNumber > 0for each cat - Check
numberOfTables > 0
Inheritance Not Working
- Verify
extendFromLevelIdmatches an existinglevelIdexactly - Check for circular references
- Remember: child values must be > 0 or non-empty to override
Wrong Difficulty
- Check
catBaseOrderTimeMultiplier(lower = less patient cats) - Verify
scoreRequiredandtimeLimitare set correctly - Check star level multipliers in restaurant config