Pular para o conteúdo
SheetForge

Sintaxe da Planilha

Uma planilha do SheetForge é autodescritiva: a coluna A é reservada para marcadores, os dados reais começam na coluna B. As linhas são identificadas pelo seu marcador, não pela sua posição, então você pode inserir linhas de comentário em qualquer lugar e nada quebra.

Para adaptar uma planilha já existente, insira uma coluna de marcador na frente dos seus dados e adicione as três linhas de marcador. As colunas de dado existentes permanecem como estão.

Marcadores (coluna A)

Coluna ASignificado
#Linha de comentário — completamente ignorada, preservada ao pé da letra no round-trip.
@nameLinha de nome de campo (um nome por coluna).
@typeLinha de tipo de campo.
@descLinha de descrição — o codegen faz o bake dela em comentários de documentação XML e tooltips do inspector.
@overlap(opcional) Política de duplicidade por coluna — true (permitir, o padrão) / false (impor unicidade de valor).
@style(opcional) Metadado de exibição da planilha — um rótulo de grupo e uma cor para esta planilha. Veja abaixo.
@enum(opcional) Marca a planilha inteira como definições de enum em vez de uma tabela de dados. Veja abaixo.
@loc(opcional) Marca a planilha como uma planilha de localização — suas células nomeiam o código de idioma de cada coluna. Veja abaixo.
@yourMarker(opcional, registrado por plugin) Um marcador estrutural personalizado — veja abaixo.
(vazio)Linha de dado.
  • @name, @type, @desc são obrigatórios; @overlap, @style e quaisquer marcadores personalizados são opcionais.
  • As linhas de marcador podem aparecer em qualquer ordem, desde que estejam acima das linhas de dado.
  • Um @marker desconhecido é um erro, com uma sugestão de correspondência mais próxima ("você quis dizer @desc?"). Marcadores personalizados registrados entram no conjunto de sugestões.
  • Dados em uma coluna que não tem cabeçalho @name/@type são um erro (proteção contra dado órfão — perda silenciosa de dados nunca é permitida).

