singe/docs/lessons/13-game-folder.adoc
2026-09-22 21:57:42 -05:00

315 lines
12 KiB
Text

== Lesson 13: A Game Folder
image::learn/13-game-folder.png[The finished lesson, 480]
Your game is a script in a folder you remember the name of, started by typing
its name at a terminal. That is fine for you and useless for anybody else. A
player should be able to copy a folder next to Singe, start the menu, see your
game listed with its title and its artwork, and press a button.
That takes one new file, about six lines long, and one change to your script.
Nothing else about the game changes at all.
=== Give the Game a Name
A game lives in a folder of its own, and the folder, the script inside it, and
the game's name should all match. Rename your folder `Rocks` and the script
inside it `Rocks.singe`.
Do not put a space in the folder name. Match the capitals exactly wherever you
type it: on most computers that are not Windows, `rocks` and `Rocks` are two
different folders, and a game that only runs on your machine is not a game
anybody else can have.
When the folder and the script share a name, Singe will find the script from
the folder alone, so both of these run your game:
----
Singe Rocks/Rocks.singe
Singe Rocks
----
=== What Goes in the Folder
Everything your game needs, and nothing else:
----
Rocks/
games.dat What the menu reads
Rocks.singe The game
art/
ship.png
rock.png
shot.png
star.png
shoot.wav
boom.wav
cabinet.png Artwork the menu shows beside the list
marquee.png Artwork the menu shows above it
----
The rule behind that layout is that a game never reaches outside its own
folder. Not for artwork, not for a sound, and not for a library you also use in
another game -- if you share code between two games, each one keeps its own
copy. There is exactly one file outside the folder a game may load, and you are
already loading it: `Singe/Framework.singe`.
The folder itself sits beside the Singe program:
----
Singe-v3.00-Linux-x86_64 The engine itself, whatever yours is called
Menu.sh Starts the menu. Menu.bat on Windows
Singe/ Support files. Not yours; leave them alone
Rocks/ Your game
data/ Everything anything writes, your save among it
----
The engine wrote `Menu.sh`, the `Singe` folder, and `data` for you, the first
time you ever ran it. Those are what appeared in your work folder in lesson
one.
=== Paths Count from Where Singe Started
Here is the change your script needs, and the reason it needs it.
Every name your game loads -- `art/ship.png`, `art/boom.wav` -- is looked for
starting at the folder Singe was *started in*, not the folder your script is
in. All through this book those have been the same folder, because you stood
inside the game's folder and typed `Singe rocks`. Start it from the
folder above, or start it from the menu, and they are not the same folder at
all, and not one of your files is found.
`Framework.singe` sets a variable for the answer. `DIR` is your script's own
folder, with the separator already on the end, so a name glued to it points at
your file from wherever Singe was started:
[source,lua]
----
local shipSprite = spriteLoad(DIR .. "art/ship.png")
local rockSprite = spriteLoad(DIR .. "art/rock.png")
local shotSprite = spriteLoad(DIR .. "art/shot.png")
local shootSound = soundLoad(DIR .. "art/shoot.wav")
local boomSound = soundLoad(DIR .. "art/boom.wav")
----
`..` is the same glue you used for `"SCORE " .. score` in lesson twelve, doing
the same job on a file name.
Do that to every name your game loads, every time, from now on. It costs seven
characters and it is the difference between a game that runs on your machine
and a game that runs.
=== games.dat
`games.dat` is how a game introduces itself. The menu reads the `games.dat` of
every game folder beside it, and lists what it finds. No `games.dat`, no
listing: your folder is invisible, however good the game inside it is.
Make a new file called `games.dat` in your `Rocks` folder. This is the smallest
one that works:
[source,lua]
----
GAMES = {
{
TITLE = "Rocks",
SCRIPT = "Rocks/Rocks.singe"
}
}
----
That is Lua, and it is a table of the kind you met in lesson six: `GAMES` is a
list, and each thing in the list is a record describing one game. One folder
can hold several games -- that is why it is a list -- but yours holds one.
`SCRIPT` is the only field the engine truly needs, and its path is counted from
the folder *above* the `games.dat`, not from the folder the `games.dat` is in.
So a `games.dat` inside `Rocks` names its own script as `Rocks/Rocks.singe`.
That looks like it says `Rocks` twice and it is correct. Every other path in
the file works the same way.
`TITLE` is what the menu shows. Games are listed in title order, so a title is
also where your game sits in the list.
=== Telling the Menu More
The menu has a whole panel to fill beside the list, and it fills it from the
rest of the entry:
[source,lua]
----
GAMES = {
{
TITLE = "Rocks",
SCRIPT = "Rocks/Rocks.singe",
CANVAS_X = 720,
CANVAS_Y = 480,
YEAR = 2026,
GENRE = "Shooter",
DEVELOPER = "Your Name Here",
PUBLISHER = "Your Name Here",
DESCRIPTION = "Shoot the rocks before they reach your ship.",
CABINET = "Rocks/cabinet.png",
MARQUEE = "Rocks/marquee.png"
}
}
----
`CABINET` and `MARQUEE` are ordinary image files in your game folder, in any
format Singe can read. The cabinet art is the big picture beside the list and
the marquee is the strip above it. Make them whatever size you like; the menu
fits them into their panels. Leave either out and the menu manages without it.
`YEAR`, `GENRE`, `DEVELOPER`, `PUBLISHER`, and `DESCRIPTION` are the text
underneath, and the engine never looks at them. They are for the person reading
the menu, which is the point.
`CANVAS_X` and `CANVAS_Y` are the size of your game's world, and 720 by 480 is
the default -- the same world you have been drawing in since lesson one. Set
them only if your game wants a different shape. A game with no `VIDEO` field is
a game with no video, which is what yours is; lesson fourteen is where that
field starts to matter.
The manual's `games.dat` section lists every field there is, including several
for hardware you do not have yet. Two are worth knowing the names of now.
`GAME_ID` is a unique identity for your game, which the online high score
tables in lesson twenty-eight need and nothing else does. `RESOLUTION_X` and
`RESOLUTION_Y` ask for a particular window size. Ignore the rest until
something sends you looking.
=== Running It from the Menu
Run `Menu.sh` -- `Menu.bat` on Windows -- from the folder Singe lives in.
Your game is in the list, sorted among anything else that is there, with its
artwork beside it and its description underneath. Up and down move one game,
left and right move a page, and start or a fire button plays the one that is
selected. Quit the game with Escape and you are back at the menu, on the same
game, ready to go again. The menu remembers which game you were on between
runs.
That is the whole of shipping a game, as far as the engine is concerned. A
player copies your folder next to Singe and it appears.
Typing the name still works, and works the same way:
----
Singe Rocks
----
When you launch a script by hand like that, Singe looks for a `games.dat`
beside it -- and in up to three folders above it -- and if an entry names the
script you launched, that entry's settings apply to it. So a game plays the
same from a terminal as from the menu, which means you can go on working the
way you have been, with `-R` and a text editor, and still trust what a player
will get.
=== Later: One File
A folder is easy to work in and slightly awkward to give away: a dozen files, a
folder inside a folder, and a player who unzips it into the wrong place. A
finished Singe game can be packed into a single `.game` file that the engine
runs directly, artwork and sounds and all, which a player copies next to Singe
and is done.
Nothing in your game has to change for that. It is lesson twenty-nine, and it
is worth waiting for: pack a game that is not finished and you will pack it
again tomorrow.
=== What Just Happened
[source,lua]
----
local shipSprite = spriteLoad(DIR .. "art/ship.png")
----
`DIR` is a variable `Singe/Framework.singe` sets before your script runs, and
holds your script's own folder with a trailing `/` on it. It exists only
because your script loads the framework, so if the first line of your script is
not `dofile("Singe/Framework.singe")`, put it there now. This is the first time
the framework has given you something you could not manage without.
[source,lua]
----
GAMES = {
{
----
Two braces, because it is a list of records. The outer one is the list. The
inner one is the first thing in the list, which is the record for one game.
Getting one of them wrong is the usual mistake, and Lua will tell you the line.
[source,lua]
----
SCRIPT = "Rocks/Rocks.singe",
----
The comma at the end of each line separates one field from the next, exactly as
in lesson six. The last field in a record has no comma after it -- Lua does not
mind if it does, but leave it off and your file will look like everybody
else's.
=== Try It
. *Make the artwork.* Draw a `cabinet.png` and a `marquee.png`, put them in
your game folder, name them in `games.dat`, and look at the menu.
. *Move your game in the list.* Change `TITLE` to `Asteroid Field` and start
the menu again. The list is sorted by title, not by folder.
. *Break `games.dat`.* Delete one of the closing braces and start the menu.
Your game is gone from the list; the others are still there. Put it back.
. *Two entries, one folder.* Add a second record to `GAMES` pointing at your
lesson seven script, with a title of its own. One folder, two games in the
menu.
. *Find your save.* You changed how the game is started, so ask yourself where
its high score went. Print `singeGetDataPath()` and look. It is not the same
folder it was last lesson, and now you know why it is worth asking rather
than assuming.
=== Break It on Purpose
Take the `DIR ..` back off one line, so it reads `spriteLoad("art/ship.png")`
again, and start the game from the folder above -- `Singe Rocks`, or from the
menu. The game dies before it draws anything, and the terminal says:
----
24:spriteLoad: Couldn't open art/ship.png: No such file or directory
----
The exact wording after the colon comes from your operating system and differs
a little between them, but the shape is the same everywhere: the line, the
engine function that gave up, and the name it could not find.
It could not find `art/ship.png` because it looked in the folder you started
Singe in, which is the folder holding `Rocks`, and there is no `art` there.
The file it wanted is one folder further down, which is exactly what `DIR`
knows and a bare name does not.
This is the error you will meet the first time you hand your game to somebody
else, if you meet it at all, because it is invisible from inside the game's own
folder. Put the `DIR ..` back and start it from both places.
=== What You Learned
* A game is a folder: the script, its artwork, and a `games.dat`, and nothing
from outside it except `Singe/Framework.singe`.
* Name the folder and the script the same, with no spaces, and `Singe Rocks`
will find it.
* Names your script loads are counted from where Singe was started, so build
every one of them from `DIR`.
* `games.dat` is a Lua file holding a `GAMES` list, and each entry is one game.
* An entry needs `TITLE` and `SCRIPT` and nothing else; paths in it count from
the folder above the `games.dat`.
* `CABINET`, `MARQUEE`, `YEAR`, `GENRE`, `DEVELOPER`, `PUBLISHER`, and
`DESCRIPTION` are what the menu shows the player.
* `Menu.sh` or `Menu.bat` starts the bundled menu, which lists every game
folder beside it, sorted by title.
* A game launched by name gets its `games.dat` entry too, so it behaves the
same either way.
* A finished game can be packed into one file later, in lesson twenty-nine.
=== Next Time
That is part two finished: you have a game, with art, sound, collision, score,
and a way for somebody else to run it. Part three is about the thing almost no
other engine does -- playing film as the world the game happens in -- and
lesson fourteen starts with the disc, the frame, and the wait.