Skip to main content

Fields and the inspector

A script's public fields and settable properties are its settings. The editor shows them in the inspector, scenes and prefabs save them per entity, and the game reads them back before the script's OnCreate runs. This page covers which members are saved, every value type the inspector can edit, the attributes that control how fields appear, and how to rename fields and classes without losing saved values.

What is saved​

MemberSaved and shown
Public fieldYes, unless it is readonly
Public property with a public getter and setterYes
Private or protected field marked [SerializeField]Yes
Auto-property with a private setter, marked [field: SerializeField]Yes, through its backing field
Any of the above marked [Transient]No
Field or property whose type is a scriptNo, it is runtime state
Everything else: private fields, get-only properties, constants, staticsNo

Saved values are written under the member's name in camel case, so RunSpeed becomes runSpeed:

{ "type": "ScriptComponent", "data": { "scripts": [
{ "type": "LanternGrove.PlayerController", "enabled": true, "fields": { "runSpeed": 320, "jumpSpeed": 860 } }
] } }

A field's initializer, such as = 320, is the value a script starts with. When the scene has a saved value for the field, that value replaces the initializer before OnCreate runs. Fields without a saved value, such as a field you add to the class later, start from the initializer. Do not count on a changed initializer to update entities you already placed: wherever a value is saved, the saved value wins.

Use [SerializeField] to keep a setting out of your script's public API, and [field: SerializeField] for a value other scripts may read but only this script should change:

[SerializeField]
private float _alertRadius = 160;

[field: SerializeField]
public int Kills { get; private set; }

Use [Transient] for public runtime state that should not end up in the scene, such as a cooldown timer another script reads:

[Transient]
public float Stamina;

Value types​

TypeIn the inspector
boolA checkbox
int, long, short, byte and other integers, float, double, decimalA number box; a slider with [Range]
stringA text box; multi-line with [Multiline]
EnumsA drop-down; [Flags] enums save their names separated by commas
Vector2X and Y boxes
ColorA color swatch with a picker
Rect2X, Y, width and height
Curve, GradientThe curve and gradient editors
GuidA text box
EntityAn entity picker; see Entity references
TextureAsset, Texture, SoundClip, MusicTrack, TileMapAn asset picker that loads the asset before the scene starts
AssetGuidAn asset picker that saves the guid without loading anything; filter it with [AssetFilter]
Arrays, List<T>, HashSet<T>, ImmutableArray<T>A list with add, remove and reorder
Nullable value types, such as float?The value with a way to clear it
Structs and classes of your ownA nested group of their public fields and settable properties

Types the engine has no converter for, such as Dictionary<TKey, TValue>, interfaces and delegates, are not saved. TileMap lives in the Talesmith.Assets.Maps namespace, so add a using for it; the other types are available in every script.

This enemy uses most of them:

assets/scripts/Enemy.cs
namespace MyGame;

public enum Behavior
{
Wander,
Guard,
Chase
}

public struct Loot
{
[AssetFilter(".tprefab")]
public AssetGuid Prefab;

[Range(0, 1)]
public float Chance;
}

/// <summary>An enemy whose fields cover most of what the inspector can edit.</summary>
public sealed class Enemy : Script
{
[Header("Stats")]
[Range(1, 500)]
public int Health = 100;

[Range(0, 600, Step = 10)]
[Tooltip("Top speed in units per second.")]
public float Speed = 140;

[Label("AI")]
public Behavior Behavior = Behavior.Guard;

[Header("Looks")]
public Color Tint = Color.White;

[Angle]
public float FacingAngle;

public Curve? SpeedOverHealth;

public Gradient? HurtColors;

[Header("References")]
public TextureAsset? Portrait;

public SoundClip? HurtSound;

[AssetFilter(".tprefab")]
public AssetGuid Corpse;

public Entity Patrol;

public List<Vector2> Waypoints = [];

public List<Loot> Drops = [];

[Multiline(4)]
public string Bark = "Halt!";

[HideInInspector]
public int Generation;

[Transient]
public float Stamina;
}

Asset references​

