├── .github └── workflows │ └── python-app.yml ├── .gitignore ├── LICENSE.txt ├── MANIFEST.in ├── README.md ├── apps ├── demo │ ├── 16x16-overworld.png │ ├── 16x16-overworld.tsx │ ├── animated.tmx │ ├── demo-stitched.py │ ├── demo.py │ ├── desert.tmx │ ├── hero.png │ ├── stitched.world │ ├── stitched0.tmx │ ├── stitched1.tmx │ ├── stitched2.tmx │ ├── stitched3.tmx │ ├── stitched4.tmx │ ├── stitched5.tmx │ ├── stitched6.tmx │ ├── stitched7.tmx │ ├── stitched8.tmx │ ├── tmw_desert_spacing.png │ ├── translate.py │ ├── water.png │ └── water.tsx └── tutorial │ ├── credits │ ├── data │ ├── grasslands.tmx │ ├── grasslands_small.tmx │ ├── hero.png │ ├── readme.md │ ├── revolution_grasslands.png │ ├── revolution_grasslands_alpha.png │ ├── revolution_snow.png │ ├── revolution_town.png │ ├── water.png │ └── water.tsx │ └── quest.py ├── docs ├── Makefile ├── _build │ ├── doctrees │ │ ├── environment.pickle │ │ ├── index.doctree │ │ └── pyscroll.doctree │ └── html │ │ ├── .buildinfo │ │ ├── _modules │ │ └── index.html │ │ ├── _sources │ │ ├── index.txt │ │ └── pyscroll.txt │ │ ├── _static │ │ ├── ajax-loader.gif │ │ ├── basic.css │ │ ├── comment-bright.png │ │ ├── comment-close.png │ │ ├── comment.png │ │ ├── default.css │ │ ├── doctools.js │ │ ├── down-pressed.png │ │ ├── down.png │ │ ├── file.png │ │ ├── jquery.js │ │ ├── minus.png │ │ ├── plus.png │ │ ├── pygments.css │ │ ├── searchtools.js │ │ ├── sidebar.js │ │ ├── underscore.js │ │ ├── up-pressed.png │ │ ├── up.png │ │ └── websupport.js │ │ ├── genindex.html │ │ ├── index.html │ │ ├── objects.inv │ │ ├── py-modindex.html │ │ ├── pyscroll.html │ │ ├── search.html │ │ └── searchindex.js ├── conf.py ├── index.rst ├── make.bat └── pyscroll.rst ├── pyproject.toml ├── pyscroll ├── __init__.py ├── animation.py ├── common.py ├── data.py ├── group.py ├── isometric.py ├── orthographic.py └── quadtree.py ├── setup.cfg ├── setup.py └── tests └── pyscroll ├── test_isometric.py └── test_pyscroll.py /.github/workflows/python-app.yml: -------------------------------------------------------------------------------- 1 | name: Pyscroll 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | python-version: ['3.9', '3.10', '3.11', '3.12'] 16 | name: Python ${{ matrix.python-version }} 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Set up Python 20 | uses: actions/setup-python@v5 21 | with: 22 | python-version: ${{ matrix.python-version }} 23 | architecture: x64 24 | - name: Install dependencies 25 | run: | 26 | python -m pip install --upgrade pip 27 | pip install flake8 pytest 28 | pip install pygame pytmx 29 | - name: Lint with flake8 30 | run: | 31 | # stop the build if there are Python syntax errors or undefined names 32 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 33 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 34 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 35 | - name: Test 36 | run: | 37 | python -m unittest discover -s tests/pyscroll -p "test_*.py" 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | __pycache__ 21 | venv 22 | 23 | # Installer logs 24 | pip-log.txt 25 | 26 | # Unit test / coverage reports 27 | .coverage 28 | .tox 29 | nosetests.xml 30 | 31 | # Translations 32 | *.mo 33 | 34 | # Mr Developer 35 | .mr.developer.cfg 36 | .project 37 | .pydevproject 38 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | prune .github 2 | exclude .gitignore 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | pyscroll 2 | ======== 3 | 4 | For Python 3.9+ and pygame 2.0+ 5 | 6 | __pygame-ce is supported__ 7 | 8 | A simple and fast module for animated scrolling maps for your new or existing 9 | game. 10 | 11 | If you find this useful, please consider making a donation to help support it 12 | https://liberapay.com/ltheden/donate 13 | 14 | Discord! https://discord.gg/2taTP4aYR6 15 | 16 | 17 | Introduction 18 | ============ 19 | 20 | pyscroll is a generic module for making a fast scrolling image with pygame. It 21 | uses a lot of magic to get great framerates out of pygame. It only exists to 22 | draw a map. It doesn't load images or data, so you can use your own custom 23 | data structures, tile storage, ect. 24 | 25 | pyscroll is compatible with pytmx (https://github.com/bitcraft/pytmx), so you 26 | can use your Tiled maps. It also has out-of-the-box support for pygame 27 | sprites. 28 | 29 | 30 | Features 31 | ======== 32 | 33 | - Reload the map tiles and data without closing the game 34 | - Sprites or plain surfaces can be drawn in layers 35 | - Animated tiles 36 | - Zoom in and out 37 | - Includes optional drop-in replacement for pygame LayeredGroup 38 | - Pixel alpha and colorkey tilesets are supported 39 | - Drawing and scrolling shapes 40 | - Fast and small footprint 41 | - Speed is not affected by map size 42 | - Support for pytmx loaded maps from Tiled Map Editor 43 | 44 | 45 | Use It Like a Camera 46 | ==================== 47 | 48 | In order to further simplify using scrolling maps, pyscroll includes a pygame 49 | sprite group that will render all sprites on the map and will correctly draw 50 | them over or under tiles. Sprites can use their rect in world coordinates, and 51 | the group will work like a camera, translating world coordinates to screen 52 | coordinates while rendering sprites and map layers. 53 | 54 | It's also useful to make minimaps or create simple chunky graphics. 55 | 56 | 57 | Installation 58 | =============================================================================== 59 | 60 | Install from pip 61 | 62 | pip install pyscroll 63 | 64 | 65 | You can also manually install it from source 66 | 67 | python setup.py install 68 | 69 | 70 | New Game Tutorial 71 | ================= 72 | 73 | This is a quick guide on building a new game with pyscroll and pygame. It uses 74 | the PyscrollGroup for efficient rendering. You are free to use any other 75 | pygame techniques and functions. 76 | 77 | Open apps/tutorial/quest.py for a gentle introduction to pyscroll and the 78 | PyscrollGroup for pygame. There are plenty of comments to get you started. 79 | 80 | The Quest demo shows how you can use a pyscroll group for drawing, how to load 81 | maps with pytmx, and how pyscroll can quickly render layers. Moving under some 82 | tiles will cause the Hero to be covered. 83 | 84 | The repo wiki has more in-depth explanations of the tutorial code, including 85 | one way to implement sprite animation. Be sure to check it out. Anyone is 86 | welcome to make additions or improvements. 87 | 88 | https://github.com/bitcraft/pyscroll/wiki 89 | 90 | 91 | Example Use with pytmx 92 | ====================== 93 | 94 | pyscroll and pytmx can load your maps from Tiled and use your pygame sprites. 95 | The following is a very basic way to load a map onto the screen. 96 | 97 | ```python 98 | from pytmx.util_pygame import load_pygame 99 | import pygame 100 | import pyscroll 101 | 102 | 103 | class Sprite(pygame.sprite.Sprite): 104 | """ 105 | Simple Sprite class for on-screen things 106 | 107 | """ 108 | def __init__(self, surface) -> None: 109 | self.image = surface 110 | self.rect = surface.get_rect() 111 | 112 | 113 | # Load TMX data 114 | tmx_data = load_pygame("desert.tmx") 115 | 116 | # Make the scrolling layer 117 | map_layer = pyscroll.BufferedRenderer( 118 | data=pyscroll.TiledMapData(tmx_data), 119 | size=(400,400), 120 | ) 121 | 122 | # make the pygame SpriteGroup with a scrolling map 123 | group = pyscroll.PyscrollGroup(map_layer=map_layer) 124 | 125 | # Add sprite(s) to the group 126 | surface = pygame.image.load("my_surface.png").convert_alpha() 127 | sprite = Sprite(surface) 128 | group.add(sprite) 129 | 130 | # Center the camera on the sprite 131 | group.center(sprite.rect.center) 132 | 133 | # Draw map and sprites using the group 134 | # Notice I did not `screen.fill` here! Clearing the screen is not 135 | # needed since the map will clear it when drawn 136 | group.draw(screen) 137 | ``` 138 | 139 | 140 | Adapting Existing Games / Map Data 141 | ================================== 142 | 143 | pyscroll can be used with existing map data, but you will have to create a 144 | class to interact with pyscroll or adapt your data handler. Try to make it 145 | follow the same API as the TiledMapData adapter and you should be fine. 146 | 147 | #### Give pyscroll surface to layer into the map 148 | 149 | pyscroll can use a list of surfaces and render them on the map, taking account 150 | their layer position. 151 | 152 | ```python 153 | map_layer = pyscroll.BufferedRenderer(map_data, map_size) 154 | 155 | # just an example for clarity. here's a made up game engine: 156 | 157 | def game_engine_draw(): 158 | surfaces = list() 159 | for game_object in my_game_engine: 160 | 161 | # pyscroll uses normal pygame surfaces. 162 | surface = game_object.get_surface() 163 | 164 | # pyscroll will draw surfaces in screen coordinates, so translate them 165 | # you need to use a rect to handle tiles that cover surfaces. 166 | rect = game_object.get_screen_rect() 167 | 168 | # the list called 'surfaces' is required for pyscroll 169 | # notice the layer. this determines which layers the sprite will cover. 170 | # layer numbers higher than this will cover the surface 171 | surfaces.append((surface, rect, game_object.layer)) 172 | 173 | # tell pyscroll to draw to the screen, and use the surfaces supplied 174 | map_layer.draw(screen, screen.get_rect(), surfaces) 175 | ``` 176 | 177 | 178 | FAQ 179 | === 180 | 181 | ## Why are tiles repeating while scrolling? 182 | Pyscroll by default will not handle maps that are not completely filled with 183 | tiles. This is in consideration of drawing speed. To clarify, you can have 184 | several layers, some layers without tiles, and that is fine; the problem is 185 | when there are empty spaces in all the layers, leaving gaps in the entire map. 186 | There are two ways to fix this issue with the 1st solution being the best 187 | performance wise: 188 | 189 | ##### 1. In Tiled (or your data), fill in the empty spots with a tile 190 | For best performance, you must have a tile in each part of the map. You can 191 | create a simple background layer, and fill with single color tiles where there 192 | are gaps. Pyscroll is very fast even with several layers, so there is 193 | virtually no penalty. 194 | 195 | ##### 2. Pass "alpha=True" to the BufferedRenderer constructor. 196 | All internal buffers will now support 'per-pixel alpha' and the areas without 197 | tiles will be fully transparent. You *may* still have graphical oddities 198 | depending on if you clear the screen or not, so you may have to experiment 199 | here. Since per-pixel alpha buffers are used, overall performance will be 200 | reduced by about 33% 201 | 202 | 203 | ## Why are there obvious/ugly 'streaks' when scrolling? 204 | Streaks are caused by missing tiles. See the above answer for solutions. 205 | 206 | ## Can I blit anything 'under' the scrolling map layer? 207 | Yes! There are two ways to handle this situation...both are experimental, but 208 | should work. These options will cause the renderer to do more housekeeping, 209 | actively clearing empty spaces in the buffer, so overall performance will be 210 | reduced. 211 | 212 | ##### 1. Pass "alpha=True" to the constructor. 213 | When drawing the screen, first blit what you want to be under the map (like 214 | a background, or parallax layer), then draw the pyscroll renderer or group. 215 | Since per-pixel alpha buffers are used, overall performance will be reduced. 216 | 217 | ##### 2. Set a colorkey. 218 | Pass "colorkey=theColorYouWant" to the BufferedRenderer constructor. In 219 | theory, you can now blit the map layer over other surfaces with transparency, 220 | but beware that it will produce some nasty side effects: 221 | 222 | 1. Overall, performance will be reduced, as empty ares are being filled. 223 | 2. If mixing 'per-pixel alpha' tilesets, tile edges may show the colorkey. 224 | 225 | ## Does the map layer support transparency? 226 | Yes...and no. By default, pyscroll handles all transparency types very well 227 | for the tiles and you should not have issues with that. However, if you are 228 | trying to blit/draw the map *over* existing graphics and "see through" 229 | transparent areas under the map, then you will have to use the "alpha", or 230 | "colorkey" methods described above. 231 | 232 | ## Does pyscroll support parallax layers? 233 | Not directly. However, you can build you own parallax effects by passing 234 | "alpha=True" to the BufferedRenderer constructor and using one renderer for 235 | each layer. Then it is just a matter of scrolling at different speeds. 236 | -------------------------------------------------------------------------------- /apps/demo/16x16-overworld.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcraft/pyscroll/31e1c282d09542ec89c6c1ac3ee6a1ff991ff5c2/apps/demo/16x16-overworld.png -------------------------------------------------------------------------------- /apps/demo/16x16-overworld.tsx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /apps/demo/animated.tmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | EgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAADYAQAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAAM4BAACbAQAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAzQEAAJsBAADLAQAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAADOAQAAmwEAAJsBAACbAQAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAAM0BAACbAQAAmwEAAJsBAACbAQAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAAM0BAADOAQAAzgEAAJsBAACbAQAAmwEAAJsBAACbAQAAmwEAAMsBAACbAQAAmwEAANUBAADWAQAAmwEAAJsBAACbAQAAmwEAAJsBAACbAQAAzQEAAMoBAACbAQAAmwEAANUBAADWAQAAmwEAAJsBAACbAQAAmwEAAJsBAADKAQAAmwEAAJsBAACbAQAAmwEAAJsBAACbAQAA 38 | 39 | 40 | 41 | 42 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8wAAAPQAAAD1AAAAAAAAAPYAAAD3AAAA+AAAAPkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwEAAAQBAAAFAQAAAAAAAAAAAAAHAQAACAEAAAkBAAD3AAAA+AAAAPkAAAAAAAAAAAAAAAAAAAAAAAAAEwEAABQBAAAVAQAAAAAAAAAAAAAXAQAAGAEAABkBAAAHAQAACAEAAAkBAAD2AAAAAAAAAAAAAAAAAAAAAAAAACQBAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXAQAAGAEAABkBAAAAAAAAAAAAAAAAAAAAAAAAAAAAACQBAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPYAAAD2AAAAAAAAAAAAAAAAAAAAAAAAACQBAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEMBAAA1AQAANQEAADQBAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQBAAAAAAAAAAAAAAAAAABCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEIAAAAjAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQgAAACMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCAAAAIwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEIAAAAjAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEIAAAAiAAAAIgAAACMAAAAAAAAAAAAAAAAAAAAAAAAAIgAAACIAAAAiAAAAIgAAACIAAAAiAAAAIgAAACMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /apps/demo/demo-stitched.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Rendering demo showing 9 TMX maps rendered at once 4 | 5 | 6 | Very basic! No animations. 7 | 8 | """ 9 | from __future__ import annotations 10 | 11 | from pathlib import Path 12 | 13 | import pygame 14 | from pygame.locals import ( 15 | K_DOWN, 16 | K_EQUALS, 17 | K_ESCAPE, 18 | K_LEFT, 19 | K_MINUS, 20 | K_RIGHT, 21 | K_UP, 22 | KEYDOWN, 23 | QUIT, 24 | VIDEORESIZE, 25 | K_r, 26 | ) 27 | from pytmx.util_pygame import load_pygame 28 | 29 | import pyscroll 30 | from pyscroll.data import MapAggregator, TiledMapData 31 | from pyscroll.group import PyscrollGroup 32 | 33 | # define configuration variables here 34 | CURRENT_DIR = Path(__file__).parent 35 | RESOURCES_DIR = CURRENT_DIR 36 | HERO_MOVE_SPEED = 200 # pixels per second 37 | 38 | 39 | def init_screen(width: int, height: int) -> pygame.Surface: 40 | screen = pygame.display.set_mode((width, height), pygame.RESIZABLE) 41 | return screen 42 | 43 | 44 | def load_image(filename: str) -> pygame.Surface: 45 | return pygame.image.load(str(RESOURCES_DIR / filename)) 46 | 47 | 48 | class Hero(pygame.sprite.Sprite): 49 | def __init__(self) -> None: 50 | super().__init__() 51 | self.image = load_image("hero.png").convert_alpha() 52 | self.velocity = [0, 0] 53 | self._position = [0.0, 0.0] 54 | self._old_position = self.position 55 | self.rect = self.image.get_rect() 56 | self.feet = pygame.Rect(0, 0, self.rect.width * 0.5, 8) 57 | 58 | @property 59 | def position(self) -> list[float]: 60 | return list(self._position) 61 | 62 | @position.setter 63 | def position(self, value: list[float]) -> None: 64 | self._position = list(value) 65 | 66 | def update(self, dt: float) -> None: 67 | self._old_position = self._position[:] 68 | self._position[0] += self.velocity[0] * dt 69 | self._position[1] += self.velocity[1] * dt 70 | self.rect.topleft = self._position 71 | self.feet.midbottom = self.rect.midbottom 72 | 73 | def move_back(self, dt: float) -> None: 74 | self._position = self._old_position 75 | self.rect.topleft = self._position 76 | self.feet.midbottom = self.rect.midbottom 77 | 78 | 79 | class QuestGame: 80 | map_path = RESOURCES_DIR / "grasslands.tmx" 81 | 82 | def __init__(self, screen: pygame.Surface) -> None: 83 | self.screen = screen 84 | self.running = False 85 | 86 | world_data = MapAggregator((16, 16)) 87 | for filename, offset in [ 88 | ("stitched0.tmx", (-20, -20)), 89 | ("stitched1.tmx", (0, -20)), 90 | ("stitched2.tmx", (20, -20)), 91 | ("stitched3.tmx", (-20, 0)), 92 | ("stitched4.tmx", (0, 0)), 93 | ("stitched5.tmx", (20, 0)), 94 | ("stitched6.tmx", (-20, 20)), 95 | ("stitched7.tmx", (0, 20)), 96 | ("stitched8.tmx", (20, 20)), 97 | ]: 98 | tmx_data = load_pygame(RESOURCES_DIR / filename) 99 | pyscroll_data = TiledMapData(tmx_data) 100 | world_data.add_map(pyscroll_data, offset) 101 | 102 | self.map_layer = pyscroll.BufferedRenderer( 103 | data=world_data, 104 | size=screen.get_size(), 105 | clamp_camera=True, 106 | ) 107 | self.map_layer.zoom = 2 108 | self.group = PyscrollGroup(map_layer=self.map_layer, default_layer=0) 109 | 110 | # put the hero in the center of the map 111 | self.hero = Hero() 112 | self.hero.layer = 0 113 | self.hero.position = (400, 400) 114 | 115 | # add our hero to the group 116 | self.group.add(self.hero) 117 | 118 | def draw(self) -> None: 119 | self.group.center(self.hero.rect.center) 120 | self.group.draw(self.screen) 121 | 122 | def handle_input(self) -> None: 123 | """ 124 | Handle pygame input events 125 | 126 | """ 127 | for event in pygame.event.get(): 128 | if event.type == QUIT: 129 | self.running = False 130 | break 131 | 132 | elif event.type == KEYDOWN: 133 | if event.key == K_ESCAPE: 134 | self.running = False 135 | break 136 | 137 | elif event.key == K_r: 138 | self.map_layer.reload() 139 | 140 | elif event.key == K_EQUALS: 141 | self.map_layer.zoom += 0.25 142 | 143 | elif event.key == K_MINUS: 144 | value = self.map_layer.zoom - 0.25 145 | if value > 0: 146 | self.map_layer.zoom = value 147 | 148 | # this will be handled if the window is resized 149 | elif event.type == VIDEORESIZE: 150 | self.screen = init_screen(event.w, event.h) 151 | self.map_layer.set_size((event.w, event.h)) 152 | 153 | # use `get_pressed` for an easy way to detect held keys 154 | pressed = pygame.key.get_pressed() 155 | if pressed[K_UP]: 156 | self.hero.velocity[1] = -HERO_MOVE_SPEED 157 | elif pressed[K_DOWN]: 158 | self.hero.velocity[1] = HERO_MOVE_SPEED 159 | else: 160 | self.hero.velocity[1] = 0 161 | 162 | if pressed[K_LEFT]: 163 | self.hero.velocity[0] = -HERO_MOVE_SPEED 164 | elif pressed[K_RIGHT]: 165 | self.hero.velocity[0] = HERO_MOVE_SPEED 166 | else: 167 | self.hero.velocity[0] = 0 168 | 169 | def update(self, dt: float) -> None: 170 | """ 171 | Tasks that occur over time should be handled here 172 | 173 | """ 174 | self.group.update(dt) 175 | 176 | def run(self) -> None: 177 | clock = pygame.time.Clock() 178 | self.running = True 179 | 180 | try: 181 | while self.running: 182 | dt = clock.tick() / 1000.0 183 | self.handle_input() 184 | self.update(dt) 185 | self.draw() 186 | pygame.display.flip() 187 | 188 | except KeyboardInterrupt: 189 | self.running = False 190 | 191 | 192 | def main() -> None: 193 | pygame.init() 194 | pygame.font.init() 195 | screen = init_screen(800, 600) 196 | pygame.display.set_caption("Quest - An epic journey.") 197 | 198 | try: 199 | game = QuestGame(screen) 200 | game.run() 201 | except KeyboardInterrupt: 202 | pass 203 | finally: 204 | pygame.quit() 205 | 206 | 207 | if __name__ == "__main__": 208 | main() 209 | -------------------------------------------------------------------------------- /apps/demo/demo.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is tested on pygame 1.9 and python 3.3 & 2.7. 3 | bitcraft (leif dot theden at gmail.com) 4 | 5 | Rendering demo for the pyscroll. 6 | 7 | Use the arrow keys to smoothly scroll the map. 8 | Window is resizable. 9 | 10 | See the "Quest" tutorial for a more simple use with 11 | pygame sprites and groups. 12 | """ 13 | import collections 14 | import logging 15 | 16 | import pygame 17 | from pygame.locals import * 18 | from pytmx.util_pygame import load_pygame 19 | 20 | import pyscroll 21 | import pyscroll.data 22 | import pyscroll.orthographic 23 | 24 | logger = logging.getLogger(__name__) 25 | ch = logging.StreamHandler() 26 | ch.setLevel(logging.INFO) 27 | logger.addHandler(ch) 28 | logger.setLevel(logging.INFO) 29 | 30 | SCROLL_SPEED = 5000 31 | 32 | 33 | # simple wrapper to keep the screen resizeable 34 | def init_screen(width, height): 35 | return pygame.display.set_mode((width, height), pygame.RESIZABLE) 36 | 37 | 38 | class ScrollTest: 39 | """Test and demo of pyscroll 40 | 41 | For normal use, please see the quest demo, not this. 42 | 43 | """ 44 | 45 | def __init__(self, filename) -> None: 46 | 47 | # load data from pytmx 48 | tmx_data = load_pygame(filename) 49 | 50 | # create new data source 51 | map_data = pyscroll.data.TiledMapData(tmx_data) 52 | 53 | # create new renderer 54 | self.map_layer = pyscroll.orthographic.BufferedRenderer( 55 | map_data, screen.get_size() 56 | ) 57 | 58 | # create a font and pre-render some text to be displayed over the map 59 | f = pygame.font.Font(pygame.font.get_default_font(), 20) 60 | t = ["scroll demo. press escape to quit", "arrow keys move"] 61 | 62 | # save the rendered text 63 | self.text_overlay = [f.render(i, 1, (180, 180, 0)) for i in t] 64 | 65 | # set our initial viewpoint in the center of the map 66 | self.center = [ 67 | self.map_layer.map_rect.width / 2, 68 | self.map_layer.map_rect.height / 2, 69 | ] 70 | 71 | # the camera vector is used to handle camera movement 72 | self.camera_acc = [0, 0, 0] 73 | self.camera_vel = [0, 0, 0] 74 | self.last_update_time = 0 75 | 76 | # true when running 77 | self.running = False 78 | 79 | def draw(self, surface) -> None: 80 | 81 | # tell the map_layer (BufferedRenderer) to draw to the surface 82 | # the draw function requires a rect to draw to. 83 | self.map_layer.draw(surface, surface.get_rect()) 84 | 85 | # blit our text over the map 86 | self.draw_text(surface) 87 | 88 | def draw_text(self, surface) -> None: 89 | y = 0 90 | for text in self.text_overlay: 91 | surface.blit(text, (0, y)) 92 | y += text.get_height() 93 | 94 | def handle_input(self) -> None: 95 | """Simply handle pygame input events""" 96 | for event in pygame.event.get(): 97 | if event.type == QUIT: 98 | self.running = False 99 | break 100 | 101 | elif event.type == KEYDOWN: 102 | if event.key == K_ESCAPE: 103 | self.running = False 104 | break 105 | 106 | # this will be handled if the window is resized 107 | elif event.type == VIDEORESIZE: 108 | init_screen(event.w, event.h) 109 | self.map_layer.set_size((event.w, event.h)) 110 | 111 | # these keys will change the camera vector 112 | # the camera vector changes the center of the viewport, 113 | # which causes the map to scroll 114 | 115 | # using get_pressed is slightly less accurate than testing for events 116 | # but is much easier to use. 117 | pressed = pygame.key.get_pressed() 118 | if pressed[K_UP]: 119 | self.camera_acc[1] = -SCROLL_SPEED * self.last_update_time 120 | elif pressed[K_DOWN]: 121 | self.camera_acc[1] = SCROLL_SPEED * self.last_update_time 122 | else: 123 | self.camera_acc[1] = 0 124 | 125 | if pressed[K_LEFT]: 126 | self.camera_acc[0] = -SCROLL_SPEED * self.last_update_time 127 | elif pressed[K_RIGHT]: 128 | self.camera_acc[0] = SCROLL_SPEED * self.last_update_time 129 | else: 130 | self.camera_acc[0] = 0 131 | 132 | def update(self, td) -> None: 133 | self.last_update_time = td 134 | 135 | friction = pow(0.0001, self.last_update_time) 136 | 137 | # update the camera vector 138 | self.camera_vel[0] += self.camera_acc[0] * td 139 | self.camera_vel[1] += self.camera_acc[1] * td 140 | 141 | self.camera_vel[0] *= friction 142 | self.camera_vel[1] *= friction 143 | 144 | # make sure the movement vector stops when scrolling off the screen 145 | if self.center[0] < 0: 146 | self.center[0] -= self.camera_vel[0] 147 | self.camera_acc[0] = 0 148 | self.camera_vel[0] = 0 149 | if self.center[0] >= self.map_layer.map_rect.width: 150 | self.center[0] -= self.camera_vel[0] 151 | self.camera_acc[0] = 0 152 | self.camera_vel[0] = 0 153 | 154 | if self.center[1] < 0: 155 | self.center[1] -= self.camera_vel[1] 156 | self.camera_acc[1] = 0 157 | self.camera_vel[1] = 0 158 | if self.center[1] >= self.map_layer.map_rect.height: 159 | self.center[1] -= self.camera_vel[1] 160 | self.camera_acc[1] = 0 161 | self.camera_vel[1] = 0 162 | 163 | self.center[0] += self.camera_vel[0] 164 | self.center[1] += self.camera_vel[1] 165 | 166 | # set the center somewhere else 167 | # in a game, you would set center to a playable character 168 | self.map_layer.center(self.center) 169 | 170 | def run(self) -> None: 171 | clock = pygame.time.Clock() 172 | self.running = True 173 | fps = 60.0 174 | fps_log = collections.deque(maxlen=20) 175 | 176 | try: 177 | while self.running: 178 | # somewhat smoother way to get fps and limit the framerate 179 | clock.tick(fps * 2) 180 | 181 | try: 182 | fps_log.append(clock.get_fps()) 183 | fps = sum(fps_log) / len(fps_log) 184 | dt = 1 / fps 185 | except ZeroDivisionError: 186 | continue 187 | 188 | self.handle_input() 189 | self.update(dt) 190 | self.draw(screen) 191 | pygame.display.flip() 192 | 193 | except KeyboardInterrupt: 194 | self.running = False 195 | 196 | 197 | if __name__ == "__main__": 198 | import sys 199 | 200 | pygame.init() 201 | pygame.font.init() 202 | screen = init_screen(800, 600) 203 | pygame.display.set_caption("pyscroll Test") 204 | 205 | try: 206 | filename = sys.argv[1] 207 | except IndexError: 208 | logger.info("no TMX map specified, using default") 209 | filename = "desert.tmx" 210 | 211 | try: 212 | test = ScrollTest(filename) 213 | test.run() 214 | except: 215 | pygame.quit() 216 | raise 217 | -------------------------------------------------------------------------------- /apps/demo/desert.tmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | H4sIAAAAAAAAA+2Y207CQBCGR7nwQAIeEhGNiFYRD2Df/+kcQidtJuvu7HSmtpGLL6Fhy377d7qdsgCAhSMTZIpcOM+jYRvwe+yB11Dy22V1g8yQ2x74hDhCjpER8/aYa47cZXCPnCHnyLiDLJ6Q5wwKR791xO8F+UBe/9Avll8hzLMLv5Wx39bAqWRe3O8L9vW/6Tg/XnP8OJUXr0sLv9B9sGb50eeUH8/VKr83dkw+ufl5X1/uyXmvrp+UTwO/k4pTqJ9FMUbCccRDS78p1P0FrTXGWDgudJ7Eh+8/M6j7i0vkKsG1cFzoPM/ak66daPtbSwj3Ob95Suf8NnBrOlrk1nQj+Hea3szKK5Yf5S7ZO5vPn0I5vxaJ386prMaG/FJ99wrCfYMkUws/CVI/Xteefql7SPK+I/Fr9hdd1R/dL5reImf9bbLT+pWG83v4aebR7uea3szDYwho9khimTme19+Qc83NjWfVt/8Gra7xgQP/lR9YXkJdABkAAA== 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /apps/demo/hero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcraft/pyscroll/31e1c282d09542ec89c6c1ac3ee6a1ff991ff5c2/apps/demo/hero.png -------------------------------------------------------------------------------- /apps/demo/stitched.world: -------------------------------------------------------------------------------- 1 | { 2 | "maps": [ 3 | { 4 | "fileName": "stitched0.tmx", 5 | "height": 320, 6 | "width": 320, 7 | "x": -320, 8 | "y": -320 9 | }, 10 | { 11 | "fileName": "stitched1.tmx", 12 | "height": 320, 13 | "width": 320, 14 | "x": 0, 15 | "y": -320 16 | }, 17 | { 18 | "fileName": "stitched2.tmx", 19 | "height": 320, 20 | "width": 320, 21 | "x": 320, 22 | "y": -320 23 | }, 24 | { 25 | "fileName": "stitched3.tmx", 26 | "height": 320, 27 | "width": 320, 28 | "x": -320, 29 | "y": 0 30 | }, 31 | { 32 | "fileName": "stitched4.tmx", 33 | "height": 320, 34 | "width": 320, 35 | "x": 0, 36 | "y": 0 37 | }, 38 | { 39 | "fileName": "stitched5.tmx", 40 | "height": 320, 41 | "width": 320, 42 | "x": 320, 43 | "y": 0 44 | }, 45 | { 46 | "fileName": "stitched6.tmx", 47 | "height": 320, 48 | "width": 320, 49 | "x": -320, 50 | "y": 320 51 | }, 52 | { 53 | "fileName": "stitched7.tmx", 54 | "height": 320, 55 | "width": 320, 56 | "x": 0, 57 | "y": 320 58 | }, 59 | { 60 | "fileName": "stitched8.tmx", 61 | "height": 320, 62 | "width": 320, 63 | "x": 320, 64 | "y": 320 65 | } 66 | ], 67 | "onlyShowAdjacentMaps": false, 68 | "type": "world" 69 | } 70 | -------------------------------------------------------------------------------- /apps/demo/stitched0.tmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | eJwTYmBgEBrFo3gUj+JRPIpJxAAnqBwh 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /apps/demo/stitched1.tmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | eJyTY2BgkBvFo3gUj+JRPIpJxADoji7h 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /apps/demo/stitched2.tmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | eJwTYmBgEBrFo3gUj+JRPIpJxAAnqBwh 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /apps/demo/stitched3.tmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | eJyTYGBgkBjFo3gUj+JRPIpJxACIGyWB 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /apps/demo/stitched4.tmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | eJyTZmBgkB7Fo3gUj+JRPIpJxAA4XCox 7 | 8 | 9 | 10 | 11 | eJztklEKACAIQ7v/fbpfvxLb3OoraCCC2HxZY7ynWQLVT/xqRjU2s+PbQ81MWNUch435sb7Oz2Fx/bqcvDPiYTmR45myonPq7zheKLM+xbP7ML7uvmo/t7u7PfuVawEzDUjU 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /apps/demo/stitched5.tmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | eJyTYGBgkBjFo3gUj+JRPIpJxACIGyWB 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /apps/demo/stitched6.tmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | eJwTYmBgEBrFo3gUj+JRPIpJxAAnqBwh 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /apps/demo/stitched7.tmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | eJyTY2BgkBvFo3gUj+JRPIpJxADoji7h 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /apps/demo/stitched8.tmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | eJwTYmBgEBrFo3gUj+JRPIpJxAAnqBwh 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /apps/demo/tmw_desert_spacing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcraft/pyscroll/31e1c282d09542ec89c6c1ac3ee6a1ff991ff5c2/apps/demo/tmw_desert_spacing.png -------------------------------------------------------------------------------- /apps/demo/translate.py: -------------------------------------------------------------------------------- 1 | """ 2 | For testing the translate methods 3 | 4 | incomplete 5 | """ 6 | import pygame 7 | 8 | 9 | class Dummy: 10 | def run(self) -> None: 11 | surface = None 12 | 13 | for spr in self.sprites(): 14 | r = self._map_layer.translate_rect(spr.rect) 15 | pygame.draw.rect(surface, (20, 20, 20), r, 1) 16 | 17 | for spr in self.sprites(): 18 | r = self._map_layer.translate_point(spr.rect.topleft) 19 | pygame.draw.circle(surface, (20, 20, 20), r, 3) 20 | 21 | spr_list = list() 22 | for spr in self.sprites(): 23 | spr_list.append(spr.rect) 24 | 25 | for r in self._map_layer.translate_rects(spr_list): 26 | pygame.draw.rect(surface, (200, 10, 10), r, 1) 27 | 28 | for p in self._map_layer.translate_points([i.topleft for i in spr_list]): 29 | pygame.draw.circle(surface, (200, 10, 10), p, 3) 30 | -------------------------------------------------------------------------------- /apps/demo/water.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcraft/pyscroll/31e1c282d09542ec89c6c1ac3ee6a1ff991ff5c2/apps/demo/water.png -------------------------------------------------------------------------------- /apps/demo/water.tsx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /apps/tutorial/credits: -------------------------------------------------------------------------------- 1 | tileset: 2 | author: Fabio 3 | http://www.revolutionresources.co.uk 4 | 5 | hero.png: 6 | author: rainddropmemory 7 | part of: legendora icon set 8 | http://www.softicons.com/art-icons/legendora-icon-set-by-raindropmemory 9 | 10 | water.png: 11 | http://opengameart.org/content/animated-water-tiles 12 | -------------------------------------------------------------------------------- /apps/tutorial/data/grasslands.tmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | H4sIAAAAAAAAA92Xy24UQQxFq7q6a4HCY8GKLRJfELGEJY81SyR+i79hk0D+KAorBtEWp8+4ZyaaBiEiWZnuqrJ9y/a1+7aUcvsfyFD35e5Mnd/LdrpOlYf1l7yov38/TrA9S96tyZMjurbE+OWAXNQcX8jzI+uP6r6u5/j/IsF4n3uKvXcn4rk5c/2YXCR3YKyWtbw5x49jcirOzxvIn8RxqmyF49z8+FdwhHy7p/377v8X8mqrms+wb4nj5oCdc+JxaE+s/a14XJ9x/1cnxO9P4ljz0++vEpw3R3R4/xY4vs5C/Teyeb3ib7b30NqV9MT6FjimWfpO6izxPOBdnd/9lLaTUb9DT8P/Yd7D93Xez/PDBjgG+Bt2tpbsTgbgqRvgeLWTlzu5nCXz41JyDqZRvwPfuTje7uT9jOc+8rIs88S+UjI8kQdr62sSfmdrb2Y8lo/z/0/JWob9Q/LuNfxuwDkhHlFHWdyiViecj/0j3nXs49kRZ0fYjLoM7IE1hJh/rr2DjQ79jkXgiRg37OX+YWWN9zDiuZXffNKgI2z3sqxVck+X3Y5zrezXeMYL4Wcr+5htk/iol7wVeOnXpP8RT8Yv7qyVJXbuYQ5U2Z4gHefN56FnkO6IcdXZuFPfPe/Hud+gJ8sF4mHM+Nyxn/fCPKI/1sVYhW3q5XvGgHnU8dxwppdl7rF+eL8W13q84z2yHsglGV9mepgPfcUu/WTeNzwTa8X6JLvs38wZclfT+oNEWCuj7NHPBnGuZDlJf1g7oWOtD8U58rjr6mkiVft7Wd4nc4zxnmSPd+A7Zd4d6pdTWd4X68L8RXyuk1aWsYw4kfeYQ855YqGdzFe/ox3iGGQ3+z2V9fh3nXGfIF+t8RTjRQzTinA/dbKuPENxD9+RI+yLY8078LxvIb/TFnPPc0kt+7nL+yNm2sn4jrrYe1w/7CHx1xNhzmW+MHaMke+LM4V7fTbXsfeR29lrRul2bnXpol9ZTzf38JmxyeqcWMxf3JvNM8bgPsD5wFzhfs4YkTs5f2W5yjiTl3jfnJ9Y96w58zbvxRxIXNzLd1kfYQ5lcx31GYvncNep5zrOD+ZTrrknsC9l/adrnVyVcbrxmPOcb+Z85jlxsmfHb88wk549W/kewgZzijV7rFaJ3/2R/11DzHfzTawztswV6uKs7Bl10rO/ZZhLnmv5HUCeqHpmbdAvrrUVO35v3mM/cM/P+jl99jcK48H6YZ6Tz82lnEdcqx362ePoj/tdxmfOcc8qGQ8x/83n5CfWE78DbJc1ae5yTTl+1FO1hzFjjjA2rgPXI2vD3zD2mfMoezO/hdgPuebe5VmCdplPviv67Bms66y5m/Meewb9GRN95AP6yZ7EGvc8Yi5m7tEm+637U3bOd+zZnviYq+YF1yh1MTb8lqHvri2u17LMf+pg3zNXsB7HlX3kc/M3v4VG2fLdZNyb2RjL/p269wQu++78cy6TE91PPMOZJ+0Pc8B9hPzn70nmeGBhPLOZ1H55BnQc6ZfrnrnF+s1iHdj9jeDz/u4z55Lv1+aQrDeSB11HLTnL3PXcw1mKPMFa4XvmJ22bt9zPsjz1TGDxDEZfqJtxJUeR0z0bEZPnTXIa9ZNfR511D3EdZ1zvmSHkByS9OSIQJwAA 24 | 25 | 26 | 27 | 28 | H4sIAAAAAAAAA+2Yu07DMBRA0/5OPwXRdu43tMyUdixCQgLBxMKjZeqLDSTYQKIbz43nBhL9C86QoYpSqGXXuY7ukY6USI59bxznxokiRVEks4O7lkrgwGFflYLDzgxxmYdPkvfMdx4rxSiq4WrRbb+hzkcS13nYrBGba3U+/sfn+0vnQxaahyxCyKO0QJsenloqgRGOLVXMqVOTGrjmoTZ1lj+EkmA76wBS2OBZa8W2M9zTKfnhkP3lER7jCXaxF+85t7INzWgN9ol5gEMc4RjPZvbOHY43He+ll8E5MV7gD07xEq9m4t7jeD+APK6J8Qa/8BtvcWIZt8nzUGasSmw1MW6d80ZKLGk1/Y529/iAj/iEz0Lu/zpxNBeM5YV2r/iG7/iBn0LycMm8uQ0Nk7kNDdu64qsuSagZpt98ae+9UGqGouSFrL+dFSU0/lozpnVQ/5UokshjPfgFIS2s7xAnAAA= 29 | 30 | 31 | 32 | 33 | H4sIAAAAAAAAA+3WQQqAMAwEwOgv9RE+Q78tHgrSQ6GgxMoM9BjYvSSNAAAAAIB3LXPEOmenAPi+fcpOANC2Ve/u2mFHxx7L/CO2eoygzq9HrtHzF3/pAVl672CZAZ53AmHC2j8QJwAA 34 | 35 | 36 | 37 | 38 | H4sIAAAAAAAAA+3BAQ0AAADCoPdPbQ43oAAAAAAAAAAAAODfAC7KO00QJwAA 39 | 40 | 41 | 42 | 43 | H4sIAAAAAAAAA+2Zx0oDQRyH1/g21pP1ZH0QFbtiV4z9DSxH+8UuXhTUk4K52QvY9aBgeQk/D0qQ7O6U3Wwi88F3SYbN7z8785+BWJbBYHAiM8WysjA7JegkeuSTvwALXepIC1lWOmaE1H/Lzzkr5ZllWO7y7Bzy52KeRh2ic+YnReQvxhKNOkTnTBWd951I+0vnfSfCWvkh+n3Lzq/fa0WVRJpfHRJ1fg0Gg+G/UYf12ODT8yvo45VYFaOfn/PZBV7ilWa/b8cO7NR7jBIPZH/EJ3wWqKMaa7A2xnf9OICDniYUQ7aOFmzFNr+DSfJG9nf8wE+BOnoxjH1+BzP84rQH4oFXfS9ee2CDdTyG4zgRtaa96nvx2gO7ZJ/CaZyJqiPIvveN7HqI2NQRNLLrYYHsi7iEy4J1OJ3pXhH0ejD4w6hNH7PDru/Z0YhN2KyYT/RcmpTc/3Z9z44u7MYe96ExET2XZOuQ7XtDOIwj7kNjInouyfYxlb5nMOgwFWLf4AzO4hzOK/xvodvjdFki8zKu4Cqu4bpCHbo9TpdNMm/hG77jNu4o1KHS42TPQafxe2Texxd8xQOMaPwfJoPs+eE0/pDMR3iMJ3iKZx7V4Xa/97KOazLf4C3e4T0+eFSH2/0+Wc5Bc7/3jppU7qtYlxp0Ej1ayd+G7UleR5j8fdif5HX85Qt8ODPJECcAAA== 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /apps/tutorial/data/grasslands_small.tmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | H4sIAAAAAAAAC9vPwMCwfxDhE2j84xSYdZQEe7CJn0AzC4SPoJnLCMSsQMwGxExQPgyzQTEzmjg+DACdbDtnkAEAAA== 16 | 17 | 18 | 19 | 20 | H4sIAAAAAAAAC2NgoB/wZmJgiAJiXyY6WooD+DMOtAuwAwBSEOA6kAEAAA== 21 | 22 | 23 | 24 | 25 | H4sIAAAAAAAAC2NgGAWDCQAADA0iRpABAAA= 26 | 27 | 28 | 29 | 30 | H4sIAAAAAAAAC2NgwA1MmRgYzIDYnAmPIiBwAMo7ArETEOsz4lcLA1ZEqnMFqtMFYj1GwmZbAOUtGQmb7QyUd2GEmD3YAAAk0puEkAEAAA== 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /apps/tutorial/data/hero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcraft/pyscroll/31e1c282d09542ec89c6c1ac3ee6a1ff991ff5c2/apps/tutorial/data/hero.png -------------------------------------------------------------------------------- /apps/tutorial/data/readme.md: -------------------------------------------------------------------------------- 1 | Quest Demo 2 | ========== 3 | 4 | This is a demonstration of using pyscroll and pytmx together to create a game. 5 | 6 | Controls: 7 | - Arrow Keys: movement 8 | - Plus/Minus (+/-): zoom the camera 9 | 10 | The window is resizeable, camera can zoom in and out using +/- keys. 11 | 12 | Enjoy! 13 | -------------------------------------------------------------------------------- /apps/tutorial/data/revolution_grasslands.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcraft/pyscroll/31e1c282d09542ec89c6c1ac3ee6a1ff991ff5c2/apps/tutorial/data/revolution_grasslands.png -------------------------------------------------------------------------------- /apps/tutorial/data/revolution_grasslands_alpha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcraft/pyscroll/31e1c282d09542ec89c6c1ac3ee6a1ff991ff5c2/apps/tutorial/data/revolution_grasslands_alpha.png -------------------------------------------------------------------------------- /apps/tutorial/data/revolution_snow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcraft/pyscroll/31e1c282d09542ec89c6c1ac3ee6a1ff991ff5c2/apps/tutorial/data/revolution_snow.png -------------------------------------------------------------------------------- /apps/tutorial/data/revolution_town.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcraft/pyscroll/31e1c282d09542ec89c6c1ac3ee6a1ff991ff5c2/apps/tutorial/data/revolution_town.png -------------------------------------------------------------------------------- /apps/tutorial/data/water.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcraft/pyscroll/31e1c282d09542ec89c6c1ac3ee6a1ff991ff5c2/apps/tutorial/data/water.png -------------------------------------------------------------------------------- /apps/tutorial/data/water.tsx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | -------------------------------------------------------------------------------- /apps/tutorial/quest.py: -------------------------------------------------------------------------------- 1 | """ Quest - An epic journey. 2 | 3 | Simple demo that demonstrates PyTMX and pyscroll. 4 | 5 | requires pygame and pytmx. 6 | 7 | https://github.com/bitcraft/pytmx 8 | 9 | pip install pytmx 10 | """ 11 | from __future__ import annotations 12 | 13 | from pathlib import Path 14 | 15 | import pygame 16 | from pygame.locals import ( 17 | K_DOWN, 18 | K_EQUALS, 19 | K_ESCAPE, 20 | K_LEFT, 21 | K_MINUS, 22 | K_RIGHT, 23 | K_UP, 24 | KEYDOWN, 25 | QUIT, 26 | VIDEORESIZE, 27 | K_r, 28 | ) 29 | from pytmx.util_pygame import load_pygame 30 | 31 | import pyscroll 32 | import pyscroll.data 33 | from pyscroll.group import PyscrollGroup 34 | 35 | # define configuration variables here 36 | CURRENT_DIR = Path(__file__).parent 37 | RESOURCES_DIR = CURRENT_DIR / "data" 38 | HERO_MOVE_SPEED = 200 # pixels per second 39 | 40 | 41 | # simple wrapper to keep the screen resizeable 42 | def init_screen(width: int, height: int) -> pygame.Surface: 43 | screen = pygame.display.set_mode((width, height), pygame.RESIZABLE) 44 | return screen 45 | 46 | 47 | # make loading images a little easier 48 | def load_image(filename: str) -> pygame.Surface: 49 | return pygame.image.load(str(RESOURCES_DIR / filename)) 50 | 51 | 52 | class Hero(pygame.sprite.Sprite): 53 | """ 54 | Our Hero 55 | 56 | The Hero has three collision rects, one for the whole sprite "rect" 57 | and "old_rect", and another to check collisions with walls, called 58 | "feet". 59 | 60 | The position list is used because pygame rects are inaccurate for 61 | positioning sprites; because the values they get are 'rounded down' 62 | as integers, the sprite would move faster moving left or up. 63 | 64 | Feet is 1/2 as wide as the normal rect, and 8 pixels tall. This 65 | size allows the top of the sprite to overlap walls. The feet rect 66 | is used for collisions, while the 'rect' rect is used for drawing. 67 | 68 | There is also an old_rect that is used to reposition the sprite if 69 | it collides with level walls. 70 | 71 | """ 72 | 73 | def __init__(self) -> None: 74 | super().__init__() 75 | self.image = load_image("hero.png").convert_alpha() 76 | self.velocity = [0, 0] 77 | self._position = [0.0, 0.0] 78 | self._old_position = self.position 79 | self.rect = self.image.get_rect() 80 | self.feet = pygame.Rect(0, 0, self.rect.width * 0.5, 8) 81 | 82 | @property 83 | def position(self) -> list[float]: 84 | return list(self._position) 85 | 86 | @position.setter 87 | def position(self, value: list[float]) -> None: 88 | self._position = list(value) 89 | 90 | def update(self, dt: float) -> None: 91 | self._old_position = self._position[:] 92 | self._position[0] += self.velocity[0] * dt 93 | self._position[1] += self.velocity[1] * dt 94 | self.rect.topleft = self._position 95 | self.feet.midbottom = self.rect.midbottom 96 | 97 | def move_back(self, dt: float) -> None: 98 | """ 99 | If called after an update, the sprite can move back 100 | 101 | """ 102 | self._position = self._old_position 103 | self.rect.topleft = self._position 104 | self.feet.midbottom = self.rect.midbottom 105 | 106 | 107 | class QuestGame: 108 | """ 109 | This class is a basic game. 110 | 111 | This class will load data, create a pyscroll group, a hero object. 112 | It also reads input and moves the Hero around the map. 113 | Finally, it uses a pyscroll group to render the map and Hero. 114 | 115 | """ 116 | 117 | map_path = RESOURCES_DIR / "grasslands.tmx" 118 | 119 | def __init__(self, screen: pygame.Surface) -> None: 120 | self.screen = screen 121 | 122 | # true while running 123 | self.running = False 124 | 125 | # load data from pytmx 126 | tmx_data = load_pygame(self.map_path) 127 | 128 | # setup level geometry with simple pygame rects, loaded from pytmx 129 | self.walls = [] 130 | for obj in tmx_data.objects: 131 | self.walls.append(pygame.Rect(obj.x, obj.y, obj.width, obj.height)) 132 | 133 | # create new renderer (camera) 134 | self.map_layer = pyscroll.BufferedRenderer( 135 | data=pyscroll.data.TiledMapData(tmx_data), 136 | size=screen.get_size(), 137 | clamp_camera=False, 138 | ) 139 | self.map_layer.zoom = 2 140 | 141 | # pyscroll supports layered rendering. our map has 3 'under' 142 | # layers. layers begin with 0. the layers are 0, 1, and 2. 143 | # sprites are always drawn over the tiles of the layer they are 144 | # on. since we want the sprite to be on top of layer 2, we set 145 | # the default layer for sprites as 2. 146 | self.group = PyscrollGroup(map_layer=self.map_layer, default_layer=2) 147 | 148 | # put the hero in the center of the map 149 | self.hero = Hero() 150 | self.hero.position = self.map_layer.map_rect.center 151 | 152 | # add our hero to the group 153 | self.group.add(self.hero) 154 | 155 | def draw(self) -> None: 156 | 157 | # center the map/screen on our Hero 158 | self.group.center(self.hero.rect.center) 159 | 160 | # draw the map and all sprites 161 | self.group.draw(self.screen) 162 | 163 | def handle_input(self) -> None: 164 | """ 165 | Handle pygame input events 166 | 167 | """ 168 | for event in pygame.event.get(): 169 | if event.type == QUIT: 170 | self.running = False 171 | break 172 | 173 | elif event.type == KEYDOWN: 174 | if event.key == K_ESCAPE: 175 | self.running = False 176 | break 177 | 178 | elif event.key == K_r: 179 | self.map_layer.reload() 180 | 181 | elif event.key == K_EQUALS: 182 | self.map_layer.zoom += 0.25 183 | 184 | elif event.key == K_MINUS: 185 | value = self.map_layer.zoom - 0.25 186 | if value > 0: 187 | self.map_layer.zoom = value 188 | 189 | # this will be handled if the window is resized 190 | elif event.type == VIDEORESIZE: 191 | self.screen = init_screen(event.w, event.h) 192 | self.map_layer.set_size((event.w, event.h)) 193 | 194 | # use `get_pressed` for an easy way to detect held keys 195 | pressed = pygame.key.get_pressed() 196 | if pressed[K_UP]: 197 | self.hero.velocity[1] = -HERO_MOVE_SPEED 198 | elif pressed[K_DOWN]: 199 | self.hero.velocity[1] = HERO_MOVE_SPEED 200 | else: 201 | self.hero.velocity[1] = 0 202 | 203 | if pressed[K_LEFT]: 204 | self.hero.velocity[0] = -HERO_MOVE_SPEED 205 | elif pressed[K_RIGHT]: 206 | self.hero.velocity[0] = HERO_MOVE_SPEED 207 | else: 208 | self.hero.velocity[0] = 0 209 | 210 | def update(self, dt: float) -> None: 211 | """ 212 | Tasks that occur over time should be handled here 213 | 214 | """ 215 | self.group.update(dt) 216 | 217 | # check if the sprite's feet are colliding with wall 218 | # sprite must have a rect called feet, and move_back method, 219 | # otherwise this will fail 220 | for sprite in self.group.sprites(): 221 | if sprite.feet.collidelist(self.walls) > -1: 222 | sprite.move_back(dt) 223 | 224 | def run(self) -> None: 225 | """ 226 | Run the game loop 227 | 228 | """ 229 | clock = pygame.time.Clock() 230 | self.running = True 231 | 232 | try: 233 | while self.running: 234 | dt = clock.tick() / 1000.0 235 | self.handle_input() 236 | self.update(dt) 237 | self.draw() 238 | pygame.display.flip() 239 | 240 | except KeyboardInterrupt: 241 | self.running = False 242 | 243 | 244 | def main() -> None: 245 | pygame.init() 246 | pygame.font.init() 247 | screen = init_screen(800, 600) 248 | pygame.display.set_caption("Quest - An epic journey.") 249 | 250 | try: 251 | game = QuestGame(screen) 252 | game.run() 253 | except KeyboardInterrupt: 254 | pass 255 | finally: 256 | pygame.quit() 257 | 258 | 259 | if __name__ == "__main__": 260 | main() 261 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pyscroll.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pyscroll.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/pyscroll" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pyscroll" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | -------------------------------------------------------------------------------- /docs/_build/doctrees/environment.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcraft/pyscroll/31e1c282d09542ec89c6c1ac3ee6a1ff991ff5c2/docs/_build/doctrees/environment.pickle -------------------------------------------------------------------------------- /docs/_build/doctrees/index.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcraft/pyscroll/31e1c282d09542ec89c6c1ac3ee6a1ff991ff5c2/docs/_build/doctrees/index.doctree -------------------------------------------------------------------------------- /docs/_build/doctrees/pyscroll.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcraft/pyscroll/31e1c282d09542ec89c6c1ac3ee6a1ff991ff5c2/docs/_build/doctrees/pyscroll.doctree -------------------------------------------------------------------------------- /docs/_build/html/.buildinfo: -------------------------------------------------------------------------------- 1 | # Sphinx build info version 1 2 | # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. 3 | config: 1396b1fe1164bf169b754df3b0bae929 4 | tags: 645f666f9bcd5a90fca523b33c5a78b7 5 | -------------------------------------------------------------------------------- /docs/_build/html/_modules/index.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Overview: module code — pyscroll 2.14.2 documentation 10 | 11 | 12 | 13 | 14 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 41 | 42 |
43 |
44 |
45 |
46 | 47 |

