Crash-tan
1.63.0 released to the wild
Crash BASIC 1.63.0 Release Notes
written by Crash-tan
SWITCH A WHOLE LAYER OFF AND ON
SET LAYER n OFF stops a layer from being drawn, and SET LAYER n ON brings
it back. Nothing on the layer is thrown away while it's off — its pixels, its
tilemap, its alpha, wrapping, clip, plane mode and shader all wait for it — and
you can keep drawing into it the whole time.
So you can build something complicated out of sight, then show all of it at
once:
SET LAYER 3 OFF
SET LAYER 3
LINE (0, 0)-(320, 200), 12, BF
' ...as much drawing as you like...
SET LAYER 0
SET LAYER 3 ONOff means the whole layer: its tilemap and every sprite and polygon on it
vanish too, and come back with it. A sprite you hid with HIDE SPRITE stays
hidden when its layer comes back on.
Want it to fade in instead of popping? Set the alpha while it's still off —
that won't switch it on — then bring it up:
SET LAYER 3 ALPHA(0)
SET LAYER 3 ON
FOR a = 0 TO 1 STEP 0.05
SET LAYER 3 ALPHA(a)
DELAY 30
NEXT aLayer alpha fades what's drawn on the layer and its tilemap. Sprites and
polygons keep their own alpha, so fade those with SET SPRITE n ALPHA(a) if
you want them to follow — or leave them solid while the scene fades in around
them.
SET LAYER n ON and SET LAYER n OFF don't change which layer you're drawing
to, so you can flip layers mid-drawing. Mix in another setting, likeSET LAYER 3 OFF ALPHA(0.5), and it switches the draw target to that layer the
way SET LAYER always has.
One thing to watch: put ON or OFF before WRAPX or WRAPY. After a wrap,
it belongs to the wrap — SET LAYER 1 WRAPX OFF turns wrapping off, not the
layer. Write SET LAYER 1 OFF WRAPX to do both.
ALPHA FADES MAP LAYERS NOW
SET LAYER n ALPHA(a) on a layer holding a map did nothing: the tiles stayed
fully solid whatever you asked for. (Maps on an AFFINE or PERSPECTIVE
layer were fine — it was ordinary flat maps that ignored it.) Now a map fades
like everything else on its layer, so two maps can cross-fade — one room
fading out as the next fades in:
FOR t = 0 TO 20
SET LAYER 1 ALPHA(1 - t / 20)
SET LAYER 2 ALPHA(t / 20)
DELAY 30
NEXT tMAKE A WHOLE LEVEL SOLID IN ONE LINE
BOUNDARY ADD "tag" FROM LAYER n looks at what's on a layer and builds the
collision for it — no more writing a BOUNDARY ADD for every wall and floor.
CREATE MAP ("level1.map.json") AS LAYER 1
BOUNDARY ADD "ground" FROM LAYER 1 SOLIDOn a map layer it reads each tile's actual art from the tileset, so a
half-height ledge is half a tile of collision, not a whole block, and a
diagonal tile becomes a proper slope your sprites can walk up. On any other
layer it reads what you've drawn there — PUTIMAGE a memory buffer, draw some
hills, then generate:
SET LAYER 2
LINE (0, 200)-(319, 239), 12, BF
BOUNDARY ADD "hills" FROM LAYER 2 SOLIDIt doesn't go pixel by pixel. It works in cells — half a tile on a map, 8
pixels anywhere else — and merges solid cells into as few boundaries as it
can, so a flat floor is one boundary, not hundreds. Want it to hug the art
more closely? CELL 4. Less? CELL 16.
Everything generated shares your tag, so sliding across the seams between its
pieces doesn't fire ONCOLLISION over and over — while running into a new
face of it, like a wall after a floor, does (more on that below). BOUNDARY DELETE "hills" clears the lot. It only collides with sprites on the same
layer.
Moving between rooms? Keep only the room you're in solid: delete the old
room's boundaries, then generate the new one's.
BOUNDARY DELETE "roomA"
BOUNDARY ADD "roomB" FROM LAYER 2 SOLIDIt works in the web player too. In multiplayer, map layers work on the server
as well. A drawn layer only exists on a player's screen, so reading one on the
server is an error — wrap it in IF ME.ISCLIENT THEN.
ONCOLLISION TELLS YOU WHEN YOU HIT A NEW FACE
Boundaries that share a tag act as one surface, so sliding along a floor made
of many pieces doesn't fire ONCOLLISION at every seam. That still holds. But
a sprite standing on a floor that ran into a wall with the same tag used to
get nothing — it was already "touching" that tag. Now meeting a new face of a
solid surface fires ONCOLLISION again, and edge$ tells you which face:
BEHAVIOR "walker"
ONCOLLISION
IF edge$ = "LEFT" THEN PRINT "bumped into "; tag_b$
END ONCOLLISION
END BEHAVIORThat's exactly what a surface from BOUNDARY ADD ... FROM LAYER needs, since
its floors, walls and steps all share one tag. Non-solid trigger boundaries
are unchanged: walking through one is still one ONCOLLISION in and oneAFTERCOLLISION out.
LOTS OF BOUNDARIES DON'T SLOW YOU DOWN
Every solid sprite used to check itself against every boundary in the game,
every tick. A level with a boundary per tile — a thousand or so — could drag
your game below 60 frames a second with a few hundred sprites moving. Now each
sprite only checks the boundaries near it. Same collisions, same results: a
tile-per-boundary test level with 400 moving sprites went from 22 ms a tick to
under 1.
WRITE A GRID THE WAY IT LOOKS
Give an array literal a type and a shape with AS, and write it the way it
looks — one row per line:
room = [[1, 0, 0, 0],
[1, 0, 1, 0],
[1, 1, 1, 1]] AS INTEGER(4, 3)
PRINT room(2, 1) ' 1: x = 2 in the second rowThe first index is still x and the second y, exactly as DIM room(4, 3)
would give you — each row you write is one y, left to right in x. That's
a map ready for CREATE MAP FROM room, without a loop in sight.
- The dimensions are optional:
AS INTEGERalone works the shape out. - Rows of different lengths, or a shape that doesn't match what you wrote, stop
with an error — nothing is quietly padded or cut off. - It works on
LOADJSONandDESERIALIZE$results too, and with your own
TYPEs:enemies = LOADJSON("enemies.json") AS Enemy(20)gives you twenty
realEnemyinstances, methods and all.
GRIDS SAVE AS ROWS, LIKE EVERYONE ELSE'S
SAVEJSON and SERIALIZE$ now write a 2D array the way every grid format does
— a list of rows — and LOADJSON and DESERIALIZE$ read one back the same
way. A map saved from your game looks like a map in the file, and a grid from
another tool loads with the (x, y) indexes you'd expect.
Heads up: two things change for 2D (and 3D) arrays. One: an array saved by
an earlier version was written column by column, so it now reads back
transposed — re-save your saved grids with this version. Two: a grid loaded
from JSON used to be indexed (row, column); it's now (x, y) like every
other grid in Crash BASIC, so a program that loads one and readsgrid(row, col) needs grid(col, row). One-dimensional arrays and objects
aren't affected.
Also fixed: a multi-dimensional array sent in a FETCH body, or shared through
CrashNet, used to arrive completely empty. It now arrives whole.
ANDROID GAMES START ON MORE PHONES
CrashPlayer for Android drew everything with Vulkan, and only Vulkan. On a
phone or tablet without it — or with a Vulkan driver too broken to use — your
game's screen opened and closed again before you could see it. No message, no
game.
Now, when Vulkan isn't there or won't start, your game runs on OpenGL ES
instead. Same game, same pictures: your shaders, sprites, polygons and layers
all come along. Nothing to change in your program. A busy scene may run a
little slower on OpenGL ES than it would on Vulkan, but it runs.
And if a game still can't start — say its program won't parse — you get told
why, instead of being dropped back at your library.
FOLDING THE PHONE DOESN'T RESTART YOUR GAME
On Android, a few everyday things made your game start over from the splash
screen, losing everything since:
- folding or unfolding a foldable
- dragging it into split-screen
- resizing its window on a Chromebook or tablet
- moving it to another display
- plugging in a gamepad
Your game now just keeps playing through all of them. Its screen is resized to
fit, the same way it already was when you rotated the device.
THE EXIT BUTTON STAYS OUT FROM UNDER THE NOTCH
On Android phones with a notch or camera cutout, turning the phone could tuck
the ✕ exit button right underneath it, where you couldn't see it or tap it. It
now moves clear of the cutout whichever way the phone is turned.