There are two ways to refer to an asset:

  • Typed references (TextureAsset, Texture, SoundClip, MusicTrack, TileMap) are loaded with the scene. The field holds the loaded asset when OnCreate runs, so you can use it at once, at the cost of loading it even if the script never does.
  • AssetGuid saves only the asset's id. Nothing loads until you ask, which suits prefabs to spawn, presets to apply or levels to load later. [AssetFilter(".tprefab")] limits the picker to matching files, and you pass the guid to Spawn, Assets.LoadAsync or similar. For prefabs this is the right choice: see Spawning and finding entities.

Both survive moving and renaming the asset in the editor, because scenes store the asset's guid, not its path.

Entity references​

An Entity field refers to another entity of the same scene or prefab. Pick it in the inspector. The scene saves the target's id and the game resolves it to the live entity when the scene or prefab instance is created, so Patrol holds a valid handle in OnCreate.

An Entity is only a handle. The entity can be destroyed while your script keeps the handle, so check World.IsAlive(entity) before using one that might be gone. A reference into another prefab or scene cannot be saved; find such entities at run time instead.

Fields that hold other scripts​

A field whose type is a script, such as public PlayerController? Player;, is never saved or shown. Keep the Entity in a saved field and look the script up when the scene starts:

public Entity Player;

private PlayerController? _controller;

protected override void OnStart() => _controller = GetScript<PlayerController>(Player);

Hot reload remaps such fields to the new instances, so a cached script reference stays valid after you change code while playing.

Inspector attributes​

The attributes live in Talesmith.Authoring, which every script can use. They are the same ones components use.

AttributeEffect
[Range(min, max)]Limits a number. With both ends finite the inspector shows a slider; [Range(0)] only sets a minimum. Step sets the drag and step increment, such as [Range(0, 600, Step = 10)].
[Tooltip("…")]Explains the field when you hover it.
[Label("…")]Shows another name than the one derived from the member name.
[Header("…")]Starts a titled group of fields from this field on.
[Multiline(lines)]Edits a string in a text box of several lines; 3 by default.
[HideInInspector]Saves the field but does not show it.
[Transient]Neither saves nor shows the field.
[Angle]The value is in radians; the inspector edits it in degrees.
[AssetFilter(".png", ".jpg")]Restricts an asset field to these extensions.
[Layer], [LayerMask]Edits an int as one collision layer by name, or as a mask with a checkbox per layer. Pass LayerSet.ShadowCasters for shadow caster layers.
[SerializeField]Saves and shows a non-public field.

Field labels come from the member name split into words, so JumpSpeed shows as Jump Speed and _alertRadius as Alert Radius. The script's own section title comes from its class name the same way.

Editing fields while playing​

While play mode runs, the inspector shows the selected entity's live values and lets you change them. Changes go to the running game only and are discarded when you stop. When you change a field in the inspector during play, the editor writes the value into the running script instance in place; the script keeps its other state and does not run OnCreate or OnStart again.

Renaming fields and classes​

Saved values are matched by name, so renames need care.

Renaming a field loses its saved values: scenes still hold the old name, which no member reads, and the next save drops it. To rename the C# member and keep the data, give it the old saved name with [JsonPropertyName]:

using System.Text.Json.Serialization;

namespace MyGame;

public sealed class Runner : Script
{
[JsonPropertyName("speed")]
public float RunSpeed = 200;
}

The member keeps reading and writing speed, so existing scenes and prefabs work unchanged. Talesmith has no attribute that reads an old name and writes a new one. To switch the saved name as well, close the scenes, replace "speed" with "runSpeed" in the script's entries of the .tscene and .tprefab files, and remove the attribute.

Renaming a class or moving it to another namespace changes its saved type name, such as MyGame.Runner. Scenes that use the old name keep the script as a missing script: the inspector shows a Missing script section, the console names the type, and the saved data is kept and written back unchanged, so nothing is lost. Rename the class back, or replace the "type" value in the .tscene and .tprefab files with the new full name. A missing script whose type exists again is created at the next play session, or by hot reload while playing.