Serializable Type System
A class type as an ordinary field: Unity saves it, and the Inspector picks it from a list.
Quick start
| Before — Unity API | After — FastTools |
|---|---|
| |


SerializableType
SerializableType stores an assembly-qualified name: the type name together with its assembly.
| Variant | Selection constraint |
|---|---|
SerializableType | No base-type constraint |
SerializableType<T> | Types assignable to T |
In code, the constructor creates a wrapper; a type incompatible with T throws ArgumentException:
var primary = new SerializableType<Weapon>(typeof(Sword));
System.Type type = primary;
var empty = new SerializableType<Weapon>(null);
For a resolved type, ToString() returns Type.Name, so a generic type reads Amplify`1, not the picker's caption.
Missing type
A stored name that no longer resolves after a class, namespace, or assembly rename. The Inspector shows it as <Missing …>.


Typereturnsnull.AssemblyQualifiedNameandToString()return the stored name, from which the type can be restored.
In a player, SerializableType and SerializableMonoScript find the type by its name, a string in the scene, prefab or asset data. Managed code stripping does not read such strings, so from Managed Stripping Level Low up a class chosen only in the Inspector may be dropped from the build, and .Type then returns null while the editor resolves it. Mark such classes [Preserve] (UnityEngine.Scripting) or list them in link.xml. The same applies to [TypeSelector] on a string.
SerializableMonoScript
The same field, but the selection survives a class rename: the field remembers the script asset itself. Pick the type in the Inspector or drag a .cs file from Project onto the field.
After renaming Sword.cs → Blade.cs | SerializableType | SerializableMonoScript |
|---|---|---|
.Type | null, a missing type | Blade, in Play Mode too |
| Stored name | Sword | Blade once the asset is saved again |
Limitations:
- only classes with their own
.csare listed: top-level, non-generic, named after the file; - generic types, nested classes and types from DLLs cannot be picked;
- there is no public constructor, so the field cannot be created in code;
- renaming the class without its file, or the file outside Unity without its
.meta, breaks the link and the field shows a missing type.
TypeSelector
| Field | Selection result |
|---|---|
string | Stores the assembly-qualified name |
SerializableType / SerializableMonoScript | Configures the wrapper's selection |
[SerializeReference] | Creates an instance of the selected implementation — see SerializeReference Selector |
Which types are offered
public interface ITwoHanded { }
public abstract class Weapon { }
public abstract class MeleeWeapon : Weapon { }
public abstract class RangedWeapon : Weapon { }
public sealed class Sword : MeleeWeapon { }
public sealed class Axe : MeleeWeapon, ITwoHanded { }
public sealed class Bow : RangedWeapon, ITwoHanded { }
| Field | [TypeSelector(…, Allow = TypeAllow.None)] | Types offered |
|---|---|---|
string | typeof(Weapon) | Axe, Bow, Sword |
SerializableType<MeleeWeapon> | typeof(ITwoHanded) | Axe, the only MeleeWeapon with ITwoHanded |
SerializableType<Weapon> | "MeleeWeapon, Assembly-CSharp" | Axe, Sword |
SerializableType<Weapon> | typeof(Sword), typeof(Axe) | Empty, AFT0009 warns: no class inherits both |
SerializableType<Weapon>[] | no argument | Axe, Bow, Sword for each entry |
To allow a set of classes, give them a common interface or base class and pass it.
Properties
| Property | Default | Behaviour |
|---|---|---|
Allow | TypeAllow.All | Lets abstract classes (Abstract), interfaces (Interface), both or neither into the list. Ignored on [SerializeReference] |
Required | false | Warns about an empty type name or a null managed reference |
In the Inspector of a runtime object, the picker leaves out types from editor-only assemblies (UnityEditor, Editor-only asmdefs and Editor folders): a player build cannot resolve them. The rule follows the object's class, so a runtime object's field declared under #if UNITY_EDITOR leaves them out too.
Required field
[TypeSelector(typeof(Weapon), Required = true)]
[SerializeField] private string _secondaryWeapon;


With Required = true, <None> remains selectable. For strings and wrappers, the check tests for an empty stored name; a missing type with a nonempty name passes this check.
For project-wide and CI validation, see required-field checks.
Constraint from another field
Pass nameof(...) to let a field or property's current value control the candidate list:
[SerializeField] private SerializableType<Weapon> _weaponClass;
[TypeSelector(nameof(_weaponClass), Allow = TypeAllow.None)]
[SerializeField] private string _weaponName;
Choose MeleeWeapon in Weapon Class, and Weapon Name offers Sword and Axe. Changing a constraint does not clear an earlier selection.


| Constraint source | Constrains to |
|---|---|
System.Type | One type |
string | A type name resolved through Type.GetType() |
SerializableType / SerializableMonoScript | The resolved .Type value |
| An array of these values | Multiple simultaneous constraints; List<T> is not supported |
- A string is first looked up among the instance fields and readable properties of the class that declares the field, inherited ones included, then as a type name.
- For a field inside a
[Serializable]class or a list element, the source is read from that same instance. - While the source is empty or unresolved it adds no constraint: the string Weapon Name then offers every concrete class in the project. A wrapper keeps its own
T.
Errors in string arguments
[TypeSelector("Spear, Assembly-CSharp")]
[SerializeField] private string _weaponName;
Analyzers catch mistakes in the strings:
AFT0006— a one-word string that names no member of the class;AFT0007— the member cannot supply base types;AFT0008— the string is not a valid type name.
If a well-formed type name refers to a type that is not loaded, like Spear above, the Inspector shows a warning:


TypeSelectorDisplay
[TypeSelectorDisplay] on a class changes only its row in the picker:
Parameter on Sword | In the picker |
|---|---|
Name = "Longsword" | Longsword in the list and the closed field; search still finds Sword |
Group = "Weapons/Melee" | Weapons → Melee → Longsword instead of the namespace |
Tooltip = "A balanced blade" | Tooltip on hover |
Icon = "d_ScriptableObject Icon" | Icon: a built-in one by name, an asset by path with extension, or one from Resources without extension |
Hidden = true | Not listed; code assignment and an already stored value still work |
Subclasses do not inherit these settings.


[TypeSelector] and [TypeSelectorDisplay] are marked [Conditional("UNITY_EDITOR")]. Classes compiled into an external DLL without that symbol carry none of their settings, including Hidden.
The picker
The picker groups types by namespace or Group and distinguishes identical names by assembly.


The picker's root page keeps the types you need most often:
- Favorites: your picks. To add a type, click the star at the right of its row, or press Space while the row is selected.
- Recent: the types chosen last.
Whether Favorites is shown and how long Recent is (0 hides it) are set in the FastTools window's Settings tab. The gear at the bottom right of the picker opens it, and both lists are cleared there too.
Generic types
Picking an open generic type opens its argument pages and returns a constructed closed type:
public abstract class Enchantment { }
public sealed class Fire : Enchantment { }
public sealed class Frost : Enchantment { }
public sealed class Enchanted<T> : MeleeWeapon
where T : Enchantment { }
Choose Enchanted<T> in the _primaryWeapon field: the window offers the subclasses of Enchantment, and choosing Fire stores Enchanted<Fire>.


- A generic argument can itself be generic: the window asks for its arguments first.
- When every argument can be inferred from the field type, the closed type is returned immediately.
- Interfaces, abstract classes, and hidden types are not offered as arguments.
TypeSelectorWindow
TypeSelectorWindow opens the same picker from a custom inspector or editor window, for example from a UI Toolkit button:
using Aspid.FastTools.Types.Editors;
var button = new Button { text = "Select weapon" };
button.clicked += () => TypeSelectorWindow.Show(
GUIUtility.GUIToScreenRect(button.worldBound),
new TypeSelectorFilter { Types = new[] { typeof(Weapon) } },
currentAqn: selectedTypeName,
onSelected: aqn => selectedTypeName = aqn);
The callback receives an assembly-qualified name, or null for <None>; dismissing the window without a choice does not invoke it.
currentAqn | Marked on opening |
|---|---|
| A name from the list | That type; the window opens in its group |
"" (the default) | <None> |
null | Nothing |
| A name missing from the list | Nothing: Enter right after opening cannot erase the stored name |
TypeSelectorFilter
| Property | Default | Purpose |
|---|---|---|
Types | empty: any type | Base types; a candidate must be assignable to each |
Allow | None; All on [TypeSelector] | Allowed categories: abstract classes and interfaces |
Predicate | null | A condition on top of Types and Allow |
AdditionalTypes | null | Candidates that bypass Types, Allow, and Predicate; Hidden filtering remains |
ArgumentFilter | null | A condition on manually chosen arguments, on top of their where constraints |
InferredArgumentFilter | null | A filter for arguments inferred from Types; receives the generic definition, the parameter and the argument |
IncludeHidden | false | Offer types marked Hidden = true, as generic arguments too |
HideNoneOption | false | Hide <None> on the root page |
Package sample
For Inspector selection of enemy types and spawn patterns, see Types; for a picker opened from editor code, see EditorTools.

