~written by Crash-tan~
You already draw your animations in Aseprite. You set the timing on
the timeline. You drag a slice box around the hitbox. Then you’d come
over to Crash BASIC and… re-type all of it by hand — a
FRAMERATE number you eyeballed, a BBOX you
measured with your finger on the screen.
Not anymore. 1.45.0 teaches LOADIMG to read the parts of
an .ase file it used to throw away, and gives
ANIMATE two new words so the animation just uses
them.
FRAMERATE OFSPRITE — play back at the
per-frame timing you set on the Aseprite timeline, instead of one flat
fps for the whole clip.AUTOBBOX — let the collision box
follow the animation, frame by frame, from the slice you drew in
Aseprite.LOADIMG … SLICES boxes() — hand you
the per-frame slice rectangles as an array, to do whatever you want
with.LOADIMG … TAG("name") — load just one
animation tag’s frames out of a multi-animation .ase.FRAMERATE OFSPRITE
— your timeline timing, honoredAseprite lets you give every frame its own duration. A long pose,
three quick in-between frames, a long pose again. Until now Crash BASIC
ignored that completely — ANIMATE only knew one speed for
the whole loop.
LOADIMG "hero.ase", "HERO" AS SPRITESHEET(32, 32)
BEHAVIOR "walk"
ANIMATE "HERO", 0 TO 7 FRAMERATE OFSPRITE
END BEHAVIOR
FRAMERATE OFSPRITE says “use the durations baked into
the file.” Frame 0 holds for however long you set it in Aseprite, frame
1 for its own time, and so on — variable per frame, exactly as authored.
Re-time the animation in Aseprite, re-export, and your game’s playback
updates with it. No magic number to keep in sync.
It needs a real Aseprite load. Point FRAMERATE OFSPRITE
at a plain PNG sprite sheet (which carries no per-frame durations) and
you halt with a clear message telling you why — not a silently frozen
sprite.
AUTOBBOX
— the hitbox follows the animationDraw a slice in Aseprite, animate it across your frames (it’s
keyframed, so a punch frame gets a wider box, a crouch frame a shorter
one), and AUTOBBOX applies it to the sprite automatically
as the animation plays:
LOADIMG "hero.ase", "HERO" AS SPRITESHEET(32, 32)
BEHAVIOR "attack"
ANIMATE "HERO", 0 TO 5 AUTOBBOX FRAMERATE OFSPRITE
END BEHAVIOR
Every time the animation advances, the bounding box updates to match
the slice on that frame. It feeds straight into the existing collision
system — boundary pushback, sprite-to-sprite,
SPRITECOLLISION(), SPRITETOUCHING$() — so your
hitbox is whatever you drew, when you drew it.
AUTOBBOX is independent of
FRAMERATE OFSPRITE; combine it with either that or a fixed
fps:
ANIMATE "HERO", 0 TO 5 AUTOBBOX FRAMERATE 12 ' fixed speed, animated hitbox
Bare AUTOBBOX uses the file’s first
slice. If your file has several slices and the first isn’t the one you
want, name it:
ANIMATE "HERO", 0 TO 5 AUTOBBOX "body" FRAMERATE OFSPRITE
A frame that no slice key covers simply leaves the box where it was.
Ask for AUTOBBOX on an image with no slices (or a name that
doesn’t exist) and you halt loud, with the available slice names
listed.
A sprite has a single collision box, so AUTOBBOX drives
one slice. But an .ase can hold several named slices at
once — a body, a sword, a feet point — and you can read any of them on
demand for your own (manual) hit checks:
' World-space box of a named slice at the sprite's CURRENT frame:
sword = SPRITEBBOX(player, "sword") ' (x1,y1)-(x2,y2) in world pixels
body = SPRITEBBOX(player) ' no name = the active collision box
' Relative-0-1 box of a slice on a specific frame, no sprite needed:
b = IMAGEBBOX("HERO", "feet", 3)
SET SPRITE 0 BBOX b
SPRITEBBOX(id, name$) gives you world coordinates
(sprite position + scale applied), ready to test against other things.
IMAGEBBOX(image$, name$, frame) gives the relative box
straight from the file. Name a slice that isn’t there and you halt loud,
with the available names listed.
LOADIMG … SLICES boxes()
— the rectangles, in your handsWant the slice boxes for yourself instead of (or in addition to) the
automatic hookup? Add a SLICES clause and
LOADIMG fills an array:
LOADIMG "hero.ase", "HERO" AS SPRITESHEET(32, 32) SLICES boxes()
' boxes(frame) is a (x1,y1)-(x2,y2) Range in relative 0–1 coordinates,
' one entry per frame — keyframes already expanded.
SET SPRITE 0, 100, 100, "HERO"
SET SPRITE 0 BBOX boxes(2) ' use frame 2's slice as the hitbox
Each element is a Range in relative 0–1 coordinates,
ready to drop straight into the new range form of BBOX
(below). The array is always dense and frame-indexable — frames with no
slice fall back to the full frame (0,0)-(1,1) — so
boxes(anyFrame) is always valid.
SLICES is Aseprite-only. Use it on a PNG, or on an
.ase with no slices, and you get a loud error rather than a
quietly empty array.
TAG("name") — load
just one animationAn .ase usually holds every animation in one file —
idle, run, jump, all on the same timeline, separated by Aseprite
tags. LOADIMG … TAG("name") pulls out just
one tag’s frames:
LOADIMG "hero.ase", "HERO_RUN" TAG("run")
BEHAVIOR "running"
ANIMATE "HERO_RUN", 0 TO 5 FRAMERATE OFSPRITE
END BEHAVIOR
Only the run tag’s frames are loaded, re-indexed from 0
— so ANIMATE … 0 TO 5 just plays the run, no offset math.
The per-frame durations and slice boxes come along narrowed to that tag,
so FRAMERATE OFSPRITE and AUTOBBOX keep
working on it. An .ase already implies a sprite sheet, so
you don’t even need AS SPRITESHEET — though you can still
add SLICES to grab the tag’s hitboxes.
Want several animations? Load the file once per tag, under different names:
LOADIMG "hero.ase", "HERO_IDLE" TAG("idle")
LOADIMG "hero.ase", "HERO_RUN" TAG("run")
LOADIMG "hero.ase", "HERO_JUMP" TAG("jump")
Tag names match case-insensitively. Ask for a tag that isn’t in the
file (or put TAG on a plain PNG) and you halt loud, with
the available tag names listed.
BBOX now takes a Range
tooTo make those slice rectangles pluggable,
SET SPRITE … BBOX learned a second form. Alongside the
classic four numbers, you can now hand it a single Range value:
SET SPRITE 0 BBOX(0.2, 0.2, 0.8, 0.8) ' the form you already know
SET SPRITE 0 BBOX boxes(curFrame) ' new: a relative-0–1 Range value
Both mean the same thing — a relative-0–1 collision box. The range
form just lets you pass one you already have (from SLICES,
or built yourself) without unpacking it into four arguments.
While we were in there, range values can now hold fractional
coordinates. A range literal like
(0.1, 0.2)-(0.8, 0.9) keeps its decimals instead of
truncating to whole numbers. Whole-number ranges are completely
unchanged — they print and step over the integer grid exactly as
before.
Two collision bugs that let a sprite slip through a
SOLID boundary are fixed:
Sliding along walls and ceilings is unchanged — only the pass-straight-through cases got closed.
All of this works the same in single-player and in CrashNet. The
Aseprite per-frame durations and slice boxes are read on every
interpreter when the file loads, so FRAMERATE OFSPRITE
timing and AUTOBBOX hitboxes resolve identically on the
server and on each client. Nothing new crosses the wire; just call
LOADIMG once at the top of your program as always.
The headline is Aseprite, but a batch of language polish rode along.
REDIM keeps your data
nowREDIM used to quietly throw away everything in the array
and hand you a fresh zero-filled one. Now it preserves
what fits: grow an array and your existing elements stay put (the new
slots default-fill); shrink it and the elements that remain are kept.
There’s no PRESERVE keyword to remember — preserving is
just what REDIM does. (Want a clean slate? That’s what
DIM and ERASE are for.)
PUSH / POP / QUEUE /
DEQUEUE are now explicitly 1-D list operations. Point one
at a multi-dimensional array and you get a clear error instead of an
array whose shape quietly stops matching its contents.
POLYGON takes an
array of pointsPOLYGON (and its UV clause) already
accepted a MUTATION. Now it also takes a plain array of
points — coordinate values (x, y), objects with
.x / .y, or an N×2 numeric array
— so you can build the geometry however is convenient and hand it
straight in.
COUNT(text$, part$) — how many times
part$ appears in text$.REVERSE$(text$) — the string,
backwards.MID$(text$, start) — the length is now
optional; leave it off to get everything from start to the
end.ASIN / ACOS /
LOG10 were always there; they’re in the reference
now.A few operations that used to fail quietly now stop with a clear message:
SQR of a negative number, and MOD by zero,
halt — they used to produce an un-representable NaN.COLLISION with an unknown shape name or too few
arguments, and NEWIMAGE with a zero/negative size, halt
instead of silently doing nothing.MUSICPLAYING actually reports playback
now (on desktop it was stuck on “not playing”), and reads 0
while paused.SPRITEDIRECTION returns an intuitive
0–360 (0 = right, 90 = down, 180 = left, 270 = up).MAPTILEX / MAPTILEY /
MAPWORLDTILEX / MAPWORLDTILEY return
-1 when there’s no map under the point, so tile
0 is no longer mistaken for “nothing there.”BBLINK 0 turns blinking off (and
leaves the sprite showing) instead of flickering every frame.FOR MAPDATA … LAYER n now actually
scans layer n.DATE$ reads as
MM/DD/YYYY.FRAMERATE fps, the four-argument
BBOX(x1,y1,x2,y2), plain PNG sprite sheets — all unchanged.
The new words are strictly additive.AUTOBBOX and OFSPRITE are new
keywords inside ANIMATE. They only mean something
in that position; variable and image names that merely contain those
letters are unaffected.TAG(…) is a new optional clause on
LOADIMG. Strictly additive — existing
LOADIMG calls are unchanged, and TAG only
applies to .ase files (it’s a loud error on a plain
PNG).REDIM preserves now. If any code
leaned on REDIM wiping an array back to zeros, use
DIM (or ERASE) for that instead.SPRITEDIRECTION range changed from
−180…180 to 0…360 — “up” reads 270 now (it was
−90). Adjust any angle comparisons that assumed the old
range.MAPTILEX / MAPTILEY return
-1 off-map (they used to return 0).
If you tested for 0 to mean “no tile,” test for
-1.DATE$ uses slashes
(MM/DD/YYYY). Code that parses it expecting dashes needs a
tweak.SQR of a negative, MOD by zero, a malformed
COLLISION, a non-positive NEWIMAGE. Anything
that relied on the old quiet NaN / 0 / no-op
will now stop with an error.Draw it once. Let the file remember the timing and the hitbox. Go make a game.
— Crash-tan