Inspector extensions
Add project-owned serialized data to all six Workbench modes through derived content types and the same field declarations used by Unity serialization
Workbench and the normal Unity Inspector read one schema from the selected content object's serialized fields. A project can derive GameCard, GameDeck, GamePlayerUnit, GameEnemyUnit, GameStatus, or GameEncounter, add Unity-serializable fields, and create that concrete type inside an existing GCS database. No GCS Runtime or Editor source change is required.
The extension belongs to the derived type. Existing base-type assets do not acquire new fields, and GCS does not convert an existing GameCard or other base asset into a derived type automatically.
Samples~/InspectorExtensions/ contains a copyable runtime and Editor assembly pair, derived classes for all six modes, typed runtime access, option providers, validation, a full-width custom control, and all three TGDropdown modes.
Keep the content type in a runtime assembly
Place derived content classes and option providers in a player-available assembly that references TinyGiants.GCS.Runtime:
{
"name": "YourGame.GCS.Content",
"references": [
"TinyGiants.GCS.Runtime"
]
}
The class must be a top-level public, concrete, non-generic type derived from the matching GCS ScriptableObject type. Keep each content class in a same-named .cs file so Unity can associate it with an exact MonoScript; types without that association are excluded because their sub-assets cannot persist reliably. Editor-only assemblies are also excluded because their content would not exist in a player build.
| Workbench mode | Derive from | Stored by | Runtime enumeration |
|---|---|---|---|
| Card | GameCard | GameCardDatabase.Cards | GCSApi.Cards() |
| Deck | GameDeck | GameDeckDatabase.Decks | GCSApi.Decks() |
| Player | GamePlayerUnit | GamePlayerUnitDatabase.PlayerUnits | GCSApi.PlayerUnits() |
| Enemy | GameEnemyUnit | GameEnemyUnitDatabase.EnemyUnits | GCSApi.EnemyUnits() |
| Status | GameStatus | GameStatusDatabase.Statuses | GCSApi.Statuses() |
| Encounter | GameEncounter | GameEncounterDatabase.Encounters | GCSApi.Encounters() |
After Unity compiles the class, open its mode and click +. When more than one concrete type is available, Workbench opens a type picker containing the built-in base type and every eligible project type. Selecting a type creates that concrete ScriptableObject as a sub-asset of the current database. The same schema also appears when the derived sub-asset is selected in the normal Unity Inspector.
using TinyGiants.GCS.Runtime;
public sealed class SampleCard : GameCard
{
public int RequiredLevel;
}
The base database list accepts this object because SampleCard is still a GameCard. The other five modes use the same inheritance rule.
Declare fields with Unity serialization rules
Workbench enumerates the same visible serialized properties as Unity. A field appears when all of these conditions are satisfied:
-
It is an instance field, not a property, constant, or static field.
-
It is public or marked
[SerializeField]. -
Its type is supported by Unity serialization.
-
It is not marked
[HideInInspector].
Public primitives, enums, Unity object references, serializable classes and structs, arrays, and supported lists therefore work without a GCS-specific registration call. A C# property such as public int Level { get; set; } is not a serialized field and does not appear automatically.
using System;
using System.Collections.Generic;
using TinyGiants.GCS.Runtime;
using UnityEngine;
public sealed class SampleCard : GameCard
{
[GCSSection("Basic")]
[GCSLabel("Required Level")]
[Min(0)]
[Tooltip("Minimum character level required to add this card to a deck")]
public int RequiredLevel;
[Tooltip("Project-owned school used by project rules")]
public string School;
[GCSOptions("Fire", "Ice", "Lightning")]
[Tooltip("Element used by project combat rules")]
public string Element;
[GCSSection("Visual")]
[Tooltip("Voice line played by the project presentation layer")]
public AudioClip Voice;
[Header("Extension Data")]
[Range(0, 10)]
public int ComboLimit = 3;
[TextArea(3, 8)]
public string Lore;
}
This example adds Required Level, School, and Element to Basic, adds Voice to Visual, then creates an independent Extension Data foldout after the built-in Behavior section for Combo Limit and Lore.
Control section placement with declaration order
Workbench does not use a separate Order number and does not sort fields alphabetically. The first appearance of a section determines the foldout order; serialized declaration order determines the field order inside each section. Reusing an existing section later appends the field there, so section grouping takes precedence over one global top-to-bottom field order.
| Declaration | Result |
|---|---|
[Header("Extension Data")] | Selects or creates the Extension Data foldout starting with this field |
[GCSSection("Basic")] | Selects the existing Basic foldout, or creates it when it does not exist |
| A following field without either attribute | Continues in the current section for the same declaring type |
| First field of a derived declaring type without either attribute | Enters the Additional foldout |
Another [Header] or [GCSSection] | Changes the current section from that field onward |
Section names are exact and case-sensitive. Use Basic, Visual, or Behavior with the same spelling to reuse the built-in foldout. Routing a derived field to an existing foldout appends it to that foldout; inheritance cannot insert a derived declaration between two fields declared in the GCS base class.
GCSSectionAttribute also accepts an optional icon string:
[GCSSection("Progression", "★")]
public int RequiredLevel;
The following standard Unity attributes have explicit Workbench behavior:
| Attribute | Workbench behavior |
|---|---|
[Header] | Starts or reuses a foldout and makes it current |
[Tooltip] | Applies the tooltip to the field row and control |
[Space] | Adds vertical space before the field |
[Min] | Clamps an edited integer or float to the declared minimum |
[Range] | Uses an integer or float slider with an input field |
[TextArea], [Multiline] | Uses a multiline text field |
[Delayed] | Commits string, integer, or float input after editing finishes |
[InspectorName] | Replaces the nicified field label or enum-option label |
[SerializeField], [HideInInspector] | Follow normal Unity visibility rules |
[GCSLabel] takes precedence over [InspectorName]. GCSInspectorControlRegistry and a control-selecting GCS attribute such as [GCSOptions] take precedence over a Unity CustomPropertyDrawer on the same field. When neither selects a control, another project-owned PropertyAttribute falls back to a bound Unity PropertyField, so its CustomPropertyDrawer remains active. Other serializable types use the same fallback. Register a specific UI Toolkit control through the Inspector controls API.
Supply single-choice options
GCSOptionsAttribute applies to a serialized string. It accepts one of three sources:
[GCSOptions("Fire", "Ice", "Lightning")]
public string Element;
[GCSOptions(
GCSInspectorOptionSource.CardRarity,
Editable = true,
EmptyValue = "Common")]
public string LootRarity;
[GCSOptions(typeof(SampleAbilityOptionProvider))]
public string AbilityId;
Editable = false uses a selection-only field. Editable = true keeps the same dropdown but also accepts a project-defined string that is not currently in the option list. Set EmptyValue when clearing the field must store a defined fallback; the built-in Rarity, Enemy Tier, and Encounter Difficulty fields use Common, Normal, and Normal respectively.
| Built-in source | Values supplied by GCS |
|---|---|
None | No built-in single-choice values |
CardType | Built-in and authored card types |
CardRarity | Built-in and authored card rarities |
CardTag | Authored card tags |
CardKeyword | Registered CardTag IDs with their display labels |
EnemyTier | Built-in and authored enemy tiers |
EncounterDifficulty | Built-in and authored encounter difficulties |
A dynamic provider implements the Runtime-only IGCSInspectorOptionProvider contract and has a public parameterless constructor. Workbench creates the provider when it needs to rebuild the options and passes the current content object to GetOptions.
using System.Collections.Generic;
using TinyGiants.GCS.Runtime;
using UnityEngine;
public sealed class SampleAbilityOptionProvider : IGCSInspectorOptionProvider
{
public IEnumerable<GCSInspectorOption> GetOptions(Object target)
{
yield return new GCSInspectorOption(
"fireball",
"Fireball",
new[] { "Combat", "Magic", "Fire" },
"Deals fire damage to one target");
yield return new GCSInspectorOption(
"guard-break",
"Guard Break",
new[] { "Combat", "Physical" },
"Reduces the target's armor");
}
}
Value is serialized, while Label is shown to the author. The string constructor stores a slash-delimited GroupPath; the list constructor used above stores explicit GroupSegments, supports any page depth, and allows a segment itself to contain /. Tooltip describes the row. Duplicate values are removed case-insensitively, so keep every stored value stable and unique.
Supply multiple-choice values
GCSMultiSelectAttribute applies to List<string> or string[]. It accepts the same fixed-option, provider-type, and built-in-source constructors as GCSOptionsAttribute.
[GCSMultiSelect(
typeof(SampleAbilityOptionProvider),
Style = GCSMultiSelectStyle.Chips)]
public List<string> GrantedAbilities = new List<string>();
[GCSMultiSelect(
GCSInspectorOptionSource.None,
Style = GCSMultiSelectStyle.CommaSeparated)]
public List<string> Factions = new List<string>();
| Style | Authoring behavior |
|---|---|
CommaSeparated | Supports comma-separated free text and a multiple-selection dropdown |
Chips | Shows ordered removable chips and adds values through the option dropdown |
With source None, Workbench also collects existing values from the same field on sibling content in the current database asset. Fixed options and provider options remain available regardless of style.
Select other GCS content
GCSSubAssetAttribute replaces an ordinary ScriptableObject reference with the GCS content picker. The picker searches registered databases, accepts derived instances of the requested base type, and can render a compact row or a summary card.
[GCSSubAsset(
typeof(GameStatus),
Required = true,
Style = GCSSubAssetStyle.Compact)]
public GameStatus RequiredStatus;
[GCSSubAsset(
typeof(GameDeck),
Style = GCSSubAssetStyle.Card)]
public GameDeck RewardDeckOverride;
Use GCSSubAssetListAttribute on an array or list of GCS ScriptableObject references:
[GCSSubAssetList(typeof(GameEnemyUnit), Required = true)]
public List<GameEnemyUnit> Reinforcements = new List<GameEnemyUnit>();
Required shows an inline warning when the reference is empty, or when a required list has no entries. It does not add an Issue Badge automatically. Register a project validator when the missing reference must participate in Workbench validation.
These attributes select existing content; they do not create an embedded ScriptableObject inside the current content asset.
Flatten serializable settings with GCSInline
GCSInlineAttribute exposes the direct visible children of a serializable class or struct in the current section without adding another nested property box.
using System;
using TinyGiants.GCS.Runtime;
using UnityEngine;
[Serializable]
public sealed class SampleRewardSettings
{
[Min(0)]
public int Gold;
[Range(0f, 1f)]
public float RareDropChance;
}
public sealed class SampleEncounter : GameEncounter
{
[Header("Extension Rewards")]
[GCSInline]
public SampleRewardSettings Rewards = new SampleRewardSettings();
}
A null reference is initialized when the type is concrete and has a parameterless constructor. Keep inline data serializable and use it for ordinary nested settings. Do not place GCSFlowGraphAttribute inside inline data; the FlowGraph editor requires a direct field path on the content object.
Inline presentation does not change a child's serialized path. A project validator targets a top-level field with nameof(SampleCard.RequiredLevel) and a nested field with its complete relative path, such as Rewards.Gold or Stats.RequiredLevel. Workbench resolves this PropertyPath before falling back to the displayed FieldLabel; the complete validation contract is documented in Inspector controls.
Add a FlowGraph editing field
GCSFlowGraphAttribute applies to a direct GameEffectFlowGraph field and adds Edit FlowGraph plus a static summary.
[GCSFlowGraph(GCSFlowGraphSummary.Entries)]
public GameEffectFlowGraph AlternateBehavior = new GameEffectFlowGraph();
Entries summarizes entry nodes. EnemyIntents summarizes patterns, leaf intents, and action nodes.
The attribute supplies authoring UI only. GCS automatically executes and validates the built-in Behavior fields on Cards, Statuses, and Enemies; it does not automatically execute or validate an additional project field such as AlternateBehavior. Project runtime code must decide when to run that graph, and a project validator must report its authoring errors. Keep custom FlowGraph fields directly on Card, Status, or Enemy derived types so the editor can apply the corresponding host rules.
Reuse the remaining built-in presentations
The Runtime metadata contract also exposes these focused controls:
| Attribute | Required field | Result |
|---|---|---|
[GCSAssetName] | string | Edits the value and renames the content sub-asset when the value is non-empty |
[GCSDescription] | string | Uses the GCS description editor with token and semantic-reference support |
[GCSLabel("Label")] | Any serialized field | Overrides the displayed label |
[GCSSegmentedBool("Off", "On")] | bool | Uses a two-segment Boolean picker |
[GCSDeckEntries] | GameDeck.Entries | Uses the card count picker and deck summary UI |
[GCSSection("Name", "Icon")] | Any serialized field | Selects a named Workbench section from this field onward |
Attributes applied to an incompatible field type fall back to the normal serialized-property control instead of converting the stored data.
Read the same typed data at runtime
Databases and GCSApi expose the base types, so use normal C# type checks or OfType<T>() to reach project fields:
using System.Linq;
using TinyGiants.GCS.Runtime;
SampleCard firstSampleCard = GCSApi.Cards()
.OfType<SampleCard>()
.FirstOrDefault();
if (firstSampleCard != null)
{
int requiredLevel = firstSampleCard.RequiredLevel;
}
if (cardInstance.GetActiveCard() is SampleCard activeSampleCard)
{
PlayVoice(activeSampleCard.Voice);
}
if (unitState.Source is SampleEnemyUnit sampleEnemy)
{
ApplyEnemyRules(sampleEnemy);
}
The same pattern works with GCSApi.Decks(), PlayerUnits(), EnemyUnits(), Statuses(), and Encounters(). These methods enumerate active databases and allocate a fresh list, so cache a result that is read repeatedly.
Derived fields are persistent authored definition data. Per-card-instance values, temporary unit values, and other battle state belong on the corresponding runtime state or in supported battle variables rather than on the ScriptableObject definition.
Preserve data through authoring operations
Workbench uses Unity serialization for derived fields and keeps the concrete type when it duplicates or pastes content. Duplication and paste copy all serialized fields, assign a unique display name, and generate a new GCS identity. Paste is accepted only when the copied concrete type is compatible with the current mode.
The Workbench clipboard stores a reference inside the current Unity Editor domain. It is not the operating-system clipboard and is not a cross-session or cross-Domain-Reload transfer format.
Built-in schema controls participate in Unity Undo/Redo. Creation, duplication, deletion, and database membership changes are also registered with Undo. A project-owned custom control participates only when it writes through GCSInspectorFieldContext.Modify; see Inspector controls.
Treat serialized names as saved-data contracts:
-
Use
[FormerlySerializedAs]when renaming a serialized field. -
Keep derived type names and assembly ownership stable, or provide an explicit asset migration.
-
Do not expect duplication to convert a base asset into a derived type; it preserves the source object's concrete type.
-
Create a project migration tool when existing base assets must become a new derived type.
Inspector extension does not automatically change Workbench List rows, Preview summaries, Used By behavior, card-face Prefabs, or runtime presentation. Those surfaces continue to read their existing GCS fields until project code or a separate presentation integration uses the derived data.