More Forge work.

This commit is contained in:
Scott Duensing 2026-09-14 22:32:53 -05:00
parent 2819449810
commit 79e5fce23e
158 changed files with 8509 additions and 883 deletions

View file

@ -7,6 +7,61 @@ SINGE 3.00
API Changes API Changes
----------- -----------
- singeScreenshot takes an optional file name, so a script that makes
pictures on purpose can name them.
- guiDraw's place in a script's drawing now decides what is over the GUI:
what is drawn on the overlay after the call goes on a layer composited
over that document, so a gun's sight or a pointer drawn after the HUD
shows over it. Before, every GUI was drawn over the whole overlay.
- onTextInput, in MODE_FULL: the text a key press produced, shifted and
laid out as typed, since a keysym is the unshifted key and a script
reading keysyms could never type a capital or a quote.
- In MODE_FULL a pad or mouse button reaches the script as the switch it
is mapped to, as the manual always said; it arrived as a keysym of 0.
- guiSetHandler set again after a document's elements were rebuilt (a
list through inner_rml) now attaches to the new element with that id.
It kept the old listener, on an element that no longer existed, so a
rebuilt list's rows stopped answering clicks.
- TAB reaches a script even when a GUI document is up: RmlUi took it for
moving focus and never handed it back, so a game whose keys include
TAB lost it. It goes to the GUI only while a text field is being typed
in.
- Forge has tutorials: ten sittings at the front of Forge.pdf, each
ending with a game that plays, with the kit of pictures, sounds, a clip,
and a model they use packed in Forge.game, and the finished description
of each in the chooser. Each tutorial's scene presses the keys the text
names and checks the result, so the book cannot drift from the editor.
On the way the editor gained the game's own form (title, players, vars,
verbs, layers, and each layer's parameters), the digits as panel keys,
DELETE to empty a field, X to release a game with every file it names,
a file picker that keeps an unset field unset, and a panel that steps
aside while a polygon is drawn. What an entity is made from is now a
type, not a kind: types = { hero = ... } in a description, type = "hero"
on an entity and in an event's filter, and the Types panel. Forge's
launcher takes every key (MODE_FULL), so ENTER and TAB reach it, and
its panel's rows and fields take clicks. A value is edited in place:
it starts selected, so typing replaces it, and the arrows put a caret
into it. A field whose values are a fixed set opens a dropdown of them
-- true and false, the scancodes, the switches, the looks, the events,
the words a parameter takes, the game's own types, rooms, entities,
states, tracks, and nodes -- opened on the value it has, under its own
row, upward when there is no room below. The panel's list and fields
box scroll, with bars, and keep the chosen row in view. The game's
name at the top of the panel, or a press on empty canvas, brings the
game's own form back. A waves track (W in the tracks) spawns a type
by count and interval at a moment, a disc frame, or a rail camera's
stop, from an entity or a list of them in turn, or at a point; and
soundDone says when a clip playSound or a sound behaviour started has
ended. Flat
bodies are the size their looks are drawn at (they were half), and a
platformer stands on its point.
- spriteFlip mirrors a sprite left to right, top to bottom, or both, for - spriteFlip mirrors a sprite left to right, top to bottom, or both, for
every draw of it, its frames included, so art that faces one way is every draw of it, its frames included, so art that faces one way is
enough for a character that walks both ways. enough for a character that walks both ways.

View file

