├── AUTHORS.txt ├── CHANGES.txt ├── LICENSE.txt ├── MANIFEST.in ├── README ├── README.md ├── README.txt ├── docs ├── Makefile ├── basic_demo_screenshot.png ├── basics.rst ├── bolt_strike_0001.png ├── bolt_strike_0002.png ├── bolt_strike_0003.png ├── bolt_strike_0004.png ├── bolt_strike_0005.png ├── bolt_strike_0006.png ├── bolt_strike_0007.png ├── bolt_strike_0008.png ├── bolt_strike_0009.png ├── bolt_strike_0010.png ├── conf.py ├── index.rst ├── install.rst ├── make.bat ├── pyganim_pig.jpg ├── spritesheets.rst ├── terrex_0.png ├── transformations.rst └── unittests.rst ├── examples ├── banana.gif ├── basic_demo.py ├── control_panel_demo.py ├── effects_demo.py ├── gameimages │ ├── crono_back.gif │ ├── crono_back_run.000.gif │ ├── crono_back_run.001.gif │ ├── crono_back_run.002.gif │ ├── crono_back_run.003.gif │ ├── crono_back_run.004.gif │ ├── crono_back_run.005.gif │ ├── crono_back_walk.000.gif │ ├── crono_back_walk.001.gif │ ├── crono_back_walk.002.gif │ ├── crono_back_walk.003.gif │ ├── crono_back_walk.004.gif │ ├── crono_back_walk.005.gif │ ├── crono_front.gif │ ├── crono_front_run.000.gif │ ├── crono_front_run.001.gif │ ├── crono_front_run.002.gif │ ├── crono_front_run.003.gif │ ├── crono_front_run.004.gif │ ├── crono_front_run.005.gif │ ├── crono_front_walk.000.gif │ ├── crono_front_walk.001.gif │ ├── crono_front_walk.002.gif │ ├── crono_front_walk.003.gif │ ├── crono_front_walk.004.gif │ ├── crono_front_walk.005.gif │ ├── crono_left.gif │ ├── crono_left_run.000.gif │ ├── crono_left_run.001.gif │ ├── crono_left_run.002.gif │ ├── crono_left_run.003.gif │ ├── crono_left_run.004.gif │ ├── crono_left_run.005.gif │ ├── crono_left_walk.000.gif │ ├── crono_left_walk.001.gif │ ├── crono_left_walk.002.gif │ ├── crono_left_walk.003.gif │ ├── crono_left_walk.004.gif │ ├── crono_left_walk.005.gif │ ├── crono_sleep.000.gif │ └── crono_sleep.001.gif ├── gif_demo.py ├── simulation_time_demo.py ├── sprite_sheet_demo.py ├── terrex_0.png ├── testimages │ ├── alsweigart1.jpg │ ├── alsweigart2.jpg │ ├── bolt_strike_0001.png │ ├── bolt_strike_0002.png │ ├── bolt_strike_0003.png │ ├── bolt_strike_0004.png │ ├── bolt_strike_0005.png │ ├── bolt_strike_0006.png │ ├── bolt_strike_0007.png │ ├── bolt_strike_0008.png │ ├── bolt_strike_0009.png │ ├── bolt_strike_0010.png │ ├── flame_a_0001.png │ ├── flame_a_0002.png │ ├── flame_a_0003.png │ ├── flame_a_0004.png │ ├── flame_a_0005.png │ ├── flame_a_0006.png │ ├── smoke_puff_0001.png │ ├── smoke_puff_0002.png │ ├── smoke_puff_0003.png │ ├── smoke_puff_0004.png │ ├── smoke_puff_0005.png │ ├── smoke_puff_0006.png │ ├── smoke_puff_0007.png │ ├── smoke_puff_0008.png │ ├── smoke_puff_0009.png │ └── smoke_puff_0010.png └── walking_demo.py ├── pyganim └── __init__.py ├── setup.py └── tests ├── banana.gif ├── basicTests.py ├── bolt1.png ├── bolt10.png ├── bolt2.png ├── bolt3.png ├── bolt4.png ├── bolt5.png ├── bolt6.png ├── bolt7.png ├── bolt8.png ├── bolt9.png ├── smoke0.png ├── smoke1.png ├── smoke2.png ├── smoke3.png ├── smoke4.png ├── smoke5.png ├── smoke6.png ├── smoke7.png ├── smoke8.png ├── smoke9.png └── smokeSpritesheet.png /AUTHORS.txt: -------------------------------------------------------------------------------- 1 | Here is an inevitably incomplete list of MUCH-APPRECIATED CONTRIBUTORS -- 2 | people who have submitted patches, reported bugs, added translations, helped 3 | answer newbie questions, and generally made Pyganim that much better: 4 | 5 | Al Sweigart (al@inventwithpython.com) 6 | Chad Estioco https://github.com/skytreader -------------------------------------------------------------------------------- /CHANGES.txt: -------------------------------------------------------------------------------- 1 | v0.9.2, 2015/08/03 -- Added sprite sheet loading feature 2 | v0.9.0, 2014/08/03 -- Initial Pip release. -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Al Sweigart 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of the {organization} nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.md 2 | recursive-include pyganim *.py 3 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Pyganim - A Sprite Animation module for Pygame 2 | 3 | http://inventwithpython.com/pyganim/ 4 | 5 | Pyganim (pronounced like "pig" and "animation") is a Python module for Pygame that makes it easy to add sprite animations to your Pygame game programs. Pyganim works with Python 2 and Python 3. 6 | 7 | The mascot of Pyganim is a red vitruvian pig. 8 | 9 | Pyganim was written by Al Sweigart and released under a "Simplified BSD" license. Contact Al with any questions/bug reports: al@inventwithpython.com 10 | 11 | 12 | Reference: 13 | http://inventwithpython.com/pyganim/reference.html 14 | 15 | Tutorial: 16 | http://inventwithpython.com/pyganim/tutorial.html -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Pyganim 2 | ======= 3 | 4 | http://inventwithpython.com/pyganim/ 5 | 6 | Pyganim (pronounced like "pig" and "animation") is a Python module for Pygame that makes it easy to add sprite animations to your Pygame game programs. Pyganim works with Python 2 and Python 3. 7 | 8 | The mascot of Pyganim is a red vitruvian pig. 9 | 10 | Pyganim was written by Al Sweigart and released under a "Simplified BSD" license. Contact Al with any questions/bug reports: al@inventwithpython.com 11 | 12 | 13 | Reference: 14 | http://inventwithpython.com/pyganim/reference.html 15 | 16 | Tutorial: 17 | http://inventwithpython.com/pyganim/tutorial.html 18 | 19 | Support 20 | ------- 21 | 22 | If you find this project helpful and would like to support its development, [consider donating to its creator on Patreon](https://www.patreon.com/AlSweigart). 23 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | Pyganim - A Sprite Animation module for Pygame 2 | 3 | http://inventwithpython.com/pyganim/ 4 | 5 | Pyganim (pronounced like "pig" and "animation") is a Python module for Pygame that makes it easy to add sprite animations to your Pygame game programs. Pyganim works with Python 2 and Python 3. 6 | 7 | The mascot of Pyganim is a red vitruvian pig. 8 | 9 | Pyganim was written by Al Sweigart and released under a "Simplified BSD" license. Contact Al with any questions/bug reports: al@inventwithpython.com 10 | 11 | 12 | Reference: 13 | http://inventwithpython.com/pyganim/reference.html 14 | 15 | Tutorial: 16 | http://inventwithpython.com/pyganim/tutorial.html -------------------------------------------------------------------------------- /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/Pyganim.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Pyganim.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/Pyganim" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Pyganim" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | -------------------------------------------------------------------------------- /docs/basic_demo_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/docs/basic_demo_screenshot.png -------------------------------------------------------------------------------- /docs/basics.rst: -------------------------------------------------------------------------------- 1 | .. default-role:: code 2 | ============== 3 | Pyganim Basics 4 | ============== 5 | 6 | Quick Start 7 | =========== 8 | 9 | First, create an animation object by calling the PygAnimation constructor and passing it a list of tuples. These tuples represent a single "frame" of the animation. The tuples have an image's filename and the number of milliseconds it is displayed before displaying the next frame: 10 | 11 | .. code:: python 12 | 13 | >>> import pyganim 14 | >>> animObj = pyganim.PygAnimation([('frame1.png', 200), ('frame2.png', 200), ('frame3.png', 600)]) 15 | >>> animObj.play() 16 | 17 | Then, during the program's loop when it must draw to the Surface object, call the `blit()` method and pass it the Surface object to draw on along with the XY coordinates: 18 | 19 | animObj.blit(windowSurface, (x, y)) 20 | 21 | The correct frame will be drawn to the Surface depending on the system time when `blit()` was called. 22 | 23 | Example Usage 24 | ============= 25 | 26 | Here's a small example program, given the following lightning bolt images: 27 | 28 | .. image:: bolt_strike_0001.png 29 | 30 | .. image:: bolt_strike_0002.png 31 | 32 | .. image:: bolt_strike_0003.png 33 | 34 | .. image:: bolt_strike_0004.png 35 | 36 | .. image:: bolt_strike_0005.png 37 | 38 | .. image:: bolt_strike_0006.png 39 | 40 | .. image:: bolt_strike_0007.png 41 | 42 | .. image:: bolt_strike_0008.png 43 | 44 | .. image:: bolt_strike_0009.png 45 | 46 | .. image:: bolt_strike_0010.png 47 | 48 | The source code is: 49 | 50 | .. code:: python 51 | 52 | import pygame 53 | from pygame.locals import * 54 | import pyganim 55 | 56 | pygame.init() 57 | windowSurface = pygame.display.set_mode((320, 240), 0, 32) 58 | pygame.display.set_caption('Pyganim Basic Demo') 59 | 60 | boltAnim = pyganim.PygAnimation([('bolt_strike_0001.png', 100), 61 | ('bolt_strike_0002.png', 100), 62 | ('bolt_strike_0003.png', 100), 63 | ('bolt_strike_0004.png', 100), 64 | ('bolt_strike_0005.png', 100), 65 | ('bolt_strike_0006.png', 100), 66 | ('bolt_strike_0007.png', 100), 67 | ('bolt_strike_0008.png', 100), 68 | ('bolt_strike_0009.png', 100), 69 | ('bolt_strike_0010.png', 100)]) 70 | boltAnim.play() 71 | 72 | while True: 73 | for event in pygame.event.get(): 74 | if event.type == QUIT: 75 | pygame.quit() 76 | sys.exit() 77 | 78 | windowSurface.fill((100, 50, 50)) 79 | boltAnim.blit(windowSurface, (100, 50)) 80 | pygame.display.update() 81 | 82 | .. image:: basic_demo_screenshot.png 83 | 84 | Other examples exist in the `/examples` folder of the repo at https://github.com/asweigart/pyganim 85 | 86 | Loading Animated Gifs 87 | ===================== 88 | 89 | To create a PygAnimation object from an animated gif, pass the gif's filename to the PygAnimation constructor: 90 | 91 | .. code:: python 92 | 93 | animObj = pyganim.PygAnimation('banana.gif') 94 | 95 | The durations for each frame will match those in the gif. 96 | 97 | Drawing the PygAnimation Object 98 | =============================== 99 | 100 | To draw the PygAnimation object to a `pygame.Surface`, first call the `play()` call the `blit()` method and pass it the Surface object and a tuple of XY coordinates for where on the Surface the animation object will be drawn. Depending on the system time that `play()` and `blit()` were called, the appropriate frame will be drawn. 101 | 102 | ================= 103 | Play, Pause, Stop 104 | ================= 105 | 106 | `PygAnimation` objects are always in one of the three possible states: 107 | 108 | * Playing 109 | * Paused 110 | * Stopped 111 | 112 | These objects also have `play()`, `pause()`, and `stop()` methods. When the `PygAnimation` object is first created, it is in the stopped state. Calling `blit()` on a stopped `PygAnimation` object -------------------------------------------------------------------------------- /docs/bolt_strike_0001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/docs/bolt_strike_0001.png -------------------------------------------------------------------------------- /docs/bolt_strike_0002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/docs/bolt_strike_0002.png -------------------------------------------------------------------------------- /docs/bolt_strike_0003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/docs/bolt_strike_0003.png -------------------------------------------------------------------------------- /docs/bolt_strike_0004.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/docs/bolt_strike_0004.png -------------------------------------------------------------------------------- /docs/bolt_strike_0005.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/docs/bolt_strike_0005.png -------------------------------------------------------------------------------- /docs/bolt_strike_0006.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/docs/bolt_strike_0006.png -------------------------------------------------------------------------------- /docs/bolt_strike_0007.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/docs/bolt_strike_0007.png -------------------------------------------------------------------------------- /docs/bolt_strike_0008.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/docs/bolt_strike_0008.png -------------------------------------------------------------------------------- /docs/bolt_strike_0009.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/docs/bolt_strike_0009.png -------------------------------------------------------------------------------- /docs/bolt_strike_0010.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/docs/bolt_strike_0010.png -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Pyganim documentation build configuration file, created by 5 | # sphinx-quickstart on Sun Aug 3 14:57:25 2014. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | import sys 17 | import os 18 | 19 | # If extensions (or modules to document with autodoc) are in another directory, 20 | # add these directories to sys.path here. If the directory is relative to the 21 | # documentation root, use os.path.abspath to make it absolute, like shown here. 22 | #sys.path.insert(0, os.path.abspath('.')) 23 | 24 | # -- General configuration ------------------------------------------------ 25 | 26 | # If your documentation needs a minimal Sphinx version, state it here. 27 | #needs_sphinx = '1.0' 28 | 29 | # Add any Sphinx extension module names here, as strings. They can be 30 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 31 | # ones. 32 | extensions = [] 33 | 34 | # Add any paths that contain templates here, relative to this directory. 35 | templates_path = ['_templates'] 36 | 37 | # The suffix of source filenames. 38 | source_suffix = '.rst' 39 | 40 | # The encoding of source files. 41 | #source_encoding = 'utf-8-sig' 42 | 43 | # The master toctree document. 44 | master_doc = 'index' 45 | 46 | # General information about the project. 47 | project = 'Pyganim' 48 | copyright = '2014, Al Sweigart' 49 | 50 | # The version info for the project you're documenting, acts as replacement for 51 | # |version| and |release|, also used in various other places throughout the 52 | # built documents. 53 | # 54 | # The short X.Y version. 55 | version = '0.9.0' 56 | # The full version, including alpha/beta/rc tags. 57 | release = '0.9.0' 58 | 59 | # The language for content autogenerated by Sphinx. Refer to documentation 60 | # for a list of supported languages. 61 | #language = None 62 | 63 | # There are two options for replacing |today|: either, you set today to some 64 | # non-false value, then it is used: 65 | #today = '' 66 | # Else, today_fmt is used as the format for a strftime call. 67 | #today_fmt = '%B %d, %Y' 68 | 69 | # List of patterns, relative to source directory, that match files and 70 | # directories to ignore when looking for source files. 71 | exclude_patterns = ['_build'] 72 | 73 | # The reST default role (used for this markup: `text`) to use for all 74 | # documents. 75 | #default_role = None 76 | 77 | # If true, '()' will be appended to :func: etc. cross-reference text. 78 | #add_function_parentheses = True 79 | 80 | # If true, the current module name will be prepended to all description 81 | # unit titles (such as .. function::). 82 | #add_module_names = True 83 | 84 | # If true, sectionauthor and moduleauthor directives will be shown in the 85 | # output. They are ignored by default. 86 | #show_authors = False 87 | 88 | # The name of the Pygments (syntax highlighting) style to use. 89 | pygments_style = 'sphinx' 90 | 91 | # A list of ignored prefixes for module index sorting. 92 | #modindex_common_prefix = [] 93 | 94 | # If true, keep warnings as "system message" paragraphs in the built documents. 95 | #keep_warnings = False 96 | 97 | 98 | # -- Options for HTML output ---------------------------------------------- 99 | 100 | # The theme to use for HTML and HTML Help pages. See the documentation for 101 | # a list of builtin themes. 102 | html_theme = 'default' 103 | 104 | # Theme options are theme-specific and customize the look and feel of a theme 105 | # further. For a list of options available for each theme, see the 106 | # documentation. 107 | #html_theme_options = {} 108 | 109 | # Add any paths that contain custom themes here, relative to this directory. 110 | #html_theme_path = [] 111 | 112 | # The name for this set of Sphinx documents. If None, it defaults to 113 | # " v documentation". 114 | #html_title = None 115 | 116 | # A shorter title for the navigation bar. Default is the same as html_title. 117 | #html_short_title = None 118 | 119 | # The name of an image file (relative to this directory) to place at the top 120 | # of the sidebar. 121 | #html_logo = None 122 | 123 | # The name of an image file (within the static path) to use as favicon of the 124 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 125 | # pixels large. 126 | #html_favicon = None 127 | 128 | # Add any paths that contain custom static files (such as style sheets) here, 129 | # relative to this directory. They are copied after the builtin static files, 130 | # so a file named "default.css" will overwrite the builtin "default.css". 131 | html_static_path = ['_static'] 132 | 133 | # Add any extra paths that contain custom files (such as robots.txt or 134 | # .htaccess) here, relative to this directory. These files are copied 135 | # directly to the root of the documentation. 136 | #html_extra_path = [] 137 | 138 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 139 | # using the given strftime format. 140 | #html_last_updated_fmt = '%b %d, %Y' 141 | 142 | # If true, SmartyPants will be used to convert quotes and dashes to 143 | # typographically correct entities. 144 | #html_use_smartypants = True 145 | 146 | # Custom sidebar templates, maps document names to template names. 147 | #html_sidebars = {} 148 | 149 | # Additional templates that should be rendered to pages, maps page names to 150 | # template names. 151 | #html_additional_pages = {} 152 | 153 | # If false, no module index is generated. 154 | #html_domain_indices = True 155 | 156 | # If false, no index is generated. 157 | #html_use_index = True 158 | 159 | # If true, the index is split into individual pages for each letter. 160 | #html_split_index = False 161 | 162 | # If true, links to the reST sources are added to the pages. 163 | #html_show_sourcelink = True 164 | 165 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 166 | #html_show_sphinx = True 167 | 168 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 169 | #html_show_copyright = True 170 | 171 | # If true, an OpenSearch description file will be output, and all pages will 172 | # contain a tag referring to it. The value of this option must be the 173 | # base URL from which the finished HTML is served. 174 | #html_use_opensearch = '' 175 | 176 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 177 | #html_file_suffix = None 178 | 179 | # Output file base name for HTML help builder. 180 | htmlhelp_basename = 'Pyganimdoc' 181 | 182 | 183 | # -- Options for LaTeX output --------------------------------------------- 184 | 185 | latex_elements = { 186 | # The paper size ('letterpaper' or 'a4paper'). 187 | #'papersize': 'letterpaper', 188 | 189 | # The font size ('10pt', '11pt' or '12pt'). 190 | #'pointsize': '10pt', 191 | 192 | # Additional stuff for the LaTeX preamble. 193 | #'preamble': '', 194 | } 195 | 196 | # Grouping the document tree into LaTeX files. List of tuples 197 | # (source start file, target name, title, 198 | # author, documentclass [howto, manual, or own class]). 199 | latex_documents = [ 200 | ('index', 'Pyganim.tex', 'Pyganim Documentation', 201 | 'Al Sweigart', '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 = [ 230 | ('index', 'pyganim', 'Pyganim Documentation', 231 | ['Al Sweigart'], 1) 232 | ] 233 | 234 | # If true, show URL addresses after external links. 235 | #man_show_urls = False 236 | 237 | 238 | # -- Options for Texinfo output ------------------------------------------- 239 | 240 | # Grouping the document tree into Texinfo files. List of tuples 241 | # (source start file, target name, title, author, 242 | # dir menu entry, description, category) 243 | texinfo_documents = [ 244 | ('index', 'Pyganim', 'Pyganim Documentation', 245 | 'Al Sweigart', 'Pyganim', 'One line description of project.', 246 | 'Miscellaneous'), 247 | ] 248 | 249 | # Documents to append as an appendix to all manuals. 250 | #texinfo_appendices = [] 251 | 252 | # If false, no module index is generated. 253 | #texinfo_domain_indices = True 254 | 255 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 256 | #texinfo_show_urls = 'footnote' 257 | 258 | # If true, do not generate a @detailmenu in the "Top" node's menu. 259 | #texinfo_no_detailmenu = False 260 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. Pyganim documentation master file, created by 2 | sphinx-quickstart on Sun Aug 3 14:57:25 2014. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to Pyganim's documentation! 7 | =================================== 8 | 9 | Contents: 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | install.rst 15 | basics.rst 16 | spritesheets.rst 17 | transformations.rst 18 | unittests.rst 19 | 20 | 21 | 22 | Indices and tables 23 | ================== 24 | 25 | * :ref:`genindex` 26 | * :ref:`modindex` 27 | * :ref:`search` 28 | 29 | -------------------------------------------------------------------------------- /docs/install.rst: -------------------------------------------------------------------------------- 1 | .. default-role:: code 2 | ============ 3 | Installation 4 | ============ 5 | 6 | Background Information 7 | ====================== 8 | 9 | Pyganim (pronounced like "pig" and "animation") is a Python module for Pygame that makes it easy to add sprite animations to your Pygame programs. Pyganim works with Python 2 and Python 3. 10 | 11 | The mascot of Pyganim is a red vitruvian pig. 12 | 13 | .. image:: pyganim_pig.jpg 14 | 15 | Pyganim was written by Al Sweigart and released under a "Simplified BSD" license. Contact Al with any questions/bug reports: al@inventwithpython.com 16 | 17 | This documentation can be found at https://pyganim.readthedocs.org 18 | 19 | Pyganim requires Pygame to run, and also requires PIL or Pillow to use the animated GIF loading feature. 20 | 21 | Pyganim runs on Python 2.5, 2.6, 2.7, 3.1, 3.2, 3.3, 3.4. 22 | 23 | Currently there is no Pillow module for Python 3.1, so the animated GIF loading does not work on that version. There is no Pygame version currently (Aug 2015) available for Python 3.5. 24 | 25 | Installation 26 | ============ 27 | 28 | Pyganim can be installed using pip by running: 29 | 30 | pip install pyganim 31 | 32 | The PyPI entry is at https://pypi.python.org/pypi/Pyganim 33 | 34 | To test if the installation worked, run `import pyganim` from the interactive shell. Pygame (and, optionally, PIL or Pillow) will need to be installed separately to load animated gifs. -------------------------------------------------------------------------------- /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\Pyganim.qhcp 119 | echo.To view the help file: 120 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Pyganim.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/pyganim_pig.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/docs/pyganim_pig.jpg -------------------------------------------------------------------------------- /docs/spritesheets.rst: -------------------------------------------------------------------------------- 1 | .. default-role:: code 2 | ========================== 3 | Loading from Sprite Sheets 4 | ========================== 5 | 6 | Sprite animations commonly come in "sprite sheet" images, such as this one: 7 | 8 | .. image:: terrex_0.png 9 | 10 | Sprites sheets can be loaded into a PygAnimation object without having to slice the sheet up into individual image files. The sprite sheet's filename is passed to the `getImagesFromSpriteSheet()`, which returns a list of `pygame.Surface` objects. 11 | 12 | All the individual images must be the same size. There are three ways to specify how to get the individual images from the sprite sheet: 13 | 14 | * Two integers can be passed for the `width` and `height` parameters for the size of the individual cells. The order of the images in the returned list start at the top left, go right across the row, and then to the left side of the next row. 15 | 16 | * Two integers can be passed for the `rows` and `cols` parameters for the number of rows and columns of images. The width and height are automatically calculated from the sprite sheet size. The order of the images in the returned list start at the top left, go right across the row, and then to the left side of the next row. 17 | 18 | * A list of `(left, top, width, height)` tuples passed for the `rects` parameter for each image from the sprite sheet. The order of the images in the returned list are the same as the `rects` tuples. 19 | 20 | Note that the return value from `getImagesFromSpriteSheet()` is just a list of `pygame.Surface` objects, but the `PygAnimation()` constructor requires a list of tuples: a `pygame.Surface` object and the duration of that frame in milliseconds. The built-in `zip()` function is useful for this: 21 | 22 | .. code:: python 23 | 24 | >>> import pyganim 25 | >>> images = pyganim.getImagesFromSpriteSheet(rows=1, cols=3) 26 | >>> frames = list(zip(images, [200, 200, 600])) 27 | >>> animObj = pyganim.PygAnimation(frames) 28 | >>> animObj.play() 29 | 30 | Note that in Python 3, `zip()` returns a "zip object" which must be converted into a list for `PygAnimation()`. 31 | 32 | See the `examples/sprite_sheet_demo.py` program for an example of loading from a sprite sheet. -------------------------------------------------------------------------------- /docs/terrex_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/docs/terrex_0.png -------------------------------------------------------------------------------- /docs/transformations.rst: -------------------------------------------------------------------------------- 1 | .. default-role:: code 2 | =============== 3 | Transformations 4 | =============== 5 | 6 | TODO 7 | 8 | See, flip(), scale(), rotate(), rotozoom(), scale2x(), smoothscale(), convert(), convert_alpha(). When called on the PygAnimation object, they are applied to all the pygame.Surface objects in the animation. They do the same thing as the pygame.Surface methods of the same names. -------------------------------------------------------------------------------- /docs/unittests.rst: -------------------------------------------------------------------------------- 1 | .. default-role:: code 2 | ========== 3 | Unit Tests 4 | ========== 5 | 6 | The unit tests under /tests can be run from Python 2 or 3. In that folder are several test image files needed to run the tests (bolt1.png to bolt10.png, etc.) 7 | 8 | > python basicTests.py 9 | -------------------------------------------------------------------------------- /examples/banana.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/banana.gif -------------------------------------------------------------------------------- /examples/basic_demo.py: -------------------------------------------------------------------------------- 1 | # test1_pyganim.py - A very very very basic pyganim test program. 2 | # 3 | # This program just runs a single animation. It shows you what you need to do to use Pyganim. Basically: 4 | # 1) Import the pyganim module 5 | # 2) Create a pyganim.PygAnimation object, passing the constructor a list of image filenames and durations. 6 | # 3) Call the play() method. 7 | # 4) Call the blit() method. 8 | # 9 | # The animation images come from POW Studios, and are available under an Attribution-only license. 10 | # Check them out, they're really nice. 11 | # http://powstudios.com/ 12 | 13 | import sys 14 | import os 15 | sys.path.append(os.path.abspath('..')) 16 | import pygame 17 | from pygame.locals import * 18 | import time 19 | import pyganim 20 | 21 | pygame.init() 22 | 23 | # set up the window 24 | windowSurface = pygame.display.set_mode((320, 240), 0, 32) 25 | pygame.display.set_caption('Pyganim Basic Demo') 26 | 27 | # create the animation objects ('filename of image', duration_in_seconds) 28 | boltAnim = pyganim.PygAnimation([('testimages/bolt_strike_0001.png', 100), 29 | ('testimages/bolt_strike_0002.png', 100), 30 | ('testimages/bolt_strike_0003.png', 100), 31 | ('testimages/bolt_strike_0004.png', 100), 32 | ('testimages/bolt_strike_0005.png', 100), 33 | ('testimages/bolt_strike_0006.png', 100), 34 | ('testimages/bolt_strike_0007.png', 100), 35 | ('testimages/bolt_strike_0008.png', 100), 36 | ('testimages/bolt_strike_0009.png', 100), 37 | ('testimages/bolt_strike_0010.png', 100)]) 38 | boltAnim.play() # there is also a pause() and stop() method 39 | 40 | mainClock = pygame.time.Clock() 41 | BGCOLOR = (100, 50, 50) 42 | while True: 43 | windowSurface.fill(BGCOLOR) 44 | for event in pygame.event.get(): 45 | if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE): 46 | pygame.quit() 47 | sys.exit() 48 | if event.type == KEYDOWN and event.key == K_l: 49 | # press "L" key to stop looping 50 | boltAnim.loop = False 51 | 52 | boltAnim.blit(windowSurface, (100, 50)) 53 | 54 | pygame.display.update() 55 | mainClock.tick(30) # Feel free to experiment with any FPS setting. -------------------------------------------------------------------------------- /examples/control_panel_demo.py: -------------------------------------------------------------------------------- 1 | # test2_pyganim.py - A pyganim test program. 2 | # 3 | # This test program is the same lightning bolt animation, but provides you with buttons and lots of debug information. 4 | # 5 | # The animation images come from POW Studios, and are available under an Attribution-only license. 6 | # Check them out, they're really nice. 7 | # http://powstudios.com/ 8 | 9 | import sys 10 | import os 11 | sys.path.append(os.path.abspath('..')) 12 | 13 | import pygame 14 | from pygame.locals import * 15 | import time 16 | import pyganim 17 | 18 | pygame.init() 19 | 20 | # set up the window 21 | WINWIDTH = 640 22 | WINHEIGHT = 480 23 | windowSurface = pygame.display.set_mode((WINWIDTH, WINHEIGHT), 0, 32) 24 | pygame.display.set_caption('Pyganim Control Panel') 25 | 26 | # create the animation objects 27 | boltAnim = pyganim.PygAnimation([('testimages/bolt_strike_0001.png', 200), 28 | ('testimages/bolt_strike_0002.png', 200), 29 | ('testimages/bolt_strike_0003.png', 200), 30 | ('testimages/bolt_strike_0004.png', 200), 31 | ('testimages/bolt_strike_0005.png', 200), 32 | ('testimages/bolt_strike_0006.png', 200), 33 | ('testimages/bolt_strike_0007.png', 200), 34 | ('testimages/bolt_strike_0008.png', 200), 35 | ('testimages/bolt_strike_0009.png', 200), 36 | ('testimages/bolt_strike_0010.png', 200)]) 37 | 38 | boltAnim.play() 39 | 40 | BASICFONT = pygame.font.Font('freesansbold.ttf', 16) 41 | WHITE = (255, 255, 255) 42 | BGCOLOR = (100, 50, 50) 43 | 44 | buttons = 'Play Pause Stop Toggle Rew. FF Loop Rev Vis PrevF NextF'.split(' ') 45 | buttonDict = {} 46 | leftPoint = 4 47 | for button in buttons: 48 | buttonDict[button] = [BASICFONT.render(button, True, WHITE)] 49 | buttonDict[button].append(buttonDict[button][0].get_rect()) 50 | pygame.draw.rect(buttonDict[button][0], WHITE, buttonDict[button][1], 1) 51 | buttonDict[button][1].bottom = WINHEIGHT - 4 52 | buttonDict[button][1].left = leftPoint 53 | leftPoint += buttonDict[button][1].width + 4 54 | 55 | while True: 56 | windowSurface.fill(BGCOLOR) 57 | for event in pygame.event.get(): 58 | if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE): 59 | pygame.quit() 60 | sys.exit() 61 | if event.type == MOUSEBUTTONDOWN: 62 | for button in buttons: 63 | if buttonDict[button][1].collidepoint(event.pos): 64 | if button == 'Play': 65 | boltAnim.play() 66 | elif button == 'Pause': 67 | boltAnim.pause() 68 | elif button == 'Stop': 69 | boltAnim.stop() 70 | elif button == 'Toggle': 71 | boltAnim.togglePause() 72 | elif button == 'Rew.': 73 | boltAnim.rewind(100) 74 | elif button == 'FF': 75 | boltAnim.fastForward(100) 76 | elif button == 'Loop': 77 | boltAnim.loop = not boltAnim.loop 78 | elif button == 'Rev': 79 | boltAnim.reverse() 80 | elif button == 'Vis': 81 | boltAnim.visibility = not boltAnim.visibility 82 | elif button == 'PrevF': 83 | boltAnim.prevFrame() 84 | elif button == 'NextF': 85 | boltAnim.nextFrame() 86 | 87 | # draw the animations to the screen 88 | #boltAnim.currentFrameNum = 3 89 | #print(boltAnim.currentFrameNum) 90 | boltAnim.blit(windowSurface, (300, 4)) 91 | for button in buttons: 92 | windowSurface.blit(buttonDict[button][0], buttonDict[button][1]) 93 | 94 | # draw the info text 95 | stateSurf = BASICFONT.render('State: %s' % boltAnim.state, True, WHITE) 96 | stateRect = stateSurf.get_rect() 97 | stateRect.topleft = (4, 130) 98 | windowSurface.blit(stateSurf, stateRect) 99 | 100 | elapsedSurf = BASICFONT.render('Elapsed: %s' % boltAnim.elapsed, True, WHITE) 101 | elapsedRect = elapsedSurf.get_rect() 102 | elapsedRect.topleft = (150, 130) 103 | windowSurface.blit(elapsedSurf, elapsedRect) 104 | 105 | curFrameSurf = BASICFONT.render('Cur Frame Num: %s' % boltAnim.currentFrameNum, True, WHITE) 106 | curFrameRect = curFrameSurf.get_rect() 107 | curFrameRect.topleft = (380, 130) 108 | windowSurface.blit(curFrameSurf, curFrameRect) 109 | 110 | loopSurf = BASICFONT.render('Looping: %s' % boltAnim.loop, True, WHITE) 111 | loopRect = loopSurf.get_rect() 112 | loopRect.topleft = (4, 150) 113 | windowSurface.blit(loopSurf, loopRect) 114 | 115 | visSurf = BASICFONT.render('Vis: %s' % boltAnim.visibility, True, WHITE) 116 | visRect = visSurf.get_rect() 117 | visRect.topleft = (150, 150) 118 | windowSurface.blit(visSurf, visRect) 119 | 120 | rightNow = time.time() 121 | 122 | timeSurf = BASICFONT.render('Current Time: %s' % rightNow, True, WHITE) 123 | timeRect = timeSurf.get_rect() 124 | timeRect.topleft = (4, 170) 125 | windowSurface.blit(timeSurf, timeRect) 126 | 127 | playTimeSurf = BASICFONT.render('Play Start Time: %s' % boltAnim._playingStartTime, True, WHITE) 128 | playTimeRect = playTimeSurf.get_rect() 129 | playTimeRect.topleft = (4, 190) 130 | windowSurface.blit(playTimeSurf, playTimeRect) 131 | 132 | pauseTimeSurf = BASICFONT.render('Pause Start Time: %s' % boltAnim._pausedStartTime, True, WHITE) 133 | pauseTimeRect = pauseTimeSurf.get_rect() 134 | pauseTimeRect.topleft = (4, 210) 135 | windowSurface.blit(pauseTimeSurf, pauseTimeRect) 136 | 137 | diffTimeSurf = BASICFONT.render('Play - Pause Time: %s' % (boltAnim._playingStartTime - boltAnim._pausedStartTime), True, WHITE) 138 | diffTimeRect = diffTimeSurf.get_rect() 139 | diffTimeRect.topleft = (4, 230) 140 | windowSurface.blit(diffTimeSurf, diffTimeRect) 141 | 142 | diff2TimeSurf = BASICFONT.render('Current - Play Time: %s' % (rightNow - boltAnim._playingStartTime), True, WHITE) 143 | diff2TimeRect = diff2TimeSurf.get_rect() 144 | diff2TimeRect.topleft = (4, 250) 145 | windowSurface.blit(diff2TimeSurf, diff2TimeRect) 146 | 147 | diff3TimeSurf = BASICFONT.render('Current - Pause Time: %s' % (rightNow - boltAnim._pausedStartTime), True, WHITE) 148 | diff3TimeRect = diff3TimeSurf.get_rect() 149 | diff3TimeRect.topleft = (4, 270) 150 | windowSurface.blit(diff3TimeSurf, diff3TimeRect) 151 | 152 | 153 | pygame.display.update() 154 | 155 | -------------------------------------------------------------------------------- /examples/effects_demo.py: -------------------------------------------------------------------------------- 1 | # test2_pyganim.py - A pyganim test program. 2 | # 3 | # This program shows off a lot more of Pyganim features, and offers some interactivity. 4 | # 5 | # The animation images come from POW Studios, and are available under an Attribution-only license. 6 | # Check them out, they're really nice. 7 | # http://powstudios.com/ 8 | 9 | import sys 10 | import os 11 | sys.path.append(os.path.abspath('..')) 12 | 13 | import pygame 14 | from pygame.locals import * 15 | import time 16 | import pyganim 17 | 18 | pygame.init() 19 | 20 | # set up the window 21 | windowSurface = pygame.display.set_mode((640, 480), 0, 32) 22 | pygame.display.set_caption('Pyganim Effects Demo') 23 | 24 | # create the animation objects 25 | boltAnim0 = pyganim.PygAnimation([('testimages/bolt_strike_0001.png', 100), 26 | ('testimages/bolt_strike_0002.png', 100), 27 | ('testimages/bolt_strike_0003.png', 100), 28 | ('testimages/bolt_strike_0004.png', 100), 29 | ('testimages/bolt_strike_0005.png', 100), 30 | ('testimages/bolt_strike_0006.png', 100), 31 | ('testimages/bolt_strike_0007.png', 100), 32 | ('testimages/bolt_strike_0008.png', 100), 33 | ('testimages/bolt_strike_0009.png', 100), 34 | ('testimages/bolt_strike_0010.png', 100)]) 35 | 36 | # create some copies of the bolt animation 37 | boltAnim1, boltAnim2, boltAnim3, boltAnim4 = boltAnim0.getCopies(4) 38 | boltAnim3.rate = 0.5 39 | boltAnim4.rate = 2.0 40 | bolts = [boltAnim0, boltAnim1, boltAnim2, boltAnim3, boltAnim4] 41 | 42 | # supply a "start time" argument to play() so that the bolt animations are 43 | # all in sync with each other. 44 | rightNow = time.time() 45 | for i in range(len(bolts)): 46 | if i == 2: 47 | continue # we're not going to call play() on boltAnim2 48 | bolts[i].play(rightNow) # make sure they all start in sync 49 | 50 | 51 | # create the fire animation 52 | fireAnim = pyganim.PygAnimation([('testimages/flame_a_0001.png', 100), 53 | ('testimages/flame_a_0002.png', 100), 54 | ('testimages/flame_a_0003.png', 100), 55 | ('testimages/flame_a_0004.png', 100), 56 | ('testimages/flame_a_0005.png', 100), 57 | ('testimages/flame_a_0006.png', 100)]) 58 | fireAnim2 = fireAnim.getCopy() 59 | fireAnim3 = fireAnim.getCopy() 60 | spinningFireAnim = fireAnim.getCopy() 61 | 62 | # do some transformation on the other two fire animation objects 63 | fireAnim2.smoothscale((200, 200)) 64 | fireAnim3.rotate(50) 65 | fireAnim3.smoothscale((256, 360)) 66 | fireAnim.rate = 1.2 # make the smaller fire slightly faster 67 | 68 | # start playing the fire animations 69 | fireAnim.play() 70 | fireAnim2.play() 71 | fireAnim3.play() 72 | spinningFireAnim.play() 73 | 74 | # You can also use pygame.Surface objects in the constructor instead of filename strings. 75 | smokeSurf1 = pygame.image.load('testimages/smoke_puff_0001.png') 76 | smokeSurf2 = pygame.image.load('testimages/smoke_puff_0002.png') 77 | smokeSurf3 = pygame.image.load('testimages/smoke_puff_0003.png') 78 | smokeSurf4 = pygame.image.load('testimages/smoke_puff_0004.png') 79 | smokeAnim = pyganim.PygAnimation([(smokeSurf1, 100), 80 | (smokeSurf2, 100), 81 | (smokeSurf3, 100), 82 | (smokeSurf4, 100), 83 | ('testimages/smoke_puff_0005.png', 100), 84 | ('testimages/smoke_puff_0006.png', 100), 85 | ('testimages/smoke_puff_0007.png', 100), 86 | ('testimages/smoke_puff_0008.png', 300), 87 | ('testimages/smoke_puff_0009.png', 300), 88 | ('testimages/smoke_puff_0010.png', 300)], loop=False) 89 | smokeAnim.play() # start playing the smoke animation 90 | 91 | # creating an animation object from an image that doesn't have transparent 92 | # pixels so that the alpha values can be changed. 93 | alAnim = pyganim.PygAnimation([('testimages/alsweigart1.jpg', 500), 94 | ('testimages/alsweigart2.jpg', 500)]) 95 | alAnim.set_alpha(50) 96 | alAnim.play() 97 | 98 | 99 | BASICFONT = pygame.font.Font('freesansbold.ttf', 16) 100 | WHITE = (255, 255, 255) 101 | BGCOLOR = (100, 50, 50) 102 | instructionSurf = BASICFONT.render('P to toggle Play/Pause, S to stop, R to reverse, LEFT/RIGHT to rewind/ff.', True, WHITE) 103 | instructionRect = instructionSurf.get_rect() 104 | instructionRect.topleft = (10, 128) 105 | instructionSurf2 = BASICFONT.render('O to replay smoke. I to toggle fire visibility. Esc to quit.', True, WHITE) 106 | instructionRect2 = instructionSurf2.get_rect() 107 | instructionRect2.topleft = (10, 148) 108 | instructionSurf3 = BASICFONT.render('Note the 3rd bolt doesn\'t play because play() wasn\'t called on it.', True, WHITE) 109 | instructionRect3 = instructionSurf2.get_rect() 110 | instructionRect3.topleft = (10, 168) 111 | 112 | mainClock = pygame.time.Clock() 113 | spinAmt = 0 114 | 115 | while True: 116 | windowSurface.fill(BGCOLOR) 117 | for event in pygame.event.get(): 118 | if event.type == QUIT: 119 | pygame.quit() 120 | sys.exit() 121 | if event.type == KEYDOWN: 122 | if event.key == K_ESCAPE: 123 | pygame.quit() 124 | sys.exit() 125 | if event.key == K_p: 126 | boltAnim0.togglePause() 127 | if event.key == K_s: 128 | boltAnim0.stop() 129 | if event.key == K_LEFT: 130 | boltAnim0.prevFrame() 131 | if event.key == K_RIGHT: 132 | boltAnim0.nextFrame() 133 | if event.key == K_r: 134 | boltAnim0.reverse() 135 | if event.key == K_o: 136 | smokeAnim.play() 137 | if event.key == K_i: 138 | fireAnim.visibility = not fireAnim.visibility 139 | fireAnim2.visibility = not fireAnim2.visibility 140 | fireAnim3.visibility = not fireAnim3.visibility 141 | 142 | # draw the animations to the screen 143 | for i in range(len(bolts)): 144 | bolts[i].blit(windowSurface, ((i*133), 0)) 145 | fireAnim3.blit(windowSurface, (30, 130)) 146 | fireAnim2.blit(windowSurface, (116, 226)) 147 | fireAnim.blit(windowSurface, (178, 278)) 148 | smokeAnim.blit(windowSurface, (350, 250)) 149 | 150 | # handle the spinning fire 151 | spinAmt += 1 152 | spinningFireAnim.clearTransforms() 153 | spinningFireAnim.rotate(spinAmt) 154 | curSpinSurf = spinningFireAnim.getCurrentFrame() # gets the current 155 | w, h = curSpinSurf.get_size() 156 | 157 | # technically, in the time span between the getCurrentFrame() call and 158 | # the following blit() call, enough time could have passed where it 159 | # has the width and height for the wrong frame. It's unlikely though. 160 | # But if you want to account for this, just use the blitFrameAtTime() 161 | # or blitFrameNum() methods instead of blit(). 162 | spinningFireAnim.blit(windowSurface, (550 - int(w/2), 350 - int(h/2))) 163 | 164 | # draw the semitransparent "picture of Al" animation on top of the spinning fire 165 | alAnim.blit(windowSurface, (512, 352)) 166 | 167 | # draw the instructional text 168 | windowSurface.blit(instructionSurf, instructionRect) 169 | windowSurface.blit(instructionSurf2, instructionRect2) 170 | windowSurface.blit(instructionSurf3, instructionRect3) 171 | 172 | pygame.display.update() 173 | mainClock.tick(30) # Feel free to experiment with any FPS setting. -------------------------------------------------------------------------------- /examples/gameimages/crono_back.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/gameimages/crono_back.gif -------------------------------------------------------------------------------- /examples/gameimages/crono_back_run.000.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/gameimages/crono_back_run.000.gif -------------------------------------------------------------------------------- /examples/gameimages/crono_back_run.001.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/gameimages/crono_back_run.001.gif -------------------------------------------------------------------------------- /examples/gameimages/crono_back_run.002.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/gameimages/crono_back_run.002.gif -------------------------------------------------------------------------------- /examples/gameimages/crono_back_run.003.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/gameimages/crono_back_run.003.gif -------------------------------------------------------------------------------- /examples/gameimages/crono_back_run.004.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/gameimages/crono_back_run.004.gif -------------------------------------------------------------------------------- /examples/gameimages/crono_back_run.005.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/gameimages/crono_back_run.005.gif -------------------------------------------------------------------------------- /examples/gameimages/crono_back_walk.000.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/gameimages/crono_back_walk.000.gif -------------------------------------------------------------------------------- /examples/gameimages/crono_back_walk.001.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/gameimages/crono_back_walk.001.gif -------------------------------------------------------------------------------- /examples/gameimages/crono_back_walk.002.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/gameimages/crono_back_walk.002.gif -------------------------------------------------------------------------------- /examples/gameimages/crono_back_walk.003.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/gameimages/crono_back_walk.003.gif -------------------------------------------------------------------------------- /examples/gameimages/crono_back_walk.004.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/gameimages/crono_back_walk.004.gif -------------------------------------------------------------------------------- /examples/gameimages/crono_back_walk.005.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/gameimages/crono_back_walk.005.gif -------------------------------------------------------------------------------- /examples/gameimages/crono_front.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/gameimages/crono_front.gif -------------------------------------------------------------------------------- /examples/gameimages/crono_front_run.000.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/gameimages/crono_front_run.000.gif -------------------------------------------------------------------------------- /examples/gameimages/crono_front_run.001.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/gameimages/crono_front_run.001.gif -------------------------------------------------------------------------------- /examples/gameimages/crono_front_run.002.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/gameimages/crono_front_run.002.gif -------------------------------------------------------------------------------- /examples/gameimages/crono_front_run.003.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/gameimages/crono_front_run.003.gif -------------------------------------------------------------------------------- /examples/gameimages/crono_front_run.004.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/gameimages/crono_front_run.004.gif -------------------------------------------------------------------------------- /examples/gameimages/crono_front_run.005.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/gameimages/crono_front_run.005.gif -------------------------------------------------------------------------------- /examples/gameimages/crono_front_walk.000.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/gameimages/crono_front_walk.000.gif -------------------------------------------------------------------------------- /examples/gameimages/crono_front_walk.001.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/gameimages/crono_front_walk.001.gif -------------------------------------------------------------------------------- /examples/gameimages/crono_front_walk.002.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/gameimages/crono_front_walk.002.gif -------------------------------------------------------------------------------- /examples/gameimages/crono_front_walk.003.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/gameimages/crono_front_walk.003.gif -------------------------------------------------------------------------------- /examples/gameimages/crono_front_walk.004.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/gameimages/crono_front_walk.004.gif -------------------------------------------------------------------------------- /examples/gameimages/crono_front_walk.005.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/gameimages/crono_front_walk.005.gif -------------------------------------------------------------------------------- /examples/gameimages/crono_left.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/gameimages/crono_left.gif -------------------------------------------------------------------------------- /examples/gameimages/crono_left_run.000.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/gameimages/crono_left_run.000.gif -------------------------------------------------------------------------------- /examples/gameimages/crono_left_run.001.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/gameimages/crono_left_run.001.gif -------------------------------------------------------------------------------- /examples/gameimages/crono_left_run.002.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/gameimages/crono_left_run.002.gif -------------------------------------------------------------------------------- /examples/gameimages/crono_left_run.003.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/gameimages/crono_left_run.003.gif -------------------------------------------------------------------------------- /examples/gameimages/crono_left_run.004.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/gameimages/crono_left_run.004.gif -------------------------------------------------------------------------------- /examples/gameimages/crono_left_run.005.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/gameimages/crono_left_run.005.gif -------------------------------------------------------------------------------- /examples/gameimages/crono_left_walk.000.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/gameimages/crono_left_walk.000.gif -------------------------------------------------------------------------------- /examples/gameimages/crono_left_walk.001.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/gameimages/crono_left_walk.001.gif -------------------------------------------------------------------------------- /examples/gameimages/crono_left_walk.002.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/gameimages/crono_left_walk.002.gif -------------------------------------------------------------------------------- /examples/gameimages/crono_left_walk.003.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/gameimages/crono_left_walk.003.gif -------------------------------------------------------------------------------- /examples/gameimages/crono_left_walk.004.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/gameimages/crono_left_walk.004.gif -------------------------------------------------------------------------------- /examples/gameimages/crono_left_walk.005.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/gameimages/crono_left_walk.005.gif -------------------------------------------------------------------------------- /examples/gameimages/crono_sleep.000.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/gameimages/crono_sleep.000.gif -------------------------------------------------------------------------------- /examples/gameimages/crono_sleep.001.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/gameimages/crono_sleep.001.gif -------------------------------------------------------------------------------- /examples/gif_demo.py: -------------------------------------------------------------------------------- 1 | # gif_demo.py - Loading the PygAnimation object from a single animated gif file. 2 | # 3 | # This program just runs a single animation. It shows you what you need to do to use Pyganim. Basically: 4 | # 1) Import the pyganim module 5 | # 2) Create a pyganim.PygAnimation object, passing the constructor the filename of an animated gif. 6 | # 3) Call the play() method. 7 | # 4) Call the blit() method. 8 | # 9 | # The animation images come from POW Studios, and are available under an Attribution-only license. 10 | # Check them out, they're really nice. 11 | # http://powstudios.com/ 12 | 13 | import sys 14 | import os 15 | sys.path.append(os.path.abspath('..')) 16 | import pygame 17 | from pygame.locals import * 18 | import time 19 | import pyganim 20 | 21 | pygame.init() 22 | 23 | # set up the window 24 | windowSurface = pygame.display.set_mode((385, 380), 0, 32) 25 | pygame.display.set_caption('Pyganim GIF Demo') 26 | 27 | # create the animation objects ('filename of image', duration_in_seconds) 28 | bananaAnim = pyganim.PygAnimation('banana.gif') 29 | bananaAnim.play() # there is also a pause() and stop() method 30 | 31 | mainClock = pygame.time.Clock() 32 | BGCOLOR = (100, 50, 50) 33 | while True: 34 | windowSurface.fill(BGCOLOR) 35 | for event in pygame.event.get(): 36 | if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE): 37 | pygame.quit() 38 | sys.exit() 39 | 40 | bananaAnim.blit(windowSurface, (10, 10)) 41 | 42 | pygame.display.update() 43 | mainClock.tick(30) # Feel free to experiment with any FPS setting. -------------------------------------------------------------------------------- /examples/simulation_time_demo.py: -------------------------------------------------------------------------------- 1 | """ 2 | This example shows how Pyganim can be used with "simulation time", instead of the actual system clock time as returned by time.time() 3 | """ 4 | 5 | import sys 6 | import os 7 | sys.path.append(os.path.abspath('..')) 8 | 9 | import pygame 10 | from pygame.locals import * 11 | import time 12 | import pyganim 13 | 14 | pygame.init() 15 | 16 | # set up the window 17 | WINDOWWIDTH = 640 18 | WINDOWHEIGHT = 480 19 | windowSurface = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT), 0, 32) 20 | pygame.display.set_caption('Pyganim Simulation Clock Demo') 21 | 22 | # creating the PygAnimation objects for walking/running in all directions 23 | animTypes = 'back_walk front_walk left_walk'.split() 24 | animObjs = {} 25 | for animType in animTypes: 26 | imagesAndDurations = [('gameimages/crono_%s.%s.gif' % (animType, str(num).rjust(3, '0')), 1) for num in range(6)] # since we are not using the real system clock, "1" here is not "1 millisecond" but 1 step in the simulation clock 27 | animObjs[animType] = pyganim.PygAnimation(imagesAndDurations) 28 | 29 | # create the right-facing sprites by copying and flipping the left-facing sprites 30 | animObjs['right_walk'] = animObjs['left_walk'].getCopy() 31 | animObjs['right_walk'].flip(True, False) 32 | animObjs['right_walk'].makeTransformsPermanent() 33 | 34 | # have the animation objects managed by a conductor. 35 | # With the conductor, we can call play() and stop() on all the animtion 36 | # objects at the same time, so that way they'll always be in sync with each 37 | # other. 38 | moveConductor = pyganim.PygConductor(animObjs) 39 | 40 | 41 | BASICFONT = pygame.font.Font('freesansbold.ttf', 16) 42 | WHITE = (255, 255, 255) 43 | RED = (255, 0, 0) 44 | BGCOLOR = (100, 50, 50) 45 | 46 | mainClock = pygame.time.Clock() 47 | 48 | 49 | 50 | 51 | 52 | simulationTime = 0 # this is the variable that contains the current "simulation time" 53 | simulationClockFunction = lambda: simulationTime 54 | pyganim.TIME_FUNC = simulationClockFunction 55 | moveConductor.play() 56 | 57 | def getPositionAtTime(t): 58 | """ 59 | Simulation time t runs from 0 to 99 60 | 61 | First quarter: walking right from 50, 50 to 250, 50 62 | Second quarter: walking down from 250, 50 to 250, 250 63 | Third quarter: walking left from 250, 250 to 50, 250 64 | Fourth quarter: walking up from 50, 250 to 50, 50 65 | """ 66 | if 0 <= t < 25: 67 | return 50 + ((t - 0) * 8), 50, 'right' 68 | elif 25 <= t < 50: 69 | return 250, 50 + ((t - 25) * 8), 'front' 70 | elif 50 <= t < 75: 71 | return 250 - ((t - 50) * 8), 250, 'left' 72 | elif 75 <= t < 100: 73 | return 50, 250 - ((t - 75) * 8), 'back' 74 | 75 | 76 | mouseDown = False 77 | 78 | while True: 79 | for event in pygame.event.get(): # event handling loop 80 | 81 | # handle ending the program 82 | if event.type == QUIT: 83 | pygame.quit() 84 | sys.exit() 85 | elif event.type == MOUSEBUTTONDOWN: 86 | mouseDown = True 87 | mousex, mousey = event.pos 88 | if not (200 <= mousex < 300) or not (390 <= mousey < 430): 89 | continue 90 | simulationTime = mousex - 200 91 | 92 | elif event.type == MOUSEBUTTONUP: 93 | mouseDown = False 94 | elif event.type == MOUSEMOTION: 95 | if not mouseDown: 96 | continue 97 | mousex, mousey = event.pos 98 | if not (200 <= mousex < 300) or not (390 <= mousey < 430): 99 | continue 100 | simulationTime = mousex - 200 101 | 102 | 103 | # draw scene 104 | windowSurface.fill(BGCOLOR) 105 | 106 | # draw character 107 | x, y, direction = getPositionAtTime(simulationTime) 108 | animObjs[direction + '_walk'].blit(windowSurface, (x, y)) 109 | 110 | # draw instructions 111 | instructionSurf = BASICFONT.render('Drag slider to see different points of the simulation. Simulation time: %s' % (simulationTime), True, WHITE) 112 | instructionRect = instructionSurf.get_rect() 113 | instructionRect.bottomleft = (10, WINDOWHEIGHT - 10) 114 | windowSurface.blit(instructionSurf, instructionRect) 115 | 116 | # draw slider 117 | pygame.draw.rect(windowSurface, WHITE, (200, 400, 100, 20)) 118 | pygame.draw.line(windowSurface, RED, (200 + simulationTime, 390), (200 + simulationTime, 430)) 119 | 120 | pygame.display.update() 121 | mainClock.tick(30) # Feel free to experiment with any FPS setting. -------------------------------------------------------------------------------- /examples/sprite_sheet_demo.py: -------------------------------------------------------------------------------- 1 | # trex image from Wyverii on http://opengameart.org/content/unsealed-terrex 2 | 3 | import sys 4 | import os 5 | sys.path.append(os.path.abspath('..')) 6 | import pygame 7 | from pygame.locals import * 8 | import pyganim 9 | 10 | pygame.init() 11 | 12 | # set up the window 13 | windowSurface = pygame.display.set_mode((320, 240), 0, 32) 14 | pygame.display.set_caption('Sprite Sheet Demo') 15 | 16 | # create the animation objects 17 | rects = [( 0, 154, 94, 77), 18 | ( 94, 154, 94, 77), 19 | (188, 154, 94, 77), 20 | (282, 154, 94, 77), 21 | (376, 154, 94, 77), 22 | (470, 154, 94, 77), 23 | (564, 154, 94, 77), 24 | (658, 154, 94, 77), 25 | (752, 154, 94, 77),] 26 | 27 | allImages = pyganim.getImagesFromSpriteSheet('terrex_0.png', rects=rects) 28 | frames = list(zip(allImages, [100] * len(allImages))) 29 | 30 | dinoAnim = pyganim.PygAnimation(frames) 31 | dinoAnim.play() # there is also a pause() and stop() method 32 | 33 | mainClock = pygame.time.Clock() 34 | BGCOLOR = (100, 50, 50) 35 | while True: 36 | windowSurface.fill(BGCOLOR) 37 | for event in pygame.event.get(): 38 | if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE): 39 | pygame.quit() 40 | sys.exit() 41 | 42 | dinoAnim.blit(windowSurface, (100, 50)) 43 | 44 | pygame.display.update() 45 | mainClock.tick(30) # Feel free to experiment with any FPS setting. -------------------------------------------------------------------------------- /examples/terrex_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/terrex_0.png -------------------------------------------------------------------------------- /examples/testimages/alsweigart1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/testimages/alsweigart1.jpg -------------------------------------------------------------------------------- /examples/testimages/alsweigart2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/testimages/alsweigart2.jpg -------------------------------------------------------------------------------- /examples/testimages/bolt_strike_0001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/testimages/bolt_strike_0001.png -------------------------------------------------------------------------------- /examples/testimages/bolt_strike_0002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/testimages/bolt_strike_0002.png -------------------------------------------------------------------------------- /examples/testimages/bolt_strike_0003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/testimages/bolt_strike_0003.png -------------------------------------------------------------------------------- /examples/testimages/bolt_strike_0004.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/testimages/bolt_strike_0004.png -------------------------------------------------------------------------------- /examples/testimages/bolt_strike_0005.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/testimages/bolt_strike_0005.png -------------------------------------------------------------------------------- /examples/testimages/bolt_strike_0006.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/testimages/bolt_strike_0006.png -------------------------------------------------------------------------------- /examples/testimages/bolt_strike_0007.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/testimages/bolt_strike_0007.png -------------------------------------------------------------------------------- /examples/testimages/bolt_strike_0008.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/testimages/bolt_strike_0008.png -------------------------------------------------------------------------------- /examples/testimages/bolt_strike_0009.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/testimages/bolt_strike_0009.png -------------------------------------------------------------------------------- /examples/testimages/bolt_strike_0010.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/testimages/bolt_strike_0010.png -------------------------------------------------------------------------------- /examples/testimages/flame_a_0001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/testimages/flame_a_0001.png -------------------------------------------------------------------------------- /examples/testimages/flame_a_0002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/testimages/flame_a_0002.png -------------------------------------------------------------------------------- /examples/testimages/flame_a_0003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/testimages/flame_a_0003.png -------------------------------------------------------------------------------- /examples/testimages/flame_a_0004.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/testimages/flame_a_0004.png -------------------------------------------------------------------------------- /examples/testimages/flame_a_0005.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/testimages/flame_a_0005.png -------------------------------------------------------------------------------- /examples/testimages/flame_a_0006.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/testimages/flame_a_0006.png -------------------------------------------------------------------------------- /examples/testimages/smoke_puff_0001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/testimages/smoke_puff_0001.png -------------------------------------------------------------------------------- /examples/testimages/smoke_puff_0002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/testimages/smoke_puff_0002.png -------------------------------------------------------------------------------- /examples/testimages/smoke_puff_0003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/testimages/smoke_puff_0003.png -------------------------------------------------------------------------------- /examples/testimages/smoke_puff_0004.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/testimages/smoke_puff_0004.png -------------------------------------------------------------------------------- /examples/testimages/smoke_puff_0005.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/testimages/smoke_puff_0005.png -------------------------------------------------------------------------------- /examples/testimages/smoke_puff_0006.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/testimages/smoke_puff_0006.png -------------------------------------------------------------------------------- /examples/testimages/smoke_puff_0007.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/testimages/smoke_puff_0007.png -------------------------------------------------------------------------------- /examples/testimages/smoke_puff_0008.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/testimages/smoke_puff_0008.png -------------------------------------------------------------------------------- /examples/testimages/smoke_puff_0009.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/testimages/smoke_puff_0009.png -------------------------------------------------------------------------------- /examples/testimages/smoke_puff_0010.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/examples/testimages/smoke_puff_0010.png -------------------------------------------------------------------------------- /examples/walking_demo.py: -------------------------------------------------------------------------------- 1 | # test4_pyganim.py - A pyganim test program. 2 | # 3 | # This program shows off the use of a PygConductor to help organize your 4 | # animation objects. Conductors basically let you call PygAnimation methods on 5 | # several PygAnimation objects at once (e.g. You can have all the animation 6 | # objects start playing at the same time.) 7 | # 8 | # The animation images come from POW Studios, and are available under an 9 | # Attribution-only license. Check them out, they're really nice. 10 | # http://powstudios.com/ 11 | # 12 | # The walking sprites are shamelessly taken from the excellent SNES game 13 | # Chrono Trigger. 14 | # http://www.videogamesprites.net/ChronoTrigger 15 | 16 | import sys 17 | import os 18 | sys.path.append(os.path.abspath('..')) 19 | 20 | import pygame 21 | from pygame.locals import * 22 | import time 23 | import pyganim 24 | 25 | pygame.init() 26 | 27 | # define some constants 28 | UP = 'up' 29 | DOWN = 'down' 30 | LEFT = 'left' 31 | RIGHT = 'right' 32 | 33 | # set up the window 34 | WINDOWWIDTH = 640 35 | WINDOWHEIGHT = 480 36 | windowSurface = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT), 0, 32) 37 | pygame.display.set_caption('Pyganim Walking Demo') 38 | 39 | # load the "standing" sprites (these are single images, not animations) 40 | front_standing = pygame.image.load('gameimages/crono_front.gif') 41 | back_standing = pygame.image.load('gameimages/crono_back.gif') 42 | left_standing = pygame.image.load('gameimages/crono_left.gif') 43 | right_standing = pygame.transform.flip(left_standing, True, False) 44 | 45 | playerWidth, playerHeight = front_standing.get_size() 46 | 47 | # creating the PygAnimation objects for walking/running in all directions 48 | animTypes = 'back_run back_walk front_run front_walk left_run left_walk'.split() 49 | animObjs = {} 50 | for animType in animTypes: 51 | imagesAndDurations = [('gameimages/crono_%s.%s.gif' % (animType, str(num).rjust(3, '0')), 100) for num in range(6)] 52 | animObjs[animType] = pyganim.PygAnimation(imagesAndDurations) 53 | 54 | # create the right-facing sprites by copying and flipping the left-facing sprites 55 | animObjs['right_walk'] = animObjs['left_walk'].getCopy() 56 | animObjs['right_walk'].flip(True, False) 57 | animObjs['right_walk'].makeTransformsPermanent() 58 | animObjs['right_run'] = animObjs['left_run'].getCopy() 59 | animObjs['right_run'].flip(True, False) 60 | animObjs['right_run'].makeTransformsPermanent() 61 | 62 | # have the animation objects managed by a conductor. 63 | # With the conductor, we can call play() and stop() on all the animtion 64 | # objects at the same time, so that way they'll always be in sync with each 65 | # other. 66 | moveConductor = pyganim.PygConductor(animObjs) 67 | 68 | direction = DOWN # player starts off facing down (front) 69 | 70 | BASICFONT = pygame.font.Font('freesansbold.ttf', 16) 71 | WHITE = (255, 255, 255) 72 | BGCOLOR = (100, 50, 50) 73 | 74 | mainClock = pygame.time.Clock() 75 | x = 300 # x and y are the player's position 76 | y = 200 77 | WALKRATE = 4 78 | RUNRATE = 12 79 | 80 | instructionSurf = BASICFONT.render('Arrow keys to move. Hold shift to run.', True, WHITE) 81 | instructionRect = instructionSurf.get_rect() 82 | instructionRect.bottomleft = (10, WINDOWHEIGHT - 10) 83 | 84 | running = moveUp = moveDown = moveLeft = moveRight = False 85 | 86 | while True: 87 | windowSurface.fill(BGCOLOR) 88 | for event in pygame.event.get(): # event handling loop 89 | 90 | # handle ending the program 91 | if event.type == QUIT: 92 | pygame.quit() 93 | sys.exit() 94 | elif event.type == KEYDOWN: 95 | if event.key == K_ESCAPE: 96 | pygame.quit() 97 | sys.exit() 98 | 99 | if event.key in (K_LSHIFT, K_RSHIFT): 100 | # player has started running 101 | running = True 102 | 103 | if event.key == K_UP: 104 | moveUp = True 105 | moveDown = False 106 | if not moveLeft and not moveRight: 107 | # only change the direction to up if the player wasn't moving left/right 108 | direction = UP 109 | elif event.key == K_DOWN: 110 | moveDown = True 111 | moveUp = False 112 | if not moveLeft and not moveRight: 113 | direction = DOWN 114 | elif event.key == K_LEFT: 115 | moveLeft = True 116 | moveRight = False 117 | if not moveUp and not moveDown: 118 | direction = LEFT 119 | elif event.key == K_RIGHT: 120 | moveRight = True 121 | moveLeft = False 122 | if not moveUp and not moveDown: 123 | direction = RIGHT 124 | 125 | elif event.type == KEYUP: 126 | if event.key in (K_LSHIFT, K_RSHIFT): 127 | # player has stopped running 128 | running = False 129 | 130 | if event.key == K_UP: 131 | moveUp = False 132 | # if the player was moving in a sideways direction before, change the direction the player is facing. 133 | if moveLeft: 134 | direction = LEFT 135 | if moveRight: 136 | direction = RIGHT 137 | elif event.key == K_DOWN: 138 | moveDown = False 139 | if moveLeft: 140 | direction = LEFT 141 | if moveRight: 142 | direction = RIGHT 143 | elif event.key == K_LEFT: 144 | moveLeft = False 145 | if moveUp: 146 | direction = UP 147 | if moveDown: 148 | direction = DOWN 149 | elif event.key == K_RIGHT: 150 | moveRight = False 151 | if moveUp: 152 | direction = UP 153 | if moveDown: 154 | direction = DOWN 155 | 156 | if moveUp or moveDown or moveLeft or moveRight: 157 | # draw the correct walking/running sprite from the animation object 158 | moveConductor.play() # calling play() while the animation objects are already playing is okay; in that case play() is a no-op 159 | if running: 160 | if direction == UP: 161 | animObjs['back_run'].blit(windowSurface, (x, y)) 162 | elif direction == DOWN: 163 | animObjs['front_run'].blit(windowSurface, (x, y)) 164 | elif direction == LEFT: 165 | animObjs['left_run'].blit(windowSurface, (x, y)) 166 | elif direction == RIGHT: 167 | animObjs['right_run'].blit(windowSurface, (x, y)) 168 | else: 169 | # walking 170 | if direction == UP: 171 | animObjs['back_walk'].blit(windowSurface, (x, y)) 172 | elif direction == DOWN: 173 | animObjs['front_walk'].blit(windowSurface, (x, y)) 174 | elif direction == LEFT: 175 | animObjs['left_walk'].blit(windowSurface, (x, y)) 176 | elif direction == RIGHT: 177 | animObjs['right_walk'].blit(windowSurface, (x, y)) 178 | 179 | 180 | # actually move the position of the player 181 | if running: 182 | rate = RUNRATE 183 | else: 184 | rate = WALKRATE 185 | 186 | if moveUp: 187 | y -= rate 188 | if moveDown: 189 | y += rate 190 | if moveLeft: 191 | x -= rate 192 | if moveRight: 193 | x += rate 194 | 195 | else: 196 | # standing still 197 | moveConductor.stop() # calling stop() while the animation objects are already stopped is okay; in that case stop() is a no-op 198 | if direction == UP: 199 | windowSurface.blit(back_standing, (x, y)) 200 | elif direction == DOWN: 201 | windowSurface.blit(front_standing, (x, y)) 202 | elif direction == LEFT: 203 | windowSurface.blit(left_standing, (x, y)) 204 | elif direction == RIGHT: 205 | windowSurface.blit(right_standing, (x, y)) 206 | 207 | # make sure the player does move off the screen 208 | if x < 0: 209 | x = 0 210 | if x > WINDOWWIDTH - playerWidth: 211 | x = WINDOWWIDTH - playerWidth 212 | if y < 0: 213 | y = 0 214 | if y > WINDOWHEIGHT - playerHeight: 215 | y = WINDOWHEIGHT - playerHeight 216 | 217 | windowSurface.blit(instructionSurf, instructionRect) 218 | 219 | pygame.display.update() 220 | mainClock.tick(30) # Feel free to experiment with any FPS setting. -------------------------------------------------------------------------------- /pyganim/__init__.py: -------------------------------------------------------------------------------- 1 | # Pyganim 2 | # A sprite animation module for Pygame. 3 | # 4 | # By Al Sweigart al@inventwithpython.com 5 | # http://inventwithpython.com/pyganim 6 | # Released under a "Simplified BSD" license 7 | # 8 | # There's a tutorial (and sample code) on how to use this library at http://inventwithpython.com/pyganim 9 | # NOTE: This module requires Pygame to be installed to use. Download it from http://pygame.org 10 | # 11 | # This should be compatible with both Python 2 and Python 3. Please email any 12 | # bug reports to Al at al@inventwithpython.com 13 | # 14 | 15 | 16 | # TODO: Feature idea: if the same image file is specified, re-use the Surface object. (Make this optional though.) 17 | # TODO: Dynamically change durations and number of frames. 18 | 19 | __version__ = '0.9.2' 20 | 21 | import pygame 22 | import time 23 | import os 24 | 25 | 26 | # setting up constants 27 | PLAYING = 'playing' 28 | PAUSED = 'paused' 29 | STOPPED = 'stopped' 30 | 31 | # These values are used in the anchor() method. 32 | NW = NORTHWEST = 'nw' 33 | N = NORTH = 'n' 34 | NE = NORTHEAST = 'ne' 35 | W = WEST = 'w' 36 | C = CENTER = 'c' 37 | E = EAST = 'e' 38 | SW = SOUTHWEST = 'sw' 39 | S = SOUTH = 's' 40 | SE = SOUTHEAST = 'se' 41 | 42 | TIME_FUNC = lambda: int(time.time() * 1000) 43 | 44 | DEFAULT_DURATION = 100 # 100ms 45 | 46 | def getImagesFromSpriteSheet(filename, width=None, height=None, rows=None, cols=None, rects=None): 47 | """Loads several sprites from a single image file (a "spritesheet"). 48 | 49 | One (and only one) of the following parameters should be specified: 50 | * width & height of each sprite (all must be the same size) 51 | * number of rows and columns of sprites (all must be the same size) 52 | * rects, which is a list of tuples formatted as (pygame.Rect, index) or (left, top, width, height) 53 | """ 54 | 55 | argsType = '' # there should be exactly 1 set of arguments passed (i.e. don't pass width/height AND rows/cols) 56 | if (width is not None or height is not None) and (argsType == ''): 57 | argsType = 'width/height' 58 | assert width is not None and height is not None, 'Both width and height must be specified' 59 | assert type(width) == int and width > 0, 'width arg must be a non-zero positive integer' 60 | assert type(height) == int and height > 0, 'height arg must be a non-zero positive integer' 61 | if (rows is not None or cols is not None) and (argsType == ''): 62 | argsType = 'rows/cols' 63 | assert rows is not None and cols is not None, 'Both rows and cols must be specified' 64 | assert type(rows) == int and rows > 0, 'rows arg must be a non-zero positive integer' 65 | assert type(cols) == int and cols > 0, 'cols arg must be a non-zero positive integer' 66 | if (rects is not None) and (argsType == ''): 67 | argsType = 'rects' 68 | for i, rect in enumerate(rects): 69 | assert len(rect) == 4, 'rect at index %s is not a sequence of four ints: (left, top, width, height)' % (i) 70 | 71 | assert (type(rect[0]), type(rect[1]), type(rect[2]), type(rect[3])) == (int, int, int, int), 'rect ' 72 | if argsType == '': 73 | raise ValueError('Only pass one set of args: width & height, rows & cols, *or* rects') 74 | 75 | sheetImage = pygame.image.load(filename) 76 | 77 | if argsType == 'width/height': 78 | for y in range(0, sheetImage.get_height(), (sheetImage.get_height() // height)): 79 | if y + height > sheetImage.get_height(): 80 | continue 81 | for x in range(0, sheetImage.get_width(), (sheetImage.get_width() // width)): 82 | if x + width > sheetImage.get_width(): 83 | continue 84 | 85 | rects.append((x, y, width, height)) 86 | 87 | if argsType == 'rows/cols': 88 | spriteWidth = sheetImage.get_width() // cols 89 | spriteHeight = sheetImage.get_height() // rows 90 | 91 | for y in range(0, sheetImage.get_height(), spriteHeight): 92 | if y + spriteHeight > sheetImage.get_height(): 93 | continue 94 | for x in range(0, sheetImage.get_width(), spriteWidth): 95 | if x + spriteWidth > sheetImage.get_width(): 96 | continue 97 | 98 | rects.append((x, y, spriteWidth, spriteHeight)) 99 | 100 | # create a list of Surface objects from the sprite sheet 101 | returnedSurfaces = [] 102 | for rect in rects: 103 | surf = pygame.Surface((rect[2], rect[3]), 0, sheetImage) # create Surface with width/height in rect 104 | surf.blit(sheetImage, (0, 0), rect, pygame.BLEND_RGBA_ADD) 105 | returnedSurfaces.append(surf) 106 | 107 | return returnedSurfaces 108 | 109 | 110 | 111 | 112 | class PygAnimation(): 113 | def __init__(self, frames, loop=True): 114 | # Constructor function for the animation object. Starts off in the STOPPED state. 115 | # 116 | # @param frames 117 | # A list of tuples for each frame of animation, in one of the following format: 118 | # (image_of_frame, duration_in_milliseconds) 119 | # (filename_of_image, duration_in_milliseconds) 120 | # Note that the images and duration cannot be changed. A new PygAnimation object 121 | # will have to be created. 122 | # @param loop Tells the animation object to keep playing in a loop. 123 | 124 | # _images stores the pygame.Surface objects of each frame 125 | self._images = [] 126 | # _durations stores the durations (in milliseconds) of each frame. 127 | # e.g. [1000, 1000, 2500] means the first and second frames last one second, 128 | # and the third frame lasts for two and half seconds. 129 | self._durations = [] 130 | # _startTimes shows when each frame begins. len(self._startTimes) will 131 | # always be one more than len(self._images), because the last number 132 | # will be when the last frame ends, rather than when it starts. 133 | # The values are in milliseconds. 134 | # So self._startTimes[-1] tells you the length of the entire animation. 135 | # e.g. if _durations is [1000, 1000, 2500], then _startTimes will be [0, 1000, 2000, 4500] 136 | self._startTimes = None 137 | 138 | # if the sprites are transformed, the originals are kept in _images 139 | # and the transformed sprites are kept in _transformedImages. 140 | self._transformedImages = [] 141 | 142 | self._state = STOPPED # The state is always either PLAYING, PAUSED, or STOPPED 143 | self._loop = loop # If True, the animation will keep looping. If False, the animation stops after playing once. 144 | self._rate = 1.0 # 2.0 means play the animation twice as fast, 0.5 means twice as slow 145 | self._visibility = True # If False, then nothing is drawn when the blit() methods are called 146 | 147 | self._playingStartTime = 0 # the time that the play() function was last called. Epoch timestamp in milliseconds. 148 | self._pausedStartTime = 0 # the time that the pause() function was last called. Epoch timestamp in milliseconds. 149 | 150 | # NOTE: There is no "self._elapsed" attribute. "Elapsed" is always calculated based on the current time and _playingStartTime. 151 | if type(frames) == str and frames.lower().endswith('.gif'): 152 | # frames is an animated gif filename 153 | gifImages = splitGif(frames) 154 | # NOTE: I can't understand how PIL and Pygame's tobytes()/tostring()/fromstring() methods work, hence the use of temporary files. 155 | # NOTE: Named temp files delete themselves on close on Windows, so the temp file has to be created here. 156 | frames = [] 157 | for im in gifImages: 158 | im.save('.temp_pyganim.gif') 159 | frames.append((pygame.image.load('.temp_pyganim.gif'), im.info['duration'])) # gif duration is already in milliseconds 160 | os.unlink('.temp_pyganim.gif') 161 | elif len(frames) > 0 and type(frames[0]) == str: 162 | # frames is a list of strings (image filenames without durations) 163 | frames = list(zip(frames, [DEFAULT_DURATION] * len(frames))) # add default duration 164 | 165 | if frames != '_copy': # ('_copy' is passed for frames by the getCopies() method) 166 | self.numFrames = len(frames) 167 | assert self.numFrames > 0, 'Must contain at least one frame.' 168 | for i in range(self.numFrames): 169 | # load each frame of animation into _images 170 | frame = (frames[i][0], int(frames[i][1])) 171 | assert type(frame) in (list, tuple) and len(frame) == 2, 'Frame %s has incorrect format. It should be a tuple of length 2, first item a pygame.Surface or image filename, second item an int/float of duration.' % (i) 172 | assert type(frame[0]) in (str, pygame.Surface), 'Frame %s image must be a string filename or a pygame.Surface' % (i) 173 | assert frame[1] > 0, 'Frame %s duration must be greater than zero.' % (i) 174 | if type(frame[0]) == str: 175 | frame = (pygame.image.load(frame[0]), frame[1]) 176 | self._images.append(frame[0]) 177 | self._durations.append(frame[1]) 178 | 179 | # calculate start times of each frame 180 | self._startTimes = [0] 181 | for i in range(self.numFrames): 182 | self._startTimes.append(self._startTimes[-1] + self._durations[i]) 183 | 184 | 185 | def reverse(self): 186 | # Reverses the order of the frames. 187 | self.elapsed = (self._durations[-1] + self._startTimes[-1]) - self.elapsed 188 | self._images.reverse() 189 | self._transformedImages.reverse() 190 | self._durations.reverse() 191 | 192 | 193 | def getCopy(self): 194 | # Returns a copy of this PygAnimation object, but one that refers to the 195 | # Surface objects of the original so it efficiently uses memory. 196 | # 197 | # NOTE: Messing around with the original Surface objects will affect all 198 | # the copies. If you want to modify the Surface objects, then just make 199 | # copies using constructor function instead. 200 | return self.getCopies(1)[0] 201 | 202 | 203 | def getCopies(self, numCopies=1): 204 | # Returns a list of copies of this PygAnimation object, but one that refers to the 205 | # Surface objects of the original so it efficiently uses memory. 206 | # 207 | # NOTE: Messing around with the original Surface objects will affect all 208 | # the copies. If you want to modify the Surface objects, then just make 209 | # copies using constructor function instead. 210 | retval = [] 211 | for i in range(numCopies): 212 | newAnim = PygAnimation('_copy', loop=self.loop) 213 | newAnim._images = self._images[:] 214 | newAnim._transformedImages = self._transformedImages[:] 215 | newAnim._durations = self._durations[:] 216 | newAnim._startTimes = self._startTimes[:] 217 | newAnim.numFrames = self.numFrames 218 | retval.append(newAnim) 219 | return retval 220 | 221 | 222 | def blit(self, destSurface, dest=(0, 0)): 223 | # Draws the appropriate frame of the animation to the destination Surface 224 | # at the specified position. 225 | # 226 | # NOTE: If the visibility attribute is False, then nothing will be drawn. 227 | # 228 | # @param destSurface 229 | # The Surface object to draw the frame 230 | # @param dest 231 | # The position to draw the frame. This is passed to Pygame's Surface's 232 | # blit() function, so it can be either a (top, left) tuple or a Rect 233 | # object. 234 | if self.isFinished(): 235 | self.state = STOPPED 236 | if not self.visibility or self.state == STOPPED: 237 | return 238 | frameNum = findStartTime(self._startTimes, self.elapsed) 239 | destSurface.blit(self.getFrame(frameNum), dest) 240 | 241 | 242 | def getFrame(self, frameNum): 243 | # Returns the pygame.Surface object of the frameNum-th frame in this 244 | # animation object. If there is a transformed version of the frame, 245 | # it will return that one. 246 | if self._transformedImages == []: 247 | return self._images[frameNum] 248 | else: 249 | return self._transformedImages[frameNum] 250 | 251 | 252 | def getCurrentFrame(self): 253 | # Returns the pygame.Surface object of the frame that would be drawn 254 | # if the blit() method were called right now. If there is a transformed 255 | # version of the frame, it will return that one. 256 | return self.getFrame(self.currentFrameNum) 257 | 258 | 259 | def clearTransforms(self): 260 | # Deletes all the transformed frames so that the animation object 261 | # displays the original Surfaces/images as they were before 262 | # transformation functions were called on them. 263 | # 264 | # This is handy to do for multiple transformation, where calling 265 | # the rotation or scaling functions multiple times results in 266 | # degraded/noisy images. 267 | self._transformedImages = [] 268 | 269 | 270 | def makeTransformsPermanent(self): 271 | self._images = [pygame.Surface(surfObj.get_size(), 0, surfObj) for surfObj in self._transformedImages] 272 | for i in range(len(self._transformedImages)): 273 | self._images[i].blit(self._transformedImages[i], (0,0)) 274 | 275 | 276 | def blitFrameNum(self, frameNum, destSurface, dest): 277 | # Draws the specified frame of the animation object. This ignores the 278 | # current playing state. 279 | # 280 | # NOTE: If the visibility attribute is False, then nothing will be drawn. 281 | # 282 | # @param frameNum 283 | # The frame to draw (the first frame is 0, not 1) 284 | # @param destSurface 285 | # The Surface object to draw the frame 286 | # @param dest 287 | # The position to draw the frame. This is passed to Pygame's Surface's 288 | # blit() function, so it can be either a (top, left) tuple or a Rect 289 | # object. 290 | if self.isFinished(): 291 | self.state = STOPPED 292 | if not self.visibility or self.state == STOPPED: 293 | return 294 | destSurface.blit(self.getFrame(frameNum), dest) 295 | 296 | 297 | def blitFrameAtTime(self, elapsed, destSurface, dest): 298 | # Draws the frame the is "elapsed" number of seconds into the animation, 299 | # rather than the time the animation actually started playing. 300 | # 301 | # NOTE: If the visibility attribute is False, then nothing will be drawn. 302 | # 303 | # @param elapsed 304 | # The amount of time into an animation to use when determining which 305 | # frame to draw. blitFrameAtTime() uses this parameter rather than 306 | # the actual time that the animation started playing. (In seconds) 307 | # @param destSurface 308 | # The Surface object to draw the frame 309 | # @param dest 310 | # The position to draw the frame. This is passed to Pygame's Surface's 311 | # blit() function, so it can be either a (top, left) tuple or a Rect 312 | # object. elapsed = int(elapsed * self.rate) 313 | if self.isFinished(): 314 | self.state = STOPPED 315 | if not self.visibility or self.state == STOPPED: 316 | return 317 | frameNum = findStartTime(self._startTimes, elapsed) 318 | destSurface.blit(self.getFrame(frameNum), dest) 319 | 320 | 321 | def isFinished(self): 322 | # Returns True if this animation doesn't loop and has finished playing 323 | # all the frames it has. 324 | return not self.loop and self.elapsed >= self._startTimes[-1] 325 | 326 | 327 | def play(self, startTime=None): # startTime is epoch timestamp in milliseconds. TODO: Change this? 328 | # Start playing the animation. 329 | 330 | # play() is essentially a setter function for self._state 331 | if startTime is None: 332 | startTime = TIME_FUNC() 333 | 334 | if self._state == PLAYING: 335 | if self.isFinished(): 336 | # if the animation doesn't loop and has already finished, then 337 | # calling play() causes it to replay from the beginning. 338 | self._playingStartTime = startTime 339 | elif self._state == STOPPED: 340 | # if animation was stopped, start playing from the beginning 341 | self._playingStartTime = startTime 342 | elif self._state == PAUSED: 343 | # if animation was paused, start playing from where it was paused 344 | self._playingStartTime = startTime - (self._pausedStartTime - self._playingStartTime) 345 | else: 346 | assert False, '_state attribute contains an invalid value: %s' % (str(self._state)[:40]) 347 | self._state = PLAYING 348 | 349 | 350 | def pause(self, startTime=None): 351 | # Stop having the animation progress, and keep it at the current frame. 352 | 353 | # pause() is essentially a setter function for self._state 354 | if startTime is None: 355 | startTime = TIME_FUNC() 356 | 357 | if self._state == PAUSED: 358 | return # do nothing 359 | elif self._state == PLAYING: 360 | self._pausedStartTime = startTime 361 | elif self._state == STOPPED: 362 | rightNow = TIME_FUNC() 363 | self._playingStartTime = rightNow 364 | self._pausedStartTime = rightNow 365 | else: 366 | assert False, '_state attribute contains an invalid value: %s' % (str(self._state)[:40]) 367 | self._state = PAUSED 368 | 369 | 370 | def stop(self): 371 | # Reset the animation to the beginning frame, and do not continue playing 372 | 373 | # stop() is essentially a setter function for self._state 374 | assert self._state in (PLAYING, PAUSED, STOPPED), '_state attribute contains an invalid value: %s' % (str(self._state)[:40]) 375 | if self._state == STOPPED: 376 | return # do nothing 377 | self._state = STOPPED 378 | 379 | 380 | def togglePause(self): 381 | # If paused, start playing. If playing, then pause. 382 | 383 | # togglePause() is essentially a setter function for self._state 384 | if self._state == PLAYING: 385 | self.pause() 386 | elif self._state in (PAUSED, STOPPED): 387 | self.play() 388 | else: 389 | assert False, '_state attribute contains an invalid value: %s' % (str(self._state)[:40]) 390 | 391 | 392 | def framesAreSameSize(self): 393 | # Returns True if all the Surface objects in this animation object 394 | # have the same width and height. Otherwise, returns False 395 | width, height = self.getFrame(0).get_size() 396 | for i in range(len(self._images)): 397 | if self.getFrame(i).get_size() != (width, height): 398 | return False 399 | return True 400 | 401 | 402 | def getMaxSize(self): 403 | # Goes through all the Surface objects in this animation object 404 | # and returns the max width and max height that it finds. (These 405 | # widths and heights may be on different Surface objects.) 406 | frameWidths = [] 407 | frameHeights = [] 408 | for i in range(len(self._images)): 409 | frameWidth, frameHeight = self._images[i].get_size() 410 | frameWidths.append(frameWidth) 411 | frameHeights.append(frameHeight) 412 | maxWidth = max(frameWidths) 413 | maxHeight = max(frameHeights) 414 | 415 | return (maxWidth, maxHeight) 416 | 417 | 418 | def getRect(self): 419 | # Returns a pygame.Rect object for this animation object. 420 | # The top and left will be set to 0, 0, and the width and height 421 | # will be set to what is returned by getMaxSize(). 422 | maxWidth, maxHeight = self.getMaxSize() 423 | return pygame.Rect(0, 0, maxWidth, maxHeight) 424 | 425 | 426 | def anchor(self, anchorPoint=NORTHWEST): 427 | # If the Surface objects are of different sizes, align them all to a 428 | # specific "anchor point" (one of the NORTH, SOUTH, SOUTHEAST, etc. constants) 429 | # 430 | # By default, they are all anchored to the NORTHWEST corner. 431 | if self.framesAreSameSize(): 432 | return # nothing needs to be anchored 433 | # This check also prevents additional calls to anchor() from doing 434 | # anything, since anchor() sets all the image to the same size. 435 | # The lesson is, you can only effectively call anchor() once. 436 | 437 | self.clearTransforms() # clears transforms since this method anchors the original images. 438 | 439 | maxWidth, maxHeight = self.getMaxSize() 440 | halfMaxWidth = int(maxWidth / 2) 441 | halfMaxHeight = int(maxHeight / 2) 442 | 443 | for i in range(len(self._images)): 444 | # go through and copy all frames to a max-sized Surface object 445 | # NOTE: This makes changes to the original images in self._images, not the transformed images in self._transformedImages 446 | newSurf = pygame.Surface((maxWidth, maxHeight)) # TODO: this is probably going to have errors since I'm using the default depth. 447 | 448 | # set the expanded areas to be transparent 449 | newSurf = newSurf.convert_alpha() 450 | newSurf.fill((0,0,0,0)) 451 | 452 | frameWidth, frameHeight = self._images[i].get_size() 453 | halfFrameWidth = int(frameWidth / 2) 454 | halfFrameHeight = int(frameHeight / 2) 455 | 456 | # position the Surface objects to the specified anchor point 457 | if anchorPoint == NORTHWEST: 458 | newSurf.blit(self._images[i], (0, 0)) 459 | elif anchorPoint == NORTH: 460 | newSurf.blit(self._images[i], (halfMaxWidth - halfFrameWidth, 0)) 461 | elif anchorPoint == NORTHEAST: 462 | newSurf.blit(self._images[i], (maxWidth - frameWidth, 0)) 463 | elif anchorPoint == WEST: 464 | newSurf.blit(self._images[i], (0, halfMaxHeight - halfFrameHeight)) 465 | elif anchorPoint == CENTER: 466 | newSurf.blit(self._images[i], (halfMaxWidth - halfFrameWidth, halfMaxHeight - halfFrameHeight)) 467 | elif anchorPoint == EAST: 468 | newSurf.blit(self._images[i], (maxWidth - frameWidth, halfMaxHeight - halfFrameHeight)) 469 | elif anchorPoint == SOUTHWEST: 470 | newSurf.blit(self._images[i], (0, maxHeight - frameHeight)) 471 | elif anchorPoint == SOUTH: 472 | newSurf.blit(self._images[i], (halfMaxWidth - halfFrameWidth, maxHeight - frameHeight)) 473 | elif anchorPoint == SOUTHEAST: 474 | newSurf.blit(self._images[i], (maxWidth - frameWidth, maxHeight - frameHeight)) 475 | self._images[i] = newSurf 476 | 477 | 478 | def nextFrame(self, jump=1): 479 | # Set the elapsed time to the beginning of the next frame. 480 | # You can jump ahead by multiple frames by specifying a different 481 | # argument for jump. 482 | # Negative values have the same effect as calling prevFrame() 483 | self.currentFrameNum += int(jump) 484 | 485 | 486 | def prevFrame(self, jump=1): 487 | # Set the elapsed time to the beginning of the previous frame. 488 | # You can jump ahead by multiple frames by specifying a different 489 | # argument for jump. 490 | # Negative values have the same effect as calling nextFrame() 491 | self.currentFrameNum -= int(jump) 492 | 493 | 494 | def rewind(self, milliseconds=None): 495 | # Set the elapsed time back relative to the current elapsed time. 496 | if milliseconds is None: 497 | self.elapsed = 0.0 498 | else: 499 | self.elapsed -= milliseconds 500 | 501 | 502 | def fastForward(self, milliseconds): 503 | # Set the elapsed time forward relative to the current elapsed time. 504 | if milliseconds is None: 505 | self.elapsed = self._startTimes[-1] 506 | else: 507 | self.elapsed += milliseconds 508 | 509 | def _makeTransformedSurfacesIfNeeded(self): 510 | # Internal-method. Creates the Surface objects for the _transformedImages list. 511 | # Don't call this method. 512 | if self._transformedImages == []: 513 | self._transformedImages = [surf.copy() for surf in self._images] 514 | 515 | 516 | # Transformation methods. 517 | # (These are analogous to the pygame.transform.* functions, except they 518 | # are applied to all frames of the animation object. 519 | def flip(self, xbool, ybool): 520 | # Flips the image horizontally, vertically, or both. 521 | # See http://pygame.org/docs/ref/transform.html#pygame.transform.flip 522 | self._makeTransformedSurfacesIfNeeded() 523 | for i in range(len(self._images)): 524 | self._transformedImages[i] = pygame.transform.flip(self.getFrame(i), xbool, ybool) 525 | 526 | 527 | def scale(self, width_height): 528 | # NOTE: Does not support the DestSurface parameter 529 | # Increases or decreases the size of the images. 530 | # See http://pygame.org/docs/ref/transform.html#pygame.transform.scale 531 | self._makeTransformedSurfacesIfNeeded() 532 | for i in range(len(self._images)): 533 | self._transformedImages[i] = pygame.transform.scale(self.getFrame(i), width_height) 534 | 535 | 536 | def rotate(self, angle): 537 | # Rotates the image. 538 | # See http://pygame.org/docs/ref/transform.html#pygame.transform.rotate 539 | self._makeTransformedSurfacesIfNeeded() 540 | for i in range(len(self._images)): 541 | self._transformedImages[i] = pygame.transform.rotate(self.getFrame(i), angle) 542 | 543 | 544 | def rotozoom(self, angle, scale): 545 | # Rotates and scales the image simultaneously. 546 | # See http://pygame.org/docs/ref/transform.html#pygame.transform.rotozoom 547 | self._makeTransformedSurfacesIfNeeded() 548 | for i in range(len(self._images)): 549 | self._transformedImages[i] = pygame.transform.rotozoom(self.getFrame(i), angle, scale) 550 | 551 | 552 | def scale2x(self): 553 | # NOTE: Does not support the DestSurface parameter 554 | # Double the size of the image using an efficient algorithm. 555 | # See http://pygame.org/docs/ref/transform.html#pygame.transform.scale2x 556 | self._makeTransformedSurfacesIfNeeded() 557 | for i in range(len(self._images)): 558 | self._transformedImages[i] = pygame.transform.scale2x(self.getFrame(i)) 559 | 560 | 561 | def smoothscale(self, width_height): 562 | # NOTE: Does not support the DestSurface parameter 563 | # Scales the image smoothly. (Computationally more expensive and 564 | # slower but produces a better scaled image.) 565 | # See http://pygame.org/docs/ref/transform.html#pygame.transform.smoothscale 566 | self._makeTransformedSurfacesIfNeeded() 567 | for i in range(len(self._images)): 568 | self._transformedImages[i] = pygame.transform.smoothscale(self.getFrame(i), width_height) 569 | 570 | 571 | 572 | # pygame.Surface method wrappers 573 | # These wrappers call their analogous pygame.Surface methods on all Surface objects in this animation. 574 | # They are here for the convenience of the module user. These calls will apply to the transform images, 575 | # and can have their effects undone by called clearTransforms() 576 | # 577 | # It is not advisable to call these methods on the individual Surface objects in self._images. 578 | def _surfaceMethodWrapper(self, wrappedMethodName, *args, **kwargs): 579 | self._makeTransformedSurfacesIfNeeded() 580 | for i in range(len(self._images)): 581 | methodToCall = getattr(self._transformedImages[i], wrappedMethodName) 582 | methodToCall(*args, **kwargs) 583 | 584 | # There's probably a more terse way to generate the following methods, 585 | # but I don't want to make the code even more unreadable. 586 | def convert(self, *args, **kwargs): 587 | # See http://pygame.org/docs/ref/surface.html#Surface.convert 588 | self._surfaceMethodWrapper('convert', *args, **kwargs) 589 | 590 | 591 | def convert_alpha(self, *args, **kwargs): 592 | # See http://pygame.org/docs/ref/surface.html#Surface.convert_alpha 593 | self._surfaceMethodWrapper('convert_alpha', *args, **kwargs) 594 | 595 | 596 | def set_alpha(self, *args, **kwargs): 597 | # See http://pygame.org/docs/ref/surface.html#Surface.set_alpha 598 | self._surfaceMethodWrapper('set_alpha', *args, **kwargs) 599 | 600 | 601 | def scroll(self, *args, **kwargs): 602 | # See http://pygame.org/docs/ref/surface.html#Surface.scroll 603 | self._surfaceMethodWrapper('scroll', *args, **kwargs) 604 | 605 | 606 | def set_clip(self, *args, **kwargs): 607 | # See http://pygame.org/docs/ref/surface.html#Surface.set_clip 608 | self._surfaceMethodWrapper('set_clip', *args, **kwargs) 609 | 610 | 611 | def set_colorkey(self, *args, **kwargs): 612 | # See http://pygame.org/docs/ref/surface.html#Surface.set_colorkey 613 | self._surfaceMethodWrapper('set_colorkey', *args, **kwargs) 614 | 615 | 616 | def lock(self, *args, **kwargs): 617 | # See http://pygame.org/docs/ref/surface.html#Surface.unlock 618 | self._surfaceMethodWrapper('lock', *args, **kwargs) 619 | 620 | 621 | def unlock(self, *args, **kwargs): 622 | # See http://pygame.org/docs/ref/surface.html#Surface.lock 623 | self._surfaceMethodWrapper('unlock', *args, **kwargs) 624 | 625 | 626 | 627 | # Getter and setter methods for properties 628 | def _propGetRate(self): 629 | return self._rate 630 | 631 | def _propSetRate(self, rate): 632 | rate = float(rate) 633 | if rate < 0: 634 | raise ValueError('rate must be greater than 0.') 635 | self._rate = rate 636 | 637 | rate = property(_propGetRate, _propSetRate) 638 | 639 | 640 | def _propGetLoop(self): 641 | return self._loop 642 | 643 | def _propSetLoop(self, loop): 644 | if self.state == PLAYING and self._loop and not loop: 645 | # if we are turning off looping while the animation is playing, 646 | # we need to modify the _playingStartTime so that the rest of 647 | # the animation will play, and then stop. (Otherwise, the 648 | # animation will immediately stop playing if it has already looped.) 649 | self._playingStartTime = TIME_FUNC() - self.elapsed 650 | self._loop = bool(loop) 651 | 652 | loop = property(_propGetLoop, _propSetLoop) 653 | 654 | 655 | def _propGetState(self): 656 | if self.isFinished(): 657 | self._state = STOPPED # if finished playing, then set state to STOPPED. 658 | 659 | return self._state 660 | 661 | def _propSetState(self, state): 662 | if state not in (PLAYING, PAUSED, STOPPED): 663 | raise ValueError('state must be one of pyganim.PLAYING, pyganim.PAUSED, or pyganim.STOPPED') 664 | if state == PLAYING: 665 | self.play() 666 | elif state == PAUSED: 667 | self.pause() 668 | elif state == STOPPED: 669 | self.stop() 670 | 671 | state = property(_propGetState, _propSetState) 672 | 673 | 674 | def _propGetVisibility(self): 675 | return self._visibility 676 | 677 | def _propSetVisibility(self, visibility): 678 | self._visibility = bool(visibility) 679 | 680 | visibility = property(_propGetVisibility, _propSetVisibility) 681 | 682 | 683 | def _propSetElapsed(self, elapsed): 684 | # Set the elapsed time to a specific value. 685 | # NOTE: elapsed is in milliseconds 686 | 687 | if self.state == STOPPED: 688 | self.state = PAUSED 689 | 690 | if self._loop: 691 | elapsed = elapsed % self._startTimes[-1] 692 | else: 693 | elapsed = getBoundedValue(0, elapsed, self._startTimes[-1]) 694 | 695 | rightNow = TIME_FUNC() 696 | self._playingStartTime = rightNow - (elapsed * self.rate) 697 | 698 | if self.state in (PAUSED, STOPPED): 699 | self.state = PAUSED # if stopped, then set to paused 700 | self._pausedStartTime = rightNow 701 | 702 | 703 | def _propGetElapsed(self): 704 | # To prevent infinite recursion, don't use the self.state property, 705 | # just read/set self._state directly because the state getter calls 706 | # this method. 707 | 708 | # NOTE: Elapsed is in milliseconds, not seconds. 709 | assert self._state in (PLAYING, PAUSED, STOPPED), '_state attribute contains an invalid value: %s' % (str(self._state)[:40]) 710 | 711 | # Find out how long ago the play()/pause() functions were called. 712 | if self._state == STOPPED: 713 | # if stopped, then just return 0 714 | return 0 715 | elif self._state == PLAYING: 716 | # if playing, then draw the current frame (based on when the animation 717 | # started playing). If not looping and the animation has gone through 718 | # all the frames already, then draw the last frame. 719 | elapsed = (TIME_FUNC() - self._playingStartTime) * self.rate 720 | elif self._state == PAUSED: 721 | # if paused, then draw the frame that was playing at the time the 722 | # PygAnimation object was paused 723 | elapsed = (self._pausedStartTime - self._playingStartTime) * self.rate 724 | 725 | elapsed = int(elapsed) 726 | 727 | if self._loop: 728 | elapsed = elapsed % self._startTimes[-1] 729 | else: 730 | elapsed = getBoundedValue(0, elapsed, self._startTimes[-1]) 731 | return int(elapsed) 732 | 733 | elapsed = property(_propGetElapsed, _propSetElapsed) 734 | 735 | 736 | def _propGetCurrentFrameNum(self): 737 | # Return the frame number of the frame that will be currently 738 | # displayed if the animation object were drawn right now. 739 | return findStartTime(self._startTimes, self.elapsed) 740 | 741 | 742 | def _propSetCurrentFrameNum(self, frameNum): 743 | # Change the elapsed time to the beginning of a specific frame. 744 | if self._state == STOPPED: 745 | self._state = PAUSED # setting the frame num automatically puts it as paused. 746 | 747 | if self.loop: 748 | frameNum = frameNum % len(self._images) 749 | else: 750 | frameNum = getBoundedValue(0, frameNum, len(self._images)-1) 751 | self.elapsed = self._startTimes[frameNum] 752 | 753 | currentFrameNum = property(_propGetCurrentFrameNum, _propSetCurrentFrameNum) 754 | 755 | 756 | 757 | class PygConductor(object): 758 | def __init__(self, *animations): 759 | assert len(animations) > 0, 'at least one PygAnimation object is required' 760 | 761 | self._animations = [] 762 | self.add(*animations) 763 | 764 | 765 | def add(self, *animations): 766 | if type(animations[0]) == dict: 767 | for k in animations[0].keys(): 768 | self._animations.append(animations[0][k]) 769 | elif type(animations[0]) in (tuple, list): 770 | for i in range(len(animations[0])): 771 | self._animations.append(animations[0][i]) 772 | else: 773 | for i in range(len(animations)): 774 | self._animations.append(animations[i]) 775 | 776 | def _propGetAnimations(self): 777 | return self._animations 778 | 779 | def _propSetAnimations(self, val): 780 | self._animations = val 781 | 782 | animations = property(_propGetAnimations, _propSetAnimations) 783 | 784 | def play(self, startTime=None): 785 | if startTime is None: 786 | startTime = TIME_FUNC() 787 | 788 | for animObj in self._animations: 789 | animObj.play(startTime) 790 | 791 | def pause(self, startTime=None): 792 | if startTime is None: 793 | startTime = TIME_FUNC() 794 | 795 | for animObj in self._animations: 796 | animObj.pause(startTime) 797 | 798 | def stop(self): 799 | for animObj in self._animations: 800 | animObj.stop() 801 | 802 | def reverse(self): 803 | for animObj in self._animations: 804 | animObj.reverse() 805 | 806 | def clearTransforms(self): 807 | for animObj in self._animations: 808 | animObj.clearTransforms() 809 | 810 | def makeTransformsPermanent(self): 811 | for animObj in self._animations: 812 | animObj.makeTransformsPermanent() 813 | 814 | def togglePause(self): 815 | for animObj in self._animations: 816 | animObj.togglePause() 817 | 818 | def nextFrame(self, jump=1): 819 | for animObj in self._animations: 820 | animObj.nextFrame(jump) 821 | 822 | def prevFrame(self, jump=1): 823 | for animObj in self._animations: 824 | animObj.prevFrame(jump) 825 | 826 | def rewind(self, seconds=None): 827 | for animObj in self._animations: 828 | animObj.rewind(seconds) 829 | 830 | def fastForward(self, seconds): 831 | for animObj in self._animations: 832 | animObj.fastForward(seconds) 833 | 834 | def flip(self, xbool, ybool): 835 | for animObj in self._animations: 836 | animObj.flip(xbool, ybool) 837 | 838 | def scale(self, width_height): 839 | for animObj in self._animations: 840 | animObj.scale(width_height) 841 | 842 | def rotate(self, angle): 843 | for animObj in self._animations: 844 | animObj.rotate(angle) 845 | 846 | def rotozoom(self, angle, scale): 847 | for animObj in self._animations: 848 | animObj.rotozoom(angle, scale) 849 | 850 | def scale2x(self): 851 | for animObj in self._animations: 852 | animObj.scale2x() 853 | 854 | def smoothscale(self, width_height): 855 | for animObj in self._animations: 856 | animObj.smoothscale(width_height) 857 | 858 | def convert(self): 859 | for animObj in self._animations: 860 | animObj.convert() 861 | 862 | def convert_alpha(self): 863 | for animObj in self._animations: 864 | animObj.convert_alpha() 865 | 866 | def set_alpha(self, *args, **kwargs): 867 | for animObj in self._animations: 868 | animObj.set_alpha(*args, **kwargs) 869 | 870 | def scroll(self, dx=0, dy=0): 871 | for animObj in self._animations: 872 | animObj.scroll(dx, dy) 873 | 874 | def set_clip(self, *args, **kwargs): 875 | for animObj in self._animations: 876 | animObj.set_clip(*args, **kwargs) 877 | 878 | def set_colorkey(self, *args, **kwargs): 879 | for animObj in self._animations: 880 | animObj.set_colorkey(*args, **kwargs) 881 | 882 | def lock(self): 883 | for animObj in self._animations: 884 | animObj.lock() 885 | 886 | def unlock(self): 887 | for animObj in self._animations: 888 | animObj.unlock() 889 | 890 | 891 | def getBoundedValue(lowerBound, value, upperBound): 892 | # Returns the value within the bounds of the lower and upper bound parameters. 893 | # If value is less than lowerBound, then return lowerBound. 894 | # If value is greater than upperBound, then return upperBound. 895 | # Otherwise, just return value as it is. 896 | if upperBound < lowerBound: 897 | upperBound, lowerBound = lowerBound, upperBound 898 | 899 | if value < lowerBound: 900 | return lowerBound 901 | elif value > upperBound: 902 | return upperBound 903 | return value 904 | 905 | 906 | def findStartTime(startTimes, target): 907 | # With startTimes as a list of sequential numbers and target as a number, 908 | # returns the index of the number in startTimes that preceeds target. 909 | # 910 | # For example, if startTimes was [0, 2000, 4500, 7300, 10000] and target was 6000, 911 | # then findStartTime() would return 2. If target was 12000, returns 4. 912 | assert startTimes[0] == 0, 'The first value in the start times list should always be 0.' 913 | lb = 0 # "lb" is lower bound 914 | ub = len(startTimes) - 1 # "ub" is upper bound 915 | 916 | # handle special cases: 917 | if len(startTimes) == 0: 918 | return 0 919 | if target >= startTimes[-1]: 920 | return ub - 1 921 | 922 | # perform binary search: 923 | while True: 924 | i = int((ub - lb) / 2) + lb 925 | 926 | if startTimes[i] == target or (startTimes[i] < target and startTimes[i+1] > target): 927 | if i == len(startTimes): 928 | return i - 1 929 | else: 930 | return i 931 | 932 | if startTimes[i] < target: 933 | lb = i 934 | elif startTimes[i] > target: 935 | ub = i 936 | 937 | 938 | def splitGif(filename): 939 | # Takes a filename of an animated GIF and returns a list of Image objects of each frame. 940 | # Requires PIL or Pillow to be installed 941 | from PIL import Image 942 | 943 | def iterAnimatedGifFrames(im): 944 | # Iterator for frames in an animated GIF. 945 | try: 946 | i= 0 947 | while 1: 948 | im.seek(i) 949 | imframe = im.copy() 950 | if i == 0: 951 | palette = imframe.getpalette() 952 | else: 953 | imframe.putpalette(palette) 954 | yield imframe 955 | i += 1 956 | except EOFError: 957 | pass 958 | 959 | gifFile = open(str(filename), 'rb') 960 | im = Image.open(gifFile) # passing a file-like object so that we can close it and the unit tests won't complain. 961 | retVal = list(iterAnimatedGifFrames(im)) 962 | gifFile.close() 963 | return retVal 964 | 965 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | 4 | setup( 5 | name='Pyganim', 6 | version=__import__('pyganim').__version__, 7 | url='http://inventwithpython.com/pyganim', 8 | author='Al Sweigart', 9 | author_email='al@inventwithpython.com', 10 | description=('A sprite animation module for Pygame.'), 11 | license='BSD', 12 | packages=['pyganim'], 13 | test_suite='tests', 14 | keywords="pygame sprite animation game 2D graphics", 15 | classifiers=[ 16 | 'Development Status :: 3 - Alpha', 17 | 'Environment :: Win32 (MS Windows)', 18 | 'Environment :: X11 Applications', 19 | 'Environment :: MacOS X', 20 | 'Intended Audience :: Developers', 21 | 'License :: OSI Approved :: BSD License', 22 | 'Operating System :: OS Independent', 23 | 'Programming Language :: Python', 24 | 'Programming Language :: Python :: 2', 25 | 'Programming Language :: Python :: 2.7', 26 | 'Programming Language :: Python :: 3', 27 | 'Programming Language :: Python :: 3.3', 28 | 'Programming Language :: Python :: 3.4', 29 | 'Programming Language :: Python :: 3.5', 30 | ], 31 | ) -------------------------------------------------------------------------------- /tests/banana.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/tests/banana.gif -------------------------------------------------------------------------------- /tests/basicTests.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import sys 3 | import os 4 | import hashlib 5 | import time 6 | import pygame 7 | 8 | sys.path.insert(0, os.path.abspath('..')) 9 | import pyganim 10 | 11 | 12 | runningOnPython2 = sys.version_info[0] == 2 13 | NUM_BOLT_IMAGES = 10 14 | BOLT_DURATIONS = 100 15 | BOLT_WIDTH, BOLT_HEIGHT = pygame.image.load('bolt1.png').get_size() 16 | 17 | 18 | def getTestAnimObj(): 19 | # Returns a standard PygAnimation object. 20 | frames = [('bolt%s.png' % (i), BOLT_DURATIONS) for i in range(1, NUM_BOLT_IMAGES + 1)] 21 | return pyganim.PygAnimation(frames) 22 | 23 | 24 | def compareSurfaces(surf1, surf2): 25 | if surf1.get_size() != surf2.get_size(): 26 | return 'Surfaces have different sizes: %s and %s' % (surf1.get_size(), surf2.get_size()) 27 | 28 | px1 = pygame.PixelArray(surf1) 29 | px2 = pygame.PixelArray(surf2) 30 | 31 | # note: the compare() method seems to be broken in Pygame for Windows Python 3.3 32 | # using this comparison instead: 33 | for x in range(surf1.get_width()): 34 | for y in range(surf1.get_height()): 35 | color1 = surf1.unmap_rgb(px1[x][y]) 36 | color2 = surf2.unmap_rgb(px1[x][y]) 37 | if color1 != color2: 38 | del px1 39 | del px2 40 | return 'Pixel at %s, %s is different: %s and %s' % (x, y, color1, color2) 41 | return None # on success, return None 42 | 43 | 44 | class TestTestImages(unittest.TestCase): 45 | # This is here just to make sure the test images of the lightning bolts haven't changed. 46 | def test_images(self): 47 | # test bolt images 48 | boltSha1Sums = {1: 'd556336b479921148ef5199cf0fa8258501603f3', 49 | 2: '970ae0745ebef5e57c8a04acb7cb98f1478777ca', 50 | 3: '377e34ced2a7594dd591d08e47eb7b87ce3d4e0a', 51 | 4: 'fb4796db338f3e2b88b7821a3acef2b5010f126a', 52 | 5: 'ddc42acb949c2fd8e8d80d3dd9db9df5de9d29c8', 53 | 6: '004995ee0a2f8bb4c7a36d566a351a1e066a61c9', 54 | 7: '11af4a9c01f5619566a3f810a0aeba9462bf0d6f', 55 | 8: '4f39899edefcea6a0196337fbb3b003064ecf8ba', 56 | 9: '1f2acefa7a5c106565274646f0234907a689baf7', 57 | 10: 'e9a41735e799ebb7caafc32ac7e8310dc4bd9742'} 58 | 59 | for i in range(1, NUM_BOLT_IMAGES + 1): 60 | boltFile = open('bolt%s.png' % i, 'rb') 61 | s = hashlib.sha1(boltFile.read()) 62 | boltFile.close() 63 | self.assertEqual(s.hexdigest(), boltSha1Sums[i]) 64 | 65 | # test animated gif 66 | gifFile = open('banana.gif', 'rb') 67 | s = hashlib.sha1(gifFile.read()) 68 | gifFile.close() 69 | self.assertEqual(s.hexdigest(), '65137061474887dfa0f183f2bc118a3e52fc353e') 70 | 71 | # test sprite sheet 72 | spritesheetFile = open('smokeSpritesheet.png', 'rb') 73 | s = hashlib.sha1(spritesheetFile.read()) 74 | spritesheetFile.close() 75 | self.assertEqual(s.hexdigest(), '566cdeb39ffa26e1fb4e0486a16e22de7ac9f6c4') 76 | 77 | # TODO - add code that checks the individual smoke spritesheet images 78 | 79 | 80 | 81 | class TestGeneral(unittest.TestCase): 82 | def test_constructor(self): 83 | # Test ctor with filenames 84 | frames = [('bolt%s.png' % (i), BOLT_DURATIONS) for i in range(1, 11)] 85 | animObj = pyganim.PygAnimation(frames) 86 | self.assertEqual(animObj._state, pyganim.STOPPED) 87 | self.assertEqual(animObj._loop, True) 88 | self.assertEqual(animObj._rate, 1.0) 89 | self.assertEqual(animObj._visibility, True) 90 | self.assertEqual(len(animObj._images), NUM_BOLT_IMAGES) 91 | self.assertEqual(len(animObj._durations), NUM_BOLT_IMAGES) 92 | 93 | # Test ctor with pygame.Surface objects 94 | frames = [(pygame.image.load('bolt%s.png' % (i)), BOLT_DURATIONS) for i in range(1, 11)] 95 | animObj = pyganim.PygAnimation(frames) 96 | self.assertEqual(animObj._state, pyganim.STOPPED) 97 | self.assertEqual(animObj._loop, True) 98 | self.assertEqual(animObj._rate, 1.0) 99 | self.assertEqual(animObj._visibility, True) 100 | self.assertEqual(len(animObj._images), NUM_BOLT_IMAGES) 101 | self.assertEqual(len(animObj._durations), NUM_BOLT_IMAGES) 102 | 103 | 104 | def test_reverse(self): 105 | animObj = getTestAnimObj() 106 | 107 | imageIdsForward = [id(animObj._images[i]) for i in range(NUM_BOLT_IMAGES)] 108 | imageIdsReverse = imageIdsForward[:] 109 | imageIdsReverse.reverse() 110 | 111 | 112 | for i in range(NUM_BOLT_IMAGES): 113 | self.assertEqual(id(animObj._images[i]), imageIdsForward[i]) 114 | animObj.reverse() # reverse 115 | for i in range(NUM_BOLT_IMAGES): 116 | self.assertEqual(id(animObj._images[i]), imageIdsReverse[i]) 117 | animObj.reverse() # reverse again to make sure they're in the original order 118 | for i in range(NUM_BOLT_IMAGES): 119 | self.assertEqual(id(animObj._images[i]), imageIdsForward[i]) 120 | 121 | 122 | def test_getCopy(self): 123 | animObj = getTestAnimObj() 124 | 125 | animCopy = animObj.getCopy() 126 | 127 | self.assertEqual(animObj._state, animCopy._state) 128 | self.assertEqual(animObj._loop, animCopy._loop) 129 | self.assertEqual(animObj._rate, animCopy._rate) 130 | self.assertEqual(animObj._visibility, animCopy._visibility) 131 | self.assertEqual(animObj._durations, animCopy._durations) 132 | 133 | for i in range(NUM_BOLT_IMAGES): 134 | self.assertEqual(id(animObj._images[i]), id(animCopy._images[i])) 135 | self.assertEqual(animObj._durations[i], animCopy._durations[i]) 136 | 137 | 138 | def test_getCopies(self): 139 | animObj = getTestAnimObj() 140 | 141 | animCopies = animObj.getCopies(5) 142 | 143 | for c in range(5): 144 | self.assertEqual(animObj._state, animCopies[c]._state) 145 | self.assertEqual(animObj._loop, animCopies[c]._loop) 146 | self.assertEqual(animObj._rate, animCopies[c]._rate) 147 | self.assertEqual(animObj._visibility, animCopies[c]._visibility) 148 | self.assertEqual(animObj._durations, animCopies[c]._durations) 149 | 150 | for i in range(NUM_BOLT_IMAGES): 151 | self.assertEqual(id(animObj._images[i]), id(animCopies[c]._images[i])) 152 | self.assertEqual(animObj._durations[i], animCopies[c]._durations[i]) 153 | 154 | 155 | def test_blit(self): 156 | animObj = getTestAnimObj() 157 | animObj.pause() # pause the animation on the first frame (bolt1.png) 158 | 159 | surf = pygame.Surface((BOLT_WIDTH, BOLT_HEIGHT)) 160 | 161 | # Test blitting to destination (0, 0) 162 | for dest in ((0, 0), (37, 37)): 163 | for i in range(1, NUM_BOLT_IMAGES + 1): 164 | surf.fill(pygame.Color('black')) 165 | animObj.blit(surf, dest) 166 | 167 | image = pygame.image.load('bolt%s.png' % i) 168 | orig = pygame.Surface((BOLT_WIDTH, BOLT_HEIGHT)) 169 | orig.fill(pygame.Color('black')) 170 | orig.blit(image, dest) 171 | 172 | self.assertEqual((dest, None), (dest, compareSurfaces(surf, orig))) 173 | 174 | animObj.nextFrame() 175 | 176 | 177 | def test_blitFrameNum(self): 178 | animObj = getTestAnimObj() 179 | 180 | surf = pygame.Surface((BOLT_WIDTH, BOLT_HEIGHT)) 181 | 182 | # Test blitting to destination (0, 0) 183 | for dest in ((0, 0), (37, 37)): 184 | for frame in range(1, NUM_BOLT_IMAGES + 1): 185 | surf.fill(pygame.Color('black')) 186 | animObj.blitFrameNum(frame, surf, dest) 187 | 188 | image = pygame.image.load('bolt%s.png' % frame) 189 | orig = pygame.Surface((BOLT_WIDTH, BOLT_HEIGHT)) 190 | orig.fill(pygame.Color('black')) 191 | orig.blit(image, dest) 192 | 193 | self.assertEqual((dest, None), (dest, compareSurfaces(surf, orig))) 194 | 195 | 196 | def test_blitFrameAtTime(self): 197 | animObj = getTestAnimObj() 198 | 199 | surf = pygame.Surface((BOLT_WIDTH, BOLT_HEIGHT)) 200 | 201 | # Test blitting to destination (0, 0) 202 | for dest in ((0, 0), (37, 37)): 203 | timeSetting = BOLT_DURATIONS / 2.0 204 | for i in range(1, NUM_BOLT_IMAGES + 1): 205 | surf.fill(pygame.Color('black')) 206 | animObj.blitFrameAtTime(timeSetting, surf, dest) 207 | timeSetting += BOLT_DURATIONS 208 | 209 | image = pygame.image.load('bolt%s.png' % i) 210 | orig = pygame.Surface((BOLT_WIDTH, BOLT_HEIGHT)) 211 | orig.fill(pygame.Color('black')) 212 | orig.blit(image, dest) 213 | 214 | self.assertEqual((dest, None), (dest, compareSurfaces(surf, orig))) 215 | 216 | 217 | def test_isFinished(self): 218 | # test on animation that doesn't loop 219 | animObj = getTestAnimObj() 220 | animObj.loop = False 221 | animObj.play() 222 | self.assertEqual(animObj.isFinished(), False) 223 | time.sleep((BOLT_DURATIONS * (NUM_BOLT_IMAGES + 1)) / 1000.0) # should be enough time to finish a single run through of the animation 224 | self.assertEqual(animObj.isFinished(), True) 225 | 226 | # test on animation that loops 227 | animObj = getTestAnimObj() 228 | animObj.loop = True 229 | animObj.play() 230 | self.assertEqual(animObj.isFinished(), False) 231 | time.sleep((BOLT_DURATIONS * (NUM_BOLT_IMAGES + 1)) / 1000.0) # should be enough time to finish a single run through of the animation 232 | self.assertEqual(animObj.isFinished(), False) # looping animations are never finished 233 | 234 | 235 | def test_getFrame(self): 236 | animObj = getTestAnimObj() 237 | 238 | for i in range(NUM_BOLT_IMAGES): 239 | frame = animObj.getFrame(i) 240 | image = pygame.image.load('bolt%s.png' % (i + 1)) 241 | self.assertEqual(None, compareSurfaces(frame, image)) 242 | 243 | 244 | def test_getCurrentFrame(self): 245 | animObj = getTestAnimObj() 246 | 247 | for i in range(NUM_BOLT_IMAGES): 248 | frame = animObj.getCurrentFrame() 249 | image = pygame.image.load('bolt%s.png' % (i + 1)) 250 | self.assertEqual(None, compareSurfaces(frame, image)) 251 | 252 | animObj.nextFrame() 253 | 254 | 255 | def test_framesAreSameSize(self): 256 | # the standard test animation object has same-sized frames 257 | self.assertTrue(getTestAnimObj().framesAreSameSize()) 258 | 259 | diffSized = pyganim.PygAnimation([(pygame.Surface((100, 100)), BOLT_DURATIONS), (pygame.Surface((100, 200)), BOLT_DURATIONS)]) 260 | self.assertFalse(diffSized.framesAreSameSize()) 261 | 262 | 263 | def test_nextFrame_prevFrame(self): 264 | animObj = getTestAnimObj() 265 | 266 | expectedFrameNum = 0 267 | self.assertEqual(expectedFrameNum, animObj.currentFrameNum) 268 | # test nextFrame() 269 | for i in range(NUM_BOLT_IMAGES * 2): 270 | animObj.nextFrame() 271 | expectedFrameNum = (expectedFrameNum + 1) % len(animObj._images) 272 | self.assertEqual(expectedFrameNum, animObj.currentFrameNum) 273 | 274 | # test prevFrame() 275 | for i in range(NUM_BOLT_IMAGES * 2): 276 | animObj.prevFrame() 277 | expectedFrameNum = (expectedFrameNum - 1) % len(animObj._images) 278 | self.assertEqual(expectedFrameNum, animObj.currentFrameNum) 279 | 280 | # test nextFrame() with jump argument 281 | for i in range(NUM_BOLT_IMAGES * 2): 282 | animObj.nextFrame(3) 283 | expectedFrameNum = (expectedFrameNum + 3) % len(animObj._images) 284 | self.assertEqual(expectedFrameNum, animObj.currentFrameNum) 285 | 286 | # test prevFrame() with jump argument 287 | for i in range(NUM_BOLT_IMAGES * 2): 288 | animObj.prevFrame(3) 289 | expectedFrameNum = (expectedFrameNum - 3) % len(animObj._images) 290 | self.assertEqual(expectedFrameNum, animObj.currentFrameNum) 291 | 292 | def test_play_pause(self): 293 | # with looping 294 | animObj = getTestAnimObj() 295 | self.assertTrue(animObj.loop) 296 | for i in range(1, NUM_BOLT_IMAGES + 3): # go a bit past the last frame 297 | animObj.play() 298 | time.sleep(BOLT_DURATIONS / 1000.0) 299 | animObj.pause() 300 | self.assertEqual(i % NUM_BOLT_IMAGES, animObj.currentFrameNum) 301 | 302 | # without looping 303 | animObj = getTestAnimObj() 304 | animObj.loop = False 305 | for i in range(1, NUM_BOLT_IMAGES + 3): # go a bit past the last frame 306 | animObj.play() 307 | time.sleep(BOLT_DURATIONS / 1000.0) 308 | animObj.pause() 309 | if i >= NUM_BOLT_IMAGES: 310 | self.assertEqual(NUM_BOLT_IMAGES - 1, animObj.currentFrameNum) # with looping off, the currentFrameNum does not advance after the last frame 311 | else: 312 | self.assertEqual(i, animObj.currentFrameNum) 313 | 314 | def test_togglePause(self): 315 | # with looping 316 | animObj = getTestAnimObj() 317 | self.assertTrue(animObj.loop) 318 | for i in range(1, NUM_BOLT_IMAGES + 3): # go a bit past the last frame 319 | animObj.togglePause() 320 | time.sleep(BOLT_DURATIONS / 1000.0) 321 | animObj.togglePause() 322 | self.assertEqual(i % NUM_BOLT_IMAGES, animObj.currentFrameNum) 323 | 324 | # without looping 325 | animObj = getTestAnimObj() 326 | animObj.loop = False 327 | for i in range(1, NUM_BOLT_IMAGES + 3): # go a bit past the last frame 328 | animObj.togglePause() 329 | time.sleep(BOLT_DURATIONS / 1000.0) 330 | animObj.togglePause() 331 | if i >= NUM_BOLT_IMAGES: 332 | self.assertEqual(NUM_BOLT_IMAGES - 1, animObj.currentFrameNum) # with looping off, the currentFrameNum does not advance after the last frame 333 | else: 334 | self.assertEqual(i, animObj.currentFrameNum) 335 | 336 | 337 | def test_getMaxSize(self): 338 | animObj = getTestAnimObj() 339 | self.assertEqual((BOLT_WIDTH, BOLT_HEIGHT), animObj.getMaxSize()) 340 | 341 | mixedSizesObj = pyganim.PygAnimation([(pygame.Surface((100, 10)), 1), (pygame.Surface((10, 200)), 1000)]) 342 | self.assertEqual((100, 200), mixedSizesObj.getMaxSize()) 343 | 344 | 345 | def test_getRect(self): 346 | animObj = getTestAnimObj() 347 | r = animObj.getRect() 348 | self.assertEqual((BOLT_WIDTH, BOLT_HEIGHT), r.size) 349 | 350 | mixedSizesObj = pyganim.PygAnimation([(pygame.Surface((100, 10)), 1), (pygame.Surface((10, 200)), 1000)]) 351 | r = mixedSizesObj.getRect() 352 | self.assertEqual((100, 200), r.size) 353 | 354 | 355 | def test_rewind(self): 356 | animObj = getTestAnimObj() 357 | 358 | animObj.play() 359 | time.sleep(0.2) 360 | animObj.pause() 361 | animObj.rewind() 362 | self.assertEqual(animObj.elapsed, 0) 363 | 364 | animObj.play() 365 | time.sleep(0.2) 366 | animObj.pause() 367 | origElapsed = animObj.elapsed 368 | animObj.rewind(100) 369 | self.assertEqual(animObj.elapsed, origElapsed - 100) 370 | 371 | 372 | def test_fastForward(self): 373 | animObj = getTestAnimObj() 374 | self.assertEqual(animObj.state, pyganim.STOPPED) 375 | animObj.fastForward(375) 376 | self.assertEqual(animObj.elapsed, 375) 377 | self.assertEqual(animObj.state, pyganim.PAUSED) 378 | 379 | animObj = getTestAnimObj() 380 | animObj.play() 381 | time.sleep(0.2) 382 | animObj.pause() 383 | origElapsed = animObj.elapsed 384 | animObj.rewind(100) 385 | self.assertEqual(animObj.elapsed, origElapsed - 100) 386 | 387 | def test_loadingAnimatedGif(self): 388 | animObj = pyganim.PygAnimation('banana.gif') # IT'S PEANUT BUTTER JELLY TIME 389 | self.assertEqual(len(animObj._images), 8) 390 | for i in range(8): 391 | self.assertEqual(animObj._durations[i], 100) 392 | 393 | 394 | class TestSpritesheet(unittest.TestCase): 395 | def test_loading(self): 396 | """ 397 | NUM_SPRITES = 10 398 | SMOKE_WIDTH = 96 399 | SMOKE_HEIGHT = 94 400 | smokeImages = [pygame.image.load('smoke%s.png' % i) for i in range(NUM_SPRITES)] 401 | 402 | 403 | # basic loading test using width/height arguments 404 | sheet = pyganim.getImagesFromSpriteSheet('smokeSpritesheet.png', width=SMOKE_WIDTH, height=SMOKE_HEIGHT) 405 | animObj = pyganim.PygAnimation(sheet.frames) 406 | 407 | self.assertEqual(len(animObj._images), NUM_SPRITES) 408 | self.assertTrue(animObj.framesAreSameSize()) 409 | self.assertEqual(animObj._images[0].get_width(), SMOKE_WIDTH) 410 | self.assertEqual(animObj._images[0].get_height(), SMOKE_HEIGHT) 411 | 412 | for i in range(NUM_SPRITES): 413 | self.assertEqual((i, None), (i, compareSurfaces(animObj._images[i], smokeImages[i]))) 414 | 415 | # basic loading test using width/height arguments with default height 416 | sheet = pyganim.getImagesFromSpriteSheet('smokeSpritesheet.png', width=SMOKE_WIDTH) 417 | animObj = pyganim.PygAnimation(sheet.frames) 418 | 419 | self.assertEqual(len(animObj._images), NUM_SPRITES) 420 | self.assertTrue(animObj.framesAreSameSize()) 421 | self.assertEqual(animObj._images[0].get_width(), SMOKE_WIDTH) 422 | self.assertEqual(animObj._images[0].get_height(), SMOKE_HEIGHT) 423 | 424 | for i in range(NUM_SPRITES): 425 | self.assertEqual((i, None), (i, compareSurfaces(animObj._images[i], smokeImages[i]))) 426 | 427 | # basic loading test using row/col arguments 428 | sheet = pyganim.getImagesFromSpriteSheet('smokeSpritesheet.png', rows=1, cols=NUM_SPRITES) 429 | animObj = pyganim.PygAnimation(sheet.frames) 430 | 431 | self.assertEqual(len(animObj._images), NUM_SPRITES) 432 | self.assertTrue(animObj.framesAreSameSize()) 433 | self.assertEqual(animObj._images[0].get_width(), SMOKE_WIDTH) 434 | self.assertEqual(animObj._images[0].get_height(), SMOKE_HEIGHT) 435 | 436 | for i in range(NUM_SPRITES): 437 | self.assertEqual((i, None), (i, compareSurfaces(animObj._images[i], smokeImages[i]))) 438 | 439 | # basic loading test using row/col arguments with default rows 440 | sheet = pyganim.getImagesFromSpriteSheet('smokeSpritesheet.png', cols=NUM_SPRITES) 441 | animObj = pyganim.PygAnimation(sheet.frames) 442 | 443 | self.assertEqual(len(animObj._images), NUM_SPRITES) 444 | self.assertTrue(animObj.framesAreSameSize()) 445 | self.assertEqual(animObj._images[0].get_width(), SMOKE_WIDTH) 446 | self.assertEqual(animObj._images[0].get_height(), SMOKE_HEIGHT) 447 | 448 | for i in range(NUM_SPRITES): 449 | self.assertEqual((i, None), (i, compareSurfaces(animObj._images[i], smokeImages[i]))) 450 | 451 | # loading using rects arguments 452 | rects = [] 453 | index = 0 454 | for x in range(0, SMOKE_WIDTH * NUM_SPRITES, SMOKE_WIDTH): 455 | rects.append((x, 0, SMOKE_WIDTH, SMOKE_HEIGHT, index)) 456 | index += 1 457 | sheet = pyganim.getImagesFromSpriteSheet('smokeSpritesheet.png', rects=rects) 458 | animObj = pyganim.PygAnimation(sheet.frames) 459 | 460 | self.assertEqual(len(animObj._images), NUM_SPRITES) 461 | self.assertTrue(animObj.framesAreSameSize()) 462 | self.assertEqual(animObj._images[0].get_width(), SMOKE_WIDTH) 463 | self.assertEqual(animObj._images[0].get_height(), SMOKE_HEIGHT) 464 | 465 | for i in range(NUM_SPRITES): 466 | self.assertEqual((i, None), (i, compareSurfaces(animObj._images[i], smokeImages[i]))) 467 | 468 | # loading using rects arguments with indexes in an arbitrary order 469 | rects = [] 470 | indexes = [9, 3, 6, 0, 1, 2, 4, 7, 5, 8] 471 | i = 0 472 | for x in range(0, SMOKE_WIDTH * NUM_SPRITES, SMOKE_WIDTH): 473 | rects.append((x, 0, SMOKE_WIDTH, SMOKE_HEIGHT, indexes[i])) 474 | i += 1 475 | sheet = pyganim.getImagesFromSpriteSheet('smokeSpritesheet.png', rects=rects) 476 | animObj = pyganim.PygAnimation(sheet.frames) 477 | 478 | self.assertEqual(len(animObj._images), NUM_SPRITES) 479 | self.assertTrue(animObj.framesAreSameSize()) 480 | self.assertEqual(animObj._images[0].get_width(), SMOKE_WIDTH) 481 | self.assertEqual(animObj._images[0].get_height(), SMOKE_HEIGHT) 482 | 483 | for i in range(NUM_SPRITES): 484 | self.assertEqual((i, None), (i, compareSurfaces(animObj._images[i], smokeImages[indexes[i]]))) 485 | """ 486 | 487 | 488 | 489 | class MiscTests(unittest.TestCase): 490 | # This is here just to make sure the test images of the lightning bolts haven't changed. 491 | def test_getBoundedValue(self): 492 | self.assertEqual(pyganim.getBoundedValue(0, 5, 10), 5) 493 | self.assertEqual(pyganim.getBoundedValue(0, 0, 10), 0) 494 | self.assertEqual(pyganim.getBoundedValue(0, -5, 10), 0) 495 | self.assertEqual(pyganim.getBoundedValue(0, 10, 10), 10) 496 | self.assertEqual(pyganim.getBoundedValue(0, 15, 10), 10) 497 | 498 | self.assertEqual(pyganim.getBoundedValue(0, -5, -10), -5) 499 | self.assertEqual(pyganim.getBoundedValue(0, 0, -10), 0) 500 | self.assertEqual(pyganim.getBoundedValue(0, 5, -10), 0) 501 | self.assertEqual(pyganim.getBoundedValue(0, -10, -10), -10) 502 | self.assertEqual(pyganim.getBoundedValue(0, -15, -10), -10) 503 | 504 | self.assertEqual(pyganim.getBoundedValue(-10, -5, 0), -5) 505 | self.assertEqual(pyganim.getBoundedValue(-10, 0, 0), 0) 506 | self.assertEqual(pyganim.getBoundedValue(-10, 5, 0), 0) 507 | self.assertEqual(pyganim.getBoundedValue(-10, -10, 0), -10) 508 | self.assertEqual(pyganim.getBoundedValue(-10, -15, 0), -10) 509 | 510 | def test_findStartTime(self): 511 | st = [0, 1000, 2000, 4000, 8000, 16000] 512 | self.assertEqual(pyganim.findStartTime(st, 0), 0) 513 | self.assertEqual(pyganim.findStartTime(st, 999), 0) 514 | self.assertEqual(pyganim.findStartTime(st, 1000), 1) 515 | self.assertEqual(pyganim.findStartTime(st, 1001), 1) 516 | self.assertEqual(pyganim.findStartTime(st, 1999), 1) 517 | self.assertEqual(pyganim.findStartTime(st, 2000), 2) 518 | self.assertEqual(pyganim.findStartTime(st, 2001), 2) 519 | self.assertEqual(pyganim.findStartTime(st, 3999), 2) 520 | self.assertEqual(pyganim.findStartTime(st, 4000), 3) 521 | self.assertEqual(pyganim.findStartTime(st, 9999999), 4) 522 | 523 | 524 | if __name__ == '__main__': 525 | unittest.main() -------------------------------------------------------------------------------- /tests/bolt1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/tests/bolt1.png -------------------------------------------------------------------------------- /tests/bolt10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/tests/bolt10.png -------------------------------------------------------------------------------- /tests/bolt2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/tests/bolt2.png -------------------------------------------------------------------------------- /tests/bolt3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/tests/bolt3.png -------------------------------------------------------------------------------- /tests/bolt4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/tests/bolt4.png -------------------------------------------------------------------------------- /tests/bolt5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/tests/bolt5.png -------------------------------------------------------------------------------- /tests/bolt6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/tests/bolt6.png -------------------------------------------------------------------------------- /tests/bolt7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/tests/bolt7.png -------------------------------------------------------------------------------- /tests/bolt8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/tests/bolt8.png -------------------------------------------------------------------------------- /tests/bolt9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/tests/bolt9.png -------------------------------------------------------------------------------- /tests/smoke0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/tests/smoke0.png -------------------------------------------------------------------------------- /tests/smoke1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/tests/smoke1.png -------------------------------------------------------------------------------- /tests/smoke2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/tests/smoke2.png -------------------------------------------------------------------------------- /tests/smoke3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/tests/smoke3.png -------------------------------------------------------------------------------- /tests/smoke4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/tests/smoke4.png -------------------------------------------------------------------------------- /tests/smoke5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/tests/smoke5.png -------------------------------------------------------------------------------- /tests/smoke6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/tests/smoke6.png -------------------------------------------------------------------------------- /tests/smoke7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/tests/smoke7.png -------------------------------------------------------------------------------- /tests/smoke8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/tests/smoke8.png -------------------------------------------------------------------------------- /tests/smoke9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/tests/smoke9.png -------------------------------------------------------------------------------- /tests/smokeSpritesheet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asweigart/pyganim/3028a51b5ecb8f1134f0ea5dc774ce6786b2f897/tests/smokeSpritesheet.png --------------------------------------------------------------------------------