跳至主要內容
SheetForge

試算表語法

SheetForge 的試算表具有自我描述性:A 欄保留給標記使用,實際資料則從 B 欄開始。列是以其標記來識別,而非以其位置,因此你可以在任何地方插入註解列,而不會破壞任何東西。

若要改造既有的試算表,只需在你的資料前面插入一欄標記欄,並加入三個標記列。既有的資料欄則維持原樣不動。

標記(A 欄)

A 欄意義
#註解列——完全被忽略,並在往返流程中逐字保留。
@name欄位名稱列(每欄一個名稱)。
@type欄位型別列。
@desc描述列——程式碼產生器會將其烘焙進 XML 檔案註解與檢閱器工具提示中。
@overlap(選填) 逐欄設定的重複值政策——true(允許,預設值)/false(強制數值唯一)。
@style(選填) 試算表顯示中繼資料——此試算表的群組標籤與顏色。詳見下方說明。
@enum(選填) 將整個試算表標示為 enum 定義,而非資料表。詳見下方說明。
@loc(選填) 將整個試算表標示為在地化試算表——它的儲存格標明每一欄的語言代碼。詳見下方說明。
@yourMarker(選填,由外掛註冊) 一種自訂結構標記——詳見下方說明。
(空白)資料列。
  • @name@type@desc必要項@overlap@style 及任何自訂標記皆為選填。
  • 標記列可以以任意順序出現,只要它們都位於資料列之上即可。
  • 未知的 @marker 會被視為錯誤,並提供最接近的候選建議(例如「你是不是想輸入 @desc?」)。已註冊的自訂標記也會加入候選建議池。
  • 若某欄沒有 @name@type 標頭卻存在資料,即為錯誤(孤兒資料防護機制——絕不允許資料被靜默遺失)。

範例(顯示欄位 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      |

item.potion空白的 price 儲存格會實體化為明確設定的預設值 10。)

型別系統

每一種型別都具有自我描述性——讀者只需查看 @type 儲存格,就能知道該欄存放的是什麼內容。

