Aller au contenu
SheetForge

Syntaxe des feuilles

Une feuille SheetForge est autodescriptive : la colonne A est réservée aux marqueurs, les données proprement dites commencent à la colonne B. Les lignes sont identifiées par leur marqueur, pas par leur position, si bien que vous pouvez insérer des lignes de commentaire n'importe où sans rien casser.

Pour adapter une feuille de calcul existante, insérez une colonne de marqueur devant vos données et ajoutez les trois lignes de marqueur. Les colonnes de données existantes restent telles quelles.

Marqueurs (colonne A)

Colonne ASignification
#Ligne de commentaire — entièrement ignorée, préservée telle quelle lors de l'aller-retour.
@nameLigne de nom de champ (un nom par colonne).
@typeLigne de type de champ.
@descLigne de description — le codegen la bake dans les commentaires de documentation XML et les infobulles de l'inspecteur.
@overlap(optionnel) Politique de doublon par colonne — true (autoriser, valeur par défaut) / false (imposer l'unicité des valeurs).
@style(optionnel) Métadonnées d'affichage de la feuille — un libellé de groupe et une couleur pour cette feuille. Voir ci-dessous.
@enum(optionnel) Marque la feuille entière comme des définitions d'enum plutôt qu'une table de données. Voir ci-dessous.
@loc(optionnel) Marque la feuille entière comme une feuille de localisation — ses cellules nomment le code de locale de chaque colonne. Voir ci-dessous.
@yourMarker(optionnel, enregistré par plugin) Un marqueur structurel personnalisé — voir ci-dessous.
(vide)Ligne de données.
  • @name, @type, @desc sont obligatoires ; @overlap, @style et tout marqueur personnalisé sont optionnels.
  • Les lignes de marqueur peuvent apparaître dans n'importe quel ordre, tant qu'elles se trouvent au-dessus des lignes de données.
  • Un @marker inconnu est une erreur, avec une suggestion de correspondance la plus proche (« vouliez-vous dire @desc ? »). Les marqueurs personnalisés enregistrés rejoignent le pool de suggestions.
  • Des données dans une colonne sans en-tête @name/@type constituent une erreur (garde-fou anti-données orphelines — aucune perte de données silencieuse n'est jamais autorisée).

Exemple (colonnes présentées 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      |

(La cellule price vide de item.potion matérialise la valeur par défaut explicite 10.)

Système de types

Chaque type est autodescriptif — un lecteur voit ce que contient une colonne à la seule lecture de la cellule @type.

NotationSignification
int float bool stringScalaires intégrés.
Enum<DamageType>Un enum C# — soit défini dans une feuille d'enum (voir ci-dessous, sans code nécessaire), soit enregistré par un plugin (EnumRegistry). Les noms de membres sont validés, les fautes de frappe reçoivent des suggestions de correspondance la plus proche.
List<T>Une liste — séparateur d'éléments ;, éléments nettoyés (trim), un élément vide est une erreur, une cellule vide est une liste vide.
RecordIdLa colonne clé de cet onglet — un auto-identifiant de type chaîne (par ex. item.sword). Toujours un scalaire obligatoire. Nom de colonne recommandé : codeName.
IntIdClé entière secondaire de cet onglet — au plus une par onglet, scalaire obligatoire, pour des id runtime/sauvegarde/backend. Nom de colonne recommandé : id. Un onglet peut avoir pour clé RecordId, IntId, ou les deux.
RecordId@EffectsUne référence à un enregistrement de l'onglet Effects par sa clé chaîne — validée en intégrité (l'onglet cible existe, possède une colonne clé, l'id se résout ; les fautes de frappe reçoivent des suggestions).
IntId@EffectsUne référence à un enregistrement de l'onglet Effects par sa clé entière — pleine parité avec RecordId@Effects : validée en intégrité de la même façon (l'onglet cible existe, possède une colonne IntId, l'id se résout), avec une suggestion de correspondance entière la plus proche en cas d'échec. Les valeurs sont canonicalisées avec int.ToString, si bien qu'un 007 saisi à la main se résout en 7.
AssetRef@IconsUne référence à un asset du groupe Addressables Icons — validée en existence par rapport au catalogue. Un sous-asset (un sprite à l'intérieur d'une texture, un matériau à l'intérieur d'une police) est adressé comme parent[sub] — l'adresse qu'Addressables donne à une entrée de sous-objet, par ex. atlas[sword] — et est validé, baké (SubObjectName) et exporté sous cette clé.
AssetRef@Icons<Sprite>La même référence restreinte à un type d'asset : une adresse ne passe que lorsque l'asset — ou l'un de ses sous-assets — peut être chargé comme Sprite. Le nom est n'importe quel type d'asset dérivé d'UnityEngine.Object que le projet connaît (moteur ou le vôtre) : le nom court quand exactement un type correspond, sinon le nom complet (MyGame.ItemData). Le codegen émet AssetReferenceT<Sprite> ; AssetRef@Icons sans <…> reste non restreint ; AssetRef<Sprite>@Icons est rejeté et la bonne orthographe est suggérée. Voir Références d'asset typées ci-dessous.
LocRef@StringsUne référence à une clé de localisation dans la feuille de localisation Strings — validée en intégrité comme RecordId@Tab (existence, suggestions de correspondance la plus proche, propagation de renommage, sélecteur, listes déroulantes), prévisualisant en ligne le texte de la locale source de l'entrée. L'onglet cible doit porter @loc (LocRefTargetNotLocalizationSheet sinon), et un LocRef nu sans @Target est rejeté. List<LocRef@Strings> et LocRef@Strings? se composent normalement. Le codegen émet une simple struct LocRef — voir Feuilles de localisation.
Color · AnimationCurve · GradientTypes intégrés de valeur visuelle. Chacun a une forme de texte compacte (ci-dessous) que le Data Studio et l'application web éditent avec un éditeur natif de couleur, de courbe ou de dégradé au lieu de texte brut ; le codegen émet des champs UnityEngine.Color / AnimationCurve / Gradient.
Modifier (exemple)Un type de cellule personnalisé enregistré par plugin (voir Création de plugins) — par ex. la mini-grammaire stat:op:value de l'exemple fourni. CustomType@Target fonctionne aussi par le seul enregistrement. Lorsque le plugin adopte IReferencingCellType, cette colonne se comporte exactement comme RecordId@Target — validée, suggérée, renommée, dessinée et sélectionnée de la même façon.
Pair<T> (exemple)Un type wrapper enregistré par plugin — une forme de valeur générique MyWrapper<T> qui regroupe plusieurs valeurs internes T dans une seule cellule (par ex. Pair<int> = 1~2). Le type interne est résolu de façon récursive, si bien que Pair<RecordId@Effects>, Pair<Enum<DamageType>>, et l'imbrication Box<Pair<int>> fonctionnent tous. Voir Création de plugins.

<> et @ signifient des choses différentes et coexistent : <> = genre/wrapper (List intégré, ou un MyWrapper<T> de plugin), @ = cible. Ainsi, List<RecordId@Effects> est une liste de références, et Pair<RecordId@Effects> regroupe deux références — toutes deux vers l'onglet Effects. La clé entière se compose de la même façon : List<IntId@Effects> est une liste de références à clé entière.

Types wrapper (MyWrapper<T>)

Un plugin peut enregistrer un wrapper — une forme de valeur générique qui possède une syntaxe externe (délimiteur, arité) et délègue le type interne au Core. Le wrapper se compose avec n'importe quel type interne. Toute référence qu'il contient reste validée, propagée lors d'un renommage de clé, et réécrite lors d'un renommage d'onglet (transmission intégrale).

Règles de rejet (cohérentes avec List) :

NotationAutorisé ?Pourquoi
Pair<RecordId@Effects> · Pair<Enum<E>> · Box<Pair<int>>OuiWrapper sur un scalaire, une référence, un enum, ou un autre wrapper.
List<Pair<int>>OuiUne liste de composites. Le délimiteur propre au wrapper doit différer de ; (le séparateur de liste) — une responsabilité de l'auteur du plugin.
Pair<List<int>>NonUne liste ne peut pas se trouver à l'intérieur d'un wrapper (List reste plat et le plus externe, même règle que List<List<T>>).
Pair<int>@EffectsNonUn wrapper est une forme de valeur ; placez plutôt le @ sur la feuille interne (Pair<RecordId@Effects>).
Pair<int?> · Pair<int=1>NonL'optionalité/les valeurs par défaut sont une notation de niveau champ, pas une partie du type interne.

Obligatoire / optionnel / valeurs par défaut

NotationSignification
float (non marqué)Obligatoire — une cellule vide est une erreur (la contamination silencieuse est bloquée à l'entrée).
float?Optionnel — une cellule vide matérialise la valeur par défaut du type (0), marquée IsDefaulted. S'applique aux quatre scalaires (int / float / bool / string) et aux trois types visuels : Color? → noir transparent #00000000, AnimationCurve? → une courbe sans clé, Gradient? → le dégradé blanc `#FFFFFF@0,#FFFFFF@1
RecordId@Effects? · IntId@Effects? · AssetRef@Icons?Référence optionnelle — une cellule vide matérialise une référence vide : « ne pointe vers rien », l'onglet/groupe cible étant conservé et la cellule marquée IsDefaulted. Ce n'est pas une référence cassée — la validation d'intégrité de référence et de clé d'asset la saute, le canevas n'y dessine aucun fil, et @overlap ne compte pas deux références vides comme des doublons. Une cellule qui porte réellement une valeur est validée exactement comme avant, si bien qu'une faute de frappe dans une colonne optionnelle est toujours détectée.
RecordId@Effects=La même chose écrite explicitement : une valeur par défaut explicite vide équivaut au simple ? ci-dessus. Une valeur par défaut non vide (RecordId@Effects=fire) se résout toujours et reste vérifiée en intégrité.
int=1Optionnel avec une valeur par défaut explicite — une cellule vide matérialise 1.
List<T>Une cellule vide est toujours autorisée (liste vide).

Là où ? n'est pas accepté, la raison est toujours la même : le Core ne peut pas inventer une valeur à partir de rien, donc ces types ont besoin d'un =default explicite. Cela couvre :

  • Enum<T>?
  • un type personnalisé de plugin — Modifier?, y compris Modifier@Tab?
  • un wrapper — Pair<int>?

Les colonnes clé sont exclues pour une raison différente : une clé vide engendrerait des doublons. Donc RecordId? (la forme d'auto-identifiant sans clé) et IntId? sont eux aussi rejetés.

Autres notations délibérément rejetées :

  • int?=1 et RecordId@Effects?=fire? et = signifient tous deux « optionnel », choisissez-en un.
  • List<T>? — une liste autorise déjà le vide.
  • List<List<T>> — pas de listes imbriquées.
  • Pair<int?> — l'optionalité est de niveau champ, pas une partie du type interne.

Règles de valeur

  • bool : uniquement true / false, insensible à la casse en entrée ; la forme canonique est en minuscules.
  • Nombres : toujours . comme séparateur décimal (indépendant de la locale). Les décimales à virgule, NaN, et Infinity sont rejetés à l'entrée.
  • Aller-retour des flottants : l'export produit le format d'aller-retour le plus court, si bien que 1.0 peut revenir sous la forme 1 — la valeur est préservée exactement (aller-retour sémantique).
  • Les comparaisons de marqueur et d'enum sont Ordinal (aucune surprise liée à la locale).

Références d'asset typées (AssetRef@Group<Type>)

AssetRef@Icons accepte n'importe quelle adresse du groupe. AssetRef@Icons<Sprite> la restreint à un seul type d'asset, et cette restriction est vérifiée à trois endroits : la validation, la génération de code et la surface de création.

  • Quels noms se résolvent. Le type est n'importe quel type d'asset dérivé d'UnityEngine.Object que le projet peut charger — aussi bien les types moteur (Sprite, Texture2D, AudioClip, une base abstraite telle que Texture) que vos propres ScriptableObject ; il n'y a pas de liste blanche. Les composants et les types réservés à l'éditeur ne sont pas candidats. Écrivez le nom court quand exactement un type le porte, sinon le nom complet avec l'espace de noms. Les noms ambigus (AmbiguousAssetType, avec chaque candidat listé) et les noms inconnus (UnknownAssetType, avec une suggestion de correspondance la plus proche) sont signalés une fois par colonne, sur la ligne @type.
  • Ce qui passe. Une adresse satisfait la restriction quand l'asset à cette adresse, ou l'un quelconque de ses sous-assets, est chargeable comme ce type — si bien qu'une texture importée en mode Sprite passe <Sprite>, et une texture ordinaire est signalée par cellule comme AssetTypeMismatch. Le sous-asset lui-même est adressable comme parent[sub], et cette clé n'est vérifiée que contre son propre type.
  • Un type que le code généré ne peut pas référencer est refusé. Un type qui vit dans un assembly prédéfini (Assembly-CSharp et ses semblables — tout dossier de script sans assembly definition) est trouvé mais signalé comme AssetTypeNotReferenceable, parce que l'assembly compagnon généré ne peut pas référencer ces assemblies et que AssetReferenceT<T> ne compilerait pas. Déplacez le type dans une assembly definition, ou retirez le <…>.
  • Ce qu'émet le codegen. AssetReferenceT<global::UnityEngine.Sprite> pour un type résolu, AssetReference pour une colonne non restreinte. L'assembly definition compagnon référence automatiquement l'assembly où vit le type, et le nom complet résolu fait partie de l'empreinte de schéma, si bien que remapper le nom régénère le code.
  • Se compose comme n'importe quel autre type : AssetRef@Icons<Sprite>?, List<AssetRef@Icons<Sprite>> et un wrapper tel que Pair<AssetRef@Icons<Sprite>> fonctionnent tous ; AssetRef@Icons<> (vide), AssetRef@Ic<ons (un chevron dans le nom de groupe) et RecordId@Skills<X> (la restriction est réservée à AssetRef) sont des erreurs de syntaxe.
  • Le formulaire de colonne du Data Studio a un bouton Type… qui liste les types candidats et réécrit la cellule @type pour vous — voir Data Studio.

Types de valeur visuelle (Color, AnimationCurve, Gradient)

Trois types intégrés portent des valeurs illisibles en texte brut. Leur forme de texte est conçue pour qu'une personne puisse taper une version courte à la main, tandis que chaque outil — les éditeurs, Export, Push, l'application web — écrit toujours la forme canonique et complète, et qu'une valeur survit à l'aller-retour feuille → Unity → feuille sans perte.

Les séparateurs sont partagés par les trois et se situent un niveau sous le séparateur de liste : à l'intérieur d'une valeur, les éléments sont séparés par ,, les champs à l'intérieur d'un élément par :, les sections par |, et le temps d'une clé est attaché avec @. Les éléments d'un List<> restent séparés par ;, et aucune des trois notations ne contient jamais de ; — si bien que List<AnimationCurve> = 0:0,1:1;0:1,1:0 se sépare proprement. Les nombres utilisent partout . comme séparateur décimal (une virgule de locale se traduit par un mauvais nombre de champs, jamais par une valeur silencieusement fausse), les espaces autour des séparateurs sont retirés, et l'aller-retour parse(render(parse(x))) == parse(x) tient pour toute entrée acceptée.

TypeEntrée acceptéeForme canonique
Color#RGB, #RGBA, #RRGGBB, #RRGGBBAA (insensible à la casse, # obligatoire)#RRGGBB en majuscules quand la couleur est opaque, #RRGGBBAA sinon — #FF8800, #FF880080
AnimationCurve`key,key,…[pre:post], où une clé est t:v, t:v:in:out, t:v:in:out:inW:outW:wmout:v:in:out:inW:outW:wm:tm` (2, 4, 7 ou 8 champs — 3, 5 et 6 sont des erreurs)
Gradient`colorKeys[alphaKeys[

Couleur : la valeur est stockée comme quatre octets. Le HDR (canaux au-dessus de 1) n'est pas pris en charge — une couleur bakée est bornée à 0…1 à l'Export. La valeur par défaut du type est le noir transparent, #00000000.

Courbe : wm est le drapeau de tangente pondérée (0 aucune · 1 entrante · 2 sortante · 3 les deux) et tm la paire de mode de tangente Left/Right, éventuellement suivie de /broken — chaque côté est l'un de Free, Auto, Linear, Constant, ClampedAuto, les mêmes noms qu'utilise l'éditeur de courbe d'Unity. Les formes plus courtes remplissent le reste : une clé à 2 champs prend comme tangentes la pente vers ses voisines (Linear/Linear), des poids de 0.33333334 et aucune pondération ; une clé à 4 champs conserve vos tangentes (Free/Free) ; une clé à 7 champs ajoute les poids. Les champs de tangente peuvent valoir Infinity ou -Infinity (un palier Constant) ; le temps, la valeur et le poids doivent être finis, les temps des clés doivent être distincts (les clés sont triées par temps à l'import, donc l'ordre dans lequel vous les saisissez n'a pas d'importance), et il n'y a aucune limite au nombre de clés. Le mode l'emporte sur le nombre : pour tout côté qui n'est pas Free, la valeur de tangente est recalculée depuis le mode au moment de l'import — le même calcul qu'effectue Unity — si bien qu'un nombre saisi à la main qui contredit son mode est remplacé, et la feuille, les éditeurs et le jeu montrent tous une seule courbe. Les modes de wrap sont ClampForever, Loop, PingPong et Default ; Once est accepté comme alias de ClampForever (Unity le normalise) et n'est jamais réécrit. Une courbe sans clé n'a pas de forme de texte : elle n'existe que comme la cellule vide d'une colonne optionnelle, et Export la rend comme une cellule vide.

Dégradé : les clés de couleur ne portent pas d'alpha (#RRGGBBAA dans la section couleur est une erreur — l'alpha a sa propre section) ; à l'intérieur d'une section, les temps @t sont soit tous présents, soit tous absents, et quand ils sont absents, les clés sont réparties uniformément (n = 10, n ≥ 2i/(n−1)) ; une section alpha manquante signifie 1@0,1@1, un mode manquant signifie Blend. Les modes sont Blend, Fixed (par paliers) et PerceptualBlend ; l'espace colorimétrique optionnel (Gamma ou Linear) ne change que la façon dont PerceptualBlend interpole. Les temps et les alphas sont 0…1 ; les temps sont quantifiés sur 16 bits à l'import, exactement comme Unity les stocke, si bien que la valeur que vous voyez est la valeur que détient le moteur. Un dégradé à une seule clé fait l'aller-retour par Unity sous la forme de deux clés identiques — l'image ne change pas, seul le nombre de clés augmente.

Listes : List<Color> = #F00;#0F0, List<Gradient> = #F00,#00F;#0F0,#000 — le séparateur de liste est inchangé.

Le Data Studio montre ces cellules comme des champs natifs de couleur, de courbe et de dégradé, et l'application web comme des aperçus avec des éditeurs complets — voir Data Studio et SheetForge Web. Les deux écrivent la forme canonique ; les formes minimales sont pour les humains.

Clés et unicité

  • RecordId (sans @) est la colonne clé : au plus une par onglet.
    • Zéro colonne clé est valide — jusqu'à ce qu'un autre onglet référence cet onglet (TargetTabHasNoKey).
    • Deux ou plus est une erreur (MultipleKeyColumns).
    • Les valeurs de clé en double (DuplicateRecordId) et les cellules de clé vides sont des erreurs.
  • IntId est une clé entière secondaire : l'unicité est imposée indépendamment, et d'autres onglets peuvent la référencer via IntId@Tab.
    • Les références par clé entière reçoivent la même validation d'intégrité, les mêmes suggestions de correspondance la plus proche, la même propagation lors d'un renommage, et le même support de graphe/canevas que RecordId@Tab.
  • Un onglet peut avoir pour clé RecordId seul, IntId seul, ou les deux, et les trois cas se comportent symétriquement partout.
    • Lorsqu'un onglet porte les deux, RecordId est la valeur d'affichage/d'identité et l'entier est affiché à ses côtés.
    • Un autre onglet peut pointer vers le même enregistrement des deux façons : RecordId@ThisTab par sa clé chaîne, ou IntId@ThisTab par sa clé entière.
  • @overlap : les colonnes ordinaires autorisent les valeurs en double par défaut. Mettez false dans la cellule @overlap d'une colonne pour imposer une unicité fondée sur la valeur.
    • 1.0 et 1 comptent comme la même valeur ; deux listes sont des doublons lorsque tous leurs éléments et leur ordre correspondent.
    • Deux références vides ne sont jamais des doublons l'une de l'autre (une valeur scalaire par défaut vide reste, elle, une valeur ordinaire).
    • Les colonnes clé sont toujours uniques ; écrire true dans @overlap sur une colonne clé est une erreur de contradiction.

Métadonnées d'affichage de la feuille (@style)

@style permet à une feuille d'indiquer à quel groupe elle appartient et quelle couleur elle a, de sorte que le regroupement et la coloration vivent dans la feuille plutôt que seulement dans l'éditeur. C'est le seul marqueur qui décrit la feuille plutôt que ses colonnes. Ses cellules ne sont donc pas alignées sur des colonnes — ce sont une liste libre de paires key=value commençant à la colonne B.

@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
CléValeurEffet
titleN'importe quel texteLes feuilles qui partagent un titre sont regroupées sous cet intitulé dans la barre latérale du Data Studio. Les sections apparaissent dans l'ordre de première apparition, et les feuilles conservent leur propre ordre à l'intérieur d'une section ; les feuilles sans titre restent dans la section par défaut.
color#RRGGBB (six chiffres hexadécimaux)Teinte cette feuille partout où elle apparaît : le point de la barre latérale, la bordure du nœud sur le canevas, et chaque port et fil qui pointe vers cette feuille.
  • Les deux clés sont optionnelles et l'ordre n'a pas d'importance ; écrivez-en une, les deux, ou aucune. Une cellule vide est ignorée (les cellules de remplissage ne posent pas de problème).
  • Les erreurs de validation arrivent toutes comme MarkerCellInvalid, avec les coordonnées de la cellule et une correction concrète :
    • une clé inconnue (avec une suggestion de correspondance la plus proche),
    • une clé répétée,
    • une valeur manquante,
    • une couleur qui n'est pas au format #RRGGBB.
  • La forme abrégée à trois chiffres (#4AF) et les couleurs nommées sont rejetées volontairement, afin que la valeur fasse l'aller-retour sous une seule notation.
  • Affichage seulement : le codegen, le bake et l'empreinte de schéma ne lisent jamais @style. Changer la couleur d'une feuille ne régénère pas le code et ne re-bake pas les ScriptableObjects.
  • Sûr pour l'aller-retour : la ligne @style est préservée comme une ligne de commentaire. Ajouter, supprimer, déplacer et renommer des colonnes la laisse intacte, car ses cellules n'appartiennent à aucune colonne. La modifier passe par le formulaire Group & color ✎ (clic droit sur une feuille dans la barre latérale du Data Studio), qui réécrit la ligne sous forme canonique.
  • Une feuille qui n'a que @style (plus des commentaires) compte comme « pas encore de table » : l'import la saute avec un avertissement au lieu d'échouer sur les trois marqueurs obligatoires manquants. Dès que vous ajoutez @name/@type/@desc, elle est analysée normalement. Voir Capacités et limites.
  • style est un nom de marqueur réservé — un plugin qui tente de l'enregistrer est rejeté, et une faute de frappe comme @styl reçoit @style comme suggestion.

Feuilles de définition d'enum (@enum)

Une colonne Enum<T> a besoin d'un T. Vous pouvez en enregistrer un depuis du C# de plugin (EnumRegistry), mais vous pouvez aussi simplement l'écrire dans la feuille — sans code, sans plugin. Une feuille est lue comme des définitions d'enum lorsque l'une des deux conditions suivantes est vraie :

  • elle porte une ligne de marqueur @enum (l'onglet peut porter n'importe quel nom), ou
  • l'onglet est nommé exactement Enum (sensible à la casse) et n'a pas de ligne @type.

La seconde règle exige que @type soit absent, volontairement : une table de données l'a toujours, donc une table existante qui se trouve s'appeler Enum reste une table. Une feuille portant à la fois @enum et @type est contradictoire et signalée comme EnumSheetMarkerConflict plutôt que devinée.

@desc ne joue aucun rôle dans cette décision — elle est légale sur les deux types de feuille, et sur une feuille d'enum elle décrit l'enum de cette colonne (voir ci-dessous).

Une feuille d'enum n'a pas de table — pas de schéma, pas de colonne clé, pas d'enregistrement. Une colonne est un enum : la cellule @name porte le nom de l'enum, et chaque ligne en dessous (colonne A vide) est un membre.

@enum    | byte       |
@desc    | Damage kind| Elemental affinity
@name    | DamageType | Element
         | Physical   | Fire
         | Magical=10 | Ice
         | True       | Lightning

Cette feuille définit deux enums, et Enum<DamageType> / Enum<Element> se résolvent désormais dans n'importe quelle cellule @type — la syntaxe de colonne est inchangée. Les trois extras montrés ci-dessus sont tous optionnels ; une simple ligne @name suivie des membres constitue toujours une feuille d'enum complète.

Vous n'avez pas besoin de taper ce squelette vous-même : Créer une feuille propose un modèle Enum definitions qui met en page la feuille pour vous, l'un des deux modèles intégrés (voir Data Studio ▸ Sheet create / delete).

  • L'ordre est la valeur, et Name=value la fixe. Une cellule de membre est soit un simple nom, soit Name=value avec un entier explicite — exactement les règles d'enum de C# : un membre non numéroté vaut la valeur précédente plus un, le premier vaut 0.
    • Normal / Rare=10 / Epic compile en 0 / 10 / 11. Le codegen n'émet le = value que là où vous en avez écrit un.
    • Les cellules de données et les listes déroulantes utilisent toujours le nom (Rare, jamais Rare=10).
    • Une valeur qui n'est pas un entier simple, ou qui tombe hors de la plage du type sous-jacent (y compris par auto-incrémentation), est InvalidEnumMemberValue.
    • C'est aussi pourquoi le Data Studio ne réordonne jamais les membres et ne rebouche jamais un trou : déplacer un membre changerait silencieusement des valeurs déjà bakées dans des assets et stockées dans des fichiers de sauvegarde.
  • @desc décrit l'enum. La cellule @desc d'une colonne devient le <summary> XML de cet enum dans le code généré (infobulles dans l'IDE), dans le même esprit que le @desc d'un champ de table de données. Cellule vide = aucune description ; la ligne de marqueur elle-même est optionnelle.
  • Les cellules @enum choisissent le type sous-jacent. La cellule de la ligne @enum dans une colonne peut nommer le type sous-jacent C# de cet enum — l'un de byte, sbyte, short, ushort, int, uint, long, ulong.
    • Une cellule vide (ou l'absence totale de ligne @enum, sur un onglet nommé Enum) signifie int. Toute autre valeur est InvalidEnumUnderlyingType.
    • Le codegen émet public enum Grade : byte { … }.
    • Pour ulong, les valeurs explicites au-delà de long.MaxValue ne sont pas prises en charge depuis une feuille — enregistrez un tel enum depuis du C# de plugin à la place.
  • Les cellules vides sont sautées, non lues comme des membres, si bien que les colonnes peuvent avoir des longueurs différentes et qu'un trou au milieu est simplement ignoré.
  • Les lignes de commentaire (#) sont ignorées n'importe où dans la feuille. Plusieurs enums par feuille et plusieurs feuilles d'enum sont tous deux acceptés. Les noms doivent être uniques parmi tous, et un nom qu'un plugin a déjà enregistré depuis le C# l'emporte : la définition de la feuille est rejetée avec DuplicateEnumName.
  • Les noms et les membres doivent être utilisables comme identifiants C# : lettres ASCII, chiffres et _, sans commencer par un chiffre, et sans être un mot-clé réservé (InvalidEnumIdentifier).
    • Le non-ASCII est rejeté volontairement, car des identifiants Unicode à l'apparence similaire produiraient un type que personne ne pourrait distinguer d'un autre.
    • Un nom déclaré sans membre en dessous est EnumSheetEmptyColumn.
    • Si un membre d'une colonne échoue, tout cet enum est écarté plutôt qu'à moitié enregistré.
  • Ce que l'import génère. Un seul SheetForgeEnums.cs pour tout le projet — les enums sont une sortie au niveau du projet, pas par onglet. Il est écrit dans le dossier de code généré des paramètres, dans le même espace de noms que les types d'onglet générés. Le premier import crée le type, le compile et termine le bake après le rechargement de domaine, sans clic supplémentaire.
  • Ajouter un membre sans ouvrir la feuille : la liste déroulante d'une cellule Enum<T> dans le Data Studio porte « Add a new member… », qui prépare le membre sur la feuille d'enum comme une seule étape d'annulation. Un enum enregistré depuis du C# de plugin n'offre pas cette ligne — le code en a la charge.
  • Les feuilles d'enum n'ont pas d'enregistrement, donc elles ne sont jamais bakées dans un ScriptableObject et Export/Push laissent leur texte intact ; l'import les signale séparément des onglets sautés.
  • Voir Capacités et limites pour les deux limites : les enums enregistrés par plugin ne peuvent pas être étendus depuis une feuille, et le fichier d'enum généré atterrit toujours dans le dossier des paramètres.

Feuilles de localisation (@loc)

Une ligne de marqueur @loc transforme la feuille en une feuille de localisation : les lignes sont des clés, les colonnes sont des locales, et la cellule @loc de chaque colonne de locale nomme son code de locale.

  • La colonne clé RecordId est obligatoire — la valeur de clé est la clé de localisation.
  • La première colonne de locale est la locale source.
  • Les colonnes de locale sont des colonnes de type chaîne. string? est la forme recommandée : une cellule vide est alors un trou de couverture, pas une erreur.
  • Deux colonnes optionnelles sont réservées par leur nom : smart (bool) et comment (string).
@loc     |            | en          | ko    |
@name    | codeName   | en          | ko    | comment
@type    | RecordId   | string?     | string? | string?
@desc    | key        | source text |       |
         | ui.ok      | OK          | 확인  | Confirm button

La feuille reste une table ordinaire pour l'édition, Export, Push, xlsx et l'application web. Ce qui change, c'est la sortie : aucune classe d'enregistrement et aucune SO de base de données, mais des constantes de clé par onglet et — lorsque le package Unity Localization est installé — la synchronisation StringTable. @enum et @loc sur la même feuille est une erreur de conflit.

L'histoire complète — les références LocRef, la génération de clé, le pont, les flux de traduction — se trouve sur Feuilles de localisation.

Marqueurs structurels personnalisés (enregistrés par plugin)

@overlap est l'exemple intégré d'un marqueur par colonne : une ligne de marqueur dont les cellules portent une valeur par colonne, validée colonne par colonne. Un plugin peut enregistrer ses propres marqueurs de la même façon — par ex. un marqueur @curve qui annote la façon dont chaque colonne numérique interpole.

La valeur est stockée comme métadonnée indépendante du domaine (FieldSchema.MarkerValues), que les validateurs, les contributeurs d'arêtes et l'infobulle d'en-tête de colonne de la fenêtre de création peuvent lire. Le Core n'interprète jamais lui-même la valeur — la validation est déléguée à la définition du marqueur.

  • Les marqueurs personnalisés enregistrés sont acceptés exactement comme @overlap : n'importe quel ordre au-dessus des données, les doublons sont rejetés, un marqueur en dessous des données est une erreur.
  • Chaque marqueur ne possède que sa validation de valeur par colonne (y compris ce que signifie une cellule vide) — il ne prend pas en charge le parsing de la ligne entière. Les « formes » de données restent du ressort de la normalisation (références, List<T>, colonnes type).
  • Les marqueurs personnalisés servent à des métadonnées de niveau colonne, pas à de nouvelles formes de données. Voir Création de plugins §4.5 pour un exemple d'enregistrement.
  • @style est l'unique marqueur intégré qui n'est pas par colonne (il décrit la feuille), il n'est donc pas le modèle à copier — c'est @overlap qui l'est.

Composer des données complexes : la normalisation d'abord

La façon recommandée d'exprimer des structures complexes est l'assemblage par référence (« assemblez, ne scriptez pas ») :

  • Les atomes vivent sous forme de lignes dans leur propre onglet.
  • Les combinaisons sont des listes de références : List<RecordId@Effects>.
  • Une colonne type (un enum) relie une ligne de données à un atome de code — votre runtime effectue un switch dessus pour distribuer le comportement. Aucun langage de script embarqué n'est nécessaire.

Les mini-grammaires (types de cellule personnalisés comme attack:add:10) sont faites pour de petits tuples — le Core fournit les conventions ; et : ; ne les utilisez pas à outrance.

Pour une logique réellement procédurale et ponctuelle, référencez un asset de script de la même façon que vous référencez une image : List<AssetRef@Scripts>. SheetForge valide la référence et bake l'addressable ; exécuter le script est le travail de votre jeu.

« Formes » de données spéciales : même des données d'apparence délicate (courbes de niveau, etc.) se normalisent proprement (List<float>, assemblage par référence). Un marqueur structurel personnalisé ajoute des métadonnées de niveau colonne (validées par colonne), pas une nouvelle forme de données — normalisez d'abord les données, et ne recourez à un marqueur personnalisé que pour des annotations par colonne réellement verbeuses. Voir Création de plugins.

Pages associées