試算表語法
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。一個分頁可以以 RecordId、IntId,或兩者作為鍵值。 |
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? → 透明黑色 #00000000、AnimationCurve? → 沒有任何關鍵影格的曲線、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?=1與RecordId@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的任何資源型別——引擎內建型別(Sprite、Texture2D、AudioClip,或像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。
視覺數值型別(Color、AnimationCurve、Gradient)
三種內建型別所帶有的數值,以原始文字呈現時難以閱讀。它們的文字形式經過設計,讓人可以手動輸入一個精簡版本,而每一項工具——編輯器、匯出、推送、網頁應用——則永遠寫入標準、完整的形式;一個數值能夠在試算表 → 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:v、t:v:in:out、t:v:in:out:inW:outW:wm或t: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——每一側都是 Free、Auto、Linear、Constant、ClampedAuto 之一,與 Unity 曲線編輯器所使用的名稱相同。較短的形式會自動補上其餘部分:2 欄位的關鍵影格會以相鄰點的斜率作為切線(Linear/Linear)、權重為 0.33333334 且不加權;4 欄位的關鍵影格會保留你自己的切線(Free/Free);7 欄位的關鍵影格則會加上權重。切線欄位可以是 Infinity 或 -Infinity(代表一個 Constant 階梯);時間、數值與權重都必須是有限值,關鍵影格的時間必須彼此不同(匯入時關鍵影格會依時間排序,因此輸入的先後順序無關緊要),且關鍵影格的數量沒有上限。模式優先於數值:對於任何不是 Free 的一側,其切線數值都會在匯入時依模式重新計算——與 Unity 所執行的計算完全相同——因此手動輸入、卻與其模式矛盾的數值會被取代,讓試算表、編輯器與遊戲三方顯示的都是同一條曲線。包裹模式為 ClampForever、Loop、PingPong 與 Default;Once 會被接受為 ClampForever 的別名(Unity 會將其正規化)且絕不會被寫回。一條沒有任何關鍵影格的曲線沒有文字形式:它只會以選填欄的空白儲存格形式存在,匯出時會被渲染為空白儲存格。
漸層:色彩關鍵影格不帶 Alpha(色彩區段中出現 #RRGGBBAA 屬於錯誤——Alpha 有自己專屬的區段);在同一個區段內,@t 時間值要嘛全部出現、要嘛全部不出現,若全部不出現,關鍵影格就會被平均分佈(n = 1 → 0,n ≥ 2 → i/(n−1));缺少 Alpha 區段代表 1@0,1@1,缺少模式代表 Blend。模式有 Blend、Fixed(階梯狀)與 PerceptualBlend;選填的色彩空間(Gamma 或 Linear)只會改變 PerceptualBlend 的內插方式。時間與 Alpha 值皆為 0…1;時間在匯入時會被量化為 16 位元,與 Unity 儲存的方式完全相同,因此你所看到的數值,正是引擎所持有的數值。只帶一個關鍵影格的漸層,經過 Unity 往返後會變成兩個相同的關鍵影格——畫面不會改變,只有關鍵影格的數量會增加。
清單:List<Color> = #F00;#0F0,List<Gradient> = #F00,#00F;#0F0,#000——清單分隔符維持不變。
Data Studio 會將這些儲存格顯示為原生的顏色、曲線與漸層欄位,網頁應用則顯示附完整編輯器的預覽——請參閱 Data Studio 與 SheetForge Web。兩者寫入的都是標準形式;精簡形式是給人手動輸入用的。
鍵值與唯一性
RecordId(不帶@)即為鍵值欄:每個分頁最多一個。- 沒有任何鍵值欄本身是有效的——直到有其他分頁參照此分頁為止(
TargetTabHasNoKey)。 - 有兩個以上則為錯誤(
MultipleKeyColumns)。 - 重複的鍵值(
DuplicateRecordId)以及空白的鍵值儲存格皆為錯誤。
- 沒有任何鍵值欄本身是有效的——直到有其他分頁參照此分頁為止(
IntId是次要的整數鍵值:其唯一性會被獨立強制執行,且其他分頁可以透過IntId@Tab參照它。- 整數鍵參照會獲得與
RecordId@Tab相同的完整性驗證、最接近候選建議、重新命名時的同步更新,以及圖形/畫布支援。
- 整數鍵參照會獲得與
- 一個分頁可以只以
RecordId為鍵值、只以IntId為鍵值,或兩者皆是,這三種情況在各處的行為皆對稱一致。- 當一個分頁同時帶有兩者時,
RecordId是顯示/識別用的值,整數則會顯示在其旁邊。 - 另一個分頁可以用任一種方式指向同一筆記錄:以字串鍵值透過
RecordId@ThisTab,或以整數鍵值透過IntId@ThisTab。
- 當一個分頁同時帶有兩者時,
@overlap:一般欄位預設允許重複值。在某欄的@overlap儲存格填入false,即可強制執行以數值為準的唯一性。1.0與1視為相同數值;當兩份清單的所有元素及其順序皆相符時,即視為重複。- 兩個空參照彼此絕不視為重複(一個空白的純量預設值,仍然是一個普通的值)。
- 鍵值欄永遠具有唯一性;在鍵值欄上填寫
@overlaptrue屬於矛盾錯誤。
試算表顯示中繼資料(@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# 底層型別——可為byte、sbyte、short、ushort、int、uint、long、ulong之一。- 空白儲存格(或分頁名為
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 Studio 中
Enum<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>、參照組裝)。自訂結構標記所新增的是欄位層級的中繼資料(逐欄驗證),而不是新的資料結構形態——請先將資料正規化,只有在真正需要繁複的逐欄註記時,才考慮使用自訂標記。請參閱外掛開發。
相關頁面
- 核心概念——這些儲存格經過剖析後會發生什麼事
- Data Studio——不必開啟試算表即可編輯欄位/型別
- 在地化試算表 — 完整的
@loc試算表形態與LocRef參照 - 外掛開發——註冊 enum 與自訂儲存格型別
- 功能與限制——關於
<>wrapper 與自訂標記語法的邊界