@ -309,11 +309,14 @@ if(LUA_INTERPRETER)
COMMENT "Writing the Forge vocabulary" COMMENT "Writing the Forge vocabulary"
) )
endif() endif()
# The book embeds the tutorials' pictures and includes their shipped descriptions, so any of
# those changing renders it again; the globs are re-run on every build.
file(GLOB_RECURSE FORGE_BOOK_PARTS CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/docs/images/tutorials/* ${CMAKE_SOURCE_DIR}/assets/Forge/tutorials/*.game)
add_custom_command( add_custom_command(
OUTPUT ${MANUAL_DIR}/Forge.pdf OUTPUT ${MANUAL_DIR}/Forge.pdf
COMMAND ${CMAKE_COMMAND} -E make_directory ${MANUAL_DIR} COMMAND ${CMAKE_COMMAND} -E make_directory ${MANUAL_DIR}
COMMAND ${ASCIIDOCTOR_PDF} -a revnumber=${PROJECT_VERSION} ${CMAKE_SOURCE_DIR}/docs/Forge.adoc -o ${MANUAL_DIR}/Forge.pdf COMMAND ${ASCIIDOCTOR_PDF} -a revnumber=${PROJECT_VERSION} ${CMAKE_SOURCE_DIR}/docs/Forge.adoc -o ${MANUAL_DIR}/Forge.pdf
DEPENDS docs/Forge.adoc docs/ForgeVocabulary.adoc DEPENDS docs/Forge.adoc docs/ForgeVocabulary.adoc ${FORGE_BOOK_PARTS}
COMMENT "Rendering Forge.pdf" COMMENT "Rendering Forge.pdf"
VERBATIM VERBATIM
) )

File diff suppressed because it is too large Load diff

View file

@ -25,7 +25,7 @@
-- The compiler: a game description to a Singe game (FORGE.md). -- The compiler: a game description to a Singe game (FORGE.md).
-- --
-- A description is data -- kinds, rooms, and rules -- and what comes out is a Lua script that -- A description is data -- types, rooms, and rules -- and what comes out is a Lua script that
-- calls the runtime in Author.singe. The rules become real functions: a condition is a Lua -- calls the runtime in Author.singe. The rules become real functions: a condition is a Lua
-- expression, an action a statement, and a rule with an action that takes time a coroutine. The -- expression, an action a statement, and a rule with an action that takes time a coroutine. The
-- description travels with the game so it can be opened again; nothing is lost in either -- description travels with the game so it can be opened again; nothing is lost in either
@ -80,6 +80,19 @@ local OPERATORS = {
} }
-- The keys of a table in name order, so anything written from one is written the same way each time.
local function sortedKeys(t)
local keys = {}
for key in pairs(t or {}) do
keys[#keys + 1] = key
end
table.sort(keys, function(a, b) return tostring(a) < tostring(b) end)
return keys
end
local function tokenize(text) local function tokenize(text)
local tokens = {} local tokens = {}
local at = 1 local at = 1
@ -348,11 +361,18 @@ local function fragment(kind, value)
end end
-- A part's parameters as Lua. One the author left empty takes the manifest's default for it,
-- and one with neither becomes nil, which the checker has already pointed out.
local function fragments(entry, item) local function fragments(entry, item)
local out = {} local out = {}
for key, kind in pairs(entry.params or {}) do for key, kind in pairs(entry.params or {}) do
out[key] = fragment(kind, item[key]) local value = item[key]
if (value == nil) and entry.defaults then
value = entry.defaults[key]
end
out[key] = fragment(kind, value) or "nil"
end end
return out return out
@ -472,7 +492,10 @@ end
-- actions, compiled as a rule's are, with self and other absent. -- actions, compiled as a rule's are, with self and other absent.
local function emitDialogue(out, name, dialogue) local function emitDialogue(out, name, dialogue)
out[#out + 1] = INDENT .. string.format("[%q] = { start = %q, nodes = {", name, dialogue.start or "start") out[#out + 1] = INDENT .. string.format("[%q] = { start = %q, nodes = {", name, dialogue.start or "start")
for nodeName, node in pairs(dialogue.nodes or {}) do -- Nodes in name order, so the same description compiles to the same text every time.
for _, nodeName in ipairs(sortedKeys(dialogue.nodes or {})) do
local node = dialogue.nodes[nodeName]
out[#out + 1] = INDENT .. INDENT .. string.format("[%q] = { name = %q, who = %s, text = %s, seconds = %s, next = %s, choices = {", out[#out + 1] = INDENT .. INDENT .. string.format("[%q] = { name = %q, who = %s, text = %s, seconds = %s, next = %s, choices = {",
nodeName, nodeName, nodeName, nodeName,
node.who and string.format("%q", node.who) or "nil", node.who and string.format("%q", node.who) or "nil",
@ -632,13 +655,13 @@ end
-- ===== Checking =============================================================================== -- ===== Checking ===============================================================================
-- --
-- What the compiler can tell an author before the game runs: a kind that is not declared, a -- What the compiler can tell an author before the game runs: a type that is not declared, a
-- condition the manifest does not have, an expression that does not parse. Returns a list of -- condition the manifest does not have, an expression that does not parse. Returns a list of
-- messages, empty when the description is sound. -- messages, empty when the description is sound.
function authorCheck(game) function authorCheck(game)
local problems = {} local problems = {}
local kinds = game.kinds or {} local types = game.types or {}
local function problem(text) local function problem(text)
problems[#problems + 1] = text problems[#problems + 1] = text
@ -658,8 +681,10 @@ function authorCheck(game)
if not ok then if not ok then
problem(where .. ": " .. tostring(err)) problem(where .. ": " .. tostring(err))
end end
elseif (kind == "kind") and (item[key] ~= nil) and (kinds[item[key]] == nil) then elseif (kind == "type") and (item[key] ~= nil) and (types[item[key]] == nil) then
problem(where .. ": no kind called '" .. tostring(item[key]) .. "'") problem(where .. ": no type called '" .. tostring(item[key]) .. "'")
elseif (item[key] == nil) and (kind ~= "entity") and ((entry.defaults or {})[key] == nil) and not (entry.optional or {})[key] then
problem(where .. ": " .. key .. " is empty")
end end
end end
end end
@ -676,32 +701,47 @@ function authorCheck(game)
end end
end end
for name, kind in pairs(kinds) do for name, entityType in pairs(types) do
local look = kind.look and AUTHOR.looks[kind.look.kind] or nil local look = entityType.look and AUTHOR.looks[entityType.look.kind] or nil
if kind.look and (look == nil) then if entityType.look and (look == nil) then
problem("kind " .. name .. ": no look called '" .. tostring(kind.look.kind) .. "'") problem("type " .. name .. ": no look called '" .. tostring(entityType.look.kind) .. "'")
elseif look then elseif look then
checkFields(kind.look, look.params, { kind = true }, "kind " .. name .. "'s look") checkFields(entityType.look, look.params, { kind = true }, "type " .. name .. "'s look")
end end
for _, b in ipairs(kind.behaviours or {}) do for _, b in ipairs(entityType.behaviours or {}) do
local behaviour = AUTHOR.behaviours[b.kind] local behaviour = AUTHOR.behaviours[b.kind]
if behaviour == nil then if behaviour == nil then
problem("kind " .. name .. ": no behaviour called '" .. tostring(b.kind) .. "'") problem("type " .. name .. ": no behaviour called '" .. tostring(b.kind) .. "'")
else else
checkFields(b, behaviour.params, { kind = true }, "kind " .. name .. "'s " .. b.kind) checkFields(b, behaviour.params, { kind = true }, "type " .. name .. "'s " .. b.kind)
end end
end end
checkFields(kind, { look = true, vars = true, behaviours = true }, {}, "kind " .. name) checkFields(entityType, { look = true, vars = true, behaviours = true }, {}, "type " .. name)
end end
if #(game.rooms or {}) == 0 then if #(game.rooms or {}) == 0 then
problem("the game has no rooms") problem("the game has no rooms")
end end
for _, room in ipairs(game.rooms or {}) do for _, room in ipairs(game.rooms or {}) do
for _, entry in ipairs(room.entities or {}) do for _, entry in ipairs(room.entities or {}) do
if kinds[entry.kind] == nil then if types[entry.type] == nil then
problem("room " .. tostring(room.name) .. ": no kind called '" .. tostring(entry.kind) .. "'") problem("room " .. tostring(room.name) .. ": no type called '" .. tostring(entry.type) .. "'")
end
end
-- A waves track's keys name a type, and are at a moment -- or at a stop, when keyed by one.
for _, track in ipairs(room.tracks or {}) do
for index, key in ipairs(track.spawns or {}) do
local where = "room " .. tostring(room.name) .. ", track " .. tostring(track.name) .. ", wave " .. index
if types[key.type] == nil then
problem(where .. ": no type called '" .. tostring(key.type) .. "'")
end
if (track.key == "stop") and (type(key.at) ~= "string") then
problem(where .. ": at should name a stop")
elseif (track.key ~= "stop") and (type(key.at) ~= "number") then
problem(where .. ": at should be a number")
end
end end
end end
end end
@ -713,8 +753,8 @@ function authorCheck(game)
elseif AUTHOR.events[rule.on or "frame"] then elseif AUTHOR.events[rule.on or "frame"] then
checkFields(rule, AUTHOR.events[rule.on or "frame"].filter, { note = true, on = true, each = true, room = true, when = true, act = true, controls = true, interrupt = true }, where) checkFields(rule, AUTHOR.events[rule.on or "frame"].filter, { note = true, on = true, each = true, room = true, when = true, act = true, controls = true, interrupt = true }, where)
end end
if rule.each and (kinds[rule.each] == nil) then if rule.each and (types[rule.each] == nil) then
problem(where .. ": no kind called '" .. tostring(rule.each) .. "'") problem(where .. ": no type called '" .. tostring(rule.each) .. "'")
end end
checkItems(AUTHOR.conditions, rule.when, "condition", where) checkItems(AUTHOR.conditions, rule.when, "condition", where)
checkItems(AUTHOR.conditions, rule.when and rule.when.any, "condition", where) checkItems(AUTHOR.conditions, rule.when and rule.when.any, "condition", where)
@ -755,6 +795,7 @@ function authorCompile(game)
-- chunk actually running, either way. -- chunk actually running, either way.
out[#out + 1] = 'local here = ((debug.getinfo(1, "S").source:gsub("^@", "")):match("^(.*[/\\\\])") or "")' out[#out + 1] = 'local here = ((debug.getinfo(1, "S").source:gsub("^@", "")):match("^(.*[/\\\\])") or "")'
out[#out + 1] = 'dofile(here .. "Author.singe")' out[#out + 1] = 'dofile(here .. "Author.singe")'
out[#out + 1] = "AUTHOR_DIR = here -- Files the description names are looked for here first."
out[#out + 1] = "" out[#out + 1] = ""
out[#out + 1] = "-- The description, as the runtime wants it: everything but the rules, which follow as code." out[#out + 1] = "-- The description, as the runtime wants it: everything but the rules, which follow as code."
out[#out + 1] = "authorBegin(" .. valueSource(data, 0) .. ")" out[#out + 1] = "authorBegin(" .. valueSource(data, 0) .. ")"
@ -770,21 +811,22 @@ function authorCompile(game)
if game.dialogues then if game.dialogues then
out[#out + 1] = "-- The dialogues: nodes of a line and choices, each choice's condition and actions as code." out[#out + 1] = "-- The dialogues: nodes of a line and choices, each choice's condition and actions as code."
out[#out + 1] = "authorDialogues({" out[#out + 1] = "authorDialogues({"
for name, dialogue in pairs(game.dialogues) do for _, name in ipairs(sortedKeys(game.dialogues)) do
emitDialogue(out, name, dialogue) emitDialogue(out, name, game.dialogues[name])
end end
out[#out + 1] = "})" out[#out + 1] = "})"
out[#out + 1] = "" out[#out + 1] = ""
end end
out[#out + 1] = "onKeyPressed = authorKeyDown" out[#out + 1] = "onKeyPressed = authorKeyDown"
out[#out + 1] = "onKeyReleased = authorKeyUp" out[#out + 1] = "onKeyReleased = authorKeyUp"
out[#out + 1] = "onInputPressed = authorSwitchDown" out[#out + 1] = "onInputPressed = authorSwitchDown"
out[#out + 1] = "onInputReleased = authorSwitchUp" out[#out + 1] = "onInputReleased = authorSwitchUp"
out[#out + 1] = "onMouseMoved = authorMouseMoved" out[#out + 1] = "onMouseMoved = authorMouseMoved"
out[#out + 1] = "onCollision = authorCollision" out[#out + 1] = "onCollision = authorCollision"
out[#out + 1] = "onTrigger = authorTrigger" out[#out + 1] = "onTrigger = authorTrigger"
out[#out + 1] = "onNavArrived = authorNavArrived" out[#out + 1] = "onNavArrived = authorNavArrived"
out[#out + 1] = "onMidiMessage = authorMidi" out[#out + 1] = "onMidiMessage = authorMidi"
out[#out + 1] = "onSoundCompleted = authorSoundDone"
out[#out + 1] = "" out[#out + 1] = ""
out[#out + 1] = "function onOverlayUpdate()" out[#out + 1] = "function onOverlayUpdate()"
out[#out + 1] = INDENT .. "authorFrame()" out[#out + 1] = INDENT .. "authorFrame()"

View file

@ -12,8 +12,21 @@
<title>forge</title> <title>forge</title>
<link type="text/rcss" href="Singe/gui.rcss"/> <link type="text/rcss" href="Singe/gui.rcss"/>
<style> <style>
body { pointer-events: none; font-family: FreeSans; font-size: 13px; color: #e6e6ee; } /* The body is the whole overlay, so the panel's 100% is a height: without it the panel
#panel { pointer-events: auto; position: absolute; left: 0px; top: 0px; width: 190px; height: 100%; background-color: #161620ff; } was nothing tall, everything in it overflow, and overflow: auto on it hid it all. */
body { pointer-events: none; width: 100%; height: 100%; font-family: FreeSans; font-size: 13px; color: #e6e6ee; }
/* Rows stay focusable: RmlUi delivers a click to the nearest focusable element under the
pointer, so a row that could not take focus could not be clicked either. ENTER and SPACE
on a focused element only become a click when its tab-index is auto, and no row's is. */
/* The panel is a column: the grip and the title, then the list and the fields box, which
share what is left and each scroll (the wheel, or the bar) rather than pushing the other
off the screen. */
#panel { pointer-events: auto; position: absolute; left: 0px; top: 0px; width: 190px; height: 100%; background-color: #161620ff; display: flex; flex-direction: column; }
#grip, #title { flex: 0 0 auto; }
#list { flex: 0 1 auto; min-height: 0px; overflow-y: auto; }
scrollbarvertical { width: 5px; }
scrollbarvertical slidertrack { background-color: #1b1b28ff; }
scrollbarvertical sliderbar { background-color: #6a5a9aff; }
#grip { pointer-events: auto; background-color: #2a2a3cff; padding: 6px 10px; color: #ffcf4a; font-size: 15px; } #grip { pointer-events: auto; background-color: #2a2a3cff; padding: 6px 10px; color: #ffcf4a; font-size: 15px; }
#grip:hover { background-color: #3a3a52ff; } #grip:hover { background-color: #3a3a52ff; }
#title { margin: 0px 10px 8px 10px; color: #9aa0b4; } #title { margin: 0px 10px 8px 10px; color: #9aa0b4; }
@ -24,8 +37,19 @@
.thumb.dim { color: #6a6a80; } .thumb.dim { color: #6a6a80; }
.thumb.swatch { height: 16px; width: 16px; margin-left: 4px; margin-right: 10px; } .thumb.swatch { height: 16px; width: 16px; margin-left: 4px; margin-right: 10px; }
.part { margin: 1px 8px 1px 18px; padding: 2px 5px; background-color: #1b1b28ff; color: #aeb4c8; font-size: 12px; } .part { margin: 1px 8px 1px 18px; padding: 2px 5px; background-color: #1b1b28ff; color: #aeb4c8; font-size: 12px; }
.part:hover { background-color: #2a2a40ff; color: #e6e6ee; }
.picked { background-color: #6a5a9aff; color: #ffffff; }
#title:hover { color: #ffcf4a; }
.part.here { background-color: #4a3a6aff; color: #ffffff; } .part.here { background-color: #4a3a6aff; color: #ffffff; }
#detail { margin: 14px 8px; padding: 6px; background-color: #10101aff; color: #b9bfd4; } /* A field's list, unfolded under its row and floating over the rows beneath, as a dropdown
does: absolute with no top or left takes the place it would have had in the flow. */
.drop { position: absolute; z-index: 2; clip: none; width: 140px; margin: 0px 0px 0px 26px; padding: 2px 0px; background-color: #0b0b12ff; border-width: 1px; border-color: #6a5a9aff; }
.choice { margin: 0px; padding: 2px 8px; color: #aeb4c8; font-size: 12px; }
.choice:hover { background-color: #32324aff; color: #e6e6ee; }
.choice.selected { background-color: #c03c96ff; color: #ffffff; }
.choice.more { color: #6a6a80; }
.choice.more:hover { background-color: #0b0b12ff; color: #6a6a80; }
#detail { flex: 0 1 auto; min-height: 0px; overflow-y: auto; margin: 14px 8px; padding: 6px; background-color: #10101aff; color: #b9bfd4; }
</style> </style>
</head> </head>
<body id="forge"> <body id="forge">

File diff suppressed because it is too large Load diff

8
assets/Forge/kit/LICENSE Normal file
View file

@ -0,0 +1,8 @@
The Forge tutorial kit.
Everything here except fox.glb was drawn or synthesised by util/forgeKit.py in the
Singe source tree and is released under CC0: use it in your own games as you like.
fox.glb is the Fox from the Khronos glTF sample models, CC-BY 4.0: model by PixelMannen,
rigging and animation by tomkranis, glTF conversion by AsoboStudio and scurest.
A game that ships it should carry this credit.

View file

@ -0,0 +1,11 @@
The Forge tutorial kit, addressed from a game as Forge/kit/<name>.
hero.png a sprite sheet, 8 columns of 32 x 48, facing right: idle, walk x4, jump, fall, hit
enemy.png a sheet, 4 columns of 32 x 32, flapping
tiles.png a tile sheet, 4 columns of 32 x 32: grass, dirt, stone, brick
coin.png key.png crate.png door.png spike.png ship.png shot.png rock.png target.png stills
yard.png hall.png painted rooms, 720 x 480, for the adventures
jump.wav coin.wav shot.wav hit.wav click.wav door.wav clips
loop.ogg eight seconds of music that loops
clip.mkv twelve seconds of 720 x 480 video at 24 frames a second: a door opens at 3 s, a target rises at 6 s, a hand reaches at 9 s
fox.glb an animated model (see LICENSE)

BIN
assets/Forge/kit/click.wav (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/Forge/kit/clip.mkv (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/Forge/kit/coin.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/Forge/kit/coin.wav (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/Forge/kit/crate.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/Forge/kit/door.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/Forge/kit/door.wav (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/Forge/kit/enemy.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/Forge/kit/fox.glb (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/Forge/kit/hall.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/Forge/kit/hero.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/Forge/kit/hit.wav (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/Forge/kit/jump.wav (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/Forge/kit/key.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/Forge/kit/loop.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/Forge/kit/rock.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/Forge/kit/ship.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/Forge/kit/shot.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/Forge/kit/shot.wav (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/Forge/kit/spike.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/Forge/kit/target.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/Forge/kit/tiles.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/Forge/kit/yard.png (Stored with Git LFS) Normal file

Binary file not shown.

View file

@ -0,0 +1,76 @@
-- Written by Forge/AuthorCompile.singe. Edit by hand or in the editor; either is fine.
return {
layers = {
{ gravity = 1500, kind = "world2d" }
},
rooms = {
{
entities = {
{ id = "ground", type = "ground", x = 360, y = 440 },
{ id = "hero", type = "hero", x = 200, y = 420 },
{ id = "readout", type = "readout", x = 12, y = 12 }
},
name = "start",
tracks = {}
}
},
rules = {
{
act = {
{ "run", direction = -1, entity = "hero" }
},
note = "Run left",
on = "frame",
when = {
{ "keyHeld", key = "LEFT" }
}
},
{
act = {
{ "run", direction = 1, entity = "hero" }
},
note = "Run right",
on = "frame",
when = {
{ "keyHeld", key = "RIGHT" }
}
},
{
act = {
{ "jump", entity = "hero" }
},
note = "Jump, with ground underfoot",
on = "frame",
when = {
{ "keyHeld", key = "SPACE" },
{ "onGround", entity = "hero" }
}
},
{
act = {
{ "setText", entity = "readout", text = "\"score \" .. score" }
},
note = "The readout, every frame",
on = "frame"
}
},
title = "New game",
types = {
ground = {
behaviours = {
{ kind = "solid" }
},
look = { b = 90, g = 70, h = 40, kind = "box", r = 60, w = 720 }
},
hero = {
behaviours = {
{ jump = 620, kind = "platformer", speed = 260 }
},
look = { anchor = "feet", b = 240, g = 90, h = 44, kind = "box", r = 90, w = 24 }
},
readout = {
look = { b = 245, g = 235, kind = "text", r = 235, text = "score 0" }
}
},
vars = { score = 0 }
}

View file

@ -0,0 +1,144 @@
-- Written by Forge/AuthorCompile.singe. Edit by hand or in the editor; either is fine.
return {
layers = {
{ gravity = 1500, kind = "world2d" }
},
rooms = {
{
entities = {
{ id = "ground", type = "ground", x = 360, y = 440 },
{ id = "hero", type = "hero", x = 200, y = 420 },
{ id = "readout", type = "readout", x = 12, y = 12 },
{ id = "coin", type = "coin", x = 300, y = 392 },
{ id = "coin1", type = "coin", x = 420, y = 392 },
{ id = "coin2", type = "coin", x = 560, y = 300 },
{ id = "spike", type = "spike", x = 640, y = 420 },
{ id = "door", type = "door", x = 700, y = 420 }
},
name = "start",
tracks = {}
},
{
entities = {
{ id = "ground", type = "ground", x = 360, y = 440 },
{ id = "readout", type = "readout", x = 12, y = 12 },
{ id = "coin", type = "coin", x = 400, y = 392 }
},
name = "cave",
tracks = {}
}
},
rules = {
{
act = {
{ "run", direction = -1, entity = "hero" }
},
note = "Run left",
on = "frame",
when = {
{ "keyHeld", key = "LEFT" }
}
},
{
act = {
{ "run", direction = 1, entity = "hero" }
},
note = "Run right",
on = "frame",
when = {
{ "keyHeld", key = "RIGHT" }
}
},
{
act = {
{ "jump", entity = "hero" }
},
note = "Jump, with ground underfoot",
on = "frame",
when = {
{ "keyHeld", key = "SPACE" },
{ "onGround", entity = "hero" }
}
},
{
act = {
{ "setText", entity = "readout", text = "\"score \" .. score" }
},
note = "The readout, every frame",
on = "frame"
},
{
a = "coin",
act = {
{ "addScore", amount = 10 },
{ "destroy", entity = "self" },
{ "playSound", file = "Forge/kit/coin.wav" }
},
b = "hero",
note = "Take a coin",
on = "enter",
when = {}
},
{
a = "spike",
act = {
{ "moveTo", entity = "hero", x = 200, y = 380 },
{ "playSound", file = "Forge/kit/hit.wav" }
},
b = "hero",
note = "Spikes hurt",
on = "enter",
when = {}
},
{
a = "door",
act = {
{ "goTo", entity = "hero", room = "cave", x = 60, y = 380 }
},
b = "hero",
note = "Through the door",
on = "enter",
when = {}
}
},
title = "New game",
types = {
coin = {
behaviours = {
{ kind = "trigger" }
},
look = { file = "Forge/kit/coin.png", kind = "sprite" },
vars = {}
},
door = {
behaviours = {
{ kind = "trigger" }
},
look = { anchor = "feet", file = "Forge/kit/door.png", kind = "sprite" },
vars = {}
},
ground = {
behaviours = {
{ kind = "solid" }
},
look = { b = 90, g = 70, h = 40, kind = "box", r = 60, w = 720 }
},
hero = {
behaviours = {
{ jump = 620, kind = "platformer", speed = 260 }
},
look = { anchor = "feet", b = 240, g = 90, h = 44, kind = "box", r = 90, w = 24 }
},
readout = {
look = { b = 245, g = 235, kind = "text", r = 235, text = "score 0" }
},
spike = {
behaviours = {
{ kind = "trigger" }
},
look = { anchor = "feet", file = "Forge/kit/spike.png", kind = "sprite" },
vars = {}
}
},
vars = { score = 0 }
}

View file

@ -0,0 +1,156 @@
-- Written by Forge/AuthorCompile.singe. Edit by hand or in the editor; either is fine.
return {
layers = {
{ gravity = 1500, kind = "world2d" }
},
rooms = {
{
entities = {
{ id = "ground", type = "ground", x = 360, y = 440 },
{ id = "hero", type = "hero", x = 200, y = 420 },
{ id = "readout", type = "readout", x = 12, y = 12 },
{ id = "coin", type = "coin", x = 300, y = 392 },
{ id = "coin1", type = "coin", x = 420, y = 392 },
{ id = "coin2", type = "coin", x = 560, y = 300 },
{ id = "spike", type = "spike", x = 640, y = 420 },
{ id = "door", type = "door", x = 700, y = 420 }
},
name = "start",
tracks = {}
},
{
entities = {
{ id = "ground", type = "ground", x = 360, y = 440 },
{ id = "readout", type = "readout", x = 12, y = 12 },
{ id = "coin", type = "coin", x = 400, y = 392 }
},
name = "cave",
tracks = {}
}
},
rules = {
{
act = {
{ "run", direction = -1, entity = "hero" }
},
note = "Run left",
on = "frame",
when = {
{ "keyHeld", key = "LEFT" }
}
},
{
act = {
{ "run", direction = 1, entity = "hero" }
},
note = "Run right",
on = "frame",
when = {
{ "keyHeld", key = "RIGHT" }
}
},
{
act = {
{ "jump", entity = "hero" },
{ "playSound", file = "Forge/kit/jump.wav" }
},
note = "Jump, with ground underfoot",
on = "frame",
when = {
{ "onGround", entity = "hero" },
{ "keyPressed", key = "SPACE" }
}
},
{
act = {
{ "setText", entity = "readout", text = "\"score \" .. score" }
},
note = "The readout, every frame",
on = "frame"
},
{
a = "coin",
act = {
{ "addScore", amount = 10 },
{ "destroy", entity = "self" },
{ "playSound", file = "Forge/kit/coin.wav" }
},
b = "hero",
note = "Take a coin",
on = "enter",
when = {}
},
{
a = "spike",
act = {
{ "moveTo", entity = "hero", x = 200, y = 380 },
{ "playSound", file = "Forge/kit/hit.wav" }
},
b = "hero",
note = "Spikes hurt",
on = "enter",
when = {}
},
{
a = "door",
act = {
{ "goTo", entity = "hero", room = "cave", x = 60, y = 380 }
},
b = "hero",
note = "Through the door",
on = "enter",
when = {}
},
{
act = {
{ "playMusic", file = "Forge/kit/loop.ogg" }
},
note = "Music",
on = "roomStart",
when = {}
}
},
title = "New game",
types = {
coin = {
behaviours = {
{ kind = "trigger" },
{ kind = "spin", rate = 180 }
},
look = { file = "Forge/kit/coin.png", kind = "sprite" },
vars = {}
},
door = {
behaviours = {
{ kind = "trigger" }
},
look = { anchor = "feet", file = "Forge/kit/door.png", kind = "sprite" },
vars = {}
},
ground = {
behaviours = {
{ kind = "solid" }
},
look = { b = 90, g = 70, h = 40, kind = "box", r = 60, w = 720 }
},
hero = {
behaviours = {
{ jump = 620, kind = "platformer", speed = 260 },
{ fps = 10, kind = "frames", states = "idle=1-1, walk=2-5, jump=6-6, fall=7-7" }
},
look = { file = "Forge/kit/hero.png", frames = 8, kind = "sprite" },
vars = {}
},
readout = {
look = { b = 245, g = 235, kind = "text", r = 235, text = "score 0" }
},
spike = {
behaviours = {
{ kind = "trigger" }
},
look = { anchor = "feet", file = "Forge/kit/spike.png", kind = "sprite" },
vars = {}
}
},
vars = { score = 0 }
}

View file

@ -0,0 +1,135 @@
-- Written by Forge/AuthorCompile.singe. Edit by hand or in the editor; either is fine.
return {
types = {
hive = {
behaviours = {
{ every = 0.6, kind = "spawner", max = 6, spawns = "rock", total = 12 }
},
look = { kind = "none" },
vars = {}
},
readout = {
look = { b = 245, g = 235, kind = "text", r = 235, text = "score 0" }
},
rock = {
behaviours = {
{ kind = "drift", vy = 90 },
{ kind = "health", max = 2 },
{ death = "Forge/kit/hit.wav", kind = "sound" }
},
look = { file = "Forge/kit/rock.png", kind = "sprite" },
vars = { health = 2 }
},
ship = {
behaviours = {
{ down = "DOWN", fire = "SPACE", kind = "keys", left = "LEFT", right = "RIGHT", up = "UP" },
{ clamp = true, kind = "mover", speed = 260 },
{ kind = "shooter", offsetY = -14, rate = 0.15, spawns = "shot" },
{ keep = true, kind = "health", max = 1 },
{ hit = "Forge/kit/hit.wav", kind = "sound" }
},
look = { file = "Forge/kit/ship.png", kind = "sprite" },
vars = { health = 1 }
},
shot = {
behaviours = {
{ kind = "projectile", life = 3, vy = -520 },
{ kind = "sound", spawn = "Forge/kit/shot.wav" }
},
look = { file = "Forge/kit/shot.png", kind = "sprite" },
vars = {}
}
},
layers = {
{ kind = "overlay" }
},
rooms = {
{
entities = {
{ id = "readout", type = "readout", x = 12, y = 12 },
{ id = "ship", type = "ship", x = 360, y = 420 },
{ id = "hive", type = "hive", x = 360, y = 20 },
{ id = "hive1", type = "hive", x = 560, y = 20 }
},
name = "start",
tracks = {}
}
},
rules = {
{
act = {
{ "setText", entity = "readout", text = "\"score \" .. score .. \" lives \" .. lives" }
},
note = "The readout, every frame",
on = "frame"
},
{
a = "shot",
act = {
{ "destroy", entity = "self" },
{ "damage", amount = 1, entity = "other" },
{ "addScore", amount = 10 }
},
b = "rock",
note = "A shot lands on a rock",
on = "collision",
when = {}
},
{
act = {
{ "emit", b = 60, count = 24, entity = "self", g = 120, r = 255 },
{ "addScore", amount = 50 }
},
type = "rock",
note = "A rock dies",
on = "death",
when = {}
},
{
a = "rock",
act = {
{ "destroy", entity = "self" },
{ "damage", amount = 1, entity = "other" }
},
b = "ship",
note = "A rock rams the ship",
on = "collision",
when = {}
},
{
act = {
{ "addVar", amount = -1, name = "lives" },
{ "flash", b = 60, g = 60, r = 255, seconds = 0.3 },
{ "setVar", entity = "self", name = "health", value = "1" },
{ "moveTo", entity = "self", x = 360, y = 420 }
},
type = "ship",
note = "The ship is lost",
on = "death",
when = {}
},
{
act = {
{ "gameOver" }
},
note = "Out of lives",
on = "frame",
when = {
{ "test", expr = "lives <= 0" }
}
},
{
act = {
{ "destroy", entity = "self" }
},
each = "rock",
note = "A rock slips past the bottom",
on = "frame",
when = {
{ "test", expr = "self.y > 500" }
}
}
},
title = "Rocks",
vars = { lives = 3, score = 0 }
}

View file

@ -0,0 +1,140 @@
-- Written by Forge/AuthorCompile.singe. Edit by hand or in the editor; either is fine.
return {
types = {
prompt = {
behaviours = {},
look = { b = 80, g = 220, kind = "text", r = 255 },
vars = {}
},
readout = {
look = { b = 245, g = 235, kind = "text", r = 235, text = "score 0" }
},
verdict = {
behaviours = {},
look = { b = 180, g = 255, kind = "text", r = 160 },
vars = {}
}
},
layers = {
{ file = "Forge/kit/clip.mkv", kind = "disc" },
{ kind = "overlay" }
},
rooms = {
{
entities = {
{ id = "readout", type = "readout", x = 12, y = 12 },
{ id = "prompt", type = "prompt", x = 250, y = 60 },
{ id = "verdict", type = "verdict", x = 250, y = 100 }
},
name = "start",
tracks = {}
}
},
rules = {
{
act = {
{ "setText", entity = "readout", text = "\"score \" .. score .. \" lives \" .. lives" }
},
note = "The readout, every frame",
on = "frame"
},
{
act = {
{ "setText", entity = "prompt", text = "\"PRESS!\"" }
},
note = "Window one is open",
on = "frame",
when = {
{ "discBetween", from = 72, to = 120 }
}
},
{
act = {
{ "setVar", name = "hitone", value = true },
{ "addScore", amount = 250 },
{ "setText", entity = "verdict", text = "\"HIT\"" }
},
note = "Window one answered in time",
on = "frame",
when = {
{ "discBetween", from = 72, to = 120 },
{ "keyPressed", key = "SPACE" },
{ "once", tag = "one" }
}
},
{
act = {
{ "addVar", amount = -1, name = "lives" },
{ "setText", entity = "verdict", text = "\"MISS\"" },
{ "setText", entity = "prompt", text = "\"\"" }
},
frame = 120,
note = "Window one closed unanswered",
on = "frameReached",
when = {
{ "test", expr = "not hitone" }
}
},
{
act = {
{ "setText", entity = "prompt", text = "\"UP!\"" }
},
note = "Window two is open",
on = "frame",
when = {
{ "discBetween", from = 150, to = 200 }
}
},
{
act = {
{ "setVar", name = "hittwo", value = true },
{ "addScore", amount = 250 },
{ "setText", entity = "verdict", text = "\"HIT\"" }
},
note = "Window two answered in time",
on = "frame",
when = {
{ "discBetween", from = 150, to = 200 },
{ "keyPressed", key = "UP" },
{ "once", tag = "two" }
}
},
{
act = {
{ "addVar", amount = -1, name = "lives" },
{ "setText", entity = "verdict", text = "\"MISS\"" },
{ "setText", entity = "prompt", text = "\"\"" }
},
frame = 200,
note = "Window two closed unanswered",
on = "frameReached",
when = {
{ "test", expr = "not hittwo" }
}
},
{
act = {
{ "setVar", name = "hitone", value = false },
{ "setVar", name = "hittwo", value = false },
{ "setText", entity = "verdict", text = "\"\"" },
{ "discTo", frame = 1 }
},
frame = 280,
note = "The clip runs out: round again",
on = "frameReached",
when = {}
},
{
act = {
{ "gameOver" }
},
note = "Out of lives",
on = "frame",
when = {
{ "test", expr = "lives <= 0" }
}
}
},
title = "Moments",
vars = { lives = 3, score = 0 }
}

View file

@ -0,0 +1,115 @@
-- Written by Forge/AuthorCompile.singe. Edit by hand or in the editor; either is fine.
return {
types = {
gun = {
behaviours = {
{ ammo = 6, kind = "gun", player = 1, reload = "offscreen" }
},
look = { kind = "none" },
vars = { ammo = 6 }
},
hand = {
behaviours = {
{ kind = "hitbox", track = "hand" }
},
look = { kind = "none" },
vars = {}
},
readout = {
look = { b = 245, g = 235, kind = "text", r = 235, text = "score 0" }
},
target = {
behaviours = {
{ kind = "hitbox", track = "target" },
{ kind = "health", max = 1 }
},
look = { kind = "none" },
vars = { health = 1 }
}
},
layers = {
{ file = "Forge/kit/clip.mkv", kind = "disc" },
{ kind = "overlay" }
},
rooms = {
{
entities = {
{ id = "readout", type = "readout", x = 12, y = 12 },
{ id = "gun", type = "gun", x = 30, y = 450 },
{ id = "target", type = "target", x = 450, y = 210 },
{ id = "hand", type = "hand", x = 600, y = 210 }
},
name = "start",
tracks = {
{
boxes = {
{ at = 150, h = 90, w = 90, x = 405, y = 165 },
{ at = 210, h = 90, w = 90, x = 405, y = 165 }
},
key = "frame",
name = "target"
},
{
boxes = {
{ at = 220, h = 105, w = 240, x = 480, y = 157 },
{ at = 260, h = 105, w = 240, x = 480, y = 157 }
},
key = "frame",
name = "hand"
}
}
}
},
rules = {
{
act = {
{ "setText", entity = "readout", text = "\"score \" .. score .. \" ammo \" .. gun.ammo .. \" misses \" .. misses" }
},
note = "The readout, every frame",
on = "frame"
},
{
act = {
{ "addScore", amount = 100 },
{ "damage", amount = 1, entity = "self" },
{ "playSound", file = "Forge/kit/hit.wav" }
},
type = "target",
note = "The target is hit",
on = "hit",
when = {}
},
{
act = {
{ "addScore", amount = -200 },
{ "flash", b = 40, g = 40, r = 255, seconds = 0.3 }
},
type = "hand",
note = "The hand is hit",
on = "hit",
when = {}
},
{
act = {
{ "addVar", amount = 1, name = "misses" },
{ "playSound", file = "Forge/kit/shot.wav" }
},
note = "A shot at nothing",
on = "miss",
when = {
{ "test", expr = "not event.offscreen" }
}
},
{
act = {
{ "say", seconds = 1, text = "\"DRAW!\"" }
},
frame = 150,
note = "Draw!",
on = "frameReached",
when = {}
}
},
title = "Draw",
vars = { misses = 0, score = 0 }
}

View file

@ -0,0 +1,244 @@
-- Written by Forge/AuthorCompile.singe. Edit by hand or in the editor; either is fine.
return {
dialogues = {
guard = {
nodes = {
ok = {
choices = {},
text = "Then go ahead.",
who = "Guard"
},
start = {
choices = {
{ next = "why", text = "Why not?" },
{ text = "Fine." }
},
text = "Nobody passes.",
who = "Guard"
},
why = {
choices = {
{
act = {
{ "setVar", name = "allowed", value = true }
},
next = "ok",
text = "I have a key.",
when = "has(\"key\")"
},
{ text = "I see." }
},
text = "The door is locked, and I have no key.",
who = "Guard"
}
},
start = "start"
}
},
types = {
backdrop = {
behaviours = {},
look = { file = "Forge/kit/yard.png", kind = "sprite" },
vars = {}
},
door = {
behaviours = {
{ kind = "hotspot", name = "door", walkX = 590, walkY = 330 }
},
look = { anchor = "feet", file = "Forge/kit/door.png", kind = "sprite" },
vars = {}
},
guard = {
behaviours = {
{ kind = "hotspot", name = "guard", walkX = 480, walkY = 360 }
},
look = { anchor = "feet", faces = "left", file = "Forge/kit/hero.png", frames = 8, kind = "sprite" },
vars = {}
},
hero = {
behaviours = {
{ kind = "walker", speed = 160 },
{ fps = 10, kind = "frames", states = "idle=1-1, walk=2-5" }
},
look = { anchor = "feet", file = "Forge/kit/hero.png", frames = 8, kind = "sprite" },
vars = {}
},
key = {
behaviours = {
{ kind = "hotspot", name = "key", walkX = 200, walkY = 400 }
},
look = { file = "Forge/kit/key.png", kind = "sprite" },
vars = {}
},
readout = {
look = { b = 245, g = 235, kind = "text", r = 235, text = "score 0" }
}
},
layers = {
{ kind = "overlay" }
},
rooms = {
{
depthSort = true,
entities = {
{ id = "readout", type = "readout", x = 12, y = 12 },
{ id = "backdrop", type = "backdrop", x = 360, y = 240 },
{ id = "hero", type = "hero", x = 150, y = 440 },
{ id = "door", type = "door", x = 590, y = 300 },
{ id = "key", type = "key", x = 200, y = 380 },
{ id = "guard", type = "guard", x = 520, y = 340 }
},
name = "yard",
scaleBy = {
{ scale = 0.6, y = 300 },
{ scale = 1.0, y = 460 }
},
tracks = {},
walk = {
{ 60, 320, 660, 320, 700, 460, 40, 460 }
}
},
{
depthSort = true,
entities = {
{ id = "backdrop", type = "backdrop", x = 360, y = 240 },
{ id = "readout", type = "readout", x = 12, y = 12 }
},
name = "hall",
tracks = {},
walk = {
{ 40, 300, 680, 300, 700, 460, 40, 460 }
}
}
},
rules = {
{
act = {
{ "setText", entity = "readout", text = "(sentence or \"\") .. \" score \" .. score" }
},
note = "The readout, every frame",
on = "frame"
},
{
act = {
{ "walkToPointer", entity = "hero", player = 1 }
},
interrupt = true,
note = "A click on the floor walks there",
on = "pressed",
switch = "SWITCH_BUTTON3",
when = {}
},
{
act = {
{ "say", seconds = 1, text = "\"It is a \" .. event.target .. \".\"" }
},
note = "Look at anything",
on = "verb",
verb = "look",
when = {}
},
{
act = {
{ "walkToHotspot", entity = "hero", target = "self" },
{ "give", item = "key" },
{ "addScore", amount = 1 },
{ "destroy", entity = "self" }
},
note = "Take the key",
on = "verb",
target = "key",
verb = "take",
when = {}
},
{
act = {
{ "walkToHotspot", entity = "hero", target = "self" },
{ "face", entity = "hero", target = "self" },
{ "talk", dialogue = "guard" }
},
note = "Talk to the guard",
on = "verb",
target = "guard",
verb = "talk",
when = {}
},
{
act = {
{ "say", seconds = 1, text = "\"The guard shakes his head.\"" }
},
note = "The door, while the guard objects",
on = "verb",
target = "door",
verb = "open",
when = {
{ "test", expr = "not allowed" }
}
},
{
act = {
{ "walkToHotspot", entity = "hero", target = "self" },
{ "playSound", file = "Forge/kit/door.wav" },
{ "fade", out = true, seconds = 0.5 },
{ "goTo", entity = "hero", room = "hall", x = 120, y = 420 },
{ "fade", out = false, seconds = 0.5 },
{ "addScore", amount = 1 }
},
controls = false,
note = "The door opens",
on = "verb",
target = "door",
verb = "open",
when = {
{ "test", expr = "allowed" }
}
},
{
act = {
{ "say", seconds = 1, text = "\"That does not work.\"" }
},
note = "Anything else",
on = "verb",
when = {}
},
{
act = {
{ "setVerb", verb = "look" }
},
key = "L",
note = "The verb keys",
on = "pressed",
when = {}
},
{
act = {
{ "setVerb", verb = "take" }
},
key = "T",
note = "Take",
on = "pressed",
when = {}
},
{
act = {
{ "setVerb", verb = "open" }
},
key = "O",
note = "Open",
on = "pressed",
when = {}
},
{
act = {
{ "setVerb", verb = "talk" }
},
key = "K",
note = "Talk",
on = "pressed",
when = {}
}
},
title = "The Yard",
vars = { allowed = false, score = 0 },
verbs = { "walk", "look", "take", "use", "open", "talk" }
}

View file

@ -0,0 +1,129 @@
-- Written by Forge/AuthorCompile.singe. Edit by hand or in the editor; either is fine.
return {
types = {
camera = {
behaviours = {
{ kind = "camera", lag = 0.2, lookY = 0.6, mode = "follow", target = "hero", x = 0, y = 3, z = 7 }
},
look = { kind = "none" },
vars = {}
},
floor = {
behaviours = {
{ kind = "solid" }
},
look = { b = 90, d = 12, g = 110, h = 0.4, kind = "mesh", r = 90, shape = "box", w = 30 },
vars = {}
},
hero = {
behaviours = {
{ down = "DOWN", kind = "keys", left = "LEFT", right = "RIGHT", up = "UP" },
{ height = 0.9, jump = 6.5, kind = "character", radius = 0.3, speed = 4 },
{ idle = "Survey", kind = "animator", walk = "Walk" }
},
look = { d = 0.6, file = "Forge/kit/fox.glb", h = 0.9, kind = "model", scale = 0.008, w = 0.6 },
vars = {}
},
ledge = {
behaviours = {
{ kind = "solid" }
},
look = { b = 70, d = 4, g = 90, h = 0.4, kind = "mesh", r = 110, shape = "box", w = 4 },
vars = {}
},
prize = {
behaviours = {
{ kind = "trigger" }
},
look = { b = 60, g = 200, kind = "mesh", r = 255, shape = "sphere", w = 0.6 },
vars = {}
},
readout = {
look = { b = 245, g = 235, kind = "text", r = 235, text = "score 0" }
}
},
layers = {
{ fov = 55, kind = "scene3d" },
{ kind = "overlay" }
},
rooms = {
{
entities = {
{ id = "readout", type = "readout", x = 12, y = 12 },
{ id = "floor", type = "floor", x = 0.0, y = -0.2, z = 0.0 },
{ id = "ledge", type = "ledge", x = 6.0, y = 1.2, z = 0.0 },
{ id = "prize", type = "prize", x = 6.0, y = 2.0, z = 0.0 },
{ id = "hero", type = "hero", x = -4.0, y = 0.5, z = 0.0 },
{ id = "camera", type = "camera", x = -4.0, y = 3.0, z = 7.0 }
},
name = "start",
tracks = {}
}
},
rules = {
{
act = {
{ "setText", entity = "readout", text = "\"score \" .. score" }
},
note = "The readout, every frame",
on = "frame"
},
{
act = {
{ "setState", entity = "self", state = "walk" }
},
each = "hero",
note = "Walking is a state the animator plays",
on = "frame",
when = {
{ "test", expr = "self.dx != 0 or self.dy != 0" }
}
},
{
act = {
{ "setState", entity = "self", state = "idle" }
},
each = "hero",
note = "Standing still",
on = "frame",
when = {
{ "test", expr = "self.dx == 0 and self.dy == 0" }
}
},
{
act = {
{ "jump", entity = "hero" }
},
note = "Jump, with ground underfoot",
on = "frame",
when = {
{ "keyPressed", key = "SPACE" },
{ "onGround", entity = "hero" }
}
},
{
a = "prize",
act = {
{ "destroy", entity = "self" },
{ "addScore", amount = 100 },
{ "playSound", file = "Forge/kit/coin.wav" }
},
b = "hero",
note = "The prize is taken",
on = "enter",
when = {}
},
{
act = {
{ "moveTo", entity = "hero", x = -4, y = 0.5, z = 0 }
},
note = "Fallen off the world",
on = "frame",
when = {
{ "test", expr = "hero.y < -5" }
}
}
},
title = "Field",
vars = { score = 0 }
}

View file

@ -0,0 +1,215 @@
-- Written by Forge/AuthorCompile.singe. Edit by hand or in the editor; either is fine.
return {
types = {
camera = {
behaviours = {
{ kind = "camera", mode = "rail", track = "rail" }
},
look = { kind = "none" },
vars = {}
},
floor = {
behaviours = {
{ kind = "solid" }
},
look = { b = 90, d = 60, g = 75, h = 0.2, kind = "mesh", r = 70, shape = "box", w = 40 },
vars = {}
},
gun = {
behaviours = {
{ ammo = 6, kind = "gun", player = 1, reload = "offscreen" }
},
look = { kind = "none" },
vars = { ammo = 6 }
},
painting = {
behaviours = {},
look = { b = 255, d = 0.2, g = 255, h = 16, kind = "mesh", r = 255, shape = "box", texture = "Forge/kit/yard.png", unlit = true, w = 24 },
vars = {}
},
pillar = {
behaviours = {},
look = { d = 1.2, h = 8, kind = "mesh", occluder = true, shape = "box", w = 1.2 },
vars = {}
},
readout = {
look = { b = 245, g = 235, kind = "text", r = 235, text = "score 0" }
},
zombie = {
behaviours = {
{ kind = "health", linger = 2, max = 2, ragdoll = true },
{ kind = "seek", speed = 1.6, stopAt = 2.2, target = "camera" },
{ attack = "Run", idle = "Survey", kind = "animator", walk = "Walk" },
{ kind = "target" },
{ after = 1.5, kind = "timer", name = "bite", start = false }
},
look = { d = 1.4, file = "Forge/kit/fox.glb", h = 0.9, kind = "model", scale = 0.01, w = 0.8 },
vars = { health = 2 }
}
},
layers = {
{ fov = 60, kind = "scene3d" },
{ kind = "overlay" }
},
rooms = {
{
entities = {
{ id = "readout", type = "readout", x = 12, y = 12 },
{ id = "floor", type = "floor", x = 0.0, y = -0.1, z = -20.0 },
{ id = "painting", type = "painting", x = 0.0, y = 6.0, z = -30.0 },
{ id = "pillar", type = "pillar", x = 0.0, y = 4.0, z = -22.0 },
{ id = "camera", type = "camera", x = 0.0, y = 1.6, z = 0.0 },
{ id = "gun", type = "gun", x = 0.0, y = 0.0, z = 0.0 }
},
name = "start",
tracks = {
{
key = "time",
name = "rail",
points = {
{
at = 0,
look = { 0, 1.2, -10 },
stop = "nil",
x = 0,
y = 1.6,
z = 0
},
{
at = 4,
look = { 0, 1.2, -16 },
stop = "gate",
x = 0,
y = 1.6,
z = -6
},
{
at = 10,
look = { 2, 1.2, -24 },
stop = "pillar",
x = 2,
y = 1.6,
z = -14
},
{
at = 14,
look = { 2, 1.2, -28 },
stop = "nil",
x = 2,
y = 1.6,
z = -18
}
}
}
}
}
},
rules = {
{
act = {
{ "setText", entity = "readout", text = "\"score \" .. score .. \" health \" .. health .. \" ammo \" .. gun.ammo" }
},
note = "The readout, every frame",
on = "frame"
},
{
act = {
{ "spawn", type = "zombie", x = -2, y = 0, z = -14 },
{ "spawn", type = "zombie", x = 2, y = 0, z = -15 }
},
name = "gate",
note = "The gate: two come out of the dark",
on = "stopped",
when = {}
},
{
act = {
{ "spawn", type = "zombie", x = -1, y = 0, z = -23 },
{ "spawn", type = "zombie", x = 0, y = 0, z = -26 },
{ "spawn", type = "zombie", x = 4, y = 0, z = -24 }
},
name = "pillar",
note = "The pillar: three more, one from behind it",
on = "stopped",
when = {}
},
{
act = {
{ "setState", entity = "self", state = "walk" }
},
type = "zombie",
note = "A zombie walks the moment it is made",
on = "spawn",
when = {}
},
{
act = {
{ "damage", amount = 1, entity = "self" },
{ "addScore", amount = 100 },
{ "playSound", file = "Forge/kit/hit.wav" }
},
type = "zombie",
note = "A shot lands",
on = "hit",
when = {}
},
{
act = {
{ "setState", entity = "self", state = "attack" },
{ "timerStart", after = 1.5, entity = "self", name = "bite" }
},
type = "zombie",
note = "It reaches the camera and rears up",
on = "arrived",
when = {}
},
{
act = {
{ "addVar", amount = -1, name = "health" },
{ "flash", b = 40, g = 40, r = 255, seconds = 0.3 },
{ "shake", amount = 0.3, seconds = 0.4 },
{ "timerStart", after = 1.5, entity = "self", name = "bite" }
},
type = "zombie",
name = "bite",
note = "The bite lands unless it was killed in time",
on = "timer",
when = {
{ "inState", entity = "self", state = "attack" }
}
},
{
act = {
{ "pathNext", entity = "camera" }
},
note = "The stop is clear: move on",
on = "frame",
when = {
{ "waiting", entity = "camera" },
{ "test", expr = "count(\"zombie\") == 0" }
}
},
{
act = {
{ "say", seconds = 2, text = "\"YOU ARE DEAD\"" },
{ "gameOver" }
},
note = "Out of health",
on = "frame",
when = {
{ "test", expr = "health <= 0" },
{ "once", scope = "game", tag = "over" }
}
},
{
act = {
{ "say", seconds = 2, text = "\"STAGE CLEAR\"" }
},
note = "The end of the rail",
on = "railEnd",
when = {}
}
},
title = "The Dead Yard",
vars = { health = 3, score = 0 }
}

View file

@ -0,0 +1,193 @@
-- Written by Forge/AuthorCompile.singe. Edit by hand or in the editor; either is fine.
return {
layers = {
{ gravity = 1500, kind = "world2d" },
{ kind = "bezel" }
},
rooms = {
{
entities = {
{ id = "ground", type = "ground", x = 360, y = 440 },
{ id = "hero", type = "hero", x = 200, y = 420 },
{ id = "readout", type = "readout", x = 12, y = 12 },
{ id = "coin", type = "coin", x = 300, y = 392 },
{ id = "coin1", type = "coin", x = 420, y = 392 },
{ id = "coin2", type = "coin", x = 560, y = 300 },
{ id = "spike", type = "spike", x = 640, y = 420 },
{ id = "door", type = "door", x = 700, y = 420 }
},
name = "start",
tracks = {}
},
{
entities = {
{ id = "ground", type = "ground", x = 360, y = 440 },
{ id = "readout", type = "readout", x = 12, y = 12 },
{ id = "coin", type = "coin", x = 400, y = 392 }
},
name = "cave",
tracks = {}
}
},
rules = {
{
act = {
{ "run", direction = -1, entity = "hero" }
},
note = "Run left",
on = "frame",
when = {
{ "keyHeld", key = "LEFT" }
}
},
{
act = {
{ "run", direction = 1, entity = "hero" }
},
note = "Run right",
on = "frame",
when = {
{ "keyHeld", key = "RIGHT" }
}
},
{
act = {
{ "jump", entity = "hero" },
{ "playSound", file = "Forge/kit/jump.wav" }
},
note = "Jump, with ground underfoot",
on = "frame",
when = {
{ "onGround", entity = "hero" },
{ "keyPressed", key = "SPACE" }
}
},
{
act = {
{ "setText", entity = "readout", text = "\"score \" .. score .. \" lives \" .. lives" }
},
note = "The readout, every frame",
on = "frame"
},
{
a = "coin",
act = {
{ "addScore", amount = 10 },
{ "destroy", entity = "self" },
{ "playSound", file = "Forge/kit/coin.wav" }
},
b = "hero",
note = "Take a coin",
on = "enter",
when = {}
},
{
a = "spike",
act = {
{ "moveTo", entity = "hero", x = 200, y = 380 },
{ "playSound", file = "Forge/kit/hit.wav" }
},
b = "hero",
note = "Spikes hurt",
on = "enter",
when = {}
},
{
a = "door",
act = {
{ "goTo", entity = "hero", room = "cave", x = 60, y = 380 },
{ "addVar", amount = -1, name = "lives" }
},
b = "hero",
note = "Through the door",
on = "enter",
when = {}
},
{
act = {
{ "playMusic", file = "Forge/kit/loop.ogg" }
},
note = "Music",
on = "roomStart",
when = {}
},
{
act = {
{ "submitScore", board = "default" },
{ "say", seconds = 2, text = "\"GAME OVER\"" },
{ "gameOver" }
},
note = "Out of lives",
on = "frame",
when = {
{ "test", expr = "lives <= 0" },
{ "once", scope = "game", tag = "over" }
}
},
{
act = {
{ "credit" }
},
note = "A coin",
on = "pressed",
switch = "SWITCH_COIN1",
when = {}
},
{
act = {
{ "addVar", amount = -1, name = "credits" },
{ "setVar", name = "lives", value = "3" },
{ "restart" }
},
note = "Continue",
on = "pressed",
switch = "SWITCH_START1",
when = {
{ "test", expr = "credits > 0 and lives <= 0" }
}
}
},
title = "Coin Run",
types = {
coin = {
behaviours = {
{ kind = "trigger" },
{ kind = "spin", rate = 180 }
},
look = { file = "Forge/kit/coin.png", kind = "sprite" },
vars = {}
},
door = {
behaviours = {
{ kind = "trigger" }
},
look = { anchor = "feet", file = "Forge/kit/door.png", kind = "sprite" },
vars = {}
},
ground = {
behaviours = {
{ kind = "solid" }
},
look = { b = 90, g = 70, h = 40, kind = "box", r = 60, w = 720 }
},
hero = {
behaviours = {
{ jump = 620, kind = "platformer", speed = 260 },
{ fps = 10, kind = "frames", states = "idle=1-1, walk=2-5, jump=6-6, fall=7-7" }
},
look = { file = "Forge/kit/hero.png", frames = 8, kind = "sprite" },
vars = {}
},
readout = {
look = { b = 245, g = 235, kind = "text", r = 235, text = "score 0" }
},
spike = {
behaviours = {
{ kind = "trigger" }
},
look = { anchor = "feet", file = "Forge/kit/spike.png", kind = "sprite" },
vars = {}
}
},
vars = { credits = 0, lives = 3, score = 0 }
}

View file

@ -461,7 +461,10 @@ ExternalProject_Add(singe
# rendered by the engine's own build into .builddir, goes in beside the scripts: Forge copies it # rendered by the engine's own build into .builddir, goes in beside the scripts: Forge copies it
# out to its data directory on its first run. # out to its data directory on its first run.
if((KANGAROO_OS STREQUAL "linux") AND (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux")) if((KANGAROO_OS STREQUAL "linux") AND (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux"))
file(GLOB forgeSources ${CMAKE_SOURCE_DIR}/assets/Forge/*) # Everything under assets/Forge -- the scripts, the kit, the tutorials -- and re-globbed on
# every build, so a file added or changed anywhere in it repacks Forge.game without anyone
# having to remember.
file(GLOB_RECURSE forgeSources CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/assets/Forge/*)
set(forgePack ${CMAKE_SOURCE_DIR}/.builddir/Forge.game) set(forgePack ${CMAKE_SOURCE_DIR}/.builddir/Forge.game)
set(forgeManual ${CMAKE_SOURCE_DIR}/.builddir/Forge.pdf) set(forgeManual ${CMAKE_SOURCE_DIR}/.builddir/Forge.pdf)
set(forgeStage ${SB_PREFIX}/forgepack) set(forgeStage ${SB_PREFIX}/forgepack)

File diff suppressed because it is too large Load diff

View file

@ -52,7 +52,7 @@
| `box` | `box`
| A filled rectangle, centred on the instance -- or standing on it, with anchor feet. | A filled rectangle, centred on the instance -- or standing on it, with anchor feet.
| `anchor` (string), `b` (number), `g` (number), `h` (number), `r` (number), `w` (number) | `anchor` (feet), `b` (number), `g` (number), `h` (number), `r` (number), `w` (number)
| `grid` | `grid`
| A map of tiles from a sheet, its top left at the instance: columns is the sheet's width in tiles, tile the tile's size, map the rows of tile numbers (1 is the first; 0 is empty), rows separated by semicolons. | A map of tiles from a sheet, its top left at the instance: columns is the sheet's width in tiles, tile the tile's size, map the rows of tile numbers (1 is the first; 0 is empty), rows separated by semicolons.
@ -60,11 +60,11 @@
| `light` | `light`
| A point or spot light, or a sun, on the instance. | A point or spot light, or a sun, on the instance.
| `b` (number), `g` (number), `intensity` (number), `r` (number), `range` (number), `shadow` (boolean), `type` (string) | `b` (number), `g` (number), `intensity` (number), `r` (number), `range` (number), `shadow` (boolean), `type` (point, spot, sun)
| `mesh` | `mesh`
| A box, sphere, cylinder, or plane with a colour or a texture. Floors, walls, crates, targets. An occluder is not seen but hides what is behind it: a pillar that is only in the painting. | A box, sphere, cylinder, or plane with a colour or a texture. Floors, walls, crates, targets. An occluder is not seen but hides what is behind it: a pillar that is only in the painting.
| `b` (number), `d` (number), `g` (number), `h` (number), `metallic` (number), `occluder` (boolean), `r` (number), `roughness` (number), `shape` (string), `texture` (file), `unlit` (boolean), `w` (number) | `b` (number), `d` (number), `g` (number), `h` (number), `metallic` (number), `occluder` (boolean), `r` (number), `roughness` (number), `shape` (box, sphere, cylinder, plane), `texture` (file), `unlit` (boolean), `w` (number)
| `model` | `model`
| A glTF model under the instance, scaled and turned, playing a clip. w, h, d say how big it counts as. | A glTF model under the instance, scaled and turned, playing a clip. w, h, d say how big it counts as.
@ -80,7 +80,7 @@
| `sprite` | `sprite`
| An image, centred on the instance -- or standing on it, with anchor feet. frames splits a sheet into that many columns; the var frame picks one. The var angle turns it, starting from the entity's rz. faces says which way the art looks (right unless said, or none): while the var facing is the other way the image is mirrored, so art drawn one way walks both. | An image, centred on the instance -- or standing on it, with anchor feet. frames splits a sheet into that many columns; the var frame picks one. The var angle turns it, starting from the entity's rz. faces says which way the art looks (right unless said, or none): while the var facing is the other way the image is mirrored, so art drawn one way walks both.
| `anchor` (string), `faces` (string), `file` (file), `frames` (number) | `anchor` (feet), `faces` (right, left, none), `file` (file), `frames` (number)
| `text` | `text`
| A line of text, its top left at the instance. The var text is what it says. | A line of text, its top left at the instance. The var text is what it says.
@ -104,7 +104,7 @@
| `body` | `body`
| A thing physics moves: a crate, a ball, debris. Mass, bounce, friction, and buoyancy as Jolt takes them. | A thing physics moves: a crate, a ball, debris. Mass, bounce, friction, and buoyancy as Jolt takes them.
| `bounce` (number), `buoyancy` (number), `friction` (number), `mass` (number), `shape` (string) | `bounce` (number), `buoyancy` (number), `friction` (number), `mass` (number), `shape` (box, sphere)
| `branching` | `branching`
| Dragon's Lair: a track of branches, each a disc frame window, the move it wants, and where the disc goes on success and on failure. Raises branchTaken and branchMissed. | Dragon's Lair: a track of branches, each a disc frame window, the move it wants, and where the disc goes on success and on failure. Raises branchTaken and branchMissed.
@ -112,7 +112,7 @@
| `camera` | `camera`
| The scene is drawn from this instance. fixed looks at a point; follow keeps an offset from a target; first sits on a target's eyes and the mouse looks; orbit circles a target; rail rides a track of points with stops. | The scene is drawn from this instance. fixed looks at a point; follow keeps an offset from a target; first sits on a target's eyes and the mouse looks; orbit circles a target; rail rides a track of points with stops.
| `eye` (number), `lag` (number), `lookX` (number), `lookY` (number), `lookZ` (number), `mode` (string), `target` (string), `track` (track), `x` (number), `y` (number), `z` (number) | `eye` (number), `lag` (number), `lookX` (number), `lookY` (number), `lookZ` (number), `mode` (fixed, follow, first, orbit, rail), `target` (string), `track` (track), `x` (number), `y` (number), `z` (number)
| `character` | `character`
| Walks, falls, and jumps in the scene: the engine's character controller. Moves by the vars dx and dy, relative to the camera. | Walks, falls, and jumps in the scene: the engine's character controller. Moves by the vars dx and dy, relative to the camera.
@ -128,7 +128,7 @@
| `gun` | `gun`
| The pointer as a gun for one player: a press of the trigger raises hit on what is under it, or miss. Ammo counts down; a shot off the screen reloads. With aim centre it fires from the middle of the picture -- a first-person gun. | The pointer as a gun for one player: a press of the trigger raises hit on what is under it, or miss. Ammo counts down; a shot off the screen reloads. With aim centre it fires from the middle of the picture -- a first-person gun.
| `aim` (string), `ammo` (number), `player` (number), `reload` (string), `trigger` (switch) | `aim` (centre), `ammo` (number), `player` (number), `reload` (offscreen), `trigger` (switch)
| `health` | `health`
| The var health; the damage action lowers it, and at zero the instance raises death and is destroyed unless keep is set. With ragdoll, a model falls limp instead and goes after linger seconds. | The var health; the damage action lowers it, and at zero the instance raises death and is destroyed unless keep is set. With ragdoll, a model falls limp instead and goes after linger seconds.
@ -144,7 +144,7 @@
| `joint` | `joint`
| Joins this body to another's -- or to the world, with no other -- by a hinge, a ball, or a slider through a world point along an axis. | Joins this body to another's -- or to the world, with no other -- by a hinge, a ball, or a slider through a world point along an axis.
| `ax` (number), `ay` (number), `az` (number), `dx` (number), `dy` (number), `dz` (number), `other` (string), `type` (string) | `ax` (number), `ay` (number), `az` (number), `dx` (number), `dy` (number), `dz` (number), `other` (string), `type` (hinge, ball, slider)
| `keys` | `keys`
| Reads a player's keys into the vars dx and dy (-1 to 1) and fire, so rules and movers read intent rather than hardware. | Reads a player's keys into the vars dx and dy (-1 to 1) and fire, so rules and movers read intent rather than hardware.
@ -159,7 +159,7 @@
| `loop` (boolean), `points` (table) | `loop` (boolean), `points` (table)
| `platformer` | `platformer`
| Runs, falls, and jumps: the engine's character controller in 2D. Rules say which way with run and jump. | Runs, falls, and jumps: the engine's character controller in 2D. Rules say which way with run and jump. Its point is its feet, so its look stands on it. Sets the var facing, and the var state to idle, walk, jump, or fall unless a rule set another.
| `jump` (number), `speed` (number) | `jump` (number), `speed` (number)
| `projectile` | `projectile`
@ -175,12 +175,12 @@
| `fly` (boolean), `speed` (number), `stopAt` (number), `target` (string) | `fly` (boolean), `speed` (number), `stopAt` (number), `target` (string)
| `shooter` | `shooter`
| Spawns a kind from an offset while the var fire is set (or always, with auto), no faster than the rate. | Spawns a type from an offset while the var fire is set (or always, with auto), no faster than the rate.
| `auto` (boolean), `offsetX` (number), `offsetY` (number), `rate` (number), `spawns` (kind) | `auto` (boolean), `offsetX` (number), `offsetY` (number), `rate` (number), `spawns` (type)
| `soft` | `soft`
| A mesh look made soft: a cloth, or a body inflated to a pressure. | A mesh look made soft: a cloth, or a body inflated to a pressure.
| `mass` (number), `pressure` (number), `stiffness` (number), `type` (string) | `mass` (number), `pressure` (number), `stiffness` (number), `type` (cloth, body)
| `solid` | `solid`
| Immovable ground or a wall, in a world2d or a scene3d room. With moving, a body that follows wherever the instance is moved -- a paddle, a lift. | Immovable ground or a wall, in a world2d or a scene3d room. With moving, a body that follows wherever the instance is moved -- a paddle, a lift.
@ -191,8 +191,8 @@
| `death` (file), `hit` (file), `pressed` (file), `spawn` (file), `volume` (number) | `death` (file), `hit` (file), `pressed` (file), `spawn` (file), `volume` (number)
| `spawner` | `spawner`
| Makes instances of a kind every so many seconds at its own position or at one of its points, up to max alive at once and total in all. | Makes instances of a type every so many seconds at its own position or at one of its points, up to max alive at once and total in all.
| `every` (number), `max` (number), `pick` (string), `points` (table), `spawns` (kind), `total` (number) | `every` (number), `max` (number), `pick` (turn, random), `points` (table), `spawns` (type), `total` (number)
| `spin` | `spin`
| Turns steadily: the var angle in 2D (a sprite turns with it), the node about Y in 3D, in degrees a second. | Turns steadily: the var angle in 2D (a sprite turns with it), the node about Y in 3D, in degrees a second.
@ -215,12 +215,12 @@
| -- | --
| `turret` | `turret`
| Fires at the nearest instance of a kind within range, no faster than the rate: spawns a projectile aimed at it, or, with no projectile, does the damage itself. | Fires at the nearest instance of a type within range, no faster than the rate: spawns a projectile aimed at it, or, with no projectile, does the damage itself.
| `damage` (number), `range` (number), `rate` (number), `spawns` (kind), `speed` (number), `targets` (kind) | `damage` (number), `range` (number), `rate` (number), `spawns` (type), `speed` (number), `targets` (type)
| `vehicle` | `vehicle`
| A car, motorcycle, tank, or boat on the engine's vehicle physics, driven by the vars dy (throttle, up is forward) and dx (steering) from keys. Wheels hang at the four corners the sizes say; a boat needs none. | A car, motorcycle, tank, or boat on the engine's vehicle physics, driven by the vars dy (throttle, up is forward) and dx (steering) from keys. Wheels hang at the four corners the sizes say; a boat needs none.
| `mass` (number), `radius` (number), `suspension` (number), `thrust` (number), `torque` (number), `type` (string), `wheelX` (number), `wheelZ` (number), `width` (number) | `mass` (number), `radius` (number), `suspension` (number), `thrust` (number), `torque` (number), `type` (car, motorcycle, tank, boat), `wheelX` (number), `wheelZ` (number), `width` (number)
| `walker` | `walker`
| Walks the room's walk areas (2D) or its floors (3D) on the navigation mesh: walkTo sends it, a click on the floor can, follow keeps it after an entity (or the camera), and it raises arrived. Its state is walk or idle as it goes. | Walks the room's walk areas (2D) or its floors (3D) on the navigation mesh: walkTo sends it, a click on the floor can, follow keeps it after an entity (or the camera), and it raises arrived. Its state is walk or idle as it goes.
@ -240,11 +240,11 @@
| `animationDone` | `animationDone`
| A clip that does not loop ended on self; event.state says which state it was for. | A clip that does not loop ended on self; event.state says which state it was for.
| `kind` (kind), `state` (state) | `state` (state), `type` (type)
| `arrived` | `arrived`
| A seeking instance reached its target. | A seeking instance reached its target.
| `kind` (kind) | `type` (type)
| `branchMissed` | `branchMissed`
| The window closed without the move and the disc went to the fail frame. | The window closed without the move and the disc went to the fail frame.
@ -259,16 +259,16 @@
| -- | --
| `collision` | `collision`
| An instance of kind a began touching one of kind b (self is a, other is b). | An instance of type a began touching one of type b (self is a, other is b).
| `a` (kind), `b` (kind) | `a` (type), `b` (type)
| `death` | `death`
| self's health reached zero. | self's health reached zero.
| `kind` (kind) | `type` (type)
| `enter` | `enter`
| An instance of kind b entered a trigger of kind a (self is the trigger, other what entered). | An instance of type b entered a trigger of type a (self is the trigger, other what entered).
| `a` (kind), `b` (kind) | `a` (type), `b` (type)
| `frame` | `frame`
| Every frame. | Every frame.
@ -284,11 +284,11 @@
| `hit` | `hit`
| A gun shot landed on self; other is the gun. | A gun shot landed on self; other is the gun.
| `kind` (kind), `player` (number) | `player` (number), `type` (type)
| `leave` | `leave`
| An instance of kind b left a trigger of kind a. | An instance of type b left a trigger of type a.
| `a` (kind), `b` (kind) | `a` (type), `b` (type)
| `midi` | `midi`
| A MIDI note came in on the port the game opened; event.pitch and event.velocity say which and how hard. (pitch, since note is what a rule's comment is called.) | A MIDI note came in on the port the game opened; event.pitch and event.velocity say which and how hard. (pitch, since note is what a rule's comment is called.)
@ -300,7 +300,7 @@
| `patrolEnd` | `patrolEnd`
| A patrolling instance reached the last of its points. | A patrolling instance reached the last of its points.
| `kind` (kind) | `type` (type)
| `pressed` | `pressed`
| A key or a switch went down. | A key or a switch went down.
@ -326,9 +326,13 @@
| A line was typed at the parser: its verb, noun, and second noun as the words table knows them, or unknown for a word it does not. The most specific rule wins. | A line was typed at the parser: its verb, noun, and second noun as the words table knows them, or unknown for a word it does not. The most specific rule wins.
| `noun` (string), `second` (string), `verb` (string) | `noun` (string), `second` (string), `verb` (string)
| `soundDone`
| A clip that playSound or a sound behaviour started has ended; event.file says which, and self is the instance that played it.
| `file` (file), `type` (type)
| `spawn` | `spawn`
| self has just been made. | self has just been made.
| `kind` (kind) | `type` (type)
| `stopped` | `stopped`
| A rail camera reached a stop; event.name says which. pathNext moves it on. | A rail camera reached a stop; event.name says which. pathNext moves it on.
@ -336,7 +340,7 @@
| `timer` | `timer`
| A timer on self went off; event.name says which. | A timer on self went off; event.name says which.
| `kind` (kind), `name` (string) | `name` (string), `type` (type)
| `verb` | `verb`
| A verb was used on a hotspot (self), with an item or not: "use key on door". The most specific rule wins. | A verb was used on a hotspot (self), with an item or not: "use key on door". The most specific rule wins.
@ -624,13 +628,13 @@
| |
| `spawn` | `spawn`
| Make an instance of a kind at a point. | Make an instance of a type at a point.
| `kind` (kind), `x` (number), `y` (number), `z` (number) | `type` (type), `x` (number), `y` (number), `z` (number)
| |
| `spawnAtPointer` | `spawnAtPointer`
| Make an instance of a kind where the player's pointer is: on the overlay in 2D, on the floor the ray meets in 3D. | Make an instance of a type where the player's pointer is: on the overlay in 2D, on the floor the ray meets in 3D.
| `kind` (kind), `player` (number) | `player` (number), `type` (type)
| |
| `stopMusic` | `stopMusic`

View file

@ -7170,7 +7170,7 @@ guiDraw(gui, x, y)
guiDraw(gui, x, y, width, height) guiDraw(gui, x, y, width, height)
---- ----
Composites the GUI's texture over the overlay for this frame, in overlay coordinates, so it obeys the Sinden border and the overscan scale like everything else on the overlay: covering the whole overlay with one argument, at its own size with a corner at `x, y`, or scaled into the `width` by `height` rectangle. The call lasts one frame; make it from `onOverlayUpdate` (or `singeMain`) every frame the GUI should be visible. GUIs draw above the overlay in the order called (the last on top), under `PARTICLE_OVER` particles and the pause indicator, and the GUI drawn last under the pointer is the one that receives the mouse. Up to sixteen calls are honored per frame; more are ignored. Any other argument count raises `Expected 1, 3 or 5 arguments`. Composites the GUI's texture over the overlay for this frame, in overlay coordinates, so it obeys the Sinden border and the overscan scale like everything else on the overlay: covering the whole overlay with one argument, at its own size with a corner at `x, y`, or scaled into the `width` by `height` rectangle. The call's place in the script's drawing decides what is under the GUI and what is over it: everything drawn on the overlay before it stays underneath, and everything drawn after it -- until the next `guiDraw`, or the script's next turn -- goes on a layer of its own composited over that GUI. A pointer drawn after the HUD is drawn over the HUD; a second document drawn after that sits over the pointer, with its own layer above. Up to sixteen such layers a frame; a call past that draws its GUI with nothing over it. `overlayClear` clears whichever layer is being drawn on, and `overlaySetMonochrome` greys the base overlay only. The call lasts one frame; make it from `onOverlayUpdate` (or `singeMain`) every frame the GUI should be visible. GUIs draw above the overlay in the order called (the last on top), under `PARTICLE_OVER` particles and the pause indicator, and the GUI drawn last under the pointer is the one that receives the mouse. Up to sixteen calls are honored per frame; more are ignored. Any other argument count raises `Expected 1, 3 or 5 arguments`.
*Parameters:* *Parameters:*
@ -7183,11 +7183,14 @@ Composites the GUI's texture over the overlay for this frame, in overlay coordin
.Example .Example
[source,lua] [source,lua]
---- ----
-- The HUD sits in the bottom right corner at a third of the overlay's width. -- The HUD sits in the bottom right corner at a third of the overlay's width, and the gun's
-- sight is drawn after it, so the sight is never hidden by the panel.
function onOverlayUpdate() function onOverlayUpdate()
overlayClear() overlayClear()
guiSetValue(hud, hudPage, "lives", tostring(lives)) guiSetValue(hud, hudPage, "lives", tostring(lives))
guiDraw(hud, overlayGetWidth() * 0.66, overlayGetHeight() * 0.8, overlayGetWidth() * 0.33, overlayGetHeight() * 0.2) guiDraw(hud, overlayGetWidth() * 0.66, overlayGetHeight() * 0.8, overlayGetWidth() * 0.33, overlayGetHeight() * 0.2)
local x, y = mouseGetPosition(0)
spriteDraw(sight, x, y, true)
return OVERLAY_UPDATED return OVERLAY_UPDATED
end end
---- ----
@ -7457,7 +7460,7 @@ guiSetHandler(gui, document, id, event, function)
guiSetHandler(gui, document, id, event, nil) guiSetHandler(gui, document, id, event, nil)
---- ----
Calls `function(gui, document, id, event, value)` whenever the element with that `id` fires the named event: `"click"` for buttons and rows, `"change"` for form controls, `"submit"` for a form, or any other RmlUi event name (`"focus"`, `"mouseover"`, `"keydown"`, ...). `value` is the element's value at that moment as a string, as `guiGetValue` would return it (a range comes as RmlUi formats it, `"85.000000"`, so pass it through `tonumber`). One function is kept per element and event; setting another replaces it, and `nil` removes it. Handlers run as the events happen, between the game's own callbacks, and an error in one ends the game like an error in any callback. Ids are at most 63 characters. Raises `No element "id" in document N of GUI G` when the element does not exist, and `Argument 5 must be a function or nil` for anything else in that place. Calls `function(gui, document, id, event, value)` whenever the element with that `id` fires the named event: `"click"` for buttons and rows, `"change"` for form controls, `"submit"` for a form, or any other RmlUi event name (`"focus"`, `"mouseover"`, `"keydown"`, ...). `value` is the element's value at that moment as a string, as `guiGetValue` would return it (a range comes as RmlUi formats it, `"85.000000"`, so pass it through `tonumber`). One function is kept per element and event; setting another replaces it, and `nil` removes it. The handler belongs to the `id`, not to one element: when the element is replaced -- a list rebuilt through `inner_rml` makes new elements -- set the handler again after the rebuild and it attaches to the element that carries the id now. Handlers run as the events happen, between the game's own callbacks, and an error in one ends the game like an error in any callback. Ids are at most 63 characters. Raises `No element "id" in document N of GUI G` when the element does not exist, and `Argument 5 must be a function or nil` for anything else in that place.
*Parameters:* *Parameters:*
@ -7489,7 +7492,7 @@ end)
guiSetInput(gui, enabled) guiSetInput(gui, enabled)
---- ----
Whether the mouse and light gun, keys, typed text and the framework switches from a pad or mouse reach the GUI. A new GUI only displays, which suits a HUD or a sign; turn input on for a menu or a form. While it is on, the pointer reaches the GUI wherever it is drawn flat (the last drawn on top) or shown on a surface in the scene, every key goes to it, a text field with focus turns on SDL's text input, and the pad's directions arrive as arrow keys with `ACTION_1` as Return and `ACTION_2` as Escape. What an element uses never reaches `onInputPressed`, `onKeyPressed` or the mouse callbacks; what nothing used falls through to them as usual. Raises an error for a handle that is not valid. Whether the mouse and light gun, keys, typed text and the framework switches from a pad or mouse reach the GUI. A new GUI only displays, which suits a HUD or a sign; turn input on for a menu or a form. While it is on, the pointer reaches the GUI wherever it is drawn flat (the last drawn on top) or shown on a surface in the scene, every key goes to it except `Tab`, which the GUI is given only while a text field is being typed in (RmlUi uses it to move focus and never hands it back, and a game whose keys include `Tab` would otherwise lose it), a text field with focus turns on SDL's text input, and the pad's directions arrive as arrow keys with `ACTION_1` as Return and `ACTION_2` as Escape. What an element uses never reaches `onInputPressed`, `onKeyPressed` or the mouse callbacks; what nothing used falls through to them as usual. Raises an error for a handle that is not valid.
*Since:* 3.00. *Since:* 3.00.
*See also:* <<guisethandler,guiSetHandler>>, <<guidraw,guiDraw>>, <<sceneprobegui,sceneProbeGui>> *See also:* <<guisethandler,guiSetHandler>>, <<guidraw,guiDraw>>, <<sceneprobegui,sceneProbeGui>>
@ -7994,7 +7997,7 @@ end
keyboardSetMode(mode) keyboardSetMode(mode)
---- ----
Switches between the two keyboard models. In `MODE_NORMAL` only keys mapped in `controls.cfg` reach the script, as `SWITCH_*` values, key repeat is dropped so each press arrives once, and the engine acts on its own switches (pause, quit, screenshot, mouse grab). In `MODE_FULL` every key reaches `onInputPressed` and `onInputReleased` as its keysym and `onKeyPressed` and `onKeyReleased` as keysym and scancode, key repeat is delivered for text entry, and keyboard mappings of the engine's own switches are ignored so that the game keeps every key; controller and mouse button mappings still act. Most games set the mode once at startup; a game with text entry switches to `MODE_FULL` for the duration and back. Any other value aborts the script. Switches between the two keyboard models. In `MODE_NORMAL` only keys mapped in `controls.cfg` reach the script, as `SWITCH_*` values, key repeat is dropped so each press arrives once, and the engine acts on its own switches (pause, quit, screenshot, mouse grab). In `MODE_FULL` every key reaches `onInputPressed` and `onInputReleased` as its keysym and `onKeyPressed` and `onKeyReleased` as keysym and scancode, the text a press produces reaches `onTextInput`, key repeat is delivered for text entry, and keyboard mappings of the engine's own switches are ignored so that the game keeps every key; controller and mouse button mappings still act, and those buttons still reach the script as their `SWITCH_*` values, since they cannot be typed. Most games set the mode once at startup; a game with text entry switches to `MODE_FULL` for the duration and back. Any other value aborts the script. In `MODE_FULL` the keysyms of four keys are also switch numbers -- Backspace is `8` (`SWITCH_BUTTON3`), Tab `9` (`SWITCH_COIN1`), Return `13`, and Escape `27` -- so a script that reads `onInputPressed` for a pad or mouse switch and also expects those keys should check `keyboardIsDown` for the key before treating the value as the switch.
*Parameters:* *Parameters:*
@ -14564,9 +14567,10 @@ end
[source,text] [source,text]
---- ----
singeScreenshot() singeScreenshot()
singeScreenshot(name)
---- ----
Requests a screenshot of the whole window, letterbox included, taken after the next frame is drawn and saved as a PNG in the game's data directory (see `singeGetDataPath`). Files are named `singe000.png` upward; each script run scans from zero for the first free name and later shots continue past the last one saved. The call forces a redraw so the shot is taken even while the display is idle. The switch mapped to `INPUT_SCREENSHOT` in `controls.cfg` does the same thing. Requests a screenshot of the whole window, letterbox included, taken after the next frame is drawn and saved as a PNG in the game's data directory (see `singeGetDataPath`). Without a name, files are named `singe000.png` upward; each script run scans from zero for the first free name and later shots continue past the last one saved. With a name the shot goes to that file, `.png` added when the name has no extension, replacing any file of that name; a name with a directory in it aborts the script. Naming the shot is for scripts that produce pictures on purpose, such as the ones that illustrate the Forge tutorials, so a rerun replaces the picture rather than adding one. The call forces a redraw so the shot is taken even while the display is idle. The switch mapped to `INPUT_SCREENSHOT` in `controls.cfg` does the same thing.
*Since:* 1.x *Since:* 1.x
*See also:* <<singegetdatapath,singeGetDataPath>> *See also:* <<singegetdatapath,singeGetDataPath>>
@ -18966,6 +18970,20 @@ Called in `MODE_FULL` only, for every key going down or up, with both the logica
*See also:* <<keyboardsetmode,keyboardSetMode>>, <<keyboardgetmodifiers,keyboardGetModifiers>>, <<oninputpressedoninputreleased,onInputPressed>> *See also:* <<keyboardsetmode,keyboardSetMode>>, <<keyboardgetmodifiers,keyboardGetModifiers>>, <<oninputpressedoninputreleased,onInputPressed>>
[#ontextinput]
==== onTextInput
[source,text]
----
function onTextInput(text)
end
----
Called in `MODE_FULL` only, with the text a key press produced -- a character or a few, in UTF-8 -- after the keyboard's shift state and layout have been applied, so a capital, a quote, or a letter from a non-English layout arrives as the person typed it. `onKeyPressed` gives the same press as its unshifted keysym and scancode and is the one to use for keys that are commands; this is the one to use for keys that are text. A GUI with a focused text field takes the text first, and the script hears nothing of it.
*Since:* 3.00.
*See also:* <<onkeypressedonkeyreleased,onKeyPressed>>, <<keyboardsetmode,keyboardSetMode>>
.Example .Example
[source,lua] [source,lua]
---- ----

BIN
docs/images/tutorials/01-chooser.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/01-entities.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/01-moved.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/01-playing.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/01-recoloured.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/01-rules.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/01-saved.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/01-types.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/02-cave.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/02-coinType.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/02-coins.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/02-placed.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/02-playing.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/02-rule.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/02-rules.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/03-coinSpin.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/03-heroType.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/03-jumpRule.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/03-music.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/03-playing.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/04-cleared.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/04-hitRule.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/04-placed.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/04-playing.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/04-rules.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/04-ship.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/04-types.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/05-disc.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/05-playing.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/05-prompts.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/05-rules.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/05-window.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/06-firstKey.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/06-playing.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/06-rules.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/06-tracks.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/06-types.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/07-dialogue.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/07-hall.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/07-hotspots.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/07-playing.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/07-verbRules.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/07-walkArea.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/08-hero.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/08-meshes.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/08-playing.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/08-rules.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/08-scene.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/08-viewport.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/09-hitRule.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/09-placed.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/09-playing.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/09-rail.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/09-room.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
docs/images/tutorials/09-rules.png (Stored with Git LFS) Normal file

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show more