Skip to content

Commit 9d8d736

Browse files
pushfoopvcraven
andauthored
Add Sprite & SpriteList programming guide pages (#1691)
* Add new Programming Guide index page to fix toctree issues * Add intro text to Programming Guide index page * Add Sprite & SpriteList programming guide page * Rename ref target for sprite examples * Link SpriteLists doc from Programming Guide index * Fix ommitted link in sprites index page * Remove extra newline after section heading * Remove exclamation point in subsection heading per review * Move minimal Sprite example into a separate file * Remove redundant wording about time to read a page * Fix formatting on sprite_minimal example * Phrasing & line length improvements in advanced SpriteList page * Reverse table entry ordering for better section flow * Use non-directory-style ref target for camera examples --------- Co-authored-by: Paul V Craven <paul@cravenfamily.com>
1 parent 1ca61fd commit 9d8d736

File tree

7 files changed

+371
-2
lines changed

7 files changed

+371
-2
lines changed

arcade/examples/sprite_minimal.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
"""
2+
Minimal Sprite Example
3+
4+
Draws a single sprite in the middle screen.
5+
6+
If Python and Arcade are installed, this example can be run from the command line with:
7+
python -m arcade.examples.sprite_minimal
8+
"""
9+
import arcade
10+
11+
12+
class WhiteSpriteCircleExample(arcade.Window):
13+
14+
def __init__(self):
15+
super().__init__(800, 600, "White SpriteCircle Example")
16+
self.sprites = None
17+
self.setup()
18+
19+
def setup(self):
20+
# 1. Create the SpriteList
21+
self.sprites = arcade.SpriteList()
22+
23+
# 2. Create & append your Sprite instance to the SpriteList
24+
self.circle = arcade.SpriteCircle(30, arcade.color.WHITE) # 30 pixel radius circle
25+
self.circle.position = self.width // 2, self.height // 2 # Put it in the middle
26+
self.sprites.append(self.circle) # Append the instance to the SpriteList
27+
28+
def on_draw(self):
29+
# 3. Call draw() on the SpriteList inside an on_draw() method
30+
self.sprites.draw()
31+
32+
33+
if __name__ == "__main__":
34+
game = WhiteSpriteCircleExample()
35+
game.run()

doc/example_code/how_to_examples/index.rst

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,8 @@ Faster Drawing with ShapeElementLists
105105

106106
:ref:`gradients`
107107

108-
.. _sprites:
108+
109+
.. _sprite_examples:
109110

110111
Sprites
111112
-------
@@ -403,7 +404,7 @@ Backgrounds
403404

404405
:ref:`background_parallax`
405406

406-
.. _examples_cameras:
407+
.. _camera_examples:
407408

408409
Cameras
409410
^^^^^^^

doc/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ The Python Arcade Library
7575

7676
programming_guide/install/index
7777
programming_guide/get_started
78+
programming_guide/sprites/index
7879
programming_guide/how_to_get_help
7980
programming_guide/directory_structure
8081
programming_guide/edge_artifacts/index

doc/programming_guide/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ external documentation where applicable.
2121

2222
install/index
2323
get_started
24+
sprites/index
2425
how_to_get_help
2526
directory_structure
2627
edge_artifacts/index
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
.. _pg_spritelists_advanced:
2+
3+
Advanced SpriteList Techniques
4+
------------------------------
5+
6+
This page provides overviews of advanced techniques. Runnable examples are
7+
not guaranteed, as the reader is expected to be able to put the work into
8+
implementing them.
9+
10+
Beginners should be careful of the following sections. Some of these techniques
11+
can slow down or crash your game if misused.
12+
13+
14+
.. _pg_spritelists_advanced_draw_order_and_sorting:
15+
16+
Draw Order & Sorting
17+
^^^^^^^^^^^^^^^^^^^^
18+
19+
In some cases, you can combine two features of SpriteList:
20+
21+
* By default, SpriteLists draw starting from their lowest index.
22+
* :py:class:`~arcade.SpriteList` has a :py:meth:`~arcade.SpriteList.sort`
23+
method nearly identical to :py:meth:`list.sort`.
24+
25+
26+
First, Consider Alternatives
27+
""""""""""""""""""""""""""""
28+
29+
Sorting in Python is a slow, CPU-bound function. Consider the following
30+
techniques to eliminate or minimize this cost:
31+
32+
* Use multiple sprite lists or :py:class:`arcade.Scene` to
33+
achieve layering
34+
* Chunk your game world into smaller regions with sprite lists
35+
for each, and only sort when something inside moves or changes
36+
* Use the :py:attr:`Sprite.depth <arcade.BasicSprite.depth>` attribute
37+
with :ref:`shaders <tutorials_shaders>` to sort on the GPU
38+
39+
For a conceptual overview of chunks as used in a commercial 2D game, please
40+
see the following:
41+
42+
* `Chunks in Factorio <https://wiki.factorio.com/Map_structure#Chunk>`_
43+
44+
45+
Sorting SpriteLists
46+
"""""""""""""""""""
47+
48+
Although the alternative listed above are often better, sorting sprite lists to
49+
control draw order can still be useful.
50+
51+
Like Python's built-in :py:meth:`list.sort`, you can pass a
52+
`callable object <https://docs.python.org/3/library/functions.html#callable>`_
53+
via the key argument to specify how to sort, along with an optional ``reverse``
54+
keyword to reverse the direction of sorting.
55+
56+
Here's an example of how you could use sorting to quickly create an
57+
inefficient prototype:
58+
59+
.. code:: python
60+
61+
62+
import random
63+
import arcade
64+
65+
66+
# Warning: the bottom property is extra slow compared to other attributes!
67+
def bottom_edge_as_sort_key(sprite):
68+
return sprite.bottom
69+
70+
71+
class InefficientTopDownGame(arcade.Window):
72+
"""
73+
Uses sorting to allow the player to move in front of & behind shrubs
74+
75+
For non-prototyping purposes, other approaches will be better.
76+
"""
77+
78+
def __init__(self, num_shrubs=50):
79+
super().__init__(800, 600, "Inefficient Top-Down Game")
80+
81+
self.background_color = arcade.color.SAND
82+
self.shrubs = arcade.SpriteList()
83+
self.drawable = arcade.SpriteList()
84+
85+
# Randomly place pale green shrubs around the screen
86+
for i in range(num_shrubs):
87+
shrub = arcade.SpriteSolidColor(20, 40, color=arcade.color.BUD_GREEN)
88+
shrub.position = random.randrange(self.width), random.randrange(self.height)
89+
self.shrubs.append(shrub)
90+
self.drawable.append(shrub)
91+
92+
self.player = arcade.SpriteSolidColor(16, 30, color=arcade.color.RED)
93+
self.drawable.append(self.player)
94+
95+
def on_mouse_motion(self, x, y, dx, dy):
96+
# Update the player position
97+
self.player.position = x, y
98+
# Sort the sprites so the highest on the screen draw first
99+
self.drawable.sort(key=bottom_edge_as_sort_key, reverse=True)
100+
101+
def on_draw(self):
102+
self.clear()
103+
self.drawable.draw()
104+
105+
106+
game = InefficientTopDownGame()
107+
game.run()
108+
109+
110+
.. _pg_spritelist_advanced_texture_atlases:
111+
112+
Custom Texture Atlases
113+
^^^^^^^^^^^^^^^^^^^^^^
114+
115+
A :py:class:`~arcade.TextureAtlas` represents :py:class:`~arcade.Texture`
116+
data packed side-by-side in video memory. As textures are added, the atlas
117+
grows to fit them all into the same portion of your GPU's memory.
118+
119+
By default, each :py:class:`~arcade.SpriteList` uses the same default
120+
atlas. Use the ``atlas`` keyword argument to specify a custom atlas
121+
for an instance.
122+
123+
This is especially useful to prevent problems when using large or oddly
124+
shaped textures.
125+
126+
Please see the following for more information:
127+
128+
* :ref:`pg_textureatlas_custom_atlas`
129+
* The :py:class:`~arcade.TextureAtlas` API documentation
130+
131+
132+
.. _pg_spritelist_advanced_lazy_spritelists:
133+
134+
Lazy SpriteLists
135+
^^^^^^^^^^^^^^^^
136+
137+
You can delay creating the OpenGL resources for a
138+
:py:class:`~arcade.SpriteList` by passing ``lazy=True`` on creation:
139+
140+
.. code:: python
141+
142+
sprite_list = SpriteList(lazy=True)
143+
144+
The SpriteList won't create the OpenGL resources until forced to
145+
by one of the following:
146+
147+
1. The first :py:meth:`SpriteList.draw() <arcade.SpriteList.draw>` call on it
148+
2. :py:meth:`SpriteList.initialize() <arcade.SpriteList.initialize>`
149+
3. GPU-backed collisions, if enabled
150+
151+
This behavior is most useful in the following cases:
152+
153+
.. list-table::
154+
:header-rows: 1
155+
156+
* - Case
157+
- Primary Purpose
158+
159+
* - Creating SpriteLists before a Window
160+
- CPU-only `unit tests <https://docs.python.org/3/library/unittest.html>`_ which
161+
never draw
162+
163+
* - Parallelized SpriteList creation
164+
- Faster loading & world generation via :py:mod:`threading`
165+
or :py:mod:`subprocess` & :py:mod:`pickle`
166+
167+
168+
.. _pg_spritelist_advanced_parallel_loading:
169+
170+
Parallelized Loading
171+
""""""""""""""""""""
172+
173+
To increase loading speed & reduce stutters during gameplay, you can
174+
run pre-gameplay tasks in parallel, such as pre-generating maps
175+
or pre-loading assets from disk into RAM.
176+
177+
178+
.. warning:: Only the main thread is allowed to access OpenGL!
179+
180+
Attempting to access OpenGL from non-main threads will
181+
raise an OpenGL Error!
182+
183+
To safely implement parallel loading, you will want to use the following
184+
general approach before allowing gameplay to begin:
185+
186+
1. Pass ``lazy=True`` when creating :py:class:`~arcade.SpriteList` instances
187+
in your loading code as described above
188+
2. Sync the SpriteList data back to the main thread or process once loading
189+
is finished
190+
3. Inside the main thread, call
191+
:py:meth:`Spritelist.initialize() <arcade.SpriteList.initialize>` on each
192+
sprite list once it's ready to allocate GPU resources
193+
194+
195+
Very advanced users can use :py:mod:`subprocess` to create SpriteLists
196+
inside another process and the :py:mod:`pickle` module to help pass data
197+
back to the main process.
198+
199+
Please see the following for additional information:
200+
201+
* :ref:`Arcade's OpenGL notes <open_gl_notes>` for arcade-specific
202+
threading considerations
203+
* Python's :py:mod:`threading` documentation
204+
* Python's :py:mod:`subprocess` and :py:mod:`pickle` documentation
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
.. _sprites:
2+
3+
Drawing & Using Sprites
4+
=======================
5+
6+
Most games built with Arcade will use sprites and sprite lists to draw image
7+
data. This section of the programming guide will help you achieve that by
8+
covering:
9+
10+
* What sprites & sprite lists are
11+
* The essentials of how to use them
12+
* How to get started with images
13+
* Non-drawing features such as collisions
14+
* Overviews of various advanced techniques
15+
16+
Beginners should start by reading & following :ref:`pg_spritelists` page
17+
(~10 minute read). If you get stuck, see :ref:`how-to-get-help`.
18+
19+
Contents
20+
--------
21+
22+
.. toctree::
23+
:maxdepth: 1
24+
25+
spritelists
26+
advanced
27+
28+
29+
I'm Impatient!
30+
--------------
31+
32+
Beginners should at least skim :ref:`pg_spritelists` (~10 minute read),
33+
but you can skip to the tutorials and full example code if you'd like:
34+
35+
* :ref:`pg_spritelists_minimal_sprite_drawing`
36+
* :ref:`Arcade's Sprite Examples <sprites>`
37+
* :ref:`Arcade's Simple Platformer Tutorial <platformer_tutorial>`

0 commit comments

Comments
 (0)