C# SDK Overview

This is the runtime library for the Archmage game configuration system, which loads and manages configurations from JSON files with support for i18n, cross-table references, durations, and data types ranging from primitives to recursive structures.
It is built around the concept of an Atlas — a registry that maps named keys to configurations. Each key points to one or more JSON files; Archmage reads and deserializes them into generated C# objects, resolves cross-table references, and calls post-load hooks.
Key features:
- I18n for multi-language text management with automatic fallback
- XRef for cross-table reference resolution via
IAtlas.BindRefs - Duration with nanosecond precision and compact JSON array encoding
- Whitelist / blacklist to load only a subset of items
- Layered overrides: additional directories or filesystems supply JSON that is merged into the base data at load time, field by field
- Unity support: built-in adapters for Addressables, Resources, and StreamingAssets; Inspector dropdowns for config ID fields, sourced from the loaded atlas, for easy selection
Requirements
Section titled “Requirements”- Unity 6000.3 or later
- com.unity.nuget.newtonsoft-json 3.2.2
- net8.0, netstandard2.1 or later
- Newtonsoft.Json 13.0.4
Installation
Section titled “Installation”Via GitHub — In the Package Manager window, click + → Add package from git URL, and enter:
https://github.com/shadowopera/sdk-cs.git?path=unity/dev.shadop.archmageOr via OpenUPM:
openupm add dev.shadop.archmageIf your project uses .asmdef files, add the following assembly references:
Shadop.Archmage.SdkShadop.Archmage.Sdk.UnityShadop.Archmage.Sdk.Unity.Addressables(optional, only if using Addressables)Shadop.Archmage.Sdk.Unity.Editor(optional)
.NET (via NuGet)
Section titled “.NET (via NuGet)”dotnet add package Shadop.ArchmageGetting Started
Section titled “Getting Started”Unity (Addressables)
Section titled “Unity (Addressables)”A complete working example is in ConfLoader.cs, covering Addressables, Resources, and StreamingAssets — with sync/async variants, concurrent loading, and I18n setup. For Inspector integration, see ArchmageEditorTools.cs, which demonstrates config ID dropdown wiring.
The recommended starting point:
using Shadop.Archmage.Sdk;
// ConfigAtlas is generated by Archmagevar atlas = new ConfigAtlas();var options = new AtlasOptions() .WithLogger(new UnityAtlasLogger()) .WithJsonSettings(UnityJsonSettingsFactory.Create()) .WithFS(new UnityAddressablesFS());
await Archmage.LoadAtlasAsync( "Assets/Configs/atlas.json", "Assets/Configs", atlas, options);using Shadop.Archmage.Sdk;
// ConfigAtlas is generated by Archmagevar atlas = new ConfigAtlas();Archmage.LoadAtlas("configs/atlas.json", "configs/", atlas);Concepts
Section titled “Concepts”atlas.json is generated by Archmage. It declares how each config key maps to its JSON files using one of three strategies:
| Strategy | Shape | Behavior |
|---|---|---|
| unique | key → "file.json" |
Deserializes one file into the config object |
| variant | key → { "/": "file.json", "alt": "file-alt.json" } |
Selects one variant by case; "/" is the default |
| many | key → ["a.json", "b.json"] |
Deserializes and merges multiple files in order |
Example atlas.json:
{ "unique": { "hero": "hero.json", "item": "clutter/item.json" }, "variant": { "game": { "/": "game.json", "hard": "game_hard.json" } }, "many": { "weapon": [ "vtbl/weapon-sword.json", "vtbl/weapon-staff.json" ] }}Loading Configs
Section titled “Loading Configs”Loading proceeds in the following steps:
- Parse
atlas.json - Apply
AtlasModifier(if set) - For each item: read files → deserialize → apply overrides
BindRefs()— resolve cross-table referencesOnLoaded()— post-load initialization
AtlasOptions
Section titled “AtlasOptions”Configure loading via the fluent AtlasOptions builder:
var opts = new AtlasOptions() // custom logger (default: console) .WithLogger(myLogger) // custom filesystem for reading base config files (default: System.IO) .WithFS(myFS) // load only these keys .WithWhitelist(new[] { "hero", "item" }) // skip these keys .WithBlacklist(new[] { "debug" }) // select a variant .WithVariant("game", "hard") // add an override directory .WithOverrideRoot("configs/override/") // add an override filesystem .WithOverrideFS(embeddedFS) // mutate atlas.json after parsing .WithAtlasModifier(atlasJson => { ... }) // custom Newtonsoft.Json settings .WithJsonSettings(customSettings);Whitelist / Blacklist — If a whitelist is set, only listed keys are loaded (blacklist is ignored). All keys must exist in the atlas or an exception is thrown.
Variant selection — A variant-mapped key loads its "/" variant unless WithVariant
selects another one. The variant in use is recorded in AtlasItem.Variant.
Override layers — Each WithOverrideRoot / WithOverrideFS call adds another
override source. When loading an item, each override source is checked in the order they
were added; any matching file is deserialized and its fields applied on top of the base
data. This is useful for environment-specific patches.
Field-level merge rules during override processing:
| Value in override | Behavior |
|---|---|
null |
Resets the target field to its default value or raise an exception |
| JSON object | Recursively merges — only fields present in the override are updated, others remain unchanged |
| Any other value | Overwrites the field |
Custom load strategy — By default, items are loaded one by one in alphabetical order.
WithLoadStrategy and WithAsyncLoadStrategy let you take control of that loop —
for example to load items in parallel:
// Parallel syncvar opts = new AtlasOptions().WithLoadStrategy((items, load) => Parallel.ForEach(items, kvp => load(kvp.Key, kvp.Value)));
// Parallel asyncvar opts = new AtlasOptions().WithAsyncLoadStrategy(async (items, loadAsync, ct) => await Task.WhenAll(items.Select(kvp => loadAsync(kvp.Key, kvp.Value, ct))));Custom File System
Section titled “Custom File System”Both WithFS and WithOverrideFS accept an IFS implementation. The default
filesystem reads from System.IO. You can supply a custom IFS to replace it or
to use as an override source — for example to load from embedded resources or
an in-memory dictionary:
class EmbeddedFS : IFS{ public bool DirectoryExists(string path) => true; public bool FileExists(string path) => /* check assembly resources */; public byte[] ReadAllBytes(string path) => /* load from resources */; public Task<byte[]> ReadAllBytesAsync(string path, CancellationToken ct) => /* async load */;}
var opts = new AtlasOptions().WithFS(new EmbeddedFS());Special Types
Section titled “Special Types”I18n — Localization
Section titled “I18n — Localization”I18n holds per-language translations and falls back to a default language when a key is missing.
var i18n = new I18n(fallbackLanguage: "en");i18n.MergeL10nFile("l10n/en.json", "en");i18n.MergeL10nFile("l10n/zh-CN.json", "zh-CN");
i18n.Text("ui.ok", "zh-CN"); // → "确认"i18n.Text("ui.ok", "ja"); // → falls back to "OK"In generated config classes, localized fields are typed as L10n. In JSON they are
represented as strings (e.g., "ui.ok"); accessing .Text on an L10n field looks up
that key in a shared I18n instance. Set L10n.GetI18n and L10n.GetPreferredLanguage
to configure the lookup before use.
L10n.GetI18n = () => i18n;L10n.GetPreferredLanguage = () => "zh-CN";
// Then in your code:string label = hero.Name.Text;XRef — Cross-table Reference
Section titled “XRef — Cross-table Reference”XRef<V, T> pairs a config ID (CfgId) with a resolved reference (Ref) set during BindRefs.
// In generated config class:public XRef<HeroCfgId, HeroCfg> Boss { get; set; }
// After loading:var boss = atlas.HeroTable[1].Boss.Ref; // resolved objectDuration
Section titled “Duration”A nanosecond-precision duration type. It serializes as a compact integer array in JSON (e.g., [0, 5] = 5 seconds).
Duration d = Duration.Second * 90 + Duration.Millisecond * 500;d.ToString(); // "1m30s500ms"d.Seconds(); // 90.5d.Milliseconds(); // 90500d.ToTimeSpan(); // TimeSpanArithmetic operators (+, -, *, /, %) and comparisons are supported.
A color type with R, G, B, A byte channels. In Unity, .ToColor()
converts it to UnityEngine.Color.
var color = Rgba.Parse("#FF8000"); // R=255, G=128, B=0, A=255color.ToString(); // "#FF8000"MinMax
Section titled “MinMax”MinMax<T> is a range bounded by Min and Max. The Sample extension methods draw a random value from the range. T may be any built-in numeric type, or Duration.
WeightedPool
Section titled “WeightedPool”WeightedPool<T> holds parallel Items and Weights arrays. The Sample / SampleIndex
extension methods draw an item (or its index) at random with probability proportional to its
weight.
Vec2<T>, Vec3<T>, Vec4<T> are typed vectors. Fields are accessed as .X, .Y, .Z, .W.
Tup1–Tup7 are heterogeneous tuples. They serialize as JSON objects with keys
item0, item1, etc. (0-based). Fields are accessed as .Item0, .Item1, etc.,
and deconstruction is supported.
Data Versioning
Section titled “Data Versioning”atlas.json can carry a version block with VCS metadata (branch, commit ID, timestamp, author). After loading, it is available on the atlas:
{ "version": { "branch": "main", "id": "a1b2c3d4e5f6...", "shortId": "a1b2c3d", "timestamp": "2025-01-01T00:00:00Z" }, ...}var ver = atlas.DataVersion; // VersionInfo?, null if not presentver?.Branch // "main"ver?.ShortID // "a1b2c3d"