標記法意義
int float bool string內建純量型別。
Enum<DamageType>一個 C# enum——可以是在 enum 定義試算表中定義(詳見下方說明,無需撰寫程式碼),或是由外掛註冊(EnumRegistry)。成員名稱會被驗證,拼字錯誤會提供最接近的候選建議。
List<T>清單——元素分隔符為 ;,元素會被去除頭尾空白,空元素是錯誤,空白儲存格則代表空清單。
RecordId此分頁的鍵值欄——一個字串形式的自我識別碼(例如 item.sword)。永遠是必要的純量型別。建議欄位名稱:codeName
IntId此分頁的次要整數鍵值——每個分頁最多一個,為必要的純量型別,供執行期/存檔/後端 id 使用。建議欄位名稱:id。一個分頁可以以 RecordIdIntId,或兩者作為鍵值。
RecordId@Effects參照到 Effects 分頁中的一筆記錄,依其字串鍵值——會進行完整性驗證(目標分頁存在、具有鍵值欄、id 可解析;拼字錯誤會提供候選建議)。
IntId@Effects參照到 Effects 分頁中的一筆記錄,依其整數鍵值——與 RecordId@Effects 完全對等:以相同方式進行完整性驗證(目標分頁存在、具有 IntId 欄、id 可解析),未命中時會提供最接近的整數候選建議。數值會以 int.ToString 正規化,因此手動輸入的 007 會被解析為 7
AssetRef@Icons參照到 Addressables 群組 Icons 中的一項資源——會依目錄驗證其是否存在。子資源(紋理中的一個 Sprite、字型中的一個材質)會以 parent[sub] 定址——這正是 Addressables 賦予子物件項目的位址,例如 atlas[sword]——並依此鍵值進行驗證、烘焙(SubObjectName)與匯出。
AssetRef@Icons<Sprite>同一種參照,但限制為單一資源型別:只有當該資源——或其某個子資源——能夠被載入為 Sprite 時,該位址才算通過驗證。型別名稱可以是專案認得的任何一種衍生自 UnityEngine.Object 的資源型別(引擎內建或你自己的皆可):恰好只有一個型別相符時使用短名稱,否則使用完整名稱(MyGame.ItemData)。程式碼產生器會輸出 AssetReferenceT<Sprite>AssetRef@Icons 若不帶 <…>,則維持不受限;AssetRef<Sprite>@Icons 則會被拒絕,並提供正確拼法的建議。詳見下方的型別化資源參照
LocRef@Strings參照在地化試算表 Strings 中的一個在地化鍵值——以與 RecordId@Tab 相同的方式進行完整性驗證(存在性、最接近候選建議、重新命名傳播、選取器、下拉選單),並內嵌預覽該條目的來源語言文字。目標分頁必須帶有 @loc(否則為 LocRefTargetNotLocalizationSheet),沒有 @Target 的裸 LocRef 會被拒絕。List<LocRef@Strings>LocRef@Strings? 一樣可以照常組合。程式碼產生器會輸出一個純粹的 LocRef 結構——請參閱在地化試算表
Color · AnimationCurve · Gradient內建的視覺數值型別。每一種都有其精簡的文字形式(詳見下方),Data Studio 與網頁應用會以原生的顏色、曲線或漸層編輯器來編輯它們,而非原始文字;程式碼產生器會輸出 UnityEngine.Color / AnimationCurve / Gradient 欄位。
Modifier (範例)由外掛註冊的自訂儲存格型別(請參閱外掛開發)——例如範例中的 stat:op:value 迷你文法。CustomType@Target 同樣只需註冊即可運作。當外掛選用實作 IReferencingCellType 時,該欄的行為就會與 RecordId@Target 完全相同——以相同方式進行驗證、提供候選建議、重新命名、繪製與挑選。
Pair<T> (範例)由外掛註冊的wrapper 型別——一種泛型數值結構 MyWrapper<T>,可將多個內部 T 值打包進單一儲存格中(例如 Pair<int> = 1~2)。內部型別會被遞迴解析,因此 Pair<RecordId@Effects>Pair<Enum<DamageType>>,以及巢狀的 Box<Pair<int>> 皆可正常運作。請參閱外掛開發

<>@ 代表不同的意義,且兩者可以並存:<> = 種類/wrapper(內建的 List,或外掛提供的 MyWrapper<T>),@ = 目標。因此 List<RecordId@Effects> 是一份參照清單,而 Pair<RecordId@Effects> 則打包了兩個參照——兩者都指向 Effects 分頁。整數鍵值的組合方式相同:List<IntId@Effects> 就是一份整數鍵參照的清單。

Wrapper 型別(MyWrapper<T>

外掛可以註冊一個wrapper——一種擁有外層語法(分隔符、元數)並將內部型別委派給 Core 處理的泛型數值結構。此 wrapper 可以與任何內部型別組合。其內部的任何參照仍會被驗證、在鍵值重新命名時同步更新、並在分頁重新命名時一併改寫(完整貫穿)。

拒絕規則(與 List 一致):

標記法是否允許?原因
Pair<RecordId@Effects> · Pair<Enum<E>> · Box<Pair<int>>允許Wrapper 包覆純量、參照、enum,或另一個 wrapper。
List<Pair<int>>允許由多個複合值組成的清單。wrapper 自身的分隔符必須與 ;(清單分隔符)不同——這是外掛開發者的責任。
Pair<List<int>>不允許清單不能位於 wrapper 內部List 必須維持扁平且位於最外層,與 List<List<T>> 規則相同)。
Pair<int>@Effects不允許wrapper 是一種數值結構;請將 @ 放在內部的葉節點上(改為 Pair<RecordId@Effects>)。
Pair<int?> · Pair<int=1>不允許選填性/預設值是欄位層級的標記法,並非內部型別的一部分。

必填/選填/預設值