All modules for which code is available

48 | 52 | 53 |
54 |
55 |
56 |
57 |
58 | 70 | 71 |
72 |
73 |
74 |
75 | 87 | 91 | 92 | -------------------------------------------------------------------------------- /docs/_build/html/_sources/index.txt: -------------------------------------------------------------------------------- 1 | .. pyscroll documentation master file, created by 2 | sphinx-quickstart on Mon May 19 21:53:31 2014. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | pyscroll 7 | ======== 8 | 9 | for Python 3.9 and Pygame 1.9 10 | 11 | A simple, fast module for adding scrolling maps to your new or existing game. 12 | 13 | 14 | Introduction 15 | ============ 16 | 17 | pyscroll is a generic module for making a fast scrolling image with PyGame. It 18 | uses a lot of magic to get reasonable framerates out of PyGame. It only exists 19 | to draw a map. It doesn't load images or data, so you can use your own custom 20 | data structures, tile storage, ect. 21 | 22 | The included class, BufferedRenderer, gives great framerates, supports layered 23 | rendering and can draw itself. It uses more memory than a typical map would, 24 | but gives much better performance. 25 | 26 | pyscroll is compatible with pytmx (https://github.com/bitcraft/pytmx), so you 27 | can use your Tiled maps. It also has out-of-the-box support for PyGame Sprites. 28 | 29 | 30 | Features 31 | ======== 32 | 33 | - Fast and small footprint 34 | - Layered drawing for tiles 35 | - Drawing and scrolling shapes 36 | - Dirty screen updates 37 | - Pygame Group included 38 | 39 | 40 | Shape Drawing 41 | ============= 42 | 43 | pyscroll has a new experimental feature for drawing shapes to the map and 44 | scrolling them as part of the map. This can be useful for game that need to 45 | draw arbitrary shaped objects. 46 | 47 | The feature requires pytmx, and that the map files be created in the Tiled Map 48 | Editor. The advantage of of using pyscroll for shape drawing is that pyscroll 49 | can draw the shapes much more efficiently that simply drawing the shapes each 50 | frame. 51 | 52 | This feature is experimental at best. Your comments and support is appreciated! 53 | 54 | * Currently, shapes will not draw over sprites. 55 | 56 | 57 | New Game Tutorial 58 | ================= 59 | 60 | This is a quick guide on building a new game with pyscroll and pygame. It uses 61 | the PyscrollGroup for efficient rendering. You are free to use any other pygame 62 | techniques and functions. 63 | 64 | Open quest.py in the tutorial folder for a gentle introduction to pyscroll and 65 | the PyscrollGroup for PyGame. There are plenty of comments to get you started. 66 | 67 | The Quest demo shows how you can use a pyscroll group for drawing, how to load 68 | maps with PyTMX, and how pyscroll can quickly render layers. Moving under some 69 | tiles will cause the Hero to be covered. 70 | 71 | It will also render a Shape on the map with experimental shape drawing. 72 | 73 | 74 | Example Use with PyTMX 75 | ====================== 76 | 77 | pyscroll and pytmx can load your maps from Tiled and use you PyGame Sprites. 78 | 79 | .. code-block:: python 80 | 81 | # Load TMX data 82 | tmx_data = pytmx.load_pygame("desert.tmx") 83 | 84 | # Make data source for the map 85 | map_data = pyscroll.TiledMapData(tmx_data) 86 | 87 | # Make the scrolling layer 88 | screen_size = (400, 400) 89 | map_layer = pyscroll.BufferedRenderer(map_data, screen_size) 90 | 91 | # make the PyGame SpriteGroup with a scrolling map 92 | group = pyscroll.PyscrollGroup(map_layer=map_layer) 93 | 94 | # Add sprites to the group 95 | group.add(srite) 96 | 97 | # Center the layer and sprites on a sprite 98 | group.center(sprite.rect.center) 99 | 100 | # Draw the layer 101 | group.draw(screen) 102 | 103 | 104 | Adapting Existing Games / Map Data 105 | ================================== 106 | 107 | pyscroll can be used with existing map data, but you will have to create a 108 | class to interact with pyscroll or adapt your data handler to have these 109 | functions / attributes: 110 | 111 | 112 | .. code-block:: python 113 | 114 | class MyData: 115 | 116 | @property 117 | def tilewidth(self): 118 | """ Return pixel width of map tiles 119 | """ 120 | 121 | @property 122 | def tileheight(self): 123 | """ Return pixel height of map tiles 124 | """ 125 | 126 | @property 127 | def width(self): 128 | """ Return number of tiles on X axis 129 | """ 130 | 131 | @property 132 | def height(self): 133 | """ Return number of tiles on Y axis 134 | """ 135 | 136 | @property 137 | def visible_layers(self): 138 | """ Return a list or iterator of layer numbers that are visible. 139 | If using a single layer map, just return [0] 140 | """ 141 | 142 | def get_tile_image(self, position): 143 | """ Return a surface for this position. 144 | Return self.default_image if there is not map data for the position. 145 | position is x, y, layer tuple 146 | """ 147 | 148 | .. toctree:: 149 | :maxdepth: 2 150 | 151 | 152 | 153 | Indices and tables 154 | ================== 155 | 156 | * :ref:`genindex` 157 | * :ref:`modindex` 158 | * :ref:`search` 159 | 160 | -------------------------------------------------------------------------------- /docs/_build/html/_sources/pyscroll.txt: -------------------------------------------------------------------------------- 1 | pyscroll package 2 | ================ 3 | 4 | Submodules 5 | ---------- 6 | 7 | pyscroll.pyscroll 8 | ----------------- 9 | 10 | .. automodule:: pyscroll.pyscroll 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | pyscroll.data 16 | ------------- 17 | 18 | .. automodule:: pyscroll.data 19 | :members: TiledMapData 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | pyscroll.util 24 | ------------- 25 | 26 | .. automodule:: pyscroll.util 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | 32 | Module contents 33 | --------------- 34 | 35 | .. automodule:: pyscroll 36 | :members: 37 | :undoc-members: 38 | :show-inheritance: -------------------------------------------------------------------------------- /docs/_build/html/_static/ajax-loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcraft/pyscroll/31e1c282d09542ec89c6c1ac3ee6a1ff991ff5c2/docs/_build/html/_static/ajax-loader.gif -------------------------------------------------------------------------------- /docs/_build/html/_static/basic.css: -------------------------------------------------------------------------------- 1 | /* 2 | * basic.css 3 | * ~~~~~~~~~ 4 | * 5 | * Sphinx stylesheet -- basic theme. 6 | * 7 | * :copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | 12 | /* -- main layout ----------------------------------------------------------- */ 13 | 14 | div.clearer { 15 | clear: both; 16 | } 17 | 18 | /* -- relbar ---------------------------------------------------------------- */ 19 | 20 | div.related { 21 | width: 100%; 22 | font-size: 90%; 23 | } 24 | 25 | div.related h3 { 26 | display: none; 27 | } 28 | 29 | div.related ul { 30 | margin: 0; 31 | padding: 0 0 0 10px; 32 | list-style: none; 33 | } 34 | 35 | div.related li { 36 | display: inline; 37 | } 38 | 39 | div.related li.right { 40 | float: right; 41 | margin-right: 5px; 42 | } 43 | 44 | /* -- sidebar --------------------------------------------------------------- */ 45 | 46 | div.sphinxsidebarwrapper { 47 | padding: 10px 5px 0 10px; 48 | } 49 | 50 | div.sphinxsidebar { 51 | float: left; 52 | width: 230px; 53 | margin-left: -100%; 54 | font-size: 90%; 55 | } 56 | 57 | div.sphinxsidebar ul { 58 | list-style: none; 59 | } 60 | 61 | div.sphinxsidebar ul ul, 62 | div.sphinxsidebar ul.want-points { 63 | margin-left: 20px; 64 | list-style: square; 65 | } 66 | 67 | div.sphinxsidebar ul ul { 68 | margin-top: 0; 69 | margin-bottom: 0; 70 | } 71 | 72 | div.sphinxsidebar form { 73 | margin-top: 10px; 74 | } 75 | 76 | div.sphinxsidebar input { 77 | border: 1px solid #98dbcc; 78 | font-family: sans-serif; 79 | font-size: 1em; 80 | } 81 | 82 | div.sphinxsidebar #searchbox input[type="text"] { 83 | width: 170px; 84 | } 85 | 86 | div.sphinxsidebar #searchbox input[type="submit"] { 87 | width: 30px; 88 | } 89 | 90 | img { 91 | border: 0; 92 | max-width: 100%; 93 | } 94 | 95 | /* -- search page ----------------------------------------------------------- */ 96 | 97 | ul.search { 98 | margin: 10px 0 0 20px; 99 | padding: 0; 100 | } 101 | 102 | ul.search li { 103 | padding: 5px 0 5px 20px; 104 | background-image: url(file.png); 105 | background-repeat: no-repeat; 106 | background-position: 0 7px; 107 | } 108 | 109 | ul.search li a { 110 | font-weight: bold; 111 | } 112 | 113 | ul.search li div.context { 114 | color: #888; 115 | margin: 2px 0 0 30px; 116 | text-align: left; 117 | } 118 | 119 | ul.keywordmatches li.goodmatch a { 120 | font-weight: bold; 121 | } 122 | 123 | /* -- index page ------------------------------------------------------------ */ 124 | 125 | table.contentstable { 126 | width: 90%; 127 | } 128 | 129 | table.contentstable p.biglink { 130 | line-height: 150%; 131 | } 132 | 133 | a.biglink { 134 | font-size: 1.3em; 135 | } 136 | 137 | span.linkdescr { 138 | font-style: italic; 139 | padding-top: 5px; 140 | font-size: 90%; 141 | } 142 | 143 | /* -- general index --------------------------------------------------------- */ 144 | 145 | table.indextable { 146 | width: 100%; 147 | } 148 | 149 | table.indextable td { 150 | text-align: left; 151 | vertical-align: top; 152 | } 153 | 154 | table.indextable dl, table.indextable dd { 155 | margin-top: 0; 156 | margin-bottom: 0; 157 | } 158 | 159 | table.indextable tr.pcap { 160 | height: 10px; 161 | } 162 | 163 | table.indextable tr.cap { 164 | margin-top: 10px; 165 | background-color: #f2f2f2; 166 | } 167 | 168 | img.toggler { 169 | margin-right: 3px; 170 | margin-top: 3px; 171 | cursor: pointer; 172 | } 173 | 174 | div.modindex-jumpbox { 175 | border-top: 1px solid #ddd; 176 | border-bottom: 1px solid #ddd; 177 | margin: 1em 0 1em 0; 178 | padding: 0.4em; 179 | } 180 | 181 | div.genindex-jumpbox { 182 | border-top: 1px solid #ddd; 183 | border-bottom: 1px solid #ddd; 184 | margin: 1em 0 1em 0; 185 | padding: 0.4em; 186 | } 187 | 188 | /* -- general body styles --------------------------------------------------- */ 189 | 190 | a.headerlink { 191 | visibility: hidden; 192 | } 193 | 194 | h1:hover > a.headerlink, 195 | h2:hover > a.headerlink, 196 | h3:hover > a.headerlink, 197 | h4:hover > a.headerlink, 198 | h5:hover > a.headerlink, 199 | h6:hover > a.headerlink, 200 | dt:hover > a.headerlink { 201 | visibility: visible; 202 | } 203 | 204 | div.body p.caption { 205 | text-align: inherit; 206 | } 207 | 208 | div.body td { 209 | text-align: left; 210 | } 211 | 212 | .field-list ul { 213 | padding-left: 1em; 214 | } 215 | 216 | .first { 217 | margin-top: 0 !important; 218 | } 219 | 220 | p.rubric { 221 | margin-top: 30px; 222 | font-weight: bold; 223 | } 224 | 225 | img.align-left, .figure.align-left, object.align-left { 226 | clear: left; 227 | float: left; 228 | margin-right: 1em; 229 | } 230 | 231 | img.align-right, .figure.align-right, object.align-right { 232 | clear: right; 233 | float: right; 234 | margin-left: 1em; 235 | } 236 | 237 | img.align-center, .figure.align-center, object.align-center { 238 | display: block; 239 | margin-left: auto; 240 | margin-right: auto; 241 | } 242 | 243 | .align-left { 244 | text-align: left; 245 | } 246 | 247 | .align-center { 248 | text-align: center; 249 | } 250 | 251 | .align-right { 252 | text-align: right; 253 | } 254 | 255 | /* -- sidebars -------------------------------------------------------------- */ 256 | 257 | div.sidebar { 258 | margin: 0 0 0.5em 1em; 259 | border: 1px solid #ddb; 260 | padding: 7px 7px 0 7px; 261 | background-color: #ffe; 262 | width: 40%; 263 | float: right; 264 | } 265 | 266 | p.sidebar-title { 267 | font-weight: bold; 268 | } 269 | 270 | /* -- topics ---------------------------------------------------------------- */ 271 | 272 | div.topic { 273 | border: 1px solid #ccc; 274 | padding: 7px 7px 0 7px; 275 | margin: 10px 0 10px 0; 276 | } 277 | 278 | p.topic-title { 279 | font-size: 1.1em; 280 | font-weight: bold; 281 | margin-top: 10px; 282 | } 283 | 284 | /* -- admonitions ----------------------------------------------------------- */ 285 | 286 | div.admonition { 287 | margin-top: 10px; 288 | margin-bottom: 10px; 289 | padding: 7px; 290 | } 291 | 292 | div.admonition dt { 293 | font-weight: bold; 294 | } 295 | 296 | div.admonition dl { 297 | margin-bottom: 0; 298 | } 299 | 300 | p.admonition-title { 301 | margin: 0px 10px 5px 0px; 302 | font-weight: bold; 303 | } 304 | 305 | div.body p.centered { 306 | text-align: center; 307 | margin-top: 25px; 308 | } 309 | 310 | /* -- tables ---------------------------------------------------------------- */ 311 | 312 | table.docutils { 313 | border: 0; 314 | border-collapse: collapse; 315 | } 316 | 317 | table.docutils td, table.docutils th { 318 | padding: 1px 8px 1px 5px; 319 | border-top: 0; 320 | border-left: 0; 321 | border-right: 0; 322 | border-bottom: 1px solid #aaa; 323 | } 324 | 325 | table.field-list td, table.field-list th { 326 | border: 0 !important; 327 | } 328 | 329 | table.footnote td, table.footnote th { 330 | border: 0 !important; 331 | } 332 | 333 | th { 334 | text-align: left; 335 | padding-right: 5px; 336 | } 337 | 338 | table.citation { 339 | border-left: solid 1px gray; 340 | margin-left: 1px; 341 | } 342 | 343 | table.citation td { 344 | border-bottom: none; 345 | } 346 | 347 | /* -- other body styles ----------------------------------------------------- */ 348 | 349 | ol.arabic { 350 | list-style: decimal; 351 | } 352 | 353 | ol.loweralpha { 354 | list-style: lower-alpha; 355 | } 356 | 357 | ol.upperalpha { 358 | list-style: upper-alpha; 359 | } 360 | 361 | ol.lowerroman { 362 | list-style: lower-roman; 363 | } 364 | 365 | ol.upperroman { 366 | list-style: upper-roman; 367 | } 368 | 369 | dl { 370 | margin-bottom: 15px; 371 | } 372 | 373 | dd p { 374 | margin-top: 0px; 375 | } 376 | 377 | dd ul, dd table { 378 | margin-bottom: 10px; 379 | } 380 | 381 | dd { 382 | margin-top: 3px; 383 | margin-bottom: 10px; 384 | margin-left: 30px; 385 | } 386 | 387 | dt:target, .highlighted { 388 | background-color: #fbe54e; 389 | } 390 | 391 | dl.glossary dt { 392 | font-weight: bold; 393 | font-size: 1.1em; 394 | } 395 | 396 | .field-list ul { 397 | margin: 0; 398 | padding-left: 1em; 399 | } 400 | 401 | .field-list p { 402 | margin: 0; 403 | } 404 | 405 | .optional { 406 | font-size: 1.3em; 407 | } 408 | 409 | .versionmodified { 410 | font-style: italic; 411 | } 412 | 413 | .system-message { 414 | background-color: #fda; 415 | padding: 5px; 416 | border: 3px solid red; 417 | } 418 | 419 | .footnote:target { 420 | background-color: #ffa; 421 | } 422 | 423 | .line-block { 424 | display: block; 425 | margin-top: 1em; 426 | margin-bottom: 1em; 427 | } 428 | 429 | .line-block .line-block { 430 | margin-top: 0; 431 | margin-bottom: 0; 432 | margin-left: 1.5em; 433 | } 434 | 435 | .guilabel, .menuselection { 436 | font-family: sans-serif; 437 | } 438 | 439 | .accelerator { 440 | text-decoration: underline; 441 | } 442 | 443 | .classifier { 444 | font-style: oblique; 445 | } 446 | 447 | abbr, acronym { 448 | border-bottom: dotted 1px; 449 | cursor: help; 450 | } 451 | 452 | /* -- code displays --------------------------------------------------------- */ 453 | 454 | pre { 455 | overflow: auto; 456 | overflow-y: hidden; /* fixes display issues on Chrome browsers */ 457 | } 458 | 459 | td.linenos pre { 460 | padding: 5px 0px; 461 | border: 0; 462 | background-color: transparent; 463 | color: #aaa; 464 | } 465 | 466 | table.highlighttable { 467 | margin-left: 0.5em; 468 | } 469 | 470 | table.highlighttable td { 471 | padding: 0 0.5em 0 0.5em; 472 | } 473 | 474 | tt.descname { 475 | background-color: transparent; 476 | font-weight: bold; 477 | font-size: 1.2em; 478 | } 479 | 480 | tt.descclassname { 481 | background-color: transparent; 482 | } 483 | 484 | tt.xref, a tt { 485 | background-color: transparent; 486 | font-weight: bold; 487 | } 488 | 489 | h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { 490 | background-color: transparent; 491 | } 492 | 493 | .viewcode-link { 494 | float: right; 495 | } 496 | 497 | .viewcode-back { 498 | float: right; 499 | font-family: sans-serif; 500 | } 501 | 502 | div.viewcode-block:target { 503 | margin: -1px -10px; 504 | padding: 0 10px; 505 | } 506 | 507 | /* -- math display ---------------------------------------------------------- */ 508 | 509 | img.math { 510 | vertical-align: middle; 511 | } 512 | 513 | div.body div.math p { 514 | text-align: center; 515 | } 516 | 517 | span.eqno { 518 | float: right; 519 | } 520 | 521 | /* -- printout stylesheet --------------------------------------------------- */ 522 | 523 | @media print { 524 | div.document, 525 | div.documentwrapper, 526 | div.bodywrapper { 527 | margin: 0 !important; 528 | width: 100%; 529 | } 530 | 531 | div.sphinxsidebar, 532 | div.related, 533 | div.footer, 534 | #top-link { 535 | display: none; 536 | } 537 | } -------------------------------------------------------------------------------- /docs/_build/html/_static/comment-bright.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcraft/pyscroll/31e1c282d09542ec89c6c1ac3ee6a1ff991ff5c2/docs/_build/html/_static/comment-bright.png -------------------------------------------------------------------------------- /docs/_build/html/_static/comment-close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcraft/pyscroll/31e1c282d09542ec89c6c1ac3ee6a1ff991ff5c2/docs/_build/html/_static/comment-close.png -------------------------------------------------------------------------------- /docs/_build/html/_static/comment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcraft/pyscroll/31e1c282d09542ec89c6c1ac3ee6a1ff991ff5c2/docs/_build/html/_static/comment.png -------------------------------------------------------------------------------- /docs/_build/html/_static/default.css: -------------------------------------------------------------------------------- 1 | /* 2 | * default.css_t 3 | * ~~~~~~~~~~~~~ 4 | * 5 | * Sphinx stylesheet -- default theme. 6 | * 7 | * :copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | 12 | @import url("basic.css"); 13 | 14 | /* -- page layout ----------------------------------------------------------- */ 15 | 16 | body { 17 | font-family: sans-serif; 18 | font-size: 100%; 19 | background-color: #11303d; 20 | color: #000; 21 | margin: 0; 22 | padding: 0; 23 | } 24 | 25 | div.document { 26 | background-color: #1c4e63; 27 | } 28 | 29 | div.documentwrapper { 30 | float: left; 31 | width: 100%; 32 | } 33 | 34 | div.bodywrapper { 35 | margin: 0 0 0 230px; 36 | } 37 | 38 | div.body { 39 | background-color: #ffffff; 40 | color: #000000; 41 | padding: 0 20px 30px 20px; 42 | } 43 | 44 | div.footer { 45 | color: #ffffff; 46 | width: 100%; 47 | padding: 9px 0 9px 0; 48 | text-align: center; 49 | font-size: 75%; 50 | } 51 | 52 | div.footer a { 53 | color: #ffffff; 54 | text-decoration: underline; 55 | } 56 | 57 | div.related { 58 | background-color: #133f52; 59 | line-height: 30px; 60 | color: #ffffff; 61 | } 62 | 63 | div.related a { 64 | color: #ffffff; 65 | } 66 | 67 | div.sphinxsidebar { 68 | } 69 | 70 | div.sphinxsidebar h3 { 71 | font-family: 'Trebuchet MS', sans-serif; 72 | color: #ffffff; 73 | font-size: 1.4em; 74 | font-weight: normal; 75 | margin: 0; 76 | padding: 0; 77 | } 78 | 79 | div.sphinxsidebar h3 a { 80 | color: #ffffff; 81 | } 82 | 83 | div.sphinxsidebar h4 { 84 | font-family: 'Trebuchet MS', sans-serif; 85 | color: #ffffff; 86 | font-size: 1.3em; 87 | font-weight: normal; 88 | margin: 5px 0 0 0; 89 | padding: 0; 90 | } 91 | 92 | div.sphinxsidebar p { 93 | color: #ffffff; 94 | } 95 | 96 | div.sphinxsidebar p.topless { 97 | margin: 5px 10px 10px 10px; 98 | } 99 | 100 | div.sphinxsidebar ul { 101 | margin: 10px; 102 | padding: 0; 103 | color: #ffffff; 104 | } 105 | 106 | div.sphinxsidebar a { 107 | color: #98dbcc; 108 | } 109 | 110 | div.sphinxsidebar input { 111 | border: 1px solid #98dbcc; 112 | font-family: sans-serif; 113 | font-size: 1em; 114 | } 115 | 116 | 117 | 118 | /* -- hyperlink styles ------------------------------------------------------ */ 119 | 120 | a { 121 | color: #355f7c; 122 | text-decoration: none; 123 | } 124 | 125 | a:visited { 126 | color: #355f7c; 127 | text-decoration: none; 128 | } 129 | 130 | a:hover { 131 | text-decoration: underline; 132 | } 133 | 134 | 135 | 136 | /* -- body styles ----------------------------------------------------------- */ 137 | 138 | div.body h1, 139 | div.body h2, 140 | div.body h3, 141 | div.body h4, 142 | div.body h5, 143 | div.body h6 { 144 | font-family: 'Trebuchet MS', sans-serif; 145 | background-color: #f2f2f2; 146 | font-weight: normal; 147 | color: #20435c; 148 | border-bottom: 1px solid #ccc; 149 | margin: 20px -20px 10px -20px; 150 | padding: 3px 0 3px 10px; 151 | } 152 | 153 | div.body h1 { margin-top: 0; font-size: 200%; } 154 | div.body h2 { font-size: 160%; } 155 | div.body h3 { font-size: 140%; } 156 | div.body h4 { font-size: 120%; } 157 | div.body h5 { font-size: 110%; } 158 | div.body h6 { font-size: 100%; } 159 | 160 | a.headerlink { 161 | color: #c60f0f; 162 | font-size: 0.8em; 163 | padding: 0 4px 0 4px; 164 | text-decoration: none; 165 | } 166 | 167 | a.headerlink:hover { 168 | background-color: #c60f0f; 169 | color: white; 170 | } 171 | 172 | div.body p, div.body dd, div.body li { 173 | text-align: justify; 174 | line-height: 130%; 175 | } 176 | 177 | div.admonition p.admonition-title + p { 178 | display: inline; 179 | } 180 | 181 | div.admonition p { 182 | margin-bottom: 5px; 183 | } 184 | 185 | div.admonition pre { 186 | margin-bottom: 5px; 187 | } 188 | 189 | div.admonition ul, div.admonition ol { 190 | margin-bottom: 5px; 191 | } 192 | 193 | div.note { 194 | background-color: #eee; 195 | border: 1px solid #ccc; 196 | } 197 | 198 | div.seealso { 199 | background-color: #ffc; 200 | border: 1px solid #ff6; 201 | } 202 | 203 | div.topic { 204 | background-color: #eee; 205 | } 206 | 207 | div.warning { 208 | background-color: #ffe4e4; 209 | border: 1px solid #f66; 210 | } 211 | 212 | p.admonition-title { 213 | display: inline; 214 | } 215 | 216 | p.admonition-title:after { 217 | content: ":"; 218 | } 219 | 220 | pre { 221 | padding: 5px; 222 | background-color: #eeffcc; 223 | color: #333333; 224 | line-height: 120%; 225 | border: 1px solid #ac9; 226 | border-left: none; 227 | border-right: none; 228 | } 229 | 230 | tt { 231 | background-color: #ecf0f3; 232 | padding: 0 1px 0 1px; 233 | font-size: 0.95em; 234 | } 235 | 236 | th { 237 | background-color: #ede; 238 | } 239 | 240 | .warning tt { 241 | background: #efc2c2; 242 | } 243 | 244 | .note tt { 245 | background: #d6d6d6; 246 | } 247 | 248 | .viewcode-back { 249 | font-family: sans-serif; 250 | } 251 | 252 | div.viewcode-block:target { 253 | background-color: #f4debf; 254 | border-top: 1px solid #ac9; 255 | border-bottom: 1px solid #ac9; 256 | } -------------------------------------------------------------------------------- /docs/_build/html/_static/doctools.js: -------------------------------------------------------------------------------- 1 | /* 2 | * doctools.js 3 | * ~~~~~~~~~~~ 4 | * 5 | * Sphinx JavaScript utilities for all documentation. 6 | * 7 | * :copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | 12 | /** 13 | * select a different prefix for underscore 14 | */ 15 | $u = _.noConflict(); 16 | 17 | /** 18 | * make the code below compatible with browsers without 19 | * an installed firebug like debugger 20 | if (!window.console || !console.firebug) { 21 | var names = ["log", "debug", "info", "warn", "error", "assert", "dir", 22 | "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", 23 | "profile", "profileEnd"]; 24 | window.console = {}; 25 | for (var i = 0; i < names.length; ++i) 26 | window.console[names[i]] = function() {}; 27 | } 28 | */ 29 | 30 | /** 31 | * small helper function to urldecode strings 32 | */ 33 | jQuery.urldecode = function(x) { 34 | return decodeURIComponent(x).replace(/\+/g, ' '); 35 | }; 36 | 37 | /** 38 | * small helper function to urlencode strings 39 | */ 40 | jQuery.urlencode = encodeURIComponent; 41 | 42 | /** 43 | * This function returns the parsed url parameters of the 44 | * current request. Multiple values per key are supported, 45 | * it will always return arrays of strings for the value parts. 46 | */ 47 | jQuery.getQueryParameters = function(s) { 48 | if (typeof s == 'undefined') 49 | s = document.location.search; 50 | var parts = s.substr(s.indexOf('?') + 1).split('&'); 51 | var result = {}; 52 | for (var i = 0; i < parts.length; i++) { 53 | var tmp = parts[i].split('=', 2); 54 | var key = jQuery.urldecode(tmp[0]); 55 | var value = jQuery.urldecode(tmp[1]); 56 | if (key in result) 57 | result[key].push(value); 58 | else 59 | result[key] = [value]; 60 | } 61 | return result; 62 | }; 63 | 64 | /** 65 | * highlight a given string on a jquery object by wrapping it in 66 | * span elements with the given class name. 67 | */ 68 | jQuery.fn.highlightText = function(text, className) { 69 | function highlight(node) { 70 | if (node.nodeType == 3) { 71 | var val = node.nodeValue; 72 | var pos = val.toLowerCase().indexOf(text); 73 | if (pos >= 0 && !jQuery(node.parentNode).hasClass(className)) { 74 | var span = document.createElement("span"); 75 | span.className = className; 76 | span.appendChild(document.createTextNode(val.substr(pos, text.length))); 77 | node.parentNode.insertBefore(span, node.parentNode.insertBefore( 78 | document.createTextNode(val.substr(pos + text.length)), 79 | node.nextSibling)); 80 | node.nodeValue = val.substr(0, pos); 81 | } 82 | } 83 | else if (!jQuery(node).is("button, select, textarea")) { 84 | jQuery.each(node.childNodes, function() { 85 | highlight(this); 86 | }); 87 | } 88 | } 89 | return this.each(function() { 90 | highlight(this); 91 | }); 92 | }; 93 | 94 | /** 95 | * Small JavaScript module for the documentation. 96 | */ 97 | var Documentation = { 98 | 99 | init : function() { 100 | this.fixFirefoxAnchorBug(); 101 | this.highlightSearchWords(); 102 | this.initIndexTable(); 103 | }, 104 | 105 | /** 106 | * i18n support 107 | */ 108 | TRANSLATIONS : {}, 109 | PLURAL_EXPR : function(n) { return n == 1 ? 0 : 1; }, 110 | LOCALE : 'unknown', 111 | 112 | // gettext and ngettext don't access this so that the functions 113 | // can safely bound to a different name (_ = Documentation.gettext) 114 | gettext : function(string) { 115 | var translated = Documentation.TRANSLATIONS[string]; 116 | if (typeof translated == 'undefined') 117 | return string; 118 | return (typeof translated == 'string') ? translated : translated[0]; 119 | }, 120 | 121 | ngettext : function(singular, plural, n) { 122 | var translated = Documentation.TRANSLATIONS[singular]; 123 | if (typeof translated == 'undefined') 124 | return (n == 1) ? singular : plural; 125 | return translated[Documentation.PLURALEXPR(n)]; 126 | }, 127 | 128 | addTranslations : function(catalog) { 129 | for (var key in catalog.messages) 130 | this.TRANSLATIONS[key] = catalog.messages[key]; 131 | this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); 132 | this.LOCALE = catalog.locale; 133 | }, 134 | 135 | /** 136 | * add context elements like header anchor links 137 | */ 138 | addContextElements : function() { 139 | $('div[id] > :header:first').each(function() { 140 | $('\u00B6'). 141 | attr('href', '#' + this.id). 142 | attr('title', _('Permalink to this headline')). 143 | appendTo(this); 144 | }); 145 | $('dt[id]').each(function() { 146 | $('\u00B6'). 147 | attr('href', '#' + this.id). 148 | attr('title', _('Permalink to this definition')). 149 | appendTo(this); 150 | }); 151 | }, 152 | 153 | /** 154 | * workaround a firefox stupidity 155 | */ 156 | fixFirefoxAnchorBug : function() { 157 | if (document.location.hash && $.browser.mozilla) 158 | window.setTimeout(function() { 159 | document.location.href += ''; 160 | }, 10); 161 | }, 162 | 163 | /** 164 | * highlight the search words provided in the url in the text 165 | */ 166 | highlightSearchWords : function() { 167 | var params = $.getQueryParameters(); 168 | var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; 169 | if (terms.length) { 170 | var body = $('div.body'); 171 | if (!body.length) { 172 | body = $('body'); 173 | } 174 | window.setTimeout(function() { 175 | $.each(terms, function() { 176 | body.highlightText(this.toLowerCase(), 'highlighted'); 177 | }); 178 | }, 10); 179 | $('') 181 | .appendTo($('#searchbox')); 182 | } 183 | }, 184 | 185 | /** 186 | * init the domain index toggle buttons 187 | */ 188 | initIndexTable : function() { 189 | var togglers = $('img.toggler').click(function() { 190 | var src = $(this).attr('src'); 191 | var idnum = $(this).attr('id').substr(7); 192 | $('tr.cg-' + idnum).toggle(); 193 | if (src.substr(-9) == 'minus.png') 194 | $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); 195 | else 196 | $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); 197 | }).css('display', ''); 198 | if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { 199 | togglers.click(); 200 | } 201 | }, 202 | 203 | /** 204 | * helper function to hide the search marks again 205 | */ 206 | hideSearchWords : function() { 207 | $('#searchbox .highlight-link').fadeOut(300); 208 | $('span.highlighted').removeClass('highlighted'); 209 | }, 210 | 211 | /** 212 | * make the url absolute 213 | */ 214 | makeURL : function(relativeURL) { 215 | return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; 216 | }, 217 | 218 | /** 219 | * get the current relative url 220 | */ 221 | getCurrentURL : function() { 222 | var path = document.location.pathname; 223 | var parts = path.split(/\//); 224 | $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { 225 | if (this == '..') 226 | parts.pop(); 227 | }); 228 | var url = parts.join('/'); 229 | return path.substring(url.lastIndexOf('/') + 1, path.length - 1); 230 | } 231 | }; 232 | 233 | // quick alias for translations 234 | _ = Documentation.gettext; 235 | 236 | $(document).ready(function() { 237 | Documentation.init(); 238 | }); 239 | -------------------------------------------------------------------------------- /docs/_build/html/_static/down-pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcraft/pyscroll/31e1c282d09542ec89c6c1ac3ee6a1ff991ff5c2/docs/_build/html/_static/down-pressed.png -------------------------------------------------------------------------------- /docs/_build/html/_static/down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcraft/pyscroll/31e1c282d09542ec89c6c1ac3ee6a1ff991ff5c2/docs/_build/html/_static/down.png -------------------------------------------------------------------------------- /docs/_build/html/_static/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcraft/pyscroll/31e1c282d09542ec89c6c1ac3ee6a1ff991ff5c2/docs/_build/html/_static/file.png -------------------------------------------------------------------------------- /docs/_build/html/_static/minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcraft/pyscroll/31e1c282d09542ec89c6c1ac3ee6a1ff991ff5c2/docs/_build/html/_static/minus.png -------------------------------------------------------------------------------- /docs/_build/html/_static/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcraft/pyscroll/31e1c282d09542ec89c6c1ac3ee6a1ff991ff5c2/docs/_build/html/_static/plus.png -------------------------------------------------------------------------------- /docs/_build/html/_static/pygments.css: -------------------------------------------------------------------------------- 1 | .highlight .hll { background-color: #ffffcc } 2 | .highlight { background: #eeffcc; } 3 | .highlight .c { color: #408090; font-style: italic } /* Comment */ 4 | .highlight .err { border: 1px solid #FF0000 } /* Error */ 5 | .highlight .k { color: #007020; font-weight: bold } /* Keyword */ 6 | .highlight .o { color: #666666 } /* Operator */ 7 | .highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */ 8 | .highlight .cp { color: #007020 } /* Comment.Preproc */ 9 | .highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */ 10 | .highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */ 11 | .highlight .gd { color: #A00000 } /* Generic.Deleted */ 12 | .highlight .ge { font-style: italic } /* Generic.Emph */ 13 | .highlight .gr { color: #FF0000 } /* Generic.Error */ 14 | .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 15 | .highlight .gi { color: #00A000 } /* Generic.Inserted */ 16 | .highlight .go { color: #333333 } /* Generic.Output */ 17 | .highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ 18 | .highlight .gs { font-weight: bold } /* Generic.Strong */ 19 | .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 20 | .highlight .gt { color: #0044DD } /* Generic.Traceback */ 21 | .highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */ 22 | .highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ 23 | .highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ 24 | .highlight .kp { color: #007020 } /* Keyword.Pseudo */ 25 | .highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ 26 | .highlight .kt { color: #902000 } /* Keyword.Type */ 27 | .highlight .m { color: #208050 } /* Literal.Number */ 28 | .highlight .s { color: #4070a0 } /* Literal.String */ 29 | .highlight .na { color: #4070a0 } /* Name.Attribute */ 30 | .highlight .nb { color: #007020 } /* Name.Builtin */ 31 | .highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */ 32 | .highlight .no { color: #60add5 } /* Name.Constant */ 33 | .highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ 34 | .highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */ 35 | .highlight .ne { color: #007020 } /* Name.Exception */ 36 | .highlight .nf { color: #06287e } /* Name.Function */ 37 | .highlight .nl { color: #002070; font-weight: bold } /* Name.Label */ 38 | .highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ 39 | .highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */ 40 | .highlight .nv { color: #bb60d5 } /* Name.Variable */ 41 | .highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */ 42 | .highlight .w { color: #bbbbbb } /* Text.Whitespace */ 43 | .highlight .mf { color: #208050 } /* Literal.Number.Float */ 44 | .highlight .mh { color: #208050 } /* Literal.Number.Hex */ 45 | .highlight .mi { color: #208050 } /* Literal.Number.Integer */ 46 | .highlight .mo { color: #208050 } /* Literal.Number.Oct */ 47 | .highlight .sb { color: #4070a0 } /* Literal.String.Backtick */ 48 | .highlight .sc { color: #4070a0 } /* Literal.String.Char */ 49 | .highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */ 50 | .highlight .s2 { color: #4070a0 } /* Literal.String.Double */ 51 | .highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ 52 | .highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */ 53 | .highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */ 54 | .highlight .sx { color: #c65d09 } /* Literal.String.Other */ 55 | .highlight .sr { color: #235388 } /* Literal.String.Regex */ 56 | .highlight .s1 { color: #4070a0 } /* Literal.String.Single */ 57 | .highlight .ss { color: #517918 } /* Literal.String.Symbol */ 58 | .highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ 59 | .highlight .vc { color: #bb60d5 } /* Name.Variable.Class */ 60 | .highlight .vg { color: #bb60d5 } /* Name.Variable.Global */ 61 | .highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */ 62 | .highlight .il { color: #208050 } /* Literal.Number.Integer.Long */ -------------------------------------------------------------------------------- /docs/_build/html/_static/sidebar.js: -------------------------------------------------------------------------------- 1 | /* 2 | * sidebar.js 3 | * ~~~~~~~~~~ 4 | * 5 | * This script makes the Sphinx sidebar collapsible. 6 | * 7 | * .sphinxsidebar contains .sphinxsidebarwrapper. This script adds 8 | * in .sphixsidebar, after .sphinxsidebarwrapper, the #sidebarbutton 9 | * used to collapse and expand the sidebar. 10 | * 11 | * When the sidebar is collapsed the .sphinxsidebarwrapper is hidden 12 | * and the width of the sidebar and the margin-left of the document 13 | * are decreased. When the sidebar is expanded the opposite happens. 14 | * This script saves a per-browser/per-session cookie used to 15 | * remember the position of the sidebar among the pages. 16 | * Once the browser is closed the cookie is deleted and the position 17 | * reset to the default (expanded). 18 | * 19 | * :copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS. 20 | * :license: BSD, see LICENSE for details. 21 | * 22 | */ 23 | 24 | $(function() { 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | // global elements used by the functions. 34 | // the 'sidebarbutton' element is defined as global after its 35 | // creation, in the add_sidebar_button function 36 | var bodywrapper = $('.bodywrapper'); 37 | var sidebar = $('.sphinxsidebar'); 38 | var sidebarwrapper = $('.sphinxsidebarwrapper'); 39 | 40 | // for some reason, the document has no sidebar; do not run into errors 41 | if (!sidebar.length) return; 42 | 43 | // original margin-left of the bodywrapper and width of the sidebar 44 | // with the sidebar expanded 45 | var bw_margin_expanded = bodywrapper.css('margin-left'); 46 | var ssb_width_expanded = sidebar.width(); 47 | 48 | // margin-left of the bodywrapper and width of the sidebar 49 | // with the sidebar collapsed 50 | var bw_margin_collapsed = '.8em'; 51 | var ssb_width_collapsed = '.8em'; 52 | 53 | // colors used by the current theme 54 | var dark_color = $('.related').css('background-color'); 55 | var light_color = $('.document').css('background-color'); 56 | 57 | function sidebar_is_collapsed() { 58 | return sidebarwrapper.is(':not(:visible)'); 59 | } 60 | 61 | function toggle_sidebar() { 62 | if (sidebar_is_collapsed()) 63 | expand_sidebar(); 64 | else 65 | collapse_sidebar(); 66 | } 67 | 68 | function collapse_sidebar() { 69 | sidebarwrapper.hide(); 70 | sidebar.css('width', ssb_width_collapsed); 71 | bodywrapper.css('margin-left', bw_margin_collapsed); 72 | sidebarbutton.css({ 73 | 'margin-left': '0', 74 | 'height': bodywrapper.height() 75 | }); 76 | sidebarbutton.find('span').text('»'); 77 | sidebarbutton.attr('title', _('Expand sidebar')); 78 | document.cookie = 'sidebar=collapsed'; 79 | } 80 | 81 | function expand_sidebar() { 82 | bodywrapper.css('margin-left', bw_margin_expanded); 83 | sidebar.css('width', ssb_width_expanded); 84 | sidebarwrapper.show(); 85 | sidebarbutton.css({ 86 | 'margin-left': ssb_width_expanded-12, 87 | 'height': bodywrapper.height() 88 | }); 89 | sidebarbutton.find('span').text('«'); 90 | sidebarbutton.attr('title', _('Collapse sidebar')); 91 | document.cookie = 'sidebar=expanded'; 92 | } 93 | 94 | function add_sidebar_button() { 95 | sidebarwrapper.css({ 96 | 'float': 'left', 97 | 'margin-right': '0', 98 | 'width': ssb_width_expanded - 28 99 | }); 100 | // create the button 101 | sidebar.append( 102 | '
«
' 103 | ); 104 | var sidebarbutton = $('#sidebarbutton'); 105 | light_color = sidebarbutton.css('background-color'); 106 | // find the height of the viewport to center the '<<' in the page 107 | var viewport_height; 108 | if (window.innerHeight) 109 | viewport_height = window.innerHeight; 110 | else 111 | viewport_height = $(window).height(); 112 | sidebarbutton.find('span').css({ 113 | 'display': 'block', 114 | 'margin-top': (viewport_height - sidebar.position().top - 20) / 2 115 | }); 116 | 117 | sidebarbutton.click(toggle_sidebar); 118 | sidebarbutton.attr('title', _('Collapse sidebar')); 119 | sidebarbutton.css({ 120 | 'color': '#FFFFFF', 121 | 'border-left': '1px solid ' + dark_color, 122 | 'font-size': '1.2em', 123 | 'cursor': 'pointer', 124 | 'height': bodywrapper.height(), 125 | 'padding-top': '1px', 126 | 'margin-left': ssb_width_expanded - 12 127 | }); 128 | 129 | sidebarbutton.hover( 130 | function () { 131 | $(this).css('background-color', dark_color); 132 | }, 133 | function () { 134 | $(this).css('background-color', light_color); 135 | } 136 | ); 137 | } 138 | 139 | function set_position_from_cookie() { 140 | if (!document.cookie) 141 | return; 142 | var items = document.cookie.split(';'); 143 | for(var k=0; k2;a== 12 | null&&(a=[]);if(y&&a.reduce===y)return e&&(c=b.bind(c,e)),f?a.reduce(c,d):a.reduce(c);j(a,function(a,b,i){f?d=c.call(e,d,a,b,i):(d=a,f=true)});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(z&&a.reduceRight===z)return e&&(c=b.bind(c,e)),f?a.reduceRight(c,d):a.reduceRight(c);var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,c,d,e):b.reduce(g,c)};b.find=b.detect= 13 | function(a,c,b){var e;E(a,function(a,g,h){if(c.call(b,a,g,h))return e=a,true});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(A&&a.filter===A)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(B&&a.every===B)return a.every(c,b);j(a,function(a,g,h){if(!(e= 14 | e&&c.call(b,a,g,h)))return n});return e};var E=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(C&&a.some===C)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return n});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;return p&&a.indexOf===p?a.indexOf(c)!=-1:b=E(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(b.isFunction(c)?c||a:a[c]).apply(a,d)})};b.pluck= 15 | function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a))return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;bd?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=function(a, 17 | c,d){d||(d=b.identity);for(var e=0,f=a.length;e>1;d(a[g])=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1));return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e=0;d--)b=[a[d].apply(this,b)];return b[0]}}; 24 | b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=J||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c=[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};b.defaults=function(a){j(i.call(arguments, 25 | 1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return q(a,b,[])};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=o||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)}; 26 | b.isArguments=function(a){return l.call(a)=="[object Arguments]"};if(!b.isArguments(arguments))b.isArguments=function(a){return!(!a||!b.has(a,"callee"))};b.isFunction=function(a){return l.call(a)=="[object Function]"};b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"}; 27 | b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.has=function(a,b){return I.call(a,b)};b.noConflict=function(){r._=G;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")};b.mixin=function(a){j(b.functions(a), 28 | function(c){K(c,b[c]=a[c])})};var L=0;b.uniqueId=function(a){var b=L++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var t=/.^/,u=function(a){return a.replace(/\\\\/g,"\\").replace(/\\'/g,"'")};b.template=function(a,c){var d=b.templateSettings,d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.escape||t,function(a,b){return"',_.escape("+ 29 | u(b)+"),'"}).replace(d.interpolate||t,function(a,b){return"',"+u(b)+",'"}).replace(d.evaluate||t,function(a,b){return"');"+u(b).replace(/[\r\n\t]/g," ")+";__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');",e=new Function("obj","_",d);return c?e(c,b):function(a){return e.call(this,a,b)}};b.chain=function(a){return b(a).chain()};var m=function(a){this._wrapped=a};b.prototype=m.prototype;var v=function(a,c){return c?b(a).chain():a},K=function(a,c){m.prototype[a]= 30 | function(){var a=i.call(arguments);H.call(a,this._wrapped);return v(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];m.prototype[a]=function(){var d=this._wrapped;b.apply(d,arguments);var e=d.length;(a=="shift"||a=="splice")&&e===0&&delete d[0];return v(d,this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];m.prototype[a]=function(){return v(b.apply(this._wrapped,arguments),this._chain)}});m.prototype.chain=function(){this._chain= 31 | true;return this};m.prototype.value=function(){return this._wrapped}}).call(this); 32 | -------------------------------------------------------------------------------- /docs/_build/html/_static/up-pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcraft/pyscroll/31e1c282d09542ec89c6c1ac3ee6a1ff991ff5c2/docs/_build/html/_static/up-pressed.png -------------------------------------------------------------------------------- /docs/_build/html/_static/up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcraft/pyscroll/31e1c282d09542ec89c6c1ac3ee6a1ff991ff5c2/docs/_build/html/_static/up.png -------------------------------------------------------------------------------- /docs/_build/html/genindex.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Index — pyscroll 2.14.2 documentation 11 | 12 | 13 | 14 | 15 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 42 | 43 |
44 |
45 |
46 |
47 | 48 | 49 |

