Rsheet is a generic Python toolkit for 2D animation (background removal, sprite sheet splitting, size normalization, animation playback). It knows nothing about your specific game: it works just as well for a fighting game, a platformer, a physics simulation... any 2D project that needs to animate sprites.
Why "Rsheet"? It all starts with the sprite sheet: Rsheet analyzes it to automatically find where the frames are, in a few seconds, so you don't have to do it by hand.
This README follows the full pipeline, in the order you'll use it on
a real project: 1) clean the background, 2) split the frames,
3) normalize the sizes, 4) play the animation. All screenshots use
the same example image, penguin_walk.png:
Installation
pip install rsheet
No local compilation is needed (the only heavy computation,
background detection, is delegated to scipy, distributed as
precompiled wheels).
Try it now
Clone the repo and run the example — it's ready to go, no setup needed:
git clone https://github.com/ELiijah-dev/rsheet.git
cd rsheet/examples
python demo.py
This opens a real pygame window playing a normalized, background-free animation, built end to end from a raw sprite sheet.
Demo — what you'll see in your console
This is exactly the script from step 4 below, run from VS Code: detection + splitting + normalization happen in a fraction of a second in the terminal, then the pygame window opens with the already-normalized animation ready to play.
(Compressed GIF for the preview — full video with sound and detailed logs)
1. rsheet.cached_removed_bg — isolating the character
A developer who just wants to strip a background often makes the mistake of targeting one specific color (the corner pixel, say). That breaks the moment the background has a slight gradient, anti-aliasing noise, or changes from one asset to another. Rsheet therefore assumes nothing about the color: it looks at the pixels along the edge of the image, infers the two dominant colors (K-means), then floods outward from those edges through everything connected that resembles the background — following the actual outline rather than guessing its shape ahead of time.
Concretely, cached_removed_bg returns a PNG with an alpha channel,
keeping only the character. The result is cached next to the source
file: the computation is only redone if the original image changes.
The tolerance parameter exists because "resembles" has no universal
answer: every sprite has its own level of noise around its edges, so
it's a dial to tune per project rather than a value baked into
Rsheet's code. The default is 40, but 6 is a good starting
point (raise it if background residue is still visible, lower it if
chunks of the character disappear).
import rsheet
png_transparent = rsheet.cached_removed_bg("penguin_walk.png", tolerance=6)
# -> penguin_walk._rsheet_bg_cache.png (background removed, ready to load in pygame)
| Before | After (tolerance=6) |
|---|---|
2. rsheet.sprite_editor — splitting and naming sprite sheets
The real problem this module solves isn't "cutting up an image" — it's the time lost manually saying "this row has 6 frames, that one has 4, that other one has 8". Rsheet treats this as a pure geometry problem rather than a layout one: a row of non-background pixels is an animation, a column of non-background pixels inside that row is a frame. No grid is assumed, so it works the same on a neatly arranged sheet or one full of gaps.
The random naming (rsheet.vocab) comes from a similar observation:
giving each detected animation a meaningful name is still a manual
task, while the code only needs a stable, unique key. Rsheet picks a
random name and guarantees it never collides with another already
used by that character — finding a free slot quickly rather than
choosing one yourself.
Concretely, process_project automatically detects the number of
rows (animations) and frames per row — you never specify a frame
count up front — then draws a unique name for each detected animation.
The result is saved to a text file (frame_coords.txt) which is then
used to build the in-game animation.
import rsheet
entries = rsheet.process_project(
"frame_coords.txt",
sheets=[
("penguin_walk.png", "penguin", "player"), # (file, character, role)
],
)
for e in entries:
print(e.character, e.sheet_num, list(e.animations.keys()))
# penguin 1 ['glide_a'] <- animation name drawn automatically
role ("player", "enemy", anything else, or None) is free-form —
Rsheet never enforces it, it just stores it.
3. rsheet.normalizer — consistent on-screen sizes
An artist never draws two frames at the exact same size — a raised wing takes up a bit more space than a lowered one, there's a few extra or missing pixels of empty space depending on the pose. If each frame were displayed as-is, the character would seem to slightly "float" or "jump" on every frame change, even while standing still.
The normalizer fixes this by computing, for each frame, its offset from a common reference size — then always anchoring to the ground rather than the center, so that only a character's head moves on a small variation, never its feet. The result is cached and invalidated by a hash of the coordinates file, because this computation only ever needs redoing if the splitting changed — not on every game launch.
Concretely, load_or_compute_norm_cache computes this offset for
every frame and saves it to frame_norm_cache.txt:
import pygame
import rsheet
norm = rsheet.load_or_compute_norm_cache(
"frame_coords.txt",
cache_path="frame_norm_cache.txt",
)
# frame_surface = the sub-image of one specific frame, cut from the
# sheet at the coordinates found by sprite_editor in step 2 (this is
# what `build_frame_cache`, in step 4, does for you automatically):
sheet = pygame.image.load("penguin_walk.png").convert_alpha()
frame_rect = entries[0].animations["glide_a"][0] # `entries` comes from step 2
frame_surface = sheet.subsurface(frame_rect.to_tuple()).copy()
dw, dh = norm["penguin"]["glide_a"][0]
surf = rsheet.apply_norm_to_surface(frame_surface, dw, dh) # ground-anchored
In practice, you'll almost never write this cutting logic by hand:
build_frame_cache(step 4) does exactly this, for every frame at once.
4. rsheet.animation — building and playing the animation
Steps 1 to 3 are deliberately "offline": they never touch pygame,
know nothing about a game loop, and write their result to plain text
files. rsheet.animation is the only module that bridges to the
runtime — it's the one that turns pixel rectangles into actual
pygame.Surface objects ready to be displayed. This separation exists
so the expensive computation (detection, normalization) is never
redone while the game is running.
AnimationController stays deliberately "dumb": it only knows how to
do one thing, advance a frame on a timer and loop — no fighting-game,
platformer, or other gameplay logic gets mixed in, so it fits any
project. Entity goes one step further by adding minimal physics
(gravity, jumping, movement) because that's such a common need it was
worth providing ready-made — but it stays optional: a project that
already has its own physics can use only AnimationController and
ignore Entity.
Concretely, build_frame_cache loads the sprite sheet as a
pygame.Surface, cuts each frame at the right spot and automatically
applies the normalization computed in step 3 — you get back
{animation_name: [surfaces...]} directly, ready to play with
AnimationController:
import pygame
import rsheet
pygame.init()
screen = pygame.display.set_mode((640, 360))
pygame.display.set_caption("Rsheet demo")
clock = pygame.time.Clock()
# Full pipeline (steps 1 to 3) — run once to prepare the files
png_transparent = rsheet.cached_removed_bg("penguin_walk.png", tolerance=6)
entries = rsheet.process_project(
"frame_coords.txt",
sheets=[(png_transparent, "penguin", "player")],
)
anim_name = list(entries[-1].animations.keys())[0] # actual name generated in step 2
sheet_num = entries[-1].sheet_num
rsheet.load_or_compute_norm_cache("frame_coords.txt", cache_path="frame_norm_cache.txt")
# Step 4 — build and play the animation
frames = rsheet.build_frame_cache("penguin", {sheet_num: png_transparent}, "frame_coords.txt")
anim = rsheet.AnimationController(frames=frames)
anim.play(anim_name)
x, y = 270, 115
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
dt = clock.tick(60) / 1000
anim.update(dt)
screen.fill((40, 40, 40))
screen.blit(anim.current_surface(), (x, y))
pygame.display.flip()
pygame.quit()
This script opens a real window and plays the animation on loop until
closed — copy-pasteable as-is (just put penguin_walk.png next to the
script, or swap in your own sprite sheet). This exact script is also
available ready to run in examples/demo.py — see Try it now
above.
For an entity with simple physics (movement, gravity, jumping),
Entity embeds an AnimationController directly — replace the last
two lines of the loop above with:
hero = rsheet.Entity(anim=anim)
hero.move(1) # moves to the right
hero.update(dt, ground_y=300) # physics + animation updated together
screen.blit(anim.current_surface(), (hero.x, hero.y))
What else is in there?
Rsheet also includes rsheet.vfx (generic light glow) and
rsheet.baking (animation pre-computation, facing-direction handling,
surface cropping/resizing) — useful once the 4 building blocks above
are in place, but not essential to get started. See each module's
docstrings for details.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file rsheet-0.1.1.tar.gz.
File metadata
- Download URL: rsheet-0.1.1.tar.gz
- Upload date:
- Size: 34.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.11.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ea48654fed9abde81b5199b27dbcec36be9f6fa16e101c56b92060a595b21cd7
|
|
| MD5 |
6e75fa29513bff8b766bb2703037ab4d
|
|
| BLAKE2b-256 |
f8606eac2624e82bc98e04d2b4f8e618d0b73c3d049b6607e2e19a37ab2249a7
|
File details
Details for the file rsheet-0.1.1-py3-none-any.whl.
File metadata
- Download URL: rsheet-0.1.1-py3-none-any.whl
- Upload date:
- Size: 34.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.11.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f6b5538321f63f653c855420e5344e3445e8f18a7a9c417177bcde64c6e9163c
|
|
| MD5 |
a9283829c06c0c25d9169237f4aa35e5
|
|
| BLAKE2b-256 |
e77d5871fba8421e8681501a69c176104f92ea9d803a74eb603f0971566f31e6
|