C# SDK Overview

C# SDK Overview
Section titled “C# SDK Overview”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
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: 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 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 integer type, float, double, 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"See Also
Section titled “See Also”- Documentation: https://docs.shadop.dev/archmage/overview-cs/sdk-cs/
- Source Code: https://github.com/shadowopera/sdk-cs