Index

50 | 51 |
52 | B 53 | | C 54 | | D 55 | | F 56 | | G 57 | | P 58 | | R 59 | | S 60 | | T 61 | | U 62 | 63 |
64 |

B

65 | 66 | 72 | 78 |
67 | 68 |
blit_tiles() (pyscroll.pyscroll.BufferedRenderer method) 69 |
70 | 71 |
73 | 74 |
BufferedRenderer (class in pyscroll.pyscroll) 75 |
76 | 77 |
79 | 80 |

C

81 | 82 | 94 |
83 | 84 |
center() (pyscroll.pyscroll.BufferedRenderer method) 85 |
86 | 87 |
88 | 89 |
(pyscroll.util.PyscrollGroup method) 90 |
91 | 92 |
93 |
95 | 96 |

D

97 | 98 | 114 | 120 |
99 | 100 |
draw() (pyscroll.pyscroll.BufferedRenderer method) 101 |
102 | 103 |
104 | 105 |
(pyscroll.util.PyscrollGroup method) 106 |
107 | 108 |
109 | 110 |
draw_objects() (pyscroll.pyscroll.BufferedRenderer method) 111 |
112 | 113 |
115 | 116 |
draw_shapes() (in module pyscroll.util) 117 |
118 | 119 |
121 | 122 |

