= Forge Scott Duensing :revnumber: 3.00 :revdate: 2026 :doctype: book :toc: left :toclevels: 3 :sectnums: :sectnumlevels: 3 :source-highlighter: rouge :icons: font :experimental: :imagesdir: images [preface] == About Forge Forge is Singe's authoring tool: a way to make a game by describing it -- placing things, attaching behaviours, writing rules -- and having ordinary Singe Lua written for you. It is itself a Singe game and is distributed on its own, beside the engine rather than inside it. This document covers the description format, the vocabulary, the compiler, and the editor. The engine calls the generated code makes (`playerNew`, `collidePointRect`, `onKeyPressed`, `scriptPush`, and the rest) are documented in the Singe Manual. == Tutorials Ten sittings, each ending with a game that plays, each needing only the one before it. They use the *kit* -- pictures, sounds, a clip, and a model -- that travels inside `Forge.game` and is named from a game as `Forge/kit/hero.png`; `Forge/kit/README.txt` lists it. Every tutorial has a scene in `testScripts/` that presses the same keys the text names and checks the result against the description the tutorial ends with, which ships in `Forge/tutorials/` and appears in the chooser, so a reader who loses the thread can open the finished game and compare. The pictures come from those scenes too. === Tutorial 1: Ten minutes image::tutorials/01-playing.png[The starter game, playing, 600] **What you will make.** Nothing new -- the game Forge starts you with, played, changed, saved, and played again. Ten minutes, and you know where everything is. **What you need.** Forge, started from the Singe menu. No files. **Steps.** . Start Forge. It opens on the *chooser*: a list of the descriptions in its data directory (none yet), the samples and tutorials that travel inside `Forge.game`, and *New game* at the bottom. The line under the list says where `Forge.pdf` -- this book -- was put, the first time Forge ran. + image::tutorials/01-chooser.png[The chooser, 600] . Press kbd:[Down] until *New game* is lit, then kbd:[Enter]. Forge writes a starter description into its data directory and opens it. On the left is the *panel*; everything to its right is the *canvas*, which is the game's own screen at the game's own coordinates. A hero stands on a strip of ground, and a readout sits in the corner. + image::tutorials/01-entities.png[The entities panel, 600] . The panel is on *Entities* -- the things placed in this room, each named `id (type)`. Under the list are the fields of whatever is selected; with nothing selected, the room's own. The last line of that box is Forge's *message*: it says what just happened, and while you type, what you are typing. . Press kbd:[Tab]. The panel turns to *Types*: what an entity can be. The starter has three -- `ground`, `hero`, `readout` -- each with a small picture of its look. Below are the lit type's fields: its look, the look's size and colour, its vars, and its behaviours. + image::tutorials/01-types.png[The types panel, 600] . Press kbd:[Tab] again for *Rules*, the sheet that says what happens. Each rule is a line; the lit one is opened to show what it is *on*, *when* it applies, and what it does *then*. Two more kbd:[Tab]s pass *Tracks* and *Dialogues*, empty for now, and come back round to *Entities*. + image::tutorials/01-rules.png[The rules panel, 600] . Back on *Entities*, press the hero on the canvas, or its row in the list. It gets a yellow frame, and the box under the list shows its fields: `id`, `type`, `x`, `y`, and the rest. Press kbd:[Enter]. The message says *type a value for id*, and the id shows selected in its row. Press kbd:[Enter] again until it is *x* that is selected -- or click the `x` row -- type `200`, and press kbd:[Enter]; the hero moves as you type. Then kbd:[Esc] to leave the form. A field passed over with nothing typed keeps its value, and the arrows put a caret into a value to change part of it. . kbd:[Tab] to *Types*, kbd:[Down] to `hero` (or click it), and kbd:[Enter] through the form the same way: `look.r` to `90`, `look.b` to `240` -- the hero turns blue as you type -- and `platformer.speed` to `260`. kbd:[Esc] when the last is in. + image::tutorials/01-recoloured.png[The hero recoloured, 600] . Press kbd:[S]. The star after the title goes: the description is saved, as a file you could open in a text editor. . Press kbd:[P]. The game is built and played: kbd:[Left] and kbd:[Right] run, kbd:[Space] jumps, and it is a little faster than it was. kbd:[Esc] ends the game, and Forge comes back on the same description. **What happened.** A game in Forge is a *description*: layers, types, rooms of entities, and rules. The editor only edits that description, and kbd:[P] compiles it into an ordinary Singe game and runs it. Nothing you saw is special to the editor: the panels are the description's parts, the canvas is the game's screen, and the file kbd:[S] wrote is the whole game. The types and the rest are explained in <<_describing_a_game_instead_of_writing_one>>; the keys are all in <<_keys>>. **Try this.** * Make the hero jump higher: `platformer.jump` in the types panel. * Give the ground a colour of its own. * Drag the hero somewhere else with the mouse, and play again. **The description.** What kbd:[S] wrote, in full. `Forge/tutorials/01-tenMinutes.game` in the chooser is this file. [source,lua] ---- include::../assets/Forge/tutorials/01-tenMinutes.game[] ---- === Tutorial 2: A platformer image::tutorials/02-playing.png[Coins, spikes, and a door, 600] **What you will make.** The starter game with something to do: coins to collect, spikes that send the hero back, and a door to a second room. **What you need.** Tutorial 1, or its finished description (`01-tenMinutes` in the chooser). From the kit: `coin.png`, `spike.png`, `door.png`, `coin.wav`, and `hit.wav`. **Steps.** . Press kbd:[2] for the *Types* panel. (The digits kbd:[1] to kbd:[5] go straight to a panel; kbd:[Tab] walks them in turn.) Press kbd:[A]: a type called `thing` appears, lit, a plain grey box. . kbd:[Enter] opens its form on `name`. Type `coin`, kbd:[Enter]. The next field is `look`: type `sprite`, kbd:[Enter]. The look's own fields follow; kbd:[Enter] past `look.anchor` and `look.faces` to `look.file`, which opens a list of the files under the game's directory and in the kit. Type `coin` to narrow it, and kbd:[Enter] takes `Forge/kit/coin.png`. kbd:[Enter] on to `behaviours`, type `trigger`, kbd:[Enter], then kbd:[Esc]. The type's row now shows the coin. + image::tutorials/02-coinType.png[The coin type, 600] . Press kbd:[1] for *Entities* and kbd:[A]: a coin of the lit type appears in the middle of the canvas, selected. Drag it down to the ground. Press kbd:[D] to duplicate it and drag the copy along; kbd:[D] again for a third, dragged up where a jump will reach it. + image::tutorials/02-coins.png[Three coins placed, 600] . The same way, make a type `spike` from `spike.png` and a type `door` from `door.png`, each a `trigger`, and each with `look.anchor` set to `feet` so that it stands on the ground where it is placed rather than being centred there. Place one of each near the right edge, the door beyond the spikes. + image::tutorials/02-placed.png[Spikes and a door, 600] . Press kbd:[3] for *Rules* and kbd:[N]: a new rule, on `frame`. kbd:[Enter] and type its `note`, `Take a coin`, kbd:[Enter], kbd:[Esc]. Press kbd:[E] to choose what it is on: type `enter` and kbd:[Enter]. The rule's fields now include `a` and `b`, the trigger and what entered it: kbd:[Enter] to `a`, type `coin`; kbd:[Enter] to `b`, type `hero`; kbd:[Enter], kbd:[Esc]. . Press kbd:[T] for an action: type `addScore`, kbd:[Enter]. It appears under the rule, lit, and kbd:[Enter] opens its `amount`: type `10`, kbd:[Enter], kbd:[Esc]. kbd:[T] again for `destroy`; its `entity` is already `self`, which in an `enter` rule is the trigger -- the coin. kbd:[T] once more for `playSound`; kbd:[Enter] opens the file list, type `coin.wav`, kbd:[Enter]. + image::tutorials/02-rule.png[The coin rule, 600] . Two more rules the same way. `Spikes hurt`, on `enter` with `a` `spike` and `b` `hero`: a `moveTo` with `entity` `hero`, `x` `200`, and `y` `380`, and a `playSound` of `hit.wav`. `Through the door`, on `enter` with `a` `door` and `b` `hero`: a `goTo` with `room` `cave`, `entity` `hero`, `x` `60`, and `y` `380`. . The cave does not exist yet. Press kbd:[1] and then kbd:[N]: a second room, empty, and the panel shows it (`room 2 of 2`). With nothing selected -- click the game's name at the top of the panel, or an empty spot on the canvas -- the form is the game's and then the room's: kbd:[Enter] to `name`, type `cave`, kbd:[Enter], kbd:[Esc]. Press kbd:[2], light `ground` with the arrows, kbd:[1], kbd:[A], and drag it to the bottom; the same for a `readout` in the corner and a `coin`. kbd:[PgUp] and kbd:[PgDn] move between rooms. + image::tutorials/02-cave.png[The cave, 600] . kbd:[S], then kbd:[P]. Run into a coin: it goes, the score climbs, and the clip plays. Run into the spikes and you are back at the start. Reach the door and you are in the cave. **What happened.** A *type* is what a thing is: a look and a list of behaviours. An *entity* is one placed in a room. The `trigger` behaviour gives an instance a volume that reports what enters and leaves it, and the `enter` *event* is what a rule listens for; its `a` and `b` say which two types, and inside the rule `self` is the trigger and `other` is what came in. Rooms are separate screens with their own entities, and `goTo` takes an entity with it. Events and the rest are in <<_events>>; every behaviour and action is in <<_the_vocabulary>>. **Try this.** * Make the spikes cost a point: an `addScore` of `-5` in the spike rule. * Put a `door` in the cave that leads back, with a second `goTo`. * Rename an entity in its form: the rules that name it change with it. **The description.** The rules tutorial 1 left are unchanged; what is new is the three types, their entities, the three rules, and the second room. [source,lua] ---- include::../assets/Forge/tutorials/02-platformer.game[] ---- === Tutorial 3: Sprites and sound image::tutorials/03-playing.png[The hero from the sheet, 600] **What you will make.** The same game, with a hero drawn from a sprite sheet who faces the way he runs, coins that spin, a jump that sounds, and music. **What you need.** Tutorial 2. From the kit: `hero.png`, `jump.wav`, and `loop.ogg`. **Steps.** . kbd:[2] for *Types*, and light `hero` with the arrows. kbd:[Enter] to `look`, type `sprite`, kbd:[Enter]; on to `look.file`, and take `Forge/kit/hero.png` from the list; on to `look.frames`, type `8`. The sheet is eight columns: standing, four of walking, a jump, a fall, and a hit, all facing right. The hero on the canvas is now the first of them. . kbd:[Enter] on to `behaviours`. It says `platformer`; type `platformer, frames` -- a comma list, and a behaviour already there keeps its values. Two new fields follow the platformer's: `frames.fps`, type `10`; and `frames.states`, type `idle=1-1, walk=2-5, jump=6-6, fall=7-7` -- each state's run of frames. kbd:[Enter], kbd:[Esc]. + image::tutorials/03-heroType.png[The hero type, 600] . Light `coin`. Its `behaviours` become `trigger, spin`, and `spin.rate` `180` -- degrees a second. + image::tutorials/03-coinSpin.png[Coins that spin, 600] . kbd:[3] for *Rules*. kbd:[Down] to rule 3, `Jump, with ground underfoot`, and once more onto its first part, `keyHeld`. Press kbd:[Delete]: the part goes. kbd:[C] for a condition: type `keyPressed`, kbd:[Enter]; kbd:[Enter] on its `key`, type `SPACE`, kbd:[Enter], kbd:[Esc]. A press rather than a hold, so what comes next happens once. kbd:[T], `playSound`, and `jump.wav` from the list. + image::tutorials/03-jumpRule.png[The jump rule, 600] . kbd:[N] for a new rule, `note` `Music`. kbd:[E], and choose `roomStart`: it fires as a room is entered. kbd:[T], `playMusic`, and `loop.ogg`. + image::tutorials/03-music.png[The music rule, 600] . kbd:[S], kbd:[P]. The hero walks, and turns to face the way he runs though the sheet only has him facing right; he jumps and falls on the frames for it; the coins turn; and the music loops. **What happened.** A `sprite` look with `frames` is a sheet of that many columns, and the `frames` behaviour picks a column by the instance's *state* -- a var every instance has, which the `platformer` sets to `idle`, `walk`, `jump`, or `fall` as it goes, and a rule can set to anything else with `setState`. The platformer also sets the var `facing`, and a sprite look is mirrored while it faces away from its art -- the look's `faces` says which way that is, right unless told. `spin` turns the var `angle`, and a sprite turns with it. Sound is an action, `playSound`, with a volume of its own if you want one, and music is another; the `sound` behaviour plays a clip per event instead, which suits a type that is hit often. Sheets, facing, and states are in <<_instances_vars_and_states>> and <<_the_long_tail_sprite_sheets_tiles_midi_water_soft_bodies>>. **Try this.** * Show the hit frame: in the spike rule, a `setState` of `hit` on the hero, and a `wait` of `0.5` before the `moveTo`. Actions that wait run as a sequence. * Make the coins face the other way: `look.faces` `left` on the coin does nothing, since a coin has no facing, but on the hero it mirrors everything. * Turn the door on the canvas with its yellow handle, or type its `rz`. **The description.** [source,lua] ---- include::../assets/Forge/tutorials/03-sprites.game[] ---- === Tutorial 4: A shoot-em-up image::tutorials/04-playing.png[Rocks, 600] **What you will make.** A game from nothing: a ship on the arrow keys that fires, rocks that fall from spawners and break up, lives, and a game over. **What you need.** Nothing but the kit: `ship.png`, `shot.png`, `rock.png`, `shot.wav`, and `hit.wav`. **Steps.** . Open *New game* and clear the starter. kbd:[1], light `hero`, kbd:[Delete]; the same for `ground`. kbd:[2], light `hero`, kbd:[Delete], and `ground` -- a type can only go once nothing is placed of it. kbd:[3], and kbd:[Delete] three times: the running and jumping rules go, leaving the readout's. . kbd:[1], and with nothing selected the form is the game's own, then the room's. kbd:[Enter] on `title`, type `Rocks`; on `vars`, type `score=0, lives=3`; on `layers`, type `overlay`. The starter's `world2d` layer goes with its gravity: nothing falls unless a behaviour says so. + image::tutorials/04-cleared.png[The game's form, 600] . kbd:[2], kbd:[A], and the ship: `name` `ship`, `look` `sprite`, `look.file` `ship.png`, `vars` `health=1`, and `behaviours` `keys, mover, shooter, health, sound`. Then each behaviour's fields: `keys.left` `LEFT`, `keys.right` `RIGHT`, `keys.up` `UP`, `keys.down` `DOWN`, `keys.fire` `SPACE`; `mover.speed` `260` and `mover.clamp` `true`; `shooter.spawns` `shot`, `shooter.rate` `0.15`, `shooter.offsetY` `-14`; `health.max` `1` and `health.keep` `true`; `sound.hit` `hit.wav`. A file field passed with kbd:[Enter] keeps what it has, `(none)` included. + image::tutorials/04-ship.png[The ship type, 600] . Three more types. `shot`: a sprite of `shot.png`, behaviours `projectile, sound`, `projectile.vy` `-520`, `projectile.life` `3`, and `sound.spawn` `shot.wav`. `rock`: `rock.png`, vars `health=2`, behaviours `drift, health, sound`, `drift.vy` `90`, `health.max` `2`, `sound.death` `hit.wav`. `hive`: a look of `none` and a `spawner` with `spawner.spawns` `rock`, `spawner.every` `0.6`, `spawner.max` `6`, and `spawner.total` `12`. + image::tutorials/04-types.png[The types, 600] . Place them: a `ship` at the bottom, and two `hive`s just above the top edge, one over the ship. A `none` look is drawn on the canvas as an empty box, so it can still be picked up. + image::tutorials/04-placed.png[Placed, 600] . kbd:[3], and the rules. `A shot lands on a rock`, on `collision` with `a` `shot` and `b` `rock`: a `destroy` (of `self`, the shot), a `damage` with `entity` `other` and `amount` `1`, and an `addScore` of `10`. `A rock dies`, on `death` with `type` `rock`: an `emit` with `count` `24` and a colour of `255`, `120`, `60`, and an `addScore` of `50`. + image::tutorials/04-hitRule.png[The hit rules, 600] . `A rock rams the ship`, on `collision` with `a` `rock` and `b` `ship`: `destroy`, and `damage` of `other` by `1`. `The ship is lost`, on `death` with `type` `ship`: `addVar` of `lives` by `-1`, a `flash` of `255`, `60`, `60` for `0.3` seconds, a `setVar` of `health` to `1`, and a `moveTo` back to `360`, `420` -- the ship keeps itself through its death, since `health.keep` is set. `Out of lives`, on `frame`, when a `test` of `lives <= 0`: `gameOver`. `A rock slips past the bottom`, on `frame` with `each` `rock`, when a `test` of `self.y > 500`: `destroy`. And the readout's `text` becomes `"score " .. score .. " lives " .. lives`. + image::tutorials/04-rules.png[The rules, 600] . kbd:[S], kbd:[P]. Fly, fire, and the rocks break up in sparks. Let three through onto the ship and the game is over; kbd:[Esc] brings Forge back. **What happened.** Without a `world2d` layer there is no physics, and the behaviours move things themselves: `keys` reads a player's keys into the vars `dx`, `dy`, and `fire`, `mover` moves by them, `shooter` spawns a type while `fire` is set, `drift` moves steadily, and `projectile` flies until it leaves the picture. `collision` still fires, from the boxes overlapping. `health` gives an instance the var `health` and raises `death` at zero; `spawner` makes a type every so often up to a limit. A rule with `each` runs once per instance of a type, with that instance as `self`, and a `test` is any expression over the vars. Expressions are in <<_expressions>>. **Try this.** * Give the rocks a `spin` behaviour. * A second `shooter` on the ship, `offsetX` `-10` and `10`, for twin guns. * A `hud` layer instead of the readout: `layers` `overlay, hud`, and a `hud.document` of your own RmlUi page with elements named for the vars in `hud.bind`. **The description.** [source,lua] ---- include::../assets/Forge/tutorials/04-shmup.game[] ---- === Tutorial 5: A quick-time event image::tutorials/05-playing.png[A window answered, 600] **What you will make.** A game over video: the kit's clip plays, and at two moments in it a prompt wants a key. In time scores; too late costs a life. The clip loops, and the game ends when the lives are gone. **What you need.** The kit's `clip.mkv`: twelve seconds in which a door opens at three, a target rises at six, and a hand reaches in at nine, with a clock along the bottom. At 24 frames a second, a moment's frame is its second times 24. **Steps.** . Open *New game* and clear it as in tutorial 4: the hero and ground entities, then their types, then the three running and jumping rules. . kbd:[1], nothing selected, and the game's form: `title` `Moments`, `vars` `score=0, lives=3`, and `layers` `disc, overlay`. A `disc` is the video a game is played over, and `disc.file` is now a field: kbd:[Enter] opens the list, take `Forge/kit/clip.mkv`. The canvas shows the clip's first frame. + image::tutorials/05-disc.png[The disc layer, 600] . Two text types. kbd:[2], kbd:[A], `name` `prompt`, `look` `text`, and a colour of `255`, `220`, `80`; kbd:[1], kbd:[A], and drag it near the top. Then `verdict`, `text`, `160`, `255`, `180`, placed under it. + image::tutorials/05-prompts.png[The prompts, 600] . kbd:[3], and three rules for the first window, frames `72` to `120`, when the door opens. `Window one is open`, on `frame`, when `discBetween` with `from` `72` and `to` `120`: a `setText` of `prompt` to `"PRESS!"`. `Window one answered in time`, on `frame`, when `discBetween` `72` to `120`, `keyPressed` with `key` `SPACE`, and `once` with `tag` `one`: a `setVar` of `hitone` to `true`, an `addScore` of `250`, and a `setText` of `verdict` to `"HIT"`. `Window one closed unanswered`, on `frameReached` with `frame` `120`, when a `test` of `not hitone`: an `addVar` of `lives` by `-1`, `verdict` to `"MISS"`, and `prompt` to `""`. + image::tutorials/05-window.png[The first window, 600] . The same three for the second window, `150` to `200`, where the target rises, wanting `UP` and setting `hittwo`. Then `The clip runs out: round again`, on `frameReached` at `280`: `hitone` and `hittwo` back to `false`, `verdict` to `""`, and a `discTo` with `frame` `1`. `Out of lives`, on `frame`, when `lives <= 0`: `gameOver`. The readout's `text` becomes `"score " .. score .. " lives " .. lives`. + image::tutorials/05-rules.png[The rules, 600] . kbd:[S], kbd:[P]. The door opens and the prompt appears; kbd:[Space] in time is a hit. Miss the target's window and a life goes. **What happened.** A `disc` layer plays a video underneath everything, and the rules read its *frame*: `discBetween` is true while the disc is inside a window, `frameReached` fires as it passes a frame, and `discTo` sends it elsewhere -- the whole of a Dragon's Lair is windows and jumps. `once` keeps a rule from firing every frame the window is open, and `keyPressed` is the frame a key goes down, where `keyHeld` is every frame it is down. `setVar` and `test` on a var of your own are how a rule remembers what another did. The `branching` behaviour does all of this in one track for a game of many branches; `Forge/fmv.game` in the chooser is one. In a released game, `games.dat` names the video, as it does for any Singe game. **Try this.** * A third window for the hand at `216` to `264`. * Show the frame: the readout's text with `.. " frame " .. frame`. * Make a miss send the disc somewhere: a `discTo` in the closed rule. **The description.** [source,lua] ---- include::../assets/Forge/tutorials/05-qte.game[] ---- === Tutorial 6: A light gun game image::tutorials/06-playing.png[Draw, 600] **What you will make.** Mad Dog McCree in miniature: the clip plays, a target rises, and the player shoots it with the mouse or a light gun. The hand that reaches in is not to be shot. Ammo runs out and is reloaded by a shot off the picture. **What you need.** The kit's `clip.mkv`, `hit.wav`, and `shot.wav`. **Steps.** . Open *New game*, clear it as before, and give the game's form `title` `Draw`, `vars` `score=0, misses=0`, `layers` `disc, overlay`, and `disc.file` `Forge/kit/clip.mkv`. . kbd:[2], and three types with a look of `none`. `gun`: `vars` `ammo=6`, `behaviours` `gun`, `gun.player` `1`, `gun.ammo` `6`, `gun.reload` `offscreen`. `target`: `vars` `health=1`, `behaviours` `hitbox, health`, `hitbox.track` `target`, `health.max` `1`. `hand`: `behaviours` `hitbox`, `hitbox.track` `hand`. Place one of each anywhere; a hitbox's place comes from its track, not from where it stands. + image::tutorials/06-types.png[The types, 600] . kbd:[4] for *Tracks*, and kbd:[N]: a new track, keyed by frame. kbd:[Enter], `name` `target`, kbd:[Enter], kbd:[Esc]. Along the bottom of the canvas is the *timeline* with its cursor: kbd:[.] and kbd:[,] step it a frame, kbd:[\]] and kbd:[\[] leap ten, and the canvas shows the clip at that frame. Take it to `150`, where the target has risen, and press kbd:[K]: a key at that frame, with a box in the middle of the canvas. kbd:[Enter] through the key's form: `x` `405`, `y` `165`, `w` `90`, `h` `90` -- or drag the box over the target and pull its corner. + image::tutorials/06-firstKey.png[The first key, 600] . Another key at `210`, the same box, so the target stays hittable while it is up; between keys the box slides from one to the next, and outside them there is none. kbd:[N] for a second track, `hand`, with keys at `220` and `260` of `480`, `157`, `240`, `105`. + image::tutorials/06-tracks.png[Two tracks, 600] . kbd:[3], and the rules. `The target is hit`, on `hit` with `type` `target`: `addScore` `100`, `damage` of `self` by `1`, and `playSound` `hit.wav`. `The hand is hit`, on `hit` with `type` `hand`: `addScore` `-200` and a red `flash`. `A shot at nothing`, on `miss`, when a `test` of `not event.offscreen`: `addVar` `misses` by `1`, `playSound` `shot.wav`. `Draw!`, on `frameReached` at `150`: a `say` of `"DRAW!"` for `1` second. The readout's `text`: `"score " .. score .. " ammo " .. gun.ammo .. " misses " .. misses`. + image::tutorials/06-rules.png[The rules, 600] . kbd:[S], kbd:[P]. Shoot the target as it rises; shoot the hand and pay for it; when the ammo is gone, shoot off the edge of the picture to reload. **What happened.** A `gun` behaviour turns a player's pointer -- the mouse, or a light gun, which is a mouse to the engine -- into shots: the trigger raises `hit` on the instance under it, with `self` the thing hit and `other` the gun, or `miss` with `event.offscreen` saying whether the shot left the picture. A `hitbox` behaviour takes its shape from a *track* of boxes keyed by disc frame, which is how a thing in a video can be shot only while it is there: the boxes are where the thing is in the picture, and the editor shows the frame under them. Tracks are in <<_tracks>>. What a cabinet with a light gun needs -- calibration, two guns, the recoil -- is in <<_arcade_plumbing_branching_video_lives_credits_the_board>>. **Try this.** * A second player: `players` `2` in the game's form, a second `gun` with `gun.player` `2`, and `hit` rules with `player` set. * Shoot the door as it opens: a third track, and a type with `health.max` `3` that takes three hits. * Score by where the shot landed: a `target` behaviour with `zones` for a 3D model is in tutorial 9; in 2D, two hitbox types on two tracks make a bull and an outer ring. **The description.** [source,lua] ---- include::../assets/Forge/tutorials/06-gun.game[] ---- === Tutorial 7: A point-and-click adventure image::tutorials/07-playing.png[The yard, 600] **What you will make.** A room of the Sierra and LucasArts kind: a painted yard, a hero who walks where you click, things to look at, take, open, and talk to, a key that goes in the inventory, a guard with a conversation, and a door to a second painted room. **What you need.** The kit's `yard.png`, `hall.png`, `hero.png`, `key.png`, `door.png`, and `door.wav`. **Steps.** . Open *New game*, clear it, and fill the game's form: `title` `The Yard`, `vars` `score=0, allowed=false`, `verbs` `walk, look, take, use, open, talk`, and `layers` `overlay`. Then the room's own fields, further down the same form: `name` `yard`, `depthSort` `true`, and `scaleBy` `300:0.6, 460:1.0` -- things standing at the back of the yard are drawn at six tenths, at the front full size, and between in proportion. . kbd:[2], kbd:[A]: a type `backdrop`, a `sprite` of `yard.png`. kbd:[1], kbd:[A], and it fills the canvas. Now the floor the hero may walk on. Press kbd:[V]: a walk area is being drawn, the panel steps aside, and every press on the canvas is a corner. Press the four corners of the yard's floor -- left and right where the wall meets it, then the near corners -- and kbd:[V] again to close it. A green outline shows it, and the panel comes back. + image::tutorials/07-walkArea.png[The walk area, 600] . The hero: a `sprite` of `hero.png` with `look.frames` `8` and `look.anchor` `feet`, `behaviours` `walker, frames`, `walker.speed` `160`, `frames.fps` `10`, and `frames.states` `idle=1-1, walk=2-5`. Place him at the front left. A walker's state is `walk` or `idle` as it goes, and the frames follow. . Three hotspots. `door`: `door.png`, `look.anchor` `feet`, `behaviours` `hotspot`, `hotspot.name` `door`, and a place to walk to first, `hotspot.walkX` `590` and `hotspot.walkY` `330`; place it in the doorway. `key`: `key.png`, a hotspot named `key` with a walk point of `200`, `400`, placed on the floor. `guard`: `hero.png` again, `frames` `8`, `look.faces` `left` so he looks toward the hero, a hotspot `guard` at `480`, `360`, placed by the door. + image::tutorials/07-hotspots.png[The hotspots, 600] . kbd:[5] for *Dialogues*, and kbd:[A]: a dialogue called `talk` with one node, `start`. kbd:[Enter], `name` `guard`. kbd:[Down] onto the node: `who` `Guard`, `text` `Nobody passes.` kbd:[A] on a node adds a choice: `text` `Why not?`, `next` `why`. kbd:[A] again: `Fine.`, with no `next`, which ends the talk. kbd:[Up] to the dialogue's own row and kbd:[A] for a second node: `name` `why`, `who` `Guard`, `text` `The door is locked, and I have no key.` Its choices: `I have a key.` with `when` `has("key")` and `next` `ok`, and kbd:[T] on that choice for an action, `setVar` of `allowed` to `true` -- kbd:[Delete] on its `entity` empties it, so the var is the game's; then `I see.` A third node `ok`: `Then go ahead.` + image::tutorials/07-dialogue.png[The dialogue, 600] . kbd:[3], and the rules. `A click on the floor walks there`, on `pressed` with `switch` `SWITCH_BUTTON3` and `interrupt` `true`: a `walkToPointer` of `hero` for `player` `1`. `Look at anything`, on `verb` with `verb` `look`: a `say` of `"It is a " .. event.target .. "."` for `1` second. `Take the key`, on `verb` `take` with `target` `key`: `walkToHotspot` of `hero` (its `target` is `self`, the key), `give` `key`, `addScore` `1`, `destroy`. `Talk to the guard`, on `verb` `talk` with `target` `guard`: `walkToHotspot`, `face` of `hero` toward `self`, and `talk` with `dialogue` `guard`. + image::tutorials/07-verbRules.png[The verb rules, 600] . `The door, while the guard objects`, on `verb` `open` with `target` `door`, when `not allowed`: `say` `"The guard shakes his head."`. `The door opens`, the same event with `controls` `false`, when `allowed`: `walkToHotspot`, `playSound` `door.wav`, a `fade` of `0.5` with `out` `true`, a `goTo` of `hero` to `room` `hall` at `120`, `420`, a `fade` back with `out` `false`, and `addScore` `1`. `Anything else`, on `verb` with nothing filled in: `say` `"That does not work."`. Then the verb keys: on `pressed` with `key` `L`, a `setVerb` of `look`; `T` for `take`, `O` for `open`, `K` for `talk`. The readout's `text`: `(sentence or "") .. " score " .. score`. . The hall. kbd:[1], kbd:[N] for a room, `name` `hall`, `depthSort` `true`. Place a `backdrop` -- and, since the type's picture is the yard, a second type `hall` from `hall.png` does better -- a walk area along its floor, and a `readout`. kbd:[PgUp] goes back to the yard. + image::tutorials/07-hall.png[The hall, 600] . kbd:[S], kbd:[P]. Click the floor and the hero walks; the sentence line says what the pointer is over. kbd:[T] then a click on the key takes it. kbd:[K] and the guard: with the key, the second choice opens, and the guard relents. kbd:[O] and the door, and the yard fades to the hall. **What happened.** A room's *walk areas* become a navigation mesh, and a `walker` finds its way across them; `depthSort` draws what is lower on the screen in front, and `scaleBy` makes what is further up smaller, so a flat picture reads as a floor. A `hotspot` is anything a *verb* can be used on: the game's `verbs` are the sentence's first word, the pointer's hotspot its second, and a click raises `verb` with both, the most specific rule winning -- `open` on `door` beats `open` on anything, and a rule with no `verb` at all is the last resort. `give`, `take`, and `has` are the inventory; `talk` runs a *dialogue*, a tree of what is said and what may be answered, each choice with a `when` and actions of its own. Actions that take time -- `walkToHotspot`, `say`, `fade`, `talk` -- make a rule a *sequence* that runs to its end while the rest of the game goes on, unless `controls` is off. All of it is in <<_adventures_rooms_walking_hotspots_verbs_and_talk>>. **Try this.** * A `use` rule: `verb` `use` with `item` `key` and `target` `door`, for when the player tries the key on the lock. * A parser: `layers` `overlay, parser`, and rules on `said` with a `verb` and a `noun` -- the Sierra way, typed. * A `saveGame` on a key, and a `loadGame` on another. **The description.** [source,lua] ---- include::../assets/Forge/tutorials/07-adventure.game[] ---- === Tutorial 8: Into 3D image::tutorials/08-playing.png[The fox in the field, 600] **What you will make.** The platformer again, in a scene: a fox on the engine's character controller, a floor and a ledge, a prize on the ledge, and a camera that follows. **What you need.** The kit's `fox.glb` and `coin.wav`. **Steps.** . Open *New game*, clear it, and in the game's form set `title` `Field` and `layers` `scene3d, overlay`. The canvas becomes the *viewport*: the scene itself, built from the types' looks, under an editor camera. `scene3d.fov` `55` narrows the view a little. + image::tutorials/08-scene.png[A scene, 600] . kbd:[2], and three `mesh` types -- solid shapes with a colour. `floor`: `look` `mesh`, `look.shape` `box`, `look.w` `30`, `look.h` `0.4`, `look.d` `12`, a colour of `90`, `110`, `90`, and `behaviours` `solid`. `ledge`: a box of `4`, `0.4`, `4` in `110`, `90`, `70`, also `solid`. `prize`: `look.shape` `sphere`, `look.w` `0.6`, in `255`, `200`, `60`, with `behaviours` `trigger`. Sizes are world units; a unit is about a metre. + image::tutorials/08-meshes.png[The meshes, 600] . The hero: `look` `model`, `look.file` `fox.glb`, `look.scale` `0.008` (the fox is drawn in centimetres), and `look.w` `0.6`, `look.h` `0.9`, `look.d` `0.6` for how big it counts as. `behaviours` `keys, character, animator`: `keys.left` `LEFT` and so on for the four arrows; `character.speed` `4`, `character.jump` `6.5`, `character.radius` `0.3`, `character.height` `0.9`; `animator.idle` `Survey` and `animator.walk` `Walk`, two of the fox's own clips. And a `camera`: a look of `none`, `behaviours` `camera`, `camera.mode` `follow`, `camera.target` `hero`, an offset of `camera.x` `0`, `camera.y` `3`, `camera.z` `7`, `camera.lookY` `0.6`, and `camera.lag` `0.2`. + image::tutorials/08-hero.png[The hero and the camera, 600] . Place them. kbd:[1], kbd:[A] puts an entity on the floor under the middle of the viewport; its `x`, `y`, and `z` are typed, in units, to a tenth. The `floor` at `0`, `-0.2`, `0`; the `ledge` at `6`, `1.2`, `0`; the `prize` at `6`, `2`, `0`; the `hero` at `-4`, `0.5`, `0`; the `camera` at `-4`, `3`, `7`. kbd:[J] and kbd:[L] orbit the editor camera round what is selected, kbd:[Y] and kbd:[H] tilt it, kbd:[I] and kbd:[K] close in and back off. An entity is picked by a press where it stands and dragged across the floor; the selected one carries the *gizmo*, three arms along the axes -- drag an arm's handle to move along that axis alone -- and a yellow fourth that turns it. + image::tutorials/08-viewport.png[The viewport, 600] . kbd:[3], and the rules. `Walking is a state the animator plays`, on `frame` with `each` `hero`, when a `test` of `self.dx != 0 or self.dy != 0`: a `setState` of `self` to `walk`. `Standing still`, the same with `self.dx == 0 and self.dy == 0` and `idle`. `Jump, with ground underfoot`, on `frame`, when `keyPressed` `SPACE` and `onGround` of `hero`: `jump` `hero`. `The prize is taken`, on `enter` with `a` `prize` and `b` `hero`: `destroy`, `addScore` `100`, `playSound` `coin.wav`. `Fallen off the world`, on `frame`, when `hero.y < -5`: a `moveTo` of `hero` back to `-4`, `0.5`, `0`. + image::tutorials/08-rules.png[The rules, 600] . kbd:[S], kbd:[P]. The arrows walk the fox relative to the camera, which follows; kbd:[Space] jumps it onto the ledge, and the prize goes. **What happened.** A `scene3d` layer makes the room a scene: positions are world units in three axes, physics is Jolt's, and the picture is drawn from whichever instance has the `camera` behaviour. `mesh` looks are shapes, `model` looks are glTF files with their own animation clips, which an `animator` plays by state. `character` is the engine's character controller, moved by the vars `dx` and `dy` that `keys` sets, relative to the camera; `trigger` and `enter` work as they did on the flat. The viewport is the same scene under an editor camera, which is why what is placed is what is seen. Scenes are in <<_games_in_the_scene>> and the viewport in <<_the_3d_viewport>>. **Try this.** * A `light` type -- `look` `light`, `look.type` `point`, a colour and an `intensity` -- placed above the ledge. * `camera.mode` `orbit`, or `first` for the fox's own eyes. * A `sky`: `scene3d.sky` with a picture of your own. **The description.** [source,lua] ---- include::../assets/Forge/tutorials/08-scene.game[] ---- === Tutorial 9: A rail shooter image::tutorials/09-playing.png[The dead yard, 600] **What you will make.** House of the Dead in a yard: the camera rides a rail and stops; at each stop zombies come out and walk at you; the gun on the pointer drops them, ragdolls and all; when the stop is clear the rail moves on. The yard is the painting from tutorial 7, standing in the scene, with its pillar as an *occluder* -- a box that is not seen but hides what walks behind it, so a zombie passes behind a pillar that is only paint. **What you need.** The kit's `fox.glb`, `yard.png`, and `hit.wav`. **Steps.** . Open *New game*, clear it, and in the game's form: `title` `The Dead Yard`, `vars` `score=0, health=3`, `layers` `scene3d, overlay`, `scene3d.fov` `60`. . The room's meshes. `floor`: a `mesh` box of `40`, `0.2`, `60` in `70`, `75`, `90`, `solid`. `painting`: a box of `24`, `16`, `0.2` in white (`255`, `255`, `255`) with `look.texture` `yard.png` and `look.unlit` `true`, so the picture is shown as painted and not as lit. `pillar`: a box of `1.2`, `8`, `1.2` with `look.occluder` `true` and no colour at all. + image::tutorials/09-room.png[The room's types, 600] . The rest. `camera`: a look of `none`, `behaviours` `camera`, `camera.mode` `rail`, `camera.track` `rail`. `gun`: as in tutorial 6. `zombie`: `look` `model`, `look.file` `fox.glb`, `look.scale` `0.01`, a size of `0.8`, `0.9`, `1.4`, `vars` `health=2`, and `behaviours` `health, seek, animator, target, timer`: `health.max` `2`, `health.ragdoll` `true`, `health.linger` `2`; `seek.target` `camera`, `seek.speed` `1.6`, `seek.stopAt` `2.2`; `animator.idle` `Survey`, `animator.walk` `Walk`, `animator.attack` `Run`; `timer.name` `bite`, `timer.after` `1.5`, `timer.start` `false`. + image::tutorials/09-zombie.png[The zombie, 600] . Place the `floor` at `0`, `-0.1`, `-20`; the `painting` at `0`, `6`, `-30`, standing across the far end; the `pillar` at `0`, `4`, `-22`, in front of the painted one; the `camera` at `0`, `1.6`, `0`; the `gun` anywhere. + image::tutorials/09-placed.png[Placed, 600] . kbd:[4] for *Tracks*, kbd:[N]: in a scene a new track is a rail, keyed by time. `name` `rail`. kbd:[K] puts a point where the editor camera stands, looking where it looks; its form takes the rest. Four points: `at` `0` at `0`, `1.6`, `0` looking at `0`, `1.2`, `-10`; `at` `4` at `0`, `1.6`, `-6` looking at `0`, `1.2`, `-16` with `stop` `gate`; `at` `10` at `2`, `1.6`, `-14` looking at `2`, `1.2`, `-24` with `stop` `pillar`; and `at` `14` at `2`, `1.6`, `-18` looking at `2`, `1.2`, `-28`. The rail is drawn through the viewport with its stops marked. + image::tutorials/09-rail.png[The rail, 600] . kbd:[3], and the rules. `The gate: two come out of the dark`, on `stopped` with `name` `gate`: two `spawn`s of `zombie`, at `-2`, `0`, `-14` and `2`, `0`, `-15`. `The pillar: three more, one from behind it`, on `stopped` `pillar`: spawns at `-1`, `0`, `-23`; `0`, `0`, `-26`; and `4`, `0`, `-24`. `A zombie walks the moment it is made`, on `spawn` with `type` `zombie`: `setState` `walk`. `A shot lands`, on `hit` with `type` `zombie`: `damage` `1`, `addScore` `100`, `playSound` `hit.wav`. + image::tutorials/09-hitRule.png[The hit rules, 600] . `It reaches the camera and rears up`, on `arrived` with `type` `zombie`: `setState` `attack` and a `timerStart` of `bite` after `1.5`. `The bite lands unless it was killed in time`, on `timer` with `type` `zombie` and `name` `bite`, when `inState` `attack`: `addVar` `health` by `-1` (its `entity` emptied with kbd:[Delete]), a red `flash`, a `shake` of `0.3` for `0.4`, and the timer started again. `The stop is clear: move on`, on `frame`, when `waiting` with `entity` `camera` and a `test` of `count("zombie") == 0`: `pathNext` `camera`. `Out of health`, on `frame`, when `health <= 0` and `once` with `tag` `over` and `scope` `game`: a `say` of `"YOU ARE DEAD"` and `gameOver`. `The end of the rail`, on `railEnd`: `say` `"STAGE CLEAR"`. The readout's `text`: `"score " .. score .. " health " .. health .. " ammo " .. gun.ammo`. + image::tutorials/09-rules.png[The rules, 600] . kbd:[S], kbd:[P]. The camera glides to the gate; two foxes come at you. Shoot them: two hits each, and they fall limp. The rail moves on to the pillar, where one comes out from behind it. **What happened.** A `camera` in `rail` mode rides a track of points in time, each with a place and a point to look at, and holds at a point with a `stop`, raising `stopped` with its name; `waiting` is true while it holds, `pathNext` sends it on, and `railEnd` fires at the last point. `seek` walks an instance straight at a target and raises `arrived`; a `timer` raises `timer` after its seconds, started by `timerStart`. In a scene the gun casts a ray from the pointer, and what it meets is a `target` -- a body the size of its look, with `zones` on named bones if a headshot should count for more. `health.ragdoll` lets a dead model fall on its own bones and `linger` before it goes. An `occluder` mesh writes depth and no colour: anything behind it is hidden, and the painting behind it shows through, which is how a 3D character walks behind a pillar that is only in the picture -- the way a Grim Fandango room is built. Rails and the gun are in <<_games_in_the_scene>>; painted rooms in <<_adventures_rooms_walking_hotspots_verbs_and_talk>>. **Try this.** * Headshots: `target.zones` on the zombie, a list of boxes on named bones typed as `name=head, bone=b_Head_05, w=0.5, h=0.5, d=0.5`, and a `hitPart` condition of `head` in a second hit rule that does `2` damage. * Fog: `scene3d.fog` `r=12, g=12, b=22, near=25, far=60`. * A third stop, and a `light` type above it. **The description.** [source,lua] ---- include::../assets/Forge/tutorials/09-rail.game[] ---- === Tutorial 10: Releasing your game image::tutorials/10-playing.png[Coin Run, 600] **What you will make.** The platformer from tutorial 3 as an arcade game: lives, a coin slot, a continue, the score panel round the picture, the score sent to the board -- and a folder with everything it needs, ready to be packed into a `.game` and listed by the Singe menu on a machine that has never seen Forge. **What you need.** Tutorial 3, or `03-sprites` from the chooser. **Steps.** . Open it, and in the game's form: `title` `Coin Run`, `vars` `score=0, lives=3, credits=0`, and `layers` `world2d, bezel`. The `bezel` is the arcade's score panel round the picture, showing the vars `score`, `lives`, and `credits` as they change. + image::tutorials/10-bezel.png[The bezel layer, 600] . kbd:[3], and onto rule 6, `Spikes hurt`, kbd:[Down] past its parts to the last, and kbd:[T]: an `addVar` of `lives` by `-1`, its `entity` emptied. Then `Out of lives`, on `frame`, when `lives <= 0` and `once` with `tag` `over` and `scope` `game`: a `submitScore` with `board` `default`, a `say` of `"GAME OVER"` for `2` seconds, and `gameOver`. `A coin`, on `pressed` with `switch` `SWITCH_COIN1`: `credit`. `Continue`, on `pressed` with `switch` `SWITCH_START1`, when `credits > 0 and lives <= 0`: `addVar` `credits` by `-1`, `setVar` `lives` to `3`, and `restart`. The readout's `text` gains `.. " lives " .. lives`. + image::tutorials/10-arcadeRules.png[The arcade rules, 600] . kbd:[S], then kbd:[X]: the game is *released* into a folder named for it in Forge's data directory -- `CoinRun` -- and the message says where. In it: `CoinRun.singe`, the compiled game; `Author.singe`, the runtime it needs; `CoinRun.game`, the description, so it can be opened again; `games.dat`, so the menu lists it, naming the script as the menu will see it, `CoinRun/CoinRun.singe`; and every file the description named, at the same relative name -- here the kit's hero, coins, and sounds under `Forge/kit/`. + image::tutorials/10-released.png[Released, 600] . Pack it, as any Singe game is packed, from a shell in Forge's data directory (the chooser said where it is, beside `Forge.pdf`): + [source,text] ---- Singe --pack CoinRun CoinRun.game ---- + Copy the `.game` beside the other games, and the menu lists *Coin Run* next time it starts. Nothing of Forge is in it but the runtime it carries. A packed game left beside the descriptions is not mistaken for one: the chooser can tell them apart. **What happened.** A released game is an ordinary Singe game: a script, a `games.dat`, and its files. The compiled script finds its own folder and loads the runtime and every named file from there first, so it runs wherever it is put, packed or loose. `submitScore` queues the score for the master service's board for this game, sent when the machine can reach it, and the menu shows the board. `credit` counts a coin, and the bezel's `credits` is what a cabinet's coin door shows. Releasing is in <<_releasing_a_game>>; the board, the bezel, and what a cabinet needs are in <<_arcade_plumbing_branching_video_lives_credits_the_board>>. **Try this.** * Open `CoinRun.game` from the release folder in Forge -- drop it in the chooser's data directory -- change something, and release again. * Two players: `players` `2`, a second hero on the other keys, and `score2` and `lives2` for the bezel's other side. * A `hud` layer instead of the readout, with an RmlUi document of your own. **The description.** [source,lua] ---- include::../assets/Forge/tutorials/10-release.game[] ---- === Recipes The rest of the catalogue, a paragraph each, with a sample to open from the chooser. A recipe names the behaviours; the tutorials above have shown how each is typed. *Racing* (`racing.game`). A `scene3d` room, a `vehicle` behaviour on a mesh -- `type` `car`, `motorcycle`, `tank`, or `boat`, with its `mass`, `thrust`, `torque`, and wheel sizes -- driven by `keys` through the vars `dy` (throttle) and `dx` (steering), a `camera` in `follow` mode behind it, and opponents on `racer`, which drives a vehicle round a track of points on its own. A `trigger` across the finish line, a `lap` var, and a `timer` for the clock. *Tower defence* (`towerdefence.game`). Creeps on `patrol` along a list of points to the base, a `spawner` making waves of them, towers with `turret` -- `targets` the creep type, a `range`, a `rate`, and a `spawns` projectile or plain `damage` -- placed by `spawnAtPointer` on a click while the gold var allows, and `patrolEnd` costing a life. *Pool and water* (`pool.game`). A `mesh` with the `water` behaviour is a pool: bodies inside it float or sink by their `buoyancy`, and a `soft` mesh is a cloth or a balloon. `body` gives anything mass, bounce, and friction; `push` shoves it; `joint` hangs one body from another by a hinge, a ball, or a slider. *Bowling* (`bowling.game`). Ten `body` pins and a ball pushed along the lane, a `trigger` behind the pins that counts what falls through, and a `frame` rule that resets the rack with `moveTo` when the ball is past them. *The maze* (`maze.game`). A `grid` look draws a map of tiles from a sheet -- `columns` wide, `tile` pixels square, the `map` rows of tile numbers -- and `solid` walls are entities placed on it; `keys` and `mover` with `clamp` walk the maze, a `timer` runs the clock, and `enter` on the exit `trigger` ends it. *Third person* (`thirdperson.game`). Tutorial 8's fox with an `orbit` camera: the mouse turns the view, the arrows walk relative to it, and `camera.eye` raises the viewpoint. `animator` with `attack` and `hit` clips, `health` on what it fights, and `animationDone` to return to `idle`. *Space* (`space.game`). `thrust` pushes a body along its nose by `dy` and turns it by `dx` -- a ship without lift -- with `projectile`s that `aim` along their flight and a `spawner` of rocks that `drift`. *Rhythm and MIDI* (`rhythm.game`). A game that opened a MIDI port hears `midi` events with `event.pitch` and `event.velocity`; a `timeBetween` window round each beat, and `once` per note, scores the hit. A `music` layer plays the track from the start. *The 3D adventure* (`adventure3d.game`). Tutorial 7's rooms in a scene: `walker` on the floors named in the room's `navFrom`, hotspots with a `walkX`, `walkY`, `walkZ`, a painted backdrop on an unlit mesh, and `occluder` boxes where the painting's furniture stands, so the hero walks behind it. The verbs, the inventory, and the dialogues are exactly the flat ones. *The board and the bezel* (`fmv.game`). `bezel` round any game, `credit` on the coin switch, `restart` on start with credits, and `submitScore` at the end; `branching` for a Dragon's Lair of windows, moves, and jumps. *Anything else.* The `lua` action runs a line of Singe Lua in a rule, with `self`, `other`, and `event` in scope, and the engine's whole API with them: the way out when the vocabulary stops, and it is meant to be used. == Describing a Game Instead of Writing One A game can be written as a *description* -- a table of types, rooms, and rules -- and compiled into an ordinary Singe game. `Forge/AuthorCompile.singe` does the compiling and `Author.singe` is the runtime the result calls. **No part of Forge ships with Singe** -- not the editor, not the compiler, not the runtime -- so a game built with it carries its own copy of that runtime and stands entirely on its own. Nothing is interpreted at run time: the rules become real Lua functions, so a description costs nothing per frame on a Raspberry Pi, and the game it produces can be opened, read, and edited by hand like any other. There is no notion of genre anywhere in it. A game declares which of the engine's own layers it uses, and that is the only difference between a light gun game, a platformer, and a quick-time event over video. === The nouns *Layers* are what the game draws through: `world2d` (physics in the XY plane, drawn into the overlay), `scene3d` (the 3D scene, with a sun, a sky, and fog; positions are world units), `overlay` (flat drawing), `disc` (the video the game is played over), `hud` (an RmlUi document bound to the game's vars), and `music`. A game lists the ones it wants. *Types* are what things are: a `look` (`box`, `sprite`, `text`, or `none` in 2D; `model`, `mesh`, `light`, `billboard`, or `text3d` in the scene), any number of `behaviours`, and `vars` -- the state every instance starts with. Everything placed or spawned is an instance of a type. *Rooms* are where things are. A room lists its `entities` -- a type, an id, a position, and any vars that differ -- and its `tracks`. A room keeps its state when it is left unless it says `reset = true`; vars declared on the game live across rooms. *Rules* are a trigger, conditions, and actions. `on` names the event -- every frame, a collision between two types, a shot landing, a death, a timer, a disc frame passing -- and `each` scopes the rule to every instance of a type, with `self` bound. All of a rule's conditions must hold for its actions to run; an `any` group inside them is an OR. .A description, in full [source,lua] ---- return { title = "One rule", layers = { { kind = "world2d", gravity = 1500 } }, vars = { score = 0 }, types = { ground = { look = { kind = "box", w = 720, h = 40, r = 60, g = 70, b = 90 }, behaviours = { { kind = "solid" } } }, hero = { look = { kind = "box", w = 24, h = 44, r = 230, g = 90, b = 170 }, behaviours = { { kind = "platformer", speed = 210, jump = 620 } } } }, rooms = { { name = "start", entities = { { type = "ground", id = "ground", x = 360, y = 440 }, { type = "hero", id = "hero", x = 120, y = 380 } } } }, rules = { { note = "Run right", on = "frame", when = { { "keyHeld", key = "RIGHT" } }, act = { { "run", entity = "hero", direction = 1 } } } } } ---- Compile it and run what comes out: [source,lua] ---- dofile("Forge/AuthorCompile.singe") dofile(authorBuild("mygame.game", singeGetDataPath() .. "mygame.singe")) ---- `testScripts/author/` holds the worked examples -- a platformer, a QTE over video, a shoot-em-up, a light gun game over video, a rail shooter, a 3D platformer, an adventure in 2D and in 3D, a racing game, a tower defence, branching video, a first-person shooter, breakout, a maze, third-person action, a space shooter, a rhythm game, and bowling, with two more for the sprite, tile, MIDI, water, and soft-body pieces -- and `testScripts/scene52.singe` onward compile and play them. === Instances, vars, and states A type's `vars` are what each instance starts with: `vars = { health = 3, ammo = 6 }`. An entity in a room may override them (`vars = { health = 5 }`), and rules read and change them: `self.health` in an expression, `setVar` and `addVar` as actions. `state` is a var every instance has, `"idle"` to begin with; `setState` changes it and `inState` tests it. Vars declared on the game -- `vars = { score = 0, lives = 3 }` -- belong to no instance and are read by name: `score`, `lives`. `setVar` and `addVar` with no entity change them, and `addScore` is `addVar` on `score`. Instances are made when a room is entered, by a `spawner` or `shooter` behaviour, or by the `spawn` action, and go with `destroy`, with a `health` that reaches zero, or with a `projectile` that leaves the picture. An instance made at run time has an id of its own (`raider#7`); rules reach it as `self` or `other`, or through `each`. === Events A rule's `on` says what triggers it: [cols="1,3"] |=== | `frame` | every frame, the default | `pressed`, `released` | a key or a switch, once per press; `key` or `switch` narrows it | `collision` | an instance of type `a` began touching one of type `b`; `self` is `a`, `other` is `b` | `hit`, `miss` | a gun's shot landed on `self` (`other` is the gun), or landed on nothing; `event.offscreen` says whether a miss left the picture | `death`, `spawn` | `self`'s health reached zero; `self` was just made | `timer` | a timer on `self` went off; `event.name` says which | `frameReached` | the disc passed `frame` | `enter`, `leave` | an instance of type `b` entered or left a trigger of type `a` | `arrived` | a seeking instance reached its target | `stopped`, `railEnd` | a rail camera reached a stop (`event.name`), or the end of its track | `animationDone` | a clip that does not loop ended on `self` | `roomStart`, `roomEnd`, `gameOver` | the game's own moments |=== The event's parameters are written on the rule itself (`on = "collision", a = "shot", b = "raider"`). `each = "raider"` runs a frame rule once per live raider, and narrows an event rule to that type; `room = "hall"` keeps a rule to one room. === Expressions Anywhere a number goes, an expression may go instead, as a string: numbers, strings, `+ - * / % ^ ..`, comparisons, `and`, `or`, `not`, and the names below. Nothing else is allowed -- an expression is checked when the game is built -- and the `lua` action is the way past it. [cols="1,2"] |=== | `self.health`, `other.x`, `event.frame` | the instance the rule is about, the other one, the event's own fields | `hero.x` | a placed entity's field or var, by id | `score`, `lives` | the game's vars | `time`, `frame` | seconds since the game began; the disc frame | `count("raider")` | live instances of a type | `random(a, b)`, `distance(a, b)`, `has("key")` | a number in a range; between two instances; whether the inventory holds an item | `abs`, `min`, `max`, `floor` | arithmetic |=== A `test` condition takes one: `{ "test", expr = "count('raider') == 0" }`. === Sequences Some actions take time. `say` shows a line and waits for it; `wait` waits. A rule with one of them runs as a coroutine, its actions in order, each waiting its turn -- a cut-scene is the same shape as `addScore`. While it runs the same rule does not start again for the same instance unless the rule says `interrupt = true`, and `controls = false` on the rule takes the keys away for its duration. === Tracks A track is keys by disc frame (`key = "frame"`) or by time (`key = "time"`), and a room may hold any number. A track of `boxes` is a hitbox that moves with the picture: [source,lua] ---- tracks = { { name = "bandit", key = "frame", boxes = { { at = 120, x = 100, y = 200, w = 60, h = 100 }, { at = 220, x = 400, y = 200, w = 60, h = 100 } } } } ---- Between keys the box is interpolated; outside them there is none. A type with `{ kind = "hitbox", track = "bandit" }` is hit where the track says, which is how a light gun game over video is 2,750 rectangles no longer. A track of `points` is a rail for a camera: positions by time, each looking at a point (`look = { x, y, z }`) or at an entity by id, and any point may be a `stop` that holds the camera there until a `pathNext` action sends it on -- which is what House of the Dead does at every doorway. [source,lua] ---- { name = "rail", key = "time", points = { { at = 0, x = 0, y = 1.6, z = 0, look = { 0, 1.2, -10 } }, { at = 4, x = 0, y = 1.6, z = -6, look = { 0, 1.2, -16 }, stop = "hall" } } } ---- === Games in the scene A game with a `scene3d` layer places its types in world units and draws them through the engine's renderer. A `model` look is a glTF file with a scale and a starting clip; `mesh` is a box, sphere, cylinder, or plane with a colour; `light` and `billboard` are what they say. The 2D looks still draw on the overlay over the scene, which is how a score readout works in both. The behaviours that live there: `character` is the engine's controller moving by the `keys` vars relative to the camera; `camera` draws the scene from the instance -- `fixed`, `follow` (an offset from a target), `orbit` and `first` (the mouse looks), or `rail`; `seek` walks toward a target and raises `arrived`; `animator` plays a clip per state and raises `animationDone`; `target` gives a `gun` something to hit, with `zones` on named bones so a shot says which part it landed on (`hitPart`); `health` with `ragdoll` lets a model fall limp on death; `solid`, `body`, and `trigger` are the physics. A gun in the scene casts a ray through the pointer, so the same `hit` and `miss` rules serve a light gun over video and a rail shooter. `testScripts/author/railshooter.game` is House of the Dead in a table: a rail with two stops, zombies that spawn there, walk to the camera, and bite on a timer unless they are killed, headshots through a zone on the head bone, and the rail moving on when the stop is clear. === Adventures: rooms, walking, hotspots, verbs, and talk A point-and-click adventure -- Sierra's or LucasArts' -- is rooms that remember themselves, a character that walks where the player clicks, hotspots a verb is used on, an inventory, dialogue, and cut-scenes. All of it is the same nouns. *Walking.* A 2D room declares `walk = { { x, y, x, y, ... }, ... }`, its walkable floor as polygons in overlay coordinates; a 3D room declares `navFrom = { "floor", "ledge" }`, the entities whose meshes are its floor. Either is baked into a navigation mesh when the room is entered, and a type with a `walker` behaviour walks it: `walkTo` sends it to a point (and waits), `walkToPointer` to where the player clicked, `walkToHotspot` to a hotspot's walk point; it raises `arrived`, and its state is `walk` or `idle` as it goes. A painted room may say `depthSort = true`, so what is lower on the picture is drawn over what is higher, and `scaleBy = { { y = 300, scale = 0.6 }, { y = 460, scale = 1 } }`, so a character further up the picture is drawn smaller; looks with `anchor = "feet"` stand on their position rather than being centred on it, which is what a walking character wants. *3D over a painting.* A room in the scene may be a picture on a flat `mesh` (its `texture`, `unlit`) with 3D characters in front of it. Whatever stands in the picture -- a pillar, a doorway, a table -- that a character has to walk behind is a `mesh` look with `occluder = true`: an invisible box that hides what is behind it while the picture shows through it. The editor's 3D viewport keeps occluders visible so they can be placed; the game hides them. `testScripts/author/painted.game` and scene 79 show a character crossing a painted room behind such a pillar. *Hotspots.* A type with `{ kind = "hotspot", name = "door", walkX = 600, walkY = 330 }` is something a verb can be used on: its look's box, or a `polygon` of x,y pairs, in 2D; a box the size of its look, met by the ray through the pointer, in 3D. The name is what the sentence line shows. *Verbs.* A game with `verbs = { "walk", "look", "take", "use", "open", "talk" }` builds a sentence line from the current verb, the hotspot under the pointer, and the item in hand (`sentence`, `hover`, `verb`, and `item` are game vars, so a text look or a HUD element can show them). A click on a hotspot raises `verb` with `verb`, `target`, and `item`, and **the most specific rule wins**: `verb = "use", item = "key", target = "door"` beats `verb = "use", target = "door"` beats `verb = "use"` beats a rule that names nothing, which is the "That doesn't work" every adventure needs. A rule with conditions ranks above one without among the same parameters, so "open the door while the guard objects" and "open the door" can share their parameters and differ in a `test`. `setVerb`, `nextVerb`, and `useItem` choose; the first verb in the list is what a click on the floor means, and the `pressed` event carries the pointer's `x` and `y` for a `walkToPointer` rule. *The parser.* A `parser` layer takes typing at a prompt: `words = { look = { "look", "examine", "l" }, door = { "door", "gate" } }` says what the game knows, and a typed line raises `said` with its `verb`, `noun`, and `second` as the words table knows them -- or `event.unknown`, the first word it did not -- with the same most-specific-wins rule. A game may offer both a verb line and a parser; the rules do not care which was used. *Inventory.* `give` and `take` keep a list in the `inventory` var; `has("key")` tests it in an expression and `has` as a condition. *Dialogue.* The game's `dialogues` are trees: [source,lua] ---- dialogues = { guard = { start = "hello", nodes = { hello = { who = "Guard", text = "Nobody passes.", choices = { { text = "Why not?", next = "why" }, { text = "Fine." } } }, why = { who = "Guard", text = "The door is locked.", choices = { { text = "I have a key.", when = 'has("key")', act = { { "setVar", name = "allowed", value = true } }, next = "ok" }, { text = "I see." } } }, ok = { who = "Guard", text = "Then go ahead." } } } } ---- `talk` runs one and waits for it: each node's line is said, its choices (those whose `when` holds, and not those already chosen once with `once`) are offered and picked with the number keys or a click, the choice's `act` runs, and `next` names the node after. With a HUD, the choices go in the element called `choices`. *Cut-scenes and rooms.* A rule with `controls = false` takes the keys away while it runs; `fade` (out, then back with `out = false`) and `goTo` change the picture and the room, and `goTo` with `at = "arrival"` puts the carried entity where the entity of that id stands in the new room, so the same rule serves a painted room and a modelled one. Sequences survive a room change. *Saving.* `saveGame` keeps the whole game -- the room, every visited room's instances and their vars, the game's vars, the inventory -- in a numbered slot, and `loadGame` brings it back. A save taken while a sequence runs records the world as it stands; the sequence does not resume. `die` is the Sierra death: a line, a fade, and the game over from the start. `testScripts/author/adventure2d.game` is the two-room adventure in a painted room, and `adventure3d.game` is the same game in a modelled room from a fixed camera -- it takes the 2D description's rules, dialogue, verbs, and parser as they are and supplies only types and rooms. Scenes 64 and 65 play them. === Vehicles, crowds, turrets, and physics toys `vehicle` puts a type on the engine's vehicle physics -- a car, a motorcycle, a tank, or a boat -- with wheels hung at the corners its look's size gives, driven by the `keys` vars (`dy` throttle, up is forward; `dx` steering), and `self.speed` says how fast. `racer` drives a vehicle round a track of `points` on its own, steering toward the next and easing off in bends, which is every opponent in a racing game. `camera` in `follow` mode with a `lag` is what sits behind the car. `walker` with `follow = "hero"` keeps an instance after an entity (or the camera) across the navigation mesh, asking again every so often -- a crowd of enemies in a shooter, a companion. `patrol` walks a list of points in turn, looping or stopping, and raises `patrolEnd` at the last: a creep's lane, a guard's round. `turret` fires at the nearest instance of a type within range, no faster than its rate, spawning a projectile aimed at it (or doing the damage itself), and `spawnAtPointer` makes an instance where the player clicked -- on the floor the ray meets, in 3D -- which is how turrets are placed. `body` is a thing physics moves (mass, bounce, friction); `thrust` pushes a body along its nose by `dy` and turns it by `dx` (a hovercraft, a ship); `joint` hangs a body from another's, or from the world, by a hinge, a ball, or a slider through a point along an axis; and `push` shoves a body, or a ragdoll's bone. `testScripts/author/racing.game` (a car, a rival on a track, checkpoint triggers, a hinged bar, a hovercraft) and `towerdefence.game` (creeps in waves on a lane, turrets placed by clicks, gold and lives) are the worked examples; scenes 66 and 67 play them. === Arcade plumbing: branching video, lives, credits, the board A `branches` track is Dragon's Lair: each branch is a window of disc frames, the `move` (a key) or `switch` it wants, and the frames the disc goes to on `success` and on `fail`; a type with `{ kind = "branching", track = "moves" }` watches it and raises `branchOpen` (with `event.move`), `branchTaken`, and `branchMissed`. Lives, credits, and continues are vars and rules -- `credit` on `SWITCH_COIN1`, a `restart` on `SWITCH_START1` when `credits > 0` -- and a `bezel` layer shows `score`, `lives`, and `credits` (and a second player's `score2` and `lives2` with `twin`) on the arcade panel around the picture. `gameOver` ends the rules, keeps the best score across runs, and shows the results card; `submitScore` sends the score to the master service's board for the game, queued until it can go. `testScripts/author/fmv.game` is the worked example and scene 68 plays it: one window answered, three missed, the lives spent, the score sent, a coin, and a continue. === The long tail: sprite sheets, tiles, MIDI, water, soft bodies `distance(a, b)` takes instances or ids (`distance(self, "hero")`) and measures through the scene in 3D. A rule with `each` on an event that has no instance of its own -- a key, a room, the disc -- runs once per instance of the type, as a frame rule does, which is how "a swing of the sword hits every enemy in reach" is one rule. A `solid` with `moving = true` is a body that follows wherever the instance is moved: a paddle, a lift. A `sprite` look turns with the var `angle` (degrees, clockwise), which an entity's `rz` starts and which rules, a `spin` behaviour, or a `projectile` with `aim` change -- a turning image is a whole one, not a sheet. `spin` turns a 3D instance about Y the same way. A `particles` look is a steady stream from the instance -- a fire, smoke, a thruster, rain -- with a colour, a rate, a size, a speed, and a life, and the `emit` action is a burst from any instance. A `sprite` look with `frames` is a sheet of that many columns, and a `frames` behaviour steps it: `states = "idle=1-1, walk=2-5"` gives each state its run of frames, played at `fps`, so a walker's walk cycle follows the state the `walker` (or a rule) sets. Art need only face one way: the look's `faces` says which (`right` unless said), and while the var `facing` -- which the `walker` and the `mover` set as they go -- is the other way, the image is mirrored. A sheet drawn facing both ways names the other way's frames for the state with `Left` or `Right` on the end (`walkLeft=6-9`), and those are used instead of the mirror; `faces = "none"` never mirrors, for a top-down sprite that turns rather than flips. A sheet entity with an `rz` (or a var `angle`) draws its frame turned. A `grid` look draws a map of tiles from a sheet -- `columns` tiles wide, each `tile` pixels square -- from rows of tile numbers (`map = "1 2 1; 2 1 2"`, `0` for nothing), which is a maze or a level in a string. A game that opened a MIDI port hears `midi` events with `event.pitch` and `event.velocity` for every note struck. In the scene, a mesh look with `water` is filled with water: its top is the surface, and a `body` with `buoyancy` floats in it; a `vehicle` of type `boat` with `thrust` is pushed by its propeller while that sits under the surface; and `soft` makes a mesh look a cloth or an inflated body. `testScripts/author/tail.game` and `pool.game` are the worked examples; scenes 70 and 71 play them. === The way out The `lua` action takes a line of Lua and emits it as it stands. It is there on purpose: when a rule needs something the vocabulary cannot say, that rule drops to Lua and the rest of the game is unaffected. A description is a convenience, not a cage, and the compiled output is a normal game you can stop describing and start editing whenever it suits you. == The Vocabulary Conditions, actions, behaviours, looks, layers, and events are not built into the compiler. Each is an entry in the `AUTHOR` table in `Author.singe` declaring its parameters and the Lua it emits, so a new kind of game is a set of entries rather than a new release. The tables below are generated from that manifest. The parameter types: `number` (a number or an expression), `expression`, `string`, `boolean`, `entity` (`"self"`, `"other"`, or an entity's id), `type`, `scancode` (a key name from `SCANCODE`), `switch` (a `SWITCH_*` name), `file`, `state`, `track`, `room`, and `lua`. A behaviour's parameters are plain values in the description; key and switch names among them are resolved when the game runs. An action or condition may also carry `defaults`, the value a parameter takes when the description leaves it out (a `shake` of `0.3` for `0.4` seconds), and `optional`, the parameters that mean something by their absence (a `goTo` with no `x`); a parameter with neither, left empty, is reported by the checker as *empty* and compiles to `nil`. include::ForgeVocabulary.adoc[] == The Editor `Forge/Forge.singe` edits a description, and it is itself a Singe game. It has its own directory beside the games, appears in the menu like one, and packs to `Forge.game`; the build does that itself (the `forge` target), with this manual inside. Nothing in it is a preview: the canvas is the same overlay at the same coordinates the game will be played in, so what is placed is what is seen. Started from the menu it opens on a chooser: the descriptions in its data directory, the samples inside `Forge.game` and any dropped into the `Forge` directory itself (those are opened as a copy, since inside a `.game` they are read only), and *New game*, which writes a starter -- ground, a hero that runs and jumps, a score readout -- and opens it. The first run copies this manual, `Forge.pdf`, out of `Forge.game` into that data directory, and the chooser says where it is. `Esc` in the editor closes the description (twice, when it has unsaved changes) and `Esc` on the chooser leaves Forge. `P` plays: the description is saved, compiled beside a copy of the runtime, and handed to the engine with `scriptPush`; when the game ends Forge comes back on the same file. A script can drive the editor instead, which is how the test scenes do it: [source,lua] ---- FORGE_LIBRARY = true dofile("Forge/Forge.singe") forgeBegin("mygame.game") function onOverlayUpdate() local x, y = mouseGetPosition(0) forgeDraw(x, y) return OVERLAY_UPDATED end ---- The panel is an RmlUi document; the canvas beside it is drawn into the overlay and picked with `collidePointRect`. The two compose because the engine offers a button to the GUI first and passes on what it did not use, while pointer motion is never consumed at all -- so point `forgePress`, `forgeDrag`, and `forgeRelease` at the mouse callbacks and clicks on the panel will not reach the canvas. The panel slides: drag the tab on its outer edge, or use the arrow keys, so an entity that lives underneath it is never permanently out of reach. === The four panels `Tab` cycles the panel through the room's *entities*, the *types*, the *rules*, the room's *tracks*, and the *dialogues*; the digits `1` to `5` go straight to one. Whatever is selected is edited the same way: `Enter` walks its fields and each is typed; `Enter` again moves to the next, keeping what was typed and leaving alone a field passed over with nothing typed; `Esc` puts the value back and leaves the form; `Delete` while typing empties the field, which for an action's `entity` means the game itself. A typed number comes back a number, because `100` and `"100"` compile to different source. The last line of the box under the list is Forge's message: what just happened. A value is typed in its own row: it starts selected whole, so typing replaces it, and kbd:[Left], kbd:[Right], kbd:[Home], and kbd:[End] put a caret into it to edit it instead; kbd:[Backspace] takes out the character before the caret, or the whole selected value, and kbd:[Delete] the character after it -- or, on the value still selected whole, empties the field for good. Shifted keys type what they say -- a capital, a quote. The game's name at the top of the panel, or a press on an empty spot of the canvas, selects nothing, which is the game's and the room's own form. The panel's list and its fields box each scroll, with a bar and the wheel, when there is more than fits, and the chosen row and the field being edited are kept in view. The mouse does the same work: a click on a row of the list selects what it names -- an entity, a type, a rule or one of its parts, a track or a key, a dialogue's node or choice -- a click on a field in the box below starts typing that field, and a click on a picker's row takes it. *Entities*: `A` adds one of the selected type in the middle of the canvas, `D` duplicates the selected one, `Delete` removes it, and dragging moves it. Its fields are `id`, `type`, `x`, `y`, `z`, `scale` -- and in a 3D room `rx`, `ry`, `rz`, its turn about each axis in degrees -- and each var its type declares, overridable here. A scale scales the look, the body, and how big the instance counts as for touching. Renaming an entity renames it in every rule that talks about it. In a 3D room `A` puts the new entity on the floor under the middle of the viewport, and positions are world units to a tenth. `PgUp` and `PgDn` move between rooms; `N` adds a room. With nothing selected, `Enter` edits the game and then the room: the game's `title`, `players`, `vars` (as `score=0, lives=3`), `verbs`, and `layers` (a list of names, as `disc, overlay`) with each layer's own parameters as `disc.file` or `scene3d.fov` -- a table parameter is typed as text: a map such as `scene3d.ambient` as `r=80, g=80, b=100`, a list of points such as `spawner.points` as `100,-20; 300,-20`, a list of records such as `target.zones` as `name=head, bone=b_Head_05, w=0.5, h=0.5, d=0.5; name=body, bone=b_Spine, w=0.8` (one record per semicolon), a map of lists such as `parser.words` as `look=look|examine|l, take=take|get`, and a list of words such as `parser.ignore` as `the; a; an` -- then the room's `name`, `reset`, `depthSort`, `walk` areas (polygons of x,y pairs, separated by semicolons, drawn on the canvas), `navFrom`, and `scaleBy` (as `300:0.6, 460:1`). Typing a layer, or a behaviour, away and back keeps its values. A field whose values are a fixed set opens a *dropdown* under its own row instead of a prompt: a file (a look's image or model, a sound, a HUD document, a layer's disc) lists the files under the game's directory and in the kit (`Forge/kit/`); a flag lists `true` and `false`; a key lists the scancodes and a switch the switches; a type's `look` lists the looks and a rule's `on` the events, each with its line of help; a word parameter lists the words it takes, as the vocabulary tables say (`anchor` is `feet`, a camera's `mode` is `fixed`, `follow`, `first`, `orbit`, or `rail`); and a type, a room, an entity, a state, a track, or a dialogue's node lists the ones the game has. The list opens on the value the field has (a value the list does not know is offered as it is), or on `(none)` when it has none, so `Enter` straight away keeps things as they are: `Enter` on a row takes it and moves on to the next field, as `Enter` on a typed value does, and a click takes it and stays. `(none)` leaves the field empty -- the default, or the game itself for an action's entity. Typing narrows the list to the names that contain what was typed -- an exact name first, then names that begin with it -- and `Backspace` widens it again; a long list shows a window of rows around the chosen one, and says how many lie past it. A dropdown opens upward when there is no room below its row. The pickers that add a condition, an action, or an event (`C`, `T`, `E`) take the panel's list over instead, since the whole manifest is being chosen from. Where the game's own names are only suggestions -- a rule may name a type, a room, or an entity that is made later, and a state can be any word -- what was typed is offered at the top of the list, so `Enter` takes a new word as readily as a known one. `Esc` on a picker leaves the field to be typed. *Types*: `A` adds one, `D` duplicates, `Delete` removes one no room still places. Its fields are `name`, `look` and what that look takes (`look.w`, `look.file`, ...), `vars` typed as `health=3, ammo=6`, `behaviours` typed as a list of names (`keys, mover, shooter`), and each behaviour's own parameters as `mover.speed` and so on. The fields come from the manifest, so a new look or behaviour is editable the moment it is declared; renaming a type renames it in every entity and rule. In a 2D room, `V` draws a polygon corner by corner on the canvas: with the selected entity's type carrying a `hotspot` behaviour it becomes that hotspot's outline, otherwise it is added to the room's walk areas. The panel steps aside while the corners are pressed. `V` or `Enter` closes it, `Esc` drops it. *Rules*: the selected rule opens in place with its conditions and actions under it, because a rule only means anything whole. `N` adds a rule, `E` picks what it is on (the event's own parameters then appear as fields on the rule), `C` adds a condition, `O` a condition in the rule's `any` group, `T` an action -- each picked from the vocabulary with its help beside it -- and `[` and `]` move the rule up and down the sheet, since rules run in order. `Delete` removes the selected condition or action, or the rule itself. *Tracks*: the room's tracks and their keys. The canvas shows the frame of the disc layer's video under the cursor and every track's box there; `,` and `.` step the cursor a frame, `[` and `]` leap ten. `N` adds a track, `K` a key at the cursor -- taking the box interpolated there, or the nearest key's -- and a key is moved by dragging its box on the canvas or by typing. `Delete` removes the selected key, or the track. In a 3D room a new track is a rail of points, drawn through the scene with its stops marked, and `K` puts a point where the editor camera stands, looking where it looks: stand where the player should and press it. `W` adds a *waves* track, whose keys are spawns: `at`, the `type` made, `count` of them, `every` so many seconds apart, and where -- `from`, an entity or a comma-separated list of entities taken in turn, or `x`, `y`, `z`. A waves track keyed by `stop` instead of `frame` or `time` ties its waves to a rail camera's stops: `at` names the stop. A wave begins once per visit to the room, and each instance it makes raises `spawn` as any other. *Dialogues*: an outline of every dialogue, the selected one's nodes under it, the selected node's choices under that, and a choice's actions under that. `A` adds at the level selected -- a dialogue, a node in it, a choice in the node -- `T` adds an action to the selected choice from the vocabulary, and `Delete` removes what is selected. A dialogue's `name` and `start`, a node's `name`, `who`, `text`, `seconds`, and `next`, and a choice's `text`, `when`, `next`, and `once` are typed like everything else. `P` plays from the room on screen: the built game begins in it, with the other rooms following in order. `X` releases the game into a folder named for it in Forge's data directory, with everything it needs (see <<_releasing_a_game>>). On the 2D canvas the selected entity carries the yellow turn handle alone: an arm the way its `rz` points, past its box, dragged round the entity to turn it, and a sheet entity shows its first frame turned the same way. In the types panel each type's picture stands beside its name -- the sprite itself (one frame of a sheet), `Aa` for a text, or a swatch of the box's colour -- so a type can be told from its neighbours without opening it. `testScripts/scene82.singe` drives both. === The 3D viewport A room in a game with a `scene3d` layer is shown as the scene itself, built from the types' looks exactly as the runtime builds them, under an editor camera that orbits whatever is selected: `J` and `L` orbit, `Y` and `H` tilt, `I` and `K` close in and back off. Nothing steps -- no behaviours, no physics -- so it is the description that is on screen, not a running game. An entity is picked by where it projects and dragged across the floor it stands on, and the selected one carries a gizmo: three arms along the world axes, red for X, green for Y, blue for Z, each ending in a handle that drags the entity along that axis alone -- the pointer's travel along the arm, as a fraction of the arm, is the distance moved -- and a fourth, yellow, the way the entity faces, whose handle dragged round the entity turns it about Y. `testScripts/scene63.singe` and `scene80` drive it. === Keys [cols="1,4"] |=== | `Tab`, `1` to `5` | entities, types, rules, tracks, dialogues | Up, Down | move through the list, and through the parts of the open rule or track | Left, Right | slide the panel; while typing, move the caret (with `Home` and `End`) | `PgUp`, `PgDn` | the previous or next room | `Enter` | type a value for whatever is selected; again for its next value | `Delete` | while typing, take out the character after the caret, or empty the field while its value is still selected whole | `Esc` | put the value back and leave the form; otherwise close the description | `A`, `D`, `Delete` | add, duplicate, delete an entity or a type | `N` | a new rule, a new track, or (in the entities) a new room | `W` | a new waves track (in the tracks) | `E` | pick what the rule is on | `C`, `O`, `T` | add a condition, an any-of condition, an action | `[`, `]` | move the rule up or down; in the tracks, leap the cursor | `,`, `.` | step the cursor | `V` | draw a walk area, or the selected hotspot's outline, corner by corner | `K` | a key at the cursor; in a 3D room, a rail point where the camera stands | `J`, `L`, `Y`, `H`, `I`, `K` | in a 3D room: orbit, tilt, and zoom the editor camera | `U`, `R` | undo and redo, forty steps deep; a drag or a typed form is one step, and a change by hand ends the redo history | `S`, `B`, `P`, `X` | save, build, play, release |=== From a script the same work is `forgeEntityAdd`, `forgeEntityDuplicate`, `forgeEntityDelete`, `forgeEntitySet`, `forgeTypeAdd`, `forgeTypeSet`, `forgeRuleNew`, `forgeRuleOn`, `forgeRuleAdd("when"|"any"|"act", name)`, `forgePartSet`, `forgePartDelete`, `forgeRuleMove`, `forgeTrackNew`, `forgeKeyAdd`, `forgeCursorTo`, `forgeUndo`, and `forgeRedo`. A description survives the round trip: loading one, saving it, and loading it again compiles to the same game, byte for byte. The editor depends on that and `testScripts/scene54.singe` asserts it. === Releasing a game `X` in the editor, or `forgeExport(folder, name)` from a script, writes everything a finished game needs into a directory of its own: the compiled script, a `games.dat` so the menu lists it (its names begin with the folder's own, `CoinRun/CoinRun.singe`, which is how both the menu and a packed copy address them), the description it was built from so it can be opened again, **a copy of the runtime**, taken out of Forge, and **every file the description names** -- looks, sounds, music, the disc, models, a HUD document -- at the same relative name it had, so the kit's `Forge/kit/hero.png` lands in `Forge/kit/` under the release. The engine's own files (`Singe/...`) and absolute paths are left where they are. The compiled script notes its own directory in `AUTHOR_DIR`, and the runtime looks for every named file there first, so the release runs wherever it is put. `--pack` turns that directory into a `.game` like any other, and it runs on a machine that has never had Forge on it. Every built game finds its own directory to load that runtime from, using `debug.getinfo` rather than `DIR`: `DIR` is the directory of the script the engine was *launched* with, so a game reached by `dofile` -- a test, a launcher, a preview -- would otherwise look beside the caller. `forgeBuild(path)` compiles what is on screen (and puts the runtime beside it, so the result plays); what `authorCheck` has to say about the description -- a type that is not declared, an expression that does not parse, a field a look, a behaviour, or an event does not take (a misspelling, nine times in ten) -- goes to the console and the panel.