Rewrite both tutorials as tutorials
They read as design commentary: why a choice was made, what the previous state of the project was, which library defect a line works around. That is useful to somebody who already knows the library and useless to somebody trying to build a game, which is who a tutorial is for. Both chapters now open with a screenshot of what the reader is building and a numbered list of the steps to get there, and each step is its own section: what you are trying to achieve, the code that achieves it, and the two or three things about that code that are easy to get wrong. Chapter 20 starts from first principles -- what a sprite, a character, an actor and a tilemap are, and the four error macros -- and chapter 21 assumes it and covers what a top-down game adds. Where a line exists because of a defect, the line comes first and the defect comes after it, with a pointer to the TODO entry that will remove the need. A reader who is copying code should not have to read an apology before they can use it. The library's own history is gone from both. That belongs in the design chapters and in git. Two new docs_examples setups stage the tutorials' own art, so the sprite and character files a reader is shown are loaded exactly as printed rather than through a generic fixture. `json excerpt=` is new for the same reason: a Tiled object is now quoted out of the real map instead of being retyped. Excerpts across docs/ go from 91 to 137. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KzBDV2fqgnUAcqCKqKvc71
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -38,6 +38,7 @@ rather than a free pass.
|
|||||||
| `c wrap=NAME` | The same, with `tests/docs_preludes/NAME.pre` before it and `NAME.post` after |
|
| `c wrap=NAME` | The same, with `tests/docs_preludes/NAME.pre` before it and `NAME.post` after |
|
||||||
| `c run=NAME` | Wrapped in `NAME`, **linked against `akgl` and run headless**. Must exit 0 and must not report an unhandled or ignored error |
|
| `c run=NAME` | Wrapped in `NAME`, **linked against `akgl` and run headless**. Must exit 0 and must not report an unhandled or ignored error |
|
||||||
| `c excerpt=PATH` | Must still appear in `PATH`, ignoring comments and whitespace. **Not compiled** |
|
| `c excerpt=PATH` | Must still appear in `PATH`, ignoring comments and whitespace. **Not compiled** |
|
||||||
|
| `json excerpt=PATH` | The same check against an asset file, for a fragment too large to show whole |
|
||||||
| `c screenshot=NAME` | Compiled as an `akglframe` body, and the source of `docs/images/NAME.png`. Optionally `size=WxH` |
|
| `c screenshot=NAME` | Compiled as an `akglframe` body, and the source of `docs/images/NAME.png`. Optionally `size=WxH` |
|
||||||
| `json kind=KIND` | Written to a file and loaded through libakgl's own loader. `KIND` is `sprite`, `character`, `tilemap` or `properties` |
|
| `json kind=KIND` | Written to a file and loaded through libakgl's own loader. `KIND` is `sprite`, `character`, `tilemap` or `properties` |
|
||||||
| `output` | The **exact stdout** of the runnable block above it, compared byte for byte |
|
| `output` | The **exact stdout** of the runnable block above it, compared byte for byte |
|
||||||
@@ -156,8 +157,16 @@ the `KINDS` table in `tools/docs_checkjson.c` loads them:
|
|||||||
|
|
||||||
| Setup | What it stages | What loads it |
|
| Setup | What it stages | What loads it |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
| `setup=spritesheet` | `./spritesheet.png`, a 12x8 grid of 48x48 frames | the `sprite` row's `prepare` |
|
||||||
| `setup=character` | `./sprites/*.json` plus a sheet, named `hero standing left` and `hero walking left` | the `character` row's `prepare` |
|
| `setup=character` | `./sprites/*.json` plus a sheet, named `hero standing left` and `hero walking left` | the `character` row's `prepare` |
|
||||||
| `setup=tilemap` | `./sprites/*.json`, `./characters/testcharacter.json`, and `assets/tileset.png` | the `tilemap` row's `prepare` |
|
| `setup=tilemap` | `./sprites/*.json`, `./characters/testcharacter.json`, and `assets/tileset.png` | the `tilemap` row's `prepare` |
|
||||||
|
| `setup=sidescroller` | every PNG and sprite file from `docs/tutorials/assets/sidescroller`, in `./` and `./sprites` | both rows' `prepare` |
|
||||||
|
| `setup=jrpg` | the same, from `docs/tutorials/assets/jrpg` | both rows' `prepare` |
|
||||||
|
|
||||||
|
The last two are how the tutorials show a reader a *real* asset file rather than a
|
||||||
|
generic one. A beginner following chapter 20 or 21 types what is on the page, so
|
||||||
|
what is on the page has to be a file that loads — the setup stages the tutorial's
|
||||||
|
own art so the block on the page is the block in the repository.
|
||||||
|
|
||||||
Those names are a contract between the setup script and the chapter using it.
|
Those names are a contract between the setup script and the chapter using it.
|
||||||
Change one and change the other, in the same commit. `assets/tileset.png` is
|
Change one and change the other, in the same commit. `assets/tileset.png` is
|
||||||
|
|||||||
@@ -32,13 +32,16 @@ per-function reference, build the Doxygen output with `doxygen Doxyfile`.
|
|||||||
| **[17. Text and fonts](17-text-and-fonts.md)** | Loading, drawing, measuring, and the teardown order that matters |
|
| **[17. Text and fonts](17-text-and-fonts.md)** | Loading, drawing, measuring, and the teardown order that matters |
|
||||||
| **[18. Audio](18-audio.md)** | The three-voice synthesizer, and the separate background-music path |
|
| **[18. Audio](18-audio.md)** | The three-voice synthesizer, and the separate background-music path |
|
||||||
| **[19. Utilities](19-utilities.md)** | Rectangle overlap, path resolution, the JSON accessors, static strings |
|
| **[19. Utilities](19-utilities.md)** | Rectangle overlap, path resolution, the JSON accessors, static strings |
|
||||||
| **[20. Tutorial: a 2D sidescroller](20-tutorial-sidescroller.md)** | Gravity, a jump, collision, coins and hazards |
|
| **[20. Tutorial: a 2D sidescroller](20-tutorial-sidescroller.md)** | Thirteen steps from an empty directory to a game with gravity, a jump, coins and hazards. **Start here** |
|
||||||
| **[21. Tutorial: a top-down JRPG](21-tutorial-jrpg.md)** | A town map, NPCs spawned from map objects, four-way animation, a text box, a follower |
|
| **[21. Tutorial: a top-down JRPG](21-tutorial-jrpg.md)** | Thirteen more, for four-way movement, NPCs, a text box, a follower, and freezing the world |
|
||||||
| **[22. Appendix](22-appendix-limits.md)** | Every limit, every status, every configuration property |
|
| **[22. Appendix](22-appendix-limits.md)** | Every limit, every status, every configuration property |
|
||||||
|
|
||||||
Both tutorials are complete programs under [`examples/`](../examples). They build with the
|
**If you are new, read chapter 20 first and read it in order.** It builds a working game from
|
||||||
library and run headless in CI, and the chapters quote them rather than restating them — so
|
an empty directory and teaches the library as it needs each piece; chapter 21 assumes it.
|
||||||
a tutorial cannot drift from a program that compiles.
|
Both are complete programs under [`examples/`](../examples) — they build with the library and
|
||||||
|
run headless in CI, and the chapters quote them rather than restating them, so a tutorial
|
||||||
|
cannot drift from a program that compiles. The picture at the top of each chapter is a frame
|
||||||
|
out of the game itself, regenerated by `cmake --build build --target docs_game_figures`.
|
||||||
|
|
||||||
## Every example here is checked
|
## Every example here is checked
|
||||||
|
|
||||||
|
|||||||
35
tests/docs_setups/jrpg.sh
Executable file
35
tests/docs_setups/jrpg.sh
Executable file
@@ -0,0 +1,35 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# Stage the JRPG tutorial's own assets in the sandbox.
|
||||||
|
#
|
||||||
|
# Same job as sidescroller.sh, and the same reason: docs/21-tutorial-jrpg.md
|
||||||
|
# shows the reader real sprite and character files out of
|
||||||
|
# docs/tutorials/assets/jrpg, and a beginner following the chapter types what is
|
||||||
|
# on the page -- so what is on the page has to be a file that loads.
|
||||||
|
#
|
||||||
|
# Two directories are staged, because the checker looks in two places:
|
||||||
|
#
|
||||||
|
# ./ a `json kind=sprite` block's spritesheet path is resolved
|
||||||
|
# relative to the sprite file, which lives in the sandbox root
|
||||||
|
# ./sprites tools/docs_checkjson.c loads everything here through
|
||||||
|
# akgl_sprite_load_json before it touches a `kind=character`
|
||||||
|
# block, since a character resolves its sprites by name
|
||||||
|
#
|
||||||
|
# $1 is the repository root. The caller runs this with the sandbox as the
|
||||||
|
# working directory.
|
||||||
|
|
||||||
|
set -u
|
||||||
|
|
||||||
|
ROOT="${1:-}"
|
||||||
|
if [ -z "${ROOT}" ]; then
|
||||||
|
echo "usage: jrpg.sh <repository-root>" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
ASSETS="${ROOT}/docs/tutorials/assets/jrpg"
|
||||||
|
|
||||||
|
cp "${ASSETS}"/*.png . || exit 1
|
||||||
|
|
||||||
|
mkdir -p sprites || exit 1
|
||||||
|
cp "${ASSETS}"/*.png sprites/ || exit 1
|
||||||
|
cp "${ASSETS}"/sprite_*.json sprites/ || exit 1
|
||||||
36
tests/docs_setups/sidescroller.sh
Executable file
36
tests/docs_setups/sidescroller.sh
Executable file
@@ -0,0 +1,36 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# Stage the sidescroller tutorial's own assets in the sandbox.
|
||||||
|
#
|
||||||
|
# docs/20-tutorial-sidescroller.md shows the reader a real sprite file and a real
|
||||||
|
# character file out of docs/tutorials/assets/sidescroller. Those are worth
|
||||||
|
# running through the loader exactly as printed rather than through a generic
|
||||||
|
# fixture, because a beginner following the chapter types what is on the page --
|
||||||
|
# so what is on the page has to be a file that loads.
|
||||||
|
#
|
||||||
|
# Two directories are staged, because the checker looks in two places:
|
||||||
|
#
|
||||||
|
# ./ a `json kind=sprite` block's spritesheet path is resolved
|
||||||
|
# relative to the sprite file, which lives in the sandbox root
|
||||||
|
# ./sprites tools/docs_checkjson.c loads everything here through
|
||||||
|
# akgl_sprite_load_json before it touches a `kind=character`
|
||||||
|
# block, since a character resolves its sprites by name
|
||||||
|
#
|
||||||
|
# $1 is the repository root. The caller runs this with the sandbox as the
|
||||||
|
# working directory.
|
||||||
|
|
||||||
|
set -u
|
||||||
|
|
||||||
|
ROOT="${1:-}"
|
||||||
|
if [ -z "${ROOT}" ]; then
|
||||||
|
echo "usage: sidescroller.sh <repository-root>" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
ASSETS="${ROOT}/docs/tutorials/assets/sidescroller"
|
||||||
|
|
||||||
|
cp "${ASSETS}"/*.png . || exit 1
|
||||||
|
|
||||||
|
mkdir -p sprites || exit 1
|
||||||
|
cp "${ASSETS}"/*.png sprites/ || exit 1
|
||||||
|
cp "${ASSETS}"/sprite_*.json sprites/ || exit 1
|
||||||
Reference in New Issue
Block a user