F

123 | 124 | 136 |
125 | 126 |
flush() (pyscroll.pyscroll.BufferedRenderer method) 127 |
128 | 129 |
130 | 131 |
(pyscroll.pyscroll.ThreadedRenderer method) 132 |
133 | 134 |
135 |
137 | 138 |

G

139 | 140 | 150 | 156 |
141 | 142 |
generate_default_image() (pyscroll.pyscroll.BufferedRenderer method) 143 |
144 | 145 | 146 |
get_edge_tiles() (pyscroll.pyscroll.BufferedRenderer method) 147 |
148 | 149 |
151 | 152 |
get_tile_image() (pyscroll.pyscroll.BufferedRenderer method) 153 |
154 | 155 |
157 | 158 |

P

159 | 160 | 174 | 184 |
161 | 162 |
pyscroll (module) 163 |
164 | 165 | 166 |
pyscroll.data (module) 167 |
168 | 169 | 170 |
pyscroll.pyscroll (module) 171 |
172 | 173 |
175 | 176 |
pyscroll.util (module) 177 |
178 | 179 | 180 |
PyscrollGroup (class in pyscroll.util) 181 |
182 | 183 |
185 | 186 |

R

187 | 188 | 194 | 200 |
189 | 190 |
redraw() (pyscroll.pyscroll.BufferedRenderer method) 191 |
192 | 193 |
195 | 196 |
run() (pyscroll.pyscroll.TileThread method) 197 |
198 | 199 |
201 | 202 |

