pyscroll
For Python 3.7+ and pygame 2.0+
A simple and fast module for animated scrolling maps for your new or existing game.
If you find this useful, please consider making a donation to help support it https://liberapay.com/ltheden/donate
Discord! https://discord.gg/2taTP4aYR6
Introduction
pyscroll is a generic module for making a fast scrolling image with pygame. It uses a lot of magic to get great framerates out of pygame. It only exists to draw a map. It doesn't load images or data, so you can use your own custom data structures, tile storage, ect.
pyscroll is compatible with pytmx (https://github.com/bitcraft/pytmx), so you can use your Tiled maps. It also has out-of-the-box support for pygame sprites.
Features
- Reload the map tiles and data without closing the game
- Sprites or plain surfaces can be drawn in layers
- Animated tiles
- Zoom in and out
- Includes optional drop-in replacement for pygame LayeredGroup
- Pixel alpha and colorkey tilesets are supported
- Drawing and scrolling shapes
- Fast and small footprint
- Speed is not affected by map size
- Support for pytmx loaded maps from Tiled Map Editor
Use It Like a Camera
In order to further simplify using scrolling maps, pyscroll includes a pygame sprite group that will render all sprites on the map and will correctly draw them over or under tiles. Sprites can use their rect in world coordinates, and the group will work like a camera, translating world coordinates to screen coordinates while rendering sprites and map layers.
It's also useful to make minimaps or create simple chunky graphics.
Installation
Install from pip
pip install pyscroll
You can also manually install it from source
python setup.py install
New Game Tutorial
This is a quick guide on building a new game with pyscroll and pygame. It uses the PyscrollGroup for efficient rendering. You are free to use any other pygame techniques and functions.
Open apps/tutorial/quest.py for a gentle introduction to pyscroll and the PyscrollGroup for pygame. There are plenty of comments to get you started.
The Quest demo shows how you can use a pyscroll group for drawing, how to load maps with pytmx, and how pyscroll can quickly render layers. Moving under some tiles will cause the Hero to be covered.
The repo wiki has more in-depth explanations of the tutorial code, including one way to implement sprite animation. Be sure to check it out. Anyone is welcome to make additions or improvements.
https://github.com/bitcraft/pyscroll/wiki
Example Use with pytmx
pyscroll and pytmx can load your maps from Tiled and use your pygame sprites. The following is a very basic way to load a map onto the screen.
from pytmx.util_pygame import load_pygame
import pygame
import pyscroll
class Sprite(pygame.sprite.Sprite):
"""
Simple Sprite class for on-screen things
"""
def __init__(self, surface):
self.image = surface
self.rect = surface.get_rect()
# Load TMX data
tmx_data = load_pygame("desert.tmx")
# Make the scrolling layer
map_layer = pyscroll.BufferedRenderer(
map_data=pyscroll.TiledMapData(tmx_data),
screen_size=(400,400),
)
# make the pygame SpriteGroup with a scrolling map
group = pyscroll.PyscrollGroup(map_layer=map_layer)
# Add sprite(s) to the group
surface = pygame.image.load("my_surface.png").convert_alpha()
sprite = Sprite(surface)
group.add(sprite)
# Center the camera on the sprite
group.center(sprite.rect.center)
# Draw map and sprites using the group
# Notice I did not `screen.fill` here! Clearing the screen is not
# needed since the map will clear it when drawn
group.draw(screen)
Adapting Existing Games / Map Data
pyscroll can be used with existing map data, but you will have to create a class to interact with pyscroll or adapt your data handler. Try to make it follow the same API as the TiledMapData adapter and you should be fine.
Give pyscroll surface to layer into the map
pyscroll can use a list of surfaces and render them on the map, taking account their layer position.
map_layer = pyscroll.BufferedRenderer(map_data, map_size)
# just an example for clarity. here's a made up game engine:
def game_engine_draw():
surfaces = list()
for game_object in my_game_engine:
# pyscroll uses normal pygame surfaces.
surface = game_object.get_surface()
# pyscroll will draw surfaces in screen coordinates, so translate them
# you need to use a rect to handle tiles that cover surfaces.
rect = game_object.get_screen_rect()
# the list called 'surfaces' is required for pyscroll
# notice the layer. this determines which layers the sprite will cover.
# layer numbers higher than this will cover the surface
surfaces.append((surface, rect, game_object.layer))
# tell pyscroll to draw to the screen, and use the surfaces supplied
map_layer.draw(screen, screen.get_rect(), surfaces)
FAQ
Why are tiles repeating while scrolling?
Pyscroll by default will not handle maps that are not completely filled with tiles. This is in consideration of drawing speed. To clarify, you can have several layers, some layers without tiles, and that is fine; the problem is when there are empty spaces in all the layers, leaving gaps in the entire map. There are two ways to fix this issue with the 1st solution being the best performance wise:
1. In Tiled (or your data), fill in the empty spots with a tile
For best performance, you must have a tile in each part of the map. You can create a simple background layer, and fill with single color tiles where there are gaps. Pyscroll is very fast even with several layers, so there is virtually no penalty.
2. Pass "alpha=True" to the BufferedRenderer constructor.
All internal buffers will now support 'per-pixel alpha' and the areas without tiles will be fully transparent. You may still have graphical oddities depending on if you clear the screen or not, so you may have to experiment here. Since per-pixel alpha buffers are used, overall performance will be reduced by about 33%
Why are there obvious/ugly 'streaks' when scrolling?
Streaks are caused by missing tiles. See the above answer for solutions.
Can I blit anything 'under' the scrolling map layer?
Yes! There are two ways to handle this situation...both are experimental, but should work. These options will cause the renderer to do more housekeeping, actively clearing empty spaces in the buffer, so overall performance will be reduced.
1. Pass "alpha=True" to the constructor.
When drawing the screen, first blit what you want to be under the map (like a background, or parallax layer), then draw the pyscroll renderer or group. Since per-pixel alpha buffers are used, overall performance will be reduced.
2. Set a colorkey.
Pass "colorkey=theColorYouWant" to the BufferedRenderer constructor. In theory, you can now blit the map layer over other surfaces with transparency, but beware that it will produce some nasty side effects:
- Overall, performance will be reduced, as empty ares are being filled.
- If mixing 'per-pixel alpha' tilesets, tile edges may show the colorkey.
Does the map layer support transparency?
Yes...and no. By default, pyscroll handles all transparency types very well for the tiles and you should not have issues with that. However, if you are trying to blit/draw the map over existing graphics and "see through" transparent areas under the map, then you will have to use the "alpha", or "colorkey" methods described above.
Does pyscroll support parallax layers?
Not directly. However, you can build you own parallax effects by passing "alpha=True" to the BufferedRenderer constructor and using one renderer for each layer. Then it is just a matter of scrolling at different speeds.