Sintaxis de la hoja
Una hoja de SheetForge es autodescriptiva: la columna A está reservada para marcadores, y los datos reales comienzan en la columna B. Las filas se identifican por su marcador, no por su posición, así que puedes insertar filas de comentario en cualquier lugar y nada se rompe.
Para adaptar una hoja de cálculo existente, inserta una columna de marcadores delante de tus datos y añade las tres filas de marcador. Las columnas de datos existentes permanecen tal como están.
Marcadores (columna A)
| Columna A | Significado |
|---|---|
# | Fila de comentario — se ignora por completo, se conserva textualmente en el ciclo de ida y vuelta. |
@name | Fila de nombre de campo (un nombre por columna). |
@type | Fila de tipo de campo. |
@desc | Fila de descripción — el codegen la incorpora en los comentarios de documentación XML y en los tooltips del inspector. |
@overlap | (opcional) Política de duplicados por columna — true (permitir, el valor predeterminado) / false (forzar unicidad de valor). |
@style | (opcional) Metadatos de visualización de la hoja — una etiqueta de grupo y un color para esta hoja. Ver más abajo. |
@enum | (opcional) Marca toda la hoja como definiciones de enum en lugar de una tabla de datos. Ver más abajo. |
@loc | (opcional) Marca la hoja entera como una hoja de localización — sus celdas nombran el código de configuración regional de cada columna. Ver más abajo. |
@yourMarker | (opcional, registrado por un plugin) Un marcador estructural personalizado — ver más abajo. |
| (vacío) | Fila de datos. |
@name,@type,@descson obligatorios;@overlap,@styley cualquier marcador personalizado son opcionales.- Las filas de marcador pueden aparecer en cualquier orden, siempre que estén por encima de las filas de datos.
- Un
@markerdesconocido es un error, con una sugerencia de coincidencia más cercana ("¿quisiste decir@desc?"). Los marcadores personalizados registrados se suman al conjunto de sugerencias. - Los datos en una columna que no tiene encabezado
@name/@typeson un error (protección contra datos huérfanos — nunca se permite la pérdida silenciosa de datos).
Ejemplo (columnas mostradas 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 celda price vacía de item.potion materializa el valor predeterminado explícito 10.)
Sistema de tipos
Cada tipo es autodescriptivo — quien lee puede ver qué contiene una columna solo con la celda @type.
| Notación | Significado |
|---|---|
int float bool string | Escalares integrados. |
Enum<DamageType> | Un enum de C# — ya sea definido en una hoja de enum (ver más abajo, sin necesidad de código) o registrado por un plugin (EnumRegistry). Los nombres de los miembros se validan, y los errores tipográficos reciben sugerencias de coincidencia más cercana. |
List<T> | Una lista — separador de elementos ;, los elementos se recortan (trim), un elemento vacío es un error, una celda vacía es una lista vacía. |
RecordId | La columna clave de esta pestaña — un autoidentificador de tipo string (por ejemplo, item.sword). Siempre es un escalar obligatorio. Nombre de columna recomendado: codeName. |
IntId | La clave entera secundaria de esta pestaña — como máximo una por pestaña, escalar obligatorio, para ids de runtime/guardado/backend. Nombre de columna recomendado: id. Una pestaña puede tener como clave RecordId, IntId, o ambas. |
RecordId@Effects | Una referencia a un registro en la pestaña Effects por su clave de tipo string — validada por integridad (la pestaña destino existe, tiene columna clave, el id se resuelve; los errores tipográficos reciben sugerencias). |
IntId@Effects | Una referencia a un registro en la pestaña Effects por su clave entera — con paridad completa respecto a RecordId@Effects: validada por integridad de la misma manera (la pestaña destino existe, tiene una columna IntId, el id se resuelve), con una sugerencia del entero más cercano en caso de fallo. Los valores se canonicalizan con int.ToString, así que un 007 escrito a mano se resuelve como 7. |
AssetRef@Icons | Una referencia a un asset en el grupo de Addressables Icons — validada por existencia contra el catálogo. Un sub-asset (un sprite dentro de una textura, un material dentro de una fuente) se direcciona como parent[sub] — la dirección que Addressables asigna a una entrada de sub-objeto, p. ej. atlas[sword] — y se valida, se hace bake (SubObjectName) y se exporta con esa clave. |
AssetRef@Icons<Sprite> | La misma referencia restringida a un solo tipo de asset: una dirección pasa solo cuando el asset — o uno de sus sub-assets — se puede cargar como Sprite. El nombre es cualquier tipo de asset derivado de UnityEngine.Object que el proyecto conozca (del motor o propio): el nombre corto cuando coincide exactamente un tipo, o si no el nombre completo (MyGame.ItemData). El codegen emite AssetReferenceT<Sprite>; AssetRef@Icons sin <…> permanece sin restricción; AssetRef<Sprite>@Icons se rechaza sugiriendo la escritura correcta. Consulta Referencias de asset tipadas más abajo. |
LocRef@Strings | Una referencia a una clave de localización en la hoja de localización Strings — validada por integridad como RecordId@Tab (existencia, sugerencias de coincidencia más cercana, propagación de renombrado, selector, listas desplegables), previsualizando en línea el texto de la configuración regional de origen de la entrada. La pestaña destino debe llevar @loc (LocRefTargetNotLocalizationSheet en caso contrario), y un LocRef desnudo sin @Target se rechaza. List<LocRef@Strings> y LocRef@Strings? se componen con normalidad. El codegen emite una struct LocRef sencilla — consulta Hojas de localización. |
Color · AnimationCurve · Gradient | Tipos de valor visuales integrados. Cada uno tiene una forma de texto compacta (más abajo) que Data Studio y la app web editan con un editor nativo de color, curva o degradado en lugar de texto en bruto; el codegen emite campos UnityEngine.Color / AnimationCurve / Gradient. |
Modifier (ejemplo) | Un tipo de celda personalizado registrado por un plugin (consulta Creación de plugins) — por ejemplo, la minigramática stat:op:value del ejemplo. CustomType@Target también funciona solo con el registro; cuando el plugin se adhiere a IReferencingCellType, esa columna se comporta exactamente igual que RecordId@Target — validada, sugerida, renombrada, dibujada y seleccionada de la misma manera. |
Pair<T> (ejemplo) | Un tipo wrapper registrado por un plugin — una forma de valor genérica MyWrapper<T> que empaqueta varios valores internos T en una sola celda (por ejemplo, Pair<int> = 1~2). El tipo interno se resuelve recursivamente, así que Pair<RecordId@Effects>, Pair<Enum<DamageType>> y el anidado Box<Pair<int>> funcionan todos. Consulta Creación de plugins. |
<> y @ significan cosas distintas y coexisten: <> = tipo/wrapper (List integrado, o un MyWrapper<T> de plugin), @ = destino. Así, List<RecordId@Effects> es una lista de referencias, y Pair<RecordId@Effects> empaqueta dos referencias — ambas hacia la pestaña Effects. La clave entera se compone de la misma manera: List<IntId@Effects> es una lista de referencias por clave entera.
Tipos wrapper (MyWrapper<T>)
Un plugin puede registrar un wrapper — una forma de valor genérica que posee una sintaxis externa propia (delimitador, aridad) y delega el tipo interno al Core. El wrapper se compone con cualquier tipo interno. Las referencias que contenga se siguen validando, se propagan al renombrar una clave, y se reescriben al renombrar una pestaña (pass-through completo).
Reglas de rechazo (coherentes con List):
| Notación | ¿Permitido? | Por qué |
|---|---|---|
Pair<RecordId@Effects> · Pair<Enum<E>> · Box<Pair<int>> | Sí | Wrapper sobre un escalar, una referencia, un enum u otro wrapper. |
List<Pair<int>> | Sí | Una lista de compuestos. El delimitador propio del wrapper debe ser distinto de ; (el separador de lista) — responsabilidad de quien crea el plugin. |
Pair<List<int>> | No | Una lista no puede ir dentro de un wrapper (List se mantiene plana y en el nivel más externo, la misma regla que List<List<T>>). |
Pair<int>@Effects | No | Un wrapper es una forma de valor; coloca el @ en la hoja interna en su lugar (Pair<RecordId@Effects>). |
Pair<int?> · Pair<int=1> | No | La opcionalidad/los valores predeterminados son una notación a nivel de campo, no forman parte del tipo interno. |
Obligatorio / opcional / valores predeterminados
| Notación | Significado |
|---|---|
float (sin marcar) | Obligatorio — una celda vacía es un error (la contaminación silenciosa se bloquea en la entrada). |
float? | Opcional — una celda vacía materializa el valor predeterminado del tipo (0), marcada como IsDefaulted. Se aplica a los cuatro escalares (int / float / bool / string) y a los tres tipos visuales: Color? → negro transparente #00000000, AnimationCurve? → una curva sin claves, Gradient? → el degradado blanco `#FFFFFF@0,#FFFFFF@1 |
RecordId@Effects? · IntId@Effects? · AssetRef@Icons? | Referencia opcional — una celda vacía materializa una referencia vacía: "no apunta a nada", con la pestaña/grupo destino preservados y la celda marcada IsDefaulted. Esto no es una referencia rota — la validación de integridad de referencias y de claves de asset la omite, el lienzo no dibuja ningún cable para ella, y @overlap no cuenta dos referencias vacías como duplicadas. Una celda que sí lleva un valor se valida exactamente igual que antes, así que un error tipográfico en una columna opcional se sigue detectando. |
RecordId@Effects= | Lo mismo escrito de forma explícita: un valor predeterminado explícito vacío equivale al ? simple de arriba. Un valor predeterminado no vacío (RecordId@Effects=fire) igual se resuelve y se sigue validando por integridad. |
int=1 | Opcional con un valor predeterminado explícito — una celda vacía materializa 1. |
List<T> | Una celda vacía siempre está permitida (lista vacía). |
Donde ? no se acepta, la razón siempre es la misma: el Core no puede inventar un valor de la nada, así que esos tipos necesitan un =default explícito. Eso cubre:
Enum<T>?- un tipo personalizado de plugin —
Modifier?, incluyendoModifier@Tab? - un wrapper —
Pair<int>?
Las columnas clave quedan excluidas por una razón distinta: una clave vacía generaría duplicados. Así que RecordId? (la forma de autoidentificador sin clave) e IntId? también se rechazan.
Otras notaciones rechazadas deliberadamente:
int?=1yRecordId@Effects?=fire—?y=dicen ambas "opcional", así que elige una.List<T>?— una lista ya permite vacío.List<List<T>>— no se permiten listas anidadas.Pair<int?>— la opcionalidad es a nivel de campo, no parte de un tipo interno.
Reglas de valores
- bool: solo
true/false, sin distinción de mayúsculas/minúsculas en la entrada; la forma canónica es en minúsculas. - Números: siempre
.como separador decimal (independiente de la configuración regional). Los decimales con coma,NaNeInfinityse rechazan en la entrada. - Los floats en el ciclo de ida y vuelta: la exportación genera el formato de ida y vuelta más corto, así que
1.0puede volver como1— el valor se preserva exactamente (ida y vuelta semántica). - Las comparaciones de marcadores y enums son Ordinal (sin sorpresas de configuración regional).
Referencias de asset tipadas (AssetRef@Group<Type>)
AssetRef@Icons acepta cualquier dirección del grupo. AssetRef@Icons<Sprite> la restringe a un solo tipo de asset, y esa restricción se comprueba en tres puntos: la validación, la generación de código y la superficie de creación.
- Qué nombres se resuelven. El tipo es cualquier tipo de asset derivado de
UnityEngine.Objectque el proyecto pueda cargar — tanto tipos del motor (Sprite,Texture2D,AudioClip, una base abstracta comoTexture) como tus propiosScriptableObjects; no hay lista blanca. Los componentes y los tipos de solo editor no son candidatos. Escribe el nombre corto cuando exactamente un tipo lo lleve, o si no el nombre completo incluyendo el namespace. Los nombres ambiguos (AmbiguousAssetType, con todos los candidatos listados) y los nombres desconocidos (UnknownAssetType, con una sugerencia de coincidencia más cercana) se reportan una vez por columna, en la fila@type. - Qué pasa la validación. Una dirección satisface la restricción cuando el asset en esa dirección, o cualquiera de sus sub-assets, se puede cargar como ese tipo — así que una textura importada en modo Sprite pasa
<Sprite>, y una textura simple se reporta por celda comoAssetTypeMismatch. El propio sub-asset es direccionable comoparent[sub], y esa clave se comprueba solo contra su propio tipo. - Un tipo que el código generado no puede referenciar se rechaza. Un tipo que vive en un ensamblado predefinido (
Assembly-CSharpy sus hermanos — cualquier carpeta de scripts sin una definición de ensamblado) se encuentra, pero se reporta comoAssetTypeNotReferenceable, porque el ensamblado complementario generado no puede referenciar esos ensamblados yAssetReferenceT<T>no compilaría. Mueve el tipo a una definición de ensamblado, o elimina el<…>. - Qué emite el codegen.
AssetReferenceT<global::UnityEngine.Sprite>para un tipo resuelto,AssetReferencepara una columna sin restricción. La definición de ensamblado complementaria referencia automáticamente el ensamblado en el que vive el tipo, y el nombre completo resuelto forma parte de la huella del esquema, así que volver a mapear el nombre regenera el código. - Se compone como cualquier otro tipo:
AssetRef@Icons<Sprite>?,List<AssetRef@Icons<Sprite>>y un wrapper comoPair<AssetRef@Icons<Sprite>>funcionan todos;AssetRef@Icons<>(vacío),AssetRef@Ic<ons(un corchete angular en el nombre del grupo) yRecordId@Skills<X>(la restricción es solo paraAssetRef) son errores de sintaxis. - El formulario de columna de Data Studio tiene un botón Tipo… que lista los tipos candidatos y reescribe la celda
@typepor ti — consulta Data Studio.
Tipos de valor visuales (Color, AnimationCurve, Gradient)
Tres tipos integrados llevan valores que son ilegibles como texto en bruto. Su forma de texto está diseñada para que una persona pueda escribir a mano una versión corta, mientras que cada herramienta — los editores, Export, Push, la app web — siempre escribe la forma canónica y completa, y un valor sobrevive hoja → Unity → hoja sin pérdida.
Los separadores son compartidos por los tres y están un nivel por debajo del separador de lista: dentro de un valor, los elementos se separan con ,, los campos dentro de un elemento con :, las secciones con |, y el tiempo de una clave se adjunta con @. Los elementos de un List<> se siguen separando con ;, y ninguna de las tres notaciones contiene jamás un ; — así que List<AnimationCurve> = 0:0,1:1;0:1,1:0 se divide limpiamente. Los números usan . como separador decimal en todas partes (una coma de configuración regional aparece como un número de campos incorrecto, nunca como un valor silenciosamente incorrecto), los espacios en blanco alrededor de los separadores se recortan, y el ciclo parse(render(parse(x))) == parse(x) se cumple para toda entrada aceptada.
| Tipo | Entrada aceptada | Forma canónica |
|---|---|---|
Color | #RGB, #RGBA, #RRGGBB, #RRGGBBAA (sin distinción de mayúsculas/minúsculas, # obligatorio) | #RRGGBB en mayúsculas cuando el color es opaco, #RRGGBBAA en caso contrario — #FF8800, #FF880080 |
AnimationCurve | `key,key,…[ | pre:post], donde una clave es t:v, t:v:in:out, t:v:in:out:inW:outW:wmot:v:in:out:inW:outW:wm:tm` (2, 4, 7 u 8 campos — 3, 5 y 6 son errores) |
Gradient | `colorKeys[ | alphaKeys[ |
Color: el valor se almacena como cuatro bytes. HDR (canales por encima de 1) no es compatible — un color generado mediante bake se limita a 0…1 en Export. El valor predeterminado del tipo es negro transparente, #00000000.
Curva: wm es la marca de tangente ponderada (0 ninguna · 1 entrada · 2 salida · 3 ambas) y tm el par de modo de tangente Left/Right, opcionalmente seguido de /broken — cada lado es uno de Free, Auto, Linear, Constant, ClampedAuto, los mismos nombres que usa el editor de curvas de Unity. Las formas más cortas completan el resto: una clave de 2 campos toma como tangentes la pendiente hacia sus vecinos (Linear/Linear), pesos de 0.33333334 y sin ponderación; una clave de 4 campos conserva tus tangentes (Free/Free); una clave de 7 campos añade pesos. Los campos de tangente pueden ser Infinity o -Infinity (un escalón Constant); el tiempo, el valor y el peso deben ser finitos, los tiempos de las claves deben ser distintos (las claves se ordenan por tiempo en la importación, así que el orden en que las escribes no importa), y no hay límite en el número de claves. El modo gana sobre el número: para cualquier lado que no sea Free, el valor de la tangente se recalcula a partir del modo en el momento de la importación — el mismo cálculo que realiza Unity — así que un número escrito a mano que contradiga su modo se reemplaza, y la hoja, los editores y el juego muestran todos una sola curva. Los modos de ajuste son ClampForever, Loop, PingPong y Default; Once se acepta como alias de ClampForever (Unity lo normaliza) y nunca se vuelve a escribir. Una curva sin claves no tiene forma de texto: existe solo como la celda vacía de una columna opcional, y Export la renderiza como una celda vacía.
Degradado: las claves de color no llevan alfa (#RRGGBBAA en la sección de color es un error — el alfa tiene su propia sección); dentro de una sección los tiempos @t están todos presentes o todos ausentes, y cuando están ausentes las claves se reparten uniformemente (n = 1 → 0, n ≥ 2 → i/(n−1)); una sección de alfa faltante significa 1@0,1@1, un modo faltante significa Blend. Los modos son Blend, Fixed (por pasos) y PerceptualBlend; el espacio de color opcional (Gamma o Linear) solo cambia cómo interpola PerceptualBlend. Los tiempos y los alfas son 0…1; los tiempos se cuantizan a 16 bits en la importación, exactamente como los almacena Unity, así que el valor que ves es el valor que tiene el motor. Un degradado con una sola clave hace round-trip a través de Unity como dos claves idénticas — la imagen no cambia, solo crece el número de claves.
Listas: List<Color> = #F00;#0F0, List<Gradient> = #F00,#00F;#0F0,#000 — el separador de lista no cambia.
Data Studio muestra estas celdas como campos nativos de color, curva y degradado, y la app web como vistas previas con editores completos — consulta Data Studio y SheetForge Web. Ambos escriben la forma canónica; las formas mínimas son para las personas.
Claves y unicidad
RecordId(sin@) es la columna clave: como máximo una por pestaña.- Cero columnas clave es válido — hasta que otra pestaña haga referencia a esta pestaña (
TargetTabHasNoKey). - Dos o más es un error (
MultipleKeyColumns). - Los valores de clave duplicados (
DuplicateRecordId) y las celdas de clave vacías son errores.
- Cero columnas clave es válido — hasta que otra pestaña haga referencia a esta pestaña (
IntIdes una clave entera secundaria: su unicidad se aplica de forma independiente, y otras pestañas sí pueden hacer referencia a ella medianteIntId@Tab.- Las referencias por clave entera reciben la misma validación de integridad, sugerencias de coincidencia más cercana, propagación al renombrar y soporte de grafo/lienzo que
RecordId@Tab.
- Las referencias por clave entera reciben la misma validación de integridad, sugerencias de coincidencia más cercana, propagación al renombrar y soporte de grafo/lienzo que
- Una pestaña puede tener como clave solo
RecordId, soloIntId, o ambas, y los tres casos se comportan de forma simétrica en todas partes.- Cuando una pestaña lleva ambas,
RecordIdes el valor de visualización/identidad y el entero se muestra junto a él. - Otra pestaña puede apuntar al mismo registro de cualquiera de las dos formas:
RecordId@ThisTabpor su clave de tipo string, oIntId@ThisTabpor su clave entera.
- Cuando una pestaña lleva ambas,
@overlap: las columnas ordinarias permiten valores duplicados de forma predeterminada. Colocafalseen la celda@overlapde una columna para forzar la unicidad basada en el valor.1.0y1cuentan como el mismo valor; dos listas son duplicadas cuando todos sus elementos y su orden coinciden.- Dos referencias vacías nunca son duplicadas entre sí (un valor predeterminado escalar vacío sigue siendo un valor ordinario).
- Las columnas clave son siempre únicas; escribir
@overlaptrueen una columna clave es un error de contradicción.
Metadatos de visualización de la hoja (@style)
@style permite que una hoja indique a qué grupo pertenece y de qué color es, de modo que la agrupación y el color viven en la hoja en lugar de solo en el editor. Es el único marcador que describe la hoja en lugar de sus columnas.
Por eso, sus celdas no están alineadas a columnas — son una lista libre de pares key=value que empieza en la columna 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| Clave | Valor | Efecto |
|---|---|---|
title | Cualquier texto | Las hojas que comparten un título se agrupan bajo ese encabezado en la barra lateral de Data Studio. Las secciones aparecen en el orden de primera aparición y las hojas mantienen su propio orden dentro de una sección; las hojas sin título permanecen en la sección predeterminada. |
color | #RRGGBB (seis dígitos hexadecimales) | Tiñe esta hoja dondequiera que aparezca: el punto de la barra lateral, el borde del nodo en el lienzo, y cada puerto y cable que apunte hacia esta hoja. |
- Ambas claves son opcionales y el orden no importa; escribe una, ambas o ninguna. Una celda vacía se ignora (las celdas de relleno no son un problema).
- Los errores de validación se reportan todos como
MarkerCellInvalid, con la coordenada de la celda y una corrección concreta:- una clave desconocida (con una sugerencia de coincidencia más cercana),
- una clave repetida,
- un valor faltante,
- un color que no es
#RRGGBB.
- La abreviatura de tres dígitos (
#4AF) y los colores con nombre se rechazan a propósito, para que el valor haga round-trip como una sola notación. - Solo visualización: el codegen, el bake y la huella del esquema nunca leen
@style. Cambiar el color de una hoja no regenera código ni vuelve a hacer bake de los ScriptableObjects. - Seguro en el ciclo de ida y vuelta: la fila
@stylese preserva como una fila de comentario. Añadir, eliminar, mover y renombrar columnas la dejan intacta, porque sus celdas no pertenecen a columnas. Editarla pasa por el formulario Group & color (clic derecho en una hoja en la barra lateral de Data Studio), que reescribe la fila en su forma canónica. - Una hoja que solo tiene
@style(más comentarios) cuenta como "todavía sin tabla": la importación la omite con una advertencia en lugar de fallar por los tres marcadores obligatorios faltantes. En cuanto añades@name/@type/@desc, se analiza con normalidad. Consulta Capacidades y límites. stylees un nombre de marcador reservado — un plugin que intenta registrarlo es rechazado, y un error tipográfico como@stylrecibe@stylecomo sugerencia.
Hojas de definición de enum (@enum)
Una columna Enum<T> necesita un T. Puedes registrar uno desde C# de un plugin (EnumRegistry), pero también puedes simplemente escribirlo en la hoja — sin código, sin plugin. Una hoja se lee como definiciones de enum cuando se cumple cualquiera de estas condiciones:
- lleva una fila de marcador
@enum(la pestaña puede llamarse de cualquier forma), o - la pestaña se llama exactamente
Enum(sensible a mayúsculas) y no tiene fila@type.
La segunda regla exige que @type esté ausente a propósito: una tabla de datos siempre la tiene, así que una tabla existente que resulte llamarse Enum sigue siendo una tabla. Una hoja que contiene tanto @enum como @type es contradictoria y se reporta como EnumSheetMarkerConflict en lugar de adivinarse.
@desc no participa en esta decisión — es legal en ambos tipos de hoja, y en una hoja de enum describe el enum en esa columna (ver más abajo).
Una hoja de enum no tiene tabla — sin esquema, sin columna clave, sin registros. Una columna es un enum: la celda @name contiene el nombre del enum, y cada fila debajo de ella (columna A en blanco) es un miembro.
@enum | byte |
@desc | Damage kind| Elemental affinity
@name | DamageType | Element
| Physical | Fire
| Magical=10 | Ice
| True | LightningEsa hoja define dos enums, y Enum<DamageType> / Enum<Element> ahora se resuelven en cualquier celda @type — la sintaxis de columna no cambia. Las tres características adicionales que se muestran arriba son todas opcionales; una fila @name desnuda más los miembros sigue siendo una hoja de enum completa.
No tienes que escribir tú mismo ese esqueleto: Create sheet incluye una plantilla Enum definitions que dispone la hoja por ti, una de las dos plantillas integradas (consulta Data Studio ▸ Crear y eliminar hojas).
- El orden es el valor, y
Name=valuelo fija. Una celda de miembro es o bien un nombre simple oName=valuecon un entero explícito — exactamente las reglas de enum de C#: un miembro sin número es el valor anterior más uno, y el primero es0.Normal / Rare=10 / Epiccompila a0 / 10 / 11. El codegen emite el= valuesolo donde tú lo escribiste.- Las celdas de datos y los menús desplegables siempre usan el nombre (
Rare, nuncaRare=10). - Un valor que no es un entero simple, o que cae fuera del rango del tipo subyacente (incluso por auto-incremento), es
InvalidEnumMemberValue. - Esa es también la razón por la que Data Studio nunca reordena los miembros ni rellena un hueco: desplazar un miembro cambiaría silenciosamente valores que ya están horneados (bake) en assets y guardados en archivos de partida.
@descdescribe el enum. La celda@descde una columna se convierte en el<summary>XML de ese enum en el código generado (tooltips en el IDE), en el mismo espíritu que el@descde un campo de tabla de datos. Celda vacía = sin descripción; la fila de marcador en sí es opcional.- Las celdas
@enumeligen el tipo subyacente. La celda de la fila@enumen una columna puede nombrar el tipo subyacente de C# para ese enum — uno debyte,sbyte,short,ushort,int,uint,long,ulong.- Una celda vacía (o ninguna fila
@enumen absoluto, en una pestaña llamadaEnum) significaint. Cualquier otra cosa esInvalidEnumUnderlyingType. - El codegen emite
public enum Grade : byte { … }. - Para
ulong, los valores explícitos por encima delong.MaxValueno son compatibles desde una hoja — registra un enum así desde C# de un plugin en su lugar.
- Una celda vacía (o ninguna fila
- Las celdas en blanco se omiten, no se leen como miembros, así que las columnas pueden tener longitudes distintas y un hueco en medio simplemente se pasa por alto.
- Las filas de comentario (
#) se ignoran en cualquier parte de la hoja. Tanto varios enums por hoja como varias hojas de enum están permitidos. Los nombres deben ser únicos entre todos ellos, y un nombre que un plugin ya registró desde C# gana — la definición de la hoja se rechaza conDuplicateEnumName. - Los nombres y los miembros deben poder usarse como identificadores de C#: letras ASCII, dígitos y
_, sin empezar con un dígito, y sin ser una palabra reservada (InvalidEnumIdentifier).- Lo no-ASCII se rechaza a propósito, porque los identificadores Unicode con apariencia similar producirían un tipo que nadie podría distinguir de otro.
- Un nombre declarado sin miembros debajo es
EnumSheetEmptyColumn. - Si algún miembro de una columna falla, ese enum completo se descarta en lugar de registrarse a medias.
- Lo que genera la importación. Un único
SheetForgeEnums.cspara todo el proyecto — los enums son una salida a nivel de proyecto, no por pestaña. Se escribe en la carpeta de código generado de los ajustes, en el mismo espacio de nombres que los tipos de pestaña generados. La primera importación crea el tipo, lo compila y termina el bake después del domain reload, sin ningún clic adicional. - Añadir un miembro sin abrir la hoja: el menú desplegable de una celda
Enum<T>en Data Studio incluye "Add a new member…", que prepara el miembro en la hoja de enum como un solo paso de undo. Un enum registrado desde C# de un plugin no ofrece esa fila — el código es su dueño. - Las hojas de enum no tienen registros, así que nunca se hornean (bake) en un ScriptableObject y Export/Push dejan su texto intacto; la importación las reporta por separado de las pestañas omitidas.
- Consulta Capacidades y límites para los dos límites: los enums registrados por plugin no se pueden extender desde una hoja, y el archivo de enum generado siempre termina en la carpeta de ajustes.
Hojas de localización (@loc)
Una fila de marcador @loc convierte la hoja en una hoja de localización: las filas son claves, las columnas son configuraciones regionales, y la celda @loc de cada columna de configuración regional nombra su código de configuración regional.
- La columna clave
RecordIdes obligatoria — el valor de clave es la clave de localización. - La primera columna de configuración regional es la de origen.
- Las columnas de configuración regional son columnas de tipo string.
string?es la forma recomendada: una celda vacía es entonces una brecha de cobertura, no un error. - Dos columnas opcionales están reservadas por nombre:
smart(bool) ycomment(string).
@loc | | en | ko |
@name | codeName | en | ko | comment
@type | RecordId | string? | string? | string?
@desc | key | source text | |
| ui.ok | OK | 확인 | Confirm buttonLa hoja sigue siendo una tabla ordinaria para editar, Export, Push, xlsx y la app web. Lo que cambia es la salida: sin clase de registro y sin SO de base de datos, pero con constantes de clave por pestaña y — cuando el paquete Unity Localization está instalado — sincronización de StringTable. @enum y @loc en la misma hoja es un error de conflicto.
La historia completa — referencias LocRef, generación de claves, el puente, flujos de trabajo de traducción — está en Hojas de localización.
Marcadores estructurales personalizados (registrados por plugin)
@overlap es el ejemplo integrado de un marcador por columna: una fila de marcador cuyas celdas llevan un valor por columna, validado columna por columna. Un plugin puede registrar sus propios marcadores de la misma manera — por ejemplo, un marcador @curve que anota cómo interpola cada columna numérica.
El valor se almacena como metadatos agnósticos de dominio (FieldSchema.MarkerValues) que los validadores, los contribuyentes de aristas y el tooltip del encabezado de columna de la ventana de creación pueden leer. Core nunca interpreta el valor en sí — la validación se delega a la definición del marcador.
- Los marcadores personalizados registrados se aceptan exactamente igual que
@overlap: en cualquier orden por encima de los datos, los duplicados se rechazan, un marcador debajo de los datos es un error. - Cada marcador solo posee su validación de valor por columna (incluido lo que significa una celda vacía) — no se hace cargo de analizar toda la fila. Las "formas" de datos permanecen en la normalización (referencias,
List<T>, columnastype). - Los marcadores personalizados son para metadatos a nivel de columna, no para nuevas formas de datos. Consulta Creación de plugins §4.5 para ver un ejemplo de registro.
@stylees el único marcador integrado que no es por columna (describe la hoja), así que no es el modelo a copiar —@overlapsí lo es.
Cómo componer datos complejos: primero la normalización
La forma recomendada de expresar estructuras complejas es el ensamblaje por referencias ("ensambla, no programes"):
- Los átomos viven como filas en su propia pestaña.
- Las combinaciones son listas de referencias:
List<RecordId@Effects>. - Una columna
type(un enum) vincula una fila de datos con un átomo de código — tu runtime haceswitchsobre ella para despachar el comportamiento. No se necesita ningún lenguaje de scripting embebido.
Las minigramáticas (tipos de celda personalizados como attack:add:10) son para tuplas pequeñas — el Core proporciona las convenciones ; y :; no abuses de ellas.
Para lógica verdaderamente procedural y puntual, haz referencia a un asset de script de la misma forma en que haces referencia a una imagen: List<AssetRef@Scripts>. SheetForge valida la referencia y hace bake del addressable; ejecutar el script es responsabilidad de tu juego.
"Formas" de datos especiales: incluso los datos de apariencia complicada (curvas de nivel, etc.) se normalizan limpiamente (
List<float>, ensamblaje por referencias). Un marcador estructural personalizado añade metadatos a nivel de columna (validados por columna), no una nueva forma de datos — normaliza los datos primero, y recurre a un marcador personalizado solo para anotaciones por columna genuinamente extensas. Consulta Creación de plugins.
Páginas relacionadas
- Conceptos fundamentales — qué sucede con estas celdas después del análisis
- Data Studio — edición de columnas/tipos sin abrir la hoja
- Hojas de localización — la forma de hoja
@locy las referenciasLocRefen detalle - Creación de plugins — registro de enums y tipos de celda personalizados
- Capacidades y límites — los límites de la sintaxis de wrapper
<>y de marcador personalizado