The C# SDK is the runtime library through which C# applications load and access config data exported by Archmage.
Archmage is a configuration solution for game development: specifications for how to structure config data, define fields, and fill in each value; pipelines that export runtime data and generate strongly-typed code; multi-language SDKs for loading and accessing that data at runtime; and a collaborative editing workflow for teams.
The SDK is built around the concept of an Atlas — a registry that maps named keys to configurations. Each key is associated with one or more JSON files. At runtime, the SDK reads these files, deserializes them into instances of generated C# types, resolves cross-table references, and calls post-load hooks.
Key features
- I18n — multi-language text management with automatic fallback
- XRef — cross-table reference resolution via
IAtlas.BindRefs - Duration — nanosecond precision; formats as human-readable strings such as
1s200ms - MinMax — random value selection within a range
- WeightedPool — weighted random selection with probability proportional to item weight
- Variants — switch an item to an alternative data set at load time via
WithVariant - Whitelist/Blacklist — load only a subset of items
- Layered overrides — merge files with matching relative paths from additional override sources (a directory path or a custom file system) into the base configs, field by field, at load time
- Synchronous and asynchronous loading — progress reporting, cancellation, and pluggable strategies for parallel loading
- Pluggable file system — load from embedded resources, in-memory data, or any other source via
IFS - Versioning — VCS metadata (branch, commit, timestamp, etc.), when present in
atlas.json, is available on the loaded atlas - Unity support — built-in adapters for Addressables, Resources, and
StreamingAssets; Inspector dropdowns for config ID fields, populated from the loaded atlas, for easy selection
- Unity 6000.3 or later
- com.unity.nuget.newtonsoft-json 3.2.2
- net8.0, netstandard2.1 or later
- Newtonsoft.Json 13.0.4
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.archmage
Or via OpenUPM:
openupm add dev.shadop.archmageNote
Unity Signature Warning: Unity may display a "Missing Signature" warning. This is expected for OpenUPM packages. Archmage is safe to use — the warning does not affect functionality. Simply proceed with your development as usual.
If 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)
dotnet add package Shadop.ArchmageA 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 Archmage
var 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 Archmage
var atlas = new ConfigAtlas();
Archmage.LoadAtlas("configs/atlas.json", "configs/", atlas);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 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
Configure loading via the fluent AtlasOptions builder:
var opts = new AtlasOptions()
// custom logger (default: stderr; silent in Unity)
.WithLogger(myLogger)
// replace the default filesystem (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 non-empty 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 raises 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 sync
var opts = new AtlasOptions().WithLoadStrategy((items, load) =>
Parallel.ForEach(items, kvp => load(kvp.Key, kvp.Value)));
// Parallel async
var opts = new AtlasOptions().WithAsyncLoadStrategy(async (items, loadAsync, ct) =>
await Task.WhenAll(items.Select(kvp => loadAsync(kvp.Key, kvp.Value, ct))));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());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<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 objectA 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.5
d.Milliseconds(); // 90500
d.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=255
color.ToString(); // "#FF8000"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 integer type, float, double, or Duration.
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.
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 present
ver?.Branch // "main"
ver?.ShortID // "a1b2c3d"- Documentation: https://docs.shadop.dev/archmage/overview-cs/sdk-cs/
- Source Code: https://github.com/shadowopera/sdk-cs
sdk-cs/
├── src/Archmage/
│ ├── Sdk/ # C# runtime source (canonical)
│ │ └── Unity/ # Unity adapters (FS, logger, JSON settings, type extensions)
│ │ └── Addressables/ # Addressables FS adapter
│ └── Editor/Unity/ # Inspector dropdowns for config ID fields
├── unity/
│ ├── ArchmageDev/ # Unity demo & development project
│ └── dev.shadop.archmage/ # Unity package (OpenUPM)
├── tests/ # xunit.v3 tests
│ ├── Conf/ # Generated config code
│ ├── testdata/ # atlas.json and config JSON
│ ├── override/ # Override-layer JSON
│ └── golden/ # Expected DumpAtlas output
├── scripts/ # Unity sync, version bump, release
│ └── rsync-unity.sh # src/ → unity/dev.shadop.archmage/ sync
└── docs/ # Documentation site (Starlight)
dotnet build src/Archmage/Archmage.csproj
dotnet test tests/Archmage.Tests.csproj
dotnet test tests/Archmage.Tests.csproj --filter "FullyQualifiedName~TestName"
UPDATE_GOLDEN=1 dotnet test tests/Archmage.Tests.csproj # regenerate golden filesApache 2.0. See LICENSE for details.