Exemplo (colunas 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      |

(a célula price vazia de item.potion materializa o padrão explícito 10.)

Sistema de tipos

Todo tipo é autodescritivo — quem lê consegue ver o que uma coluna contém apenas pela célula @type.

NotaçãoSignificado
int float bool stringEscalares embutidos.
Enum<DamageType>Um enum C# — definido em uma planilha de enum (veja abaixo, sem necessidade de código) ou registrado por um plugin (EnumRegistry). Nomes de membro validados, erros de digitação recebem sugestões de correspondência mais próxima.
List<T>Uma lista — separador de elemento ;, elementos com espaços removidos (trim), um elemento vazio é um erro, uma célula vazia é uma lista vazia.
RecordIdA coluna-chave desta aba — um autoidentificador em string (por exemplo, item.sword). Sempre um escalar obrigatório. Nome de coluna recomendado: codeName.
IntIdA chave inteira secundária desta aba — no máximo uma por aba, escalar obrigatório, para ids de runtime/save/backend. Nome de coluna recomendado: id. Uma aba pode ter chave em RecordId, IntId, ou ambos.
RecordId@EffectsUma referência a um registro na aba Effects pela sua chave em string — validada por integridade (a aba de destino existe, tem uma coluna-chave, o id resolve; erros de digitação recebem sugestões).
IntId@EffectsUma referência a um registro na aba Effects pela sua chave inteira — paridade completa com RecordId@Effects: validada por integridade da mesma forma (a aba de destino existe, tem uma coluna IntId, o id resolve), com uma sugestão de correspondência mais próxima inteira quando não encontrado. Os valores são canonicalizados com int.ToString, então um 007 digitado à mão resolve como 7.
AssetRef@IconsUma referência a um asset no grupo Addressables Icons — validada quanto à existência em relação ao catálogo. Um sub-asset (um sprite dentro de uma textura, um material dentro de uma fonte) é endereçado como parent[sub] — o endereço que o Addressables atribui a uma entrada de sub-objeto, por exemplo atlas[sword] — e é validado, cozido (bake) (SubObjectName) e exportado por essa chave.
AssetRef@Icons<Sprite>A mesma referência restrita a um único tipo de asset: um endereço passa somente quando o asset — ou um de seus sub-assets — pode ser carregado como Sprite. O nome é qualquer tipo de asset derivado de UnityEngine.Object que o projeto conhece (do engine ou seu próprio): o nome curto quando exatamente um tipo corresponde, senão o nome completo (MyGame.ItemData). O codegen emite AssetReferenceT<Sprite>; AssetRef@Icons sem <…> permanece sem restrição; AssetRef<Sprite>@Icons é rejeitado com a grafia correta sugerida. Veja Referências de asset tipadas abaixo.
LocRef@StringsUma referência a uma chave de localização na planilha de localização Strings — validada por integridade como RecordId@Tab (existência, sugestões de correspondência mais próxima, propagação de renomeação, seletor, dropdowns), exibindo em linha o texto no idioma de origem da entrada. A aba-alvo precisa carregar @loc (senão LocRefTargetNotLocalizationSheet), e um LocRef puro sem @Target é rejeitado. List<LocRef@Strings> e LocRef@Strings? se compõem normalmente. O codegen emite uma struct LocRef simples — veja Planilhas de Localização.
Color · AnimationCurve · GradientTipos de valor visual embutidos. Cada um tem uma forma de texto compacta (abaixo) que o Data Studio e o aplicativo web editam com um editor nativo de cor, curva ou gradiente em vez de texto bruto; o codegen emite campos UnityEngine.Color / AnimationCurve / Gradient.
Modifier (exemplo)Um tipo de célula personalizado registrado por plugin (veja Autoria de Plugins) — por exemplo, a mini-gramática stat:op:value do exemplo. CustomType@Target também funciona apenas por registro. Quando o plugin opta por IReferencingCellType, essa coluna se comporta exatamente como RecordId@Target — validada, sugerida, renomeada, desenhada e selecionável da mesma forma.
Pair<T> (exemplo)Um tipo wrapper registrado por plugin — uma forma de valor genérica MyWrapper<T> que empacota vários valores internos T em uma única célula (por exemplo, Pair<int> = 1~2). O tipo interno é resolvido recursivamente, então Pair<RecordId@Effects>, Pair<Enum<DamageType>> e o aninhado Box<Pair<int>> funcionam todos. Veja Autoria de Plugins.

<> e @ significam coisas diferentes e coexistem: <> = tipo/wrapper (List embutido, ou um MyWrapper<T> de plugin), @ = alvo. Então List<RecordId@Effects> é uma lista de referências, e Pair<RecordId@Effects> empacota duas referências — ambas para a aba Effects. A chave inteira se compõe da mesma forma: List<IntId@Effects> é uma lista de referências por chave inteira.

Tipos wrapper (MyWrapper<T>)

Um plugin pode registrar um wrapper — uma forma de valor genérica que possui uma sintaxe externa própria (delimitador, aridade) e delega o tipo interno ao Core. O wrapper se compõe com qualquer tipo interno. Quaisquer referências dentro dele ainda são validadas, propagadas ao renomear uma chave e reescritas ao renomear uma aba (pass-through completo).

Regras de rejeição (consistentes com List):

NotaçãoPermitido?Por quê
Pair<RecordId@Effects> · Pair<Enum<E>> · Box<Pair<int>>SimWrapper sobre um escalar, referência, enum, ou outro wrapper.
List<Pair<int>>SimUma lista de compostos. O delimitador do próprio wrapper precisa ser diferente de ; (o separador de lista) — uma responsabilidade de quem cria o plugin.
Pair<List<int>>NãoUma lista não pode ficar dentro de um wrapper (List permanece plana e no nível mais externo, mesma regra de List<List<T>>).
Pair<int>@EffectsNãoUm wrapper é uma forma de valor; coloque o @ na folha interna em vez disso (Pair<RecordId@Effects>).
Pair<int?> · Pair<int=1>NãoOpcionalidade/padrões são uma notação em nível de campo, não parte do tipo interno.

Obrigatório / opcional / padrões

NotaçãoSignificado
float (sem marcação)Obrigatório — uma célula vazia é um erro (contaminação silenciosa é bloqueada na entrada).
float?Opcional — uma célula vazia materializa o padrão do tipo (0), sinalizado como IsDefaulted. Aplica-se aos quatro escalares (int / float / bool / string) e aos três tipos visuais: Color? → preto transparente #00000000, AnimationCurve? → uma curva sem chaves, Gradient? → o gradiente branco `#FFFFFF@0,#FFFFFF@1
RecordId@Effects? · IntId@Effects? · AssetRef@Icons?Referência opcional — uma célula vazia materializa uma referência vazia: "não aponta para nada", com a aba/grupo de destino preservados e a célula sinalizada como IsDefaulted. Isso não é uma referência quebrada — a validação de integridade de referência e de chave de asset a ignora, o canvas não desenha nenhum fio para ela, e @overlap não conta duas referências vazias como duplicatas. Uma célula que de fato carrega um valor é validada exatamente como antes, então um erro de digitação em uma coluna opcional ainda é capturado.
RecordId@Effects=A mesma coisa escrita explicitamente: um padrão explícito vazio é equivalente ao ? isolado acima. Um padrão não vazio (RecordId@Effects=fire) ainda resolve e ainda é verificado quanto à integridade.
int=1Opcional com um padrão explícito — uma célula vazia materializa 1.
List<T>Uma célula vazia é sempre permitida (lista vazia).

Onde o ? não é aceito, o motivo é sempre o mesmo: o Core não consegue inventar um valor a partir do nada, então esses tipos precisam de um =default explícito. Isso cobre:

  • Enum<T>?
  • um tipo personalizado de plugin — Modifier?, incluindo Modifier@Tab?
  • um wrapper — Pair<int>?

As colunas-chave são excluídas por um motivo diferente: uma chave vazia geraria duplicatas. Então RecordId? (a forma de autoidentificador sem chave) e IntId? também são rejeitados.

Outras notações deliberadamente rejeitadas:

  • int?=1 e RecordId@Effects?=fire? e = ambos dizem "opcional", então escolha um.
  • List<T>? — uma lista já permite vazio.
  • List<List<T>> — sem listas aninhadas.
  • Pair<int?> — opcionalidade é em nível de campo, não parte de um tipo interno.

Regras de valor

  • bool: somente true / false, sem diferenciar maiúsculas/minúsculas na entrada; a forma canônica é minúscula.
  • Números: sempre . como separador decimal (independente de localidade). Decimais com vírgula, NaN e Infinity são rejeitados na entrada.
  • Round-trip de floats: a exportação renderiza o formato de round-trip mais curto, então 1.0 pode voltar como 1 — o valor é preservado exatamente (round-trip semântico).
  • Comparações de marcador e de enum são Ordinal (sem surpresas de localidade).

Referências de asset tipadas (AssetRef@Group<Type>)

AssetRef@Icons aceita qualquer endereço no grupo. AssetRef@Icons<Sprite> a restringe a um único tipo de asset, e essa restrição é verificada em três pontos: validação, geração de código e a superfície de autoria.

  • Quais nomes resolvem. O tipo é qualquer tipo de asset derivado de UnityEngine.Object que o projeto consegue carregar — tipos do engine (Sprite, Texture2D, AudioClip, uma base abstrata como Texture) e os seus próprios ScriptableObjects igualmente; não há lista de permissões. Componentes e tipos exclusivos do editor não são candidatos. Escreva o nome curto quando exatamente um tipo o carrega, senão o nome completo incluindo o namespace. Nomes ambíguos (AmbiguousAssetType, com todos os candidatos listados) e nomes desconhecidos (UnknownAssetType, com uma sugestão de correspondência mais próxima) são relatados uma vez por coluna, na linha @type.
  • O que passa. Um endereço satisfaz a restrição quando o asset naquele endereço — ou qualquer um dos seus sub-assets — pode ser carregado como aquele tipo — então uma textura importada no modo Sprite passa em <Sprite>, e uma textura comum é relatada por célula como AssetTypeMismatch. O próprio sub-asset é endereçável como parent[sub], e essa chave é verificada apenas contra o seu próprio tipo.
  • Um tipo que o código gerado não consegue referenciar é recusado. Um tipo que vive em um assembly predefinido (Assembly-CSharp e os demais do mesmo tipo — qualquer pasta de script sem uma assembly definition) é encontrado, mas relatado como AssetTypeNotReferenceable, porque o assembly complementar gerado não consegue referenciar esses assemblies e AssetReferenceT<T> não compilaria. Mova o tipo para uma assembly definition, ou remova o <…>.
  • O que o codegen emite. AssetReferenceT<global::UnityEngine.Sprite> para um tipo resolvido, AssetReference para uma coluna sem restrição. A assembly definition complementar referencia automaticamente o assembly ao qual o tipo pertence, e o nome completo resolvido faz parte do fingerprint do esquema, então remapear o nome regenera o código.
  • Se compõe como qualquer outro tipo: AssetRef@Icons<Sprite>?, List<AssetRef@Icons<Sprite>> e um wrapper como Pair<AssetRef@Icons<Sprite>> funcionam todos; AssetRef@Icons<> (vazio), AssetRef@Ic<ons (um colchete angular no nome do grupo) e RecordId@Skills<X> (a restrição é somente para AssetRef) são erros de sintaxe.
  • O formulário de coluna do Data Studio tem um botão Tipo… que lista os tipos candidatos e reescreve a célula @type para você — veja Data Studio.

Tipos de valor visual (Color, AnimationCurve, Gradient)

Três tipos embutidos carregam valores que não são legíveis como texto bruto. A forma de texto deles é projetada para que uma pessoa consiga digitar uma versão curta à mão, enquanto toda ferramenta — os editores, Exportação, Push, o aplicativo web — sempre grava a forma canônica e completa, e um valor sobrevive a planilha → Unity → planilha sem perdas.

Os separadores são compartilhados pelos três e ficam um nível abaixo do separador de lista: dentro de um valor, itens são separados por ,, campos dentro de um item por :, seções por |, e o tempo de uma chave é anexado com @. Elementos de um List<> continuam separados por ;, e nenhuma das três notações jamais contém um ; — então List<AnimationCurve> = 0:0,1:1;0:1,1:0 se separa de forma limpa. Os números usam . como ponto decimal em todo lugar (uma vírgula de localidade aparece como uma contagem de campo errada, nunca como um valor silenciosamente errado), espaços em branco ao redor dos separadores são removidos, e o round trip parse(render(parse(x))) == parse(x) vale para toda entrada aceita.

TipoEntrada aceitaForma canônica
Color#RGB, #RGBA, #RRGGBB, #RRGGBBAA (sem diferenciar maiúsculas/minúsculas, # obrigatório)#RRGGBB maiúsculo quando a cor é opaca, #RRGGBBAA caso contrário — #FF8800, #FF880080
AnimationCurve`key,key,…[pre:post], onde uma chave é 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 campos — 3, 5 e 6 são erros)
Gradient`colorKeys[alphaKeys[

Cor: o valor é armazenado como quatro bytes. HDR (canais acima de 1) não é suportado — uma cor cozida (bake) é limitada a 0…1 na Exportação. O padrão do tipo é preto transparente, #00000000.

Curva: wm é a flag de tangente ponderada (0 nenhuma · 1 entrada · 2 saída · 3 ambas) e tm é o par de modo de tangente Left/Right, opcionalmente seguido por /broken — cada lado é um entre Free, Auto, Linear, Constant, ClampedAuto, os mesmos nomes que o editor de curva da Unity usa. Formas mais curtas preenchem o resto: uma chave de 2 campos usa a inclinação até os seus vizinhos como tangentes (Linear/Linear), pesos de 0.33333334 e nenhuma ponderação; uma chave de 4 campos mantém as suas tangentes (Free/Free); uma chave de 7 campos acrescenta pesos. Campos de tangente podem ser Infinity ou -Infinity (um degrau Constant); tempo, valor e peso precisam ser finitos, os tempos das chaves precisam ser distintos (as chaves são ordenadas por tempo na importação, então a ordem em que você as digita não importa), e não há limite para o número de chaves. O modo vence o número: para qualquer lado que não seja Free, o valor da tangente é recalculado a partir do modo no momento da importação — o mesmo cálculo que a Unity realiza — então um número digitado à mão que contradiz o seu modo é substituído, e a planilha, os editores e o jogo todos mostram uma única curva. Os modos de wrap são ClampForever, Loop, PingPong e Default; Once é aceito como um alias de ClampForever (a Unity o normaliza) e nunca é gravado de volta. Uma curva sem chaves não tem forma de texto: ela existe apenas como a célula vazia de uma coluna opcional, e a Exportação a renderiza como uma célula vazia.

Gradiente: chaves de cor não carregam alfa (#RRGGBBAA na seção de cor é um erro — o alfa tem a sua própria seção); dentro de uma seção os tempos @t estão todos presentes ou todos ausentes, e quando ausentes as chaves são distribuídas uniformemente (n = 10, n ≥ 2i/(n−1)); uma seção de alfa ausente significa 1@0,1@1, um modo ausente significa Blend. Os modos são Blend, Fixed (degraus) e PerceptualBlend; o espaço de cor opcional (Gamma ou Linear) só muda como o PerceptualBlend interpola. Tempos e alfas são 0…1; os tempos são quantizados para 16 bits na importação, exatamente como a Unity os armazena, então o valor que você vê é o valor que o engine guarda. Um gradiente com uma única chave faz o round-trip pela Unity como duas chaves idênticas — a imagem não muda, apenas a contagem de chaves cresce.

Listas: List<Color> = #F00;#0F0, List<Gradient> = #F00,#00F;#0F0,#000 — o separador de lista permanece o mesmo.

O Data Studio mostra essas células como campos nativos de cor, curva e gradiente, e o aplicativo web como prévias com editores completos — veja Data Studio e SheetForge Web. Ambos gravam a forma canônica; as formas mínimas são para as pessoas.

Chaves e unicidade

  • RecordId (sem @) é a coluna-chave: no máximo uma por aba.
    • Zero colunas-chave é válido — até que outra aba referencie esta aba (TargetTabHasNoKey).
    • Duas ou mais é um erro (MultipleKeyColumns).
    • Valores de chave duplicados (DuplicateRecordId) e células de chave vazias são erros.
  • IntId é uma chave inteira secundária: a unicidade é imposta de forma independente, e outras abas podem referenciá-la via IntId@Tab.
    • Referências por chave inteira recebem a mesma validação de integridade, sugestões de correspondência mais próxima, propagação ao renomear e suporte de grafo/canvas que RecordId@Tab.
  • Uma aba pode ter chave somente em RecordId, somente em IntId, ou em ambos, e os três casos se comportam de forma simétrica em todo lugar.
    • Quando uma aba carrega ambos, RecordId é o valor de exibição/identidade e o inteiro é mostrado ao lado dele.
    • Outra aba pode apontar para o mesmo registro de qualquer uma das formas: RecordId@ThisTab pela sua chave em string, ou IntId@ThisTab pela sua chave inteira.
  • @overlap: colunas comuns permitem valores duplicados por padrão. Coloque false na célula @overlap de uma coluna para impor unicidade baseada em valor.
    • 1.0 e 1 contam como o mesmo valor; duas listas são duplicatas quando todos os elementos e sua ordem coincidem.
    • Duas referências vazias nunca são duplicatas uma da outra (um padrão escalar vazio ainda é um valor comum).
    • Colunas-chave são sempre únicas; escrever @overlap true em uma coluna-chave é um erro de contradição.

Metadado de exibição da planilha (@style)

O @style permite que uma planilha diga a qual grupo ela pertence e qual é a sua cor, então o agrupamento e a coloração vivem na planilha, e não somente no editor. É o único marcador que descreve a planilha, em vez de suas colunas. Suas células não são, portanto, alinhadas a colunas — elas são uma lista livre de pares key=value começando na coluna 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
ChaveValorEfeito
titleQualquer textoPlanilhas que compartilham um título são agrupadas sob esse cabeçalho na barra lateral do Data Studio. As seções aparecem na ordem da primeira ocorrência, e as planilhas mantêm sua própria ordem dentro de uma seção; planilhas sem título permanecem na seção padrão.
color#RRGGBB (seis dígitos hexadecimais)Tinge esta planilha em todo lugar onde ela aparece: o ponto na barra lateral, a borda do nó no canvas, e toda porta e fio que apontam para esta planilha.
  • As duas chaves são opcionais e a ordem não importa; escreva uma, as duas, ou nenhuma. Uma célula vazia é ignorada (células de preenchimento são aceitáveis).
  • Erros de validação chegam todos como MarkerCellInvalid, com a coordenada da célula e uma correção concreta:
    • uma chave desconhecida (com uma sugestão de correspondência mais próxima),
    • uma chave repetida,
    • um valor ausente,
    • uma cor que não está no formato #RRGGBB.
  • A forma abreviada de três dígitos (#4AF) e nomes de cor são rejeitados de propósito, então o valor faz o round-trip como uma única notação.
  • Somente exibição: o codegen, o bake e o fingerprint do esquema nunca leem o @style. Mudar a cor de uma planilha não regenera código nem refaz o bake dos ScriptableObjects.
  • Seguro para round-trip: a linha @style é preservada como uma linha de comentário. Adicionar, excluir, mover e renomear colunas a deixam intocada, porque suas células não pertencem a colunas. Editá-la passa pelo formulário Group & color (clique com o botão direito em uma planilha na barra lateral do Data Studio), que reescreve a linha em forma canônica.
  • Uma planilha que só tem @style (mais comentários) conta como "ainda sem tabela": a importação a ignora com um aviso, em vez de falhar pelos três marcadores obrigatórios ausentes. Assim que você adicionar @name/@type/@desc, ela é analisada normalmente. Veja Capacidades e Limites.
  • style é um nome de marcador reservado — um plugin que tenta registrá-lo é rejeitado, e um erro de digitação como @styl recebe @style como sugestão.

Planilhas de definição de enum (@enum)

Uma coluna Enum<T> precisa de um T. Você pode registrar um a partir de C# de plugin (EnumRegistry), mas também pode simplesmente escrevê-lo na planilha — sem código, sem plugin. Uma planilha é lida como definições de enum quando qualquer uma destas condições é verdadeira:

  • ela carrega uma linha de marcador @enum (a aba pode ter qualquer nome), ou
  • a aba se chama exatamente Enum (sensível a maiúsculas/minúsculas) e não tem linha @type.

A segunda regra exige que o @type esteja ausente de propósito: uma tabela de dados sempre o tem, então uma tabela existente que por acaso se chama Enum continua sendo uma tabela. Uma planilha que carrega tanto @enum quanto @type é contraditória e é relatada como EnumSheetMarkerConflict, em vez de ser adivinhada.

O @desc não participa dessa decisão — ele é válido em ambos os tipos de planilha, e em uma planilha de enum ele descreve o enum naquela coluna (veja abaixo).

Uma planilha de enum não tem tabela nenhuma — sem esquema, sem coluna-chave, sem registros. Uma coluna é um enum: a célula @name guarda o nome do enum, e toda linha abaixo dela (coluna A em branco) é um membro.

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

Essa planilha define dois enums, e Enum<DamageType> / Enum<Element> agora resolvem em qualquer célula @type — a sintaxe da coluna permanece a mesma. Os três extras mostrados acima são todos opcionais; uma linha @name simples mais membros ainda é uma planilha de enum completa.

Você não precisa digitar esse esqueleto você mesmo: o Create sheet vem com um template Enum definitions que monta a planilha para você, um dos dois templates embutidos (veja Data Studio ▸ Criação/exclusão de planilha).

  • A ordem é o valor, e Name=value a fixa. A célula de um membro é ou um nome simples ou Name=value com um inteiro explícito — exatamente as regras de enum do C#: um membro sem número é o valor anterior mais um, o primeiro é 0.
    • Normal / Rare=10 / Epic compila para 0 / 10 / 11. O codegen emite o = value somente onde você escreveu um.
    • Células de dado e dropdowns sempre usam o nome (Rare, nunca Rare=10).
    • Um valor que não é um inteiro simples, ou que cai fora do intervalo do tipo subjacente (inclusive por auto-incremento), é InvalidEnumMemberValue.
    • É também por isso que o Data Studio nunca reorganiza membros e nunca preenche uma lacuna retroativamente: deslocar um membro mudaria silenciosamente valores já cozidos (bake) em assets e armazenados em arquivos de save.
  • @desc descreve o enum. A célula @desc de uma coluna vira o <summary> XML daquele enum no código gerado (tooltips na IDE), no mesmo espírito do @desc de campo de uma tabela de dados. Célula vazia = sem descrição; a própria linha de marcador é opcional.
  • As células de @enum escolhem o tipo subjacente. A célula da linha @enum em uma coluna pode nomear o tipo subjacente C# daquele enum — um entre byte, sbyte, short, ushort, int, uint, long, ulong.
    • Célula vazia (ou nenhuma linha @enum sequer, em uma aba chamada Enum) significa int. Qualquer outra coisa é InvalidEnumUnderlyingType.
    • O codegen emite public enum Grade : byte { … }.
    • Para ulong, valores explícitos acima de long.MaxValue não são suportados a partir de uma planilha — registre um enum assim a partir de C# de plugin em vez disso.
  • Células em branco são puladas, não lidas como membros, então colunas podem ter comprimentos diferentes, e um buraco no meio é simplesmente ignorado.
  • Linhas de comentário (#) são ignoradas em qualquer lugar da planilha. Vários enums por planilha e várias planilhas de enum são ambos aceitáveis. Os nomes precisam ser únicos entre todos eles, e um nome que um plugin já registrou a partir de C# vence — a definição da planilha é rejeitada com DuplicateEnumName.
  • Nomes e membros precisam ser utilizáveis como identificadores C#: letras ASCII, dígitos e _, não começando com um dígito, e não sendo uma palavra-chave reservada (InvalidEnumIdentifier).
    • Não-ASCII é rejeitado deliberadamente, porque identificadores Unicode parecidos produziriam um tipo que ninguém consegue distinguir de outro.
    • Um nome declarado sem membros abaixo dele é EnumSheetEmptyColumn.
    • Se qualquer membro de uma coluna falhar, aquele enum inteiro é descartado, em vez de ser registrado pela metade.
  • O que a importação gera. Um único SheetForgeEnums.cs para o projeto inteiro — enums são uma saída em nível de projeto, não por aba. Ele é gravado na pasta de código gerado das configurações, no mesmo namespace dos tipos de aba gerados. A primeira importação cria o tipo, o compila e termina o bake depois do domain reload, sem nenhum clique extra.
  • Adicionando um membro sem abrir a planilha: o dropdown de uma célula Enum<T> no Data Studio traz "Add a new member…", que prepara o membro na planilha de enum como um único passo de undo. Um enum registrado a partir de C# de plugin não oferece essa linha — o código é o dono dele.
  • Planilhas de enum não têm registros, então nunca são cozidas (bake) em um ScriptableObject, e Exportação/Push deixam o seu texto intocado; a importação as relata separadamente das abas ignoradas.
  • Veja Capacidades e Limites para os dois limites: enums registrados por plugin não podem ser estendidos a partir de uma planilha, e o arquivo de enum gerado sempre cai na pasta de configurações.

Planilhas de localização (@loc)

Uma linha de marcador @loc transforma a planilha em uma planilha de localização: as linhas são chaves, as colunas são idiomas, e a célula @loc de cada coluna de idioma nomeia o seu código de idioma.

  • A coluna-chave RecordId é obrigatória — o valor da chave é a chave de localização.
  • A primeira coluna de idioma é o idioma de origem.
  • Colunas de idioma são colunas de string. string? é a forma recomendada: uma célula vazia é então uma lacuna de cobertura, não um erro.
  • Duas colunas opcionais são reservadas por nome: smart (bool) e comment (string).
@loc     |            | en          | ko    |
@name    | codeName   | en          | ko    | comment
@type    | RecordId   | string?     | string? | string?
@desc    | key        | source text |       |
         | ui.ok      | OK          | 확인  | Confirm button

A planilha continua sendo uma tabela comum para edição, Exportação, Push, xlsx e o aplicativo web. O que muda é a saída: nenhuma classe de registro e nenhum SO de Database, mas constantes de chave por aba e — quando o pacote Unity Localization está instalado — sincronização de StringTable. @enum e @loc na mesma planilha é um erro de conflito.

A história completa — referências LocRef, cunhagem, a ponte, fluxos de tradução — está em Planilhas de Localização.

Marcadores estruturais personalizados (registrados por plugin)

@overlap é o exemplo embutido de um marcador por coluna: uma linha de marcador cujas células carregam um valor por coluna, validado coluna por coluna. Um plugin pode registrar seus próprios marcadores da mesma forma — por exemplo, um marcador @curve que anota como cada coluna numérica interpola.

O valor é armazenado como metadado agnóstico de domínio (FieldSchema.MarkerValues) que validadores, contribuidores de aresta e o tooltip do cabeçalho de coluna da janela de autoria podem ler. O Core nunca interpreta o valor em si — a validação é delegada à definição do marcador.

  • Marcadores personalizados registrados são aceitos exatamente como o @overlap: qualquer ordem acima dos dados, duplicatas rejeitadas, um marcador abaixo dos dados é um erro.
  • Cada marcador possui apenas sua validação de valor por coluna (incluindo o que uma célula vazia significa) — ele não assume o parsing da linha inteira. "Formas" de dado permanecem na normalização (referências, List<T>, colunas type).
  • Marcadores personalizados são para metadado em nível de coluna, não novas formas de dado. Veja Autoria de Plugins §4.5 para um exemplo de registro.
  • @style é o único marcador embutido que não é por coluna (ele descreve a planilha), então ele não é o modelo a copiar — o @overlap é.

Compondo dados complexos: normalização primeiro

A forma recomendada de expressar estruturas complexas é a montagem por referência ("monte, não programe"):

  • Átomos vivem como linhas em sua própria aba.
  • Combinações são listas de referência: List<RecordId@Effects>.
  • Uma coluna type (um enum) liga uma linha de dado a um átomo de código — o seu runtime faz um switch nela para despachar comportamento. Nenhuma linguagem de script embutida é necessária.

Mini-gramáticas (tipos de célula personalizados como attack:add:10) são para tuplas pequenas — o Core fornece as convenções ; e :; não abuse delas.

Para lógica genuinamente procedural e pontual, referencie um asset de script da mesma forma que você referencia uma imagem: List<AssetRef@Scripts>. O SheetForge valida a referência e faz o bake do addressable; executar o script é trabalho do seu jogo.

"Formas" de dado especiais: mesmo dados de aparência complicada (curvas de nível, etc.) se normalizam de forma limpa (List<float>, montagem por referência). Um marcador estrutural personalizado adiciona metadado em nível de coluna (validado por coluna), não uma nova forma de dado — normalize os dados primeiro, e recorra a um marcador personalizado somente para anotações por coluna genuinamente verbosas. Veja Autoria de Plugins.

Páginas relacionadas