標記法意義
float(無標記)必填——空白儲存格即為錯誤(在入口處就阻擋靜默污染)。
float?選填——空白儲存格會實體化為型別預設值0),並標記為 IsDefaulted。適用於四種純量型別(int/float/bool/string),以及三種視覺型別:Color? → 透明黑色 #00000000AnimationCurve? → 沒有任何關鍵影格的曲線、Gradient? → 白色漸層 `#FFFFFF@0,#FFFFFF@1
RecordId@Effects? · IntId@Effects? · AssetRef@Icons?選填參照——空白儲存格會實體化為一個空參照:「不指向任何東西」,同時保留目標分頁/群組資訊,並將該儲存格標記為 IsDefaulted。這並非失效的參照——參照完整性與資源鍵值驗證都會略過它,畫布不會為它繪製任何連線,@overlap 也不會將兩個空參照視為重複。若儲存格確實帶有值,則會依照原本的方式進行驗證,因此選填欄位中的拼字錯誤仍然會被抓出。
RecordId@Effects=明確寫出的同一件事:空白的明確預設值,與上方單純的 ? 等效。非空白的預設值(RecordId@Effects=fire)仍會被解析,也仍會進行完整性檢查。
int=1選填,並具有明確的預設值——空白儲存格會實體化為 1
List<T>空白儲存格永遠是允許的(代表空清單)。

? 不被接受的地方,原因永遠相同:Core 無法憑空捏造出一個值,因此這些型別需要明確寫出 =default。這包括:

  • Enum<T>?
  • 外掛自訂型別——Modifier?,包括 Modifier@Tab?
  • wrapper——Pair<int>?

鍵值欄則基於不同的原因被排除:空白的鍵值會滋生重複。因此 RecordId?(不帶目標的自我識別形式)與 IntId? 同樣會被拒絕。

其他刻意被拒絕的標記法:

  • int?=1RecordId@Effects?=fire——?= 兩者都代表「選填」,因此只能擇一使用。
  • List<T>?——清單本身就已允許空白。
  • List<List<T>>——不允許巢狀清單。
  • Pair<int?>——選填性是欄位層級的概念,並非內部型別的一部分。

數值規則

  • bool:僅接受 true / false,輸入時不區分大小寫;標準形式為小寫。
  • 數字:小數點永遠使用 .(不受地區設定影響)。以逗號表示的小數、NaN,以及 Infinity 都會在入口處被拒絕。
  • 浮點數往返:匯出會採用最短的往返格式呈現,因此 1.0 可能會變回 1——但數值本身會被精確保留(語意上的往返)。
  • 標記與 enum 的比對皆採用 Ordinal(序數)比較(不會有地區設定造成的意外)。