S

203 | 204 | 214 | 220 |
205 | 206 |
scroll() (pyscroll.pyscroll.BufferedRenderer method) 207 |
208 | 209 | 210 |
set_data() (pyscroll.pyscroll.BufferedRenderer method) 211 |
212 | 213 |
215 | 216 |
set_size() (pyscroll.pyscroll.BufferedRenderer method) 217 |
218 | 219 |
221 | 222 |

T

223 | 224 | 234 | 240 |
225 | 226 |
ThreadedRenderer (class in pyscroll.pyscroll) 227 |
228 | 229 | 230 |
TiledMapData (in module pyscroll.data) 231 |
232 | 233 |
235 | 236 |
TileThread (class in pyscroll.pyscroll) 237 |
238 | 239 |
241 | 242 |

U

243 | 244 | 260 | 272 |
245 | 246 |
update() (pyscroll.pyscroll.BufferedRenderer method) 247 |
248 | 249 |
250 | 251 |
(pyscroll.pyscroll.ThreadedRenderer method) 252 |
253 | 254 | 255 |
(pyscroll.util.PyscrollGroup method) 256 |
257 | 258 |
259 |
261 | 262 |
update_queue() (pyscroll.pyscroll.BufferedRenderer method) 263 |
264 | 265 |
266 | 267 |
(pyscroll.pyscroll.ThreadedRenderer method) 268 |
269 | 270 |
271 |
273 | 274 | 275 | 276 |
277 |
278 |
279 |
280 |
281 | 282 | 283 | 284 | 296 | 297 |
298 |
299 |
300 |
301 | 313 | 317 | 318 | -------------------------------------------------------------------------------- /docs/_build/html/index.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | pyscroll — pyscroll 2.14.2 documentation 10 | 11 | 12 | 13 | 14 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 41 | 42 |
43 |
44 |
45 |
46 | 47 |
48 |

