singe/docs/lessons/01-hello.adoc
2026-09-22 21:57:42 -05:00

273 lines
10 KiB
Text

== Lesson 1: Hello
image::learn/01-hello.png[The finished lesson, 480]
That is what you will have at the end of this lesson. It is not much to look
at. It is also a program you wrote, running on a real game engine, and
everything else in this book is built on the five lines that made it.
=== Set Up Your Work Folder
Make one folder to keep everything in, called `singe` or whatever you like,
and put your copy of Singe in it. This is your *work folder*, and every
lesson in this book puts a new folder inside it. You will make it once and
never think about it again.
Inside the work folder, make a folder called `hello`, and inside that a file
called `hello.singe`.
[literal]
----
singe/ <- your work folder, with Singe in it
hello/
hello.singe
----
That nesting is not fussiness. Singe treats the folder you run it from as the
place where games live, one folder per game, and it keeps its own files
there too. Your game goes in its own folder, one level down. From lesson two
onward each lesson is another folder beside `hello`.
Two things about the names. The part after the dot, `.singe`, is how Singe
knows this is a script. And on most computers that are not Windows, capital
letters count: `Hello.singe` and `hello.singe` are two different files. Get
into the habit now of typing names exactly.
Giving the folder and the script the same name matters as well, and you will
see why in a moment.
Open `hello.singe` in your text editor. It is empty. That is correct.
=== Type This In
[source,lua]
----
function onOverlayUpdate()
overlayClear()
overlayPrint(2, 2, "Hello!")
return OVERLAY_UPDATED
end
----
Save it.
Do not worry yet about what any of it means. Two things are worth noticing
before you run it, because they will be true of every program you ever write.
The first is that the spaces at the start of the middle three lines are there
on purpose. They do nothing at all to the program -- Singe would run it
exactly the same without them -- but they show you at a glance that those
three lines belong *inside* the thing that starts with `function` and ends
with `end`. Every programmer does this. Do it from the first day and you will
never have to learn it later.
The second is the order of the lines. They happen top to bottom, one after
another, like the steps of a recipe. That is the single most important thing
about a program, and almost everything that confuses a beginner comes from
forgetting it.
=== Run It
Open a terminal, go to your *work folder* -- not the `hello` folder inside it
-- and name the folder you want to run:
----
Singe hello
----
On Windows that is `Singe.exe hello`. If your copy of Singe lives somewhere
else, type the path to it, such as `../Singe hello`.
That is why the folder and the script share a name. Given a folder, Singe
looks inside it for a script called the same thing. Name them differently and
you get `Error: Unable to locate the game.`, which is the engine saying it
looked and found nothing it recognised. You can always be explicit instead and
write `Singe hello/hello.singe`, which does the same job.
A black window opens with the word `Hello!` near the top left corner. Press
*Escape* or *Q* to quit.
Two things will have happened that you did not ask for, and both are normal.
Folders called `Singe` and `data` appeared in your work folder, beside
`hello`: the first is the engine unpacking the files it needs, the second is
where anything your games save will go. Leave both alone. And the window is
mostly black, because you have not drawn anything except six letters.
=== What Just Happened
Now the five lines, one at a time.
[source,lua]
----
function onOverlayUpdate()
----
`function` starts a group of instructions and gives it a name. Everything from
here down to the matching `end` belongs to it. This one is called
`onOverlayUpdate`, and the empty parentheses after the name will make sense in
lesson five.
That name is not yours to choose. Singe looks for a function with exactly that
name and calls it about sixty times a second, forever, so that you have a
chance to draw. If you spell it `onoverlayupdate` or `onOverlayUpdated`, Singe
will not find it, your window will stay black, and nothing will tell you you
made a mistake. That is the first real lesson: the computer does what you
wrote, not what you meant.
A function that you write and the engine calls is a *callback*. You do not
call it yourself, and you cannot say when it runs; you write it, and Singe
calls you back when there is something to do. Every name in this book that
begins with `on` is a callback, and there is one for nearly everything that
can happen: a button pressed, a key typed, a sound finishing, the disc
reaching a frame. `onOverlayUpdate` is the one for drawing, and it is the only
place drawing is allowed.
[source,lua]
----
overlayClear()
----
This is a function too, but not one you have to write: Singe provides it,
along with several hundred others, and you are *calling* it. Calling a
function means saying its name followed by parentheses. This one wipes the
screen clean.
It comes first because drawing does not replace what was there before, it
draws on top of it. Clear, then draw, every time.
[source,lua]
----
overlayPrint(2, 2, "Hello!")
----
Another call, and this one needs to be told three things, which go inside the
parentheses separated by commas. Things you hand to a function like this are
called *arguments*.
The first two are numbers: how far across and how far down. The third is the
text to print, and it is in double quotes because that is how you tell the
computer that `Hello!` is a piece of text rather than the name of something.
Text in quotes is called a *string*, and you will use strings constantly.
Sooner or later you will want a double quote inside a string, and the obvious
way does not work: the second quote ends the string early, and you get the
same `unfinished string` error you will meet at the end of this lesson. There
are two ways round it, and both are worth knowing.
[source,lua]
----
overlayPrint(2, 2, "She said \"hello\" to me.")
overlayPrint(2, 4, 'She said "hello" to me.')
----
The first puts a backslash in front of each quote that belongs to the text.
The backslash says "the next character is part of the string, not the end of
it", and the pair together is called an *escape*. The second uses single
quotes to mark the ends of the string, which leaves double quotes free to be
ordinary text. Lua accepts either kind of quote, as long as the string ends
with the same kind it started with.
Both of those lines print exactly the same thing. Use whichever is easier to
read, which is usually the single quotes when the text is full of double
ones.
The two numbers are not pixels. `overlayPrint` is the engine's own plain
text, and it counts in character cells: `(0, 0)` is the very top left corner,
`(1, 0)` is one letter to the right, and `(0, 1)` is one line down. So `(2, 2)`
is two letters in and two lines down. Other ways of drawing text do count in
pixels, and you will meet them in lesson nineteen.
[source,lua]
----
return OVERLAY_UPDATED
----
`return` hands an answer back to whoever called the function. Singe asks your
`onOverlayUpdate` one question every time it calls it: did you change
anything? `OVERLAY_UPDATED` means yes, so put it on screen.
`OVERLAY_UPDATED` has no quotes around it because it is not text. It is a name
that Singe has already given to a value, so that you can write something you
can read instead of a number you would have to remember.
[source,lua]
----
end
----
The matching end of the `function`. Every `function` has exactly one, and
forgetting it is the single most common mistake there is.
=== Try It
Change one thing at a time, save, and run it again. That loop -- change, run,
look -- is the whole job.
. *Move the text.* Change `overlayPrint(2, 2, "Hello!")` to
`overlayPrint(10, 5, "Hello!")`. Then try `(0, 0)`. Then try `(200, 2)`, and
work out why it vanished.
. *Say something else.* Put your own name in the quotes.
. *Say two things.* Add a second `overlayPrint` line under the first one, with
a different row number and different text. Nothing stops you having as many
as you like.
. *Take the clear out.* Delete the `overlayClear()` line and run it. Nothing
changes, because nothing on your screen moves yet. Put it back anyway. In
lesson two it will matter enormously, and this is a good time to see that
some mistakes wait before they bite.
=== Break It on Purpose
Beginners spend more time reading error messages than reading books about
programming, so let us get the first one over with now.
Delete the closing quote after `Hello!`, so the line reads
`overlayPrint(2, 2, "Hello!)`, and run it. Singe refuses to start and says:
----
Error running script: hello.singe:3: unfinished string near '"Hello!)'
----
Read it from the left. `hello.singe` is which file. `3` is which line. The
rest is what went wrong, in this case a piece of text that was opened and
never closed. Go to line three, put the quote back, and it runs.
Almost every error you will ever see has that shape: a file, a line, and a
complaint. The complaint is sometimes written for someone who already knows
Lua, but the file and the line are always right, and nine times in ten the
line is enough.
=== Stop Restarting It
You have now started Singe by hand five or six times. Stop doing that. Run it
like this instead:
----
Singe -R hello
----
`-R` tells Singe to watch your script. Save a change in your editor, and the
game restarts itself a moment later with your change in it, without closing
the window. If you save a mistake, the error is printed and the window sits
empty until you save a fix.
Use `-R` for the rest of this book.
=== What You Learned
* A program is a file of instructions that happen in order, top to bottom.
* A `function` groups instructions under a name and stops at its `end`.
* `onOverlayUpdate` is a name Singe looks for and calls many times a second,
and it is where all your drawing goes.
* Calling a function means writing its name and parentheses. Anything inside
the parentheses is an argument.
* A function the engine calls for you, rather than one you call yourself, is a
callback. `onOverlayUpdate` is one.
* Text in double quotes is a string.
* Clear the screen before you draw on it.
* An error tells you the file and the line. Start there.
=== Next Time
Your `Hello!` sits where you put it and never moves. To make it move, you need
somewhere to keep a number that changes -- a *variable* -- and that is the
whole of lesson two.