BitmapListAnimationLib — Sprite Sheet Animation
Animates through a sequence of images in a sprite sheet — frame-by-frame animation for sprites, loading spinners, character walk cycles, explosions, and any visual effect built from sequential frames. The animation cycles through equally-sized cells in a grid-based sprite sheet image, attached to an Image control. 47 functions.
| Category | Count | Description |
|---|---|---|
| Error Handling | 4 | bmplistani_error, errormsg$, strerror$, clearerror |
| Creation & Destruction | 3 | bmplistani# (2 overloads), bmplistani_free |
| Playback Control | 3 | start, stop, stopatcurrent |
| Sprite Sheet Config | 7 | animationbitmap (set/get), loadspritesheet#, animationcount, animationrowcount (get/set) |
| Timing | 4 | duration, delay (get/set) |
| Behavior — Easing | 4 | animationtype, interpolation (get/set) |
| Behavior — Flags | 8 | autoreverse, inverse, loop, enabled (get/set) |
| State Queries | 3 | running, normalizedtime, name$ |
| Property Name | 2 | propertyname (get/set) |
| Triggers | 4 | trigger, triggerinverse (get/set) |
| Events | 5 | onfinish, onprocess (get/set), clearcallbacks# |
Cross-Platform Support
| Platform | Status | Notes |
|---|---|---|
| Windows | ✅ Full Support | Win32/Win64 |
| Linux | ✅ Full Support | GTK-based |
| Android | ✅ Full Support | Use local file paths for assets |
Error Handling
| Function | Signature | Description |
|---|---|---|
bmplistani_error() | bmplistani_error@ | Last error code (0 = no error) |
bmplistani_errormsg$() | bmplistani_errormsg$@ | Last error message as string |
bmplistani_strerror$(code) | bmplistani_strerror$@n | Description for a given error code |
bmplistani_clearerror() | bmplistani_clearerror@ | Clear the error state |
Error Codes
| Code | Constant | Description |
|---|---|---|
| 0 | ERR_NONE | No error |
| 1 | ERR_NIL_ANIMATION | Animation pointer is nil |
| 2 | ERR_INVALID_BITMAP | Invalid bitmap or source image |
| 3 | ERR_INVALID_VALUE | Invalid parameter value |
| 4 | ERR_ANIMATION_RUNNING | Cannot modify while animation is running |
| 5 | ERR_FILE_NOT_FOUND | Sprite sheet file not found or load error |
How It Works
A bitmap list animation cycles through frames in a sprite sheet — a single image file containing a grid of equally-sized frames. The animation is attached to an Image control and swaps the displayed frame at the rate determined by the duration and frame count:
' 1. Create an Image control to display the sprite let sprite# = image#(frm#, 100, 100, 64, 64) ' 2. Create the animation on the Image let ani# = bmplistani#(sprite#) ' 3. Load sprite sheet from file bmplistani_loadspritesheet#(ani#, "https://plan9basic.com/assets/img_examples/spritesheet.png") ' 4. Configure frame grid bmplistani_animationcount#(ani#, 8) ' 8 total frames bmplistani_animationrowcount#(ani#, 2) ' arranged in 2 rows ' 5. Set timing (all 8 frames in 1 second = 8 FPS) bmplistani_duration#(ani#, 1.0) bmplistani_interpolation#(ani#, "Linear") ' 6. Loop and start bmplistani_loop#(ani#, 1) bmplistani_start(ani#)
bmplistani_error() after calling loadspritesheet#. If the file is not found or cannot be loaded, error code 5 is set and the animation will not display anything.Sprite Sheet Layout
Sprite sheets are organized as grids of equally-sized frames. Frames are numbered left-to-right, top-to-bottom, starting from 0:
animationcount = 8, animationrowcount = 2 → 4 columns × 2 rows
The engine automatically calculates frame size: frame width = sheet width ÷ columns, frame height = sheet height ÷ rows, where columns = animationcount ÷ animationrowcount.
FPS = animationcount ÷ duration. For example, 8 frames with 1.0s duration = 8 FPS. 8 frames with 0.5s duration = 16 FPS.Creating Sprite Sheets
Free Resources
- OpenGameArt.org — CC0 licensed game assets (coins, explosions, characters)
- Kenney.nl — Free CC0 game assets with characters, effects, and UI
- itch.io — Many free sprite packs (search “free sprite sheet”)
Tools for Creating Your Own
- Piskel (piskelapp.com) — Free online pixel art editor with sprite sheet export
- TexturePacker — Combine individual frames into optimized sprite sheets
- Any image editor — Create a grid manually with uniform frame sizes
Requirements
- All frames must have identical dimensions
- Frames arranged in a grid: left-to-right, top-to-bottom
- No gaps or spacing between frames
- PNG format recommended (supports transparency)
- Power-of-2 dimensions recommended for performance (64, 128, 256)
Interpolation & Easing
For sprite animations, interpolation controls how frame timing is distributed across the duration. "Linear" is almost always the right choice — it gives each frame equal screen time:
| Interpolation | Effect on Frame Timing | Best For |
|---|---|---|
"Linear" | Each frame shown for equal time | Almost all sprite animations (recommended) |
"Quadratic" | Slow start, fast end | Acceleration effects (wheels spinning up) |
"Cubic" | More pronounced acceleration | Dramatic speed-up effects |
Easing Types (AnimationType)
| Type | Effect | Description |
|---|---|---|
"In" | Slow start → fast end | Frame rate accelerates |
"Out" | Fast start → slow end | Frame rate decelerates |
"InOut" | Slow → fast → slow | Speed up then slow down |
Creation & Destruction
| Function | Signature | Description |
|---|---|---|
bmplistani#(parent#) | bmplistani#@# | Create animation attached to parent Image control |
bmplistani#(parent#, name$) | bmplistani#@#$ | Create named animation attached to parent Image |
bmplistani_free(ani#) | bmplistani_free@# | Destroy animation object |
image# control as its parent — it swaps the displayed bitmap frame on that Image.Playback Control
| Function | Signature | Description |
|---|---|---|
bmplistani_start(ani#) | bmplistani_start@# | Start the animation |
bmplistani_stop(ani#) | bmplistani_stop@# | Stop and reset to first frame |
bmplistani_stopatcurrent(ani#) | bmplistani_stopatcurrent@# | Stop at current frame |
Sprite Sheet Configuration
Loading
| Function | Signature | Description |
|---|---|---|
bmplistani_loadspritesheet#(ani#, filename$) | bmplistani_loadspritesheet#@#$ | Load sprite sheet from local file and assign bitmap |
bmplistani_animationbitmap#(ani#, sourceImage#) | bmplistani_animationbitmap#@## | Set bitmap from another Image control’s bitmap |
bmplistani_animationbitmap#(ani#) | bmplistani_animationbitmap#@# | Get the current sprite sheet bitmap pointer |
Grid Configuration
| Function | Signature | Description |
|---|---|---|
bmplistani_animationcount#(ani#, count) | bmplistani_animationcount#@#n | Set total number of frames |
bmplistani_animationcount(ani#) | bmplistani_animationcount@# | Get frame count |
bmplistani_animationrowcount#(ani#, rows) | bmplistani_animationrowcount#@#n | Set number of rows in sprite sheet grid |
bmplistani_animationrowcount(ani#) | bmplistani_animationrowcount@# | Get row count |
Timing
| Function | Signature | Description |
|---|---|---|
bmplistani_duration#(ani#, seconds) | bmplistani_duration#@#n | Set total animation duration in seconds |
bmplistani_duration(ani#) | bmplistani_duration@# | Get duration |
bmplistani_delay#(ani#, seconds) | bmplistani_delay#@#n | Set delay before animation starts |
bmplistani_delay(ani#) | bmplistani_delay@# | Get delay |
Behavior Flags
Easing & Interpolation
| Function | Signature | Description |
|---|---|---|
bmplistani_animationtype#(ani#, type$) | bmplistani_animationtype#@#$ | Set easing: "In", "Out", "InOut" |
bmplistani_animationtype$(ani#) | bmplistani_animationtype$@# | Get easing type |
bmplistani_interpolation#(ani#, type$) | bmplistani_interpolation#@#$ | Set interpolation curve |
bmplistani_interpolation$(ani#) | bmplistani_interpolation$@# | Get interpolation type |
Boolean Flags
| Function | Signature | Description |
|---|---|---|
bmplistani_autoreverse#(ani#, flag) | bmplistani_autoreverse#@#n | If 1, plays forward then backward (ping-pong) |
bmplistani_autoreverse(ani#) | bmplistani_autoreverse@# | Get autoreverse flag |
bmplistani_inverse#(ani#, flag) | bmplistani_inverse#@#n | If 1, plays frames in reverse order |
bmplistani_inverse(ani#) | bmplistani_inverse@# | Get inverse flag |
bmplistani_loop#(ani#, flag) | bmplistani_loop#@#n | If 1, loops indefinitely |
bmplistani_loop(ani#) | bmplistani_loop@# | Get loop flag |
bmplistani_enabled#(ani#, flag) | bmplistani_enabled#@#n | Enable or disable the animation |
bmplistani_enabled(ani#) | bmplistani_enabled@# | Get enabled state |
State Queries
| Function | Signature | Description |
|---|---|---|
bmplistani_running(ani#) | bmplistani_running@# | Returns 1 if currently animating |
bmplistani_normalizedtime(ani#) | bmplistani_normalizedtime@# | Returns progress from 0.0 to 1.0 |
bmplistani_name$(ani#) | bmplistani_name$@# | Get the animation’s name |
normalizedtime to determine which frame is showing: currentFrame = int(normalizedtime * animationcount). This is useful in onprocess callbacks for triggering events at specific frames.Property Name
| Function | Signature | Description |
|---|---|---|
bmplistani_propertyname#(ani#, name$) | bmplistani_propertyname#@#$ | Set property name for the animation |
bmplistani_propertyname$(ani#) | bmplistani_propertyname$@# | Get property name |
Triggers
| Function | Signature | Description |
|---|---|---|
bmplistani_trigger#(ani#, expr$) | bmplistani_trigger#@#$ | Set trigger expression (starts animation) |
bmplistani_trigger$(ani#) | bmplistani_trigger$@# | Get trigger expression |
bmplistani_triggerinverse#(ani#, expr$) | bmplistani_triggerinverse#@#$ | Set inverse trigger (reverses animation) |
bmplistani_triggerinverse$(ani#) | bmplistani_triggerinverse$@# | Get inverse trigger expression |
Events
| Event Setter | Getter | Callback Signature | Description |
|---|---|---|---|
bmplistani_onfinish#(ani#, func$) | bmplistani_onfinish$(ani#) | function name(sender#) | Fired when animation completes (not in loop mode) |
bmplistani_onprocess#(ani#, func$) | bmplistani_onprocess$(ani#) | function name(sender#) | Fired on every animation frame |
bmplistani_clearcallbacks#(ani#) | — | — | Disconnect all callbacks |
Complete Examples
Simple Sprite Animation
' Basic sprite animation from a local file let frm# = form#("Sprite Demo", 400, 300) form_position#(frm#, 4) let sprite# = image#(frm#, 150, 100, 100, 100) let spriteAni# = bmplistani#(sprite#) bmplistani_loadspritesheet#(spriteAni#, "https://plan9basic.com/assets/img_examples/spritesheet.png") bmplistani_animationcount#(spriteAni#, 4) bmplistani_animationrowcount#(spriteAni#, 1) bmplistani_duration#(spriteAni#, 0.5) bmplistani_interpolation#(spriteAni#, "Linear") bmplistani_loop#(spriteAni#, 1) ' Always check for load errors if bmplistani_error() <> 0 then print "Error: " + bmplistani_errormsg$() else bmplistani_start(spriteAni#) end if form_show(frm#) while form_visible(frm#) = 1 processmessages() end while
Character Walk Cycle (2-Row Grid)
let frm# = form#("Walk Cycle Demo", 400, 300) form_position#(frm#, 4) let bg# = rectangle#(frm#, 0, 0, 400, 300) rectangle_fill#(bg#, "#87CEEB") let character# = image#(frm#, 160, 150, 80, 100) ' 8 frames, 2 rows × 4 columns let walkAni# = bmplistani#(character#) bmplistani_loadspritesheet#(walkAni#, "https://plan9basic.com/assets/img_examples/character_walk.png") bmplistani_animationcount#(walkAni#, 8) bmplistani_animationrowcount#(walkAni#, 2) bmplistani_duration#(walkAni#, 0.8) bmplistani_interpolation#(walkAni#, "Linear") bmplistani_loop#(walkAni#, 1) bmplistani_start(walkAni#) form_show(frm#) while form_visible(frm#) = 1 processmessages() end while
Explosion (Play Once with Callback)
let frm# = form#("Explosion Demo", 400, 300) form_position#(frm#, 4) let bg# = rectangle#(frm#, 0, 0, 400, 300) rectangle_fill#(bg#, "#1a1a2e") let explosion# = image#(frm#, 100, 30, 200, 200) let explodeAni# = bmplistani#(explosion#) bmplistani_loadspritesheet#(explodeAni#, "https://plan9basic.com/assets/img_examples/explosion.png") bmplistani_animationcount#(explodeAni#, 12) bmplistani_animationrowcount#(explodeAni#, 3) bmplistani_duration#(explodeAni#, 0.8) bmplistani_interpolation#(explodeAni#, "Linear") ' Play once, then hide when done bmplistani_loop#(explodeAni#, 0) bmplistani_onfinish#(explodeAni#, "onexplosiondone") let btn# = button#(frm#, "Explode!", 150, 250, 100, 40) button_onclick#(btn#, "doexplode") form_show(frm#) function doexplode(sender#) image_visible#(explosion#, 1) bmplistani_start(explodeAni#) endfunction function onexplosiondone(sender#) image_visible#(explosion#, 0) endfunction while form_visible(frm#) = 1 processmessages() end while
Speed Control
let frm# = form#("Speed Control", 400, 380) form_position#(frm#, 4) let sprite# = image#(frm#, 150, 40, 100, 100) let spriteAni# = bmplistani#(sprite#) bmplistani_loadspritesheet#(spriteAni#, "https://plan9basic.com/assets/img_examples/spritesheet.png") bmplistani_animationcount#(spriteAni#, 8) bmplistani_animationrowcount#(spriteAni#, 1) bmplistani_duration#(spriteAni#, 1.0) bmplistani_interpolation#(spriteAni#, "Linear") bmplistani_loop#(spriteAni#, 1) bmplistani_start(spriteAni#) let speedLbl# = label#(frm#, "Duration: 1.00s") label_move#(speedLbl#, 130, 195) let fpsLbl# = label#(frm#, "(8.0 FPS)") label_move#(fpsLbl#, 165, 225) let btnSlower# = button#(frm#, "Slower", 80, 270, 100, 40) button_onclick#(btnSlower#, "slower") let btnFaster# = button#(frm#, "Faster", 220, 270, 100, 40) button_onclick#(btnFaster#, "faster") let currentSpeed = 1.0 form_show(frm#) function updatespeedlabels() local fps fps = 8 / currentSpeed label_text#(speedLbl#, "Duration: " + stri$(currentSpeed, 2) + "s") label_text#(fpsLbl#, "(" + stri$(fps, 1) + " FPS)") endfunction function slower(sender#) currentSpeed = currentSpeed + 0.25 if currentSpeed > 4.0 then currentSpeed = 4.0 bmplistani_stop(spriteAni#) bmplistani_duration#(spriteAni#, currentSpeed) bmplistani_start(spriteAni#) updatespeedlabels() endfunction function faster(sender#) currentSpeed = currentSpeed - 0.25 if currentSpeed < 0.25 then currentSpeed = 0.25 bmplistani_stop(spriteAni#) bmplistani_duration#(spriteAni#, currentSpeed) bmplistani_start(spriteAni#) updatespeedlabels() endfunction while form_visible(frm#) = 1 processmessages() end while
Ping-Pong (Auto-Reverse)
let frm# = form#("Ping-Pong Demo", 350, 300) form_position#(frm#, 4) let sprite# = image#(frm#, 125, 50, 100, 100) let spriteAni# = bmplistani#(sprite#) bmplistani_loadspritesheet#(spriteAni#, "https://plan9basic.com/assets/img_examples/character.png") bmplistani_animationcount#(spriteAni#, 6) bmplistani_animationrowcount#(spriteAni#, 1) bmplistani_duration#(spriteAni#, 1.5) bmplistani_interpolation#(spriteAni#, "Linear") bmplistani_autoreverse#(spriteAni#, 1) bmplistani_loop#(spriteAni#, 1) bmplistani_start(spriteAni#) let lbl# = label#(frm#, "Frames play forward then backward") label_move#(lbl#, 40, 180) let btnToggle# = button#(frm#, "Toggle Auto-Reverse", 95, 240, 160, 35) button_onclick#(btnToggle#, "toggle") form_show(frm#) function toggle(sender#) local current bmplistani_stop(spriteAni#) current = bmplistani_autoreverse(spriteAni#) if current = 1 then bmplistani_autoreverse#(spriteAni#, 0) label_text#(lbl#, "Forward only, then restarts") else bmplistani_autoreverse#(spriteAni#, 1) label_text#(lbl#, "Frames play forward then backward") end if bmplistani_start(spriteAni#) endfunction while form_visible(frm#) = 1 processmessages() end while
Troubleshooting
| Problem | Cause | Solution |
|---|---|---|
| “File not found” error (code 5) | Wrong file path | Use absolute path or ensure file is in working directory. Check spelling and extension. |
| Animation not visible | Image control hidden or zero-sized | Verify Image has proper dimensions and is visible. Check that sprite sheet loaded successfully. |
| Frames appear scrambled | Wrong count/row values | Ensure animationcount matches actual frame count. Ensure animationrowcount matches grid rows. |
| Animation too fast/slow | Wrong duration | FPS = frames ÷ duration. Adjust duration for desired speed. |
| Frames show partial images | Non-uniform frame sizes | All frames must be identical size. No gaps between frames in the sprite sheet. |
| Nothing happens after start | Missing bmplistani_start | Loading the sprite sheet does not auto-start. Call start explicitly. |
Best Practices
| Practice | Why |
|---|---|
Always use "Linear" interpolation | Gives each frame equal display time — correct for almost all sprite animations |
Check bmplistani_error() after loading | Catch file-not-found errors before attempting to start |
| Use PNG format with transparency | Alpha channel lets sprites display over any background |
| Use power-of-2 frame dimensions | 64×64, 128×128, 256×256 give best GPU performance |
| Calculate FPS: frames ÷ duration | 8 frames at 0.5s = 16 FPS; 8 frames at 1.0s = 8 FPS |
Use onfinish for one-shot effects | Explosions, damage flashes, and pick-ups should play once then hide |
Use autoreverse + loop for ping-pong | Breathing, idle bobbing, or pendulum-style animations |
| Stop before changing duration | Call stop, change duration, then start again |
Parent must be an image# control | BitmapListAnimation swaps frames on the Image bitmap |
| No gaps between frames in sheet | Engine divides sheet evenly — gaps cause misaligned frames |
Quick Reference
| Function | Signature | Description |
|---|---|---|
bmplistani_error / errormsg$ / strerror$ / clearerror | various | Error handling (4) |
bmplistani#(parent#[, name$]) | bmplistani#@# / bmplistani#@#$ | Create (2 overloads) |
bmplistani_free(ani#) | bmplistani_free@# | Destroy |
bmplistani_start / stop / stopatcurrent | various | Playback control (3) |
bmplistani_loadspritesheet# / animationbitmap# | various | Sprite loading (3) |
bmplistani_animationcount / animationrowcount | various | Grid config (4) |
bmplistani_duration / delay | various | Timing (4) |
bmplistani_animationtype / interpolation | various | Easing & curves (4) |
bmplistani_autoreverse / inverse / loop / enabled | various | Behavior flags (8) |
bmplistani_running / normalizedtime / name$ | various | State queries (3) |
bmplistani_propertyname# / propertyname$ | various | Property name (2) |
bmplistani_trigger / triggerinverse | various | Automatic triggers (4) |
bmplistani_onfinish / onprocess / clearcallbacks# | various | Events (5) |
47 functions. Part of the Plan9Basic Animation library system.