/** * @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 * collision.c the tile queries and the swept resolution libakgl does not have * 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 #include #include #include #include #include #include #include /* * 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 */ /* * Which layer of the map is solid. * * akgl_TilemapLayer records Tiled's numeric layer `id` and does *not* record the * layer's name -- akgl_tilemap_load_layers reads `id`, `opacity`, `visible`, `x`, * `y` and `type`, and nothing else. So a game cannot ask for "the layer called * terrain"; it matches on the id Tiled assigned, which for level1.tmj is 2. */ #define SS_TERRAIN_LAYER_ID 2 /* 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 /** * @brief What one call to ss_collide_resolve found. * * `blocked_x` and `blocked_y` say the sweep stopped the actor on that axis this * step; `grounded` says there is solid terrain one pixel under where the actor * will be when the step finishes, which is the test a jump is gated on. */ typedef struct { bool blocked_x; bool blocked_y; bool grounded; } ss_Contact; /** * @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. */ } ss_Game; extern ss_Game ss_game; /* collision.c -- everything akgl_physics_arcade_collide would have done. */ akerr_ErrorContext AKERR_NOIGNORE *ss_collide_bind(akgl_Tilemap *map); akerr_ErrorContext AKERR_NOIGNORE *ss_collide_solid_at(float32_t x, float32_t y, bool *dest); akerr_ErrorContext AKERR_NOIGNORE *ss_collide_box_blocked(SDL_FRect *box, bool *dest); akerr_ErrorContext AKERR_NOIGNORE *ss_collide_predict(akgl_Actor *obj, float32_t dt, float32_t *dx, float32_t *dy); akerr_ErrorContext AKERR_NOIGNORE *ss_collide_resolve(akgl_Actor *obj, SDL_FRect *body, float32_t dt, ss_Contact *dest); akerr_ErrorContext AKERR_NOIGNORE *ss_collide_settle(akgl_Actor *obj, SDL_FRect *body); /* 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_