Two agents were given nothing but the chapter text and asked to build the game from it. Between them they named every place the chapters showed a fragment and called it an explanation. This closes those. Chapter 20 now shows what it previously only named: the whole ss_Game struct rather than a truncated one, SS_COIN_COUNT and SS_HAZARD_COUNT, ss_ActorData, the player and blob body rectangles, asset_path, hitbox, find_actor, respawn, both jump handlers, the control-map function head, the gamepad buttons, the blob's probe arithmetic, and a complete standalone CMakeLists rather than three lines out of one. It also names the library globals a reader is expected to use without declaring, lists all seven actor hooks rather than the two this game replaces, and says in order what each of the two hook functions ends up doing. Chapter 21 gains the same treatment: the header's includes and declarations, the textbox colours and their SDL_Color type, TTF_Font, the jrpg_Townsfolk row type, character_load in full, the player lookup, the frame loop, and per-file include tables for both games. Two claims were wrong and are corrected. "Each step is complete on its own" was not true of a cumulative tutorial. And the first argument to akgl_controller_handle_event is not a game-state word the dispatcher reads -- the header says nothing reads it and it is required only as a non-NULL token. Every step that produces code now ends with what the reader should see when they run it. Excerpts across docs/ go from 137 to 190. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KzBDV2fqgnUAcqCKqKvc71
136 lines
5.5 KiB
C
136 lines
5.5 KiB
C
/**
|
|
* @file sidescroller.h
|
|
* @brief Shared declarations for the sidescroller tutorial game.
|
|
*
|
|
* The game is four translation units and this is the seam between them:
|
|
*
|
|
* main.c startup, asset loading, the frame loop, teardown
|
|
* player.c the player's hooks and its control bindings
|
|
* actors.c the coins, the patrolling blob, the flying moth
|
|
*
|
|
* Nothing here is prefixed `akgl_`. That prefix belongs to the library; a game
|
|
* built on it takes its own, and this one is `ss_`.
|
|
*/
|
|
|
|
#ifndef _SIDESCROLLER_H_
|
|
#define _SIDESCROLLER_H_
|
|
|
|
#include <stdbool.h>
|
|
|
|
#include <SDL3/SDL.h>
|
|
|
|
#include <akerror.h>
|
|
|
|
#include <akgl/actor.h>
|
|
#include <akgl/collision.h>
|
|
#include <akgl/error.h>
|
|
#include <akgl/game.h>
|
|
#include <akgl/physics.h>
|
|
#include <akgl/tilemap.h>
|
|
#include <akgl/types.h>
|
|
|
|
/*
|
|
* The level's geometry, in the units the map is authored in. level1.tmj is 40x15
|
|
* tiles of 16 pixels, so the world is 640x240 pixels and the view is a 480x240
|
|
* window onto it that scrolls horizontally.
|
|
*/
|
|
#define SS_TILE_SIZE 16 /* Pixels per map cell, from level1.tmj */
|
|
#define SS_VIEW_WIDTH 480 /* Camera width in map pixels */
|
|
#define SS_VIEW_HEIGHT 240 /* Camera height; the whole map is this tall */
|
|
#define SS_WINDOW_SCALE 2 /* Integer upscale from the view to the window */
|
|
|
|
/* The player's collision box, as an offset into its 32x32 sprite frame. The art
|
|
* does not fill the frame edge to edge, and a box the full width of the frame
|
|
* catches on doorways the character visibly clears. */
|
|
#define SS_PLAYER_BOX_X 8.0f
|
|
#define SS_PLAYER_BOX_Y 0.0f
|
|
#define SS_PLAYER_BOX_W 16.0f
|
|
#define SS_PLAYER_BOX_H 32.0f
|
|
|
|
/* Upward velocity a jump installs directly into the actor's environmental term,
|
|
* in pixels per second. Under the map's 900 px/s^2 gravity this peaks a little
|
|
* under 100 px -- six tiles -- which is what the platform heights are cut to. */
|
|
#define SS_JUMP_SPEED 420.0f
|
|
|
|
/* How fast thrust bleeds away when no direction is held, as a fraction per
|
|
* second. The library has no friction at all: see ss_player_friction. */
|
|
#define SS_FRICTION_GROUND 12.0f
|
|
#define SS_FRICTION_AIR 1.5f
|
|
|
|
/* How many of each kind of thing level1.tmj places. */
|
|
#define SS_COIN_COUNT 4
|
|
#define SS_HAZARD_COUNT 2
|
|
|
|
/* How far the autoplay script waits between jumps, in frames. */
|
|
#define SS_AUTOPLAY_JUMP_PERIOD 45
|
|
/* How long it holds the jump key, in frames. A tap jumps lower than a hold. */
|
|
#define SS_AUTOPLAY_JUMP_HOLD 8
|
|
/*
|
|
* The device id the autoplay script stamps on its synthetic key events.
|
|
* Deliberately not 0: the control map binds keyboard 0 meaning "any keyboard",
|
|
* and driving it from a non-zero id is what proves that binding works.
|
|
*/
|
|
#define SS_AUTOPLAY_KBID 11
|
|
|
|
/**
|
|
* @brief Per-actor game data, hung off akgl_Actor::actorData.
|
|
*
|
|
* The library never reads or frees `actorData`, so it is the place a game puts
|
|
* what the library has no field for. These live in a fixed table in actors.c
|
|
* rather than being allocated, which is the same discipline the library's own
|
|
* pools follow.
|
|
*/
|
|
typedef struct {
|
|
float32_t home_x; /**< Where the map placed this actor. The moth orbits it; the player respawns at it. */
|
|
float32_t home_y;
|
|
float32_t phase; /**< Seconds of flight, for the moth's orbit. */
|
|
float32_t facing; /**< -1.0 walking left, +1.0 walking right. The blob's patrol direction. */
|
|
} ss_ActorData;
|
|
|
|
/** @brief The whole game's state. One player, one level, no menus. */
|
|
typedef struct {
|
|
akgl_Actor *player; /**< Borrowed from the actor pool; the map created it. */
|
|
akgl_Actor *coins[SS_COIN_COUNT]; /**< Cleared to NULL as each one is collected. */
|
|
akgl_Actor *hazards[SS_HAZARD_COUNT]; /**< The blob and the moth. Borrowed, never released. */
|
|
int coins_taken;
|
|
int deaths;
|
|
bool jump_requested; /**< Set by the jump binding, consumed by the movement logic. */
|
|
bool grounded; /**< Last step's verdict; what gates the next jump. */
|
|
bool autoplay; /**< Drive the player from a script instead of the keyboard. */
|
|
int frame; /**< Frames drawn so far. */
|
|
float32_t final_x; /**< Where the player finished, stamped before teardown for the summary line. */
|
|
float32_t final_y;
|
|
} ss_Game;
|
|
|
|
extern ss_Game ss_game;
|
|
|
|
/**
|
|
* @brief The collision world, owned by main.c.
|
|
*
|
|
* The physics backend resolves through it after every sub-move; the game only
|
|
* touches it to ask questions -- a ledge probe, a wall probe, a spawn point that
|
|
* needs lifting clear.
|
|
*/
|
|
extern akgl_CollisionWorld ss_collision;
|
|
|
|
/**
|
|
* @brief Is there solid geometry one pixel under this shape?
|
|
*
|
|
* The predicate a jump is gated on, and it is deliberately *not* the same
|
|
* question as "did the last step's resolution stop me falling". A contact says
|
|
* something pushed back this step; this says there is a floor to push off, which
|
|
* stays true through the frame after landing and is what makes a jump feel like
|
|
* it registered.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *ss_grounded(akgl_CollisionShape *shape, float32_t x, float32_t y, bool *dest);
|
|
|
|
/* player.c */
|
|
akerr_ErrorContext AKERR_NOIGNORE *ss_player_bind(akgl_Actor *obj);
|
|
akerr_ErrorContext AKERR_NOIGNORE *ss_player_controls(int controlmapid, char *actorname);
|
|
akerr_ErrorContext AKERR_NOIGNORE *ss_player_autoplay(int frame);
|
|
|
|
/* actors.c */
|
|
akerr_ErrorContext AKERR_NOIGNORE *ss_actors_bind(void);
|
|
|
|
#endif // _SIDESCROLLER_H_
|