├── MANIFEST.in
├── setup.cfg
├── apps
├── demo
│ ├── hero.png
│ ├── water.png
│ ├── 16x16-overworld.png
│ ├── tmw_desert_spacing.png
│ ├── 16x16-overworld.tsx
│ ├── stitched0.tmx
│ ├── stitched1.tmx
│ ├── stitched2.tmx
│ ├── stitched3.tmx
│ ├── stitched5.tmx
│ ├── stitched6.tmx
│ ├── stitched7.tmx
│ ├── stitched8.tmx
│ ├── stitched4.tmx
│ ├── translate.py
│ ├── desert.tmx
│ ├── stitched.world
│ ├── water.tsx
│ ├── animated.tmx
│ ├── demo-stitched.py
│ └── demo.py
└── tutorial
│ ├── data
│ ├── hero.png
│ ├── water.png
│ ├── revolution_snow.png
│ ├── revolution_town.png
│ ├── revolution_grasslands.png
│ ├── revolution_grasslands_alpha.png
│ ├── readme.md
│ ├── grasslands_small.tmx
│ ├── grasslands.tmx
│ └── water.tsx
│ ├── credits
│ └── quest.py
├── docs
├── _build
│ ├── html
│ │ ├── objects.inv
│ │ ├── _static
│ │ │ ├── up.png
│ │ │ ├── down.png
│ │ │ ├── file.png
│ │ │ ├── minus.png
│ │ │ ├── plus.png
│ │ │ ├── comment.png
│ │ │ ├── ajax-loader.gif
│ │ │ ├── up-pressed.png
│ │ │ ├── comment-close.png
│ │ │ ├── down-pressed.png
│ │ │ ├── comment-bright.png
│ │ │ ├── pygments.css
│ │ │ ├── default.css
│ │ │ ├── sidebar.js
│ │ │ ├── doctools.js
│ │ │ ├── basic.css
│ │ │ └── underscore.js
│ │ ├── .buildinfo
│ │ ├── _sources
│ │ │ ├── pyscroll.txt
│ │ │ └── index.txt
│ │ ├── _modules
│ │ │ └── index.html
│ │ ├── searchindex.js
│ │ ├── search.html
│ │ ├── py-modindex.html
│ │ ├── genindex.html
│ │ └── index.html
│ └── doctrees
│ │ ├── index.doctree
│ │ ├── pyscroll.doctree
│ │ └── environment.pickle
├── pyscroll.rst
├── index.rst
├── Makefile
├── make.bat
└── conf.py
├── setup.py
├── pyscroll
├── __init__.py
├── common.py
├── group.py
├── animation.py
├── quadtree.py
└── isometric.py
├── .gitignore
├── .github
└── workflows
│ └── python-app.yml
├── pyproject.toml
├── tests
└── pyscroll
│ ├── test_pyscroll.py
│ └── test_isometric.py
└── README.md
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | prune .github
2 | exclude .gitignore
3 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [metadata]
2 | description-file = README.md
3 |
--------------------------------------------------------------------------------
/apps/demo/hero.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitcraft/pyscroll/HEAD/apps/demo/hero.png
--------------------------------------------------------------------------------
/apps/demo/water.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitcraft/pyscroll/HEAD/apps/demo/water.png
--------------------------------------------------------------------------------
/apps/tutorial/data/hero.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitcraft/pyscroll/HEAD/apps/tutorial/data/hero.png
--------------------------------------------------------------------------------
/apps/demo/16x16-overworld.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitcraft/pyscroll/HEAD/apps/demo/16x16-overworld.png
--------------------------------------------------------------------------------
/apps/tutorial/data/water.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitcraft/pyscroll/HEAD/apps/tutorial/data/water.png
--------------------------------------------------------------------------------
/docs/_build/html/objects.inv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitcraft/pyscroll/HEAD/docs/_build/html/objects.inv
--------------------------------------------------------------------------------
/apps/demo/tmw_desert_spacing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitcraft/pyscroll/HEAD/apps/demo/tmw_desert_spacing.png
--------------------------------------------------------------------------------
/docs/_build/html/_static/up.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitcraft/pyscroll/HEAD/docs/_build/html/_static/up.png
--------------------------------------------------------------------------------
/docs/_build/doctrees/index.doctree:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitcraft/pyscroll/HEAD/docs/_build/doctrees/index.doctree
--------------------------------------------------------------------------------
/docs/_build/html/_static/down.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitcraft/pyscroll/HEAD/docs/_build/html/_static/down.png
--------------------------------------------------------------------------------
/docs/_build/html/_static/file.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitcraft/pyscroll/HEAD/docs/_build/html/_static/file.png
--------------------------------------------------------------------------------
/docs/_build/html/_static/minus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitcraft/pyscroll/HEAD/docs/_build/html/_static/minus.png
--------------------------------------------------------------------------------
/docs/_build/html/_static/plus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitcraft/pyscroll/HEAD/docs/_build/html/_static/plus.png
--------------------------------------------------------------------------------
/docs/_build/doctrees/pyscroll.doctree:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitcraft/pyscroll/HEAD/docs/_build/doctrees/pyscroll.doctree
--------------------------------------------------------------------------------
/docs/_build/html/_static/comment.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitcraft/pyscroll/HEAD/docs/_build/html/_static/comment.png
--------------------------------------------------------------------------------
/apps/tutorial/data/revolution_snow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitcraft/pyscroll/HEAD/apps/tutorial/data/revolution_snow.png
--------------------------------------------------------------------------------
/apps/tutorial/data/revolution_town.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitcraft/pyscroll/HEAD/apps/tutorial/data/revolution_town.png
--------------------------------------------------------------------------------
/docs/_build/doctrees/environment.pickle:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitcraft/pyscroll/HEAD/docs/_build/doctrees/environment.pickle
--------------------------------------------------------------------------------
/docs/_build/html/_static/ajax-loader.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitcraft/pyscroll/HEAD/docs/_build/html/_static/ajax-loader.gif
--------------------------------------------------------------------------------
/docs/_build/html/_static/up-pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitcraft/pyscroll/HEAD/docs/_build/html/_static/up-pressed.png
--------------------------------------------------------------------------------
/docs/_build/html/_static/comment-close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitcraft/pyscroll/HEAD/docs/_build/html/_static/comment-close.png
--------------------------------------------------------------------------------
/docs/_build/html/_static/down-pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitcraft/pyscroll/HEAD/docs/_build/html/_static/down-pressed.png
--------------------------------------------------------------------------------
/apps/tutorial/data/revolution_grasslands.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitcraft/pyscroll/HEAD/apps/tutorial/data/revolution_grasslands.png
--------------------------------------------------------------------------------
/docs/_build/html/_static/comment-bright.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitcraft/pyscroll/HEAD/docs/_build/html/_static/comment-bright.png
--------------------------------------------------------------------------------
/apps/tutorial/data/revolution_grasslands_alpha.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitcraft/pyscroll/HEAD/apps/tutorial/data/revolution_grasslands_alpha.png
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/apps/demo/16x16-overworld.tsx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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:
--------------------------------------------------------------------------------
/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:
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
73 |
74 |
75 |
87 |
91 |
92 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/_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 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
86 |
87 |
88 |
100 |
104 |
105 |
--------------------------------------------------------------------------------
/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/_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/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 |
56 |
57 |
83 |
84 |
85 |
86 |
87 |
88 |
105 |
106 |
107 |
119 |
123 |
124 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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; k 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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/_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 | $('').
141 | attr('href', '#' + this.id).
142 | attr('title', _('Permalink to this headline')).
143 | appendTo(this);
144 | });
145 | $('dt[id]').each(function() {
146 | $('').
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 | $('' + _('Hide Search Matches') + '
')
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/_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 |
79 |
80 |
C
81 |
95 |
96 |
D
97 |
121 |
122 |
F
123 |
137 |
138 |
G
139 |
157 |
158 |
P
159 |
185 |
186 |
R
187 |
201 |
202 |
S
203 |
221 |
222 |
T
223 |
241 |
242 |
U
243 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
299 |
300 |
301 |
313 |
317 |
318 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/docs/_build/html/_static/underscore.js:
--------------------------------------------------------------------------------
1 | // Underscore.js 1.3.1
2 | // (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
3 | // Underscore is freely distributable under the MIT license.
4 | // Portions of Underscore are inspired or borrowed from Prototype,
5 | // Oliver Steele's Functional, and John Resig's Micro-Templating.
6 | // For all details and documentation:
7 | // http://documentcloud.github.com/underscore
8 | (function(){function q(a,c,d){if(a===c)return a!==0||1/a==1/c;if(a==null||c==null)return a===c;if(a._chain)a=a._wrapped;if(c._chain)c=c._wrapped;if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return false;switch(e){case "[object String]":return a==String(c);case "[object Number]":return a!=+a?c!=+c:a==0?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source==
9 | c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if(typeof a!="object"||typeof c!="object")return false;for(var f=d.length;f--;)if(d[f]==a)return true;d.push(a);var f=0,g=true;if(e=="[object Array]"){if(f=a.length,g=f==c.length)for(;f--;)if(!(g=f in a==f in c&&q(a[f],c[f],d)))break}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return false;for(var h in a)if(b.has(a,h)&&(f++,!(g=b.has(c,h)&&q(a[h],c[h],d))))break;if(g){for(h in c)if(b.has(c,
10 | h)&&!f--)break;g=!f}}d.pop();return g}var r=this,G=r._,n={},k=Array.prototype,o=Object.prototype,i=k.slice,H=k.unshift,l=o.toString,I=o.hasOwnProperty,w=k.forEach,x=k.map,y=k.reduce,z=k.reduceRight,A=k.filter,B=k.every,C=k.some,p=k.indexOf,D=k.lastIndexOf,o=Array.isArray,J=Object.keys,s=Function.prototype.bind,b=function(a){return new m(a)};if(typeof exports!=="undefined"){if(typeof module!=="undefined"&&module.exports)exports=module.exports=b;exports._=b}else r._=b;b.VERSION="1.3.1";var j=b.each=
11 | b.forEach=function(a,c,d){if(a!=null)if(w&&a.forEach===w)a.forEach(c,d);else if(a.length===+a.length)for(var e=0,f=a.length;e2;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/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 |
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 |
171 |
172 |
173 |
Indices and tables
174 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
219 |
220 |
221 |
233 |
237 |
238 |
--------------------------------------------------------------------------------