Skip to content

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:

FilePurpose
StreamingAssets/game_config.jsonThe JSON source file
GameConfigData.csRoot C# container class
GameConfigManager.csSingleton that loads and caches configs
LevelDefinitionData.csLevel 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 inheritance
foreach (var entry in entries)
{
if (!string.IsNullOrEmpty(entry.extendFromLevelId))
{
var resolved = ResolveInheritance(entry);
_lookup[entry.levelId] = resolved;
}
}

The ResolveInheritance() method:

  1. Gets the base level
  2. Recursively resolves the base’s inheritance first (supports multi-level inheritance)
  3. Creates a merged LevelDefinition with base values overridden by child values
  4. Uses “child value if set (> 0 or non-empty), otherwise base value” logic

Inheritance Rules

Property TypeRule
Numeric (int, float)Use child if > 0, else use base
StringUse child if non-empty, else use base
ListsUse 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 TypeRuntime TypeConversion
availableCategories: ["CAKE", "DRINK"]List<CakeCategory>Enum.TryParse()
catSpawnsConfigList<LevelConfigCatSpawnInfo>catId resolved via CatDatabase
availableOrderItemTypesList<OrderItemTypeWeight>itemType string → enum
autoInventoryIncrementSpeedPerCategoryDictionary<string, float>List → Dictionary

Where Config Values Are Used

GlobalGameSettings

Location: GameConfigManager.GlobalSettings

PropertyUsed ByPurpose
defaultCatPatienceOrderingCatBase patience time for cats
defaultPatBoostTimeOrderingCatTime added when patting
defaultMaxPatsOrderingCatMaximum pat count
maxCakeInventoryCountInventoryServingCounterMax items per counter
drinkHoldToCreateSpeedDrink machinePour speed multiplier
uniqueCatSpawnChanceMultiplierLevelSpawnGeneratorUnique cat spawn rate
snackCookingTimeCooking systemBase cook time
bigBakedCookingTimeMultiplierCooking systemLarge item cook time

LevelConfig → CatSpawnManager

File: CatSpawnManager.cs

// Configure table count from level
public void ConfigureTablesForLevel(LevelConfig level)
{
catPositionsLayout.SetActiveTableCount(level.numberOfTables);
}
// Initialize adaptive spawning with level config
public 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 distribution
float levelDurationSeconds = levelConfig.timeLimit;
// Cat spawn proportions and timing
List<LevelConfig.LevelConfigCatSpawnInfo> catPool = CreateCatPool(levelConfig.catSpawnsConfig, catDatabase);
// Filter available cakes by category
availableCakes = 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 chances
for (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 are
float adjustedPatience = catData.baseOrderTime * levelConfig.catBaseOrderTimeMultiplier;

LevelConfig → Scoring & Prestige

File: GameRunManager / ScoringManager

// After level completion
int prestige = levelConfig.basePrestigeGivenUponCompletion;
// Time bonus check
if (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

  1. Check catSpawnsConfig has entries with matching catId in CatDatabase
  2. Verify proportionNumber > 0 for each cat
  3. Check numberOfTables > 0

Inheritance Not Working

  1. Verify extendFromLevelId matches an existing levelId exactly
  2. Check for circular references
  3. Remember: child values must be > 0 or non-empty to override

Wrong Difficulty

  1. Check catBaseOrderTimeMultiplier (lower = less patient cats)
  2. Verify scoreRequired and timeLimit are set correctly
  3. Check star level multipliers in restaurant config