Tabellensyntax
Eine SheetForge-Tabelle ist selbstbeschreibend: Spalte A ist für Markierungen reserviert, die eigentlichen Daten beginnen in Spalte B. Zeilen werden anhand ihrer Markierung identifiziert, nicht anhand ihrer Position, sodass Sie überall Kommentarzeilen einfügen können, ohne dass etwas kaputtgeht.
Um eine bestehende Tabelle anzupassen, fügen Sie eine Markierungsspalte vor Ihren Daten ein und ergänzen die drei Markierungszeilen. Bestehende Datenspalten bleiben unverändert.
Markierungen (Spalte A)
| Spalte A | Bedeutung |
|---|---|
# | Kommentarzeile – wird vollständig ignoriert, beim Round-Trip wortgetreu erhalten. |
@name | Feldnamen-Zeile (ein Name pro Spalte). |
@type | Feldtyp-Zeile. |
@desc | Beschreibungszeile – Codegen übernimmt sie in XML-Doc-Kommentare und Inspector-Tooltips. |
@overlap | (optional) Duplikat-Richtlinie pro Spalte — true (zulassen, Standard) / false (Werteindeutigkeit erzwingen). |
@style | (optional) Anzeigemetadaten für die Tabelle — eine Gruppenbezeichnung und eine Farbe für diese Tabelle. Siehe unten. |
@enum | (optional) Markiert die gesamte Tabelle als Enum-Definitionen statt als Datentabelle. Siehe unten. |
@loc | (optional) Markiert die Tabelle als Lokalisierungstabelle – ihre Zellen benennen den Sprachcode jeder Spalte. Siehe unten. |
@yourMarker | (optional, per Plugin registriert) Eine benutzerdefinierte Strukturmarkierung — siehe unten. |
| (leer) | Datenzeile. |
@name,@type,@descsind erforderlich;@overlap,@styleund alle benutzerdefinierten Markierungen sind optional.- Markierungszeilen dürfen in beliebiger Reihenfolge erscheinen, solange sie über den Datenzeilen stehen.
- Eine unbekannte
@markerist ein Fehler, mit einem Vorschlag für die nächstliegende Übereinstimmung („meinten Sie@desc?"). Registrierte benutzerdefinierte Markierungen werden in den Vorschlagspool aufgenommen. - Daten in einer Spalte ohne
@name/@type-Kopfzeile sind ein Fehler (Schutz vor verwaisten Daten – stiller Datenverlust ist nie erlaubt).
Beispiel (Spalten dargestellt als A | B | C | D):
# | Item definitions — hand-edited by design team
@name | codeName | displayName | price
@type | RecordId | string | int=10
@desc | unique key | shown in UI | shop price (gold)
| item.sword | Sword | 120
| item.potion | Potion |(Die leere price-Zelle von item.potion materialisiert den expliziten Standardwert 10.)
Typsystem
Jeder Typ ist selbstbeschreibend – ein Leser erkennt allein anhand der @type-Zelle, was eine Spalte enthält.
| Notation | Bedeutung |
|---|---|
int float bool string | Eingebaute Skalare. |
Enum<DamageType> | Ein C#-enum — entweder in einer Enum-Definitionstabelle definiert (siehe unten, kein Code nötig) oder von einem Plugin registriert (EnumRegistry). Membernamen werden validiert, bei Tippfehlern gibt es Vorschläge für die nächstliegende Übereinstimmung. |
List<T> | Eine Liste — Elementtrennzeichen ;, Elemente werden getrimmt, ein leeres Element ist ein Fehler, eine leere Zelle ist eine leere Liste. |
RecordId | Die Schlüsselspalte dieses Tabs — ein selbstidentifizierender String (z. B. item.sword). Immer ein erforderlicher Skalar. Empfohlener Spaltenname: codeName. |
IntId | Der sekundäre Integer-Schlüssel dieses Tabs — höchstens einer pro Tab, erforderlicher Skalar, für Runtime-/Save-/Backend-IDs. Empfohlener Spaltenname: id. Ein Tab kann über RecordId, IntId oder beide schlüsseln. |
RecordId@Effects | Eine Referenz auf einen Datensatz im Tab Effects über dessen String-Schlüssel — integritätsgeprüft (Ziel-Tab existiert, besitzt eine Schlüsselspalte, die Id löst sich auf; bei Tippfehlern gibt es Vorschläge). |
IntId@Effects | Eine Referenz auf einen Datensatz im Tab Effects über dessen Integer-Schlüssel — volle Parität zu RecordId@Effects: auf dieselbe Weise integritätsgeprüft (Ziel-Tab existiert, besitzt eine IntId-Spalte, die Id löst sich auf), mit einem Vorschlag für den nächstliegenden Integer bei einem Fehltreffer. Werte werden mit int.ToString kanonisiert, sodass ein von Hand eingegebenes 007 sich als 7 auflöst. |
AssetRef@Icons | Eine Referenz auf ein Asset in der Addressables-Gruppe Icons — auf Existenz gegen den Katalog geprüft. Ein Sub-Asset (ein Sprite innerhalb einer Textur, ein Material innerhalb einer Schriftart) wird als parent[sub] adressiert — die Adresse, die Addressables einem Sub-Objekt-Eintrag gibt, z. B. atlas[sword] — und wird unter diesem Schlüssel validiert, gebackt (SubObjectName) und exportiert. |
AssetRef@Icons<Sprite> | Dieselbe Referenz, auf einen Asset-Typ beschränkt: Eine Adresse besteht nur, wenn sich das Asset — oder eines seiner Sub-Assets — als Sprite laden lässt. Der Name ist ein beliebiger von UnityEngine.Object abgeleiteter Asset-Typ, den das Projekt kennt (Engine oder eigener): der Kurzname, wenn genau ein Typ passt, sonst der vollständige Name (MyGame.ItemData). Codegen gibt AssetReferenceT<Sprite> aus; AssetRef@Icons ohne <…> bleibt uneingeschränkt; AssetRef<Sprite>@Icons wird abgelehnt, mit der korrekten Schreibweise als Vorschlag. Siehe Typisierte Asset-Referenzen weiter unten. |
LocRef@Strings | Eine Referenz auf einen Lokalisierungsschlüssel in der Lokalisierungstabelle Strings – integritätsgeprüft wie RecordId@Tab (Existenz, Vorschläge für die nächstliegende Übereinstimmung, Umbenennungs-Propagation, Picker, Dropdowns), mit einer Inline-Vorschau des Texts in der Quellsprache des Eintrags. Der Ziel-Tab muss @loc tragen (sonst LocRefTargetNotLocalizationSheet), und ein nacktes LocRef ohne @Target wird abgelehnt. List<LocRef@Strings> und LocRef@Strings? setzen sich wie gewohnt zusammen. Codegen gibt eine einfache LocRef-Struct aus – siehe Lokalisierungstabellen. |
Color · AnimationCurve · Gradient | Eingebaute visuelle Werttypen. Jeder hat eine kompakte Textform (unten), die das Data Studio und die Web-App mit einem nativen Farb-, Kurven- oder Verlaufs-Editor statt mit Rohtext bearbeiten; Codegen gibt UnityEngine.Color- / AnimationCurve- / Gradient-Felder aus. |
Modifier (Beispiel) | Ein per Plugin registrierter benutzerdefinierter Zelltyp (siehe Plugin-Erstellung) — z. B. die stat:op:value-Mini-Grammatik des Beispiels. CustomType@Target funktioniert allein durch Registrierung ebenso. Entscheidet sich das Plugin für IReferencingCellType, verhält sich diese Spalte exakt wie RecordId@Target — auf dieselbe Weise validiert, vorgeschlagen, umbenannt, gezeichnet und ausgewählt. |
Pair<T> (Beispiel) | Ein per Plugin registrierter Wrapper-Typ — eine generische Wertform MyWrapper<T>, die mehrere innere T-Werte in eine Zelle packt (z. B. Pair<int> = 1~2). Der innere Typ wird rekursiv aufgelöst, sodass Pair<RecordId@Effects>, Pair<Enum<DamageType>> und verschachteltes Box<Pair<int>> alle funktionieren. Siehe Plugin-Erstellung. |
<> und @ bedeuten unterschiedliche Dinge und bestehen nebeneinander: <> = Art/Wrapper (eingebautes List, oder ein Plugin-MyWrapper<T>), @ = Ziel. So ist List<RecordId@Effects> eine Liste von Referenzen, und Pair<RecordId@Effects> packt zwei Referenzen — beide in den Tab Effects. Der Integer-Schlüssel setzt sich auf dieselbe Weise zusammen: List<IntId@Effects> ist eine Liste von Integer-Schlüssel-Referenzen.
Wrapper-Typen (MyWrapper<T>)
Ein Plugin kann einen Wrapper registrieren — eine generische Wertform, die eine äußere Syntax (Trennzeichen, Arität) besitzt und den inneren Typ an den Core delegiert. Der Wrapper lässt sich mit jedem inneren Typ kombinieren. Alle darin enthaltenen Referenzen werden weiterhin validiert, bei Schlüssel-Umbenennung propagiert und bei Tab-Umbenennung umgeschrieben (vollständiges Pass-Through).
Ablehnungsregeln (konsistent mit List):
| Notation | Erlaubt? | Warum |
|---|---|---|
Pair<RecordId@Effects> · Pair<Enum<E>> · Box<Pair<int>> | Ja | Wrapper über einen Skalar, eine Referenz, ein enum oder einen weiteren Wrapper. |
List<Pair<int>> | Ja | Eine Liste von Kompositen. Das eigene Trennzeichen des Wrappers muss sich von ; (dem Listentrennzeichen) unterscheiden — das liegt in der Verantwortung der Plugin-Erstellung. |
Pair<List<int>> | Nein | Eine Liste darf nicht innerhalb eines Wrappers stehen (List bleibt flach und ganz außen, dieselbe Regel wie bei List<List<T>>). |
Pair<int>@Effects | Nein | Ein Wrapper ist eine Wertform; setzen Sie das @ stattdessen auf das innere Blatt (Pair<RecordId@Effects>). |
Pair<int?> · Pair<int=1> | Nein | Optionalität/Standardwerte sind eine Notation auf Feldebene, nicht Teil des inneren Typs. |
Erforderlich / optional / Standardwerte
| Notation | Bedeutung |
|---|---|
float (unmarkiert) | Erforderlich — eine leere Zelle ist ein Fehler (stille Kontamination wird bereits am Eingang blockiert). |
float? | Optional — eine leere Zelle materialisiert den Typ-Standardwert (0), markiert mit IsDefaulted. Gilt für die vier Skalare (int / float / bool / string) sowie für die drei visuellen Typen: Color? → transparentes Schwarz #00000000, AnimationCurve? → eine Kurve ohne Schlüsselpunkte, Gradient? → der weiße Verlauf `#FFFFFF@0,#FFFFFF@1 |
RecordId@Effects? · IntId@Effects? · AssetRef@Icons? | Optionale Referenz — eine leere Zelle materialisiert eine leere Referenz: „zeigt auf nichts", wobei Ziel-Tab/-Gruppe erhalten bleiben und die Zelle mit IsDefaulted markiert wird. Das ist keine kaputte Referenz — Referenz-Integritäts- und Asset-Schlüssel-Validierung überspringen sie, die Canvas zeichnet dafür keine Verbindung, und @overlap zählt zwei leere Referenzen nicht als Duplikate. Eine Zelle, die doch einen Wert trägt, wird genau wie zuvor validiert, sodass ein Tippfehler in einer optionalen Spalte weiterhin erkannt wird. |
RecordId@Effects= | Dasselbe explizit geschrieben: Ein leerer expliziter Standardwert ist gleichbedeutend mit dem bloßen ? oben. Ein nicht leerer Standardwert (RecordId@Effects=fire) wird weiterhin aufgelöst und weiterhin integritätsgeprüft. |
int=1 | Optional mit einem expliziten Standardwert — eine leere Zelle materialisiert 1. |
List<T> | Eine leere Zelle ist immer zulässig (leere Liste). |
Wo ? nicht akzeptiert wird, ist der Grund stets derselbe: Der Core kann keinen Wert aus dem Nichts erfinden, daher benötigen diese Typen ein explizites =default. Das betrifft:
Enum<T>?- einen benutzerdefinierten Plugin-Typ —
Modifier?, einschließlichModifier@Tab? - einen Wrapper —
Pair<int>?
Die Schlüsselspalten sind aus einem anderen Grund ausgeschlossen: Ein leerer Schlüssel würde Duplikate erzeugen. Daher werden auch RecordId? (die schlüssellose Selbstidentifikator-Form) und IntId? abgelehnt.
Weitere bewusst abgelehnte Notationen:
int?=1undRecordId@Effects?=fire—?und=bedeuten beide „optional", wählen Sie also eines.List<T>?— eine Liste erlaubt Leerheit bereits.List<List<T>>— keine verschachtelten Listen.Pair<int?>— Optionalität ist feldbezogen, nicht Teil eines inneren Typs.
Wertregeln
- bool: nur
true/false, bei der Eingabe wird Groß-/Kleinschreibung ignoriert; die kanonische Form ist Kleinschreibung. - Zahlen: immer
.als Dezimaltrennzeichen (unabhängig vom Gebietsschema). Komma-Dezimalzahlen,NaNundInfinitywerden am Eingang abgelehnt. - Floats im Round-Trip: Export erzeugt das kürzeste round-trip-fähige Format, sodass
1.0als1zurückkommen kann — der Wert bleibt exakt erhalten (semantischer Round-Trip). - Vergleiche von Markierungen und enum-Werten erfolgen ordinal (keine Überraschungen durch das Gebietsschema).
Typisierte Asset-Referenzen (AssetRef@Group<Type>)
AssetRef@Icons akzeptiert jede Adresse in der Gruppe. AssetRef@Icons<Sprite> schränkt das auf einen Asset-Typ ein, und diese Einschränkung wird an drei Stellen geprüft: Validierung, Codegenerierung und die Authoring-Oberfläche.
- Welche Namen sich auflösen. Der Typ ist ein beliebiger von
UnityEngine.Objectabgeleiteter Asset-Typ, den das Projekt laden kann — Engine-Typen (Sprite,Texture2D,AudioClip, eine abstrakte Basis wieTexture) ebenso wie Ihre eigenenScriptableObjects; es gibt keine Allowlist. Components und reine Editor-Typen kommen nicht infrage. Schreiben Sie den Kurznamen, wenn genau ein Typ ihn trägt, sonst den vollständigen Namen samt Namespace. Mehrdeutige Namen (AmbiguousAssetType, mit jedem Kandidaten aufgelistet) und unbekannte Namen (UnknownAssetType, mit einem Vorschlag für die nächstliegende Übereinstimmung) werden einmal pro Spalte gemeldet, auf der@type-Zeile. - Was besteht. Eine Adresse erfüllt die Einschränkung, wenn das Asset an dieser Adresse — oder eines seiner Sub-Assets — sich als dieser Typ laden lässt — sodass eine im Sprite-Modus importierte Textur
<Sprite>besteht, während eine gewöhnliche Textur pro Zelle alsAssetTypeMismatchgemeldet wird. Das Sub-Asset selbst ist alsparent[sub]adressierbar, und dieser Schlüssel wird nur gegen seinen eigenen Typ geprüft. - Ein Typ, den der generierte Code nicht referenzieren kann, wird abgelehnt. Ein Typ, der in einer vordefinierten Assembly liegt (
Assembly-CSharpund ihre Geschwister — jeder Skriptordner ohne Assembly-Definition), wird gefunden, aber alsAssetTypeNotReferenceablegemeldet, weil die generierte Begleitassembly diese Assemblies nicht referenzieren kann undAssetReferenceT<T>nicht kompilieren würde. Verschieben Sie den Typ in eine Assembly-Definition, oder lassen Sie das<…>weg. - Was Codegen ausgibt.
AssetReferenceT<global::UnityEngine.Sprite>für einen aufgelösten Typ,AssetReferencefür eine uneingeschränkte Spalte. Die Begleitassembly-Definition referenziert automatisch die Assembly, in der der Typ liegt, und der aufgelöste vollständige Name ist Teil des Schema-Fingerabdrucks, sodass eine Neuzuordnung des Namens den Code neu generiert. - Setzt sich zusammen wie jeder andere Typ:
AssetRef@Icons<Sprite>?,List<AssetRef@Icons<Sprite>>und ein Wrapper wiePair<AssetRef@Icons<Sprite>>funktionieren alle;AssetRef@Icons<>(leer),AssetRef@Ic<ons(eine spitze Klammer im Gruppennamen) undRecordId@Skills<X>(die Einschränkung gilt nur fürAssetRef) sind Syntaxfehler. - Das Spaltenformular des Data Studio hat eine Schaltfläche Typ…, die die Kandidatentypen auflistet und die
@type-Zelle für Sie umschreibt — siehe Data Studio.
Visuelle Werttypen (Color, AnimationCurve, Gradient)
Drei eingebaute Typen tragen Werte, die als Rohtext unlesbar sind. Ihre Textform ist so gestaltet, dass eine Person von Hand eine kurze Version eintippen kann, während jedes Werkzeug — die Editoren, Export, Push, die Web-App — stets die kanonische, vollständige Form schreibt, und ein Wert übersteht Tabelle → Unity → Tabelle ohne Verlust.
Die Trennzeichen sind bei allen dreien gleich und liegen eine Ebene unter dem Listentrennzeichen: Innerhalb eines Werts werden Einträge durch , getrennt, Felder innerhalb eines Eintrags durch :, Abschnitte durch |, und die Zeit eines Schlüsselpunkts wird mit @ angehängt. Elemente einer List<> werden weiterhin durch ; getrennt, und keine der drei Notationen enthält jemals ein ; — sodass sich List<AnimationCurve> = 0:0,1:1;0:1,1:0 sauber aufteilen lässt. Zahlen verwenden überall . als Dezimalpunkt (ein Gebietsschema-Komma zeigt sich als falsche Feldanzahl, niemals als still falscher Wert), Leerraum um Trennzeichen wird getrimmt, und der Round-Trip parse(render(parse(x))) == parse(x) gilt für jede akzeptierte Eingabe.
| Typ | Akzeptierte Eingabe | Kanonische Form |
|---|---|---|
Color | #RGB, #RGBA, #RRGGBB, #RRGGBBAA (Groß-/Kleinschreibung wird ignoriert, # erforderlich) | Großgeschrieben #RRGGBB, wenn die Farbe deckend ist, sonst #RRGGBBAA — #FF8800, #FF880080 |
AnimationCurve | `key,key,…[ | pre:post], wobei ein Schlüsselpunkt t:v, t:v:in:out, t:v:in:out:inW:outW:wmodert:v:in:out:inW:outW:wm:tm` ist (2, 4, 7 oder 8 Felder — 3, 5 und 6 sind Fehler) |
Gradient | `colorKeys[ | alphaKeys[ |
Farbe: Der Wert wird als vier Bytes gespeichert. HDR (Kanäle über 1) wird nicht unterstützt — eine gebackene Farbe wird beim Export auf 0…1 begrenzt. Der Typ-Standardwert ist transparentes Schwarz, #00000000.
Kurve: wm ist das Gewichtete-Tangenten-Flag (0 keine · 1 eingehend · 2 ausgehend · 3 beide) und tm das Tangenten-Modus-Paar Left/Right, optional gefolgt von /broken — jede Seite ist eine von Free, Auto, Linear, Constant, ClampedAuto, dieselben Namen, die Unitys Kurven-Editor verwendet. Kürzere Formen füllen den Rest auf: Ein 2-Felder-Schlüsselpunkt übernimmt die Steigung zu seinen Nachbarn als Tangenten (Linear/Linear), Gewichte von 0.33333334 und keine Gewichtung; ein 4-Felder-Schlüsselpunkt behält Ihre Tangenten (Free/Free); ein 7-Felder-Schlüsselpunkt fügt Gewichte hinzu. Tangentenfelder dürfen Infinity oder -Infinity sein (eine Constant-Stufe); Zeit, Wert und Gewicht müssen endlich sein, die Zeiten der Schlüsselpunkte müssen sich voneinander unterscheiden (Schlüsselpunkte werden beim Import nach Zeit sortiert, daher spielt die Reihenfolge, in der Sie sie eintippen, keine Rolle), und es gibt keine Begrenzung für die Anzahl der Schlüsselpunkte. Der Modus gewinnt gegenüber der Zahl: Für jede Seite, die nicht Free ist, wird der Tangentenwert beim Import aus dem Modus neu berechnet — dieselbe Berechnung, die Unity durchführt —, sodass eine von Hand eingetippte Zahl, die ihrem Modus widerspricht, ersetzt wird, und die Tabelle, die Editoren und das Spiel alle dieselbe Kurve zeigen. Wrap-Modi sind ClampForever, Loop, PingPong und Default; Once wird als Alias für ClampForever akzeptiert (Unity normalisiert es) und nie zurückgeschrieben. Eine Kurve ohne Schlüsselpunkte hat keine Textform: Sie existiert nur als leere Zelle einer optionalen Spalte, und Export gibt sie als leere Zelle aus.
Verlauf: Farbpunkte tragen kein Alpha (#RRGGBBAA im Farbabschnitt ist ein Fehler — Alpha hat seinen eigenen Abschnitt); innerhalb eines Abschnitts sind die @t-Zeiten entweder alle vorhanden oder alle fehlend, und wenn sie fehlen, werden die Schlüsselpunkte gleichmäßig verteilt (n = 1 → 0, n ≥ 2 → i/(n−1)); ein fehlender Alpha-Abschnitt bedeutet 1@0,1@1, ein fehlender Modus bedeutet Blend. Modi sind Blend, Fixed (Stufen) und PerceptualBlend; der optionale Farbraum (Gamma oder Linear) ändert nur, wie PerceptualBlend interpoliert. Zeiten und Alphawerte liegen bei 0…1; Zeiten werden beim Import auf 16 Bit quantisiert, genau wie Unity sie speichert, sodass der angezeigte Wert der Wert ist, den die Engine hält. Ein Verlauf mit einem einzelnen Schlüsselpunkt kommt durch Unity im Round-Trip als zwei identische Schlüsselpunkte zurück — das Bild bleibt unverändert, nur die Anzahl der Schlüsselpunkte wächst.
Listen: List<Color> = #F00;#0F0, List<Gradient> = #F00,#00F;#0F0,#000 — das Listentrennzeichen bleibt unverändert.
Das Data Studio zeigt diese Zellen als native Farb-, Kurven- und Verlaufsfelder, die Web-App als Vorschauen mit vollständigen Editoren — siehe Data Studio und SheetForge Web. Beide schreiben die kanonische Form; die Minimalformen sind für Menschen gedacht.
Schlüssel und Eindeutigkeit
RecordId(ohne@) ist die Schlüsselspalte: höchstens eine pro Tab.- Null Schlüsselspalten sind gültig — bis ein anderer Tab diesen Tab referenziert (
TargetTabHasNoKey). - Zwei oder mehr sind ein Fehler (
MultipleKeyColumns). - Doppelte Schlüsselwerte (
DuplicateRecordId) und leere Schlüsselzellen sind Fehler.
- Null Schlüsselspalten sind gültig — bis ein anderer Tab diesen Tab referenziert (
IntIdist ein sekundärer Integer-Schlüssel: Die Eindeutigkeit wird unabhängig erzwungen, und andere Tabs können ihn überIntId@Tabreferenzieren.- Integer-Schlüssel-Referenzen erhalten dieselbe Integritätsprüfung, dieselben Vorschläge für die nächstliegende Übereinstimmung, dieselbe Umbenennungs-Propagation und dieselbe Graph-/Canvas-Unterstützung wie
RecordId@Tab.
- Integer-Schlüssel-Referenzen erhalten dieselbe Integritätsprüfung, dieselben Vorschläge für die nächstliegende Übereinstimmung, dieselbe Umbenennungs-Propagation und dieselbe Graph-/Canvas-Unterstützung wie
- Ein Tab kann nur über
RecordId, nur überIntIdoder über beide schlüsseln — alle drei Fälle verhalten sich überall symmetrisch.- Trägt ein Tab beide, ist
RecordIdder Anzeige-/Identitätswert, und der Integer wird daneben angezeigt. - Ein anderer Tab kann auf denselben Datensatz auf beide Arten verweisen:
RecordId@ThisTabüber dessen String-Schlüssel, oderIntId@ThisTabüber dessen Integer-Schlüssel.
- Trägt ein Tab beide, ist
@overlap: gewöhnliche Spalten erlauben standardmäßig doppelte Werte. Setzen Siefalsein die@overlap-Zelle einer Spalte, um wertbasierte Eindeutigkeit zu erzwingen.1.0und1gelten als derselbe Wert; zwei Listen sind Duplikate, wenn alle Elemente und deren Reihenfolge übereinstimmen.- Zwei leere Referenzen sind niemals Duplikate voneinander (ein leerer Skalar-Standardwert bleibt dagegen ein gewöhnlicher Wert).
- Schlüsselspalten sind immer eindeutig;
@overlaptrueauf eine Schlüsselspalte zu schreiben, ist ein Widerspruchsfehler.
Anzeigemetadaten der Tabelle (@style)
@style lässt eine Tabelle festlegen, zu welcher Gruppe sie gehört und welche Farbe sie hat, sodass Gruppierung und Färbung in der Tabelle leben statt nur im Editor. Es ist die einzige Markierung, die die Tabelle statt ihrer Spalten beschreibt. Ihre Zellen sind daher nicht an Spalten ausgerichtet — sie bilden eine freie Liste von key=value-Paaren, die bei Spalte B beginnt.
@style | title=Combat | color=#4D8FF0
@name | codeName | displayName | power
@type | RecordId | string | int
@desc | unique key | shown in UI | attack power
| skill.fire | Fireball | 12| Schlüssel | Wert | Wirkung |
|---|---|---|
title | Beliebiger Text | Tabellen mit demselben Titel werden in der Data-Studio-Seitenleiste unter dieser Überschrift gebündelt. Abschnitte erscheinen in der Reihenfolge ihres ersten Auftretens, und Tabellen behalten ihre eigene Reihenfolge innerhalb eines Abschnitts; Tabellen ohne Titel bleiben im Standardabschnitt. |
color | #RRGGBB (sechs Hex-Ziffern) | Färbt diese Tabelle überall dort ein, wo sie erscheint: der Punkt in der Seitenleiste, der Knotenrahmen auf der Canvas und jeder Port und jede Verbindung, die auf diese Tabelle zeigt. |
- Beide Schlüssel sind optional, und die Reihenfolge spielt keine Rolle; schreiben Sie einen, beide oder keinen. Eine leere Zelle wird ignoriert (Füllzellen sind unproblematisch).
- Validierungsfehler werden alle als
MarkerCellInvalidgemeldet, mit Zellkoordinate und einem konkreten Lösungsvorschlag:- ein unbekannter Schlüssel (mit Vorschlag für die nächstliegende Übereinstimmung),
- ein wiederholter Schlüssel,
- ein fehlender Wert,
- eine Farbe, die nicht
#RRGGBBist.
- Die Drei-Ziffern-Kurzform (
#4AF) und benannte Farben werden bewusst abgelehnt, damit der Wert als eine einzige Notation round-trip-fähig bleibt. - Nur Anzeige: Codegen, Bake und der Schema-Fingerabdruck lesen
@stylenie. Das Ändern der Farbe einer Tabelle löst weder eine Codegenerierung noch ein erneutes Backen von ScriptableObjects aus. - Round-Trip-sicher: Die
@style-Zeile wird wie eine Kommentarzeile bewahrt. Hinzufügen, Löschen, Verschieben und Umbenennen von Spalten lassen sie unberührt, weil ihre Zellen zu keiner Spalte gehören. Das Bearbeiten erfolgt über das Formular Group & color (Rechtsklick auf eine Tabelle in der Data-Studio-Seitenleiste), das die Zeile in kanonischer Form neu schreibt. - Eine Tabelle, die nur
@style(plus Kommentare) besitzt, zählt als „noch keine Tabelle": Der Import überspringt sie mit einer Warnung, statt an den drei fehlenden Pflichtmarkierungen zu scheitern. Sobald Sie@name/@type/@deschinzufügen, wird sie normal geparst. Siehe Möglichkeiten & Grenzen. styleist ein reservierter Markierungsname — ein Plugin, das versucht, ihn zu registrieren, wird abgelehnt, und ein Tippfehler wie@stylerhält@styleals Vorschlag.
Enum-Definitionstabellen (@enum)
Eine Enum<T>-Spalte benötigt ein T. Sie können eines aus Plugin-C#-Code registrieren (EnumRegistry), aber Sie können es auch einfach in die Tabelle schreiben — ohne Code, ohne Plugin. Eine Tabelle wird als Enum-Definitionen gelesen, wenn eine der beiden Bedingungen zutrifft:
- sie trägt eine
@enum-Markierungszeile (der Tab kann beliebig benannt sein), oder - der Tab heißt exakt
Enum(Groß-/Kleinschreibung beachtet) und besitzt keine@type-Zeile.
Die zweite Regel verlangt bewusst, dass @type fehlt: Eine Datentabelle besitzt sie immer, daher bleibt eine bestehende Tabelle, die zufällig Enum heißt, weiterhin eine Datentabelle. Eine Tabelle, die sowohl @enum als auch @type trägt, ist widersprüchlich und wird als EnumSheetMarkerConflict gemeldet, statt erraten zu werden.
@desc spielt bei dieser Entscheidung keine Rolle — sie ist auf beiden Tabellenarten zulässig, und auf einer Enum-Definitionstabelle beschreibt sie das Enum in dieser Spalte (siehe unten).
Eine Enum-Definitionstabelle hat keine Datentabelle — kein Schema, keine Schlüsselspalte, keine Datensätze. Eine Spalte ist ein Enum: Die @name-Zelle trägt den Namen des Enums, und jede Zeile darunter (Spalte A bleibt leer) ist ein Member.
@enum | byte |
@desc | Damage kind| Elemental affinity
@name | DamageType | Element
| Physical | Fire
| Magical=10 | Ice
| True | LightningDiese Tabelle definiert zwei Enums, und Enum<DamageType> / Enum<Element> lösen sich nun in jeder @type-Zelle auf — die Spaltensyntax bleibt unverändert. Alle drei oben gezeigten Extras sind optional; eine nackte @name-Zeile plus Member ist weiterhin eine vollständige Enum-Definitionstabelle.
Sie müssen dieses Gerüst nicht selbst eintippen: Sheet erstellen liefert eine Enum definitions-Vorlage, die die Tabelle für Sie anlegt — eine der beiden eingebauten Vorlagen (siehe Data Studio ▸ Sheet create / delete).
- Die Reihenfolge ist der Wert, und
Name=valuelegt sie fest. Eine Member-Zelle ist entweder ein reiner Name oderName=valuemit einem expliziten Integer — genau nach C#s Enum-Regeln: Ein unnummerierter Member ist der vorherige Wert plus eins, der erste ist0.Normal / Rare=10 / Epickompiliert zu0 / 10 / 11. Codegen gibt das= valuenur dort aus, wo Sie eines geschrieben haben.- Datenzellen und Dropdowns verwenden immer den Namen (
Rare, niemalsRare=10). - Ein Wert, der kein reiner Integer ist oder außerhalb des Wertebereichs des zugrunde liegenden Typs liegt (auch durch Auto-Inkrement), ist
InvalidEnumMemberValue. - Das ist auch der Grund, warum das Data Studio Member nie umordnet und nie eine Lücke auffüllt: Das Verschieben eines Members würde stillschweigend Werte ändern, die bereits in Assets gebacken und in Spielständen gespeichert sind.
@descbeschreibt das Enum. Die@desc-Zelle einer Spalte wird im generierten Code zum XML-<summary>dieses Enums (Tooltips in der IDE) — im selben Sinne wie das@desceines Felds in einer Datentabelle. Leere Zelle = keine Beschreibung; die Markierungszeile selbst ist optional.@enum-Zellen wählen den zugrunde liegenden Typ. Die Zelle der@enum-Zeile in einer Spalte kann den zugrunde liegenden C#-Typ für dieses Enum benennen — einen vonbyte,sbyte,short,ushort,int,uint,long,ulong.- Eine leere Zelle (oder gar keine
@enum-Zeile, auf einem Tab namensEnum) bedeutetint. Alles andere istInvalidEnumUnderlyingType. - Codegen gibt
public enum Grade : byte { … }aus. - Für
ulongwerden explizite Werte oberhalb vonlong.MaxValueaus einer Tabelle nicht unterstützt — registrieren Sie ein solches Enum stattdessen aus Plugin-C#-Code.
- Eine leere Zelle (oder gar keine
- Leere Zellen werden übersprungen, nicht als Member gelesen, daher dürfen Spalten unterschiedlich lang sein, und eine Lücke in der Mitte wird einfach überlesen.
- Kommentarzeilen (
#) werden überall in der Tabelle ignoriert. Mehrere Enums pro Tabelle und mehrere Enum-Definitionstabellen sind beide unproblematisch. Namen müssen über alle hinweg eindeutig sein, und ein Name, den ein Plugin bereits aus C# registriert hat, gewinnt — die Tabellen-Definition wird mitDuplicateEnumNameabgelehnt. - Namen und Member müssen als C#-Bezeichner nutzbar sein: ASCII-Buchstaben, Ziffern und
_, nicht mit einer Ziffer beginnend, und kein reserviertes Schlüsselwort (InvalidEnumIdentifier).- Nicht-ASCII wird bewusst abgelehnt, weil zum Verwechseln ähnliche Unicode-Bezeichner einen Typ erzeugen würden, den niemand von einem anderen unterscheiden kann.
- Ein deklarierter Name ohne Member darunter ist
EnumSheetEmptyColumn. - Scheitert ein Member einer Spalte, wird das gesamte Enum verworfen, statt halb registriert zu werden.
- Was der Import erzeugt. Eine einzige
SheetForgeEnums.csfür das gesamte Projekt — Enums sind eine projektweite Ausgabe, keine pro Tab. Sie wird in den in den Einstellungen konfigurierten Ordner für generierten Code geschrieben, im selben Namespace wie die generierten Tab-Typen. Der erste Import erzeugt den Typ, kompiliert ihn und schließt den Bake nach dem Domain-Reload ab, ohne zusätzlichen Klick. - Einen Member hinzufügen, ohne die Tabelle zu öffnen: Das Dropdown einer
Enum<T>-Zelle im Data Studio trägt „Add a new member…", das den Member als einen Undo-Schritt auf die Enum-Definitionstabelle vormerkt. Ein aus Plugin-C#-Code registriertes Enum bietet diese Zeile nicht an — der Code besitzt es. - Enum-Definitionstabellen haben keine Datensätze, daher werden sie nie in ein ScriptableObject gebacken, und Export/Push lassen ihren Text unangetastet; der Import meldet sie getrennt von übersprungenen Tabs.
- Siehe Möglichkeiten & Grenzen für die beiden Grenzen: per Plugin registrierte Enums lassen sich nicht aus einer Tabelle erweitern, und die generierte Enum-Datei landet immer im Einstellungsordner.
Lokalisierungstabellen (@loc)
Eine @loc-Markierungszeile macht aus der Tabelle eine Lokalisierungstabelle: Zeilen sind Schlüssel, Spalten sind Sprachen, und die @loc-Zelle jeder Sprachspalte benennt deren Sprachcode.
- Die
RecordId-Schlüsselspalte ist erforderlich – der Schlüsselwert ist der Lokalisierungsschlüssel. - Die erste Sprachspalte ist die Quellsprache.
- Sprachspalten sind String-Spalten.
string?ist die empfohlene Form: Eine leere Zelle ist dann eine Abdeckungslücke, kein Fehler. - Zwei optionale Spalten sind namentlich reserviert:
smart(bool) undcomment(string).
@loc | | en | ko |
@name | codeName | en | ko | comment
@type | RecordId | string? | string? | string?
@desc | key | source text | |
| ui.ok | OK | 확인 | Confirm buttonFür Bearbeitung, Export, Push, xlsx und die Web-App bleibt die Tabelle eine gewöhnliche Tabelle. Was sich ändert, ist die Ausgabe: keine Record-Klasse und kein Database-SO, sondern pro Tab Schlüsselkonstanten und – wenn das Unity-Localization-Paket installiert ist – StringTable-Synchronisierung. @enum und @loc auf derselben Tabelle ist ein Konfliktfehler.
Die vollständige Geschichte – LocRef-Referenzen, Prägen neuer Schlüssel, die Bridge, Übersetzungs-Workflows – steht auf Lokalisierungstabellen.
Benutzerdefinierte Strukturmarkierungen (per Plugin registriert)
@overlap ist das eingebaute Beispiel für eine spaltenweise Markierung: eine Markierungszeile, deren Zellen einen Wert pro Spalte tragen, validiert Spalte für Spalte. Ein Plugin kann auf dieselbe Weise eigene Markierungen registrieren — z. B. eine @curve-Markierung, die annotiert, wie jede numerische Spalte interpoliert.
Der Wert wird als domänenunabhängige Metadaten (FieldSchema.MarkerValues) gespeichert, die Validatoren, Kanten-Contributor und der Spalten-Header-Tooltip des Authoring-Fensters lesen können. Der Core interpretiert den Wert selbst nie — die Validierung wird an die Markierungsdefinition delegiert.
- Registrierte benutzerdefinierte Markierungen werden genau wie
@overlapakzeptiert: beliebige Reihenfolge über den Daten, Duplikate werden abgelehnt, eine Markierung unterhalb der Daten ist ein Fehler. - Jede Markierung besitzt nur ihre spaltenweise Wertvalidierung (einschließlich der Bedeutung einer leeren Zelle) — sie übernimmt nicht das Parsen der gesamten Zeile. Daten-„Formen" bleiben Sache der Normalisierung (Referenzen,
List<T>,type-Spalten). - Benutzerdefinierte Markierungen sind für Metadaten auf Spaltenebene gedacht, nicht für neue Datenformen. Siehe Plugin-Erstellung §4.5 für ein Registrierungsbeispiel.
@styleist die einzige eingebaute Markierung, die nicht spaltenweise ist (sie beschreibt die Tabelle) — sie ist also nicht das Vorbild dafür,@overlapist es.
Komplexe Daten zusammensetzen: zuerst normalisieren
Der empfohlene Weg, komplexe Strukturen auszudrücken, ist der Zusammenbau über Referenzen („zusammensetzen statt skripten"):
- Atome leben als Zeilen in ihrem eigenen Tab.
- Kombinationen sind Referenzlisten:
List<RecordId@Effects>. - Eine
type-Spalte (ein enum) verknüpft eine Datenzeile mit einem Code-Atom — Ihre Runtime führt darauf einswitchaus, um Verhalten zu dispatchen. Keine eingebettete Skriptsprache nötig.
Mini-Grammatiken (benutzerdefinierte Zelltypen wie attack:add:10) sind für kleine Tupel gedacht — der Core stellt die Konventionen ; und : bereit; überstrapazieren Sie sie nicht.
Für wirklich prozedurale Einmal-Logik referenzieren Sie ein Skript-Asset genauso, wie Sie ein Bild referenzieren würden: List<AssetRef@Scripts>. SheetForge validiert die Referenz und bakt das Addressable; das Ausführen des Skripts ist Aufgabe Ihres Spiels.
Besondere Daten-„Formen": Selbst kompliziert wirkende Daten (Level-Kurven usw.) lassen sich sauber normalisieren (
List<float>, Zusammenbau über Referenzen). Eine benutzerdefinierte Strukturmarkierung fügt Metadaten auf Spaltenebene hinzu (spaltenweise validiert), keine neue Datenform – normalisieren Sie die Daten zuerst, und greifen Sie nur bei wirklich umfangreichen spaltenweisen Annotationen zu einer benutzerdefinierten Markierung. Siehe Plugin-Erstellung.
Verwandte Seiten
- Kernkonzepte – was mit diesen Zellen nach dem Parsen geschieht
- Data Studio – Spalten/Typen bearbeiten, ohne die Tabelle zu öffnen
- Lokalisierungstabellen – die
@loc-Tabellenform undLocRef-Referenzen im Detail - Plugin-Erstellung – enums und benutzerdefinierte Zelltypen registrieren
- Möglichkeiten & Grenzen – die Grenzen der
<>-Wrapper- und Custom-Marker-Syntax