Scripting
Scripts are plain C# classes in your project’s Assets folder. Entity → New Script creates one, attaches it to the selected entity and opens it. Common namespaces (System, System.Collections, System.Numerics, Griff, Griff.Data, Griff.Input, Griff.Runtime, Griff.Scenes, Griff.Simulations) are imported automatically, and so is the namespace of each genre your project uses: a project whose scenes or prefabs have a Match Board gets Griff.Modules.Match3, so a script names MatchBoard as it does Sprite. A genre the project doesn’t use yet is a using away (using Griff.Modules.Strategy;): every genre’s types are there to compile against, and a script that names one brings that genre into the game, and into its export.
The script editor
Section titled “The script editor”Scripts open in Griff’s own code editor, as tabs next to Scene and Game: from New Script, by double-clicking a .cs file in Assets, with the code button on a script component’s header in the Inspector (Edit Script), or by double-clicking a script error in Output. It’s C# with the compiler behind it, so it knows Griff’s whole API and your other scripts, unsaved changes included:
- Colors by meaning: properties, methods, types, parameters and fields each have their own color. Code is set in JetBrains Mono, which ships with Griff (SIL Open Font License; see
Themes/Fontsand Licenses). - Completion as you type, with each item’s signature and documentation; signature help inside calls (Up and Down pick an overload); hover anything for its documentation, or for the compiler’s message.
- Errors and warnings as you type: underlined where they are, counted in the header (click for the list), and in red in the line numbers.
- Ctrl+S saves, compiles and swaps the change into the scene, and into the game if it’s playing. Playing saves open scripts first.
- The component panel beside the code (the Component button) shows the component the script declares: what it does, what it needs, and each property with its kind, starting value and description, plus its value on an entity in the scene that uses it (live while playing), editable right there. Click a property’s name to go to it in the code; the property the caret is in lights up. New properties show up as soon as you type them, waiting for a save.
- F12 or Ctrl+click goes to a definition, Shift+F12 lists every use, F2 renames across all scripts, Shift+Alt+F formats, Ctrl+F finds and Ctrl+H replaces (with case, whole-word and regular-expression options), Ctrl+G goes to a line, Ctrl+/ comments lines, Ctrl+D duplicates them, Ctrl+Shift+L deletes them, Alt+Up and Alt+Down move them, and Ctrl+wheel zooms. Brackets and quotes close themselves,
}lines up with its{, uses of the name at the caret are highlighted, blocks fold (Expand All and Collapse All above the code open or fold every one), and indent guides follow the nesting. - Snippets:
prop(a property for the Inspector),update,start,triggerandcoroutinein a class;if,for,foreachandwhilein a method. Type the name and press Tab. - Breakpoints. Click beside a line number (a red dot), or press F9 on the line, and the game pauses when a script gets there, in the middle of its frame (a breakpoint on a line without code goes on the next one with some). The script opens at that line with a yellow arrow, and below the code are the values in scope: the object the code runs on (
this: the script’s own fields, then what it builds on, its Entity, Transform and the rest) and the method’s parameters and the variables set by that line, each to open up (an object’s fields and properties, a list’s items, a dictionary’s entries). Hovering a variable in the code shows its value too. Continue (F5 or F6, or the panel’s button) lets the script carry on from that line; Stop leaves the frame, and rewinding goes back to before it, from any step. Breakpoints work while the game runs, follow their code as lines are added or taken out above them (unsaved edits too: the game stops at the code as it was built), are kept with the project, and Debug → Remove All Breakpoints (Ctrl+Shift+F9) clears them. Agents set them and read the values through the Debugger (see Agents). - Stepping. Stopped at a line, Step Over (F10) runs it and stops at the next line of the same method, without stopping in what it calls (a breakpoint in there still stops it). Step Into (F11) stops at the next line of script that runs: inside a method the line calls, in this script or another (which opens at that line), or in a script Griff runs for it, such as a spawned thing’s Start; a call into Griff that runs no script is stepped over. Step Out (Shift+F11) runs the rest of the method and stops at the next line of the script that called it. A method Griff called (Update, a collision, a message, a coroutine reaching its
yield) returns into Griff: a step off its end goes on to the next line of any script that runs, which may be the same script on the next entity, and if none runs before the frame ends, the game pauses there, after the frame, with the panel saying so; Step Into from there (or from any pause) plays the next frame up to the first line of script it runs. Recursion, lambdas called by .NET (a list’sForEach), catch blocks and crowds step as you’d expect, and the panel under the code has Continue, Over, Into and Out, with the Debug menu and their shortcuts working while you look round the editor. - Exceptions. A script that throws an exception the game doesn’t catch stops the game at the line that threw, in the middle of its frame and before anything unwinds, as a breakpoint does: the line gets a red band, the panel under the code says what was thrown, and the values in scope there are below it (the line’s own variables as they were when it threw, and
this). A call into Griff that throws (a bad prefab name, say) stops at the line of script that called it; a crowd script running on another core stops at its own line with its own values; a coroutine stops at the line it was on, its variables read from the routine running it. The error goes to Output once. Continue (F5) lets the exception end that frame (the rest of it doesn’t run, as without the debugger) and the game plays on, stopping again if it throws again; a step ends the frame and pauses after it (Step Into then plays the next frame to its first line); Stop leaves it. Debug → Rewind to Before the Exception (also the panel’s button, and a tick on the rewind bar) goes back to the moment the step that threw began, paused there: put a breakpoint before the line and Continue (or Step Frame) to watch it happen again. It’s there until the game stops, while that moment is on the rewind timeline. To make that moment exact, play mode remembers the game between every two steps (copied out as a snapshot is, kept only if the next step throws); a scene too big to copy out in a quarter of a millisecond isn’t remembered every step, and rewinds to the last snapshot before it instead. Debug → Pause on Exceptions (on unless turned off, kept between sessions) turns it all off: an exception then ends the frame and pauses the game, with the error in Output, as in an exported game. The editor’s build of the scripts has guards for this: each stretch of a method’s code sits in atrywhose filter notes an exception coming up through it with the variables in scope there and catches nothing, so a script’s owncatchstill catches, nothing changes until something is thrown, and an exported game has none. - Breakpoints cost nothing until the game gets to one. The editor builds scripts with a probe at the start of each line, a test of a switch of that line’s own that a breakpoint turns on, so setting one rebuilds or restarts nothing, and a line without one only reads a switch that’s off (about 3% of a tight loop of one-line sums, in the Debug-built code the editor runs anyway). An exported game has no probes at all. Stopping changes nothing in the game: while it’s held, what the editor does doesn’t draw on the game’s random numbers, reading values runs none of its code (a script’s fields are read as they are), and the time it stood still isn’t time it owes. With a breakpoint set, a crowd’s scripts (
[Crowd]) run on the game’s thread, where one can stop them, rather than across the cores; they play out the same either way, also while stepping through them. While a step is under way every line’s switch is on, and each line asks whether the step ends there (by where on the stack it runs, which tells a call from a return); once it’s over, only breakpoints’ switches stay on.
Scripts can still be edited in any other editor; Griff picks up the saved file either way.
public class PlayerController : Behavior{ // Public properties are saved with the scene and editable in the Inspector. public float Speed { get; set; } = 7; public float JumpSpeed { get; set; } = 13;
protected override void Update() { var body = GetComponent<Body>()!; var velocity = body.Velocity; velocity.X = Input.Horizontal * Speed; // A/D or arrow keys if (body.IsGrounded && Input.WasPressed(Key.Space)) { velocity.Y = JumpSpeed; Audio.Play("Sounds/jump.wav"); } body.Velocity = velocity; }
protected override void OnTriggerEnter(Entity other) { if (other.Name == "Coin") Game.Destroy(other); }}Scripts read the game’s tables with Tables: float damage = Tables.Get("Spells", "Fireball")["Damage"];, or a class of their own filled from a row, Tables.Get<Spell>("Spells", "Fireball") (see Tables of game data). A SimulationScript plays a balance simulation’s runs, and Game.Simulate("Balance") plays one from a game (see Script simulations).
Describe a script for the component catalog and the Inspector’s tooltips with a /// summary over the class and its properties (say what units a number is in), and say how it fits with other components using attributes:
/// <summary>Walks toward the player and explodes next to them.</summary>[Category("Enemies")] // where the catalog lists it (default: Scripts)[Requires(typeof(Body), Note = "moves by velocity")] // Add Component adds a Body along with it[Requires(typeof(Collider))] // a base type: any kind of collider will do[WorksWith(typeof(Health), Note = "so it can be shot")]public class Creeper : Behavior{ /// <summary>How close it gets before exploding, in world units.</summary> [Distance] // drawn as a circle around it in the Scene view, with a handle public float FuseRange { get; set; } = 1.5f;
/// <summary>What it walks toward.</summary> [EntityName] // picked from the scene's entities in the Inspector (Many = true for a list) public string? Target { get; set; } = "Player";
/// <summary>Flash before exploding.</summary> [Group("Warning")] // a subheading in the Inspector, over this and the properties after it public bool Flash { get; set; } = true;
/// <summary>Flashes a second.</summary> [ShownWhen(nameof(Flash))] // faded in the Inspector while Flash is off public float FlashRate { get; set; } = 4;
/// <summary>The blast's damage, e.g. "3d6".</summary> [Dice] // built in the Inspector: how many dice, their sides, what's added public string? Blast { get; set; } = "3d6";
/// <summary>Which way it runs from the light.</summary> [Direction] // a compass in the Inspector, an arrow to turn in the Scene view public Vector2 Flee { get; set; } = -Vector2.UnitX;
/// <summary>An item that defuses it, e.g. "Wire Cutters".</summary> [Names(NameKind.Item)] // picked from the items the game gives (NameKind.Flag, Prefab, Message...) public string? DefusedBy { get; set; }}Other fields with editors of their own are marked the same way: [Points] (a list of “x y” points), [TileNumbers], [TileAnimations], [AutoTiles] and [Tilesets] (tiles picked off the tilemap’s tilesets), [Clips], [ChoiceOf(nameof(Choices))] (one of what another property of it lists), [PrefabCounts] (prefabs and how many), [OutputFormat(nameof(Output))] (text showing an output), [Format("{0} the price", Preview = nameof(ReadsAs))] (text with holes, with a method that says how it reads now), [NumberFormat], [NameList] (names in order, as text or a list), [LetterCounts("ABC")], [ItemCounts] and [ItemUses] (an inventory’s), [TileCounts], [ActionValue(nameof(Do))] (the value the action another property names takes), [DialogueLines], [TableSets(nameof(Table))], [Offset] (a point from the entity, with a knob in the Scene view) and [Resizable] (a size, with resize handles). [Names(NameKind.Saved)] picks a name in the save data (Defines = true on a field that saves under it), and [Names(...)] on an action’s value says what it names (void Unlock([Names(NameKind.Flag)] string flag)), so a trigger’s With offers those. The editor’s guide says what each looks like.
A script’s fields get good editors without any of these: a list of strings is rows to add, remove and reorder (numbers or colors when they all are), and one whose lines are names with values ("Sword: 30") a name and its value a row, a dictionary; an enum of many choices (a Key) is searched for by typing; a Quaternion shows as angles; a string called Font, Prefab, Scene or Message, or ending in one (NextScene, WinMessage), picks from the project’s; a Vector2 called Direction or ending in it gets the compass. Mark a list [Pairs] (or [Pairs(Separator = "=", Values = CellKind.Number)]) or [ListRows(CellKind.Color)] to say what it holds while it’s still empty. And when typing is right for a string, say why with [PlainText("what the shopkeeper says")]: it stays a plain box (a list’s lines one block of text), and component_describe tells agents it’s free text. See Every field has an editor.
Scenes keep what was set in them when a script changes shape. Rename a class or a property and say what it was called with [FormerName]: scenes saved under the old name still load, and save under the new one. It goes on an [Action] method or an [Output] too: a trigger, button, card, counter, wire or condition that still names the old action or output keeps working. A name that resolves to nothing (a trigger doing an action nothing it’s done on has, a counter reading an output that isn’t there, a wire’s input) is flagged in the Inspector and in scene_tree, reported when a game is loaded and said in Output as the game starts, instead of silently doing nothing. When what a value means changes, raise the component’s [ComponentVersion] and add an upgrade step from the version before, which older scenes run through as they load (steps see properties under their current names). Values a component has no property for (one you removed, or from a newer version of it) are kept and written back, so loading and saving loses nothing. Griff’s own components follow the same rules.
[FormerName("Creeper")] // was class Creeper: scenes that use it still load[ComponentVersion(2)] // version 1 measured the fuse in seconds; 2 in framespublic class Bomber : Behavior{ [FormerName("FuseTime")] // was FuseTime public int FuseFrames { get; set; } = 90;
[UpgradeFrom(1)] // brings version 1 data up to 2 static void SecondsToFrames(SavedData data) => data.Set("FuseFrames", (int)(data.Get<float>("FuseFrames") * 60));}A few more things scripts often do:
// Spawn a prefab (Assets/Prefabs/Bullet.prefab) where the player is.var bullet = Game.Spawn("Bullet", new Vector2(Transform.Position.X, Transform.Position.Y));
// A prefab your data names (a field you made) may be missing: TrySpawn warns once and gives null, instead of stopping the game.var spark = Game.TrySpawn(Effect, this, "its Effect", new Vector2(Transform.Position.X, Transform.Position.Y));
// Do something later, or repeatedly. Timers belong to this entity and stop if it's destroyed.After(0.5f, () => Game.Destroy(bullet));Every(2f, () => Game.Spawn("Enemy", new Vector2(10, 0)));
// Is there ground just below? Ignore our own collider.var below = Game.Physics.Raycast(new Vector2(Transform.Position.X, Transform.Position.Y), -Vector2.UnitY, 0.6f, ignore: Entity);
// Juice: a burst of particles, a camera shake, and a kick back from where the gun fires.GetComponent<ParticleEmitter>()?.Burst(20);Game.ShakeCamera(0.3f, 0.2f);Game.KickCamera(new Vector2(-0.3f, 0), 0.15f);
// Tweens: pop in, glide somewhere, then fade out and disappear.Transform.Scale = new Vector3(0, 0, 1);ScaleTo(Vector2.One, 0.4f, Ease.OutBack);MoveTo(new Vector2(4, 2), 1f).Then(() => FadeTo(0, 0.3f).Then(() => Game.Destroy(Entity!)));
// Find things. Every copy of a prefab shares its name, so Game.Find("Player") takes this machine's player in a network game// and one in the screen's minigame first; scripts are found by type.foreach (var enemy in Game.FindAll<Enemy>()) enemy.Hit();Game.Find<ScoreCounter>()?.Add(1);
// Random spawn points, loot rolls and variety. Rand.Seed(42) makes a run repeat (particles and camera shake draw from Rand too).if (Rand.Chance(0.25f))
Game.Spawn(Rand.Pick(["Coin", "Gem"]), Rand.InsideCircle(3));
// Sequences that take time: a coroutine waits with `yield return` and picks up where it left off.StartCoroutine(Waves());
IEnumerator Waves(){ for (var wave = 1; wave <= 3; wave++) { for (var i = 0; i < wave * 4; i++) { Game.Spawn("Enemy", Rand.InsideCircle(6)); yield return Wait(0.3f); } yield return WaitUntil(() => Game.FindAll<Enemy>().Count == 0); } Game.LoadScene("Win", fade: 0.5f);}
// Messages: tell whoever's interested, without knowing who they are. Listen in Start; listeners stop by themselves// when their entity is destroyed or the script reloads.Game.Send("Score", 10);On<int>("Score", points => Total += points);On("PlayerDied", () => Game.ReloadScene(fade: 0.5f));
// Named animation clips on one sprite sheet (Clips: "Idle 0-3, Run 4-9 @12, Jump 10, Die 11-15 once").GetComponent<Animation>()!.Play(body.IsGrounded ? (moving ? "Run" : "Idle") : "Jump");
// Chase the player around walls: the waypoints to head for, one after another.var path = Game.Find("Level")!.GetComponent<Tilemap>()!.FindPath(Transform.WorldPosition, player.Transform.WorldPosition);if (path.Count > 0) body.Velocity = Vector2.Normalize(path[0] - new Vector2(Transform.Position.X, Transform.Position.Y)) * Speed;
// See what the code is thinking: shapes drawn over the game this frame (or for a few seconds).Debug.Path(path, Color.Yellow);Debug.Circle(Transform.Position, SightRange, Color.Red);Debug.Text(Transform.Position + new Vector3(0, 1, 0), state);// A module's drawings to debug with, by name (a fighting game's hitboxes); a system of your own can make one (IDebugDrawing).Game.ShowDebugDrawing("Hitboxes", true);
// Line of sight that sees through other enemies: only walls (the Default layer) and the player block it.var seen = Game.Physics.Raycast(eye, toPlayer, 10, ignore: Entity, layers: "Default, Player");
// Game flow: start over after dying, keep the music playing from scene to scene, quit from a menu.Game.ReloadScene(fade: 0.4f);Game.KeepBetweenScenes(Entity!); // gives it a Keep Between Scenes
// Between scenes and genres: through to the cave arriving at its mouth; a minigame played on top, and how it went.Game.LoadScene("Cave", fade: 0.4f, color: null, arriveAt: "Cave Mouth");Game.PlayMinigame("Lockpick", from: Entity);On<int>("Minigame Won", score => Game.Do(Game.Find("Gate")!, "Open"));Game.Quit();
// Named input actions: Jump, Fire, Dash, Interact and Pause work on keyboard, mouse and gamepad from the start.if (Input.WasPressed("Jump") && body.IsGrounded) velocity.Y = JumpSpeed;Input.Bind("Dash", Key.LeftShift, Key.K, GamepadButton.B); // change one, or add your own
// A jump pressed a moment before landing still jumps when you land (each press counts once), and one pressed a moment// after running off a ledge still jumps (coyote time). Letting go early makes a short hop.if (body.WasGrounded(0.1f) && Input.WasPressed("Jump", buffer: 0.12f)) velocity.Y = JumpSpeed;if (Input.WasReleased("Jump") && velocity.Y > 0) velocity.Y *= 0.5f;
// Move without going faster diagonally; aim with the right stick, or the mouse; shake the controller.body.Velocity = Input.Move * Speed;var aim = Input.AimFrom(Transform.Position);if (Input.IsDown("Fire") && aim != Vector2.Zero) Shoot(aim);Input.Rumble(0.6f, 0.25f);
// A controls screen: press a key for Jump. The player's choice is saved and comes back next time.if (waitingForKey && Input.AnyPressed is { } pressed){ Input.Rebind("Jump", pressed); waitingForKey = false;}label.Value = $"Jump: {string.Join(" / ", Input.BindingsOf("Jump"))}"; // "Jump: Space / Gamepad A"
// Type your name for the high score table.PlayerName = Input.TypeInto(PlayerName, maxLength: 12);
// Hurt things, and make hits feel like hits.Game.Find("Boss")!.GetComponent<Health>()!.Damage(2, source: Entity);Flash(Color.Red); // this entity's sprites, for a momentGame.HitStop(0.08f); // freeze the game for a split secondGame.FlashScreen(Color.Red with { A = 0.3f }, 0.2f); // the whole screenGame.SlowMotion(0.25f, 1); // the last enemy falls: a second at quarter speed, easing back
// Scores without a counter of your own: add to the Counter, or read it.Game.Find<Counter>()!.Add(100);var score = Game.Find<Counter>()!.Value;
// Dialogue: change the text and the Typewriter types it out. A key press finishes the line, then moves on.if (Input.WasPressed("Interact")){ if (!typewriter.IsDone) typewriter.Skip(); else text.Value = lines[++line];}
// An options menu (sliders and toggles do this with no code). It's remembered next time.Audio.MusicVolume = 0.5f;Audio.Muted = !Audio.Muted;Game.Fullscreen = true;
// A panel opens: pick its first button for the keyboard and gamepad.Game.Focus(Game.Find("Retry")!.GetComponent<Button>());
// Panels: open a shop from a shopkeeper, close the top one, and see what's open.Game.OpenPanel("Shop");Game.ClosePanel();if (Game.IsPaused) return; // a pause menu is openGame.Pause(Game.Find("Arcade")!); // hold a minigame still while the rest runs on (Game.Resume starts it again)if (Entity.IsPaused) return; // this entity is in a paused groupGetComponent<Panel>()!.Opened += _ => Audio.Play("Sounds/open.wav");
// Show the player what to press, on the keyboard or the controller they're using.hint.Value = $"Press {Input.PromptFor("Jump")} to jump"; // or just "Press {Jump} to jump"keys.Value = Input.AllFor("DropTool"); // every key, "Esc / Right Click", as "{DropTool:all}"; Input.SlotFor("PanUp", 1) the secondbuy.Interactable = coins >= 10;Game.ListenForBinding("Dash"); // a rebind button of your own
// Wall jumps: WallSide is -1 or 1 while a wall stops the body (it has to be pushing into it).if (!body.IsGrounded && body.WallSide != 0 && Input.WasPressed("Jump")) body.Velocity = new Vector2(-body.WallSide * 6, JumpSpeed);
// Levels: a cutscene line when the boss appears, a fade to cover a cut, enemies that just spotted you.Game.StartDialogue(Game.Find("Boss")!.GetComponent<Dialogue>()!);Game.FadeThrough(0.4f, () => player.Transform.Position = new Vector3(40, 2, 0));GetComponent<Chase>()!.Spotted += _ => Audio.Play("Sounds/alert.wav");
// Puzzles: pull a lever from a cutscene, see what the player carries, react to a plate.Game.Find("Gate Lever")!.GetComponent<Switch>()!.IsOn = true;if (player.GetComponent<Inventory>()?.Has("Key") == true) Debug.Text(Transform.Position, "I have the key");GetComponent<Switch>()!.Changed += on => Game.ShakeCamera(on ? 0.2f : 0.1f);Game.Power(Game.Find("Fan")!, on: false); // what a switch would do: stop its movers, spawners, particles...
// Players: tune the built-in movement from a script, or react to it.var player = GetComponent<PlatformerMovement>()!;player.AirJumps = hasBoots ? 1 : 0;player.Landed += speed => Game.ShakeCamera(speed / 100);if (player.IsClimbing || player.IsDashing) return;player.Launch(); // thrown by an explosion: air jumps and dash come backbody.DropThrough(); // fall through the one-way platform underneath
// Atmosphere: is the player in the shadows? A sound from over there, music for the boss, a light that dies.// A storm rolls in, and the color drains as health runs low.if (Game.LightAt(player) < 0.3f) return; // the guard can't see themGame.PlaySoundAt("Sounds/creak.wav", new Vector2(Transform.Position.X, Transform.Position.Y));Audio.CrossfadeMusic("Music/boss.mp3", seconds: 2);On<int>("Beat", beat => { if (beat % 2 == 0) Game.Flash(Entity); }); // a Beat Clock's beatif (Input.WasPressed("Jump") && Math.Abs(Game.Find<BeatClock>()!.SecondsFromBeat) < 0.1f) Combo++;GetComponent<Light>()!.Intensity = 0;Game.Find<Weather>()!.Amount = 1;Game.Find<ScreenEffects>()!.Saturation = health.Current / health.Max;
// Rooms and attacks: react to the camera moving on, swing from a cutscene, add to a hit.GetComponent<Room>()!.Entered += _ => Game.Find("Boss")!.IsEnabled = true;GetComponent<Attack>()!.Swing();GetComponent<Attack>()!.Hit += enemy => Game.ShakeCamera(0.15f);
// Remember things between runs.if (Score > Game.SaveData.GetInt("best")) Game.SaveData.Set("best", Score);SaveData writes each change to disk at once, off the game’s thread (setting a value every frame costs the frame nothing; what changes while one write is under way goes in the next), and the game waits for the last write as it stops. A write that fails, a full disk or a file another program holds, is said once in Output (Game.Problems) and the game goes on; the next change tries again.
Rand gives random numbers for gameplay: Range, Chance, Pick, Sign, Direction, InsideCircle, Color, and Seed to make a run repeatable. Games one running editor or player makes all start from the same numbers unless the project turns on Game → New Random Numbers Each Game.
What a behavior can use:
- Lifecycle:
Start,Update(every step of the game: 60 times a second whatever the screen’s frame rate, so read the controls and move things here; the screen is drawn between steps, so it moves smoothly),FixedUpdate(every step just before bodies move, and only while game time moves: not while paused, atTime.Scale0 or in a hit stop, when physics stops too; push bodies here),LateUpdate(every step once everything else has moved: physics, pickups and damage, cameras following their targets; screen anchors are placed after it, so a camera of your own moved here keeps the HUD pinned),OnCollision,OnTriggerEnter,OnTriggerExit,OnClick(the entity’s button was clicked),OnDamage(its Health was hurt: how much, and by what),OnDeath(its Health reached zero, just before it’s destroyed),OnPickup(it collected a Pickup),OnDestroy; andClaimsBack, overridden to have Back (Esc and B) while it’s true (protected override bool ClaimsBack => tool is not null;), so a panel doesn’t open on it, or close if the game goes on behind it, and the script puts the tool down first (see Pause menus and panels). A press a panel acts on (the Esc that closes it, its Open On key) is used up:Input.WasPresseddoesn’t see it in that frame. - Crowds: a script marked
[Crowd]changes nothing but its own entity in itsUpdate,FixedUpdateandLateUpdate(its own values, its entity’s transform and components), so the game runs a crowd of them together, spread across the computer’s cores, rather than one after another: thousands of bunnies, boids or bullets. Scripts still run in hierarchy order, a crowd (scripts of one crowd type next to each other in it) all at once at the place of its first: what comes before it has run and what comes after hasn’t, and one inside another entity runs after the rest of its crowd, on the game’s thread. A crowd script reads its own entity, the game’s time and controls, and whatever nothing changes meanwhile (not another of its crowd, which may be moving at that moment);Randgives each script of a crowd numbers of its own, from a seed the crowd takes from the game’s numbers, so a crowd plays out the same on any number of cores and a seeded run repeats. What would change something else throws, saying so: spawning, destroying, messages, timers, coroutines, tweens, sounds, debug drawing, flashes, saving, adding or removing components, enabling or disabling entities and changing scenes. Do those from a script that isn’t a crowd one, or inStart, which runs as any script’s does.Game.WorkerCountsays how many threads share a game’s work (one for each logical processor, up to 16, to start; 1 runs everything on the game’s thread): it’s the game’s own, set by whatever runs it, so another game in the same process (Play with Players, a simulation) keeps its own. - Services:
Transform(withTeleport(position), to move it somewhere new without it being drawn gliding there),Time(Delta, the step’s game time;Total,Scale(above 1, each step covers more game time, so what goes a step at a time is coarser);FastForward, fast-forward that stays exact: 16 takes sixteen steps of the usual length for each one, so a sim plays out step for step as at normal speed, only sooner, up to what the machine keeps up with (it runs slower rather than skipping steps), withUnscaledDeltastill real time for the camera and menus; from 1 to 64, and nothing in a network game (see Game speed);Frame, the steps taken;StepRate, steps a second, the project’s Step Rate unless a script changes it, andFixedDelta, a step’s length;Blend, how far the frame being drawn is between the last two steps;DrawnFramesandRealTotal, the frames the screen has shown and the real time they took, for a frame rate),Input(IsDown,WasPressed,WasReleased,Horizontal,Vertical,Move,MouseWorld,Scroll(mouse wheel notches this step),Touches(every finger on a touch screen, each with itsId, where it is in the world and whether it went down or was lifted this step; the first is the mouse too); keyboard, mouse and Xbox-style gamepads, withLeftStick,RightStick,LeftTriggerandRightTrigger(also buttons,GamepadButton.RightTrigger, when pulled past halfway),IsGamepadConnected,UsingGamepad(for showing “Press A” rather than “Press Space”) andRumble;AimFrom(position), the right stick or towards the mouse; named actions:IsDown("Fire"),WasPressed("Jump"),WasPressed("Jump", buffer: 0.12f)(a press in the last moment that hasn’t been used yet),WasReleased, withBindto set an action’s keys and buttons or add a new one, andBindingsOfto show them.Rebindis for the player’s own choice from a controls screen: it’s saved, comes back next time and wins overBind;ResetBindingsforgets it, andAnyPressedis whatever was just pressed. Actions are case-insensitive; a misspelled one says which exist.TypedTextandTypeIntoare for typing names;PromptFor("Jump")is what to show for an action on the device in use, “Space” or “A”, andInputPrompts.Namenames any key or button),Audio(Play(optionally with apanfrom -1, left, to 1, right, and apitch: 2 is an octave up),PlayMusic,CrossfadeMusic,FadeOutMusic,StopMusic,PlayMusicFrom(music started part way in, to carry a song on where it was),Loop(a sound that repeats until stopped, whoseVolume,PanandPitchcan change as it plays),Music(what’s playing; none once music that doesn’t loop has played to its end),MusicTime(seconds of it heard, allowing for the sound card’s delay),MusicLengthandMusicTempo(see Keeping the beat), andVolume,MusicVolume,SoundVolumeandMutedfor an options menu; the same sound started several times in one frame plays once) andDebug(Line,Ray,Box,Circle,Point,Path,Text: drawn over the game for one frame, or forseconds:, with lines the same thickness however far the camera zooms;Debug.Enabled = falsehides them all). - Players at one computer: a
PlayedBy(Player) gives an entity to one of them;Game.InputOf(entity)is then that player’s keys or controller (aLocalPlayerInput), andGame.LocalPlayerssays who plays with what:Playing(how many the scene has),Input(player),DeviceOf(player, playing),Describe(player, playing),Choose(player, PlayerDevice.Gamepad1),Swap(1, 2, playing)andResetChoices(). The machine’sInputStatekeeps controllers apart:GamepadCount,IsDown(pad, button),LeftStickOf(pad),Rumble(pad, strength, seconds), andHoldGamepadButton(pad, button, down)to play one from a test. - Multiplayer:
Game.Network(the game’sINetworkservice, alsoGame.Services.Get<INetwork>()) hosts and joins network games and says who’s playing, and everything it says has an answer offline too (no players, everything yours, a message just sent here), so there’s nothing to check first.Host(),HostServer()(a dedicated server),Join("192.168.1.5")(orJoin("")for the first game on the local network) andLeave();Role,IsOnline,IsHost,IsClient,IsConnectingandStatus(kept after a game ends, saying why);Players,LocalPlayer(each aNetPlayerwithId,Name,Team,Color,Score,Ping,IsHost,IsLocaland theirAvatar),RemotePlayers(on the host, the others, each with theirInput),PlayerJoined,PlayerLeftandEndedevents, and the messages “PlayerJoined” and “PlayerLeft”.Network.Send("Chat", "gg")reaches every machine as a message (data can be a number, text, a vector, a color, an entity, a player or a list of them), withNetwork.Sendersaying who sent it.OwnerOf(entity)andIsMine(entity)say whose something is,AddScoreandTeamNames()keep the score and the teams,PlayerNameis remembered, andFakeLagpretends a bad connection. A game plays over the network only when what runs it says so (OverNetwork): the player and the editor’s Play do, so a game hosted anywhere else (a test, a simulation, a tool) stays on this machine, hosting on 127.0.0.1 with no firewall question. A game has a network session only while it’s networked:Network.Session(aNetSession, null offline) also hasSendToHostandSendTo(player, ...)to reach one machine, the host’sRespawn(player),SetTeamandKick, andTraffic. Scripts run on the host; theirInputis the keys of whoever plays their entity (Game.InputOf(entity)for any entity), so a script on a player’s character answers to that player. - Messages:
Game.Send("Name")orGame.Send("Name", data)reaches every listener straight away and returns how many heard it.On("Name", () => ...)andOn<int>("Name", value => ...)listen until the entity is destroyed or the script is reloaded (like coroutines, so listen inStart); they return a listener you canCancel(), andStopListening()stops all of a behavior’s. Listeners in a scene you leave stop too, unless their entity is kept withKeepBetweenScenes.Game.Onmakes one that doesn’t belong to a behavior (pass an entity as itsownerto make it that entity’s).Game.Send("Name", data, from: Entity)says who sent it, for a Trigger‘s From (and a Message Scope on the sender or something it’s inside keeps it to part of the scene, as Griff’s own components’ messages are);Game.SendTo(entity, "Name", data, children: true)reaches only that entity (and everything inside it). A listener with no entity of its own hears only what’s sent to everyone. - Actions:
Game.Do(entity, "Damage", 2)(or"2", or with no value) does an action on an entity as a Trigger would: every component of it with an action called that does it ("Health.Damage"names one component’s). A script’s own methods marked[Action]are actions too, found like its properties; see Joining in below. - Timers:
After(seconds, action)andEvery(seconds, action)return a timer you canCancel(). They run on game time, so they pause and slow down with it.Game.AfterandGame.Everymake timers that don’t belong to an entity. - Coroutines:
StartCoroutine(SomeMethod())runs anIEnumeratormethod straight away up to its firstyield return, then picks it up again once what it yielded is done:null(the next frame),Wait(seconds)(or just a number),WaitUntil(() => ...),WaitWhile(() => ...), a tween, a timer, another coroutine, or anotherIEnumeratormethod (run to the end first, like calling it). They run on game time like timers and return a coroutine you canStop();StopCoroutines()stops all of a behavior’s. They stop when the entity is destroyed and when the script is reloaded, and sinceStartthen runs again on the new version, a coroutine started inStartrestarts running your new code.Game.StartCoroutinemakes one that doesn’t belong to a behavior. A saved game doesn’t keep a coroutine under way (a rewind does): keep what it’s doing in fields, which a saved game keeps, and start it again inOnLoaded, called once a loaded game’s fields are back (see What scripts keep). - Tweens:
MoveTo,ScaleTo,RotateToandFadeTo(sprite, text and tilemap alpha) animate this entity.Tween(seconds, t => ...)animates anything. Each takes an optional curve fromEase(Linear,InQuad,OutQuad(the default),InOutQuad,InCubic,OutCubic,InOutCubic,InOutSine,InBack,OutBack,OutElastic,OutBounce) and returns a tween you canCancel()or chain with.Then(...). A newMoveTotakes over from an unfinished one. Like timers, tweens run on game time and stop if their entity is destroyed;Game.MoveTo(entity, ...)and friends work on any entity. - Systems: a
GameSystemdoes something every step for a whole kind of thing at once, rather than for one entity: overrideUpdate, andPhaseto say when it runs (Begin,Logic,Scripts,Simulate,Physics,Resolve,PresentorEnd;Logicunless it says), after Griff’s own steps there.AddSystem(new Magnets())in a script’sStartruns it from that frame until the entity goes (destroyed, left behind by a scene change, or the script reloaded);Game.AddSystemruns one for the rest of the game, and itsSceneChangedis called when the scene changes, to forget what it kept. A game has one system of each type, so adding another replaces it (and a scene that adds one each time it starts still runs just one).Game.RemoveSystem,Game.GetSystem<T>()andGame.Systemsfind and stop them, andGame.Framelists every step of a frame in order, Griff’s and yours. Systems run while the game is paused too (Time.Deltais 0 then). OverrideDraw(FrameDrawing frame)to draw what the system knows of that no one entity draws, once every entity has drawn itself:frame.AddOverWorld(...)puts quads (FrameDrawing.Quad,Line,Label,Outline,Circle) over the world and under the HUD, andframe.Spritescan be changed in place (Griff’s strategy, tactics, block world and tool overlays draw this way). A system opts into more by implementing a small interface, which the game finds once when the system is added:IViewDrawerdraws the world its own way inDrawView, before any system’sDraw(as the first-person view does);ISeesThroughEyessays whose eyes the game is seen through (Eyes, whichGame.Eyesgives and sounds are heard from, andLooksThrough);IHudLayoutlays a HUD out inLayOutHud, early each frame, and gives its buttons asHudButtons(all of them areGame.HudButtons);INoticeSourcegives a short message asNotice(Game.Notice);IMouseOnHudsays when the mouse is over what the system draws (MouseOnHud), so clicks there aren’t meant for the world;IPlayerToolsruns one player’s own controls (a hotbar, building tools) for each other player on the host of a network game, handed the player and their controls (RunForPlayer(player, input):Game.Inputstays this machine’s,Game.Owned<T>(player)is what they have andGame.HudViewOf(input)the view they see, so what the system keeps for each player stays theirs);IClaimsBacksays there is something under way that Back (Esc) cancels (ClaimsBack), so a pause menu waits (a script overrides its ownClaimsBackinstead);IPlayersListenerhearsPlayersChangedin a network game as each scene starts and whenever the players change;IGridStepListenerhears of each grid step before it’s taken (GridStepStarting: a move history remembers where everything was);IPlayerSettingskeeps settings a player chooses for themselves with the game’s own (read back from the save data as the game starts, written as they change, and shown and set by a Slider or Toggle set to one of them: the Rhythm module’s note speed and the Visual Novels module’s skip and auto speed are kept so);IReadingPreferencessays how the player likes dialogue skipped and read on, and which lines they’ve read; andISceneEndListenerhearsSceneEndingas the game leaves a scene or stops, to keep what it leaves behind (a block world saved). The sameGamemembers Griff’s own genres use (Game.PlaySoundAt,Game.Warn,Game.TurnOrderOf,Game.Ownedand the rest) are public and documented, so a system of yours can do whatever a genre’s does, and the frame’s steps are named byStepsmembers (Steps.Dragging), soGame.Frameand a module’sStepAfter(Steps.Dragging, "Match Boards", ...)agree on where a step goes. A system’s fields are its state: a rewind takes them back with the rest of the game (see Rewind), lists, dictionaries and the entities they point to included; mark one that isn’t with[NotState("why")](a cache, a list kept only to reuse) and overrideRestoredto work it out again once a rewind has put the rest back. - Joining in with Griff’s components: a script component can be what a Counter counts (a property marked
[Output]; the counter’s Track and Output name it,*for the first in the scene), steer a Grid Movement itself instead of the player’s keys (IGridDriver: its next step, and whether each step uses a turn’s action), get ready as each of its side’s turns begins (ITurnReady), say when an action can’t be done now so a button doing it fades (IActionReady), name what it does as actions ([Action]on a method, taking nothing or one number, whole number, true-or-false or name: a Trigger or a wire can then do it, andGame.Do(entity, "Explode")does it from code; one that needs part of the game can first take the service it needs, as Griff’s own components do:[Action] void Pause(IPanels panels), withIFlags,ISpawning,IDialogues,IMinigamesand the rest, each provided by the system that owns that part, and a system a script adds can provide one of its own, an interface ofIGameService;Game.Services.Get<IFlags>()asks for one from code), say what a switch turns on and off ([Powered]on a true-or-false property, such as its ownRunning), name what it knows as outputs ([Output]on a property giving a number, whole number, true or false, or a name, withOutOfnaming what it’s out of, or on a method taking a name, read as"Cargo:Ore": a Counter, Bar, Text or wire can then show it, once the script has started, so its getter can useGame(one spawned this step is read from the next), andComponentOutputs.Read(entity, "Lives")reads it from code), take the player’s input only while it has focus ([Focus]on its true-or-falseControllable, which gives it a Focus action, or[FocusFrom(typeof(PlatformerMovement))]on the class to go by another’s; see Mixing genres), do something when a message is sent (IHearsMessages, as a dungeon goes down a floor on its Next Floor Message), keep keys and the mouse wheel for itself ([ClaimsKeys(Key.D1, ..., Wheel = true)], as a hotbar does, so building tools are picked by clicking), and tell an agent playing the game what it’s up to (IPlaytestReport), the same way Griff’s match boards, tactics units, goals and dungeons do. Each genre’s types (MatchBoard,Commander,TacticsUnit,Weapon,MoveHistory) live in their module’s namespace (Griff.Modules.Strategy,Griff.Modules.FirstPerson), which scripts get without ausingonce their project uses the genre (another is ausingaway), and what a genre offers onGame(Game.Train,Game.Craft,Game.Equip,Game.Undo()) comes with it. - Drawing: a component that implements
IDrawabledraws itself, as Griff’s sprites, tilemaps, text, bars, particles, boards, ropes and wires do:Draw(Drawing drawing)adds quads (Rectin the entity’s own space;Quad,Line,Outline,Circle,Text,ImageandBarin the world), knowing what’s in view (View), how thick a thin line is (LineWidth) and whether the game is running (IsPlaying). The game is drawn between its last two steps: transforms get there by themselves, and what a component draws from its own numbers gets there throughdrawing.Blend, aStepBlendsaying how far the frame is between the steps. Keep a number, point or color that changes in aSmoothed<T>field (_angle.Set(this, value)as the game runs), and drawdrawing.Blend.Of(_angle)(TurnOffor an angle, the short way round), or keep what it was yourself and drawdrawing.Blend.Between(was, now); it follows the transforms’ rules, drawing as it is whatever jumped. A system drawing the game’s own state asksframe.Blend(orframe.BlendOf(entity)), and draws a HUD it laid out over the camera’s view during the step throughframe.PinToView, so it keeps still on the screen as the camera moves, zooms and shakes between steps; anIDrawEffectasksStepBlend.For(entity).DrawOrderplaces the entity among the others (0 unless it says; an entity’s sprite sets it if it has one) andDrawStepplaces its draws among the entity’s own (DrawSteps.Shapes, over the sprite, unless it says; fromDrawSteps.OverWorldon, after the whole world). Each frame,Drawis called once for each entity in hierarchy order (the Hierarchy panel’s, top to bottom), and for an entity’s drawables inDrawSteporder; the draws are then put back to front, each entity’s kept together, so a drawable is called in the same order whatever layer it’s on and doesn’t see what’s drawn under it (a system’sDrawsees the whole frame).IDrawEffectchanges how its entity is drawn without drawing (a tint for its sprite and text, a transform for it and what’s inside it, or drawing it over everything), andILightSourcegives off light in the dark. The Scene view draws them too. - Networking: see Multiplayer for
[NetLocal],[PlayerMovement],[Sent],[NotSent]andINetState. - Game:
Spawn(an entity, or a prefab by name),Destroy,FindandFindAll(by name, orFind<T>()andFindAll<T>()by component type;Game.Scene.Active<T>()gives just those on enabled entities,Game.Scene.Running<T>()those of them that aren’t paused, and asking every frame costs next to nothing, since the scene keeps its components by type),LoadSceneandReloadScene(optionally with afade:in seconds and acolor:; fades use real time, so they work from a paused menu, andIsChangingSceneis true until the switch),KeepBetweenScenes(carry an entity into the next scenes, still running, as a Keep Between Scenes does: it takes the place of one of the same name there, and returning to its original scene doesn’t duplicate it),LoadScene(name, fade, color, arriveAt:)andArrivedAt(the players arriving at a named spot; see Doors between scenes),PlayMinigame,WinMinigame,LoseMinigameandMinigame(another scene played on top; see Minigames),Remembers,RememberedScenes,ForgetScene,ForgetScenesandNewGame(scenes that remember how they were left; see Scenes that remember),Quit,ShakeCamera,HitStop(freeze game time for a moment of real time;Time.IsFrozenis true meanwhile),SlowMotion(game time at a fraction of normal speed for a moment of real time, easing back; on top ofTime.Scale, andTime.SlowMotionScalesays how much),Flash(an entity’s sprites and its children’s),FlashScreen(the whole view, fading out on real time),Fullscreen(remembered, like the volumes),FocusandFocused(the button or slider picked for the keyboard and gamepad),OpenPanel,ClosePanel,TopPanel,OpenPanelsandIsPaused(see Pause menus and panels),PauseandResume(a group of the game: see Pausing part of the game),ListenForBindingandRebinding(see Controls screens),FadeThrough(fade out, call something while the screen is covered, fade back in),LightAt(how lit a place or an entity is, 0 to 1),PlaySoundAt(a sound from somewhere on or off screen),StartDialogue,AdvanceDialogue,ChooseDialogue(pick one of its choices) andTalking(the dialogue being said),HasFlag,SetFlagandFlagChanged(see Dialogue flags),SaveGame,LoadGame,DeleteSave,QuickSave,QuickLoad,HasSave,SavedGamesandSavedGameIn(see Saving and loading; scripts’ private fields and the objects they hold are saved too,[NotSaved]leaves one out,ISavesStatesaves a script’s own way andOnLoadedis called once they’re back: see What scripts keep),SaveVersionand a system that is anISaveUpgrader(older saves brought up to date as they load; see Saves that outlast updates),Skipping,AutoReading,SkipUnread,AutoSpeedandHasRead(see Skip and Auto),SaveData(GetInt,GetFloat,GetBool,GetString,Set,Delete,Clear) andPhysics, the physics world of the scene being played (Raycast,OverlapPoint,OverlapBox,OverlapCircle, each about that scene and takinglayers:to only find colliders on those layers;Impacts, the last step’s hard knocks, andPushes, what shoved what along, each pair once, the pusher first:foreach (var (pusher, crate) in Game.Physics.Pushes)).
Built-in components:
Transform,Camera(withShake),Sprite(single images or sprite sheets, optionally nine-sliced with Slice) andText(withAlignand aFont: a.ttf/.otffile in Assets, which ships with the game, Griff’s free fonts among them once picked, or an installed font’s name; see Text and fonts);Animation, which plays a range of sprite-sheet frames, or named Clips (Idle 0-3, Run 4-9 @12, Die 11-15 once: a name, frames, and optionally a speed andonce; frames written high to low,Close 3-0, play backward) switched withPlay("Run"), which carries on if that clip is already playing. Its Clip picks the one the game starts with.ClipSettings("Run")gives the frames, speed and looping a clip plays with,Animation.FrameAt(time, count, fps, loop)which of its frames shows at a time, andAnimation.FormatClipswrites clips back as text. Its Time (seconds into the clip) is kept with the scene, so a rewind comes back to the frame it was on;Tilemap, a painted grid of tiles from one or more tilesets, square, isometric or hex (Shape,TileHeight,SortByY,Neighbors(x, y); see Isometric maps and Hex maps above) (GetTileandSetTilefrom scripts,Tilesets()andTilesetOf(tile)to see which picture a tile is cut from,FillRect(fromX, fromY, toX, toY, tile)for a box andFloodFill(x, y, tile)for an area, like the Scene view’s tools; -1 clears; each takes aTileTurnafter the tile to lay it turned or flipped, andGetTurn(x, y)andSetTurn(x, y, turn)read and change a cell’s turn, see Turned and flipped tiles;Layers()lists the map’s layers,LayerOfthe map a layer belongs to andBaseMapthe one whose tilesets it uses), with background and one-way tiles.FindPath(from, to)finds the shortest way between two world positions around its solid tiles (background and one-way tiles can be walked through), as waypoints at the corners, moving diagonally but never cutting a wall’s corner (passdiagonal: falsefor four directions);FindSteps(start, goals, avoid, blockingLayers)plans in cells as a grid mover walks, every cell to the nearest of several goal cells round solid tiles, the cells to avoid (where others stand) and solid colliders on the layers named (null if none can be reached;diagonal: truefor eight ways);ParticleEmitter, for continuous effects or one-shot bursts (Burst;BurstOnStartwithDestroyWhenFinishedmakes a self-cleaning effect prefab;Columns,RowsandFramessuch as"0-1"draw frames of a sprite sheet, each particle picking one at birth, orAnimateFramesstepping through them);Button, a clickable area that lightens under the mouse and darkens while held, with a no-code Action (and Fade), a Shortcut key or a Shortcut Action (an input action, so the player can rebind it:button.ShortcutAction = "OpenMap"),OnClickfor behaviors on its entity and aClickedevent for anyone else, Over Panels to keep working while a panel it isn’t on is open (a menu bar over a letter), andIsFocusedwhile the keyboard or gamepad has picked it. Put the labelTexton the button’s own entity so it’s shaded with it;Slider(ValuebetweenMinandMax,Snap, aChangedevent) andToggle(IsOn, aChangedevent; it needs aButtonon its entity), each with a Setting or Save As; see Options;Panel(Open, Pause Game, Pauses, Open On, Open Message, Close On Back, Popup, First,IsTop, andOpenedandClosedevents) andChoice(Index,Value,Step, aChangedevent; it needs aButtonon its entity); see Pause menus and panels, and Controls screens above. Buttons haveInteractable,NeedsSaved,IsUsableandIsListening, andTexthasShown, its value with button prompts filled in;Bar(Value,Max,Fractionand Track); see HUD bars;Chart(Add(value),Add("Cards", value),Set("Cards", values),Clear(),Clear("Cards"),Points("Cards"),SeriesNamesandBounds); see Charts;Pickup(IsCollectedand aCollectedevent) andCounter(Value,Best,Addand aChangedevent); see Pickups and scores;Follow, which moves its entity after a Target, smoothly, with an offset, dead zone, look-ahead and an area to Stay Inside (see Cameras). Scripts can setTargetEntityto follow something directly.Camerahas Sort By Y for top-down games;SortGroup, which sorts its entity and everything inside it as one at the root’s feet (plus aGroundOffset), its parts by their own Order within it, for a thing made of several sprites in a top-down game;Parallax, which scrolls a background layer at a fraction of the world’s speed and can repeat its sprite across the view;ScreenAnchor, which pins its entity to one of nine points on the screen plus an Offset, for HUDs and menus. The Scene view shows it where the game does (on a 16:9 screen) and dragging it there sets its Offset; with the camera’s HudSize set, it keeps its size on the screen as the camera zooms (Camera.HudScalesays how many times its own size it’s drawn);Health(Damage,Heal,Kill,IsDead,IsInvincible, Respawn, andDamaged,DiedandRespawnedevents) andDamageOnTouch(with Kill); see Health and hazards and Checkpoints above.SpritehasFlashandBlinkfor scripts to use themselves;Spawner(with anEntitySpawnedevent,SpawnedandAlive) andLifetime;Typewriter(IsDone,Skip,Restartand aFinishedevent);Mover(Path,OffsetAt,TripSeconds,IsFinished),Patrol(Turnand aTurnedevent),Chase(IsChasing,TargetEntityand aSpottedevent) andShooter(aFiredevent, to give each shot something extra); see Moving platforms, Enemies and Turrets above;Draggable(IsHeld, theTargetit’s on, andPickedUpandDroppedevents) andDropTarget(Contents,IsFull,AcceptsKind, andReceivedandTakenevents), withGame.Heldfor what the mouse holds, andGrabber(Holding,InReach,Press(), andPickedUpandDroppedevents); see Drag and drop;WallRooms(Rooms,Room(id),RoomAt(cell)orRoomAt(point),RoomOf(entity),EntitiesIn(room)andEntitiesIn<T>(room),Refresh()and aChangedevent; eachWallRoomhasId,Cells,Area,OpenandClosed), the rooms a map’s walls make (see Rooms from walls);SelectionControls(Selected,SelectedCount,Select(entity)orSelect(entities),Add,Remove,Clear(),Group(n)andSetGroup(n, entities),Busyto leave the mouse to a tool in hand,IsDragging, andSelectionChangedandCommanded(target, point, selected)events, a right-click’s target, the point in the world and what’s selected) andSelectable(IsSelected,KindName,Area(), andSelectedandDeselectedevents); its keys are the input actionsSelect,Command,AddToSelection,AssignGroupandGroup1toGroup9(InputActions.GroupActions). A selection of your own rules keeps aSelectionStateand answers itsISelectionRules, as the strategy Commander does; see Selecting as in a strategy game;Switch(IsOn, which scripts can set, and aChangedevent),Inventory(Count,Has,Add,Take,AddItems,TakeItems,Clear,Use,Usable,ForTeam, andChangedandUsedevents) andTrail(Copies); see Switches, Keys and inventory, and Trails above. The ideas every genre shares:Teams.Of(entity),Teams.Allies(a, b),Teams.Foes(a, b)andGame.TeamOf(entity)for teams (a component of your own joins one by implementingITeamMember), andGame.CountItems(entity, item),Game.CanPay(entity, cost),Game.Pay(entity, cost),Game.Give(entity, item, count)andItems.Parse(text)for items (one of your own holds them by implementingIHoldsItems); see Mixing genres. Movers, spawners and shooters haveRunningand doors haveLocked, which switches set and scripts can too. A tilemap’sShownTileis the frame an animated tile is showing;Checkpoint(Activeand aReachedevent),Door(aUsedevent) andDialogue(IsTalking,Line,SaidandSpeaker(the line showing and who says it),IsDone,ChoicesandPicked, andLineShown,ChosenandFinishedevents); see Checkpoints, Doors and Dialogue above.FollowhasSnap, to jump straight to its target after a teleport of your own;PlatformerMovement(Facing,IsClimbing,IsDashing,IsWallSliding,JumpsLeft,JumpSpeedFor(gravity),Launch, andJumped,LandedandDashedevents)TopDownMovement(Facing,IsDashing, Isometric, and aDashedevent) andGridMovement(Facing,IsMoving,Cell,Step(x, y)to step from a script, even when it isn’t Controllable,FindMap()for the tilemap it steps on,CellAt(world)andCellCenter(cell),WalkTo(cells),WalkTo(cell)orWalkTo(entity)to walk somewhere andStopWalking(), withIsWalking,IsWaiting,GoalandDestination, andStepped,Bumped,ArrivedandBlockedevents; see Walking somewhere on a grid); see Players with no code.GridChasehasIsChasing,TargetEntityandSpottedandReachedevents, andGridPatrolhasLeg,Away,Path,PointOf(leg),IsFinishedand anArrivedevent (see Grid enemies).AnimationhasHasClip;Ladder,Pushable(OnGoal, and aPushedevent on a grid; off a grid,Game.Physics.Pushessays what shoved it),Goal(CoveredBy,IsCovered,AcceptsKind, andCoveredandUncoveredevents),MoveHistory(Undo(),Restart(),Record(),Moves,CanUndo, andUndoneandRestartedevents),Bouncer(aBouncedevent) andCrumble(Crumbled,IsShaking, andFellandReturnedevents); see Ladders, crates, springs and crumbling ledges. A tilemap’sIsLadderandLadderAtfind its ladder tiles, andShownTile(x, y, tile)is the piece an autotile shows;Light(CurrentIntensity, its flicker included,WorldCenterand Shadows),WeatherandScreenEffects(see Shadows, Weather and Screen effects above),Room(Area,IsCurrentand anEnteredevent),SoundSource(Loudness),BeatClock(IsPlaying,Beat,Bar,BeatInBar,Beats,Phase,Position,CurrentTempo,SecondsPerBeat,SecondsFromBeat,OnBeat(every, offset), andBeatStartedandBarStartedevents) andBeatPulse(Scale); the camera has Ambient,CurrentAmbient(a room’s, as it fades in) andIsDark, and Transition, andFollowhasCurrentRoomandIsChangingRoom. See Rooms, Lights and darkness, Scene transitions and Sounds in the level above;Unit(MoveTo,AttackMoveTo,Attack,Stop,Hold,Order,Destination,Target,IsSelectedand anArrivedevent),Worker(Gather,Carrying,Load,Source),Building(IsBuilt,Rally,IsSelectedand aFinishedevent),Producer(Queue,Progress,Optionsand aTrainedevent),Fighter(Targetand aSwungevent) andFaction(Amount,Add,CanAfford,Spend,Refund,Lacking,IsDefeatedand aDefeatedevent);Costs.Parsereads a cost.Game.SelectedandGame.Selectare the player’s selection,Game.Train,Game.CancelTrainingandGame.PlaceBuildingdo what the command card does (saying why not when they can’t),Game.FactionOf,Game.SupplyUsedandGame.SupplyProvidedlook up a team,Game.IsVisibleasks the fog of war, andGame.LookAtpoints the camera; see Real-time strategy;Multiplayer,Networked,PlayerSpawn,NameTag,ScoreboardandTextField(Value,IsFocused); see Multiplayer.Scene.FindNamedfinds an entity by name, this machine’s player’s own first;Walker(Feet,IsWalking,Route,Said,Hovered,HoveredHotspot,HoveredItem,WalkTo,Stop,Say,ChooseLook,ChooseUse, andArrived,Spoke,DidandUsedItemevents),Hotspot(StandPoint,Called,CanUse,Look(),Use(),UseItem(item)and aDoneevent),InventoryBar(Holding,Pick,PutBack,Combine("Rope + Hook"),LookAt,DescriptionOf,IconOf,CombinationOf, andPicked,CombinedandLookedevents),UseWith(Takes(item)and aDoneevent) andWalkArea(Contains,ScaleAt, andWalkArea.ScaleIn(scene, point));Input.Cursorsets the pointer over the game (MouseCursor.Hand,Cross,Look,HiddenorArrow, on the desktop and in a browser); see Point-and-click adventures. A script component whose shape is a ring of points (a stringPointsproperty, “x y; x y”) can be anIPointOutline, and the Scene view edits its points as it does a polygon collider’s;FirstPersonMovement(Facing,PitchandStride) andWeapon(IsCoolingDown, andFiredandHitevents);Game.FirstPersonView,Game.FirstPersonViewer,Game.CurrentWeapon,Game.EquipandGame.Fire(a shot without ammo, for cutscenes), andInput.MouseDelta,Input.WantsMouseLockandInput.IsMouseLockedfor mouse look of your own; see First-person shooters;Attack(Swing,IsAttacking,IsHitting, andSwungandHitevents); see Swords and close-up attacks.TurnOrder(EndTurn(),IsTurn(team),IsComputer(team),IsBetweenTurns,TeamList, andTurnStartedandTurnEndedevents; setCurrentto hand the turn to a team) andTurnTaker(IsTurn,Act(count),ActionsLeft, andTurnStartedandTurnEndedevents); see Turns. Tilemaps haveSlopeOf,SlopeCellsandPhysics.SlopeShapes(and an isometric or hex map’s walls,Physics.WallShapes);BoxCollider,CircleCollider,PolygonColliderandBody, for physics. Boxes, circles and polygons collide in any combination.GetComponent<Collider>()finds any kind. A polygon’sPointsare its corners (“-1 0; 1 0; 0 1”, in its own units;PolygonCollider.FormatPoints(points)writes a list of them so),WorldPointsits corners in the world as it is,WorldCentroidwhere it balances (what it turns round),ToWorld(point)andFromWorld(point)turn a point of its own into the world and back,PieceCountandPieceCorners(piece)are the convex pieces a concave one is split into, andProblemsays what was wrong with points that don’t make a proper shape and how they were fixed up (null when they do; see Polygon colliders). A body’sBounciness(0 to 1) makes it bounce off what it hits, settling once the bounces get small. WithRotateson it turns:Spinis how fast (degrees a second, anticlockwise),PushAt(force, point, seconds)pushes it at a point in the world (off its middle, it turns too),Twist(torque, seconds)turns it (by the torque over itsInertia, from itsMassand collider’s size, round itsCenterOfMass), andGrip(0 to 1) is how well it grips what it slides along. A ball (aCircleCollideron a body that rotates) rolls, slowed by itsRollingResistance(0.1 by default; 0 rolls on and on): kick one by setting itsVelocity(ball.Velocity = new Vector2(8, 3);, and it rolls on when it lands) or withPushAtunder or over its middle for backspin or topspin (see Balls that roll).Asleepis true while it rests asleep with what it touches (set it, or itsVelocityorSpin, to wake it), andStillForis how long it’s been still. A collider’sTurnssays whether it turns with its body; a box’sWorldShape(middle, half size and turned axis) andWorldCornersare the box as it’s turned (WorldBoxis the box round it), a polygon’sWorldPointsare its corners as it’s turned, and any collider’sContains(point)checks a point against it as it is (WorldBoundsis the box round any kind).Groundis what it’s standing on; when that moves (a platform moved by a script or a tween) the body rides along with it.WasGrounded(0.1f)is true while grounded and for that long after walking off an edge (but not after jumping),AirTimeis how long it’s been off the ground, andWallSideis -1 or 1 while a wall on that side stops it (for wall jumps), andDropThrough()falls through one-way platforms for a moment (IsDroppingThrough;Game.Physics.OnlyOneWayBelowsays whether there’s anything else underneath). Give colliders a Layer (Player,Enemy,Bullet; empty isDefault) and list what they pass through in Ignore Layers, e.g.Player, Bulleton a bullet: ignored pairs don’t block each other, collide or trigger, and only one side has to list the other. Tilemaps have both too.
Saving a script while the game is playing starts the game over with the new version, as a fresh Play would (with as many players, in every player’s window when playing with several), from the scene as it was when you pressed Play. The scripts aren’t swapped into the game as it stands: each would start again (spawning what its Start spawns a second time) without the fields of its own it had, and the others would read scripts that hadn’t started yet.
Rewind
Section titled “Rewind”Rewinding puts the whole game back as it was, and the same game goes on from there just as it did, to the last bit: the scene as its file would hold it, and everything the game kept besides. That’s every system’s state (timers, coroutines and tweens with where they’d got to, message listeners, systems scripts added, the dialogue seen and a conversation under way, a swing under way, a respawn countdown, which control is picked, the camera’s slide into a room, a strategy game’s selection, orders, training queues and fog), the clock (slow motion, a hit stop), the game’s random numbers (so the same rolls come up again), the save data (a best score saved since goes back too), which scripts have started (a script that had doesn’t start again), and each component’s own fields that the scene file doesn’t hold: a script’s private fields, a body’s time in the air, a sprite’s flash or blink, live particles, a falling-block board’s piece and those to come, a match-3 board’s slides and falls. A coroutine, or a lambda given to After, Every or Tween, goes back with what it holds: where it had got to, its variables, and the entities and components it points to (the ones put back). What can’t be rewound is kept as it is: an object of .NET’s or a library’s other than its collections (a StringBuilder, a Stopwatch, a Task and so an async method, a file, a socket, a Random of your own: use Rand, or a GameRandom, which is a Random that rewinds), anything static, and a field marked [NotState("why")], which you can mark yourself to leave something out (a cache, a list kept only to reuse; one of a script’s starts as a new script’s would). Sound isn’t rewound: sounds playing stop and sound sources start theirs again, and music plays on (a beat clock and a beat pulse follow it). Rewinding works with one player, not in a network game or Play with Players. Movers, patrols, checkpoints, panels and pause groups keep their place in the scene file too (a mover’s Time, a patrol’s Walked, a checkpoint’s Active, a panel’s Open, a pause group’s Paused), so rewinding into a pause keeps the pause menu up, and so do puzzles: switches, what they’ve switched (Running, Locked), keys used up, inventories and a match-3 board’s gems and Swaps. A ledge that has crumbled stays gone (its Crumbled is saved) and comes back in its own time.
Rewind in big scenes
Section titled “Rewind in big scenes”A rewind snapshot holds what the scene file would and loads back as the file would (the same ids; a color keeps its 8 bits a channel, and a number that isn’t finite comes back as the property’s default), kept in a compact binary form made without garbage. With it goes what the game keeps besides (see above), written as the snapshot goes through the entities: the systems’ fields, and each component’s own unless it’s at rest (a sprite that isn’t flashing or blinking has nothing to keep, nor has a transform), so BunnyMark’s 50,000 bunnies take as long to copy out as before (3.07 ms against 3.03 in a Release build) and make about 5 KB of garbage a snapshot. Taking one is split so the game waits for as little of it as it can. Between two steps, each entity’s values are copied out into a record of its own, its strings kept as they are (a string never changes) and lists of lines copied, so the snapshot is that moment whatever happens after; the code that copies a component is compiled for its type the first time it’s seen, so this is little more than reading the values, even in a Debug editor. Then, on another thread (it reads nothing but the copies), each record is compared with the entity’s record in the last snapshot and only the bytes that changed are kept: about 25 bytes for a bunny that moved, 4 for a wall that didn’t. Every tenth snapshot is kept whole, so rewinding to one decodes at most ten, and one that’s dropped stays in memory (and counted) until nothing kept is written against it. Snapshots are taken ten times a second. A big scene’s are taken less often only so that they fit in 256 MB (Timeline.MemoryBudget) while still reaching back about 20 seconds, or so that copying them out costs a step half a millisecond at most on average in the editor (Timeline.CostPerStep, at most two seconds apart): a step waits for it, and a slow one makes that frame late. A BunnyMark bunny takes about 35 bytes a snapshot (200 before). 20,000 of them take 1.1 ms to copy out (2.8 ms in a Debug editor) and 1.5 ms (7.2 ms) to settle on the other thread, and they’re kept ten times a second in both (before: 3.5 ms (15 ms) all on one step, every 0.6 seconds (every two)); 50,000 take 2.9 ms (6.6 ms), kept about seven times a second (four). Rewinding a script rebuilt while the game ran reads its values by name, as the scene file would. While the game plays, the Hierarchy and the Inspector catch up with it ten times a second (SceneDocument.RuntimeRefreshed) rather than every frame, and as it pauses or steps; the Hierarchy keeps the rows of entities still there and makes only the rows in sight, so a scene of 20,000 entities fills it at once.