Scena
2D games and creative graphics in Python.
Built for learning to program, and for fun. Easy to begin, room to grow.
Tutorial · API Guide · Examples · Changelog
Scena is a Python framework for 2D games and creative graphics, built for learning to program and for making good games without the complications.
You write the game: what is on the screen, how it moves, what happens when two things touch, what comes next. Scena gives you a vocabulary in which each of these is a line or two that says what it means, and it takes care of the machinery underneath: the loop, the drawing, the timers, the cleanup. A first program is three lines, a whole game fits on one screen, and with over 100 ready-made looks, effects and backgrounds it looks good from the start.
A whole game, one screen of code
from random import uniform
from scena import *
init(width=800, height=600, title="Catch the stars")
Wallpaper("dark navy")
st.score = 0
label = Text(text="", top_left=(20, 580), font_size=22)
label["text"] @= lambda: f"Stars: {st.score}"
paddle = Sprite("k-rolling-ball/button_blue.png", width=90, center=(400, 50), group="paddle")
@every(0.5)
def drop_star():
star = Sprite("k-rolling-ball/star.png", size=36, center=(uniform(40, 760), 580), group="stars")
star.glow = True
def update(dt):
if keyp("LEFT"):
paddle.move_by(-420 * dt, 0)
if keyp("RIGHT"):
paddle.move_by(420 * dt, 0)
group["stars"].move_by(0, -180 * dt)
@collide("paddle", "stars")
def catch(paddle, star):
star.destroy()
st.score += 1
paddle.pop(scale=1.25)
@leave_playfield("stars")
def missed(star):
star.destroy()
run()
No game loop to write, no event queue to poll, no list of objects to draw or to
clean up. The label is a formula (@=): it keeps up with the score by itself.
Why Scena
-
Built for learning. The first program is a few lines; the concepts arrive one at a time, in the order a learner needs them. Programming is the point, so nothing is hidden behind a wizard.
-
Simple things are simple.
Circle()draws a circle.Sprite("ship.png")shows a ship. Parameters are gradual: start withCircle(), thenCircle(center=(200, 150), radius=40), thencircle.visual = Neon("cyan"). Add detail only when you want it. -
Code that says what it means.
@collide,@when,@everyand@clickread like the rules of the game. A formula (@=) states a relation once, and it stays true. A health bar that rides above the hero and shrinks with each hit, with no update code:bar = Rect(width=60, height=6, color="lime green") bar["center"] @= lambda: hero.center + (0, 40) # rides above the hero bar["width"] @= lambda: 60 * st.life / 100 # shrinks with each hit
-
You write the game. Scena handles the machinery, never the decisions. It does not invent content or behavior you did not ask for: what is on the screen is what your code says.
-
Mistakes explain themselves. A misspelled key, color or parameter gets a message that says what was wrong and suggests the closest right name.
What's in the box
Shapes and sprites. Circles, rectangles, polygons, lines, text, images and sprite sheets. Every one draws with nothing but defaults, and takes more when you want more. A sprite animates from a strip, a grid, one file per frame or a packed atlas (TexturePacker JSON, Kenney XML), and its hitbox can come from the art.
ball = Circle() # a circle in the middle of the window
ball.color = "coral"
ball.visual = Neon("cyan") # one of 30 looks
hero = Sprite(Sheet("ninja/Run__*.png", fps=12)) # an animation from its frames
hero.glow = True
Geometry. Build a shape the way you think of it (center and radius, two points, a point, an angle and a length), lay things out with boxes and grids, and ask for intersections, closest points, midpoints and tangents. With points you can drag and a few formulas, a compass-and-ruler construction stays live as you move it.
a = Anchor(420, 320) # points you can drag
b = Anchor(580, 380)
c1 = Circle(center=lambda: a.center, perimeter_point=lambda: b.center)
c2 = Circle(center=lambda: b.center, perimeter_point=lambda: a.center)
bisector = Line(p1=lambda: intersection(c1, c2, 0), p2=lambda: intersection(c1, c2, 1))
Animation in one line. Move by an offset, to a place, or along a track or a
shape's outline; by time or by speed; with easing, loops and yoyo, facing the way
you go. Turn, scale and fade the same way, or tween any number. Effects such as
pop, flash, dissolve_out and explosion_at clean up after themselves.
ship.move_to(400, 300, time=1.5, ease="ease_in_out") # to a place, in a time
ship.move_by(0, 200, speed=300) # by an offset, at a speed
probe.move_along(orbit, time=4, loops="forever", face=True) # along a path, facing ahead
enemy.scale_to(1.08, time=0.45, loops="forever", yoyo=True)
Rules and time. Conditions and timers are decorators, and after, every,
wait, until, race and parallel say how things happen over time. A
sequence reads like a script, top to bottom, with no state machine to build: a
boss arrives, waits, and starts the fight.
@when(lambda: st.score >= 10)
def level_up():
st.speed *= 1.5
class Boss(Sprite):
def setup(self):
self.mode = self.arriving
@on_mode
async def arriving(self):
await self.move_to(750, 600, time=2, ease="ease_out")
await wait(0.5)
self.pop(scale=1.3)
self.mode = self.fighting
Formulas. Link a value to others once, in the constructor or later with
@=; Scena keeps it up to date.
a = Circle(center=(200, 300), radius=8)
b = Circle(center=(600, 300), radius=8)
bridge = Line(p1=lambda: a.center, p2=lambda: b.center) # follows a and b
size = Text(text="", font_size=18)
size["text"] @= lambda: f"{distance(a, b):.0f} px" # measures it
size["center"] @= lambda: middle_point(a, b) # and sits in the middle
Collision. A collision between objects or groups is one decorator. Ask for automatic resolution and walls stop the hero; one-way platforms are one rule. Hitboxes are made from the art, or shaped by hand when you want.
collision.floor = True # a world with gravity
resolve("hero", "walls")
resolve("hero", "platforms", only=lambda c: c.side == "bottom") # one-way
@collide("shots", "enemies")
def hit(shot, enemy):
canvas.explosion_at(enemy)
shot.destroy()
enemy.destroy()
Looks, effects and backgrounds. Over 100 ready to use: looks for shapes, screen effects, animated backgrounds and one-shot explosions, all composable. Any look works on any shape or sprite.
from scena import *
init(width=900, height=300, title="One look, any host")
Wallpaper("dark navy")
ball = Circle(center=(150, 150), radius=70, color="royal blue")
star = RegularPolygon(n=5, center=(450, 150), radius=85, color="gold")
plane = Sprite("plane/Fly1.png", height=110, center=(750, 150))
for thing in (ball, star, plane):
thing.visual = ElectricField()
run()
visual = Flame() |
Portal() |
canvas.explosion_at(x, y) |
Stardust() |
visual = Water() |
ship.dissolve_out(0.5) |
Mouse and widgets. Everything a good interface, a board game or a strategy game needs, made simple. Click, hover, drag, drop and wheel-rotate work on any shape, sprite or group. Buttons, sliders, check boxes, drop-downs, menus and text inputs bind straight to your game's state.
@drag_to(token) # the token can be dragged; this runs when it is let go
def dropped(token, target, pos):
if target is None:
token.move_to(home, time=0.3) # nowhere to go: back home
@hover(group["cards"])
def lift(card):
card.scale_to(1.1, time=0.1)
Slider(st["radius"], label="Radius", min_value=10, max_value=100)
Button(label="New game", on_click=new_game)
Scena's own level editor is built with Scena.
And more. A camera that pans, zooms, shakes and frames; layers and
parallax; Endless worlds that build themselves as the camera moves and rebuild
the same piece when you walk back; tracks to follow; platform helpers (the floor
ahead, which sides touch, leaning on slopes); acts for menus and levels; sound
effects, music, filters and the microphone.
Level editor
scena-editor comes with Scena. Draw platforms, place points and images, give
them names and groups, and save a level file. The editor places geometry; your
code decides what each piece becomes.
scena-editor levels/ninja_perigos.json
level = Level("levels/ninja_perigos.json")
for piece in level.group("chao"):
piece.make() # the shape as drawn
lift = level["elevador"].make(visual=Metal("gold")) # or as you choose
Gallery
Every clip below is a program from examples/,
played and recorded by the computer (nothing is edited by hand). Each one is the
last step of a tutorial.
Install
Scena needs Python 3.12 or newer, on Windows, macOS or Linux.
pip install scena
Then, in any script: from scena import *.
To try the examples, download
scena-examples.zip
(the examples of the latest release and the images and sounds they use), unzip it
and run one:
python scena-examples/naves_4_escudo.py
Learn
- Tutorial: from the first
shapes to a space shooter, bouncing balls, a side-scrolling plane, a ninja
platformer built in the level editor, a board game, a voice-driven music
studio and an endless runner. Each one ends in a file in
examples/. - API Guide: every class, function and effect, with a live picture of each look.
The tutorial and the Guide are in Portuguese today; the English version is in progress. The API itself (every name you type) is in English.
Is Scena for you?
Scena is for learning to program, for small 2D games and for creative coding, and for having fun doing it.
- Simple code, smooth games. Your code is about the game's logic, not about the machine. Scena is written in Python and does a lot for you, and it is made to run typical games (tens to hundreds of objects) at 60 fps on an ordinary computer. When a scene asks for more than the machine has, it lowers the detail of its effects before it drops frames.
- Scripts or classes, both simple. Scena was built for both. A flat script
with no classes is a complete game (the one above). When an enemy deserves its
own behavior,
class Enemy(Sprite)withsetupandon_updateis all a class needs. Events, modes and acts work the same in either style. - What it is not. It is not trying to be a professional engine: it is 2D only, runs on the desktop, and is made for scenes with tens to hundreds of objects. Simple games stay simple, even if that means less power for very large ones.
License
Scena's code and documentation are under the MIT license.
The images and sounds in examples/media/ come from other artists under their own
licenses (mostly CC0). A few sounds ask for credit; the lines to copy are in
examples/media/CREDITS.md.
Contributions are welcome under the same license: see CONTRIBUTING.md.
By André Mota. For my sons, with much love.
Metadata
Release files for scena 0.9.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| scena-0.9.0.tar.gz | 2.8 MB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| scena-0.9.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 4.2 MB
Release files / scena-0.9.0.tar.gz
| Download URL | scena-0.9.0.tar.gz |
|---|---|
| Size | 2.8 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
bbe23e2022a4249c58b092a1848167a7340859fb3babdce77b12e175f5606419
|
|
BLAKE2b-256 checksum How to use checksums |
03116aea81778c95958733e2e01ba87ec418c50d3174884a2db99e7d3a0980cd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.
Transparency logRelease files / scena-0.9.0-py3-none-any.whl
| Download URL | scena-0.9.0-py3-none-any.whl |
|---|---|
| Size | 1.4 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8d01df23d9c85b924ab7950546b4c2403de691ad3eaec79d3cc68c95fcc344a6
|
|
BLAKE2b-256 checksum How to use checksums |
b2cdc7dd875ad314158cfde90a992709b83de191e868fa684f10ccb5d782bbfa
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.
Transparency logSecurity for scena 0.9.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for scena-0.9.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.