型別化資源參照(AssetRef@Group<Type>

AssetRef@Icons 接受該群組中的任何位址。AssetRef@Icons<Sprite> 則將其限縮為單一資源型別,而這項限縮會在三個地方受到檢查:驗證、程式碼產生,以及編寫介面。

  • 哪些名稱能夠解析。 型別可以是專案能夠載入、且衍生自 UnityEngine.Object 的任何資源型別——引擎內建型別(SpriteTexture2DAudioClip,或像 Texture 這樣的抽象基底型別)與你自己的 ScriptableObject 一視同仁;沒有允許清單這回事。元件(Component)與僅限編輯器使用的型別皆非候選對象。恰好只有一個型別帶有該名稱時,寫短名稱即可,否則請寫包含命名空間的完整名稱。名稱有歧義時(AmbiguousAssetType,會列出每一個候選型別)與名稱未知時(UnknownAssetType,並附上最接近的候選建議),皆會在 @type 列上、每欄各回報一次。
  • 什麼樣的資源能通過。 當該位址上的資源本身、或其任一子資源,能夠被載入為該型別時,這項限制就算通過——因此以 Sprite 模式匯入的紋理能通過 <Sprite>,而一般紋理則會逐儲存格回報為 AssetTypeMismatch。子資源本身可透過 parent[sub] 定址,且該鍵值只會依其自身的型別受到檢查。
  • 產生的程式碼無法參照到的型別,會被拒絕。 一個位於預先定義的組件(Assembly-CSharp 及其同類——任何沒有組件定義的指令碼資料夾)中的型別,雖然找得到,但會被回報為 AssetTypeNotReferenceable,因為產生的附屬組件無法參照那些組件,AssetReferenceT<T> 也就無法編譯通過。請將該型別移入一個組件定義中,或移除 <…>
  • 程式碼產生器會輸出什麼。 已解析的型別會輸出 AssetReferenceT<global::UnityEngine.Sprite>,不受限的欄則輸出 AssetReference。附屬組件定義會自動參照該型別所在的組件,且已解析的完整名稱會納入結構描述指紋中,因此重新對應名稱會使程式碼重新產生。
  • 與其他型別的組合方式相同AssetRef@Icons<Sprite>?List<AssetRef@Icons<Sprite>>,以及像 Pair<AssetRef@Icons<Sprite>> 這樣的 wrapper 皆可正常運作;AssetRef@Icons<>(空白)、AssetRef@Ic<ons(角括號出現在群組名稱中),以及 RecordId@Skills<X>(此限制僅適用於 AssetRef)則皆為語法錯誤。
  • Data Studio 的欄表單帶有一個 型別… 按鈕,會列出候選型別並代你改寫 @type 儲存格——請參閱 Data Studio

視覺數值型別(ColorAnimationCurveGradient

三種內建型別所帶有的數值,以原始文字呈現時難以閱讀。它們的文字形式經過設計,讓人可以手動輸入一個精簡版本,而每一項工具——編輯器、匯出、推送、網頁應用——則永遠寫入標準、完整的形式;一個數值能夠在試算表 → Unity → 試算表之間往返而不遺失任何內容。

這三種型別共用同一套分隔符,其階層恰好位於清單分隔符之下一層:在一個數值內部,項目之間以 , 分隔,一個項目內的欄位之間以 : 分隔,區段之間以 | 分隔,而一個關鍵影格的時間則以 @ 附加。List<> 的元素仍然以 ; 分隔,而這三種記法本身絕不會包含 ;——因此 List<AnimationCurve> = 0:0,1:1;0:1,1:0 能夠乾淨地拆分。數字在任何地方都以 . 作為小數點(若使用地區設定的逗號,會造成欄位數量錯誤,而絕不會是靜默的錯誤數值),分隔符前後的空白會被去除,且對任何一個可接受的輸入,往返關係 parse(render(parse(x))) == parse(x) 皆成立。

型別可接受的輸入標準形式
Color#RGB#RGBA#RRGGBB#RRGGBBAA(不分大小寫,# 為必要)顏色不透明時為大寫的 #RRGGBB,否則為 #RRGGBBAA——例如 #FF8800#FF880080
AnimationCurve`key,key,…[pre:post],其中一個關鍵影格為 t:vt:v:in:outt:v:in:out:inW:outW:wmt:v:in:out:inW:outW:wm:tm`(2, 4, 7 或 8 個欄位——3, 5 和 6 為錯誤)
Gradient`colorKeys[alphaKeys[

顏色:數值以四個位元組儲存。不支援 HDR(數值超過 1 的色版)——烘焙後的顏色會在匯出時被鉗制到 0…1 之間。此型別的預設值為透明黑色 #00000000

曲線wm 是加權切線旗標(0 代表無・1 代表入切線・2 代表出切線・3 代表兩側皆是),tm 則是切線模式組合 Left/Right,後面可選擇性地再接上 /broken——每一側都是 FreeAutoLinearConstantClampedAuto 之一,與 Unity 曲線編輯器所使用的名稱相同。較短的形式會自動補上其餘部分:2 欄位的關鍵影格會以相鄰點的斜率作為切線(Linear/Linear)、權重為 0.33333334 且不加權;4 欄位的關鍵影格會保留你自己的切線(Free/Free);7 欄位的關鍵影格則會加上權重。切線欄位可以是 Infinity-Infinity(代表一個 Constant 階梯);時間、數值與權重都必須是有限值,關鍵影格的時間必須彼此不同(匯入時關鍵影格會依時間排序,因此輸入的先後順序無關緊要),且關鍵影格的數量沒有上限。模式優先於數值:對於任何不是 Free 的一側,其切線數值都會在匯入時依模式重新計算——與 Unity 所執行的計算完全相同——因此手動輸入、卻與其模式矛盾的數值會被取代,讓試算表、編輯器與遊戲三方顯示的都是同一條曲線。包裹模式為 ClampForeverLoopPingPongDefaultOnce 會被接受為 ClampForever 的別名(Unity 會將其正規化)且絕不會被寫回。一條沒有任何關鍵影格的曲線沒有文字形式:它只會以選填欄的空白儲存格形式存在,匯出時會被渲染為空白儲存格。

漸層:色彩關鍵影格不帶 Alpha(色彩區段中出現 #RRGGBBAA 屬於錯誤——Alpha 有自己專屬的區段);在同一個區段內,@t 時間值要嘛全部出現、要嘛全部不出現,若全部不出現,關鍵影格就會被平均分佈(n = 10n ≥ 2i/(n−1));缺少 Alpha 區段代表 1@0,1@1,缺少模式代表 Blend。模式有 BlendFixed(階梯狀)與 PerceptualBlend;選填的色彩空間(GammaLinear)只會改變 PerceptualBlend 的內插方式。時間與 Alpha 值皆為 0…1;時間在匯入時會被量化為 16 位元,與 Unity 儲存的方式完全相同,因此你所看到的數值,正是引擎所持有的數值。只帶一個關鍵影格的漸層,經過 Unity 往返後會變成兩個相同的關鍵影格——畫面不會改變,只有關鍵影格的數量會增加。

清單List<Color> = #F00;#0F0List<Gradient> = #F00,#00F;#0F0,#000——清單分隔符維持不變。

Data Studio 會將這些儲存格顯示為原生的顏色、曲線與漸層欄位,網頁應用則顯示附完整編輯器的預覽——請參閱 Data StudioSheetForge Web。兩者寫入的都是標準形式;精簡形式是給人手動輸入用的。

鍵值與唯一性

  • RecordId(不帶 @)即為鍵值欄:每個分頁最多一個。
    • 沒有任何鍵值欄本身是有效的——直到有其他分頁參照此分頁為止(TargetTabHasNoKey)。
    • 有兩個以上則為錯誤(MultipleKeyColumns)。
    • 重複的鍵值(DuplicateRecordId)以及空白的鍵值儲存格皆為錯誤。
  • IntId 是次要的整數鍵值:其唯一性會被獨立強制執行,且其他分頁可以透過 IntId@Tab 參照它。
    • 整數鍵參照會獲得與 RecordId@Tab 相同的完整性驗證、最接近候選建議、重新命名時的同步更新,以及圖形/畫布支援。
  • 一個分頁可以只以 RecordId 為鍵值、只以 IntId 為鍵值,或兩者皆是,這三種情況在各處的行為皆對稱一致。
    • 當一個分頁同時帶有兩者時,RecordId 是顯示/識別用的值,整數則會顯示在其旁邊。
    • 另一個分頁可以用任一種方式指向同一筆記錄:以字串鍵值透過 RecordId@ThisTab,或以整數鍵值透過 IntId@ThisTab
  • @overlap:一般欄位預設允許重複值。在某欄的 @overlap 儲存格填入 false,即可強制執行以數值為準的唯一性。
    • 1.01 視為相同數值;當兩份清單的所有元素及其順序皆相符時,即視為重複。
    • 兩個空參照彼此絕不視為重複(一個空白的純量預設值,仍然是一個普通的值)。
    • 鍵值欄永遠具有唯一性;在鍵值欄上填寫 @overlap true 屬於矛盾錯誤。

試算表顯示中繼資料(@style

@style 讓一份試算表能夠說明自己屬於哪個群組、是什麼顏色,讓群組與配色能夠存放於試算表本身,而不只是存在編輯器裡。它是唯一一個描述試算表本身、而非其欄位的標記。因此它的儲存格並不對齊欄位——而是從 B 欄開始的一份自由排列的 key=value 配對清單。

@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
效果
title任意文字共用相同標題的試算表,會在 Data Studio 側邊欄中被收攏到該標題底下。各區段依照首次出現的順序顯示,同一區段內的試算表仍維持自己原有的順序;沒有標題的試算表則留在預設區段中。
color#RRGGBB(六位十六進位數字)為這份試算表在任何出現的地方著色:側邊欄的圓點、畫布上節點的邊框,以及每一條指這份試算表的連接埠與連線。
  • 兩個鍵皆為選填,順序也無關緊要;可以只寫一個、兩個都寫,或兩個都不寫。空白儲存格會被忽略(填充用的空白儲存格沒有問題)。
  • 驗證錯誤皆會以 MarkerCellInvalid 的形式回報,並附上儲存格座標與具體的修正方式:
    • 未知的鍵(附最接近的候選建議)、
    • 重複的鍵、
    • 缺少值、
    • 不符合 #RRGGBB 格式的顏色。
  • 三位數簡寫(#4AF)與具名顏色會被刻意拒絕,以確保該值只會以單一標記法往返。
  • 僅供顯示:程式碼產生、烘焙與結構描述指紋絕不會讀取 @style。變更一份試算表的顏色,不會重新產生程式碼,也不會重新烘焙 ScriptableObject。
  • 往返安全@style 列會如同註解列一樣被保留。新增、刪除、移動與重新命名欄位都不會動到它,因為它的儲存格並不屬於任何欄位。編輯它需透過Group & color 表單(在 Data Studio 側邊欄中右鍵點擊某份試算表),該表單會以標準形式改寫整列內容。
  • 一份只有 @style(外加註解)的試算表會被視為「尚無資料表」:匯入會附上警告並略過它,而不是因為缺少三個必要標記而失敗。一旦你加入 @name@type@desc,它就會被正常剖析。詳見功能與限制
  • style 是保留的標記名稱——試圖註冊它的外掛會被拒絕,而像 @styl 這樣的拼字錯誤,也會得到 @style 作為候選建議。

Enum 定義試算表(@enum

一個 Enum<T> 欄位需要一個 T。你可以透過外掛 C# 程式碼註冊一個(EnumRegistry),但你也可以直接寫在試算表裡——不需要程式碼,也不需要外掛。當以下任一條件成立時,一份試算表就會被讀取為 enum 定義:

  • 它帶有一列 @enum 標記(分頁可以取任何名稱),
  • 該分頁的名稱恰好是 Enum(區分大小寫),沒有 @type 列。

第二條規則刻意要求 @type 必須不存在:資料表永遠會有它,因此一個剛好叫做 Enum 的既有資料表,仍然會維持是一份資料表。若一份試算表同時帶有 @enum@type,即屬矛盾狀態,會直接以 EnumSheetMarkerConflict 回報,而不會被自行猜測判斷。

@desc 在這項判斷中完全不起作用——它在兩種試算表上都是合法的,而在 enum 試算表上,它描述的是該欄的 enum(詳見下方說明)。

一份 enum 試算表沒有資料表——沒有結構描述、沒有鍵值欄,也沒有記錄。一欄就是一個 enum@name 儲存格存放該 enum 的名稱,其下方的每一列(A 欄留白)則是一個成員。

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

這份試算表定義了兩個 enum,Enum<DamageType> / Enum<Element> 現在能在任何 @type 儲存格中解析——欄位語法本身並無變化。上方展示的三項額外設定皆為選填;即使只有一列單純的 @name 加上成員,依然是一份完整的 enum 試算表。

你不必自己手動輸入這個骨架:建立試算表功能內建出貨一個 Enum definitions 範本,會為你排好整份試算表,是兩個內建範本之一(請參閱 Data Studio ▸ 試算表的建立/刪除)。

  • 順序即是值,且 Name=value 能將其固定。 成員儲存格可以是單純的名稱,也可以是帶有明確整數的 Name=value——規則與 C# enum 完全相同:未編號的成員取前一個值加一,第一個成員則為 0
    • Normal / Rare=10 / Epic 會編譯成 0 / 10 / 11。程式碼產生器只有在你有寫出 = value 的地方才會輸出它。
    • 資料儲存格與下拉選單永遠使用名稱Rare,絕不會是 Rare=10)。
    • 若某個值不是單純的整數,或超出底層型別的範圍(包括因自動遞增而超出),即為 InvalidEnumMemberValue
    • 這也是為什麼 Data Studio 絕不會重新排列成員,也絕不會回填中間的空缺:移動一個成員的位置,會靜默地變更已經烘焙進資源、並儲存在存檔中的數值。
  • @desc 用來描述該 enum。 一欄的 @desc 儲存格,會在產生的程式碼中成為該 enum 的 XML <summary>(IDE 中的工具提示),與資料表欄位 @desc 的用途相同。空白儲存格=無描述;此標記列本身為選填。
  • @enum 儲存格用來選擇底層型別。 某一欄 @enum 列的儲存格,可以指定該 enum 的 C# 底層型別——可為 bytesbyteshortushortintuintlongulong 之一。
    • 空白儲存格(或分頁名為 Enum 且完全沒有 @enum 列)代表 int。其他任何值皆為 InvalidEnumUnderlyingType
    • 程式碼產生器會輸出 public enum Grade : byte { … }
    • 對於 ulong,超過 long.MaxValue 的明確數值無法透過試算表設定——請改為透過外掛 C# 註冊這類 enum。
  • 空白儲存格會被跳過,不會被視為成員,因此不同欄可以有不同的長度,中間的空缺也只會被直接略過。
  • 註解列(#)在試算表中的任何位置都會被忽略。一份試算表中可以有多個 enum,也可以有多份 enum 試算表,兩者皆可行。所有 enum 的名稱在彼此之間都必須是唯一的,若某個名稱已由外掛透過 C# 註冊,則以該外掛為準——該份試算表定義會以 DuplicateEnumName 被拒絕。
  • 名稱與成員都必須能作為合法的 C# 識別碼:僅限 ASCII 字母、數字與 _,不能以數字開頭,也不能是保留關鍵字(InvalidEnumIdentifier)。
    • 非 ASCII 字元會被刻意拒絕,因為外觀相似的 Unicode 識別碼,會產生一個沒有人能夠分辨彼此差異的型別。
    • 已宣告名稱但底下沒有任何成員時,會回報 EnumSheetEmptyColumn
    • 若某一欄中有任一成員失敗,該整個 enum 就會被整個捨棄,而不會以半註冊狀態留存。
  • 匯入會產生什麼。 整個專案共用一個 SheetForgeEnums.cs——enum 是專案層級的輸出,而非逐分頁輸出。它會寫入設定中所指定的產生程式碼資料夾,並使用與產生分頁型別相同的命名空間。第一次匯入會建立該型別、將其編譯,並在網域重新載入後完成烘焙,不需要額外的點擊操作。
  • 不開啟試算表也能新增成員Data StudioEnum<T> 儲存格下拉選單的最下方帶有 "Add a new member…",會將該成員以一個復原步驟的形式暫存到 enum 試算表上。由外掛透過 C# 註冊的 enum,則不會提供這一列——程式碼才是它的所有者。
  • Enum 試算表沒有記錄,因此絕不會被烘焙進 ScriptableObject,匯出/推送也不會動到它們的文字;匯入會將它們與被略過的分頁分開回報。
  • 關於兩項邊界,請參閱功能與限制:由外掛註冊的 enum 無法透過試算表擴充,而產生的 enum 檔案永遠會落在設定所指定的資料夾中。

在地化試算表(@loc

一列 @loc 標記列會把試算表變成一份在地化試算表:列是鍵值,欄是語言,而每個語言欄的 @loc 儲存格會標明它的語言代碼。

  • RecordId 鍵值欄為必要項——鍵值就是在地化鍵值。
  • 第一欄語言欄是來源語言
  • 語言欄是字串欄。建議寫成 string?:如此一來,空白儲存格就是一個涵蓋缺口,而不是錯誤。
  • 有兩個選填欄以名稱保留:smart(bool)與 comment(string)。
@loc     |            | en          | ko    |
@name    | codeName   | en          | ko    | comment
@type    | RecordId   | string?     | string? | string?
@desc    | key        | source text |       |
         | ui.ok      | OK          | 확인  | Confirm button

試算表在編輯、Export、Push、xlsx 與網頁應用中依然是一份普通表格。改變的是輸出:沒有記錄類別、沒有 Database SO,但有逐分頁的鍵值常數,以及——當 Unity Localization 套件安裝後——StringTable 同步。同一份試算表上同時出現 @enum@loc 是衝突錯誤。

完整的說明——LocRef 參照、鍵值生成、橋接、翻譯工作流程——都在在地化試算表

自訂結構標記(由外掛註冊)

@overlap逐欄標記的內建範例:這種標記列的每個儲存格各自帶有一欄的值,並逐欄進行驗證。外掛也能以相同方式註冊自己的標記——例如一個用來標註每個數值欄如何內插的 @curve 標記。

這類值會被儲存為與領域無關的中繼資料(FieldSchema.MarkerValues),供驗證器、邊貢獻者,以及編寫視窗的欄標頭工具提示讀取。Core 本身絕不會解讀該值——驗證工作會委派給標記的定義本身。

  • 已註冊的自訂標記,其接受方式與 @overlap 完全相同:可置於資料列上方的任意順序、重複標記會被拒絕、標記若出現在資料列下方則為錯誤。
  • 每個標記只負責自己的逐欄數值驗證(包括空白儲存格代表的意義)——它並不會接管整列的剖析工作。資料的「結構形態」仍交由正規化處理(參照、List<T>type 欄)。
  • 自訂標記是用於欄位層級的中繼資料,而非新的資料結構形態。註冊範例請參閱外掛開發 §4.5
  • @style 是唯一一個並非逐欄標記的內建標記(它描述的是試算表本身),因此它不是應該仿效的範本——@overlap 才是。

組合複雜資料:正規化優先

表達複雜結構的建議方式是參照組裝(「用組裝取代寫腳本」):

  • 原子資料以列的形式存放在自己的分頁中。
  • 組合則是參照清單:List<RecordId@Effects>
  • 一個 type 欄(一個 enum)會將資料列連結到程式碼中的原子——你的執行期程式可以對它使用 switch 來分派行為。完全不需要內嵌指令碼語言。

迷你文法(例如 attack:add:10 這類自訂儲存格型別)適用於小型元組——Core 提供了 ;: 這兩種慣例,請勿過度使用。

對於真正屬於程序性、一次性的邏輯,請以參照圖片相同的方式參照一個指令碼資源:List<AssetRef@Scripts>。SheetForge 會驗證該參照並烘焙其 addressable;至於執行該指令碼,則是你的遊戲該做的事。

特殊的資料「結構形態」:即使是看起來棘手的資料(例如等級曲線等),也能被乾淨地正規化(List<float>、參照組裝)。自訂結構標記所新增的是欄位層級的中繼資料(逐欄驗證),而不是新的資料結構形態——請先將資料正規化,只有在真正需要繁複的逐欄註記時,才考慮使用自訂標記。請參閱外掛開發

相關頁面