232 lines
6.1 KiB
Text
232 lines
6.1 KiB
Text
-- Forge tutorial 4, "A shoot-em-up": a new game with no gravity, a ship on the keys, shots,
|
|
-- rocks from spawners, health and death, lives, and game over. Starts from New game.
|
|
TUTORIAL = { number = 4, name = "04-shmup", tag = "TUTORIAL4" }
|
|
dofile("testScripts/tutorialDrive.singe")
|
|
|
|
local frames = 0
|
|
local stage = 0
|
|
local game = nil
|
|
|
|
tutorialBegin(nil)
|
|
|
|
|
|
local function lightType(name)
|
|
local names = {}
|
|
local from, to = 1, 1
|
|
|
|
for typeName in pairs(FORGE.game.types) do
|
|
names[#names + 1] = typeName
|
|
end
|
|
table.sort(names)
|
|
for at, typeName in ipairs(names) do
|
|
if typeName == FORGE.typeName then
|
|
from = at
|
|
end
|
|
if typeName == name then
|
|
to = at
|
|
end
|
|
end
|
|
for _ = 1, math.abs(to - from) do
|
|
key((to > from) and SCANCODE.DOWN or SCANCODE.UP)
|
|
end
|
|
end
|
|
|
|
|
|
local function ruleGo(index, part)
|
|
local guard = 0
|
|
|
|
while ((FORGE.rule ~= index) or (FORGE.part ~= (part or 0))) and (guard < 60) do
|
|
local past = (FORGE.rule > index) or ((FORGE.rule == index) and (FORGE.part > (part or 0)))
|
|
|
|
key(past and SCANCODE.UP or SCANCODE.DOWN)
|
|
guard = guard + 1
|
|
end
|
|
end
|
|
|
|
|
|
local function kitType(name, file)
|
|
key(SCANCODE.MAIN_2)
|
|
key(SCANCODE.A)
|
|
form("name", name)
|
|
form("look", "sprite")
|
|
form("look.file", file)
|
|
end
|
|
|
|
|
|
local function place(x, y)
|
|
key(SCANCODE.MAIN_1)
|
|
key(SCANCODE.A)
|
|
local entry = forgeRoom().entities[FORGE.selected]
|
|
|
|
forgePress(entry.x, entry.y)
|
|
forgeDrag(x, y)
|
|
forgeRelease()
|
|
end
|
|
|
|
|
|
-- A rule from scratch: N, its note, E and its event, then the filter fields.
|
|
local function rule(note, on, filters)
|
|
key(SCANCODE.N)
|
|
form("note", note)
|
|
key(SCANCODE.E)
|
|
pick(on)
|
|
for _, pair in ipairs(filters or {}) do
|
|
form(pair[1], pair[2])
|
|
end
|
|
end
|
|
|
|
|
|
-- A part: C or T, its name, and its fields.
|
|
local function part(partKey, name, fields)
|
|
key(partKey)
|
|
pick(name)
|
|
for _, pair in ipairs(fields or {}) do
|
|
form(pair[1], pair[2])
|
|
end
|
|
end
|
|
|
|
|
|
local function step()
|
|
if stage == 1 then
|
|
-- Clear the starter: the hero and the ground go, types and all, and so do their rules.
|
|
key(SCANCODE.MAIN_1)
|
|
selectEntity("hero")
|
|
key(SCANCODE.DELETE)
|
|
selectEntity("ground")
|
|
key(SCANCODE.DELETE)
|
|
key(SCANCODE.MAIN_2)
|
|
lightType("hero")
|
|
key(SCANCODE.DELETE)
|
|
lightType("ground")
|
|
key(SCANCODE.DELETE)
|
|
key(SCANCODE.MAIN_3)
|
|
ruleGo(1, 0)
|
|
key(SCANCODE.DELETE)
|
|
key(SCANCODE.DELETE)
|
|
key(SCANCODE.DELETE)
|
|
-- The game itself: no world, so no gravity; lives to lose.
|
|
key(SCANCODE.MAIN_1)
|
|
forgeSelect(nil)
|
|
form("title", "Rocks")
|
|
form("vars", "score=0, lives=3")
|
|
form("layers", "overlay")
|
|
tutorialShot("cleared")
|
|
|
|
elseif stage == 2 then
|
|
kitType("ship", "ship.png")
|
|
form("vars", "health=1")
|
|
form("behaviours", "keys, mover, shooter, health, sound")
|
|
form("keys.left", "LEFT")
|
|
form("keys.right", "RIGHT")
|
|
form("keys.up", "UP")
|
|
form("keys.down", "DOWN")
|
|
form("keys.fire", "SPACE")
|
|
form("mover.speed", 260)
|
|
form("mover.clamp", "true")
|
|
form("shooter.spawns", "shot")
|
|
form("shooter.rate", 0.15)
|
|
form("shooter.offsetY", -14)
|
|
form("health.max", 1)
|
|
form("health.keep", "true")
|
|
form("sound.hit", "hit.wav")
|
|
tutorialShot("ship")
|
|
|
|
elseif stage == 3 then
|
|
kitType("shot", "shot.png")
|
|
form("behaviours", "projectile, sound")
|
|
form("projectile.vy", -520)
|
|
form("projectile.life", 3)
|
|
form("sound.spawn", "shot.wav")
|
|
kitType("rock", "rock.png")
|
|
form("vars", "health=2")
|
|
form("behaviours", "drift, health, sound")
|
|
form("drift.vy", 90)
|
|
form("health.max", 2)
|
|
form("sound.death", "hit.wav")
|
|
key(SCANCODE.A)
|
|
form("name", "hive")
|
|
form("look", "none")
|
|
form("behaviours", "spawner")
|
|
form("spawner.spawns", "rock")
|
|
form("spawner.every", 0.6)
|
|
form("spawner.max", 6)
|
|
form("spawner.total", 12)
|
|
tutorialShot("types")
|
|
|
|
elseif stage == 4 then
|
|
lightType("ship")
|
|
place(360, 420)
|
|
key(SCANCODE.MAIN_2)
|
|
lightType("hive")
|
|
place(360, 20)
|
|
key(SCANCODE.D)
|
|
forgePress(380, 40)
|
|
forgeDrag(560, 20)
|
|
forgeRelease()
|
|
tutorialShot("placed")
|
|
|
|
elseif stage == 5 then
|
|
key(SCANCODE.MAIN_3)
|
|
rule("A shot lands on a rock", "collision", { { "a", "shot" }, { "b", "rock" } })
|
|
part(SCANCODE.T, "destroy", {})
|
|
part(SCANCODE.T, "damage", { { "entity", "other" }, { "amount", 1 } })
|
|
part(SCANCODE.T, "addScore", { { "amount", 10 } })
|
|
rule("A rock dies", "death", { { "type", "rock" } })
|
|
part(SCANCODE.T, "emit", { { "count", 24 }, { "r", 255 }, { "g", 120 }, { "b", 60 } })
|
|
part(SCANCODE.T, "addScore", { { "amount", 50 } })
|
|
tutorialShot("hitRule")
|
|
|
|
elseif stage == 6 then
|
|
rule("A rock rams the ship", "collision", { { "a", "rock" }, { "b", "ship" } })
|
|
part(SCANCODE.T, "destroy", {})
|
|
part(SCANCODE.T, "damage", { { "entity", "other" }, { "amount", 1 } })
|
|
rule("The ship is lost", "death", { { "type", "ship" } })
|
|
part(SCANCODE.T, "addVar", { { "name", "lives" }, { "amount", -1 } })
|
|
clear("entity")
|
|
part(SCANCODE.T, "flash", { { "r", 255 }, { "g", 60 }, { "b", 60 }, { "seconds", 0.3 } })
|
|
part(SCANCODE.T, "setVar", { { "name", "health" }, { "value", "1" } })
|
|
part(SCANCODE.T, "moveTo", { { "x", 360 }, { "y", 420 } })
|
|
rule("Out of lives", "frame", {})
|
|
part(SCANCODE.C, "test", { { "expr", "lives <= 0" } })
|
|
part(SCANCODE.T, "gameOver", {})
|
|
rule("A rock slips past the bottom", "frame", { { "each", "rock" } })
|
|
part(SCANCODE.C, "test", { { "expr", "self.y > 500" } })
|
|
part(SCANCODE.T, "destroy", {})
|
|
-- The readout, kept from the starter, says the lives too.
|
|
ruleGo(1, 1)
|
|
form("text", '"score " .. score .. " lives " .. lives')
|
|
tutorialShot("rules")
|
|
|
|
elseif stage == 7 then
|
|
key(SCANCODE.S)
|
|
local built = tutorialEnd()
|
|
local mine = onOverlayUpdate
|
|
|
|
dofile(built)
|
|
game = onOverlayUpdate
|
|
onOverlayUpdate = mine
|
|
authorKeyDown(0, SCANCODE.SPACE.value)
|
|
|
|
elseif stage == 19 then
|
|
tutorialShot("playing")
|
|
debugPrint(string.format("%s after firing a while: score %s, lives %s, %d rocks, %d shots", TUTORIAL.tag, tostring(AUTHOR_VARS.score), tostring(AUTHOR_VARS.lives), authorCount("rock"), authorCount("shot")))
|
|
elseif stage == 20 then
|
|
debugPrint(TUTORIAL.tag .. " RESULT done")
|
|
singeQuit()
|
|
end
|
|
end
|
|
|
|
|
|
function onOverlayUpdate()
|
|
frames = frames + 1
|
|
if frames % 12 == 0 and stage < 20 then
|
|
stage = stage + 1
|
|
step()
|
|
end
|
|
if game ~= nil then
|
|
return game()
|
|
end
|
|
forgeDraw(nil, nil)
|
|
|
|
return OVERLAY_UPDATED
|
|
end
|