Collision detection, but better....
Crash BASIC 1.53.0 Release Notes
written by Crash-tan
A BEHAVIOR CAN NOW REACT TO BEING HIT
Behaviors could move a sprite around, but they could never notice when the sprite ran into something. If you wanted to score a point when a ball struck a brick, you had to poll — sweep every collision pair yourself every frame and work out which of them were new since last frame.
A BEHAVIOR can now carry the reaction inline:
BEHAVIOR "ball"
BIMPULSE 6, -4
ONCOLLISION
IF tag_b$ = "brick" THEN score = score + 10
END ONCOLLISION
END BEHAVIORONCOLLISION runs when a contact begins. AFTERCOLLISION runs when it ends. Neither repeats in between, which is the part that makes them pleasant to use: a character that lands on the ground fires ONCOLLISION once, stands there for as long as you like without firing anything, and fires AFTERCOLLISION when it walks off the edge. You do not have to remember what was touching what last frame, because the engine already did.
Inside the block you have the details:
index_a, tag_a$ |
this sprite — always "me" |
index_b, tag_b$ |
the other sprite, or -1 and the boundary's tag |
edge$ |
which face was struck: LEFT, RIGHT, TOP, BOTTOM, SLOPE |
impact_vx, impact_vy |
how fast you were going when you hit |
That last pair is worth dwelling on, because it is something you could not previously get at all. Collision resolution has to stop the sprite, so by the time any code of yours could look, the speed that caused the impact was already gone — zeroed or reflected away. ONCOLLISION runs before that happens, so damage that scales with how hard you landed, or a dust puff sized by impact speed, is now just arithmetic.
Both sprites in a sprite-to-sprite hit get their own handler, each seeing itself as index_a — including a sprite that never moved. A hazard can react to being walked into without any code on the thing that walked into it.
A boundary's tag names a kind of boundary, not one particular rectangle, so a wall built from twenty segments all tagged "wall" is one wall. Sliding along it does not re-fire.
If a behavior is built with INCLUDES, every handler it inherited runs, in the order they were composed.
One rule to know: these handlers run inline on the same loop that drives every behavior in your game, so PAUSE, DELAY, DELAY UNTIL and YIELD are rejected inside them. Waiting there would not pause one sprite, it would freeze all of them. Set a variable or swap a behavior instead — whatever you change takes effect on the next tick.
FOR COLLISIONS NO LONGER MISSES SOLID HITS
FOR COLLISIONS had a blind spot with solid sprites, and it was a quiet one.
When two solid sprites collide, the engine's job is to stop them, so it pushes them apart in the same tick. FOR COLLISIONS then looked at where everything currently was, found no overlap, and reported nothing. The collision had provably happened — it is why the sprite stopped — but the only evidence was gone before you could ask. Boundaries never had this problem; sprite pairs always did.
A solid collision is now reportable for exactly the tick it happens on. If you have a FOR COLLISIONS a, b loop that has been mysteriously quiet around solid sprites, it will start finding them.
Two things this deliberately does not do. Sprites that merely come to rest next to each other are still not colliding — touching edges is a position, not an impact, and nothing was ever pushed apart. And a collision does not linger: it is visible for its tick and then gone. A sprite pressing continuously into a wall is being corrected every tick, so it reports a fresh collision every tick, which is a different thing from one collision hanging around.
SLIDING ALONG A TILED WALL NO LONGER STICKS
If you build levels by putting a boundary on every tile, you may have met this one: a sprite moving diagonally along a wall slides fine for a moment and then stops dead, wedged, and no amount of pushing frees it. Walls drawn as one big rectangle never showed it. A wall made of tiles showed it constantly.
Sliding down a wall always worked, which made this look like a problem with moving upward. It was not. The engine walked your boundaries in order, correcting the sprite against each one in turn, and each boundary saw the position as corrected by the ones before it. When you slide along a tiled wall you are straddling the seam between two tiles — one beside you, one you are moving into — and whichever of them the engine happened to reach first decided the outcome. Reach the tile beside you first and everything works. Reach the tile you are entering first and it shoves you back the way you came, while the other tile holds the other axis, and between them you are pinned.
A tilemap lists its boundaries top to bottom, so going down you met the helpful tile first and going up you met the unhelpful one. Down was never right; it was lucky. Both directions fail if the boundaries happen to be listed the other way round.
The engine now settles one axis at a time across all of your boundaries before moving to the other, so the order they are listed in cannot change the result. Diagonal sliding behaves the same in every direction, against walls, floors and ceilings alike, however your level happens to be built.
Nothing about how you write levels changes, and no existing collision behaviour moved — every corner, slope and wall-slide case that worked before still gives exactly the answer it did.
THE BEHAVIOR REFERENCE TELLS THE TRUTH ABOUT BVELOCITY
The sounds and the code did not change here; the written reference did.
BVELOCITY's own documentation said it "sets the starting velocity once when the behavior activates," and recommended it for jumps, knockback and recoil. Both were wrong, and the entries for BIMPULSE and BFRICTION had been quietly contradicting it for some time. BVELOCITY is a constant: it is re-applied every tick for as long as the behavior is active, which is why friction cannot slow it and why several behaviors carrying it add together instead of overriding each other.
The practical consequence had also been documented backwards. BBOUNCE's reference promised that a ball with BBOUNCE 0.9, 0.9 and a constant BVELOCITY would "bounce around indefinitely." It will not, and never did — the bounce reverses the velocity, then the next tick rebuilds the constant and overwrites it, so the ball just presses against the wall. What bounces is accumulated momentum, the kind BIMPULSE, BACCELERATION and BGRAVITY build up. Three of BBOUNCE's examples used BVELOCITY and would not have worked as labelled; they now use BIMPULSE.
If you have been reaching for BVELOCITY to make something bounce and wondering why it sulks against the wall instead, that is why. BIMPULSE 8, 0 with BBOUNCE does what you wanted.
This matters beyond people reading the docs directly: this is the same reference an assistant reads when it writes Crash BASIC for you, and it had been handing out the wrong primitive.