Delete the sidescroller's collision, and let the library do it
collision.c was 383 lines that re-implemented akgl_physics_simulate's own arithmetic, because movementlogicfunc runs before gravity, drag and the move and so had to predict where the actor would end up. The whole file goes. What replaces it is three lines in main.c, a shape on the player and the blob, and a `collidable` property on the map's terrain layer -- which also retires SS_TERRAIN_LAYER_ID, a compiled-in layer id that broke whenever somebody reordered layers in Tiled. grounded stays a probe rather than becoming a contact test. A contact says something pushed back this step; grounded asks whether there is a floor to push off, and that stays true through the frame after landing. The blob keeps its own wall and ledge probes for the same reason: the resolver does not say which side of the blob it was pushed from, and says nothing at all about a floor that is missing. The summary line now carries the player's resting position, because exiting 0 was not evidence that collision ran. It reports 136.0,160.0 grounded after 240 autoplay frames -- feet at y=192, flush on the surface of terrain row 12. The tutorial's collision section is rewritten around the library's, keeping the quarter-of-a-pixel story as what a predicting resolver costs. The docs harness learns `json excerpt=`, so the map property the chapter quotes is checked against the map. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KzBDV2fqgnUAcqCKqKvc71
This commit is contained in:
@@ -86,14 +86,15 @@ static akerr_ErrorContext *respawn(akgl_Actor *obj)
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief The player's `movementlogicfunc`: friction, the jump, and terrain.
|
||||
* @brief The player's `movementlogicfunc`: friction and the jump.
|
||||
*
|
||||
* Called by `akgl_physics_simulate` once per actor per step, *before* gravity,
|
||||
* drag, the velocity recomputation and `move`.
|
||||
* drag, the velocity recomputation and `move`. Terrain is no longer this
|
||||
* function's problem -- the library resolves it after the move, which is the
|
||||
* only place it can be resolved without predicting the arithmetic above.
|
||||
*/
|
||||
static akerr_ErrorContext *ss_player_movement(akgl_Actor *obj, float32_t dt)
|
||||
{
|
||||
ss_Contact contact;
|
||||
float32_t friction = 0.0f;
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
@@ -113,6 +114,11 @@ static akerr_ErrorContext *ss_player_movement(akgl_Actor *obj, float32_t dt)
|
||||
* ground than in the air. Below a pixel per second it is snapped to zero,
|
||||
* because an exponential decay never actually arrives.
|
||||
*/
|
||||
/* Where the last step left the player, one pixel down. This runs before the
|
||||
* step it is deciding about, so it is one frame stale by construction --
|
||||
* which is one frame of coyote time, and not a thing a player can feel. */
|
||||
PASS(errctx, ss_grounded(&obj->shape, obj->x, obj->y, &ss_game.grounded));
|
||||
|
||||
if ( AKGL_BITMASK_HASNOT(obj->state, AKGL_ACTOR_STATE_MOVING_LEFT) &&
|
||||
AKGL_BITMASK_HASNOT(obj->state, AKGL_ACTOR_STATE_MOVING_RIGHT) ) {
|
||||
friction = SS_FRICTION_AIR;
|
||||
@@ -132,18 +138,13 @@ static akerr_ErrorContext *ss_player_movement(akgl_Actor *obj, float32_t dt)
|
||||
* `speed_y`, which is 0 for this character, so a jump written as thrust is
|
||||
* scaled to nothing by the ellipse cap in akgl_physics_simulate.
|
||||
*
|
||||
* `ss_game.grounded` is last step's verdict, one frame stale. That is the
|
||||
* ordering again -- this hook runs before the step it is deciding about --
|
||||
* and one frame of coyote time is not a thing a player can feel.
|
||||
* `ss_game.grounded` is the probe above.
|
||||
*/
|
||||
if ( (ss_game.jump_requested == true) && (ss_game.grounded == true) ) {
|
||||
obj->ey = -SS_JUMP_SPEED;
|
||||
}
|
||||
ss_game.jump_requested = false;
|
||||
|
||||
PASS(errctx, ss_collide_resolve(obj, &ss_player_body, dt, &contact));
|
||||
ss_game.grounded = contact.grounded;
|
||||
|
||||
/*
|
||||
* The character maps MOVING_UP to the jump sprites, so the state word says
|
||||
* "in the air" whether the actor is rising or falling. Nothing else reads
|
||||
@@ -151,7 +152,7 @@ static akerr_ErrorContext *ss_player_movement(akgl_Actor *obj, float32_t dt)
|
||||
* character_ss_player.json, so the thrust it would authorise is capped to
|
||||
* nothing.
|
||||
*/
|
||||
if ( contact.grounded == true ) {
|
||||
if ( ss_game.grounded == true ) {
|
||||
AKGL_BITMASK_DEL(obj->state, AKGL_ACTOR_STATE_MOVING_UP);
|
||||
} else {
|
||||
AKGL_BITMASK_ADD(obj->state, AKGL_ACTOR_STATE_MOVING_UP);
|
||||
@@ -285,9 +286,12 @@ akerr_ErrorContext *ss_player_bind(akgl_Actor *obj)
|
||||
|
||||
ss_game.player = obj;
|
||||
|
||||
/* Lift the spawn point clear of the step it overlaps *before* recording it,
|
||||
* so a respawn does not put the player back inside the geometry. */
|
||||
PASS(errctx, ss_collide_settle(obj, &ss_player_body));
|
||||
/* The library's default response only stops an actor *entering* geometry;
|
||||
* one that starts inside it is simply stuck, so lift the spawn point clear
|
||||
* of the step it overlaps *before* recording it. */
|
||||
PASS(errctx, akgl_collision_shape_box(&obj->shape, &ss_player_body, 0.0f));
|
||||
obj->shape_override = true;
|
||||
PASS(errctx, akgl_collision_settle(&ss_collision, &obj->shape, &obj->x, &obj->y, 0));
|
||||
ss_player_data.home_x = obj->x;
|
||||
ss_player_data.home_y = obj->y;
|
||||
obj->actorData = (void *)&ss_player_data;
|
||||
|
||||
Reference in New Issue
Block a user