pyscroll

49 |

for Python 3.9 and Pygame 1.9

50 |

A simple, fast module for adding scrolling maps to your new or existing game.

51 |
52 |
53 |

Introduction

54 |

pyscroll is a generic module for making a fast scrolling image with PyGame. It 55 | uses a lot of magic to get reasonable framerates out of PyGame. It only exists 56 | to draw a map. It doesn’t load images or data, so you can use your own custom 57 | data structures, tile storage, ect.

58 |

The included class, BufferedRenderer, gives great framerates, supports layered 59 | rendering and can draw itself. It uses more memory than a typical map would, 60 | but gives much better performance.

61 |

pyscroll is compatible with pytmx (https://github.com/bitcraft/pytmx), so you 62 | can use your Tiled maps. It also has out-of-the-box support for PyGame Sprites.

63 |
64 |
65 |

Features

66 |
    67 |
  • Fast and small footprint
  • 68 |
  • Layered drawing for tiles
  • 69 |
  • Drawing and scrolling shapes
  • 70 |
  • Dirty screen updates
  • 71 |
  • Pygame Group included
  • 72 |
73 |
74 |
75 |

Shape Drawing

76 |

pyscroll has a new experimental feature for drawing shapes to the map and 77 | scrolling them as part of the map. This can be useful for game that need to 78 | draw arbitrary shaped objects.

79 |

The feature requires pytmx, and that the map files be created in the Tiled Map 80 | Editor. The advantage of of using pyscroll for shape drawing is that pyscroll 81 | can draw the shapes much more efficiently that simply drawing the shapes each 82 | frame.

83 |

This feature is experimental at best. Your comments and support is appreciated!

84 |
    85 |
  • Currently, shapes will not draw over sprites.
  • 86 |
87 |
88 |
89 |

New Game Tutorial

90 |

This is a quick guide on building a new game with pyscroll and pygame. It uses 91 | the PyscrollGroup for efficient rendering. You are free to use any other pygame 92 | techniques and functions.

93 |

Open quest.py in the tutorial folder for a gentle introduction to pyscroll and 94 | the PyscrollGroup for PyGame. There are plenty of comments to get you started.

95 |

The Quest demo shows how you can use a pyscroll group for drawing, how to load 96 | maps with PyTMX, and how pyscroll can quickly render layers. Moving under some 97 | tiles will cause the Hero to be covered.

98 |

It will also render a Shape on the map with experimental shape drawing.

99 |
100 |
101 |

Example Use with PyTMX

102 |

pyscroll and pytmx can load your maps from Tiled and use you PyGame Sprites.

103 |
# Load TMX data
104 | tmx_data = pytmx.load_pygame("desert.tmx")
105 | 
106 | # Make data source for the map
107 | map_data = pyscroll.TiledMapData(tmx_data)
108 | 
109 | # Make the scrolling layer
110 | screen_size = (400, 400)
111 | map_layer = pyscroll.BufferedRenderer(map_data, screen_size)
112 | 
113 | # make the PyGame SpriteGroup with a scrolling map
114 | group = pyscroll.PyscrollGroup(map_layer=map_layer)
115 | 
116 | # Add sprites to the group
117 | group.add(srite)
118 | 
119 | # Center the layer and sprites on a sprite
120 | group.center(sprite.rect.center)
121 | 
122 | # Draw the layer
123 | group.draw(screen)
124 | 
125 |
126 |
127 |
128 |

Adapting Existing Games / Map Data

129 |

pyscroll can be used with existing map data, but you will have to create a 130 | class to interact with pyscroll or adapt your data handler to have these 131 | functions / attributes:

132 |
class MyData:
133 | 
134 |     @property
135 |     def tilewidth(self):
136 |         """ Return pixel width of map tiles
137 |         """
138 | 
139 |     @property
140 |     def tileheight(self):
141 |         """ Return pixel height of map tiles
142 |         """
143 | 
144 |     @property
145 |     def width(self):
146 |         """ Return number of tiles on X axis
147 |         """
148 | 
149 |     @property
150 |     def height(self):
151 |         """ Return number of tiles on Y axis
152 |         """
153 | 
154 |     @property
155 |     def visible_layers(self):
156 |         """ Return a list or iterator of layer numbers that are visible.
157 |         If using a single layer map, just return [0]
158 |         """
159 | 
160 |     def get_tile_image(self, position):
161 |         """ Return a surface for this position.
162 |         Return self.default_image if there is not map data for the position.
163 |         position is x, y, layer tuple
164 |         """
165 | 
166 |
167 |
168 |
    169 |
170 |
171 |
172 |
173 |

Indices and tables

174 | 179 |
180 | 181 | 182 |
183 |
184 |
185 |
186 |
187 |

Table Of Contents

188 | 198 | 199 |

This Page

200 | 204 | 216 | 217 |
218 |
219 |
220 |
221 | 233 | 237 | 238 | -------------------------------------------------------------------------------- /docs/_build/html/objects.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcraft/pyscroll/31e1c282d09542ec89c6c1ac3ee6a1ff991ff5c2/docs/_build/html/objects.inv -------------------------------------------------------------------------------- /docs/_build/html/py-modindex.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Python Module Index — pyscroll 2.14.2 documentation 10 | 11 | 12 | 13 | 14 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 44 | 45 |
46 |
47 |
48 |
49 | 50 | 51 |

Python Module Index

52 | 53 |
54 | p 55 |
56 | 57 | 58 | 59 | 61 | 62 | 64 | 67 | 68 | 69 | 72 | 73 | 74 | 77 | 78 | 79 | 82 |
 
60 | p
65 | pyscroll 66 |
    70 | pyscroll.data 71 |
    75 | pyscroll.pyscroll 76 |
    80 | pyscroll.util 81 |
83 | 84 | 85 |
86 |
87 |
88 |
89 |
90 | 102 | 103 |
104 |
105 |
106 |
107 | 119 | 123 | 124 | -------------------------------------------------------------------------------- /docs/_build/html/search.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Search — pyscroll 2.14.2 documentation 10 | 11 | 12 | 13 | 14 | 23 | 24 | 25 | 26 | 27 | 28 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 49 | 50 |
51 |
52 |
53 |
54 | 55 |

Search

56 |
57 | 58 |

59 | Please activate JavaScript to enable the search 60 | functionality. 61 |

62 |
63 |

64 | From here you can search these documents. Enter your search 65 | words into the box below and click "search". Note that the search 66 | function will automatically search for all of the words. Pages 67 | containing fewer words won't appear in the result list. 68 |

69 |
70 | 71 | 72 | 73 |
74 | 75 |
76 | 77 |
78 | 79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | 100 | 104 | 105 | -------------------------------------------------------------------------------- /docs/_build/html/searchindex.js: -------------------------------------------------------------------------------- 1 | Search.setIndex({envversion:42,terms:{load:0,all:1,just:0,show:0,api:1,over:0,move:0,higher:1,abil:1,effici:0,content:[],onli:0,slow:1,group:[0,1],also:0,mydata:0,bitcraft:0,better:0,legacytiledmapdata:1,window:1,experiment:0,update_queu:1,creat:[0,1],under:0,folder:0,pixel:[0,1],them:[0,1],sourc:0,"return":[0,1],get:[0,1],python:0,spritegroup:0,quest:0,background:1,requir:0,portion:1,colorkei:1,magic:0,arbitrari:0,list:[0,1],coord:1,collect:1,benefit:1,vector:1,either:1,each:0,small:[0,1],redraw:1,where:1,page:0,smaller:1,frame:0,some:[0,1],blit_til:1,see:1,blite:1,set_siz:1,iter:[0,1],ect:0,pass:1,poll:1,run:1,happen:1,best:0,out:0,compat:0,index:0,total:1,onto:1,correctli:1,tmx:0,clamp_camera:1,overlap:1,current:0,realign:1,won:1,suitabl:1,default_imag:0,drawn:1,order:1,gener:0,attribut:0,load_pygam:0,hero:0,appreci:0,tilewidth:0,screen:[0,1],pygam:[0,1],given:1,free:0,layeredupd:1,quick:0,reason:0,gentl:0,here:1,offset:1,bufferedrender:[0,1],valu:1,box:0,great:0,thread:1,tileheight:0,threadedrender:1,draw_shap:1,manag:1,queue:1,base:1,loop:1,fals:1,map_data:0,chang:1,basemap:1,screen_siz:0,block:1,comment:0,arg:1,own:0,srite:0,render:[0,1],modul:[],set_data:1,number:[0,1],height:0,mai:1,set:1,empti:1,visibl:[0,1],framer:0,guid:0,storag:0,your:[0,1],size:1,differ:1,from:[0,1],com:0,would:0,area:1,interact:0,support:[0,1],two:1,fast:0,custom:0,width:0,much:[0,1],includ:0,lot:0,handl:1,start:0,quickli:0,"function":0,sprite:[0,1],tmx_data:[0,1],handler:0,imag:[0,1],draw_object:1,search:0,hurt:1,coordin:1,part:0,editor:0,illus:1,than:[0,1],must:1,none:1,dirti:[0,1],alia:1,work:1,displai:1,structur:0,until:1,defin:1,bilt:1,limit:1,can:[0,1],posit:[0,1],more:[0,1],typic:0,def:0,layer:[0,1],larger:1,file:[0,1],advantag:0,give:0,demo:0,dure:1,flush:1,ani:0,doesn:0,kwarg:1,simpli:0,blit:1,have:0,need:[0,1],everi:1,well:1,tilethread:1,option:1,oper:1,surfac:[0,1],multipl:1,techniqu:0,open:0,inform:1,rather:1,make:0,depth:1,visible_lay:0,member:1,how:0,add:[0,1],other:0,pad:1,build:0,increment:1,tupl:[0,1],tile:[0,1],you:[0,1],properti:0,simpl:0,singl:0,updat:[0,1],poor:1,generate_default_imag:1,map_lay:0,http:0,plenti:0,buffer:1,pyscrollgroup:[0,1],object:[0,1],redrawn:1,"class":[0,1],get_tile_imag:[0,1],get_edge_til:1,footprint:0,contain:1,desert:0,built:1,memori:0,github:0,off:1,center:[0,1],whole:1,cover:0,rect:[0,1],scroll:[0,1],caus:0,perform:[0,1],itself:0,expens:1,thi:[0,1],unoptim:1,axi:0,self:0,tiledmapdata:[0,1],usual:1},objtypes:{"0":"py:module","1":"py:class","2":"py:method","3":"py:function","4":"py:attribute"},objnames:{"0":["py","module","Python module"],"1":["py","class","Python class"],"2":["py","method","Python method"],"3":["py","function","Python function"],"4":["py","attribute","Python attribute"]},filenames:["index","pyscroll"],titles:["pyscroll","pyscroll package"],objects:{"":{pyscroll:[1,0,0,"-"]},"pyscroll.pyscroll.TileThread":{run:[1,2,1,""]},"pyscroll.data":{TiledMapData:[1,4,1,""]},"pyscroll.pyscroll":{BufferedRenderer:[1,1,1,""],ThreadedRenderer:[1,1,1,""],TileThread:[1,1,1,""]},"pyscroll.pyscroll.ThreadedRenderer":{update_queue:[1,2,1,""],update:[1,2,1,""],flush:[1,2,1,""]},"pyscroll.util":{draw_shapes:[1,3,1,""],PyscrollGroup:[1,1,1,""]},"pyscroll.util.PyscrollGroup":{draw:[1,2,1,""],update:[1,2,1,""],center:[1,2,1,""]},"pyscroll.pyscroll.BufferedRenderer":{draw:[1,2,1,""],center:[1,2,1,""],blit_tiles:[1,2,1,""],draw_objects:[1,2,1,""],flush:[1,2,1,""],update:[1,2,1,""],generate_default_image:[1,2,1,""],update_queue:[1,2,1,""],set_size:[1,2,1,""],set_data:[1,2,1,""],redraw:[1,2,1,""],get_edge_tiles:[1,2,1,""],scroll:[1,2,1,""],get_tile_image:[1,2,1,""]},pyscroll:{util:[1,0,0,"-"],data:[1,0,0,"-"],pyscroll:[1,0,0,"-"]}},titleterms:{featur:0,modul:1,submodul:1,indic:0,packag:1,shape:0,exist:0,tabl:0,pyscrol:[0,1],welcom:[],content:1,exampl:0,adapt:0,"new":0,document:[],pytmx:0,map:0,draw:0,util:1,game:0,data:[0,1],introduct:0,tutori:0}}) -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # pyscroll documentation build configuration file, created by 4 | # sphinx-quickstart on Mon May 19 21:53:31 2014. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import os 16 | import sys 17 | 18 | # If extensions (or modules to document with autodoc) are in another directory, 19 | # add these directories to sys.path here. If the directory is relative to the 20 | # documentation root, use os.path.abspath to make it absolute, like shown here. 21 | # sys.path.insert(0, os.path.abspath('.')) 22 | 23 | # -- General configuration ------------------------------------------------ 24 | 25 | # If your documentation needs a minimal Sphinx version, state it here. 26 | # needs_sphinx = '1.0' 27 | 28 | # Add any Sphinx extension module names here, as strings. They can be 29 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 30 | # ones. 31 | extensions = [ 32 | "sphinx.ext.autodoc", 33 | "sphinx.ext.todo", 34 | "sphinx.ext.viewcode", 35 | ] 36 | 37 | # Add any paths that contain templates here, relative to this directory. 38 | templates_path = ["_templates"] 39 | 40 | # The suffix of source filenames. 41 | source_suffix = ".rst" 42 | 43 | # The encoding of source files. 44 | # source_encoding = 'utf-8-sig' 45 | 46 | # The master toctree document. 47 | master_doc = "index" 48 | 49 | # General information about the project. 50 | project = "pyscroll" 51 | copyright = "2014, bitcraft" 52 | 53 | # The version info for the project you're documenting, acts as replacement for 54 | # |version| and |release|, also used in various other places throughout the 55 | # built documents. 56 | # 57 | # The short X.Y version. 58 | version = "2.14.2" 59 | # The full version, including alpha/beta/rc tags. 60 | release = "2.14.2" 61 | 62 | # The language for content autogenerated by Sphinx. Refer to documentation 63 | # for a list of supported languages. 64 | # language = None 65 | 66 | # There are two options for replacing |today|: either, you set today to some 67 | # non-false value, then it is used: 68 | # today = '' 69 | # Else, today_fmt is used as the format for a strftime call. 70 | # today_fmt = '%B %d, %Y' 71 | 72 | # List of patterns, relative to source directory, that match files and 73 | # directories to ignore when looking for source files. 74 | exclude_patterns = ["_build", "tests"] 75 | 76 | # The reST default role (used for this markup: `text`) to use for all 77 | # documents. 78 | # default_role = None 79 | 80 | # If true, '()' will be appended to :func: etc. cross-reference text. 81 | # add_function_parentheses = True 82 | 83 | # If true, the current module name will be prepended to all description 84 | # unit titles (such as .. function::). 85 | # add_module_names = True 86 | 87 | # If true, sectionauthor and moduleauthor directives will be shown in the 88 | # output. They are ignored by default. 89 | # show_authors = False 90 | 91 | # The name of the Pygments (syntax highlighting) style to use. 92 | pygments_style = "sphinx" 93 | 94 | # A list of ignored prefixes for module index sorting. 95 | # modindex_common_prefix = [] 96 | 97 | # If true, keep warnings as "system message" paragraphs in the built documents. 98 | # keep_warnings = False 99 | 100 | 101 | # -- Options for HTML output ---------------------------------------------- 102 | 103 | # The theme to use for HTML and HTML Help pages. See the documentation for 104 | # a list of builtin themes. 105 | html_theme = "default" 106 | 107 | # Theme options are theme-specific and customize the look and feel of a theme 108 | # further. For a list of options available for each theme, see the 109 | # documentation. 110 | # html_theme_options = {} 111 | 112 | # Add any paths that contain custom themes here, relative to this directory. 113 | # html_theme_path = [] 114 | 115 | # The name for this set of Sphinx documents. If None, it defaults to 116 | # " v documentation". 117 | # html_title = None 118 | 119 | # A shorter title for the navigation bar. Default is the same as html_title. 120 | # html_short_title = None 121 | 122 | # The name of an image file (relative to this directory) to place at the top 123 | # of the sidebar. 124 | # html_logo = None 125 | 126 | # The name of an image file (within the static path) to use as favicon of the 127 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 128 | # pixels large. 129 | # html_favicon = None 130 | 131 | # Add any paths that contain custom static files (such as style sheets) here, 132 | # relative to this directory. They are copied after the builtin static files, 133 | # so a file named "default.css" will overwrite the builtin "default.css". 134 | html_static_path = ["_static"] 135 | 136 | # Add any extra paths that contain custom files (such as robots.txt or 137 | # .htaccess) here, relative to this directory. These files are copied 138 | # directly to the root of the documentation. 139 | # html_extra_path = [] 140 | 141 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 142 | # using the given strftime format. 143 | # html_last_updated_fmt = '%b %d, %Y' 144 | 145 | # If true, SmartyPants will be used to convert quotes and dashes to 146 | # typographically correct entities. 147 | # html_use_smartypants = True 148 | 149 | # Custom sidebar templates, maps document names to template names. 150 | # html_sidebars = {} 151 | 152 | # Additional templates that should be rendered to pages, maps page names to 153 | # template names. 154 | # html_additional_pages = {} 155 | 156 | # If false, no module index is generated. 157 | # html_domain_indices = True 158 | 159 | # If false, no index is generated. 160 | # html_use_index = True 161 | 162 | # If true, the index is split into individual pages for each letter. 163 | # html_split_index = False 164 | 165 | # If true, links to the reST sources are added to the pages. 166 | # html_show_sourcelink = True 167 | 168 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 169 | # html_show_sphinx = True 170 | 171 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 172 | # html_show_copyright = True 173 | 174 | # If true, an OpenSearch description file will be output, and all pages will 175 | # contain a tag referring to it. The value of this option must be the 176 | # base URL from which the finished HTML is served. 177 | # html_use_opensearch = '' 178 | 179 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 180 | # html_file_suffix = None 181 | 182 | # Output file base name for HTML help builder. 183 | htmlhelp_basename = "pyscrolldoc" 184 | 185 | 186 | # -- Options for LaTeX output --------------------------------------------- 187 | 188 | latex_elements = { 189 | # The paper size ('letterpaper' or 'a4paper'). 190 | #'papersize': 'letterpaper', 191 | # The font size ('10pt', '11pt' or '12pt'). 192 | #'pointsize': '10pt', 193 | # Additional stuff for the LaTeX preamble. 194 | #'preamble': '', 195 | } 196 | 197 | # Grouping the document tree into LaTeX files. List of tuples 198 | # (source start file, target name, title, 199 | # author, documentclass [howto, manual, or own class]). 200 | latex_documents = [ 201 | ("index", "pyscroll.tex", "pyscroll Documentation", "bitcraft", "manual"), 202 | ] 203 | 204 | # The name of an image file (relative to this directory) to place at the top of 205 | # the title page. 206 | # latex_logo = None 207 | 208 | # For "manual" documents, if this is true, then toplevel headings are parts, 209 | # not chapters. 210 | # latex_use_parts = False 211 | 212 | # If true, show page references after internal links. 213 | # latex_show_pagerefs = False 214 | 215 | # If true, show URL addresses after external links. 216 | # latex_show_urls = False 217 | 218 | # Documents to append as an appendix to all manuals. 219 | # latex_appendices = [] 220 | 221 | # If false, no module index is generated. 222 | # latex_domain_indices = True 223 | 224 | 225 | # -- Options for manual page output --------------------------------------- 226 | 227 | # One entry per manual page. List of tuples 228 | # (source start file, name, description, authors, manual section). 229 | man_pages = [("index", "pyscroll", "pyscroll Documentation", ["bitcraft"], 1)] 230 | 231 | # If true, show URL addresses after external links. 232 | # man_show_urls = False 233 | 234 | 235 | # -- Options for Texinfo output ------------------------------------------- 236 | 237 | # Grouping the document tree into Texinfo files. List of tuples 238 | # (source start file, target name, title, author, 239 | # dir menu entry, description, category) 240 | texinfo_documents = [ 241 | ( 242 | "index", 243 | "pyscroll", 244 | "pyscroll Documentation", 245 | "bitcraft", 246 | "pyscroll", 247 | "One line description of project.", 248 | "Miscellaneous", 249 | ), 250 | ] 251 | 252 | # Documents to append as an appendix to all manuals. 253 | # texinfo_appendices = [] 254 | 255 | # If false, no module index is generated. 256 | # texinfo_domain_indices = True 257 | 258 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 259 | # texinfo_show_urls = 'footnote' 260 | 261 | # If true, do not generate a @detailmenu in the "Top" node's menu. 262 | # texinfo_no_detailmenu = False 263 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. pyscroll documentation master file, created by 2 | sphinx-quickstart on Mon May 19 21:53:31 2014. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | pyscroll 7 | ======== 8 | 9 | for Python 3.9+ and Pygame 1.9 10 | 11 | A simple, fast module for adding scrolling maps to your new or existing game. 12 | Includes support to load and render maps in TMX format from the Tiled map editor. 13 | 14 | 15 | Introduction 16 | ============ 17 | 18 | pyscroll is a generic module for making a fast scrolling image with PyGame. It 19 | uses a lot of magic to get reasonable framerates out of PyGame. It only exists 20 | to draw a map. It doesn't load images or data, so you can use your own custom 21 | data structures, tile storage, ect. 22 | 23 | The included class, BufferedRenderer, gives great framerates, supports layered 24 | rendering and can draw itself. 25 | 26 | pyscroll is compatible with pytmx (https://github.com/bitcraft/pytmx), so you 27 | can use your Tiled maps. It also has out-of-the-box support for PyGame Sprites. 28 | 29 | 30 | Features 31 | ======== 32 | 33 | - Fast and small footprint 34 | - Layered drawing for tiles 35 | - Drawing and scrolling shapes 36 | - Dirty screen updates 37 | - Pygame Sprite compatible group included 38 | 39 | 40 | Shape Drawing 41 | ============= 42 | 43 | pyscroll has a new experimental feature for drawing shapes to the map and 44 | scrolling them as part of the map. This can be useful for game that need to 45 | draw arbitrary shaped objects. 46 | 47 | The feature requires pytmx, and that the map files be created in the Tiled Map 48 | Editor. The advantage of of using pyscroll for shape drawing is that pyscroll 49 | can draw the shapes much more efficiently that simply drawing the shapes each 50 | frame. 51 | 52 | This feature is experimental at best. Your comments and support is appreciated! 53 | 54 | * Currently, shapes will not draw over sprites. 55 | 56 | 57 | New Game Tutorial 58 | ================= 59 | 60 | This is a quick guide on building a new game with pyscroll and pygame. It uses 61 | the PyscrollGroup for efficient rendering. You are free to use any other pygame 62 | techniques and functions. 63 | 64 | Open quest.py in the tutorial folder for a gentle introduction to pyscroll and 65 | the PyscrollGroup for PyGame. There are plenty of comments to get you started. 66 | 67 | The Quest demo shows how you can use a pyscroll group for drawing, how to load 68 | maps with PyTMX, and how pyscroll can quickly render layers. Moving under some 69 | tiles will cause the Hero to be covered. 70 | 71 | It will also render a Shape on the map with experimental shape drawing. 72 | 73 | 74 | Example Use with PyTMX 75 | ====================== 76 | 77 | pyscroll and pytmx can load your maps from Tiled and use you PyGame Sprites. 78 | 79 | .. code-block:: python 80 | 81 | # Load TMX data 82 | tmx_data = pytmx.load_pygame("desert.tmx") 83 | 84 | # Make data source for the map 85 | map_data = pyscroll.TiledMapData(tmx_data) 86 | 87 | # Make the scrolling layer 88 | screen_size = (400, 400) 89 | map_layer = pyscroll.BufferedRenderer(map_data, screen_size) 90 | 91 | # make the PyGame SpriteGroup with a scrolling map 92 | group = pyscroll.PyscrollGroup(map_layer=map_layer) 93 | 94 | # Add sprites to the group 95 | group.add(sprite) 96 | 97 | # Center the layer and sprites on a sprite 98 | group.center(sprite.rect.center) 99 | 100 | # Draw the layer 101 | group.draw(screen) 102 | 103 | 104 | Adapting Existing Games / Map Data 105 | ================================== 106 | 107 | pyscroll can be used with existing map data, but you will have to create a 108 | class to interact with pyscroll or adapt your data handler. 109 | 110 | 111 | .. toctree:: 112 | :maxdepth: 2 113 | 114 | 115 | 116 | Indices and tables 117 | ================== 118 | 119 | * :ref:`genindex` 120 | * :ref:`modindex` 121 | * :ref:`search` 122 | 123 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | goto end 41 | ) 42 | 43 | if "%1" == "clean" ( 44 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 45 | del /q /s %BUILDDIR%\* 46 | goto end 47 | ) 48 | 49 | 50 | %SPHINXBUILD% 2> nul 51 | if errorlevel 9009 ( 52 | echo. 53 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 54 | echo.installed, then set the SPHINXBUILD environment variable to point 55 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 56 | echo.may add the Sphinx directory to PATH. 57 | echo. 58 | echo.If you don't have Sphinx installed, grab it from 59 | echo.http://sphinx-doc.org/ 60 | exit /b 1 61 | ) 62 | 63 | if "%1" == "html" ( 64 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 68 | goto end 69 | ) 70 | 71 | if "%1" == "dirhtml" ( 72 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 76 | goto end 77 | ) 78 | 79 | if "%1" == "singlehtml" ( 80 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 84 | goto end 85 | ) 86 | 87 | if "%1" == "pickle" ( 88 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can process the pickle files. 92 | goto end 93 | ) 94 | 95 | if "%1" == "json" ( 96 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 97 | if errorlevel 1 exit /b 1 98 | echo. 99 | echo.Build finished; now you can process the JSON files. 100 | goto end 101 | ) 102 | 103 | if "%1" == "htmlhelp" ( 104 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 105 | if errorlevel 1 exit /b 1 106 | echo. 107 | echo.Build finished; now you can run HTML Help Workshop with the ^ 108 | .hhp project file in %BUILDDIR%/htmlhelp. 109 | goto end 110 | ) 111 | 112 | if "%1" == "qthelp" ( 113 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 114 | if errorlevel 1 exit /b 1 115 | echo. 116 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 117 | .qhcp project file in %BUILDDIR%/qthelp, like this: 118 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\pyscroll.qhcp 119 | echo.To view the help file: 120 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\pyscroll.ghc 121 | goto end 122 | ) 123 | 124 | if "%1" == "devhelp" ( 125 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished. 129 | goto end 130 | ) 131 | 132 | if "%1" == "epub" ( 133 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 137 | goto end 138 | ) 139 | 140 | if "%1" == "latex" ( 141 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 145 | goto end 146 | ) 147 | 148 | if "%1" == "latexpdf" ( 149 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 150 | cd %BUILDDIR%/latex 151 | make all-pdf 152 | cd %BUILDDIR%/.. 153 | echo. 154 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 155 | goto end 156 | ) 157 | 158 | if "%1" == "latexpdfja" ( 159 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 160 | cd %BUILDDIR%/latex 161 | make all-pdf-ja 162 | cd %BUILDDIR%/.. 163 | echo. 164 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 165 | goto end 166 | ) 167 | 168 | if "%1" == "text" ( 169 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 170 | if errorlevel 1 exit /b 1 171 | echo. 172 | echo.Build finished. The text files are in %BUILDDIR%/text. 173 | goto end 174 | ) 175 | 176 | if "%1" == "man" ( 177 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 178 | if errorlevel 1 exit /b 1 179 | echo. 180 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 181 | goto end 182 | ) 183 | 184 | if "%1" == "texinfo" ( 185 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 186 | if errorlevel 1 exit /b 1 187 | echo. 188 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 189 | goto end 190 | ) 191 | 192 | if "%1" == "gettext" ( 193 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 194 | if errorlevel 1 exit /b 1 195 | echo. 196 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 197 | goto end 198 | ) 199 | 200 | if "%1" == "changes" ( 201 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 202 | if errorlevel 1 exit /b 1 203 | echo. 204 | echo.The overview file is in %BUILDDIR%/changes. 205 | goto end 206 | ) 207 | 208 | if "%1" == "linkcheck" ( 209 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 210 | if errorlevel 1 exit /b 1 211 | echo. 212 | echo.Link check complete; look for any errors in the above output ^ 213 | or in %BUILDDIR%/linkcheck/output.txt. 214 | goto end 215 | ) 216 | 217 | if "%1" == "doctest" ( 218 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 219 | if errorlevel 1 exit /b 1 220 | echo. 221 | echo.Testing of doctests in the sources finished, look at the ^ 222 | results in %BUILDDIR%/doctest/output.txt. 223 | goto end 224 | ) 225 | 226 | if "%1" == "xml" ( 227 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 228 | if errorlevel 1 exit /b 1 229 | echo. 230 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 231 | goto end 232 | ) 233 | 234 | if "%1" == "pseudoxml" ( 235 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 236 | if errorlevel 1 exit /b 1 237 | echo. 238 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 239 | goto end 240 | ) 241 | 242 | :end 243 | -------------------------------------------------------------------------------- /docs/pyscroll.rst: -------------------------------------------------------------------------------- 1 | pyscroll package 2 | ================ 3 | 4 | Submodules 5 | ---------- 6 | 7 | pyscroll.pyscroll 8 | ----------------- 9 | 10 | .. automodule:: pyscroll.pyscroll 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | pyscroll.data 16 | ------------- 17 | 18 | .. automodule:: pyscroll.data 19 | :members: TiledMapData 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | pyscroll.util 24 | ------------- 25 | 26 | .. automodule:: pyscroll.util 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | 32 | Module contents 33 | --------------- 34 | 35 | .. automodule:: pyscroll 36 | :members: 37 | :undoc-members: 38 | :show-inheritance: -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools", "setuptools-scm"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "pyscroll" 7 | version = "2.31" 8 | description = "Fast scrolling maps library for pygame" 9 | readme = "README.md" 10 | license = {file = "LICENSE"} 11 | authors = [ 12 | {name = "bitcraft", email = "leif.theden@gmail.com"} 13 | ] 14 | classifiers = [ 15 | "Intended Audience :: Developers", 16 | "Development Status :: 5 - Production/Stable", 17 | "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", 18 | "Programming Language :: Python :: 3.9", 19 | "Programming Language :: Python :: 3.10", 20 | "Programming Language :: Python :: 3.11", 21 | "Programming Language :: Python :: 3.12", 22 | "Topic :: Games/Entertainment", 23 | "Topic :: Multimedia :: Graphics", 24 | "Topic :: Software Development :: Libraries :: pygame", 25 | ] 26 | requires-python = ">=3.9" 27 | 28 | [project.urls] 29 | source = "https://github.com/bitcraft/pyscroll" 30 | 31 | [tool.setuptools] 32 | include-package-data = true 33 | 34 | [tool.setuptools.packages.find] 35 | where = ["."] 36 | include = ["pyscroll*"] 37 | 38 | [tool.black] 39 | line-length = 88 40 | target-version = ["py39"] 41 | 42 | [tool.isort] 43 | line_length = 88 44 | profile = "black" 45 | skip_gitignore = true -------------------------------------------------------------------------------- /pyscroll/__init__.py: -------------------------------------------------------------------------------- 1 | from .data import PyscrollDataAdapter, TiledMapData 2 | from .group import PyscrollGroup 3 | from .isometric import IsometricBufferedRenderer 4 | from .orthographic import BufferedRenderer 5 | 6 | __version__ = 2, 30 7 | __author__ = "bitcraft" 8 | __author_email__ = "leif.theden@gmail.com" 9 | __description__ = "Pygame Scrolling" 10 | -------------------------------------------------------------------------------- /pyscroll/animation.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from collections.abc import Sequence 4 | from typing import NamedTuple, Union 5 | 6 | from pygame import Surface 7 | 8 | 9 | class AnimationFrame(NamedTuple): 10 | image: Surface 11 | duration: float 12 | 13 | 14 | TimeLike = Union[float, int] 15 | 16 | __all__ = ("AnimationFrame", "AnimationToken") 17 | 18 | 19 | class AnimationToken: 20 | __slots__ = ["next", "positions", "frames", "index"] 21 | 22 | def __init__( 23 | self, 24 | positions: set[tuple[int, int, int]], 25 | frames: Sequence[AnimationFrame], 26 | initial_time: float = 0.0, 27 | ) -> None: 28 | """ 29 | Constructor 30 | 31 | Args: 32 | positions: Set of positions where the tile is on the map 33 | frames: Sequence of frames that compromise the animation 34 | initial_time: Used to compensate time between starting and changing animations 35 | 36 | Raises: 37 | ValueError: If the frames sequence is empty 38 | """ 39 | if not frames: 40 | raise ValueError("Frames sequence cannot be empty") 41 | 42 | frames = tuple(AnimationFrame(*frame_data) for frame_data in frames) 43 | self.positions = positions 44 | self.frames = frames 45 | self.next = frames[0].duration + initial_time 46 | self.index = 0 47 | 48 | def advance(self, last_time: TimeLike) -> AnimationFrame: 49 | """ 50 | Advance the frame, and set timer for next frame 51 | 52 | Timer value is calculated by adding last_time and the 53 | duration of the next frame 54 | 55 | The next frame is returned 56 | 57 | * This API may change in the future 58 | 59 | Args: 60 | last_time: Duration of the last frame 61 | 62 | Returns: 63 | AnimationFrame: The next frame in the animation 64 | """ 65 | # advance the animation frame index, looping by default 66 | if self.index == len(self.frames) - 1: 67 | self.index = 0 68 | else: 69 | self.index += 1 70 | 71 | # set the timer for the next advance 72 | next_frame = self.frames[self.index] 73 | self.next = next_frame.duration + last_time 74 | return next_frame 75 | 76 | def __lt__(self, other): 77 | """ 78 | Compare the animation token with another object based on the next frame time 79 | 80 | Args: 81 | other: The object to compare with 82 | 83 | Returns: 84 | bool: True if the next frame time is less than the other object's time 85 | """ 86 | try: 87 | return self.next < other.next 88 | except AttributeError: 89 | return self.next < other 90 | 91 | def __repr__(self) -> str: 92 | return f"AnimationToken(positions={self.positions}, frames={self.frames})" 93 | -------------------------------------------------------------------------------- /pyscroll/common.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from contextlib import contextmanager 4 | from typing import Any, Union 5 | 6 | from pygame import Rect, Surface, Vector2 7 | 8 | RectLike = Union[Rect, tuple[Any, Any, Any, Any]] 9 | Vector2D = Union[tuple[float, float], tuple[int, int], Vector2] 10 | Vector2DInt = tuple[int, int] 11 | 12 | 13 | @contextmanager 14 | def surface_clipping_context(surface: Surface, clip: RectLike): 15 | original = surface.get_clip() 16 | surface.set_clip(clip) 17 | yield 18 | surface.set_clip(original) 19 | 20 | 21 | def rect_difference(a: RectLike, b: RectLike) -> list[Rect]: 22 | """ 23 | Compute difference of two rects. Returns up to 4. 24 | 25 | """ 26 | raise NotImplementedError 27 | 28 | 29 | def rect_to_bb(rect: RectLike) -> tuple[int, int, int, int]: 30 | x, y, w, h = rect 31 | return x, y, x + w - 1, y + h - 1 32 | -------------------------------------------------------------------------------- /pyscroll/group.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | import pygame 6 | 7 | if TYPE_CHECKING: 8 | from .orthographic import BufferedRenderer 9 | 10 | 11 | class PyscrollGroup(pygame.sprite.LayeredUpdates): 12 | """ 13 | Layered Group with ability to center sprites and scrolling map. 14 | 15 | Args: 16 | map_layer: Pyscroll Renderer 17 | 18 | """ 19 | 20 | def __init__(self, map_layer: BufferedRenderer, *args, **kwargs) -> None: 21 | pygame.sprite.LayeredUpdates.__init__(self, *args, **kwargs) 22 | self._map_layer = map_layer 23 | 24 | def center(self, value) -> None: 25 | """ 26 | Center the group/map on a pixel. 27 | 28 | The basemap and all sprites will be realigned to draw correctly. 29 | Centering the map will not change the rect of the sprites. 30 | 31 | Args: 32 | value: x, y coordinates to center the camera on 33 | 34 | """ 35 | self._map_layer.center(value) 36 | 37 | @property 38 | def view(self) -> pygame.Rect: 39 | """ 40 | Return a Rect representing visible portion of map. 41 | 42 | """ 43 | return self._map_layer.view_rect.copy() 44 | 45 | def draw(self, surface: pygame.surface.Surface) -> list[pygame.rect.Rect]: 46 | """ 47 | Draw map and all sprites onto the surface. 48 | 49 | Args: 50 | surface: Surface to draw to 51 | 52 | """ 53 | ox, oy = self._map_layer.get_center_offset() 54 | draw_area = surface.get_rect() 55 | view_rect = self.view 56 | 57 | new_surfaces = list() 58 | spritedict = self.spritedict 59 | gl = self.get_layer_of_sprite 60 | new_surfaces_append = new_surfaces.append 61 | 62 | for spr in self.sprites(): 63 | new_rect = spr.rect.move(ox, oy) 64 | if spr.rect.colliderect(view_rect): 65 | try: 66 | new_surfaces_append((spr.image, new_rect, gl(spr), spr.blendmode)) 67 | except AttributeError: 68 | # should only fail when no blendmode available 69 | new_surfaces_append((spr.image, new_rect, gl(spr))) 70 | spritedict[spr] = new_rect 71 | 72 | self.lostsprites = [] 73 | return self._map_layer.draw(surface, draw_area, new_surfaces) 74 | -------------------------------------------------------------------------------- /pyscroll/isometric.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from pyscroll.common import Vector2D, Vector2DInt 4 | from pyscroll.orthographic import BufferedRenderer 5 | 6 | log = logging.getLogger(__file__) 7 | 8 | 9 | def vector3_to_iso( 10 | vector3: tuple[int, int, int], offset: tuple[int, int] = (0, 0) 11 | ) -> tuple[int, int]: 12 | """ 13 | Convert 3D cartesian coordinates to isometric coordinates. 14 | """ 15 | if len(vector3) != 3: 16 | raise ValueError("Input tuple must have exactly 3 elements") 17 | return ( 18 | (vector3[0] - vector3[1]) + offset[0], 19 | ((vector3[0] + vector3[1]) >> 1) - vector3[2] + offset[1], 20 | ) 21 | 22 | 23 | def vector2_to_iso( 24 | vector2: tuple[int, int], offset: tuple[int, int] = (0, 0) 25 | ) -> tuple[int, int]: 26 | """ 27 | Convert 2D cartesian coordinates to isometric coordinates. 28 | """ 29 | if len(vector2) != 2: 30 | raise ValueError("Input tuple must have exactly 2 elements") 31 | return ( 32 | (vector2[0] - vector2[1]) + offset[0], 33 | ((vector2[0] + vector2[1]) >> 1) + offset[1], 34 | ) 35 | 36 | 37 | class IsometricBufferedRenderer(BufferedRenderer): 38 | """TEST ISOMETRIC 39 | 40 | here be dragons. lots of odd, untested, and unoptimised stuff. 41 | 42 | - coalescing of surfaces is not supported 43 | - drawing may have depth sorting issues 44 | """ 45 | 46 | def _draw_surfaces(self, surface, rect, surfaces) -> None: 47 | if surfaces is not None: 48 | [(surface.blit(i[0], i[1]), i[2]) for i in surfaces] 49 | 50 | def _initialize_buffers(self, view_size: Vector2DInt) -> None: 51 | """Create the buffers to cache tile drawing 52 | 53 | :param view_size: (int, int): size of the draw area 54 | :return: None 55 | """ 56 | import math 57 | 58 | from pygame import Rect 59 | 60 | tw, th = self.data.tile_size 61 | mw, mh = self.data.map_size 62 | buffer_tile_width = int(math.ceil(view_size[0] / tw) + 2) * 2 63 | buffer_tile_height = int(math.ceil(view_size[1] / th) + 2) * 2 64 | buffer_pixel_size = buffer_tile_width * tw, buffer_tile_height * th 65 | 66 | self.map_rect = Rect(0, 0, mw * tw, mh * th) 67 | self.view_rect.size = view_size 68 | self._tile_view = Rect(0, 0, buffer_tile_width, buffer_tile_height) 69 | self._redraw_cutoff = 1 # TODO: optimize this value 70 | self._create_buffers(view_size, buffer_pixel_size) 71 | self._half_width = view_size[0] // 2 72 | self._half_height = view_size[1] // 2 73 | self._x_offset = 0 74 | self._y_offset = 0 75 | 76 | self.redraw_tiles() 77 | 78 | def _flush_tile_queue(self) -> None: 79 | """Blits (x, y, layer) tuples to buffer from iterator""" 80 | iterator = self._tile_queue 81 | surface_blit = self._buffer.blit 82 | map_get = self._animation_map.get 83 | 84 | bw, bh = self._buffer.get_size() 85 | bw /= 2 86 | 87 | tw, th = self.data.tile_size 88 | twh = tw // 2 89 | thh = th // 2 90 | 91 | for x, y, l, tile, gid in iterator: 92 | tile = map_get(gid, tile) 93 | x -= self._tile_view.left 94 | y -= self._tile_view.top 95 | 96 | # iso => cart 97 | iso_x = ((x - y) * twh) + bw 98 | iso_y = (x + y) * thh 99 | surface_blit(tile, (iso_x, iso_y)) 100 | 101 | def center(self, coords: Vector2D) -> None: 102 | """center the map on a "map pixel" """ 103 | x, y = [round(i, 0) for i in coords] 104 | self.view_rect.center = x, y 105 | 106 | tw, th = self.data.tile_size 107 | 108 | left, ox = divmod(x, tw) 109 | top, oy = divmod(y, th) 110 | 111 | vec = int(ox / 2), int(oy) 112 | 113 | iso = vector2_to_iso(vec) 114 | self._x_offset = iso[0] 115 | self._y_offset = iso[1] 116 | 117 | print(self._tile_view.size) 118 | assert self._buffer 119 | print(self._buffer.get_size()) 120 | 121 | # center the buffer on the screen 122 | self._x_offset += (self._buffer.get_width() - self.view_rect.width) // 2 123 | self._y_offset += (self._buffer.get_height() - self.view_rect.height) // 4 124 | 125 | # adjust the view if the view has changed without a redraw 126 | dx = int(left - self._tile_view.left) 127 | dy = int(top - self._tile_view.top) 128 | view_change = max(abs(dx), abs(dy)) 129 | 130 | # force redraw every time: edge queuing not supported yet 131 | self._redraw_cutoff = 0 132 | 133 | if view_change and (view_change <= self._redraw_cutoff): 134 | self._buffer.scroll(-dx * tw, -dy * th) 135 | self._tile_view.move_ip(dx, dy) 136 | self._queue_edge_tiles(dx, dy) 137 | self._flush_tile_queue() 138 | 139 | elif view_change > self._redraw_cutoff: 140 | # logger.info('scrolling too quickly. redraw forced') 141 | self._tile_view.move_ip(dx, dy) 142 | self.redraw_tiles() 143 | 144 | # def redraw_tiles(self): 145 | # """ redraw the visible portion of the buffer -- it is slow. 146 | # """ 147 | # if self._clear_color: 148 | # self._buffer.fill(self._clear_color) 149 | # 150 | # v = self._tile_view 151 | # self._tile_queue = [] 152 | # for x in range(v.left, v.right): 153 | # for y in range(v.top, v.bottom): 154 | # ix, iy = vector2_to_iso((x, y)) 155 | # tile = self.data.get_tile_image((ix, iy, 0)) 156 | # if tile: 157 | # self._tile_queue.append((x, y, 0, tile, 0)) 158 | # print((x, y), (ix, iy)) 159 | # 160 | # self._flush_tile_queue() 161 | -------------------------------------------------------------------------------- /pyscroll/quadtree.py: -------------------------------------------------------------------------------- 1 | """ 2 | Two classes for quadtree collision detection. 3 | 4 | A quadtree is used with pyscroll to detect overlapping tiles. 5 | """ 6 | from __future__ import annotations 7 | 8 | import itertools 9 | from collections.abc import Sequence 10 | from typing import TYPE_CHECKING 11 | 12 | from pygame import Rect 13 | 14 | if TYPE_CHECKING: 15 | from .common import RectLike 16 | 17 | 18 | class FastQuadTree: 19 | """ 20 | An implementation of a quad-tree. 21 | 22 | This faster version of the quadtree class is tuned for pygame's rect 23 | objects, or objects with a rect attribute. The return value will always 24 | be a set of a tuples that represent the items passed. In other words, 25 | you will not get back the objects that were passed, just a tuple that 26 | describes it. 27 | 28 | Items being stored in the tree must be a pygame.Rect or have have a 29 | .rect (pygame.Rect) attribute that is a pygame.Rect 30 | 31 | original code from https://pygame.org/wiki/QuadTree 32 | """ 33 | 34 | __slots__ = ["items", "cx", "cy", "nw", "sw", "ne", "se"] 35 | 36 | def __init__(self, items: Sequence, depth: int = 4, boundary=None) -> None: 37 | """Creates a quad-tree. 38 | 39 | Args: 40 | items: Sequence of items to check 41 | depth: The maximum recursion depth 42 | boundary: The bounding rectangle of all of the items in the quad-tree 43 | 44 | """ 45 | 46 | # The sub-quadrants are empty to start with. 47 | self.nw = self.ne = self.se = self.sw = None 48 | 49 | # Find this quadrant's centre. 50 | if boundary: 51 | boundary = Rect(boundary) 52 | else: 53 | # If there isn't a bounding rect, then calculate it from the items. 54 | boundary = Rect(items[0]).unionall(items[1:]) 55 | 56 | cx = self.cx = boundary.centerx 57 | cy = self.cy = boundary.centery 58 | 59 | # If we've reached the maximum depth then insert all items into this 60 | # quadrant. 61 | depth -= 1 62 | if depth == 0 or not items: 63 | self.items = items 64 | return 65 | 66 | self.items = [] 67 | nw_items = [] 68 | ne_items = [] 69 | se_items = [] 70 | sw_items = [] 71 | 72 | for item in items: 73 | # Which of the sub-quadrants does the item overlap? 74 | in_nw = item.left <= cx and item.top <= cy 75 | in_sw = item.left <= cx and item.bottom >= cy 76 | in_ne = item.right >= cx and item.top <= cy 77 | in_se = item.right >= cx and item.bottom >= cy 78 | 79 | # If it overlaps all 4 quadrants then insert it at the current 80 | # depth, otherwise append it to a list to be inserted under every 81 | # quadrant that it overlaps. 82 | if in_nw and in_ne and in_se and in_sw: 83 | self.items.append(item) 84 | else: 85 | in_nw and nw_items.append(item) 86 | in_ne and ne_items.append(item) 87 | in_se and se_items.append(item) 88 | in_sw and sw_items.append(item) 89 | 90 | # Create the sub-quadrants, recursively. 91 | if nw_items: 92 | self.nw = FastQuadTree( 93 | nw_items, depth, (boundary.left, boundary.top, cx, cy) 94 | ) 95 | if ne_items: 96 | self.ne = FastQuadTree( 97 | ne_items, depth, (cx, boundary.top, boundary.right, cy) 98 | ) 99 | if se_items: 100 | self.se = FastQuadTree( 101 | se_items, depth, (cx, cy, boundary.right, boundary.bottom) 102 | ) 103 | if sw_items: 104 | self.sw = FastQuadTree( 105 | sw_items, depth, (boundary.left, cy, cx, boundary.bottom) 106 | ) 107 | 108 | def __iter__(self): 109 | return itertools.chain(self.items, self.nw, self.ne, self.se, self.sw) 110 | 111 | def hit(self, rect: RectLike) -> set[tuple[int, int, int, int]]: 112 | """ 113 | Returns the items that overlap a bounding rectangle. 114 | 115 | Returns the set of all items in the quad-tree that overlap with a 116 | bounding rectangle. 117 | 118 | Args: 119 | rect: The bounding rectangle being tested 120 | 121 | """ 122 | # Find the hits at the current level. 123 | hits = {tuple(self.items[i]) for i in rect.collidelistall(self.items)} 124 | 125 | # Recursively check the lower quadrants. 126 | left = rect.left <= self.cx 127 | right = rect.right >= self.cx 128 | top = rect.top <= self.cy 129 | bottom = rect.bottom >= self.cy 130 | 131 | if left: 132 | if top and self.nw: 133 | hits |= self.nw.hit(rect) 134 | if bottom and self.sw: 135 | hits |= self.sw.hit(rect) 136 | if right: 137 | if top and self.ne: 138 | hits |= self.ne.hit(rect) 139 | if bottom and self.se: 140 | hits |= self.se.hit(rect) 141 | 142 | return hits 143 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | # pip install wheel twine 4 | # python3 setup.py sdist bdist_wheel 5 | # python3 -m twine upload --repository pypi dist/* 6 | from setuptools import setup 7 | 8 | setup() 9 | -------------------------------------------------------------------------------- /tests/pyscroll/test_isometric.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from pyscroll.isometric import vector2_to_iso, vector3_to_iso 3 | 4 | class IsometricFunctionsTest(unittest.TestCase): 5 | def test_vector3_to_iso(self): 6 | self.assertEqual(vector3_to_iso((1, 1, 0)), (0, 1)) 7 | self.assertEqual(vector3_to_iso((2, 1, 0)), (1, 1)) 8 | self.assertEqual(vector3_to_iso((1, 2, 0)), (-1, 1)) 9 | self.assertEqual(vector3_to_iso((1, 1, 1)), (0, 0)) 10 | self.assertEqual(vector3_to_iso((-1, -1, 0)), (0, -1)) 11 | self.assertEqual(vector3_to_iso((-2, -1, 0)), (-1, -2)) 12 | self.assertEqual(vector3_to_iso((-1, -2, 0)), (1, -2)) 13 | self.assertEqual(vector3_to_iso((-1, -1, -1)), (0, 0)) 14 | self.assertEqual(vector3_to_iso((0, 0, 0)), (0, 0)) 15 | self.assertEqual(vector3_to_iso((100, 100, 0)), (0, 100)) 16 | self.assertEqual(vector3_to_iso((200, 100, 0)), (100, 150)) 17 | self.assertEqual(vector3_to_iso((100, 200, 0)), (-100, 150)) 18 | self.assertEqual(vector3_to_iso((100, 100, 100)), (0, 0)) 19 | 20 | def test_vector2_to_iso(self): 21 | self.assertEqual(vector2_to_iso((1, 1)), (0, 1)) 22 | self.assertEqual(vector2_to_iso((2, 1)), (1, 1)) 23 | self.assertEqual(vector2_to_iso((1, 2)), (-1, 1)) 24 | self.assertEqual(vector2_to_iso((0, 0)), (0, 0)) 25 | self.assertEqual(vector2_to_iso((-1, -1)), (0, -1)) 26 | self.assertEqual(vector2_to_iso((-2, -1)), (-1, -2)) 27 | self.assertEqual(vector2_to_iso((-1, -2)), (1, -2)) 28 | self.assertEqual(vector2_to_iso((0, 0)), (0, 0)) 29 | self.assertEqual(vector2_to_iso((100, 100)), (0, 100)) 30 | self.assertEqual(vector2_to_iso((200, 100)), (100, 150)) 31 | self.assertEqual(vector2_to_iso((100, 200)), (-100, 150)) 32 | 33 | def test_vector3_to_iso_invalid_inputs(self): 34 | with self.assertRaises(ValueError): 35 | vector3_to_iso((1, 2)) 36 | 37 | def test_vector2_to_iso_invalid_inputs(self): 38 | with self.assertRaises(ValueError): 39 | vector2_to_iso((1, 2, 3)) 40 | -------------------------------------------------------------------------------- /tests/pyscroll/test_pyscroll.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from unittest import mock 3 | 4 | import pygame 5 | 6 | from pyscroll.data import PyscrollDataAdapter 7 | from pyscroll.orthographic import BufferedRenderer 8 | 9 | 10 | class DummyDataAdapter(PyscrollDataAdapter): 11 | tile_size = (32, 32) 12 | map_size = (32, 32) 13 | visible_tile_layers = [1] 14 | 15 | def get_animations(self): 16 | return list() 17 | 18 | def get_tile_image(self, *position): 19 | return position[0] * position[1] 20 | 21 | 22 | class DummyBufferer: 23 | _tile_view = pygame.Rect(2, 2, 2, 2) 24 | _clear_color = None 25 | _buffer = mock.Mock() 26 | _clear_surface = mock.Mock() 27 | data = DummyDataAdapter() 28 | 29 | 30 | class TestTileQueue(unittest.TestCase): 31 | def setUp(self) -> None: 32 | self.mock = DummyBufferer() 33 | self.queue = BufferedRenderer._queue_edge_tiles 34 | 35 | def verify_queue(self, expected: set[tuple[int, int]]) -> None: 36 | queue = {i[:2] for i in self.mock._tile_queue} 37 | self.assertEqual(queue, set(expected)) 38 | 39 | def test_queue_left(self) -> None: 40 | self.queue(self.mock, -1, 0) 41 | self.verify_queue({(2, 3), (2, 2)}) 42 | 43 | def test_queue_top(self) -> None: 44 | self.queue(self.mock, 0, -1) 45 | self.verify_queue({(2, 2), (3, 2)}) 46 | 47 | def test_queue_right(self) -> None: 48 | self.queue(self.mock, 1, 0) 49 | self.verify_queue({(3, 3), (3, 2)}) 50 | 51 | def test_queue_bottom(self) -> None: 52 | self.queue(self.mock, 0, 1) 53 | self.verify_queue({(2, 3), (3, 3)}) 54 | --------------------------------------------------------------------------------