Pygame tutorial
Step 000 - Installation, Documentation and Examples
- Installation
install python
Pygame is not shipped together with Python, it is a 3rd-party-package.
To install it on your computer you need a version of Python installed correctly on your computer that works good together with Pygame:
Python2.x (recommended)
or Python 3.x (experimental)
<note warning>from Pygame FAQ: Does Pygame work with Python 3?
Mostly yes.
Only the scrap, _movie, surfarray for Numeric, and threads modules have not been ported.
The Pygame alternative, pgreloaded (Pygame Reloaded), also supports Python 3.
</note>
Install Pygame
Linux Users of Ubuntu Linux can type: sudo apt-get install python-pygame
or install pygame using synaptic or the ubuntu software center.
Windows Install pygame from http://www.pygame.org/download.shtml
Mac Install pygame from http://www.pygame.org/download.shtml
source installing from source : http://www.pygame.org/download.shtml
- documentation
You can view the pygame documentation online at:
http://www.pygame.org/docs/
If you have no internet access, you can still read pygame's documentation offline in your browser.
Just open the file index.html
in pygame's docs
folder.
The File should be located inside your python installation: /dist-packages/pygame/docs/index.html
Linux /usr/lib/python2.6/dist-packages/pygame/docs/index.html
Windows ?
Mac ?
- Examples
pygame will install a bunch of highly useful examples inside the folder /dist-packages/pygame/examples
.
It is a good idea to copy the whole example
folder (including his sub-folders data
and macosx
) to another location like to your desktop.
In that way, you can modify all pygame examples without destroying the originals.
A short description of each pygame example can be found at pygames documentation homepage:
http://www.pygame.org/docs/ref/examples.html
Enjoy
Now it is time to enjoy your successfull installation of python and pygame.
How about a round of mindless alien shooting ?
Open a terminal and type:
python -m pygame.examples.aliens
- The Editor and Other Tools
Text Editor
To modify the python source code files (.py) in your favourite text editor, make sure that you are able to load, modify and start python files using your editor.
Try to open and start the aliens example from within your text editor.
Every OS is shipped with at least one very simple text editor, and python usually installs the IDLE text editor on your system.
For a list of text editors and IDE and their features see http://wiki.python.org/moin/PythonEditors
Graphic Editor
For the next chapters, it is helpful if you know how to paint and modify images, especially in the .png format.
Most image editors / viewers can display those graphics (even your webbrowser) but not every image editor can handle transparency (transparent backgrounds).
You can download and install the free and open source image editor GIMP for every operation system, althoug you will only need very few of it's functions for the next chapters.
A simple image viewer with rudimentary editing functions such as IrfanView for Windows or gthumb for Linux may be sufficient.
Sound
For the next chapters, it is helpful if you can generate and manipulate (and record!) sound effects on your computer.
You need functions to cut parts of recorded sound effects and ideally a noise reduction.
Both functions are provided by the free and open source program audacity, wich can also generate sounds.
For an specific sound effect generator, you may try out http://www.drpetter.se/project_sfxr.html
- Download from Github
Source code examples and other necessary files like graphics and sounds will be hosted not on this wiki, but instead on github.
While it is not necessary to create an account at github.com to download all code examples for this book, it may be useful if you want to publish your edits / comments back into this wiki.
What you should know however is how to download code and files directly from github:
Open this URL in your browser: https://github.com/horstjens/ThePythonGameBook
and click on the big Downloads icon.
choose Download .tar.gz for Linux, or Download .zip for Windows
extract the archive file on your Desktop.
All neccessary files for the next chapters are inside the pygame
folder
Now you are ready, start with step001 and write your first pygame program !
Step 001 - Pygame Basics
The next few code examples demonstrate how to set up a working pygame program and move surfaces or sprites (2-dimensional shapes) around.
The important thing to understand is that (in those pygame examples) there is no real movement.
In fact, what you see as movement is an illusion only happening in your brain.
What you do see is a fast sequence of images, beamed at you from your computer monitor.
The same happens if you watch TV, cinema or even a flip book - or any computer game.
You see lots of similar pictures at a high frequency or frame rate (like 60 pictures per second).
If you see, let's say, a picture of a blue ball in the upper left corner in the first picture (or frame) and thus at each following picture (frame) the ball is a bit more right than in the previous picture, you think that the ball is moving from left to right.
Code Discussion and Source Code
In the next chapters (steps) you can always expect a short code discussion where the new concepts and matching code lines are explained followed by the complete source code example at the end of the page.
The discussion of the code is written before the complete source code because the source code examples tend to be rather long.
For best results do not copy & paste the source code examples but instead click on the name of the source code example (on top of the source code) to download and save it.
If your browser or operating system allows you to open the source code example directly with a Python editor like geany or IDLE you can run the code examples directly from your editor.
Step 002 - Window and Framerate
This first pygame example is very simple: It opens a window and displays the frame rate.
You can close
the program by pressing the ESCAPE key (on the top left of most keyboards) or by clicking on the close button in the window menubar.
Screenshot of 002_display_fps.py Screenshot of the improved variant 002_display_fps_pretty.py
Where Is the Source Code ?
The source code example to this “step” is always at the end of the page, at source code on github.
The reason is that source code examples can be several 100 lines long and (like in this case) come in several variants or include several files.
In ThePythonGameBook the full source code is always the last heading on a page.
Code Discussion
Allow Special Chars
The first line is a very special kind of comment.
If written in the first or second line,
it allows the use of (national) special characters1) inside strings and comments in python2.x
This special comment structure is no longer necessary in python3.x
# -*- coding: utf-8 -*-
Multi-Line Docstring
A docstring in triple quotes allows you to span a comment over several lines.
Use comments to describe what the program should do.
This cannot be overstated, use comments to describe what the code is supposed to do.
You can also provide an email address, a project webpage or information about the author.
"""The following program simplifies
gimmicks and enhances unicorns."""
The docstring with triple quotes explains what the program does.
Import and Start Pygame
Because pygame is not yet included into python it must be correctly installed and imported.
During this wiki book I will always write import pygame
and never import pygame as pg
or from pygame import *
.
import pygame
pygame.init()
Screen and Background
The visible pygame window is called screen (in nearly all pygame code examples) and it describes what you see.
Pygame has lot of commands to create and handle rectangular graphical areas, called pygame surfaces.
Imagine a sheet of paper or a napkin, where you can draw or write.
If you glue (blit) those papers on a single page of a flip_book (the screen) you would make them visible.
In this code example, the background surface is filled with white colour - pygame colours are always tuples of (red, green, blue) values where each value can be an integer from 0 to 255.
The tuple (255,0,0) describes the red color (full red and nothing else), while all colours together (255,255,255) describe white.
Black is (0,0,0).
After painting and writing on a surface but before blitting the surface it is a good idea to .convert()
the surface.
This helps pygame to store the surface in memory optimized for fast blitting.
Note that surfaces with transparency need .convert_alpha()
instead.
screen = pygame.display.set_mode((640,480)) # Set screen size of pygame window
background = pygame.Surface(screen.get_size()) # Create empty pygame surface
background.fill((255,255,255)) # Fill the background white color (red,green,blue)
background = background.convert() # Convert Surface to make blitting faster
Blitting the Background
After creating the background surface (and filling it with white color) this surface is not visible by itself.
Only after blitting (~painting) it to the visible screen surface you actually see the white background.
If you comment out this line you will see a black window only - freshly created surfaces like the screen are black by default.
screen.blit(background, (0, 0))
Here, the destination surface is 'screen' on which you blit the surface 'background' to position (0, 0)
so that the upper left corner of 'background' is on position (0, 0) on the 'screen' surface.
Event Handler
Pygame creates a lot of events all the time (the mouse is moved, a key is pressed etc).
You can react on those events with an event handler like the following example.
If a key is pressed, the Pygame event queue registers a KEYDOWN event.
There exists another method to ask what keys are pressed at the moment, see the next examples.
The Pygame documentation at http://www.pygame.org/docs/ref/key.html lists all valid keyboard constants.
for event in pygame.event.get():
if event.type == pygame.QUIT:
mainloop = False # pygame window closed by user
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
mainloop = False # user pressed ESC
Understanding Time
The object pygame.time.Clock()
(which will be stored in the variable clock
) has a method tick()
which returns the amount of milliseconds passed since the last frame.
Inside the mainloop, the code:
milliseconds = clock.tick(FPS) # do not go faster than this framerate
stores the number of milliseconds into the variable milliseconds
.
The next code line
playtime += milliseconds / 1000.0 # add seconds to playtime
calculates the number of seconds passed (by dividing by 1000.0) and increases the value of the variable playtime
.
Ideas
Change the screen resolution.
Try blitting the background in the middle of the screen, not in the topleft corner.
See keycodes and let the program print something as soon as you press the <key>space</key> key.
Documentation
http://www.pygame.org/docs/ref/event.html#pygame.event.get
http://www.pygame.org/docs/ref/time.html
http://www.pygame.org/docs/ref/key.html
http://www.pygame.org/docs/ref/surface.html#Surface.fill
http://www.pygame.org/docs/ref/surface.html#Surface.blit
http://www.pygame.org/docs/ref/surface.html#Surface.convert
Source Code on Github
To run both variants of this example you need:
file | in folder | download | comment |
002_display_fps.py pygame Download the whole Archive with all files from Github:
https://github.com/horstjens/ThePythonGameBook/archives/master simple version
002_display_fps_pretty.py pygame OOP version
There exist 2 code examples, a simple example from Horst and an object oriented variant of yipyip.
Both versions have a similar functionality but implement this in a different manner.
If you are new to python/pygame programming and have not yet worked with the class command you may want to try out the simple variant and come back if you are more familiar with>
Simple Version
This is the most basic meaningful pygame program.
No fancy stuff.
View/Edit/Download the file directly in Github: https://github.com/horstjens/ThePythonGameBook/blob/master/pygame/002_display_fps.py
OOP Version
An object orientated version, which uses the pygame.font module for displaying
frame rate and game duration as text.
(See also →pygame text)
https://github.com/horstjens/ThePythonGameBook/blob/master/pygame/002_display_fps_pretty.py
Click reload in your browser if you see no code.
Step 003 - Surfaces and Drawing
Screenshot of 003_static_blit.py Screenshot of the improved variant 003_static_blit_pretty.py
This program is blitting two different pygame surfaces into the pygame window (called screen):
The (white) background surface and
the (blue-black) ball surface
Unlike the usual Cartesian_coordinate_system, Pygame use an x,y coordinates system where the position (0,0) is defined as the top left corner of the screen.
Moving down means having a higher y value, moving to the right means having a higher x value.
vs.
Cartesian coordinate system.
The point (0,0) is in the middle Pygame coordinate system.
The point (0,0) is in the top left corner
Code Discussion
Surfaces
Please note that despite the name the ball surface is a rectangular surface.
Onto this surface the program draws a blue circle.
Because the ball surface was not filled like the background surface, it remains black (Red Green Blue value: (0,0,0)).
Also note that in this program nothing is blitted inside the mainloop, all painting and blitting occurs before the mainloop.
The ball is visible on the screen because the background was blitted first and then the background the ball surface was blitted (quasi-on-top).
Reverse the order of blitting and you should see only the white background.
Blitting the background on the screen is a good method to clean all graphic artefacts from a screen.
Drawing
Pygame knows a lot of drawing functions.
In this code example, the pygame.draw.circle
function is used.
All of Pygame's drawing function need a pygame surface to draw on.
Think of those surfaces like papers or beer mats.
The top left corner of the Pygame surface has the coordinate (0,0).
Let's take a look at the pygame.draw_circle
documentation and the code lines in the source code example:
# create a rectangular surface for the ball
ballsurface = pygame.Surface((50,50))
# draw blue filled circle on ball surface
pygame.draw.circle(ballsurface, (0,0,255), (25,25),25)
In the first code line, a pygame surface is created with the dimensions 50 pixel width (x-axis) and 50 pixel height (y-axis).
This little pygame surface is named ball.
The second code line draws a circle on the surface ball with the colours
0 (no portion of red), 0 (no portion of green), 255 (full portion of blue) at the position (25,25) counted from the top left corner of the ball surface.
(25,25) is exactly the middle of (50,50).
This circle has a radius of 25 pixels, and because there is no line width argument, pygame falls back to the default width of 0 which results in a blue filled circle.
Some Drawing Functions
In the official pygame documentation, the chapter draw lists 9 drawing functions.
4 of them are used in this code example to draw green figures on the background surface.
Try to change the values and see what happens:
#------- try out some pygame draw functions --------
# pygame.draw.rect(Surface, color, Rect, width=0): return Rect
pygame.draw.rect(background, (0,255,0), (50,50,100,25)) # rect: (x1, y1, width, height)
# pygame.draw.circle(Surface, color, pos, radius, width=0): return Rect
pygame.draw.circle(background, (0,200,0), (200,50), 35)
# pygame.draw.polygon(Surface, color, pointlist, width=0): return Rect
pygame.draw.polygon(background, (0,180,0), ((250,100),(300,0),(350,50)))
# pygame.draw.arc(Surface, color, Rect, start_angle, stop_angle, width=1): return Rect
pygame.draw.arc(background, (0,150,0),(400,10,150,100), 0, 3.14) # radiant instead of grad
Ideas
Paint a giant pink circle on the middle of the screen
Find out wich line you must out-comment (using #) to remove the blue ball and the black square
Can you use the pygame.draw.polygon function to draw a pentagram:
Can you make an arc from the topright to the lower-left corner1)?
Use python's range function to draw a pretty pattern across the screen.
Start with this code and expand to all 4 corners:
for point in range(0,641,64): # range(start, stop, step)
pygame.draw.line(background, (255,0,255), (0,0), (480, point), 1)
Can you use the point variable also inside the color tuple to make each line with a different color?
Documentation
http://www.pygame.org/docs/ref/draw.html
http://www.pygame.org/docs/ref/surface.html#Surface.blit
http://www.pygame.org/docs/ref/surface.html#Surface.convert
http://www.pygame.org/docs/ref/surface.html#Surface.convert_alpha
Source Code on Github
file | in folder | download | comment |
003_static_blit.py pygame Download the whole Archive with all files from Github:
https://github.com/horstjens/ThePythonGameBook/archives/master simple version
003_static_blit_pretty.py pygame pretty version
Simple Version
This version draws some lines and blits a surface.
no fancy stuff.
View/Edit/Download the file directly in Github: https://github.com/horstjens/ThePythonGameBook/blob/master/pygame/003_static_blit.py
OOP Version
Improvements:
using 2 different class objects, one for the game, one for the ball.
See →sprites
better text display, see →text
https://github.com/horstjens/ThePythonGameBook/blob/master/pygame/003_static_blit_pretty.py
Step 004 - Colorkey and Alpha Values
004_colorkey.py
004_alphademo.py
004_alphademo_pretty.py
004_per-pixel-alphademo.py
In this page you will find 4 code examples: 004_colorkey.py , 004_alphademo.py , 004_alphademo_pretty.py and 004_per-pixel-alphademo.py.
All examples demonstrate how to work with transparency and pygame surfaces.
004_colorkey.py is simple to understand.
Compare it with 003_static_blit.py on the previous page and look at the blue circle.
Do you notice a difference ? Right, the ugly black corners around the blue circle are gone.
This is thanks to pygame's set_colorkey command.
By defining a specific color on a pygame surface as colorkey, you make this color (in our example, the black color) transparent.
There are 2 kind of image files: Those with in-build transparency , and those without.
Only graphic files of the file type .png and .gif can have transparency1).
Other file tpyes like the popular .jpg format can not have transparency.
If you want transparency for such images you must add them manually with pygame.
004_alphademo.py shows you how to do that.
Look at the slowly blinking colormonster (3rd from the left the second row).
You can see the amount of alpha-value (the white text below the picture) necessary to make a picture fully visible or fully transparent.
You can also press the keys <key>Ins</key>, <key>Dec</key>, <key>Home</key>, <key>End</key>,<key>PageUp</key>, <key>PageDown</key>, <key>Num+</key>, <key>Num–</key> to manually change the rgb and alpha values in the topright images.
Press the keys to see what happens.
For even more effects, you can change the Blit mode by pressing <key>ENTER</key>.
This affects the mixing of rgb values from the image and those specified by the keys.
004_per-pixel-alphademo.py shows you an interesting effect by changing the alpha-value of individual pixels.
Move the mouse around and use the scroll wheel.
- Code Discussion 004_colorkey.py
Colorkey
There is only one important line in this example.
To get rid of the ugly black corners of the blue ball surface we declare the color black (0.0.0) as transparent.
#make black the transparent color (red,green,blue)
ball.set_colorkey((0,0,0))
To make the blitting of a pygame surface faster, it is a good idea to use .convert() on the surface once all drawing on the surface is done.
Note that if the surface contains transparent colors, you need to use convert.alpha() instead of .convert().
Blitting on the Screen
The same ballsurface is blitted on the screen (not on the background!) twice in this example:
once (the left ball) before the mainloop start and once inside the mainloop.
Inside the mainloop a pattern of colourful lines is drawn each frame.
The lines will draw on top of the left surface,
but the right surface is drawn on top of the lines.
Note that the “corners” of the ball surfaces are not simply white but transparent.
The colourful lines are good visible
“below” the corners of the right ball surface.
Note that in this code example, inside the mainloop all commands draw directly on the screen surface.
Documentation
http://www.pygame.org/docs/ref/surface.html#Surface.set_colorkey
- Code Discussion 004_alphademo.py
004_alphademo.py shall show you the possibilities of alpha-values (transparency) and image files.
Don't worry if you do not understand all of 004_alphademo.py yet - i don't understand all of it, neither.
The important thing is to know how to use it.
Some techniques like loading surfaces from files and using text will be explained in other pages.
Also note that there are 2 ways of handling keyboard input in 004_alphademo.py.
pressed_keys : to indicate of a specific key is pressed at this moment.
key_pressed : to check if a specific key was (once) pressed and released.
Both methods will be discussed in more detail in →step011.
Blend Modes
From the different blit-modes, BLEND_RGBA_MULT (mode number 8) seems to be the most meaningful one.
Using this blit mode, you can make an image all blue, red or green by setting the per-pixel-alpha values for the corresponding colour.
At the same time, you can set an alpha value for the whole image.
This works both for images without transparency such as .jpg images but also for images with in-build transparency such as .gif or .png images.
def get_alpha_surface( surf, alpha=128, red=128, green=128, blue=128, mode=pygame.BLEND_RGBA_MULT):
"""
Allocate a new surface with user-defined values (0-255)
for red, green, blue and alpha.
Thanks to Claudio Canepa <ccanepacc@gmail.com>.
"""
tmp = pygame.Surface( surf.get_size(), pygame.SRCALPHA, 32)
tmp.fill( (red,green,blue,alpha) )
tmp.blit(surf, (0,0), surf.get_rect(), mode)
return tmp
For an example of how to use this function, see this code snippet:
tmp = get_alpha_surface(pngMonster3, a, r, g, b, mode)
screen.blit(tmp, (600,10))
The different modes are integer values and can be found in the pygame documentation or by using python's
interactive interpreter:
>>>import pygame
>>>help(pygame.constants)
Alpha Value for the Whole Surface
If you have a pygame surface without in-build transparency (let's say loaded from an .jpg image) you can set an alpha-value (transparency) for the whole surface with a simple set_alpha() command as shown in this code snippet from 004_alphademo.py:
jpgMonster2.set_alpha(alpha) # alpha (0-255) for whole surface
screen.blit(jpgMonster2, (400,300)) # blit on screen
Documentation
http://www.pygame.org/docs/ref/surface.html#Surface.blit
more about blend modes:
http://stackoverflow.com/questions/601776/what-do-the-blend-modes-in-pygame-mean
http://illusions.hu/effectwiki/doku.php?id=list_of_blendings
- Ideas
<note tip>replace colormonster.jpg
with a picture of your own head.
rename the picture or the relevant code line inside 005_alphademo.py
.
Can you make your face all blue or all purple just by pressing keys ?</note>
<note tip>set_aplpha() allows you to create fading effects for game intros, in-game cinematic or game-over screens.
Use a paint programm like Gimp, create a stunning game title (or game over message) and save it in the .jpg format.
Now try to let pygame fade in or fade out of this picture (ideally while playing music, see →step010)</note>
Source Code on Github
file | in folder | download | comment |
004_colorkey.py pygame Download the whole Archive with all files from Github:
https://github.com/horstjens/ThePythonGameBook/archives/master This program runs stand-alone and does not need other files
004_alphademo.py pygame this program needs other files (see below) to run
004_alphademo_pretty.py pygame yipyip's version of alphademo.
This program needs other files (see below) to run
800px-La_naissance_de_Venus.jpg
pygame/data source: wikipedia
colormonster.jpg
pygame/data a .jpg graphic an not have in-build transparency.
(notice the white background)
colormonster.png
pygame/data a .png (as well as .gif) graphic can have in-build transparency.
Notice the transparent background
ente.jpg
pygame/data a picture of a duck2)
If you see no colourful source code below, click on “reload” on your browser.
Or follow the links to github.
004_colorkey.py
View/Edit/Download the file directly in Github: https://github.com/horstjens/ThePythonGameBook/blob/master/pygame/004_colorkey.py
004_alphademo.py
This program need other graphic files to be located in a subfolder called data
.
See the file table above for details.
To get the program working correctly, you best download and unpack the whole file archive (link in the file table above).
View/Edit/Download the file directly in Github: https://github.com/horstjens/ThePythonGameBook/blob/master/pygame/004_alphademo.py
004_alphademo_pretty.py
This program need other graphic files to be located in a subfolder called data
.
See the file table above for details.
To get the program working correctly, you best download and unpack the whole file archive (link in the file table above).
<note tip>This version is for python 2.x only ! For python 3.x, see the next code example below</note>
View/Edit/Download the file directly in Github: https://github.com/horstjens/ThePythonGameBook/blob/master/pygame/004_alphademo_pretty.py
004_alphademo_pretty_python3x.py
This program need other graphic files to be located in a subfolder called data
.
See the file table above for details.
To get the program working correctly, you best download and unpack the whole file archive (link in the file table above).
<note tip>This verison works with python3.x only.
For python 2x version, see the oode example above</note>
View/Edit/Download the file directly in Github: https://github.com/horstjens/ThePythonGameBook/blob/master/pygame/004_alphademo_pretty_python3x.py
004_per-pixel-alphademo.py
This program needs one other graphic file -ente.jpg- to be located in a subfolder called data
.
See the file table above for details.
To get the program working correctly, you best download and unpack the whole file archive (link in the file table above).
Note: For Python 3.x, change the two instances of the xrange() function (which was deprecated in Python 3) to range().
https://github.com/horstjens/ThePythonGameBook/blob/master/pygame/004_per-pixel-alphademo.py
Step 006 - Time-based Movement
Code Discussion
To avoid the hardware-dependence of frame-based movement you can force pygame to calculate time-based movement.
Please note that we still speak of the illusion of movement, because all what pygame does is blitting blobs (surfaces) to different positions - but in a more reliable way
!
Time-based movement does not mean that your pygame program will run on crappy computers as smooth as on fast machines - pygame can not (yet) do that! Time-based movement just means that independent of the speed of the computer, in the source code example below the ball will always take the same amount of time to cross the screen.
On fast machines, the movement will appear very smooth, while on slow machines the movement will appear bumpy.
Calculating the passed Time
The secret to time-based movement is to let pygame calculate how much time was passed since the last cycle of the mainloop (to be exact: since the last call of the clock.tick command).
Pygame provide this information in milliseconds.
In the source code example below I convert this value into seconds 1).
This time value is multiplied to the speed vector:
#calculate new center of ball (time-based)
ballx += dx * seconds # float, since seconds passed since last frame is a decimal value
bally += dy * seconds
Note that the value of seconds will be something like 0.05 seconds per frame 2) so you need rather high dx and dy values to see any movement at all.
In this example dx and dy speed values are set at 60 pixel per second.
Blitting on a clean Screen
In this pygame example, the whole background is blitted onto the screen each cycle (frame) of the mainloop.
This will overwrite all old images of the ball surface.
On top of the fresh background, the ball is blitted each frame.
While this method of cleaning the whole screen allow you care-free programming, very big pygame windows (screen resolution) or slow computers will show a drop in the frame-rate.
The next chapters will show you more time- and memory-efficient methods of cleaning only those parts of the screen that need cleaning.
This is called dirty rects cleaning.
Source Code on Github
To run this example you need:
file | in folder | download |
006_time_based_movement.py pygame Download the whole Archive with all files from Github:
https://github.com/horstjens/ThePythonGameBook/archives/master
View/Edit/Download the file directly in Github: https://github.com/horstjens/ThePythonGameBook/blob/master/pygame/006_time_based_movement.py
Step 008 - Animation
Remember the Subsurfaces from the last step? And remember that the snake had always the same grinning facial expression? Now, let's make some animated sprites1) - not pygame sprites, but surfaces that show different images of an animation cycle, like a bird flapping its wings.
Instead of painting all the single pictures of an animation cycle myself, I prefer to use ready-made sprite sheets.
In the data folder, you will find a spritesheet2) from Ari Feldmann's Spritelib ( http://www.widgetworx.com/spritelib/ ) showing several pictures of an animal.
If you use Ari's sprite's, make sure to include his license information in your program.
With the help of pygame's subsurface command, I will show you how to extract single images from the sprite sheet and use them.
Code Discussion
Source Code on Github
To run this example you need:
file | in folder | download |
008_animation.py pygame
Download the whole Archive with all files from Github:
https://github.com/horstjens/ThePythonGameBook/archives/master
char9.bmp
pygame/data
View/Edit/Download the file directly in Github: https://github.com/horstjens/ThePythonGameBook/blob/master/pygame/008_animation.py
Step 009 - Tile-based Graphics
Escape the maze!
In this code example, you can control a little red ball using the cursor keys <key>←</key> <key>→</key> <key>↑</key> <key>↓</key> and you must seek to escape a walled maze by finding the way to the orange goal.
Code Discussion
Source Code on Github
To run this example you need:
file | in folder | download |
009_01_tile_based_graphic_(ugly).py
simple but ugly verions pygame
Download the whole Archive with all files from Github:
https://github.com/horstjens/ThePythonGameBook/archives/master
009_02_tile_based_graphic_(improved).py.py
improved version pygame
Ugly Version
View/Edit/Download the file directly in Github: https://github.com/horstjens/ThePythonGameBook/blob/master/pygame/009_01_tile_based_graphic_(ugly).py
Improved Version
View/Edit/Download the file directly in Github: https://github.com/horstjens/ThePythonGameBook/blob/master/pygame/009_02_tile_based_graphic_(improved).py
Step 010 - Using Sound and Music
Code Discussion
Pygame has several commands to play sound.
Pygame offers a set of Sound commands to load and play short sound files - usually .wav
or .ogg
files.
For longer files, pygame offers pygame.music commands to stream longer music / sound / voice files directly from the harddisk.
In the source code example below, loading sounds and music is done from a subfolder called data
, like the graphic files in the previous source code example.
Initializing the Mixer
To get rid of a nasty delay between giving the play command for a sound and hearing it you need to initialize the pygame mixer.
This is done before writing the pygame.init() command.
Under Linux, I got best results with this line:
pygame.mixer.pre_init(44100, -16, 2, 2048) # setup mixer to avoid sound lag
pygame.init() #initialize pygame
loading music and sound files
Note that loading the sound and music files from harddisk must be done after setting up the mixer.
In the code-example below, an-turr.ogg is a longer music file while jump.wav and fail.wav are short sound effects:
try:
pygame.mixer.music.load(os.path.join('data', 'an-turr.ogg'))#load music
jump = pygame.mixer.Sound(os.path.join('data','jump.wav')) #load sound
fail = pygame.mixer.Sound(os.path.join('data','fail.wav')) #load sound
except:
raise UserWarning, "could not load or play soundfiles in 'data' folder :-("
Playing Music and Sound from Files
After loading the sound effects or the music and thus creating pygame objects, you can simply call the .play()
method of those objects.
Inside the brackets you write -1 for endless play or the number of times or the time length to play the sound.
#for sound effects:
#Sound.play(loops=0, maxtime=0, fade_ms=0): return Channel
jump.play() # play the jump sound effect once
The music is usually played endlessly:
#music is already the name of the music object
#pygame.mixer.music.play(loops=0, start=0.0): return None
music.play(-1) # play endless
<note tip>you hear just a short crack noise instead of a sound? Some sounds simply do not work with pygame.
Try to open (and edit) the sound with a sound editor like Audacity and save it again, preferable in the free .ogg (vorbis) format.</note>
versions with and without graphic
Note that you do not need pygame's graphic at all to play music files.
One of the code examples below use graphical output, the other example use only text output.
In text output, you still need to initialize pygame and set up the mixer.
- Generate Sound Effects
Unlike music, sound effects are easy enough to generate and not worth the hassle of copyright fights.
You may find good sound effects here:
overview of resources at the pygame homepage: http://pygame.org/wiki/resources
wikimedia commons: http://commons.wikimedia.org/wiki/Category:Sound
To generate sound effects, you can either:
plug a microphon1) in your computer and start the recording software of your OS.
every OS is shipped with a primitive voice recording software.
use a program that can generate sound effects, like:
http://audacity.sourceforge.net/
http://www.drpetter.se/project_sfxr.html
http://www.bfxr.net/ (online)
Documentation
http://www.pygame.org/docs/ref/mixer.html#pygame.mixer.pre_init
http://www.pygame.org/docs/ref/mixer.html#pygame.mixer.Sound
http://www.pygame.org/docs/ref/music.html#pygame.mixer.music.load
http://www.pygame.org/docs/ref/music.html#pygame.mixer.music.play
Source Code on Github
To run this example you need:
file | in folder | download |
010_sound_and_music.py pygame
Download the whole Archive with all files from Github:
https://github.com/horstjens/ThePythonGameBook/archives/master
010_sound_only_no_graphic.py pygame
an-turr.ogg
from modarchive.org pygame/data
fail.wav pygame/data
jum.wav pygame/data
without graphic
View/Edit/Download the file directly in Github: https://github.com/horstjens/ThePythonGameBook/blob/master/pygame/010_sound_only_no_graphic.py
with graphic
View/Edit/Download the file directly in Github: https://github.com/horstjens/ThePythonGameBook/blob/master/pygame/010_sound_and_music.py
Step 011 - Keys, Rotating and Zoom
Code Discussion
Pressed Keys
Surfaces can not only moved around, but also rotated and -unlike beer mats- zoomed.
The next source code examples introduce a new method of keyboard control.
Instead of checking a queued event with pygame.event.get() the function pygame.key.get_pressed() delivers the actual state of the complete keyboard.
This state is represented by a tuple of 0/1 values for each key.
A value of 0 signals an unpressed, a value of 1 signals a pressed State of a key.
You can access the value for a specific key with the K_<A_SPECIFIC_KEY> values defined in pygame.constants.
For example
pressedkeys = pygame.key.get_pressed()
if pressedkeys[pygame.K_x]:
do_something()
checks if the key “x” is pressed.
This method is ideal for constant movement, where a sprite moves or rotate as long as a specific key is pressed.
In the source code example bleow, the cursor keys <key>←</key> <key>↑</key> <key>→</key> <key>↓</key> are used to move the snake surface, while the keys <key>w</key> and <key>s</key> zoom/shrink the snake and the keys <key>a</key> and <key>d</key> rotate the snake.
It is possible to press several keys together, like left and right cursor, and the program will move the correctly (not at all in this case):
dx, dy = 0, 0 # no cursor key, no movement
if pressedkeys[pygame.K_LEFT]:
dx -= speed
if pressedkeys[pygame.K_RIGHT]:
dx += speed
Note that this method of keyboard control is less precise than the if pygame.event.type…
- method because there is no guarantee that a fast key-pressing will be noticed by pygame.
Depending on how fast the computer can calculate each cycle of the main-loop, there could be a chance that you press and release a key just between 2 main loop cycles and pygame would not notice.
However, if your program runs around 30 frames per second, you would need lightning fast fingers to become not noticed by pygame.
Also note that this keyboard control method is not ideal for pre-defined movement like stones on a board game.
While you can control with the time-based movement the speed of a surface, it lay in the skill of the user and his dexterity in pressing and releasing a key to control how long a surface moves (and where its movements end exactly).
troublesome subsurface
Also note that the cleaning of the old surface (using the subsurface
method) is done inside a try…except block.
If the subsurface is no longer inside the surface, pygame would raise an error.
This can happen when you zoom or rotate the snake outside the screen.
# only blit the part of the background where the snake was (cleanrect)
try:
#if the subsurface is outside the screen pygame would raise an error
#this can happen when using rotozoom, therfore check inside try..except
#Surface.subsurface(Rect): return Surface
dirtyrect = background.subsurface((round(snakex,0),
round(snakey,0), snake.get_width(), snake.get_height()))
screen.blit(dirtyrect, (round(snakex,0), round(snakey,0)))
except:
screen.blit(background,(0,0)) # blit the whole background (slow but secure)
do not get lost in space !
Also note that in this code example there is no checking if the snake is inside the screen whatsoever.
You can try to move the snake outside of the right , move it down, left and up and reappear from the left.
Zooming around the Center
the pygame.transform.rotozoom command would rotate around the position (0,0) of a Surface - the topleft corner.
To create a more pleasing rotation aroundthe center effect, we need some tricks:
First, the original snake surface is copied into snake_original right after creation, to have always a not-manipulated image:
snake_original = snake.copy() # store a unmodified copy of the snake surface
<note tip>
snake_original = snake
would not work because both snake and snake_original would be just pointers to the same python object (the manipulated snake).
If you need a copy of an object, use the .copy() method</note>
Always the original_snake surface is zoomed and rotated with the current zoom and angle values by the pygame.transform.rotozoom command.
But how to avoid a rotation around the topleft corner?
For that, before pygame.transform.rotozoom
does its work, the current rectangle of the snake surface is stored into the variable oldrect by the surface.get_rect()
command.
Pygame rects have several useful properties, like pre-defined constants for center, centerx, centery, width, height etc.
After rotating and now having a usually resized snake surface (pygame always calculate a rectangle around the visible surface, thus a rotated snake fits in a bigger rectangle than a non-rotated snake) the rectangle of the new surface is stored into the variable newrect.
This is also done with the surface.get_rect()
command.
Now the code example simply blits the new surface so that its new center lays on the same spot as the center of the old rectangle - rotated around the center.
if turnfactor != 0 or zoomfactor !=1.0:
angle += turnfactor * turnspeed * seconds # time-based turning
zoom *= zoomfactor
# the surface shrinks and zooms and moves by rotating
oldrect = snake.get_rect() # store current surface rect
snake = pygame.transform.rotozoom(snake_original, angle, zoom)
newrect = snake.get_rect() # store new surface rect
# put new surface rect center on same spot as old surface rect center
snakex += oldrect.centerx - newrect.width / 2
snakey += oldrect.centery - newrect.height / 2
Documentation
http://www.pygame.org/docs/ref/rect.html
http://www.pygame.org/docs/ref/transform.html#pygame.transform.rotozoom
http://www.pygame.org/docs/ref/key.html#pygame.key.get_pressed
Source Code on Github
To run this example you need:
file | in folder | download |
011_rotozoom.py pygame
Download the whole Archive with all files from Github:
https://github.com/horstjens/ThePythonGameBook/archives/master
background640x480_a.jpg
pygame/data
snake.gif
pygame/data
View/Edit/Download the file directly in Github: https://github.com/horstjens/ThePythonGameBook/blob/master/pygame/011_rotozoom.py
Step 012 - Text
Code Discussion
There are more ways to display text than the pygame.display.set_caption(“msg”)
command.
You can render any text directly on a pygame surface.
This code example uses two defined functions to better seperate the text writing from the surface display commands.
Note that inside the second function is a call to the first function, generating a random colour tuple of (red, green, blue)
values.
All the text rendering happens inside the write
function:
def write(msg="pygame is cool"):
myfont = pygame.font.SysFont("None", random.randint(34,128))
mytext = myfont.render(msg, True, newcolour())
mytext = mytext.convert_alpha()
return mytext
Inside this write
-function, text is written to a surface (pygame uses the system font if no specific font-file is loaded) using a randomized font size and randomized colour.
The text surface has already a transparent color and is returned to the function call inside the main loop:
# inside mainloop
textsurface = write("hello world")
Code Challenges:
prompt user for text to randomize
read in a text file of words to display, change the word on collision with wall
For fun, no cleanrect method is used here and the background is only blitted to the screen when the text surface touches the left screen border.
Documentation
http://www.pygame.org/docs/ref/font.html#pygame.font.SysFont
http://www.pygame.org/docs/ref/freetype.html#Font.render
The Flytext Function
This exmple is written as a big function, flytext().
The function flytext accept the two arguments msg (the text message to display) and duration.
Example:
To call the function and let it write “Python is cool” for seven seconds, use:
flytext("Python is cool", 7)
Note that if you start flytext without arguments, the last two codelines will run the function with default arguments, writing “hello World” for five seconds.
You can change the default values in the line where the flytext function is defined:
def flytext(msg="hello world", duration=5):
Source Code on Github
To run this example you need:
file | in folder | download |
012_text.py pygame
Download the whole Archive with all files from Github:
https://github.com/horstjens/ThePythonGameBook/archives/master
View/Edit/Download the file directly in Github: https://github.com/horstjens/ThePythonGameBook/blob/master/pygame/012_text.py
Step 013 - Catch the Thief Game
How to Play
This code example should teach nearly nothing new but instead put all the wisdom from the previous steps together to create a little game.
In this catch-the-thief-game up to 2 players control sprites1) (the Pygame snake and the babytux bird) and are tasked to help the police (blue circle with white “P”) to catch a thief (red triangle with transparent “T”).
To make the game interesting (and difficult), the police moves toward the middle position between the both players (this position is marked with a black cross).
The thief moves randomly.
All sprites are controlled indirectly, like ships in water (or spaceships in space): The player point the mouse or press the cursor - keys <key>←</key> <key>→</key> <key>↑</key> <key>↓</key> in the direction the sprite should move.
The sprite will eventually move into this direction but will continue to do so even if it gets a new order.
Bouncing off the screen edge will change the direction.
If no orders are given, all sprites except the thief will eventually come to an halt because of friction.
The player can use an emergency break and force his sprite to halt by pressing <key>ENTER</key> or pressing the left mouse button.
Instead of the cursor keys, the player can also use <key>w</key>, <key>a</key>, <key>s</key>, <key>s</key>, <key>CTRL</key>.
If no second player is present, one skilled player can try to control both sprites with mouse & keyboard.
Code Discussion
In the code, those pygame surfaces are called sprites.
Please note that pygame has it's own, very sophisticated pygame.Sprite>def.
Most important are the last two lines, basically the only lines outside a function:
if __name__ == "__main__":
play_the_game()
Those lines control if the game is started directly or imported from another program (like a program that manages a highscore list).
If the programm is started directly, the internal variable __name__ has the value "__main__".
In this case, the game start itself by calling the play_the_game() function.
Because the loaded background image is larger than the pygame display screen resolution, the background image is resized using this command:
background = pygame.transform.scale(background, (screen.get_width(), screen.get_height()))
After defining some useful functions for later use the game creates several pygame surfaces: screen and background and several sprites: bird, snake, police, cross, thief where each sprite get the variables x, y, dx, dy with the spritename as prefix.
X and Y control the position while dx and dy control the movement speed.
Inside the mainloop, all sprites are cleaned, updated (player commands are computed here) and finally blittedn on the screen again.
The mainloop switch into a special GameOver - mode after the playtime runs out.
While in the GameOver mode, no sprites are displayed and only the score is rendered for the players information.
The cleanblit function calls and a lot of headache could all be ignored and instead a simple line of
#screen.blit(background, (0,0)) # not GameOver
would do the job.
However, framerate would drop when using large screen resolutions.
Before blitting (drawing) the sprites, a check is made if the police sprite is near enough at the thief sprite to credit the players with a score point.
There are some code lines to see if the pygame sound mixer is currently busy and if he is silent the spring.wav sound is played.
Also because at each catching of the thief the screen is filled with a random colour, the code line checks if this frame (mainloop cycle) is the first of “normal” activity (no thief is catched) right after an “catch” frame.
If yes, the old original background is blitted on the screen.
catch_in_last_frame = catch_in_this_frame # save old catch info
catch_in_this_frame = False
if (distx < police.get_width() /2) and (disty < police.get_height()/2):
catch_in_this_frame = True
points += seconds
screen.fill(randomcolour())
if not pygame.mixer.get_busy():
spring.play() # only play this sound if mixer is silent at the moment
else:
# no catch this time
if catch_in_last_frame:
screen.blit(background, (0,0)) # restore backgrounnd
Ideas
<note tip>ideas:
head over to http://www.openstreetmap.org/ , find a map from your city, and use it ! (right-click on the map, select “save image as…”)
Improve one-player modus by adding <key>w</key> <key>a</key> <key>s</key> <key>d</key> as control keys
enable direct control for mouse or keyboard (like manipulating snakex, snakey instead of snakedx, snakedy)
create additional sprites
play a music file
use proportional force (like a rubber band): the further away the police surface is from the cross, the faster it travels toward the cross
split the code into a config file (with all the constants and variables) and into a game file
dynamic score points: calculate the current distance between police and thief.
Give points all the time, based on this distance (0 distance - max points)
draw a line between police and thief.
The line should become thicker if the distance police-thief becomes shorter
</note>
Trivia
<note>trivia: This games is inspired by the Austrian tv cult series Kottan.
As the content and visuals of this series are -sadly- neither public domain nor creative-commons licensed i use free graphics.
But on your own computer, you can replace the sprite graphics with some characters of your favorite TV show.</note>
Documentation
http://www.pygame.org/docs/ref/transform.html#pygame.transform.scale
http://www.pygame.org/docs/ref/mixer.html#pygame.mixer.get_busy
Source Code on Github
To run this example you need:
file | in folder | download |
013_catch_the_thief.py pygame
Download the whole Archive with all files from Github:
https://github.com/horstjens/ThePythonGameBook/archives/master
wien.jpg
pygame/data
snake.gif
pygame/data
babytux.png
from wikimedia commons pygame/data
spring.wav
from Battle of Wesnoth pygame/data
time_is_up_game_over.ogg
from Neverball pygame/data
View/Edit/Download the file directly in Github: https://github.com/horstjens/ThePythonGameBook/blob/master/pygame/013_catch_the_thief.py
Step 014 - Pygame sprites
code discussion
If you analyze the previous catch-the-thief game you will notice that most of the code in the main loop takes care of cleaning, calculating and blitting the pygame surfaces (the sprites).
Pygame provides a very powerful pygame.sprite class for more elegant and object-oriented sprite programming.
The advantages of using a sprite>self) but you can also use>pygame.sprite.Groups for mass processing
pygame provides easy-to-use collision detection between sprites
what is a>
Please refer to the python documentation or other tutorials for a better introduction into object-oriented programming.
For quick and dirty coding, it is enough if you compare using a sprite>Cookie_cutter you can construct many cookies.
Think of the cookie cutter as the class and of the cookies as the (constructed) instances of this>.
The>code example of a sprite>
+
=
dough a cookie cutter many cookies
some coding >class Snake(pygame.sprite.Sprite):
"""the pygame Snake"""
image = pygame.image.load("Snake.gif")
image = image.convert_alpha()
def __init__(self, startpos):
pygame.sprite.Sprite.__init__(self,
self.groups)
self.pos = startpos
self.image = Snake.image
self.rect = self.image.get_rect()
def update(self):
self.rect.center = self.pos
discussion of sprite>
In the code example above (below the cookies) you can how a sprite>class
) with a beginning capital Letter, in this case Snake instead of snake.
Also note that this>derives from (is a child of) pygame's Sprite>class Snake(pygame.sprite.Sprite):
Directly after the> image = pygame.image.load("Snake.gif")
image = image.convert_alpha()
describe>class prefix self
Now comes the part describing each>self is used.
You could use another prefix but most python coders write self.
Each function need at least the argument self, even if no other parameter is passed to the function.
The first function that every pygame sprite>def __init__(self):
A part from the usual self, you can give the new born Snake as many arguments as you need, like initial position, behaviour, color etc.
In the next example, only a startpos is given, defaulting to (50,50) if the sprite is created without a startpos.
The sprite will not work until you tell pygame to do all the stuff that it needs doing to create a new sprite.
This is done by calling the __init__ function of the>, also a good way to handle pygame's sprite groups.
storing>
If you want to store any parameters for the sprite itself (so that other functions like an update or kill function can access them, you need to save the parameter into a>def __init__(self, startpos=(50,50)):
pygame.sprite.Sprite.__init__(self,self.groups) # never forget this line !
self.pos = startpos # store startpos into a>
call the property of the>
Now you are free to code all the property's of the>self
to indicate that a property (like the position) is valid or this individual snake (class instance) only.
Here, the starpos argument is stored into the variable self.pos:
self.pos = startpos
To access>self.image = Snake.image
useful stuff for each>
self.image …how the sprite look.
It's a pygame surface, loaded or created, and you can use the pygame.draw commands on it.
self.rect …very useful, get it with the command self.rect = self.image.get_rect()
after you are done with creating self.image
self.radius … useful for circular collision detection
self.rect.center … if you have self.rect, use this to control the postion of a sprite on the screen.
update(self, time): … a function (best with the passed seconds since last frame as argument) where you can calculate what the sprite should do, like moving, bouncing of walls etc
kill(self) … very useful to destroy a sprite.
the call inside the>a complete sprite>
This is the complete code for the Birdcatcher>source code.
This>self.image.
class BirdCatcher(pygame.sprite.Sprite):
#>
image = pygame.Surface((100,100)) # created on the fly
image.set_colorkey((0,0,0)) # black transparent
pygame.draw.circle(self.image, (255,0,0), (50,50), 50, 2) # red circle
image = self.image.convert_alpha()
# code for each individual>
def __init__(self):
pygame.sprite.Sprite.__init__(self, self.groups) # THE most important line !
self.image = BirdCatcher.image # make>
self.rect = self.image.get_rect()
self.radius = 50 # for collide check
def update(self, seconds):
# no need for seconds but the other sprites need it
self.rect.center = pygame.mouse.get_pos()
before the main loop
Before the main loop starts, you define some pygame.sprite.Groups to contain all the sprites:
If you have a Sprite Snake and the two groups allgroup and snakegroup you can assign that all Snake sprites should be members of both groups:
allgroup = pygame.sprite.Group()
snakegroup = pygame.sprite.Group()
# each Snake sprite is automatically member of both groups:
Snake.groups = allgroup, snakegroup
# create a single Snake named "mypython"
mypython = Snake()
what to do in the mainloop
You must make sure that your sprites belong to a sprite group (like allsprites).
In the mainloop, you simply call those commands each frame:
allsprites.clear(screen, background)
allsprites.update(seconds)
allsprites.draw(screen)
pygame.display.flip()
documentation
http://www.pygame.org/docs/ref/sprite.html
http://www.pygame.org/docs/ref/examples.html (pygame version 1.9)
the aliens example: www.pygame.org
source code on github
To run this example you need:
file | in folder | download |
014_sprites.py pygame
Download the whole Archive with all files from Github:
https://github.com/horstjens/ThePythonGameBook/archives/master
babytux.png
pygame/data
babytux_neg.png
pygame/data
View/Edit/Download the file directly in Github: https://github.com/horstjens/ThePythonGameBook/blob/master/pygame/014_sprites.py
Step 015 - Explosions, healthbars and gravity
code discussion
In the code example below (see source code) you can create and kill sprites by mouse click.
For each kill there is an explosion effect: a sound plays and a random amount of fragments is flying around.
The fragments are subject to a gravity force.
Also note:
Each Bird sprite has it's own livebar sprite (a green bar to display the remaining hitpoints).
Each bird create it's own hitbar inside the init method !
class Bird(pygame.sprite.Sprite):
# ..
def __init__(self, pos):
#..
Livebar(self) #create a Livebar for this Bird.
self is the Bird sprite as parameter
Each Livebar get his “boss” as parameter when created:
class Livebar(pygame.sprite.Sprite):
#..
def __init__(self, boss):
#..
self.boss = boss # the boss is the bird sprite
The main purpose of the code example is to demonstrate the difference between a bad and a good sprite collision detection.
As each Bird1) sprite moves around the screen, it is checked (in the mainloop) for collision with another Bird sprite.
(A even more clever way will be demonstrated in the next steps).
good
The efficient way to do so was friendly showed me by Gummbum from the pygame mailing list
#...before mainloop...
othergroup = [] #create empty list
#...inside mainloop...
while mainloop:
# ...
the usual mainloop code
# test if a bird collides with another bird
for bird in birdgroup:
othergroup[:] = birdgroup.sprites() # This is the correct code, no garbage collection
othergroup.remove(bird) # remove the actual bird, only all other birds remain
if pygame.sprite.spritecollideany(bird, othergroup):
bird.crashing = True
crashgroup = pygame.sprite.spritecollide(bird, othergroup, False )
for crashbird in crashgroup:
bird.dx -= crashbird.pos[0] - bird.pos[0]
bird.dy -= crashbird.pos[1] - bird.pos[1]
wrong
If you write instead of the othergroup[:] = ….
line this code:
othergroup = birdgroup.copy() # WRONG ! THIS CODE MAKES UGLY TIME-CONSUMING GARBAGE COLLECTION !
you will notice longer delays between 2 frames as soon as many objects are on the screen.
The .copy() command, excecuted each frame, each up huge blocks of memory.
This block of memory has to be cleaned again from time to time and while pygame
does so, you can notice a huge pause…the game “hangs”.
This effect will only become visible if you have many sprites moving around.
Watch the max(ms)
display in the pygame title or the length of the green Timebar bars.
In the source code example at the end of this page you can toggle efficient and inefficient coding by pressing <key>b</key>.
clever
Instead of creating another group for all Birds without the actual Bird you can also compare with the whole birdgroup and check the Bird's number attribute to make sure there is a collision between two different sprites:
# very clever coding
crashgroup = pygame.sprite.spritecollide(bird, birdgroup, False) # the actual Bird is also in birdgroup
for crashbird in crashgroup:
if crashbird.number != bird.number: #avoid collision with itself
In the source code example at the end of this page you can toggle clever coding by pressing <key>c</key>
overwriting a>
Take a look at the code of the def kill(self) method of the (huge) Bird sprite>Timebar sprite>self.kill() command, but you will find no def kill(self):
function in the Timebar sprite>pygame.sprite.Sprite>def kill(self) method.
As the name suggest, self.kill()
removes the actual sprite from the screen and from all groups.
class Timebar(pygame.sprite.Sprite):
#..
def update(self, time):
self.rect.centery = self.rect.centery - 7 # each timebar kill itself when it leaves the screen
if self.rect.centery < 0:
self.kill() # remove the sprite from screen and from all groups
over writing kill
However, the more complicated Bird sprite>def kill(self): function.
That is because I want to do some extra stuff before killing the sprite, like playing a sound effect and shattering fragments around the screen.
Also in this case I want to remove the Bird sprite from a special dictionary where I store each Bird sprite and its individual number.
Therefore, I overwrite the def kill(self):
function, do my special things and finally call pygame's kill method directly: pygame.sprite.Sprite.kill(self)
Big Badda Boom!
class Bird(pygame.sprite.Sprite):
#..
def kill(self):
"""because i want to do some special effects (sound, dictionary etc.)
before killing the Bird sprite i have to write my own kill(self)
function and finally call pygame.sprite.Sprite.kill(self)
to do the 'real' killing"""
cry.play() #play sound effect
for _ in range(random.randint(3,15)):
Fragment(self.pos) # create Fragment sprites
Bird.birds[self.number] = None # kill Bird in sprite dictionary
pygame.sprite.Sprite.kill(self) # kill the actual Bird
Exploding fragments
The Fragment>2).
Note that you can toggle gravity by pressing <key>g</key> during the game.
The effect of the gravity is handled in the update method of the Fragment>class Fragment(pygame.sprite.Sprite):
"""a fragment of an exploding Bird"""
gravity = True # fragments fall down ?
def __init__(self, pos):
pygame.sprite.Sprite.__init__(self, self.groups)
self.pos = [0.0,0.0]
self.pos[0] = pos[0]
self.pos[1] = pos[1]
#...
self.dx = random.randint(-self.fragmentmaxspeed,self.fragmentmaxspeed)
self.dy = random.randint(-self.fragmentmaxspeed,self.fragmentmaxspeed)
def update(self, seconds):
#...
self.pos[0] += self.dx * seconds
self.pos[1] += self.dy * seconds
if Fragment.gravity:
self.dy += FORCE_OF_GRAVITY # gravity suck fragments down
self.rect.centerx = round(self.pos[0],0)
self.rect.centery = round(self.pos[1],0)
Layers
By default, pygame will blit the sprites in the order the sprites are added.
IF you prefer precise order of drawing the sprites (like the mouse pointer always before all other sprites) you can do two things:
use several clear
, update
and draw
commands, one for each sprite group:
use the pygame.sprite-LayeredUpdates
group instead of a sprite group and set a default layer for each group, like in the code example below:
# LayeredUpdates instead of group to draw in correct order
allgroup = pygame.sprite.LayeredUpdates() # important
#assign default groups to each sprite>
Livebar.groups = bargroup, allgroup
Timebar.groups = bargroup, allgroup
Bird.groups = birdgroup, allgroup
Fragment.groups = fragmentgroup, allgroup
BirdCatcher.groups = stuffgroup, allgroup
#assign default layer for each sprite (lower numer is background)
BirdCatcher._layer = 5 # top foreground
Fragment._layer = 4
Timebar._layer = 3
Bird._layer = 2
Livebar._layer = 1 #background
More about layers in the next step.
putting the whole game inside a function
You may have noticed that the whole game sits inside a function called def game():
.
This is useful when we later make a game menu that starts the game.
Because it will be sensible to split the game code into a menu.py and a startgame.py file.
The menu.py file will import the startgame and call it when the user chooses the menu option.
To be more precise, the menu.py file will start a single function inside the startgame.py file.
(If there is no meaningful function defined inside startgame.py the menu file would start startgame.py right after importing it, before displaying a menu and waiting for the user's choice).
python modules
We have no menu.py (yet) but we can prepare for it by stuffing all interesting parts (shooting up penguins etc) inside a function - let's call the function game() (see below).
But how do we start the game directly? For that, we check the internal python variable __name__.
In this variable, python stores the name of the python module that imported the actual python program.
If we started the actual python program directly (from the terminal or from the python editor), then this variable gets the value __main__ from python.
Therefore you will find in many python modules3) those lines:
def game():
#..
all the interesting stuff
if __name__ == "__main__":
print "i was started directly and will start game()"
game() # start the interesting stuff
else:
print "i am imported by",__name__, "and will do nothing at the moment"
source code on github
To run this example you need:
file | in folder | download |
015_more_sprites.py pygame
Download the whole Archive with all files from Github:
https://github.com/horstjens/ThePythonGameBook/archives/master
babytux.png
pygame/data
babytux_neg.png
pygame/data
claws.ogg
from Battle of Wesnoth pygame/data
View/Edit/Download the file directly in Github: https://github.com/horstjens/ThePythonGameBook/blob/master/pygame/015_more_sprites.py
click reload in your browser if you see no code here:
Step 016 - LayeredUpdates and Parallax scrolling
gameplay
The source code example below is not much of a game, but it demonstrates the uses of layers for sprites and Parallax_scrolling (the mountains).
Also, you can hide penguins behind blocks and mountains.
Additionally, you can aim with the mouse pointer and press <key>p</key> to print out information about the layers (at the text console).
Trivia:
According to wikipedia, Parallax scrolling was first introduced to computer games by the arcade game Moon_Patrol 1982.
The mountains (and in higher levels, futuristic buildings) moved at different speed to create the illusion of a side-scrolling computer game.
proposals to tinker:
Change the layer of the three types of>pevious step, let the Birds change _layer
by random and explode them by mouseclick !
code discussion
- layers
Do you remember the sprite groups from the last step ? Sprite groups are basically containers where the sprites “live” in.
As soon as a sprite is killed (by it's kill()
functoin) the sprite is not also removed from the screen but also from all containers holding it.
In the last two steps, i had two uses for sprite groups:
The allgroup
(every sprite was member of this group), to clear, draw, and update all sprites from within the main loop
other groups like birdgroup
, used to check each sprite in this group for collision detection (crashgroup)
You will note that there exist different variants of sprite groups:
the old spritegroup
the newer LayeredUpdate group
When using pygame.sprite.Layeredupdate()
instead of pygame.sprite.Group()
you can give each sprite a variable _layer
as well as a variable groups
to influence the drawing order of the sprite.
In the previous step those variables were defined outside the sprite>__init__(self) method of each sprite>
The sprite groups must exist (be defined in the mainloop) before you can assign sprites to the groups.
That means, inside the mainloop, before you create a Bird sprite or assign images to the Bird>before you call pygame.sprite.Sprite.__init__(self, *groups):
<note>#… means that i let away some not so important code lines</note>
class Bird(pygame.sprite.Sprite):
#...
def __init__(self, startpos=screen.get_rect().center):
self.groups = birdgroup, allgroup # assign groups BEFORE calling pygame.sprite.Sprite.__init__
self._layer = 7 # assign _layer BEFORE calling pygame.sprite.Sprite.__init__
pygame.sprite.Sprite.__init__(self, self.groups ) #call parent>
self.pos = starpos
# ...
# define sprite groups
birdgroup = bargroup = pygame.sprite.Group() # simple group for collision detection
allgroup = pygame.sprite.LayeredUpdates() # more sophisticated and modern group
# assign images to the Bird>
Bird()
pygame.sprite.LayeredUpdates
please see the official pygame doumentation at: http://www.pygame.org/docs/ref/sprite.html#pygame.sprite.LayeredUpdates
changing layers
The cool things about layers is: you can change them even at runtime, to place sprites more in the foreground or more in the background (see code example below).
The change_layer method does exaclty that.
Important: you need only to change the layer of the LayeredUpdates-group that actually draws the sprites on the screen.
In the code example below, this is the allgroup.
(Special thanks to Gummbum for helping me out here).
Because i want only to change the layer of the Bird sprites and their Lifebar sprites i loop over all sprites in the groups birdgroup and bargroup.
Each Bird sprite is a member of the allgroup as well as of the birdgroup.
Each Lifebar sprite is a member of the allgroup as well as of the bargroup.
if pygame.mouse.get_pressed()[0]:
if birdlayer < 10:
birdlayer += 1
cooldowntime = .5 # seconds
cry.play()
for bird in birdgroup:
allgroup.change_layer(bird, birdlayer) # allgroup draws the sprite
for bar in bargroup:
allgroup.change_layer(bar, birdlayer) # allgroup draws the sprite
- Textsprite
Up to now, i blitted all text to the screen or to the background with the write function.
In this example, you will see a new>Text, also printing a msg to the screen.
The Text sprite has no advantages over blitting directly to the background yet, but you can use it later to change the _layer of the Text sprite or if you want the Text sprite to move around.
Note that
the>newmsg to update the displaying text string.
def newmsg(self, birdlayer):
self.image = write("current Bird _layer = %i" % birdlayer)
self.rect = self.image.get_rect()
self.rect.center = (screen.get_width()/2,10)
- waiting Birds
Take a look at the Bird>Bird.waittime indicating how long a Bird should stay “invisible”.
Instead of messing around with drawing and not drawing, during his “invisible” waittime, each bird is simply teleported to the position (-100,-100), that is outside your screen.
If the waittime is over, the Bird sprite is teleported to his Bird.pos position and act like a normal Bird sprite - speeding around, crashing into walls and other birds, exploding.
Class Bird(pygame.sprite.Sprite):
waittime = 1.0 #seconds
#...
def update(self, seconds):
#---make Bird only visible after waiting time
self.lifetime += seconds
if self.lifetime > (self.waittime) and self.waiting:
self.newspeed()
self.waiting = False
self.rect.centerx = round(self.pos[0],0)
self.rect.centery = round(self.pos[1],0)
if self.waiting:
self.rect.center = (-100,-100)
else:
# speedcheck
#..
all the other things
But why the waittime ? The answer is a modification to the Fragment>def __init__(self, layer=4):
#...
Lifebar(self.number) #create a Lifebar for this Bird.
# starting implosion of blue fragments
for _ in range(8):
Fragment(self.pos, True)
See the for loop ? I simply wanted 8 (blue) Fragments, so i do not use an variable like a or b or x inside the for loop but simply an underscore.
The True parameter in the Fragment>dual-use Fragments
The fragment>if-else construct.
The new part is only valid for “bluefrag” Fragments.
After choosing per random from where (screen edge) the Fragment start, self.dx and self.dy are calculated… The Fragment should fly from the screen edge toward the position of the Bird sprite (self.target).
But how fast does the fragment fly ? Because in the update method of the fragment>unit of a sprite is measured in pixel per second.
Meaning each blue sprite will need exactly one second to travel from it's origin to the Bird sprite position.
But what if the waittime for each Birdsprite is 2 seconds ? or just a half second ? For this reason, dx and dy are multiplied by the factor ( 1.0 / Bird.waittime).
So if the Bird waits 2 seconds before appearing, the blue Fragments have more time and should travel slower: 1.0 / 2.0 = 0.5, the speed is reduced.
On the other hand, if the waittime is shorter (say 0.5 seconds) the Fragments should fly faster: ( 1.0 / 0.5 = 2); dx and dy is doubled.
For aesthetic reasons, i allow the blue Fragments to live up to a half second after reaching their target by adding to self.lifetime a value between 0 and 0.5: random.random() * 0.5
.
Random.random() creates a float value between 0 and 1.
class Fragment(pygame.sprite.Sprite):
#...
def __init__(self, pos, bluefrag = False):
#...
self.bluefrag = bluefrag
self.pos=[0.0,0.0]
self.target = pos
if self.bluefrag: # blue frament implodes from screen edge toward Bird
self.color = (0,0,random.randint(25,255)) # blue
self.side = random.randint(1,4)
if self.side == 1: # left side
self.pos[0] = 0
self.pos[1] = random.randint(0,screen.get_height())
elif self.side == 2: # top
self.pos[0] = random.randint(0,screen.get_width())
#...
# calculating flytime for one second..
Bird.waittime should be 1.0
self.dx = (self.target[0] - self.pos[0]) * 1.0 / Bird.waittime
self.dy = (self.target[1] - self.pos[1]) * 1.0 / Bird.waittime
self.lifetime = Bird.waittime + random.random() * .5 # a bit more livetime after the Bird appears
else: # red fragment explodes from the bird toward screen edge
#...
all the stuff for red Fragments
def update(self, seconds):
# ...
self.pos[0] += self.dx * seconds
self.pos[1] += self.dy * seconds
# ...
self.rect.centerx = round(self.pos[0],0)
self.rect.centery = round(self.pos[1],0)
- scrolling mountains
You will notice that at the start of the game, the moutains “walk” in from right to left.
All moutnains are instances of the same mountain>type parameter (making blue, red or pink mountains).
Most of the work in this>__ini__-method:
class Mountain(pygame.sprite.Sprite):
#...
def __init__(self, type):
self.type = type
if self.type == 1:
self._layer = -1
self.dx = -100
self.color = (0,0,255) # blue mountains, close
elif self.type == 2:
self._layer = -2
self.color = (200,0,255) # pink mountains, middle
self.dx = -75
else:
self._layer = -3
self.dx = -35
self.color = (255,0,0) # red mountains, far away
self.groups = allgroup, mountaingroup
pygame.sprite.Sprite.__init__(self, self.groups) # THE Line
#...
Maybe the most interesting part here is the creation of the actual mountain.
This is done with the help of the random.random() function (creates an decimal number between 0.0 and 1.0) and the pygame.draw.polygon
-method.
On each mountain surface, a filled triangle (polygon) is created from the lower left corner to a corner in the middle (x/2) and at a random height (the moutain peak) and back to the lower right corner.
The syntax for pygame.draw.poligon is: pygame.draw.polygon(surface, color, pointlist, width=0)
class Mountain(pygame.sprite.Sprite):
#...
def __init__(self, type):
#...
self.dy = 0
x = 100 * self.type * 1.5
y = screen.get_height() / 2 + 50 * (self.type -1)
self.image = pygame.Surface((x,y))
self.image.set_colorkey((0,0,0)) # black is transparent
pygame.draw.polygon(self.image, self.color,
((0,y),
(0,y-10*self.type),
(x/2, int(random.random()*y/2)),
(x,y-10*self.type),
(x,y),
(9,y)),0) # width=0 fills the polygon
self.image.convert_alpha()
self.rect = self.image.get_rect()
Each mountain has a .parent
attribute, set to False in the __ini__
-method.
In the update
-method of the moutain>type is created and placed directly to it's right side (yet invisible because outside the screen border).
If a mountain travels too far to the left side, it is killed.
class Mountain(pygame.sprite.Sprite):
#...
def update(self, time):
if self.rect.centerx + self.rect.width/2+10 < 0:
self.kill()
# create new mountains if necessary
if not self.parent:
if self.rect.centerx < screen.get_width():
self.parent = True
Mountain(self.type) # new Mountain coming from the right side
source code on github
To run this example you need:
file | in folder | download |
016_layers.py pygame
Download the whole Archive with all files from Github:
https://github.com/horstjens/ThePythonGameBook/archives/master
babytux.png
pygame/data
babytux_neg.png
pygame/data
claws.ogg
from Battle of Wesnoth pygame/data
View/Edit/Download the file directly in Github: https://github.com/horstjens/ThePythonGameBook/blob/master/pygame/016_layers.py
Step 017 - Rotating, shooting, inheritance, physics
Time to play! The source code example below is an actual playable game, demonstrating several different concepts:
Trigonometric_functions to rotate objects
Inheritance to build>Elastic collision between to physical objects (birds, but reduced to physical disc's).
<note important>Horst: what are physical discs?</note>
gameplay
With the source code example below you can shoot down little penguins (again).
This time, you steer a fat penguin and can rotate and move him with the <key>w</key>,<key>a</key>,<key>s</key>,<key>d</key> keys.
The movement is relative to the facing of the fat penguin; by pressing <key>w</key> he will not move upwards, but instead forward depending on his actual rotation.
You can still move sidewards by pressing <key>q</key> and <key>r</key>.
To calculate the forward movement of a rotated object in a cartesian (x,y) coordinate system some Trigonometric_functions are used.
To make the game more interesting, some small penguins are also in the game.
The player can shoot at them (using the <key>SPACE</key> key) and collide with them but should avoid the fragments when a small penguin explodes.
If a small penguin reaches the ground, he get some random “fuel”1) and moves upward (negative dy) until the “fuel” runs out.
Gravity, that the player can turn on and off with the <key>g</key>, will suck penguins, shots and fragments down.
The small objects (penguins, shots) are pushed around by forces (boost, gravity, impact of shots and fragments or other penguins), leading to diagonal movement (dx and dy).
While the rotation of the fat penguin dedicate it's movement, the movement of the small objects dedicate their rotation.
The goal of the game is too shoot down as many small penguins while trying to reach a 100% hit ratio.
Each time one small penguin is killed a new one is created.
The ratio of hits / misses will be calculated by the computer.
The game ends if the gametime
runs out or the player was hit by too much red fragments.
to tinker
change the constants (all in CAPITAL LETTER) like GRAVITY, FRICTION etc.
uncomment out-commented lines in Bird.areacheck
to let birds bounce off walls
uncomment out-commented lines in Bird.speedcheck
to introduce a general speed limit
give the small birds more “fuel” to fly higher: change self.boostmax
in Bird.__init__
code discussion
- some trigonometry for pygame
How to calculate sine and cosine for angle x Radiand vs.
Grad: 360° = 2 * π
π (Pi) = 3.1418….
While python will take care of most mathematics involved in rotating sprites for you, it may be a good to refresh some school wisdom.
In this example games, those trigometric functions are used:
The sine: takes an angle (in radiant) as argument, returns the y coordinate
The cosine: takes an angle (in radiant) as argument, returns the x coordinate
The arctangent: takes a fraction as argument, returns an angle (in radiant)
As you can see in the drawing at the right side, sine is the vertical coordinate of a given point ( D in the diagram) on a unit circle2) while the cosine is the horizontal coordinate of the same point.
The relation between those coordinates (vertical / horizontal) equals the tangent of the angle.
The arctangent function is the inverse function of the tangent: arctangent takes the relation (y/x) as argument and returns the angle.
grad and radiant
There are several methods to measure the angle:
divide a full circle into 360 degrees (grad) or
divide a full circle into 2 times Pi 3)
While python's pygame module can handle basic rotation of sprites and surfaces using the 360 degree method, for calculating sine, cosine and arctangent, you need python's math module.
Math functions use radians.
Two small functions help here:
import math
def radians_to_degrees(radians):
return (radians / math.pi) * 180.0
def degrees_to_radians(degrees):
return degrees * (math.pi / 180.0)
- calculating direction for a given rotation
The player's>BigBird knows the rotation of the sprite and need the movement vectors dx
and dy
to calculate.
Here is the code, not-so important bits are omitted:
import math
GRAD = math.pi / 180
class BigBird(pygame.sprites.Sprites):
#...
def __init__(self):
pressedkeys = pygame.key.get_pressed()
self.ddx = 0.0
self.ddy = 0.0
if pressedkeys[pygame.K_w]: # forward
self.ddx = -math.sin(self.angle*GRAD)
self.ddy = -math.cos(self.angle*GRAD)
if pressedkeys[pygame.K_s]: # backward
self.ddx = +math.sin(self.angle*GRAD)
self.ddy = +math.cos(self.angle*GRAD)
if pressedkeys[pygame.K_e]: # right side
self.ddx = +math.cos(self.angle*GRAD)
self.ddy = -math.sin(self.angle*GRAD)
if pressedkeys[pygame.K_q]: # left side
self.ddx = -math.cos(self.angle*GRAD)
self.ddy = +math.sin(self.angle*GRAD)
#...
self.dx += self.ddx * self.speed
self.dy += self.ddy * self.speed
#...
self.pos[0] += self.dx * seconds
self.pos[1] += self.dy * seconds
#...
- calculating rotation for a given direction
On the other hand, if you know dx and dy and need the fitting angle, you can use this code:
class Bullet(pygame.sprite.Sprite):
#...
def update(self, time):
#...
#--------- rotate into direction of movement ------------
#--- calculate with math.atan ---
#if self.dx != 0 and self.dy!=0:
# ratio = self.dy / self.dx
# if self.dx > 0:
# self.angle = -90-math.atan(ratio)/math.pi*180.0 # in grad
# else:
# self.angle = 90-math.atan(ratio)/math.pi*180.0 # in grad
#--- or calculate with math.atan2 ---
self.angle = math.atan2(-self.dx, -self.dy)/math.pi*180.0
self.image = pygame.transform.rotozoom(self.image0,self.angle,1.0)
Using math.atan2 function instead of math.atan save some code lines.
You can view the documentation for the math module online at http://docs.python.org/library/math.html :
<note>
math.atan(x)
Return the arc tangent of x, in radians.
math.atan2(y, x)
Return atan(y / x), in radians.
The result is between -pi and pi.
The vector in the plane from the origin to point (x, y) makes
this angle with the positive X axis.
The point of atan2() is that the signs of both inputs are known
to it, so it can compute the correct quadrant for the angle.
For
example, atan(1) and atan2(1, 1) are both pi/4,
but atan2(-1, -1) is -3*pi/4.
</note>
- using>
In this code example exist 2 kinds of birds: The big (fat) BigBird and many smalle SmallBird's.
Both have a common parent>elastic collision
Inside the game's mainloop is a tiny “physic engine” in use:
It checks with a crashgroup if one bird is actually crashing into another bird.
If so, both birds move away from each other.
This could be done with less sophisticated code like in the previous examples, like by simply giving the crashbird's new random values for dx and dy.
However Lenoard Michlmayr was so nice to help me out here with some code for an elastic collision:
Note that to simplify the calculation, each bird is calculated as a disc.
Also in the very special case that there is no speed (like if one bird is “beamed” into another bird) some random values for dx and dy are created.
# ...
def elastic_collision(sprite1, sprite2):
"""elasitc collision between 2 sprites (calculated as disc's).
The function alters the dx and dy movement vectors of both sprites.
The sprites need the property .mass, .radius, .pos[0], .pos[1], .dx, dy
pos[0] is the x postion, pos[1] the y position"""
# here we do some physics: the elastic
# collision
#
# first we get the direction of the push.
# Let's assume that the sprites are disk
# shaped, so the direction of the force is
# the direction of the distance.
dirx = sprite1.pos[0] - sprite2.pos[0]
diry = sprite1.pos[1] - sprite2.pos[1]
#
# the velocity of the centre of mass
sumofmasses = sprite1.mass + sprite2.mass
sx = (sprite1.dx * sprite1.mass + sprite2.dx * sprite2.mass) / sumofmasses
sy = (sprite1.dy * sprite1.mass + sprite2.dy * sprite2.mass) / sumofmasses
# if we sutract the velocity of the centre
# of mass from the velocity of the sprite,
# we get it's velocity relative to the
# centre of mass.
And relative to the
# centre of mass, it looks just like the
# sprite is hitting a mirror.
#
bdxs = sprite2.dx - sx
bdys = sprite2.dy - sy
cbdxs = sprite1.dx - sx
cbdys = sprite1.dy - sy
# (dirx,diry) is perpendicular to the mirror
# surface.
We use the dot product to
# project to that direction.
distancesquare = dirx * dirx + diry * diry
if distancesquare == 0:
# no distance? this should not happen,
# but just in case, we choose a random
# direction
dirx = random.randint(0,11) - 5.5
diry = random.randint(0,11) - 5.5
distancesquare = dirx * dirx + diry * diry
dp = (bdxs * dirx + bdys * diry) # scalar product
dp /= distancesquare # divide by distance * distance.
cdp = (cbdxs * dirx + cbdys * diry)
cdp /= distancesquare
# We are done.
(dirx * dp, diry * dp) is
# the projection of the velocity
# perpendicular to the virtual mirror
# surface.
Subtract it twice to get the
# new direction.
#
# Only collide if the sprites are moving
# towards each other: dp > 0
if dp > 0:
sprite2.dx -= 2 * dirx * dp
sprite2.dy -= 2 * diry * dp
sprite1.dx -= 2 * dirx * cdp
sprite1.dy -= 2 * diry * cdp
The function is called with 2 sprites as arguments during the mainloop.
Note that the elastic_collision function has no return values but
instead manipulates the .dx and .dy properties (the movement vectors) of both sprites.
# ...
inside mainloop
# ------ collision detection
for bird in birdgroup: # test if a bird collides with another bird
bird.crashing = False # make bird NOT blue
# check the Bird.number to make sure the bird is not crashing with himself
if not bird.waiting: # do not check birds outside the screen
crashgroup = pygame.sprite.spritecollide(bird, birdgroup, False )
for crashbird in crashgroup: # test bird with other bird collision
if crashbird.number > bird.number: #avoid checking twice
bird.crashing = True # make bird blue
crashbird.crashing = True # make other bird blue
if not (bird.waiting or crashbird.waiting):
elastic_collision(crashbird, bird) # change dx and dy of both birds
source code on github
To run this example you need:
file | in folder | download |
017_turning_and_physic.py pygame
Download the whole Archive with all files from Github:
https://github.com/horstjens/ThePythonGameBook/archives/master
babytux.png
pygame/data
babytux_neg.png
pygame/data
claws.ogg
from Battle of Wesnoth pygame/data
wormhole.ogg pygame/data
bomb.ogg pygame/data
shoot.ogg pygame/data
beep.ogg pygame/data
View/Edit/Download the file directly in Github: https://github.com/horstjens/ThePythonGameBook/blob/master/pygame/017_turning_and_physic.py
Step 018 - Mask and pixel perfect collision detection
- gameplay
In this variation of the previous game, you can toggle with the <key>c</key> key between the three different methods for collision detection:
collide_rect - the default method, and the fastest of all.
sprites need a self.rect attribute
collide_circle - slower but better looking.
sprites need a self.radius attribute
collide_mask - the slowest but most precise method.
sprites need a self.mask attribute, taken from the sprites image.
Fly the smaller penguin around (with <key>w</key> <key>a</key> <key>s</key> <key>d</key> <key>q</key> <key>e</key>, shoot at the big penguin with <key>space</key> and check the pattern of the yellow impact “wounds” depending of the different collide methods.
A bitmask is like an image with only two colours: collision-relevant and non-relevant.
Like an black / white photograph.
You can create your own bitmask but pygame has a command to create a bitmaks from an image:
pygame.mask.from_surface(Surface, threshold = 127) -> Mask
Note that if you rotate a sprite and rotate the sprite's image, you must create a new mask from the rotated image.
- collision dedection
#..
inside mainloop
elif event.key == pygame.K_c:
if collision == "rect":
collision = "circle"
elif collision == "circle":
collision = "mask"
elif collision == "mask":
collision = "rect"
screentext2.newmsg("collsion detection: %s" % collision)
# ...
# ------ collision detection
for bird in birdgroup:
#...
if collision == "rect":
crashgroup = pygame.sprite.spritecollide(bird, bulletgroup, False, pygame.sprite.collide_rect)
elif collision == "circle":
crashgroup = pygame.sprite.spritecollide(bird, bulletgroup, False, pygame.sprite.collide_circle)
elif collision == "mask":
crashgroup = pygame.sprite.spritecollide(bird, bulletgroup, False, pygame.sprite.collide_mask)
for ball in crashgroup: # test for collision with bullet
# ...
- problem: sprites overshooting each other
There is a common problem if you test very small, very fast sprites for collisions with small objects: the fast sprite is “teleporting” or overshooting over the obstacle, not triggering a collision detection.
Some quick solutions are:
use slower, bigger sprites and bigger obstacles
use a higher framerate
use a self.radius value of a sprite that is far bigger than the actual sprite.
Use this self.radius together with pygame.sprite.collide_circle for collision detection
experts: calculate if 2 vectors, originating from self.pos with self.dx,self.dy cross each others.
- what's new
The code is nearly the same code as in the previous example.
The Fragment>source code on github
To run this example you need:
file | in folder | download |
018_perfect_collision_detection.py pygame
Download the whole Archive with all files from Github:
https://github.com/horstjens/ThePythonGameBook/archives/master
babytux.png
pygame/data
crossmonster.png
pygame/data
xmonster.png
pygame/data
claws.ogg
from Battle of Wesnoth pygame/data
bomb.ogg pygame/data
shoot.ogg pygame/data
beep.ogg pygame/data
View/Edit/Download the file directly in Github: https://github.com/horstjens/ThePythonGameBook/blob/master/pygame/018_perfect_collision_detection.py
Step 019 - Homing missiles
In this example game, two players can fire bullets and rockets at each other or at big nasty monsters.
Bullets have a short range, but steal hitpoints if hitting a player or a monster.
There are 2 different kind of rockets:
a “light”, weak damage, fast flying variant.
Short lifetime.
Can be shot down.
a “heavy”, heavy damage, slow flying “sliding” variant.
Longer lifetime.
Can not be shot down, but it's flightpath can be disturbed if hit.
Both types of rockets aim automatically toward the target.
While the “light” rockets move in the direction of their heading (boring), the “heavy” rockets “slide” like in open space, making more interesting flight patterns.
A player can store a maximum ammo of 32 rockets (see class Player, self.rocketsmax).
The first 16 rockets are light rockets, the others are heavy rockets.
Not shooting increase the stock of missiles of each player (see the blue rocketbar above each player)
If no more monsters are present, the players can fight each other (duel mode).
New monsters can be created my pressing <key>m</key>.
If both players are shot down by monsters, the monsters start fighting themselves (Overtime).
You can press <key>o</key> to get more overtime and watch the monsters fighting each other.
Monster states
Each 15 seconds, (see class Monster, self.hunttime) the monster has a chance to select a new target.
Each monster has a primitive State_machine, cycling between the three states “do nothing”, “shoot bullets at target” and “shoot rockets at target”.
Each “state” is exactly 3 seconds long.
This is the code line to see in what “state” or “phase” a monster currently is (inside class Monster, def update):
self.phase = self.phases[int(self.alivetime) % 3 ]
Self.alivetime is a variable counting the time (in seconds) of existence of a monster.
The %-sign is the modulo operator.
It doesn't return the result of a division, but instead the remainder.
As you can see in this example, the remainder cycles between 0 , 1 and 2 if the divisor is 3:
for x in range(31): # python2.x
print x, "divided by 3 =", x/3, "remainder:", x%3,"float result:", x/3.0
output:
0 divided by 3 = 0 remainder: 0 float result: 0.0
1 divided by 3 = 0 remainder: 1 float result: 0.333333333333
2 divided by 3 = 0 remainder: 2 float result: 0.666666666667
3 divided by 3 = 1 remainder: 0 float result: 1.0
4 divided by 3 = 1 remainder: 1 float result: 1.33333333333
5 divided by 3 = 1 remainder: 2 float result: 1.66666666667
...
classes
All>
additional resources
data019.tar.gz
source code on github
To run this example you need:
file | in folder | download |
019_homing_missiles.py pygame
Download the whole Archive with all files from Github:
https://github.com/horstjens/ThePythonGameBook/archives/master
player_red2.png pygame/data
player_blue2.png pygame/data
xmonster_s.png pygame/data
xmonster_fire_s.png pygame/data
xmonster_left_s.png pygame/data
xmonster_right_s.png pygame/data
claws.ogg pygame/data
wormhole.ogg pygame/data
bomb.ogg pygame/data
shoot.ogg pygame/data
beep.ogg pygame/data
explode.ogg pygame/data
View/Edit/Download the file directly in Github: https://github.com/horstjens/ThePythonGameBook/blob/master/pygame/019_homing_missiles.py
Step 020 - Shooting bullets from the end of a cannon barrel
video
Video link: http://www.youtube.com/watch?v=SDsomsEWl7E
video
description
In this example, two tanks can be controlled by the players (using both hands), moving forward and backward and rotating.
Additionally, the turrets can rotate also.
The turrets can shoot bullets out of the tanks's main cannon (please admire the recoil effect) and the tanks can fire tracer rounds from machine guns.
Each gun has a machine gun at the turret and at its bow (see graphic at the right).
This source code examples teaches nothing new but demonstrate how to solve a specific problem: Creating bullet-sprites not at the center of it's launcher, but at the end (and some space away) from it.
The most obvious solution for such a problem would be to create the bullet at the center of it's launcher (the Tank) and use the Layer system to make sure the Tank is drawn on top of the bullet.
But if you have a fine eye you will notice some ugly effects: if you rotate a cannon fast enough, it will look like the bullet exits at the side of the cannon instead of at it's end.
Also creating one Sprite at the exact position of another sprite will trigger a collision detection, needing more code to make sure that a tank cannot shoot itself.
source code discussion
To deal with the problem of creating a bullet sprite at the exact end of a rotated cannon sprite, see the source code below.
All you need is a little knowledge of the math.sin and math.cos function (remember to transform grad into radiant), like explained in step017 - rotating, shooting, inheritance.
If you like complicated explanations: What you do here is creating a vector and rotating it to find the coordinate of a point (the musszle).
This is done in the methods calculate_origin of the Bullet and the Tracer>Bullet
For the Bullet, shooting out of the muzzle of the tanks main gun the problem is this: It's boss sprite, the Tank turret, is rotated by turretAngle.
Also the cannon is very long, nearly as long as the side of the Tank.
def calculate_origin(self):
# - spawn bullet at end of turret barrel instead tank center -
# cannon is around Tank.side long, calculatet from Tank center
# later subtracted 20 pixel from this distance
# so that bullet spawns closer to tank muzzle
self.pos[0] += math.cos(degrees_to_radians(self.boss.turretAngle)) * (Tank.side-20)
self.pos[1] += math.sin(degrees_to_radians(-self.boss.turretAngle)) * (Tank.side-20)
Tracer
For the Tracer (shooting out of the red bow rectangle of the tank) the point of launching is the little red rectangle in the front of the Tank.
Because the Tank can rotate it's turret independent of the tank's own rotation, the important variable here is tankangle.
The bow machine gun rectangle is not so much distanced from the Tank's center (side/2), but it is a bit on the left side.
De facto i created here an 2D-Vector, 30° from the Tank's center and with the lenght of Tank.side/2.
This vector is rotated with the tankAngle to find the coordinates of the point of origin for the Tracer round.
def calculate_origin(self):
"""overwriting because another point of origin is needed"""
# - spawn bullet at end of bow rect (and some extra distance)
# the bow rect is in the middle -left from the tank center
# calculatet by going -30° from the Tank center for the half tank side
self.pos[0] += math.cos(degrees_to_radians(30+self.boss.tankAngle)) * (Tank.side/2)
self.pos[1] += math.sin(degrees_to_radians(-30-self.boss.tankAngle)) * (Tank.side/2)
keyboard overflow problem
While playing you will notice that sometimes some keyboard command seem to be ignored when you press several keys at once.
It also can happen that if you press some combinations of more than 2 keys at the same time, it seems like a different key was pressed (ghost)
To test it out:
run example tankdemo03.py from below.
press <key>w</key> and <key>s</key> to move the tank forward and back (works)
press <key>w</key> and <key>s</key> at the same time, to stop the tank movement (works also)
if you press <key>d</key> and <key>a</key> at the same time, the tank does not rotate (correctly)
while pressing <key>d</key> and <key>a</key>, also press <key>w</key> or <key>s</key>.
This time, it works (the tank does not rotate, but moves forward & back).
In this case, 3 keys are pressed simultaneously and interpreted correctly.
the combinations <key>w</key> and <key>a</key> , <key>s</key> and <key>a</key>, <key>w</key> and <key>d</key>, <key>s</key> and <key>d</key> all work, letting the tank rotate and move at the same time
while pressing <key>w</key> and <key>s</key> at the same time (no movement), also press <key>d</key> to rotate the tank.
This does not work ! Worse, the bow mg start firing, but you have not pressed <key>LCTRL</key> ! (ghost key effect)
This phenomenon is well known among game designer.
Ultimately it is caused by the way how keyboards are constructed.
A possible solution is to design games with fewer keys to be pressed and use keys like SHIFT, ALT and CTRL because those keys are better recognized by design of the hardware.
Also think about accepting input from Mouse and Joysticks (see pygame documentation) or writing network-games where each player has his own keyboard.
See the glossary entry keyboard for more information.
You will find there a very cool program1) to test out how many keys you can press at the same time without confusing your keyboard.
keyboard
additional resources
no additional resources necessary.
classchart
ideas
<note tip>Can you add sound effects to the Tankdemo ? See step010 - using sound and music</note>
<note tip>Can you create moving or stationary practice targets for the Tankdemo ? See step017 - rotating, shooting...</note>
source code on github
To run this example you need:
file | in folder | download |
020_shooting_from_tank.py pygame
Download the whole Archive with all files from Github:
https://github.com/horstjens/ThePythonGameBook/archives/master
View/Edit/Download the file directly in Github: https://github.com/horstjens/ThePythonGameBook/blob/master/pygame/020_shooting_from_tank.py
Step 021 - Rotating toward a target
video
Video link: http://www.youtube.com/watch?v=nfaOmkhK-V0
Bug: tank angle and turret angle is swapped in the video.
The source code below is correct.
video
description
In the code example below, you can move both tanks (as in step020) or you can “teleport” the yellow tank toward the mouse pointer by clicking the left mouse button.
The blue tank will rotate it's turrent toward the position of the yellow tank.
The problem is to decide in wich direction the turret should move.
source code discussion
In the code example below this is solved by the Tank>aim_at_player.
Note that the angle diff can result in values > than 360, so the result is divided by 360 and its modulo1), is saved.
You will also note that very few lines are altered since step020, mostly the tank> def aim_at_player(self, targetnumber=0):
#print "my pos: x:%.1f y:%.1f " % ( self.pos[0], self.pos[1])
#print "his pos: x:%.1f y:%.1f " % ( Tank.book[0].pos[0], Tank.book[0].pos[1])
deltax = Tank.book[targetnumber].pos[0] - self.pos[0]
deltay = Tank.book[targetnumber].pos[1] - self.pos[1]
angle = math.atan2(-deltax, -deltay)/math.pi*180.0
diff = (angle - self.turretAngle - 90) %360 #reset at 360
if diff == 0:
self.turndirection = 0
elif diff > 180:
self.turndirection = 1
else:
self.turndirection = -1
return diff
And also the mainloop's event handler is expanded.
He now checks the MOUSEBUTTON event to teleport player1 tank around:
# teleport player1 tank if left mousebutton is pressed
elif event.type == pygame.MOUSEBUTTONDOWN:
if pygame.mouse.get_pressed()[0]:
#left mousebutton was pressed
player1.pos[0]=pygame.mouse.get_pos()[0]
player1.pos[1]=pygame.mouse.get_pos()[1]
<note>
User yipyip has donated a very interesting solution to this problem, using Complex numbers.
See his source code here: yipyip's solution
</note>
ideas
<note tip>Do you remember the keyboard-rollover problem from step020 ? Now you could use less keyboard commands by letting both players turrets automatically rotate toward each other</note>
<note tip>Can you code an Artificial_intelligence for the bow machine gun ? Let it firing as soon as it's Deflection angle is low enough:
if -5 < tankDiffAngle < 5:
Tracer(self)
</note>
additional resources
no additional resources needed
source code on github
To run this example you need:
file | in folder | download |
021_targeting.py pygame
Download the whole Archive with all files from Github:
https://github.com/horstjens/ThePythonGameBook/archives/master
View/Edit/Download the file directly in Github: https://github.com/horstjens/ThePythonGameBook/blob/master/pygame/021_targeting.py
Step 022 - Scrolling and mini-map
video
Video link: http://www.youtube.com/watch?v=DusPphBj98A
video
description
In this example you can control the 2 famous tanks from the previous step.
But some features were added:
The map or “playground” is larger than the screen resolution.
.
The player sprite(s) can be steered around the whole map (but not over the map edge).
The position of all Tank, Bullet and Tracer sprites and the current visible area of the map can be seen in the minimap or radar-map.
The minimap is always visible on the top right corner of the screen.
The minimap itself is also a Sprite, called Radarmap.
The dimensions of screen, the big map and the radarmap are all visible inside the Config>real-time-strategy games (rts-games).
code discussion
In the Code examples of this part of the book, all sprites have a self.pos attribute, a list of two coordinates [x,y].
This self.pos variable is used for all calculations (angle, movement etc.).
Until now, the self.pos variable was identical with the self.rect position (self.rect.centerx, self.rect.centery), the only difference being that self.rect demands integer values while the self.pos variables could handle decimal points.
But now with the bigmap being by far larger as the visible screen area, the math gets more complex.
While the self.pos variables stay more or less untouched, the position of the visible screen area in realation to the big map is to be calculated when blitting the sprites at the self.rect.center position.
Config.cornerpoint shows the way
In this example, the offset of the visible screen to the bigmap is calculated with the aid of the variable Config.cornerpoint.
It's a list of 2 coordinates [x,y] and stored inside the Config-class so that other>screen area.
At the beginning, those are set to [0,0] and there is no difference between the self.rect coordinates and the self.pos coordinates.
As soon as the player moves the visible area with the Cursor - Keys, the value of Config.cornerpoint is changed (this is done inside the mainloop):
#...
# -------- scroll the big map ----------
scrollx = 0
scrolly = 0
pressedkeys = pygame.key.get_pressed()
# --- handle Cursor keys to scroll map ----
if pressedkeys[pygame.K_LEFT]:
scrollx -= Config.scrollstepx
if pressedkeys[pygame.K_RIGHT]:
scrollx += Config.scrollstepx
if pressedkeys[pygame.K_UP]:
scrolly -= Config.scrollstepy
if pressedkeys[pygame.K_DOWN]:
scrolly += Config.scrollstepy
# -------- scroll the visible part of the map ------
Config.cornerpoint[0] += scrollx
Config.cornerpoint[1] += scrolly
#...
Config.scrollstepx and Config.scrollstepy are variables to control how many pixels the visible area should scroll each frame.
Do not make those values too big, or the scrolling look jumpy.
Spritely Tank>
Now, let's take a look inside a typical Sprite>Tank>update(self)-function, there is the blitting of the self.rect.center variables.
Here the values of Config.cornerpoint are subtracted.
Change the - into a + for a funny effect:
#...
self.rect.centerx = round(self.pos[0] - Config.cornerpoint[0], 0) #x
self.rect.centery = round(self.pos[1] - Config.cornerpoint[1], 0) #y
#...
Bulleting Sprites
Also note that in contrast to the previous step, in this code example the Bullet sprite>Bullet (and Tracer) sprites.
This is necessary so that the Radarmap sprite can iterate throug all Tank sprites (in the Tank book) and all Bullets (in the Bullet book) to draw little dots.
Inside the Radarmap>self.factorx and self.factory are variables indication the relation between bigmap to radarmap.
#...
inside Radarmap>
for tanknumber in Tank.book: # tank are circles with radius 4
pos = Tank.book[tanknumber].pos
color = Tank.book[tanknumber].color
pygame.draw.circle(self.image,color, (round(pos[0] * self.factorx,0),
round(pos[1] * self.factory,0)), 4 )
for bulletnumber in Bullet.book:
if Bullet.book[bulletnumber].tracer:
dotlength = 2 # bullets are rectangles with sidelength 4 (bullets) or 2 (tracer)
else:
dotlength = 4 # rect with length 1 is not visible
pos = Bullet.book[bulletnumber].pos
color = Bullet.book[bulletnumber].color
pygame.draw.rect(self.image, color,(round(pos[0] * self.factorx,0),
round(pos[1] * self.factory,0),
dotlength, dotlength))
#...
ideas
<note tip>How powerful is your computer ? Change all those variables in the Config>loading images (step007) ? try to load an existing image to use as bigmap</note>
<note tip>What of building obstacles for the tanks to navigate around?</note>
<note tip>What about lobbing projectiles over obstacles by calculating kinetic energy required to move an object that weighs X amount Y distance?</note>
additional resources
no additional resources necessary
source code on github
To run this example you need:
file | in folder | download |
022_minimap.py pygame
Download the whole Archive with all files from Github:
https://github.com/horstjens/ThePythonGameBook/archives/master
View/Edit/Download the file directly in Github: https://github.com/horstjens/ThePythonGameBook/blob/master/pygame/022_minimap.py
Step 023 - LayeredDirty Sprites
<note warning>This page is under construction!</note>
According to the pygame documentation at
http://www.pygame.org/docs/ref/sprite.html#pygame.sprite.LayeredDirty
pygame provides a LayeredDirty group for sprites.
This is handy for sprites that do not move around a lot and don't need to be redrawn every frame.
Sadly i am not able to understand how to change the layer of a LayeredDirty sprite after it's creation…
The only way to assign o layer to a sprite DirtyLayered sprite group is to watch the order of creating those sprites.
LayerdDirty works best with DirtySprites, a subclass of Sprites:
pygame.sprite.DirtySprite
a more featureful subclass of Sprite with more attributes
pygame.sprite.DirtySprite(*groups): return DirtySprite
None
Extra DirtySprite attributes with their default values:
dirty = 1
if set to 1, it is repainted and then set to 0 again
if set to 2 then it is always dirty ( repainted each frame,
flag is not reset)
0 means that it is not dirty and therefor not repainted again
blendmode = 0
its the special_flags argument of blit, blendmodes
source_rect = None
source rect to use, remember that it is relative to
topleft (0,0) of self.image
visible = 1
normally 1, if set to 0 it will not be repainted
(you must set it dirty too to be erased from screen)
layer = 0
(READONLY value, it is read when adding it to the
LayeredRenderGroup, for details see doc of LayeredRenderGroup)
Also see schwarzweiss game for an example of how to use LayeredDirty sprite group.
source code on github
- Step 024 - Game menu
<note warning>This page is under construction!</note>
description
easygui,
menu,
pygame
Most games do not throw the player directly into the game (like in all previous steps) but instead offer some kind of game menu.
Inside a game menu, the player can usually access options (like changinging the screen resolution or toggling full-screen mode) ), he can view the highscore list, starting or leaving the game and sometimes watch an introduction or help screen.
Tasks like this are ideally done using a GUI (graphical User Interface but sadly, pygame is not shipped with any GUI.
Instead, you can use an existing gui like Tkinter, EasyGui, wxpython to write your game menu and launch the whole pygame “game” from within.
This can be done by programming the whole pygame game as a function, passing arguments to it and returning the score value.
#...
inside gui...
import pygamegame
#...
some gui code letting the player choose the screen resolution
score = pygamegame(screen_resolution) # start the pygame game and return the score to the menu
#...
some gui code to add the score to the highscore
Alternatively you can write your own menu system using pygame.
The source code example below includes several files:
additional resources
Normally you do not want to have more than one executable program in the project folder, to avoid confusing the user.
Instead, you want another folder (data) inside the project folder, and only a single executable program in the project folder calling all other necessary programs.
For this example, it is necessary that the folder where easyguimenu.py is located has an subfolder called data.
Inside this data folder must be the programs screensaver.py, easygui.py and one empty file named __init__.py.
To make this code example working, you need 4 files, 3 of them located inside a subfolder named data:
easyguimenu.py (the game menu, see source code below)
inside the subfolder data:
init.py (an empty file, just the filename is important)
easygui.py (see EasyGUI)
screensaver.py (the game itself, see source code below)
You best download and extract this archive, it contains all necessary files and folders:
all files and folders in one archive
ideas
<note tip>Replace the command easygui.buttonboxe with easygui.choicebox.
Try out other EasyGUI commands.</note>
<note tip>Easygui can display gif-graphics and if you install python-imaging-tk correctly it can display also jpg and png graphics.
Make your game menu even more pretty by displaying a nice graphic.</note>
source code
pygame screensaver
This is not even a game, just some sort of screensaver drawing random colored circles.
Written as a function in pygame, it accepts a screenresolution tuple (x,y) as argument and returns the time in seconds the screensaver was watched.
- screensaver.py
- #!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# screensaver.py
import pygame
import random
def screensaver(screenresolution = (640,480)):
# -*- coding: utf-8 -*-
"""very simple test "game" or screensaver.
all the user have to do is press ESC or SPACE.
the "game" paint random circles.
the "game" accept a screen resolution tuple as argument.
the "game" returns the time passed until the user pressed space"""
pygame.init() #initialize pygame
screen=pygame.display.set_mode((screenresolution[0],screenresolution[1])) # set screensize of pygame window
background = pygame.Surface(screen.get_size()) #create empty pygame surface
background.fill((255,255,255)) #fill the background white color (red,green,blue)
background = background.convert() #convert Surface object to make blitting faster
screen.blit(background, (0,0)) #draw the background on screen
clock = pygame.time.Clock() #create a pygame clock object
mainloop = True
FPS = 30 # desired framerate in frames per second.
try out other values !
playtime = 0.0 # how many seconds the "game" is played
while mainloop:
milliseconds = clock.tick(FPS) # do not go faster than this framerate
playtime += milliseconds / 1000.0 # add seconds to playtime
# paint random circles
color = (random.randint(0,255), random.randint(0,255), random.randint(0,255))
pygame.draw.circle(screen, color, (random.randint(0,screenresolution[0]),
random.randint(0,screenresolution[1])),
random.randint(1, min(screenresolution[0], screenresolution[1])),
random.randint(0,1))
for event in pygame.event.get():
if event.type == pygame.QUIT:
mainloop = False # pygame window closed by user
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
mainloop = False # user pressed ESC
if event.key == pygame.K_SPACE:
mainloop = False # user pressed ESC
pygame.display.set_caption("press ESC to quit.
frame rate: %.2f fps, time: %.2f seonds" % (clock.get_fps(), playtime))
pygame.display.flip() # flip the screen like in a flip book
print "This 'game' was played for %.2f seconds" % playtime
pygame.quit() # this line is important so that the pygame window does not remain open.
return playtime # in seconds
if __name__ == '__main__':
screensaver()
Here is the code of a very simple menu system written in Easygui.
It allows to change the screen resolution, to start the screensaver, and to quit.
The number of seconds the screensaver was watched is returned from the screensaver.py program to this program and displayed.
- easyguimenu.py
- #!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# easyguimenu.py
#
# Copyright 2011 Horst JENS <horst.jens@spielend-programmieren.at>
# license: gpl
# part of http://ThePythonGameBook.com
# needs easygui from http://easygui.sourceforge.net/ to work
# both easygui.py and screensaber.py must be located in a
# subdirectory 'data'.
In this subdirectory there have to exist an
# empty file with the name '__init__.py'
from data import easygui
from data import screensaver
def gamemenu():
resolution = [640,480]
watched = 0
msg = "Welcome at screensaver game menu.
please choose wisely:"
buttons = ["watch screensaver", "change resolution", "quit"]
picture = None # gif file
while True: #endless loop
title = "screensave will run with %ix%i resolution" % (resolution[0], resolution[1])
selection = easygui.buttonbox(msg, title, buttons, picture)
if selection == "quit":
easygui.msgbox("bye-bye")
break # leave loop
elif selection == "watch screensaver":
watched += 1
playtime = screensaver.screensaver(resolution)
msg += "\n you watched the screensaver for %i seconds" % playtime
elif selection == "change resolution":
resolution[0] = easygui.integerbox("Please enter the new value for the x resolution:",
title, resolution[0], 0, 4000)
resolution[1] = easygui.integerbox("Please enter the new value for the y resolution:",
title, resolution[1], 0, 2000)
return watched # returns how many times the screensaver was watched (if anybody ask)
if __name__ == '__main__':
gamemenu()
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
- 002_display_fps.py
Open a Pygame window and display framerate.
Program terminates by pressing the ESCAPE-Key.
works with python2.7 and python3.4
URL : http://thepythongamebook.com/en:part2:pygame:step002
Author : horst.jens@spielend-programmieren.at
License: GPL, see http://www.gnu.org/licenses/gpl.html
"""
#the next line is only needed for python2.x and not necessary for python3.x
from __future__ import print_function, division
import pygame
# Initialize Pygame.
pygame.init()
# Set size of pygame window.
screen=pygame.display.set_mode((640,480))
# Create empty pygame surface.
background = pygame.Surface(screen.get_size())
# Fill the background white color.
background.fill((255, 255, 255))
# Convert Surface object to make blitting faster.
background = background.convert()
# Copy background to screen (position (0, 0) is upper left corner).
screen.blit(background, (0,0))
# Create Pygame clock object.
clock = pygame.time.Clock()
mainloop = True
# Desired framerate in frames per second.
Try out other values.
FPS = 30
# How many seconds the "game" is played.
playtime = 0.0
while mainloop:
# Do not go faster than this framerate.
milliseconds = clock.tick(FPS)
playtime += milliseconds / 1000.0
for event in pygame.event.get():
# User presses QUIT-button.
if event.type == pygame.QUIT:
mainloop = False
elif event.type == pygame.KEYDOWN:
# User presses ESCAPE-Key
if event.key == pygame.K_ESCAPE:
mainloop = False
# Print framerate and playtime in titlebar.
text = "FPS: {0:.2f} Playtime: {1:.2f}".format(clock.get_fps(), playtime)
pygame.display.set_caption(text)
#Update Pygame display.
pygame.display.flip()
# Finish Pygame.
pygame.quit()
# At the very last:
print("This game was played for {0:.2f} seconds".format(playtime))
This Gist brought to you by gist-it.view rawpygame/002_display_fps.py
#!/usr/bin/env python
"""
002_display_fps_pretty.py
Display framerate and playtime.
Works with Python 2.7 and 3.3+.
URL: http://thepythongamebook.com/en:part2:pygame:step002
Author: yipyip
License: Do What The Fuck You Want To Public License (WTFPL)
See http://sam.zoy.org/wtfpl/
"""
####
import pygame
####
class PygView(object):
def __init__(self, width=640, height=400, fps=30):
"""Initialize pygame, window, background, font,...
"""
pygame.init()
pygame.display.set_caption("Press ESC to quit")
self.width = width
self.height = height
#self.height = width // 4
self.screen = pygame.display.set_mode((self.width, self.height), pygame.DOUBLEBUF)
self.background = pygame.Surface(self.screen.get_size()).convert()
self.clock = pygame.time.Clock()
self.fps = fps
self.playtime = 0.0
self.font = pygame.font.SysFont('mono', 20, bold=True)
def run(self):
"""The mainloop
"""
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
running = False
milliseconds = self.clock.tick(self.fps)
self.playtime += milliseconds / 1000.0
self.draw_text("FPS: {:6.3}{}PLAYTIME: {:6.3} SECONDS".format(
self.clock.get_fps(), " "*5, self.playtime))
pygame.display.flip()
self.screen.blit(self.background, (0, 0))
pygame.quit()
def draw_text(self, text):
"""Center text in window
"""
fw, fh = self.font.size(text) # fw: font width, fh: font height
surface = self.font.render(text, True, (0, 255, 0))
# // makes integer division in python3
self.screen.blit(surface, ((self.width - fw) // 2, (self.height - fh) // 2))
####
if __name__ == '__main__':
# call with width of window and fps
PygView(640, 400).run()
This Gist brought to you by gist-it.view rawpygame/002_display_fps_pretty.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
003_static_blit.py
static blitting and drawing
url: http://thepythongamebook.com/en:part2:pygame:step003
author: horst.jens@spielend-programmieren.at
licence: gpl, see http://www.gnu.org/licenses/gpl.html
work with python3.4 and python2.7
Blitting a surface on a static position
Drawing a filled circle into ballsurface.
Blitting this surface once.
introducing pygame draw methods
The ball's rectangular surface is black because the background
color of the ball's surface was never defined nor filled."""
#the next line is only needed for python2.x and not necessary for python3.x
from __future__ import print_function, division
import pygame
pygame.init()
screen=pygame.display.set_mode((640,480))
background = pygame.Surface(screen.get_size())
background.fill((255,255,255)) # fill the background white
background = background.convert() # prepare for faster blitting
ballsurface = pygame.Surface((50,50)) # create a rectangular surface for the ball
#pygame.draw.circle(Surface, color, pos, radius, width=0)
# draw blue filled circle on ball surface
pygame.draw.circle(ballsurface, (0,0,255), (25,25),25)
ballsurface = ballsurface.convert()
ballx = 320
bally = 240
#------- try out some pygame draw functions --------
# see the original documentation at http://www.pygame.org/docs/ref/draw.html
# pygame.draw.rect(Surface, color, Rect, width=0): return Rect
# rect: (x-position of topleft corner, y-position of topleft corner, width, height)
pygame.draw.rect(background, (0,255,0), (50,50,100,25))
# pygame.draw.circle(Surface, color, pos, radius, width=0): return Rect
pygame.draw.circle(background, (0,200,0), (200,50), 35)
# pygame.draw.polygon(Surface, color, pointlist, width=0): return Rect
pygame.draw.polygon(background, (0,180,0), ((250,100),(300,0),(350,50)))
# pygame.draw.arc(Surface, color, Rect, start_angle, stop_angle, width=1): return Rect
# radiant instead of grad
pygame.draw.arc(background, (0,150,0),(400,10,150,100), 0, 3.14)
#------- blit the surfaces on the screen to make them visible
screen.blit(background, (0,0)) # blit the background on the screen (overwriting all)
screen.blit(ballsurface, (ballx, bally)) # blit the topleft corner of ball surface at pos (ballx, bally)
clock = pygame.time.Clock()
mainloop = True
FPS = 30 # desired framerate in frames per second.
try out other values !
playtime = 0.0
while mainloop:
milliseconds = clock.tick(FPS) # do not go faster than this frame rate
playtime += milliseconds / 1000.0
# ----- event handler -----
for event in pygame.event.get():
if event.type == pygame.QUIT:
mainloop = False # pygame window closed by user
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
mainloop = False # user pressed ESC
pygame.display.set_caption("Frame rate: {:0.2f} frames per second."
" Playtime: {:.2} seconds".format(
clock.get_fps(),playtime))
pygame.display.flip() # flip the screen like in a flipbook
print("this 'game' was played for %.2f seconds" % playtime)
This Gist brought to you by gist-it.view rawpygame/003_static_blit.py
# -*- coding: utf-8 -*-
"""
003_static_blit_pretty.py
static blitting and drawing (pretty version)
url: http://thepythongamebook.com/en:part2:pygame:step003
author: horst.jens@spielend-programmieren.at
licence: gpl, see http://www.gnu.org/licenses/gpl.html
works with pyhton3.4 and python2.7
Blitting a surface on a static position
Drawing a filled circle into ballsurface.
Blitting this surface once.
introducing pygame draw methods
The ball's rectangular surface is black because the background
color of the ball's surface was never defined nor filled."""
import pygame
class PygView(object):
def __init__(self, width=640, height=400, fps=30):
"""Initialize pygame, window, background, font,...
default arguments
"""
pygame.init()
pygame.display.set_caption("Press ESC to quit")
self.width = width
self.height = height
self.screen = pygame.display.set_mode((self.width, self.height), pygame.DOUBLEBUF)
self.background = pygame.Surface(self.screen.get_size()).convert()
self.background.fill((255,255,255)) # fill background white
self.clock = pygame.time.Clock()
self.fps = fps
self.playtime = 0.0
self.font = pygame.font.SysFont('mono', 24, bold=True)
def paint(self):
"""painting on the surface"""
#------- try out some pygame draw functions --------
# pygame.draw.line(Surface, color, start, end, width)
pygame.draw.line(self.background, (0,255,0), (10,10), (50,100))
# pygame.draw.rect(Surface, color, Rect, width=0): return Rect
pygame.draw.rect(self.background, (0,255,0), (50,50,100,25)) # rect: (x1, y1, width, height)
# pygame.draw.circle(Surface, color, pos, radius, width=0): return Rect
pygame.draw.circle(self.background, (0,200,0), (200,50), 35)
# pygame.draw.polygon(Surface, color, pointlist, width=0): return Rect
pygame.draw.polygon(self.background, (0,180,0), ((250,100),(300,0),(350,50)))
# pygame.draw.arc(Surface, color, Rect, start_angle, stop_angle, width=1): return Rect
pygame.draw.arc(self.background, (0,150,0),(400,10,150,100), 0, 3.14) # radiant instead of grad
# ------------------- blitting a Ball --------------
myball = Ball() # creating the Ball object
myball.blit(self.background) # blitting it
def run(self):
"""The mainloop
"""
self.paint()
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
running = False
milliseconds = self.clock.tick(self.fps)
self.playtime += milliseconds / 1000.0
self.draw_text("FPS: {:6.3}{}PLAYTIME: {:6.3} SECONDS".format(
self.clock.get_fps(), " "*5, self.playtime))
pygame.display.flip()
self.screen.blit(self.background, (0, 0))
pygame.quit()
def draw_text(self, text):
"""Center text in window
"""
fw, fh = self.font.size(text)
surface = self.font.render(text, True, (0, 0, 0))
self.screen.blit(surface, (50,150))
class Ball(object):
"""this is not a native pygame sprite but instead a pygame surface"""
def __init__(self, radius = 50, color=(0,0,255), x=320, y=240):
"""create a (black) surface and paint a blue ball on it"""
self.radius = radius
self.color = color
self.x = x
self.y = y
# create a rectangular surface for the ball 50x50
self.surface = pygame.Surface((2*self.radius,2*self.radius))
# pygame.draw.circle(Surface, color, pos, radius, width=0) # from pygame documentation
pygame.draw.circle(self.surface, color, (radius, radius), radius) # draw blue filled circle on ball surface
self.surface = self.surface.convert() # for faster blitting.
# to avoid the black background, make black the transparent color:
# self.surface.set_colorkey((0,0,0))
# self.surface = self.surface.convert_alpha() # faster blitting with transparent color
def blit(self, background):
"""blit the Ball on the background"""
background.blit(self.surface, ( self.x, self.y))
####
if __name__ == '__main__':
# call with width of window and fps
PygView().run()
This Gist brought to you by gist-it.view rawpygame/003_static_blit_pretty.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
004_colorkey.py
dynamic blitting and colorkey
url: http://thepythongamebook.com/en:part2:pygame:step004
author: horst.jens@spielend-programmieren.at
licence: gpl, see http://www.gnu.org/licenses/gpl.html
Blitting one surface on 2 static positions, once before the
mainloop and once inside the mainloop.
using colorkey to make a part of the surfaces tranparent
blitting lines on the screen to create a colourful pattern
like in a screensaver
"""
import pygame
import random
pygame.init()
screen=pygame.display.set_mode((640,480))
background = pygame.Surface(screen.get_size())
background.fill((255,255,255)) # fill the background white (red,green,blue)
background = background.convert() # faster blitting
ballsurface = pygame.Surface((50,50)) # create a new surface (black by default)
ballsurface.set_colorkey((0,0,0)) # make black the transparent color (red,green,blue)
#pygame.draw.circle(Surface, color, pos, radius, width=0)
pygame.draw.circle(ballsurface, (0,0,255), (25,25),25) # paint blue circle
ballsurface = ballsurface.convert_alpha() # faster blitting, convert_alpha() because transparency
screen.blit(background, (0,0)) #draw background on screen (overwriting all)
ballx = 20 # left ball position
bally = 240
screen.blit(ballsurface, (ballx, bally)) #draw the ball surface (lines will draw over this ball)
ballx2 = 400 # right ball position
bally2 = 380
clock = pygame.time.Clock()
mainloop = True
FPS = 30 # desired framerate in frames per second.
try out other values !
playtime = 0.0
t = 0 # variable used to draw a pattern
color1 = 0
color2 = 0
while mainloop:
milliseconds = clock.tick(FPS) # do not go faster than this framerate
playtime += milliseconds / 1000.0
for event in pygame.event.get():
if event.type == pygame.QUIT:
mainloop = False # pygame window closed by user
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
mainloop = False # user pressed ESC
# ------- draw cute pattern ------------------
pygame.draw.line(screen, (color1,255-color1,color2), (32*t,0), (0,480-24*t))
pygame.draw.line(screen, (255-color2,color2,color1), (32*t,480), (640,480-24*t))
screen.blit(ballsurface, (ballx2, bally2)) #draw the ball over the lines
t += 1 # increase t
if t > 20:
t = 0 # reset t
color1 = random.randint(0,255) # new color
color2 = random.randint(0,255)
# --------- end of cute pattern drawing code ----------
pygame.display.set_caption("Frame rate %.2f frames per second.
Playtime: %.2f seconds" % (clock.get_fps(),playtime))
pygame.display.flip() # flip the screen 30 times a second
print "This 'game' was played for %.2f seconds." % playtime
This Gist brought to you by gist-it.view rawpygame/004_colorkey.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
""" 004_alphademo.py
colorkey and alpha-value
url: http://thepythongamebook.com/en:part2:pygame:step004
author: horst.jens@spielend-programmieren.at
per-pixel-alpha code by Claudio Canepa
licence: gpl, see http://www.gnu.org/licenses/gpl.html
works with pyhton3.4 and python2.7
"""
import pygame
import os
def get_alpha_surface( surf, alpha=128, red=128, green=128, blue=128, mode=pygame.BLEND_RGBA_MULT):
"""returns a copy of a surface object with user-defined
values for red, green, blue and alpha.
Values from 0-255.
thanks to Claudio Canepa
for this function."""
tmp = pygame.Surface( surf.get_size(), pygame.SRCALPHA, 32)
tmp.fill( (red,green,blue,alpha) )
tmp.blit(surf, (0,0), surf.get_rect(), mode)
return tmp
def bounce(value, direction, bouncing=True, valuemin=0, valuemax=255):
"""bouncing a value (like alpha or color) between
baluemin and valuemax.
When bouncing is True,
direction (usually -1 or 1) is inverted when reaching valuemin or valuemax"""
value += direction # increase or decrase value by direction
if value <= valuemin:
value = valuemin
if bouncing:
direction *= -1
elif value >= valuemax:
value = valuemax
if bouncing:
direction *= -1
return value, direction
def write(msg="pygame is cool", size=24, color=(255,255,255)):
myfont = pygame.font.SysFont("None", size)
mytext = myfont.render(msg, True, color)
mytext = mytext.convert_alpha()
return mytext
def alphademo(width=800, height=600):
pygame.init()
screen=pygame.display.set_mode((width, height))
background = pygame.Surface(screen.get_size()).convert()
#background.fill((255, 255, 255)) #fill the background white
venus = pygame.image.load(os.path.join("data","800px-La_naissance_de_Venus.jpg")).convert()
# transform venus and blit on background in one go
pygame.transform.scale(venus, (width, height), background)
# --------- png image with convert.alpha() ------------------
# .png and .gif graphics can have transparency.
use convert_alpha()
pngMonster = pygame.image.load(os.path.join("data", "colormonster.png")).convert_alpha()
pngMonster0 = pngMonster.copy() # a copy
pngMonster3 = pngMonster.copy() # copy for per-pixel alpha
# ---------- jpg image ------------
# using .convert() at an .png image is the same as using a .jpg
# => no transparency !
jpgMonster = pygame.image.load(os.path.join("data","colormonster.jpg")).convert()
jpgMonster0 = jpgMonster.copy() # copy of jpgMonster
jpgMonster1 = jpgMonster.copy() # another copy to demonstrate colorkey
jpgMonster1.set_colorkey((255,255,255)) # make white color transparent
jpgMonster1.convert_alpha()
jpgMonster2 = jpgMonster.copy() # another copy for surface alpha
jpgMonster3 = jpgMonster.copy() # anoter copy for per-pixel alpha
# ------- text surfaces ----------
png0text = write("png (has alpha)")
png3text = write("png with pixel-alpha")
jpg0text = write("jpg (no alpha)")
jpg1text = write("jpg with colorkey")
jpg2text = write("jpg with surface alpha")
jpg3text = write("jpg with pixel-alpha")
# ------- for bitmap-alpha --------
alpha = 128 # between 0 and 255.
direction = 1 # change of alpha
# ------- for per-pixel-alpha -----
r = 255 # red
g = 255 # green
b = 255 # blue
a = 255 # pixel-alpha
modeNr = 7
# index 7, int-value 8, name="BLEND_RGB_MULT" ,usage = pygame.BLEND_RGB_MULT
paper = pygame.Surface((400,100)) # background for instructions
#paper.fill((0,0,0)) # is already black, no fill necessary
paper.set_alpha(128) # half-transparent
modelist = [ "BLEND_ADD",
"BLEND_SUB",
"BLEND_MULT",
"BLEND_MIN",
"BLEND_MAX",
"BLEND_RGBA_ADD",
"BLEND_RGBA_SUB",
"BLEND_RGBA_MULT",
"BLEND_RGBA_MIN",
"BLEND_RGBA_MAX" ]
# ------- mainloop ----------
clock = pygame.time.Clock()
mainloop = True
effects = False
while mainloop:
clock.tick(30)
screen.blit(background, (0,0)) # draw background every frame
pygame.display.set_caption("insert/del=red:%i, home/end=green:%i, pgup/pgdwn=blue:%i, +/-=pixalpha:%i press ESC" % ( r, g, b, a))
for event in pygame.event.get():
if event.type == pygame.QUIT:
mainloop = False
elif event.type == pygame.KEYDOWN: # press and release key
if event.key == pygame.K_ESCAPE:
mainloop = False
if event.key == pygame.K_RETURN or event.key == pygame.K_KP_ENTER:
#modeNr += 1
#if modeNr > 9:
# modeNr = 0 # cycle throug number 0 to 9
modeNr = (modeNr + 1) % len(modelist) # by yipyip
mode = pygame.constants.__dict__[modelist[modeNr]]
# ------ keyb is pressed ? -------
dr, dg, db, da = 0,0,0,0 # set changing to 0 for red, green, blue, pixel-alpha
pressed_keys = pygame.key.get_pressed()
if pressed_keys[pygame.K_PAGEUP]:
db = 1 # blue up
if pressed_keys[pygame.K_PAGEDOWN]:
db = -1 # blue down
if pressed_keys[pygame.K_HOME]:
dg = 1 # green up
if pressed_keys[pygame.K_END]:
dg = -1 # green down
if pressed_keys[pygame.K_INSERT]:
dr = 1 # red up
if pressed_keys[pygame.K_DELETE]:
dr = -1 # red down
if pressed_keys[pygame.K_KP_PLUS]:
da = 1 # alpha up
if pressed_keys[pygame.K_KP_MINUS]:
da = -1 # alpha down
# ------- change color and alpha values --------
alpha, direction = bounce(alpha, direction) # change alpha
r, dr = bounce(r,dr, False) # red for per-pixel
g, dg = bounce(g,dg, False) # green for per-pixel
b, db = bounce(b, db, False) # blue for per-pixel
a, da = bounce(a, da, False) # alpha for per-pixel
# ----- blit jpgMonster0 as ist is, no alpha at all ------
screen.blit(jpgMonster0, (0, 300))
screen.blit(jpg0text,(0,550))
# ------blit jpgMonster1 with the colorkey set to white ------
screen.blit(jpgMonster1, (200,300))
screen.blit(jpg1text, (200,550))
# ----- blit jpgmonster2 with alpha for whole surface --------
jpgMonster2.set_alpha(alpha) # alpha for whole surface
screen.blit(jpgMonster2, (400,300)) # blit on screen
screen.blit(jpg2text,(400,550))
screen.blit(write("surface-alpha: %i" % alpha),(400,570))
# ----- blit jpgmonster3 with per-pixel alpha-------
tmp = get_alpha_surface(jpgMonster3, a, r, g, b, mode) # get current alpha
screen.blit(tmp, (600,300))
screen.blit(jpg3text, (600, 550))
# ----- blit pngMonster0 as it is, with transparency from image ---
screen.blit(pngMonster0, (0, 10))
screen.blit(png0text, (0, 200))
# ----- blit pngMonster1 with colorkey set to black ----
# *** png already has alpha, does not need colorkey **
# ----- blit pngMonster2 with alpha for whole surface -----
# *** surface-alpha does not work if surface (png) already has alpha ***
# ----- blit pngmonster3 with per-pixel alpha-------
tmp = get_alpha_surface(pngMonster3, a, r, g, b, mode) # get current alpha
screen.blit(tmp, (600,10))
screen.blit(png3text, (600,200))
# ---- instructions ----
screen.blit(paper, (188,150)) # semi-transparent background for instructions
screen.blit(write("press [INS] / [DEL] to change red value: %i" % r,24, (255,255,255)),(190,150))
screen.blit(write("press [HOME] / [END] to change green value: %i" % g),(190,170))
screen.blit(write("press [PgUp] / [PgDwn] to chgange blue value: %i"% b), (190, 190))
screen.blit(write("press [Enter] for mode: %i (%s)" % (mode, modelist[modeNr])), (190,230))
screen.blit(write("press [+] / [-] (Keypad) to chgange alpha value: %i"% a), (190, 210))
# ------ next frame --------
pygame.display.flip() # flip the screen 30 times a second
if __name__ == "__main__":
alphademo()
This Gist brought to you by gist-it.view rawpygame/004_alphademo.py
#!/usr/bin/env python
"""
004_alphademo_pretty.py
Experiments with colorkey and alpha-value
URL: http://thepythongamebook.com/en:part2:pygame:step004
Author: horst.jens@spielend-programmieren.at, prettifying by yipyip
per-pixel-alpha code by Claudio Canepa
Licence: gpl, see http://www.gnu.org/licenses/gpl.html
works with pyhton2.7
"""
####
import pygame
import os
import itertools
####
BLENDMODES = ((pygame.BLEND_ADD, "ADD"),
(pygame.BLEND_SUB, "SUB"),
(pygame.BLEND_MULT, "MULT"),
(pygame.BLEND_MIN, "MIN"),
(pygame.BLEND_MAX, "MAX"),
(pygame.BLEND_RGBA_ADD, "RGBA ADD"),
(pygame.BLEND_RGBA_SUB, "RGBA SUB"),
(pygame.BLEND_RGBA_MULT, "RGBA MULT"),
(pygame.BLEND_RGBA_MIN, "RGBA MIN"),
(pygame.BLEND_RGBA_MAX, "RGBA MAX"))
####
def load_pic(name, path="data"):
return pygame.image.load(os.path.join(path, name))
####
def check(x, minval=0, maxval=255):
return min(maxval, max(minval, x))
####
def get_alpha_surface(surface, rgba=(128, 128, 128, 128), mode=pygame.BLEND_RGBA_ADD):
"""
Return a copy of a surface object with user-defined
values for red, green, blue and alpha.
Values from 0-255.
(Thanks to Claudio Canepa )
"""
new_surface = pygame.Surface(surface.get_size(), pygame.SRCALPHA|pygame.HWSURFACE)
new_surface.fill(rgba)
new_surface.blit(surface, (0, 0), surface.get_rect(), mode)
return new_surface
####
class AlphaDemo(object):
def __init__(self, width=900, height=600, fontsize=14):
pygame.init()
self.screen = pygame.display.set_mode((width, height), pygame.DOUBLEBUF)
self.background = pygame.Surface(self.screen.get_size()).convert()
self.font = pygame.font.SysFont('mono', fontsize, bold=True)
self.clock = pygame.time.Clock()
#self.background.fill((255, 255, 255))
venus = load_pic("800px-La_naissance_de_Venus.jpg").convert()
# transform venus and blit
pygame.transform.scale(venus, (width, height), self.background)
# .png and .gif graphics can have transparency, use convert_alpha()
self.png_monster = load_pic("colormonster.png").convert_alpha()
# jpg image, no transparency!
self.jpg_monster = load_pic("colormonster.jpg").convert()
# per pixel rgba
self.pp_rgba = [255, 255, 255, 128]
alpha_up = range(0, 256, 4)
alpha_down = alpha_up[-1::-1]
self.glob_alphas = itertools.cycle(alpha_up + alpha_down)
self.step = 4
self.mode_nr = 5
def run(self):
"""
Mainloop
"""
mainloop = True
while mainloop:
self.clock.tick(20)
# draw background every frame
self.screen.blit(self.background, (0, 0))
for event in pygame.event.get():
if event.type == pygame.QUIT:
mainloop = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
mainloop = False
self.action(pygame.key.get_pressed())
pygame.display.flip()
def action(self, pressed_keys):
red, green, blue, alpha = self.pp_rgba
if pressed_keys[pygame.K_PAGEUP]:
blue = blue + self.step
if pressed_keys[pygame.K_PAGEDOWN]:
blue = blue - self.step
if pressed_keys[pygame.K_HOME]:
green = green + self.step
if pressed_keys[pygame.K_END]:
green = green - self.step
if pressed_keys[pygame.K_INSERT]:
red = red + self.step
if pressed_keys[pygame.K_DELETE]:
red = red - self.step
if pressed_keys[pygame.K_KP_PLUS]:
alpha = alpha + self.step
if pressed_keys[pygame.K_KP_MINUS]:
alpha = alpha - self.step
if pressed_keys[pygame.K_RETURN]:
self.mode_nr = (self.mode_nr + 1) % len(BLENDMODES)
mode, mode_text = BLENDMODES[self.mode_nr]
self.pp_rgba = map(check, (red, green, blue, alpha))
glob_alpha = self.glob_alphas.next()
self.show_surfaces(self.png_monster, 'png', 0, 0, 200, 180,
glob_alpha, self.pp_rgba, mode)
self.show_surfaces(self.jpg_monster, 'jpg', 0, 300, 200, 180,
glob_alpha, self.pp_rgba, mode)
text = "ins/del=red>%d home/end=green>%d pgup/pgdwn=blue>%d "\
"+/-=ppalpha>%d " % tuple(self.pp_rgba)
pygame.display.set_caption("%s Mode>%s" % (text, mode_text))
def show_surfaces(self, surf, pictype, x, y, x_delta, height,
glob_alpha, pp_rgba, mode):
yh = y + height
#pure surface
self.screen.blit(surf, (x, y))
self.write(x, y + height, "%s pure" % pictype)
# with with colorkey
ck_surf = surf.copy()
ck_surf.set_colorkey((255,255,255))
x = x + x_delta
self.screen.blit(ck_surf, (x, y))
self.write(x, yh, "%s colorkey" % pictype)
# with alpha for whole surface
alpha_surf = surf.copy()
alpha_surf.set_alpha(glob_alpha)
x = x + x_delta
self.screen.blit(alpha_surf, (x, y))
self.write(x, yh, "%s alpha> %d" % (pictype, glob_alpha))
# with per-pixel alpha
ppa_surf = surf.copy()
ppa_surf = get_alpha_surface(ppa_surf, pp_rgba, mode)
x = x + x_delta
self.screen.blit(ppa_surf, (x, y))
self.write(x, yh, "%s, per-pixel-alpha" % pictype)
def write(self, x, y, msg, color=(255,255,0)):
self.screen.blit(self.font.render(msg, True, color), (x, y))
####
if __name__ == "__main__":
AlphaDemo().run()
This Gist brought to you by gist-it.view rawpygame/004_alphademo_pretty.py
#!/usr/bin/env python
"""
004_alphademo_pretty.py
Experiments with colorkey and alpha-value
URL: http://thepythongamebook.com/en:part2:pygame:step004
Original Author: horst.jens@spielend-programmieren.at, prettifying by yipyip
per-pixel-alpha code by Claudio Canepa
updating to python3.6 by tatatingting
Licence: gpl, see http://www.gnu.org/licenses/gpl.html
works with pyhton3.6
"""
####
import pygame
import os
import itertools
####
BLENDMODES = ((pygame.BLEND_ADD, "ADD"),
(pygame.BLEND_SUB, "SUB"),
(pygame.BLEND_MULT, "MULT"),
(pygame.BLEND_MIN, "MIN"),
(pygame.BLEND_MAX, "MAX"),
(pygame.BLEND_RGBA_ADD, "RGBA ADD"),
(pygame.BLEND_RGBA_SUB, "RGBA SUB"),
(pygame.BLEND_RGBA_MULT, "RGBA MULT"),
(pygame.BLEND_RGBA_MIN, "RGBA MIN"),
(pygame.BLEND_RGBA_MAX, "RGBA MAX"))
####
def load_pic(name, path="data"):
return pygame.image.load(os.path.join(path, name))
####
def check(x, minval=0, maxval=255):
return min(maxval, max(minval, x))
####
def get_alpha_surface(surface, rgba=(128, 128, 128, 128), mode=pygame.BLEND_RGBA_ADD):
"""
Return a copy of a surface object with user-defined
values for red, green, blue and alpha.
Values from 0-255.
(Thanks to Claudio Canepa )
"""
new_surface = pygame.Surface(surface.get_size(), pygame.SRCALPHA | pygame.HWSURFACE)
new_surface.fill(rgba)
new_surface.blit(surface, (0, 0), surface.get_rect(), mode)
return new_surface
####
class AlphaDemo(object):
def __init__(self, width=900, height=600, fontsize=14):
pygame.init()
self.screen = pygame.display.set_mode((width, height), pygame.DOUBLEBUF)
self.background = pygame.Surface(self.screen.get_size()).convert()
self.font = pygame.font.SysFont('mono', fontsize, bold=True)
self.clock = pygame.time.Clock()
# self.background.fill((255, 255, 255))
venus = load_pic("800px-La_naissance_de_Venus.jpg").convert()
# transform venus and blit
pygame.transform.scale(venus, (width, height), self.background)
# .png and .gif graphics can have transparency, use convert_alpha()
self.png_monster = load_pic("colormonster.png").convert_alpha()
# jpg image, no transparency!
self.jpg_monster = load_pic("colormonster.jpg").convert()
# per pixel rgba
self.pp_rgba = [255, 255, 255, 128]
alpha_up = range(0, 256, 4)
alpha_down = alpha_up[-1::-1]
self.glob_alphas = itertools.cycle(list(alpha_up) + list(alpha_down))
self.step = 4
self.mode_nr = 5
def run(self):
"""
Mainloop
"""
mainloop = True
while mainloop:
self.clock.tick(20)
# draw background every frame
self.screen.blit(self.background, (0, 0))
for event in pygame.event.get():
if event.type == pygame.QUIT:
mainloop = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
mainloop = False
self.action(pygame.key.get_pressed())
pygame.display.flip()
def action(self, pressed_keys):
red, green, blue, alpha = self.pp_rgba
if pressed_keys[pygame.K_PAGEUP]:
blue = blue + self.step
if pressed_keys[pygame.K_PAGEDOWN]:
blue = blue - self.step
if pressed_keys[pygame.K_HOME]:
green = green + self.step
if pressed_keys[pygame.K_END]:
green = green - self.step
if pressed_keys[pygame.K_INSERT]:
red = red + self.step
if pressed_keys[pygame.K_DELETE]:
red = red - self.step
if pressed_keys[pygame.K_KP_PLUS]:
alpha = alpha + self.step
if pressed_keys[pygame.K_KP_MINUS]:
alpha = alpha - self.step
if pressed_keys[pygame.K_RETURN]:
self.mode_nr = (self.mode_nr + 1) % len(BLENDMODES)
mode, mode_text = BLENDMODES[self.mode_nr]
self.pp_rgba = list(map(check, (red, green, blue, alpha)))
glob_alpha = self.glob_alphas.__next__()
self.show_surfaces(self.png_monster, 'png', 0, 0, 200, 180,
glob_alpha, self.pp_rgba, mode)
self.show_surfaces(self.jpg_monster, 'jpg', 0, 300, 200, 180,
glob_alpha, self.pp_rgba, mode)
text = "ins/del=red>%d home/end=green>%d pgup/pgdwn=blue>%d " \
"+/-=ppalpha>%d " % tuple(self.pp_rgba)
pygame.display.set_caption("%s Mode>%s" % (text, mode_text))
def show_surfaces(self, surf, pictype, x, y, x_delta, height,
glob_alpha, pp_rgba, mode):
yh = y + height
# pure surface
self.screen.blit(surf, (x, y))
self.write(x, y + height, "%s pure" % pictype)
# with with colorkey
ck_surf = surf.copy()
ck_surf.set_colorkey((255, 255, 255))
x = x + x_delta
self.screen.blit(ck_surf, (x, y))
self.write(x, yh, "%s colorkey" % pictype)
# with alpha for whole surface
alpha_surf = surf.copy()
alpha_surf.set_alpha(glob_alpha)
x = x + x_delta
self.screen.blit(alpha_surf, (x, y))
self.write(x, yh, "%s alpha> %d" % (pictype, glob_alpha))
# with per-pixel alpha
ppa_surf = surf.copy()
ppa_surf = get_alpha_surface(ppa_surf, pp_rgba, mode)
x = x + x_delta
self.screen.blit(ppa_surf, (x, y))
self.write(x, yh, "%s, per-pixel-alpha" % pictype)
def write(self, x, y, msg, color=(255, 255, 0)):
self.screen.blit(self.font.render(msg, True, color), (x, y))
####
if __name__ == "__main__":
AlphaDemo().run()
This Gist brought to you by gist-it.view rawpygame/004_alphademo_pretty_python3x.py
#!/usr/bin/env python
"""
004_per-pixel-alphademo.py
Experiments with alpha values.
Use mouse and scrollwheel.
URL: http://thepythongamebook.com/en:part2:pygame:step004
Author: Dirk Ketturkat
License: Do What The Fuck You Want To Public License (WTFPL)
See http://sam.zoy.org/wtfpl/
"""
import pygame
import os
def load_pic(name, path="data"):
pic = pygame.image.load(os.path.join(path, name))
if pic.get_alpha():
return pic.convert_alpha()
else:
return pic.convert()
def check(x, minval=0, maxval=255):
return min(maxval, max(minval, x))
def offset(len1, len2):
""" For picture centering
"""
return max(0, (len1 - len2) // 2)
class PeepDemo(object):
def __init__(self, **args):
pygame.init()
self.width = args['width']
self.height = args['height']
self.fps = args['fps']
self.screen = pygame.display.set_mode((self.width, self.height), pygame.DOUBLEBUF)
self.clock = pygame.time.Clock()
pygame.display.set_caption("Move Mouse and Scroll Mouse Wheel")
self.pic = load_pic(args['pic'])
self.background = pygame.Surface(self.screen.get_size()).convert()
self.background.fill(args['backcol'])
self.ppa_surface = pygame.Surface(self.screen.get_size(), flags=pygame.SRCALPHA)
self.pic_offset = offset(self.width, self.pic.get_width()), offset(self.height, self.pic.get_height())
# init stuff for circles with alpha value
self.center = self.width // 2, self.height // 2
self.max_radius = min(self.width, self.height)
self.hole_count = args['holes']
self.calc_centers(self.center, self.center, self.hole_count)
self.calc_rad_alphas(self.max_radius, self.hole_count)
self.rad_alphas = []
self.centers = []
def calc_rad_alphas(self, radius, n):
"""
Calculate linear radius and alpha values
"""
assert 0 < n < 256, "Invalid number of holes!"
rad_step = radius // n
alpha_step = 256 // n
self.rad_alphas = [(radius - i * rad_step, 255 - i*alpha_step) for i in range(n)]
def calc_centers(self, center, pos, holes):
"""
Calculate center points from center (of window) to mouse position
"""
cx, cy = center
mx, my = pos
vx, vy = mx - cx, my - cy
xs = vx // holes
ys = vy // holes
self.centers = [(cx + xs*i, cy + ys*i) for i in range(holes)]
def run(self):
"""
Mainloop
"""
mainloop = True
while mainloop:
self.flip()
for event in pygame.event.get():
if event.type == pygame.QUIT:
mainloop = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
mainloop = False
elif event.type == pygame.MOUSEMOTION:
self.calc_centers(self.center, pygame.mouse.get_pos(),
self.hole_count)
elif event.type == pygame.MOUSEBUTTONDOWN:
# check mouse wheel
if event.button in (4, 5):
self.hole_count = check(self.hole_count + [-1, 1][event.button-4], 2, 64)
self.calc_rad_alphas(self.max_radius, self.hole_count)
self.calc_centers(self.center, pygame.mouse.get_pos(), self.hole_count)
self.show()
pygame.quit()
def show(self):
"""
Draw all
"""
# picture on screen
self.screen.blit(self.pic, self.pic_offset)
# circles on alpha surface
for (r, a), c in zip(self.rad_alphas, self.centers):
pygame.draw.circle(self.ppa_surface, (0, 0, 0, a), c, r)
# alpha surface on screen
self.screen.blit(self.ppa_surface, (0, 0))
# erase alpha surface for new circles
self.ppa_surface.fill((0, 0, 0))
def flip(self):
"""
Show drawing and erase
"""
pygame.display.flip()
self.screen.blit(self.background, (0, 0))
self.clock.tick(self.fps)
opts = {'width': 800,
'height': 600,
'backcol': (255, 0, 0),
'fps': 100,
'fontsize': 18,
'pic': 'ente.jpg',
'holes': 7}
if __name__ == "__main__":
PeepDemo(**opts).run()
This Gist brought to you by gist-it.view rawpygame/004_per-pixel-alphademo.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
006_time_baed_movement.py
url: http://thepythongamebook.com/en:part2:pygame:step006
author: horst.jens@spielend-programmieren.at
licence: gpl, see http://www.gnu.org/licenses/gpl.html
works with python3.4 and pyhton2.7
bouncing ball.
Movement is now time based.
Because at coding, you never know exactly how many milliseconds
will have been passed between two frames, this example use pygame's
clock function to calculate the passed time and move the ballsurface at
constantly the same speed.
If you toggle the wild circle painting by pressing SPACE, the computer
has more to paint, framerate will drop, more time will pass between
2 frames and movement of the ball surface will be choppy (less smooth).
However, the movent speed remain unchanged because of the time-based movement.
"""
#the next line is only needed for python2.x and not necessary for python3.x
from __future__ import print_function, division
import pygame
import random
def wildPainting():
"""draw random circles to give the cpu some work to do"""
pygame.draw.circle(background, (random.randint(0,255),
random.randint(0,255), random.randint(0,255)),
(random.randint(0,screenrect.width),
random.randint(0,screenrect.height)),
random.randint(50,500))
#pygame.mixer.pre_init(44100, -16, 2, 2048) # setup mixer to avoid sound lag
pygame.init()
screen=pygame.display.set_mode((640,480)) # try out larger values and see what happens !
screenrect = screen.get_rect()
background = pygame.Surface(screen.get_size()) #create surface for background
background.fill((255,255,255)) #fill the background white (red,green,blue)
background = background.convert() #convert surface for faster blitting
background2 = background.copy() # clean background to restore for later
ballsurface = pygame.Surface((50,50)) #create a new surface (black by default)
ballsurface.set_colorkey((0,0,0)) #make black the transparent color (red,green,blue)
#pygame.draw.circle(Surface, color, pos, radius, width=0)
pygame.draw.circle(ballsurface, (0,0,255), (25,25),25) # paint blue circle
ballsurface = ballsurface.convert_alpha() # for faster blitting.
because transparency, use convert_alpha()
ballrect = ballsurface.get_rect()
ballx, bally = 550,240 # start position for the ball surface (topleft corner)
dx,dy = 60, 50 # speed of ball surface in pixel per second !
screen.blit(background, (0,0)) #blit the background on screen (overwriting all)
screen.blit(ballsurface, (ballx, bally)) #blit the ball surface on the screen (on top of background)
clock = pygame.time.Clock() #create pygame clock object
mainloop = True
FPS = 60 # desired max.
framerate in frames per second.
playtime = 0
paint_big_circles = False
cleanup = True
while mainloop:
milliseconds = clock.tick(FPS) # milliseconds passed since last frame
seconds = milliseconds / 1000.0 # seconds passed since last frame (float)
playtime += seconds
for event in pygame.event.get():
if event.type == pygame.QUIT:
mainloop = False # pygame window closed by user
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
mainloop = False # user pressed ESC
elif event.key == pygame.K_1:
FPS = 5
elif event.key == pygame.K_2:
FPS = 20
elif event.key == pygame.K_3:
FPS = 30
elif event.key == pygame.K_4:
FPS = 40
elif event.key == pygame.K_5:
FPS = 50
elif event.key == pygame.K_6:
FPS = 60
elif event.key == pygame.K_7:
FPS = 70
elif event.key == pygame.K_8:
FPS = 80
elif event.key == pygame.K_9:
FPS = 90
elif event.key == pygame.K_0:
FPS = 1000 # absurd high value
elif event.key == pygame.K_x:
paint_big_circles = not paint_big_circles # toggle
elif event.key == pygame.K_y:
cleanup = not cleanup # toggle boolean value
elif event.key == pygame.K_w: # restore old background
background.blit(background2, (0,0)) # clean the screen
pygame.display.set_caption("x: paint ({}) y: cleanup ({}) ,"
" w: white, 0-9: limit FPS to {}"
" (now: {:.2f})".format(
paint_big_circles, cleanup, FPS,clock.get_fps()))
if cleanup:
screen.blit(background, (0,0)) #draw background on screen (overwriting all)
if paint_big_circles:
wildPainting()
#calculate new center of ball (time-based)
ballx += dx * seconds # float, since seconds passed since last frame is a decimal value
bally += dy * seconds
# bounce ball if out of screen
if ballx < 0:
ballx = 0
dx *= -1
elif ballx + ballrect.width > screenrect.width:
ballx = screenrect.width - ballrect.width
dx *= -1
if bally < 0:
bally = 0
dy *= -1
elif bally + ballrect.height > screenrect.height:
bally = screenrect.height - ballrect.height
dy *= -1
# paint the ball
screen.blit(ballsurface, (round(ballx,0), round(bally,0 )))
pygame.display.flip() # flip the screen 30 times a second
print("This 'game' was played for {:.2f} seconds".format(playtime))
This Gist brought to you by gist-it.view rawpygame/006_time_based_movement.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
008_animation.py
animation & spritesheet
url: http://thepythongamebook.com/en:part2:pygame:step008
author: horst.jens@spielend-programmieren.at
licence: gpl, see http://www.gnu.org/licenses/gpl.html
spritesheet from
http://www.flyingyogi.com
using subsurface, this program gets "sprites" from a sprite sheet
and display them, creating an animation.
works with python3.4 and pyhton2.7
"""
#the next line is only needed for python2.x and not necessary for python3.x
from __future__ import print_function, division
import pygame
import random
import os
pygame.init()
folder = "data" # replace with "." if pictures lay in the same folder as program
try:
spritesheet = pygame.image.load(os.path.join(folder, "char9.bmp"))
except:
raise(UserWarning, "i'm unable to load 'cahr9.bmp' form the folder 'data'") # error msg and exit
screen=pygame.display.set_mode((800,480)) # try out larger values and see what happens !
spritesheet.convert() # convert only works afteer display_setmode is set.
screenrect = screen.get_rect()
background = pygame.Surface((screen.get_size()))
backgroundrect = background.get_rect()
background.fill((255,0,255)) # fill white
background = background.convert()
screen.blit(background,(0,0))
lions = [] # a list for the lion images
# the spritesheet has lions, 128 x 64 pixels
sz = 128
w, h = 128, 64
# for nbr in range(1,5,1): # first line contains 4 pictures of lions
# lions.append(spritesheet.subsurface((sz*(nbr-1),64,sz,sz)))
# for nbr in range(5,7,1): # second line contains 2 pictures of lions
# lions.append(spritesheet.subsurface((sz*(nbr-5),262-64,sz,sz)))
for nbr in range(4): # first line contains 4 pictures of lions
lions.append(spritesheet.subsurface((sz*nbr,64,w,h)))
for nbr in range(2): # second line contains 2 pictures of lions
lions.append(spritesheet.subsurface((sz*nbr,198,w,h)))
print("len:",len(lions), lions[0].get_size())
for nbr in range(len(lions)):
lions[nbr].set_colorkey((0,0,0)) # black transparent
lions[nbr] = lions[nbr].convert_alpha()
print("converted nbr", nbr)
for nbr in range(len(lions)):
screen.blit(lions[nbr], (nbr*(sz+1), 0)) #blit the ball surface on the screen (on top of background)
print("blitted nbr", nbr)
#
clock = pygame.time.Clock() #create pygame clock object
mainloop = True
FPS = 60 # desired max.
framerate in frames per second.
playtime = 0
cycletime = 0
#newnr = 0 # index of the first lionimage to display
#oldnr = 0 # needed to compare if image has changed
interval = .15 # how long one single images should be displayed in seconds
picnr = 0
while mainloop:
milliseconds = clock.tick(FPS) # milliseconds passed since last frame
seconds = milliseconds / 1000.0 # seconds passed since last frame (float)
playtime += seconds
cycletime += seconds
if cycletime > interval: # Note that milliseconds is a lot smaller than interval
mypicture = lions[picnr] ##
#screen.blit(background.subsurface((300,300,128,66)),(300,300)) ##
screen.blit(mypicture, (300,300))
picnr += 1
if picnr > 5:
picnr = 0
cycletime = 0 # reset cycletime.
for event in pygame.event.get():
if event.type == pygame.QUIT:
mainloop = False # pygame window closed by user
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
mainloop = False # user pressed ESC
pygame.display.set_caption("[FPS]: %.2f picture: %i" % (clock.get_fps(), picnr))
#this would repaint the whole screen (secure, but slow)
#screen.blit(background, (0,0)) #draw background on screen (overwriting all)
pygame.display.flip() # flip the screen 30 times a second
print("This 'game' was played for {:.2f} seconds".format(playtime))
This Gist brought to you by gist-it.view rawpygame/008_animation.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Name: 010_sound_only_no_graphic.py
Purpose: demonstrate use of pygame for playing sound & music
URL: http://ThePythonGameBook.com
Author: Horst.Jens@spielend-programmieren.at
Licence: gpl, see http://www.gnu.org/licenses/gpl.html
works with pyhton3.4 and python2.7
"""
#the next line is only needed for python2.x and not necessary for python3.x
from __future__ import print_function, division
import pygame
import os
import sys
# if using python2, the get_input command needs to act like raw_input:
if sys.version_info[:2] <= (2, 7):
get_input = raw_input
else:
get_input = input # python3
pygame.mixer.pre_init(44100, -16, 2, 2048) # setup mixer to avoid sound lag
pygame.init() #initialize pygame
# look for sound & music files in subfolder 'data'
pygame.mixer.music.load(os.path.join('data', 'an-turr.ogg'))#load music
jump = pygame.mixer.Sound(os.path.join('data','jump.wav')) #load sound
fail = pygame.mixer.Sound(os.path.join('data','fail.wav')) #load sound
# play music non-stop
pygame.mixer.music.play(-1)
# game loop
gameloop = True
while gameloop:
# indicate if music is playing
if pygame.mixer.music.get_busy():
print(" ...
music is playing")
else:
print(" ...
music is not playing")
# print menu
print("please press key:")
print("[a] to play 'jump.wav' sound")
print("[b] to play 'fail.wav' sound")
print("[m] to toggle music on/off")
print("[q] to quit")
answer = get_input("press key [a] or [b] or [m] or [q], followed by [ENTER]")
answer = answer.lower() # force lower case
if "a" in answer:
jump.play()
print("playing jump.wav once")
elif "b" in answer:
fail.play()
print("playing fail.wav once")
elif "m" in answer:
if pygame.mixer.music.get_busy():
pygame.mixer.music.stop()
else:
pygame.mixer.music.play()
elif "q" in answer:
#break from gameloop
gameloop = False
else:
print("please press either [a], [b], [m] or [q] and [ENTER]")
print("bye-bye")
pygame.quit() # clean exit
This Gist brought to you by gist-it.view rawpygame/010_sound_only_no_graphic.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
010_sound_and_music.py
plays music and sound effects
url: http://thepythongamebook.com/en:part2:pygame:step010
author: horst.jens@spielend-programmieren.at
licence: gpl, see http://www.gnu.org/licenses/gpl.html
This program plays music and
plays a sound effect whenever the a of b key is pressed and released
All files must be in a 'data' subfolder.
The 'data' subfolder must be in the same folder as the program.
works with python3.4 and python2.7
"""
import pygame
import os
pygame.mixer.pre_init(44100, -16, 2, 2048) # setup mixer to avoid sound lag
pygame.init() #initialize pygame
try:
pygame.mixer.music.load(os.path.join('data', 'an-turr.ogg'))#load music
jump = pygame.mixer.Sound(os.path.join('data','jump.wav')) #load sound
fail = pygame.mixer.Sound(os.path.join('data','fail.wav')) #load sound
except:
raise(UserWarning, "could not load or play soundfiles in 'data' folder :-(")
pygame.mixer.music.play(-1) # play music non-stop
screen=pygame.display.set_mode((640,480)) # set screensize of pygame window
background = pygame.Surface(screen.get_size()) #create empty pygame surface
background.fill((255,255,255)) #fill the background white color (red,green,blue)
background = background.convert() #convert Surface object to make blitting faster
screen.blit(background, (0,0)) #draw the background on screen
clock = pygame.time.Clock() #create a pygame clock object
mainloop = True
FPS = 30 # desired framerate in frames per second.
try out other values !
while mainloop:
milliseconds = clock.tick(FPS) # do not go faster than this framerate
for event in pygame.event.get():
if event.type == pygame.QUIT:
mainloop = False # pygame window closed by user
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
mainloop = False # user pressed ESC
if event.key == pygame.K_a:
fail.play() # play sound effect
if event.key == pygame.K_b:
jump.play() # play sound effect
elif event.type == pygame.MOUSEBUTTONDOWN:
bstate = pygame.mouse.get_pressed()
print ('bstate: ', bstate)
if bstate[0]:
fail.play()
print (pygame.mouse.get_pos())
pygame.draw.circle(screen, (255,255,0), pygame.mouse.get_pos(), 30)
# print the framerate into the pygame window title
pygame.display.set_caption("FPS: {:.2f} Press [a] or [b] to play sound effects".format(clock.get_fps()))
pygame.display.flip() # flip the screen
This Gist brought to you by gist-it.view rawpygame/010_sound_and_music.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
011-rotozoom.py
moving, rotating and zooming a pygame surface
url: http://thepythongamebook.com/en:part2:pygame:step011
author: horst.jens@spielend-programmieren.at
licence: gpl, see http://www.gnu.org/licenses/gpl.html
loading the background image and snake.gif from a subfolder called 'data'
The subfolder must be inside the same folder as the program itself.
The snake surface can be moved with the cursor keys,
rotated with a and d key and and zoomed with w and s key
works with pyhton3.4 and python2.7
"""
#the next line is only needed for python2.x and not necessary for python3.x
from __future__ import print_function, division
import pygame
import os
try:
# load from subfolder 'data'
background = pygame.image.load(os.path.join("data","background640x480_a.jpg"))
snake = pygame.image.load(os.path.join("data","snake.gif"))
except:
raise(UserWarning, "Unable to find the images in the folder 'data' :-( ")
#finally:
pygame.init()
screen=pygame.display.set_mode((640,480)) # try out larger values and see what happens !
background = background.convert() # jpg can not have transparency
snake = snake.convert_alpha() # png image has transparent color
snake_original = snake.copy() # store a unmodified copy of the snake surface
snakex, snakey = 250, 240 # start position of snake surface
dx, dy = 0, 0 # snake speed in pixel per second !
speed = 60 # in pixel / second
angle = 0 # current orientation of snake
zoom = 1.0 # current zoom factor
zoomspeed = 0.01
turnspeed = 180 # in Grad (360) per second
screen.blit(background, (0,0)) # blit background on screen (overwriting all)
screen.blit(snake, (snakex, snakey)) # blit the snake shape
clock = pygame.time.Clock() # create pygame clock object
mainloop = True
FPS = 60 # desired max.
framerate in frames per second.
while mainloop:
milliseconds = clock.tick(FPS) # milliseconds passed since last frame
seconds = milliseconds / 1000.0 # seconds passed since last frame
for event in pygame.event.get():
if event.type == pygame.QUIT:
mainloop = False # pygame window closed by user
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
mainloop = False # user pressed ESC
pygame.display.set_caption("press cursor keys and w a s d - fps:"
"%.2f zoom: %.2f angle %.2f" % (clock.get_fps(), zoom, angle))
# only blit the part of the background where the snake was (cleanrect)
#try:
#if the subsurface is outside the screen pygame would raise an error
#this can happen when using rotozoom, therfore check inside try..except
# dirtyrect = background.subsurface((round(snakex,0),
# round(snakey,0), snake.get_width(), snake.get_height()))
# screen.blit(dirtyrect, (round(snakex,0), round(snakey,0)))
#except:
#print "autch!"
snakerect = pygame.Rect(round(snakex,0),
round(snakey,0), snake.get_width(), snake.get_height())
dirty = background.subsurface(snakerect.clip(screen.get_rect()))
dirtyrect = dirty.get_rect()
screen.blit(dirty, (round(snakex), round(snakey)))
#screen.blit(background,(0,0)) # blit the whole background (slow but secure)
#raise UserWarning, "subsurface out of screen?"
# move snake with cursor keys
pressedkeys = pygame.key.get_pressed()
dx, dy = 0, 0 # no cursor key, no movement
if pressedkeys[pygame.K_LEFT]:
dx -= speed
if pressedkeys[pygame.K_RIGHT]:
dx += speed
if pressedkeys[pygame.K_UP]:
dy -= speed
if pressedkeys[pygame.K_DOWN]:
dy += speed
#calculate new center of snake
snakex += dx * seconds # time based movement
snakey += dy * seconds
# rotate snake with a and d key
turnfactor = 0 # neither a nor d, no turning
if pressedkeys[pygame.K_a]:
turnfactor += 1 # counter-clockwise
if pressedkeys[pygame.K_d]:
turnfactor -= 1 #clock-wise
# zoom snake with w and s key
zoomfactor = 1.0 # neither w nor s, no zooming
if pressedkeys[pygame.K_w]:
zoomfactor += zoomspeed
if pressedkeys[pygame.K_s]:
zoomfactor -= zoomspeed
if turnfactor != 0 or zoomfactor !=1.0:
angle += turnfactor * turnspeed * seconds # time-based turning
zoom *= zoomfactor
# the surface shrinks and zooms and moves by rotating
oldrect = snake.get_rect() # store current surface rect
snake = pygame.transform.rotozoom(snake_original, angle, zoom)
newrect = snake.get_rect() # store new surface rect
# put new surface rect center on same spot as old surface rect center
snakex += oldrect.centerx - newrect.centerx
snakey += oldrect.centery - newrect.centery
# paint the snake
screen.blit(snake, (round(snakex,0), round(snakey,0)))
pygame.display.flip() # flip the screen 30 times a second # flip the screen 30 (or FPS) times a second
This Gist brought to you by gist-it.view rawpygame/011_rotozoom.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
012_text.py
displaying and moving text
url: http://thepythongamebook.com/en:part2:pygame:step012
author: horst.jens@spielend-programmieren.at
licence: gpl, see http://www.gnu.org/licenses/gpl.html
This program demonstrate how to render and blit text into a surface
works with pyhton3.4 and python2.7
"""
import pygame
import random
def flytext(msg="hello world", duration=5):
"""blinking text bouncing around the screen"""
def newcolour():
# any colour but black or white
return (random.randint(10,250), random.randint(10,250), random.randint(10,250))
def write(msg="pygame is cool"):
myfont = pygame.font.SysFont("None", random.randint(34,128))
mytext = myfont.render(msg, True, newcolour())
mytext = mytext.convert_alpha()
return mytext
pygame.init()
x = 60
y = 60
dx = 5
dy = 5
screen = pygame.display.set_mode((640,400))
background = pygame.Surface((screen.get_width(), screen.get_height()))
background.fill((255,255,255)) # white
background = background.convert()
screen.blit(background, (0,0)) # clean whole screen
clock = pygame.time.Clock()
mainloop = True
FPS = 60 # desired framerate in frames per second.
while mainloop:
milliseconds = clock.tick(FPS) # milliseconds passed since last frame
seconds = milliseconds / 1000.0 # seconds passed since last frame
for event in pygame.event.get():
if event.type == pygame.QUIT:
mainloop = False # pygame window closed by user
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
mainloop = False # user pressed ESC
textsurface = write("hello world")
#screen.blit(background, (0,0)) # clean whole screen
x += dx
y += dy
if x < 0:
x = 0
dx *= -1
screen.blit(background, (0,0)) # clean whole screen
elif x + textsurface.get_width() > screen.get_width():
x = screen.get_width() - textsurface.get_width()
dx *= -1
if y < 0:
y = 0
dy *= -1
elif y + textsurface.get_height() > screen.get_height():
y = screen.get_height() - textsurface.get_height()
dy *= -1
screen.blit(textsurface, (x,y))
pygame.display.flip()
pygame.quit()
if __name__=="__main__":
flytext()
This Gist brought to you by gist-it.view rawpygame/012_text.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
013_catch_the_thief.py
a game without pygame sprites
url: http://thepythongamebook.com/en:part2:pygame:step013
author: horst.jens@spielend-programmieren.at
licence: gpl, see http://www.gnu.org/licenses/gpl.html
The player(s) can control the Pygame snake (with cursor keys) and the
Tux bird (with the mouse).
A blue police icon moves toward the middle
distance between snake and bird (indicated by a cross).
Your task is to catch the thief (red triangle) with the blue police circle.
The thief moves by random.
You have only a short period of time.
For each
millisecond where the police circle touches the thief triangle, you get points.
Loading images and sounds from a subfolder called 'data'
The subfolder must be inside the same folder as the program itself.
works with pyhton3.4 and python2.7
"""
#the next line is only needed for python2.x and not necessary for python3.x
from __future__ import print_function, division
import pygame
import os
import random
# ------ a> Config.screenwidth:
x = Config.screenwidth - sprite.get_width()/2
dx *= -1
if y - sprite.get_height()/2 < 0:
y = sprite.get_height()/2
dy *= -1
elif y + sprite.get_height()/2 > Config.screenheight:
y = Config.screenheight - sprite.get_height()/2
dy *= -1
return x,y,dx,dy
def randomcolour():
"""returns a random colour tuple (red,green,blue)"""
return (random.randint(0,255), random.randint(0,255), random.randint(0,255))
def arrow(sprite, dx, dy):
midx = sprite.get_width() /2
midy = sprite.get_height() /2
return sprite
def play_the_game():
pygame.mixer.pre_init(44100, -16, 2, 2048) # setup mixer to avoid sound lag
pygame.init()
try:
# load graphic files from subfolder 'data'
background = pygame.image.load(os.path.join("data","wien.jpg"))
snake = pygame.image.load(os.path.join("data","snake.gif"))
bird = pygame.image.load(os.path.join("data","babytux.png"))
# load sound files
over = pygame.mixer.Sound(os.path.join('data','time_is_up_game_over.ogg'))
spring = pygame.mixer.Sound(os.path.join('data', 'spring.wav'))
except:
raise(UserWarning, "Unable to find or play the files in the folder 'data' :-( ")
# ----------- start ---------
screen=pygame.display.set_mode((1024,600)) # try out larger values and see what happens !
Config.screen = screen # copy screen into the Config> Config.birdx:
Config.birddx += 1
if mousey < Config.birdy:
Config.birddy -= 1
elif mousey > Config.birdy:
Config.birddy += 1
if pygame.mouse.get_pressed()[0] == True:
Config.birddx = 0 # stop movement by mouseclick (left button)
Config.birddy = 0
# ---- keyboard ------
# cursor keys or wasd
pressedkeys = pygame.key.get_pressed() # all keys that are pressed now
if pressedkeys[pygame.K_LEFT] or pressedkeys[pygame.K_a]:
Config.snakedx -= 1
if pressedkeys[pygame.K_RIGHT] or pressedkeys[pygame.K_d]:
Config.snakedx += 1
if pressedkeys[pygame.K_UP] or pressedkeys[pygame.K_w]:
Config.snakedy -= 1
if pressedkeys[pygame.K_DOWN] or pressedkeys[pygame.K_s]:
Config.snakedy += 1
if pressedkeys[pygame.K_RETURN] or pressedkeys[pygame.K_LCTRL]:
Config.snakedx = 0 # stop movement by pressing the 's' key
Config.snakedy = 0
# ------------ compute movement ----------------
Config.crossx = min(Config.birdx,Config.snakex) + ( max(Config.birdx, Config.snakex) - # cross is in the middle of bird and snake
min(Config.birdx,Config.snakex)) / 2.0 -cross.get_width()/2
Config.crossy = min(Config.birdy,Config.snakey) + ( max(Config.birdy, Config.snakey) -
min(Config.birdy,Config.snakey)) / 2.0 - cross.get_height()/2
if Config.crossx < Config.policex:
Config.policedx -= 1 # police moves toward cross
elif Config.crossx > Config.policex:
Config.policedx += 1
if Config.crossy > Config.policey:
Config.policedy += 1
elif Config.crossy < Config.policey:
Config.policedy -= 1
Config.thiefdx += random.randint( -Config.erratic,Config.erratic ) # thief is erratic
Config.thiefdy += random.randint( -Config.erratic,Config.erratic )
Config.thiefdx = max(Config.thiefdx, -Config.thiefmaxspeed) # limit speed of thief
Config.thiefdx = min(Config.thiefdx, Config.thiefmaxspeed)
Config.thiefdy = max(Config.thiefdy, -Config.thiefmaxspeed)
Config.thiefdy = min(Config.thiefdy, Config.thiefmaxspeed)
# ---- friction...
sprites get slower ----
Config.policedx *= 0.995
Config.policedy *= 0.995
Config.snakedx *= 0.995
Config.snakedy *= 0.995
Config.birddx *= 0.995
Config.birddy *= 0.995
# --------- new position -----------
Config.policex += Config.policedx * seconds
Config.policey += Config.policedy * seconds
Config.birdx += Config.birddx * seconds
Config.birdy += Config.birddy * seconds
Config.snakex += Config.snakedx * seconds
Config.snakey += Config.snakedy * seconds
Config.thiefx += Config.thiefdx * seconds
Config.thiefy += Config.thiefdy * seconds
# ----------- bounce ----------
Config.policex, Config.policey, Config.policedx, Config.policedy = bounce(police, Config.policex, Config.policey, Config.policedx, Config.policedy)
Config.birdx, Config.birdy, Config.birddx, Config.birddy = bounce(bird, Config.birdx, Config.birdy, Config.birddx, Config.birddy)
Config.snakex, Config.snakey, Config.snakedx, Config.snakedy = bounce(snake, Config.snakex, Config.snakey, Config.snakedx, Config.snakedy)
Config.thiefx, Config.thiefy, Config.thiefdx, Config.thiefdy = bounce(thief, Config.thiefx, Config.thiefy, Config.thiefdx, Config.thiefdy)
# --- police got thief ? collision detection -----
distx = max(Config.policex + police.get_width()/2 , Config.thiefx +
thief.get_width()/2) - min(Config.policex +
police.get_width()/2, Config.thiefx + thief.get_width()/2)
disty = max(Config.policey + police.get_height()/2 , Config.thiefy +
thief.get_height()/2) - min(Config.policey + police.get_width()/2,
Config.thiefy + thief.get_width()/2)
catch_in_last_frame = catch_in_this_frame # save old catch info
catch_in_this_frame = False
if (distx < police.get_width() /2) and (disty < police.get_height()/2):
catch_in_this_frame = True
points += seconds
screen.fill(randomcolour())
if not pygame.mixer.get_busy():
spring.play() # only play this sound if mixer is silent at the moment
else: # no catch this time
if catch_in_last_frame:
screen.blit(background, (0,0)) # restore background
# ---------- blit ----------------
draw(bird, Config.birdx, Config.birdy)
draw(snake, Config.snakex, Config.snakey)
pygame.draw.line(screen, randomcolour(), (Config.snakex,Config.snakey), (Config.birdx, Config.birdy), 1)
pygame.draw.line(screen, randomcolour(), (Config.crossx,Config.crossy), (Config.policex, Config.policey) ,1)
draw(police, Config.policex, Config.policey)
draw(cross, Config.crossx, Config.crossy)
draw(thief, Config.thiefx, Config.thiefy)
pygame.display.flip() # flip the screen FPS times a second
pygame.quit()
# check if the program is imported.
if not, start it directly
if __name__ == "__main__":
play_the_game()
This Gist brought to you by gist-it.view rawpygame/013_catch_the_thief.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
014-sprites.py
real pygame sprites
url: http://thepythongamebook.com/en:part2:pygame:step014
author: horst.jens@spielend-programmieren.at
licence: gpl, see http://www.gnu.org/licenses/gpl.html
Real pygame Sprites moving around.
Create more sprites with mouse click.
Shows collision detection
loading images from a subfolder called 'data'
all images files must be in the subfolder 'data'.
The subfolder must be inside the
same folder as the program itself.
works with pyhton3.4 and python2.7
"""
import pygame
import os
import random
import math
pygame.mixer.pre_init(44100, -16, 2, 2048) # setup mixer to avoid sound lag
pygame.init()
#screen=pygame.display.set_mode((640,480)) # try out larger values and see what happens !
screen=pygame.display.set_mode((1920,1080), pygame.FULLSCREEN)
winstyle = 0 # |FULLSCREEN # Set the display mode
BIRDSPEED = 50.0
def write(msg="pygame is cool"):
myfont = pygame.font.SysFont("None", 32)
mytext = myfont.render(msg, True, (0,0,0))
mytext = mytext.convert_alpha()
return mytext
class BirdCatcher(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self, self.groups)
self.image = pygame.Surface((100,100)) # created on the fly
self.image.set_colorkey((0,0,0)) # black transparent
pygame.draw.circle(self.image, (255,0,0), (50,50), 50, 2) # red circle
self.image = self.image.convert_alpha()
self.rect = self.image.get_rect()
self.radius = 50 # for collide check
def update(self, seconds):
# no need for seconds but the other sprites need it
self.rect.center = pygame.mouse.get_pos()
class Bird(pygame.sprite.Sprite):
image=[] # list of all images
# not necessary:
birds = {} # a dictionary of all Birds, each Bird has its own number
number = 0
def __init__(self, startpos=(50,50), area=screen.get_rect(), speed=None):
pygame.sprite.Sprite.__init__(self, self.groups)
self.pos = [0.0,0.0]
self.pos[0] = startpos[0]*1.0 # float
self.pos[1] = startpos[1]*1.0 # float
self.image = Bird.image[0]
self.rect = self.image.get_rect()
self.area = area # where the sprite is allowed to move
self.newspeed(speed)
self.catched = False
#--- not necessary:
self.number = Bird.number # get my personal Birdnumber
Bird.number+= 1 # increase the number for next Bird
Bird.birds[self.number] = self # store myself into the Bird dictionary
#print "my number %i Bird number %i " % (self.number, Bird.number)
def newspeed(self, speed=None):
# new birdspeed, but not 0
if speed == None:
speedrandom = random.choice([-1,1]) # flip a coin
self.dx = random.random() * BIRDSPEED * speedrandom + speedrandom
self.dy = random.random() * BIRDSPEED * speedrandom + speedrandom
else:
self.dx, self.dy = speed
def update(self, seconds):
self.pos[0] += self.dx * seconds
self.pos[1] += self.dy * seconds
# -- check if out of screen
if not self.area.contains(self.rect):
self.image = Bird.image[1] # crash into wall
# --- compare self.rect and area.rect
if self.pos[0] + self.rect.width/2 > self.area.right:
self.pos[0] = self.area.right - self.rect.width/2
if self.pos[0] - self.rect.width/2 < self.area.left:
self.pos[0] = self.area.left + self.rect.width/2
if self.pos[1] + self.rect.height/2 > self.area.bottom:
self.pos[1] = self.area.bottom - self.rect.height/2
if self.pos[1] - self.rect.height/2 < self.area.top:
self.pos[1] = self.area.top + self.rect.height/2
self.newspeed() # calculate a new direction
else:
if self.catched:
self.image = Bird.image[2] # blue rectangle
else:
self.image = Bird.image[0] # normal bird image
#--- calculate new position on screen -----
self.rect.centerx = round(self.pos[0],0)
self.rect.centery = round(self.pos[1],0)
background = pygame.Surface((screen.get_width(), screen.get_height()))
background.fill((255,255,255)) # fill white
background.blit(write("Press left mouse button for more sprites.
Press ESC to quit"),(5,10))
background = background.convert() # jpg can not have transparency
screen.blit(background, (0,0)) # blit background on screen (overwriting all)
clock = pygame.time.Clock() # create pygame clock object
mainloop = True
FPS = 60 # desired max.
framerate in frames per second.
# load images into>015_more_sprites.py
pygame sprites with hitbars and exploding fragments
url: http://thepythongamebook.com/en:part2:pygame:step015
author: horst.jens@spielend-programmieren.at
licence: gpl, see http://www.gnu.org/licenses/gpl.html
pygame sprites moving araound and exploding into little fragments
(on mouseclick).
Effect of gravity on the fragments can be toggled.
Differnt coding style and its outcome on performance (framerate)
can be toggled and is displayed by green bars.
a long bar indicates
a slow performance.
works with pyhton3.4 and python2.7
"""
#the next line is only needed for python2.x and not necessary for python3.x
from __future__ import print_function, division
def game():
import pygame
import os
import random
import math
pygame.mixer.pre_init(44100, -16, 2, 2048) # setup mixer to avoid sound lag
pygame.init()
screen=pygame.display.set_mode((1920, 1080), pygame.FULLSCREEN) # try out larger values and see what happens !
#winstyle = 0 # |FULLSCREEN # Set the display mode
BIRDSPEEDMAX = 200
BIRDSPEEDMIN = 10
FRICTION =.999
HITPOINTS = 100.0
FORCE_OF_GRAVITY = 9.81 # in pixel per second² .See http://en.wikipedia.org/wiki/Gravitational_acceleration
print(pygame.ver)
def write(msg="pygame is cool"):
"""write text into pygame surfaces"""
myfont = pygame.font.SysFont("None", 32)
mytext = myfont.render(msg, True, (0,0,0))
mytext = mytext.convert_alpha()
return mytext
#define sprite groups
birdgroup = pygame.sprite.LayeredUpdates()
bargroup = pygame.sprite.Group()
stuffgroup = pygame.sprite.Group()
fragmentgroup = pygame.sprite.Group()
# LayeredUpdates instead of group to draw in correct order
allgroup = pygame.sprite.LayeredUpdates() # more sophisticated than simple group
> self.lifetime:
self.kill()
self.pos[0] += self.dx * seconds
self.pos[1] += self.dy * seconds
if Fragment.gravity:
self.dy += FORCE_OF_GRAVITY # gravity suck fragments down
self.rect.centerx = round(self.pos[0],0)
self.rect.centery = round(self.pos[1],0)
> BIRDSPEEDMIN and abs(self.dy) > BIRDSPEEDMIN:
self.dx *= FRICTION
self.dy *= FRICTION
# spped limit
if abs(self.dx) > BIRDSPEEDMAX:
self.dx = BIRDSPEEDMAX * self.dx / self.dx
if abs(self.dy) > BIRDSPEEDMAX:
self.dy = BIRDSPEEDMAX * self.dy / self.dy
# new position
self.pos[0] += self.dx * seconds
self.pos[1] += self.dy * seconds
# -- check if Bird out of screen
if not self.area.contains(self.rect):
self.crashing = True # change colour later
# --- compare self.rect and area.rect
if self.pos[0] + self.rect.width/2 > self.area.right:
self.pos[0] = self.area.right - self.rect.width/2
if self.pos[0] - self.rect.width/2 < self.area.left:
self.pos[0] = self.area.left + self.rect.width/2
if self.pos[1] + self.rect.height/2 > self.area.bottom:
self.pos[1] = self.area.bottom - self.rect.height/2
if self.pos[1] - self.rect.height/2 < self.area.top:
self.pos[1] = self.area.top + self.rect.height/2
self.newspeed() # calculate a new direction
#--- calculate actual image: crasing, catched, both, nothing ?
self.image = Bird.image[self.crashing + self.catched*2]
#--- calculate new position on screen -----
self.rect.centerx = round(self.pos[0],0)
self.rect.centery = round(self.pos[1],0)
#--- loose hitpoins
if self.crashing:
self.hitpoints -=1
#--- check if still alive
if self.hitpoints <= 0:
self.kill()
#--------------- no> millimax:
millimax = milliseconds
seconds = milliseconds / 1000.0 # seconds passed since last frame
for event in pygame.event.get():
if event.type == pygame.QUIT:
mainloop = False # pygame window closed by user
elif event.type == pygame.MOUSEBUTTONUP:
x, y = pygame.mouse.get_pos()
angle = random.random()*2*3.1415
Bird( pygame.mouse.get_pos(), speed=(500*math.cos(angle),500*math.sin(angle)) )
elif event.type == pygame.MOUSEBUTTONDOWN:
x, y = pygame.mouse.get_pos()
angle = random.random()*2*3.1415
Bird( pygame.mouse.get_pos(), speed=(500*math.cos(angle),500*math.sin(angle)) )
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
mainloop = False # user pressed ESC
elif event.key == pygame.K_g:
Fragment.gravity = not Fragment.gravity # toggle gravity>016_layers.py
pygame sprites with different layers and parallax scrolling
url: http://thepythongamebook.com/en:part2:pygame:step016
author: horst.jens@spielend-programmieren.at
licence: gpl, see http://www.gnu.org/licenses/gpl.html
change the sprite layer by clicking with left or right mouse button
the birdsprites will appear before or behind the blocks
point on a sprite and pres "p" to print out more information about that sprite
part of www.pythongamebook.com by Horst JENS
works with python3.4 and python2.7
"""
#the next line is only needed for python2.x and not necessary for python3.x
from __future__ import print_function, division
def game():
import pygame
import os
import random
pygame.mixer.pre_init(44100, -16, 2, 2048) # setup mixer to avoid sound lag
pygame.init()
screen=pygame.display.set_mode((640,480)) # try out larger values and see what happens !
#winstyle = 0 # |FULLSCREEN # Set the display mode
print("pygame version", pygame.ver)
BIRDSPEEDMAX = 200
BIRDSPEEDMIN = 10
FRICTION =.999
FORCE_OF_GRAVITY = 9.81
def write(msg="pygame is cool"):
"""write text into pygame surfaces"""
myfont = pygame.font.SysFont("None", 32)
mytext = myfont.render(msg, True, (0,0,0))
mytext = mytext.convert_alpha()
return mytext
> self.area.bottom:
self.pos[1] = self.area.bottom
self.newspeed() # calculate a new direction
self.pos[0] += self.dx * time
self.pos[1] += self.dy * time
self.rect.centerx = round(self.pos[0],0)
self.rect.centery = round(self.pos[1],0)
> (self.waittime) and self.waiting:
self.newspeed()
self.waiting = False
self.rect.centerx = round(self.pos[0],0)
self.rect.centery = round(self.pos[1],0)
if self.waiting:
self.rect.center = (-100,-100)
else:
# speedcheck
# friction make birds slower
if abs(self.dx) > BIRDSPEEDMIN and abs(self.dy) > BIRDSPEEDMIN:
self.dx *= FRICTION
self.dy *= FRICTION
# spped limit
if abs(self.dx) > BIRDSPEEDMAX:
self.dx = BIRDSPEEDMAX * self.dx / self.dx
if abs(self.dy) > BIRDSPEEDMAX:
self.dy = BIRDSPEEDMAX * self.dy / self.dy
# movement
self.pos[0] += self.dx * seconds
self.pos[1] += self.dy * seconds
# -- check if Bird out of screen
if not self.area.contains(self.rect):
self.crashing = True # change colour later
# --- compare self.rect and area.rect
if self.pos[0] + self.rect.width/2 > self.area.right:
self.pos[0] = self.area.right - self.rect.width/2
if self.pos[0] - self.rect.width/2 < self.area.left:
self.pos[0] = self.area.left + self.rect.width/2
if self.pos[1] + self.rect.height/2 > self.area.bottom:
self.pos[1] = self.area.bottom - self.rect.height/2
if self.pos[1] - self.rect.height/2 < self.area.top:
self.pos[1] = self.area.top + self.rect.height/2
self.newspeed() # calculate a new direction
#--- calculate actual image: crasing, catched, both, nothing ?
self.image = Bird.image[self.crashing + self.catched*2]
#--- calculate new position on screen -----
self.rect.centerx = round(self.pos[0],0)
self.rect.centery = round(self.pos[1],0)
#--- loose hitpoins
if self.crashing:
self.hitpoints -=1
#--- check if still alive
if self.hitpoints <= 0:
self.kill()
> self.lifetime:
self.kill()
self.pos[0] += self.dx * seconds
self.pos[1] += self.dy * seconds
if Fragment.gravity and not self.bluefrag:
self.dy += FORCE_OF_GRAVITY # gravity suck fragments down
self.rect.centerx = round(self.pos[0],0)
self.rect.centery = round(self.pos[1],0)
background = pygame.Surface((screen.get_width(), screen.get_height()))
background.fill((255,255,255)) # fill white
background.blit(write("press left mouse button to increase Bird's layer"),(50,40))
background.blit(write("press right mouse button to decrease Bird's layer."),(50,65))
background.blit(write("layer of mountains are: -1 (blue), -2 (pink), -3 (red)"),(50,90))
background.blit(write("Press ESC to quit, p to print info at mousepos"), (50,115))
# secret keys: g (gravity), p (print layers)
background = background.convert() # jpg can not have transparency
screen.blit(background, (0,0)) # blit background on screen (overwriting all)
#define sprite groups.
Do this before creating sprites
blockgroup = pygame.sprite.LayeredUpdates()
birdgroup = pygame.sprite.Group()
textgroup = pygame.sprite.Group()
bargroup = pygame.sprite.Group()
stuffgroup = pygame.sprite.Group()
mountaingroup = pygame.sprite.Group()
# only the allgroup draws the sprite, so i use LayeredUpdates() instead Group()
allgroup = pygame.sprite.LayeredUpdates() # more sophisticated, can draw sprites in layers
try: # load images into> -4:
birdlayer -= 1
cooldowntime = .5
cry.play()
for bird in birdgroup:
allgroup.change_layer(bird, birdlayer) # allgroup draws the sprite !
for bar in bargroup:
allgroup.change_layer(bar, birdlayer) # allgroup draws the sprite
else:
cooldowntime -= seconds # to avoid speedclicking
pygame.display.set_caption("fps: %.2f birds: %i grav: %s" % (clock.get_fps(), len(birdgroup), Fragment.gravity))
birdtext.newmsg("current Bird _layer = %i" % birdlayer) # update text for birdlayer
# ------ collision detection
for bird in birdgroup:
bird.cleanstatus()
#pygame.sprite.spritecollide(sprite, group, dokill, collided = None): return Sprite_list
crashgroup = pygame.sprite.spritecollide(hunter, birdgroup, False, pygame.sprite.collide_circle)
# pygame.sprite.collide_circle works only if one sprite has self.radius
# you can do without that argument collided and only the self.rects will be checked
for crashbird in crashgroup:
crashbird.catched = True # will get a blue border from Bird.update()
for bird in birdgroup: # test if a bird collides with another bird
# check the Bird.number to make sure the bird is not crashing with himself
crashgroup = pygame.sprite.spritecollide(bird, birdgroup, False )
for crashbird in crashgroup:
if crashbird.number != bird.number: #different number means different birds
bird.crashing = True
if not bird.waiting:
bird.dx -= crashbird.pos[0] - bird.pos[0]
bird.dy -= crashbird.pos[1] - bird.pos[1]
# create 10 new Birds if fewer than 11 birds alive
if len(birdgroup) < 10:
for _ in range(random.randint(1,5)):
Bird(birdlayer)
# ----------- clear, draw , update, flip -----------------
allgroup.clear(screen, background)
allgroup.update(seconds)
allgroup.draw(screen)
pygame.display.flip()
if __name__ == "__main__":
game()
else:
print("i was imported by", __name__)
This Gist brought to you by gist-it.view rawpygame/016_layers.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
017_turning_and_physic.py
pygame sprites primitive physic (elastic collision)
url: http://thepythongamebook.com/en:part2:pygame:step017
author: horst.jens@spielend-programmieren.at
physic by Leonard Michlmayr
licence: gpl, see http://www.gnu.org/licenses/gpl.html
move the big bird around with the keys w,a,s,d and q and e
fire with space, toggle gravity with g
works with pyhton3.4 and python2.7
"""
#the next line is only needed for python2.x and not necessary for python3.x
from __future__ import print_function, division
def game(folder = "data"):
import pygame
import os
import random
import math
#------ starting pygame -------------
pygame.mixer.pre_init(44100, -16, 2, 2048) # setup mixer to avoid sound lag
pygame.init()
screen=pygame.display.set_mode((640,480)) # try out larger values and see what happens !
#winstyle = 0 # |FULLSCREEN # Set the display mode
print("pygame version", pygame.ver)
# ------- game constants ----------------------
BIRDSPEEDMAX = 200
FRAGMENTMAXSPEED = 200
FRICTION =.991 # between 1 and 0.
1 means no friction at all (deep space)
FORCE_OF_GRAVITY = 2.81 # pixel per second square earth: 9.81 m/s²
GRAD = math.pi / 180 # 2 * pi / 360 # math module needs Radiant instead of Grad
# ----------- functions -----------
def write(msg="pygame is cool", color=(0,0,0)):
"""write text into pygame surfaces"""
myfont = pygame.font.SysFont("None", 32)
mytext = myfont.render(msg, True, color)
mytext = mytext.convert_alpha()
return mytext
def getclassname(class_instance):
"""this function extract the>"
parts = text.split(".") # like [""]
return parts[-1][0:-2] # from the last (-1) part, take all but the last 2 chars
def elastic_collision(sprite1, sprite2):
"""elasitc collision between 2 sprites (calculated as disc's).
The function alters the dx and dy movement vectors of both sprites.
The sprites need the property .mass, .radius, .pos[0], .pos[1], .dx, dy
pos[0] is the x postion, pos[1] the y position"""
# here we do some physics: the elastic
# collision
#
# first we get the direction of the push.
# Let's assume that the sprites are disk
# shaped, so the direction of the force is
# the direction of the distance.
dirx = sprite1.pos[0] - sprite2.pos[0]
diry = sprite1.pos[1] - sprite2.pos[1]
#
# the velocity of the centre of mass
sumofmasses = sprite1.mass + sprite2.mass
sx = (sprite1.dx * sprite1.mass + sprite2.dx * sprite2.mass) / sumofmasses
sy = (sprite1.dy * sprite1.mass + sprite2.dy * sprite2.mass) / sumofmasses
# if we sutract the velocity of the centre
# of mass from the velocity of the sprite,
# we get it's velocity relative to the
# centre of mass.
And relative to the
# centre of mass, it looks just like the
# sprite is hitting a mirror.
#
bdxs = sprite2.dx - sx
bdys = sprite2.dy - sy
cbdxs = sprite1.dx - sx
cbdys = sprite1.dy - sy
# (dirx,diry) is perpendicular to the mirror
# surface.
We use the dot product to
# project to that direction.
distancesquare = dirx * dirx + diry * diry
if distancesquare == 0:
# no distance? this should not happen,
# but just in case, we choose a random
# direction
dirx = random.randint(0,11) - 5.5
diry = random.randint(0,11) - 5.5
distancesquare = dirx * dirx + diry * diry
dp = (bdxs * dirx + bdys * diry) # scalar product
dp /= distancesquare # divide by distance * distance.
cdp = (cbdxs * dirx + cbdys * diry)
cdp /= distancesquare
# We are done.
(dirx * dp, diry * dp) is
# the projection of the velocity
# perpendicular to the virtual mirror
# surface.
Subtract it twice to get the
# new direction.
#
# Only collide if the sprites are moving
# towards each other: dp > 0
if dp > 0:
sprite2.dx -= 2 * dirx * dp
sprite2.dy -= 2 * diry * dp
sprite1.dx -= 2 * dirx * cdp
sprite1.dy -= 2 * diry * cdp
# -----------> BIRDSPEEDMAX:
# self.dx = BIRDSPEEDMAX * (self.dx/abs(self.dx)) # dx/abs(dx) is 1 or -1
#if abs(self.dy) > BIRDSPEEDMAX:
# self.dy = BIRDSPEEDMAX * (self.dy/abs(self.dy))
if abs(self.dx) > 0 :
self.dx *= FRICTION # make the Sprite slower over time
if abs(self.dy) > 0 :
self.dy *= FRICTION
def areacheck(self):
if not self.area.contains(self.rect):
self.crashing = True # change colour later
# --- compare self.rect and area.rect
if self.pos[0] + self.rect.width/2 > self.area.right:
self.pos[0] = self.area.right - self.rect.width/2
self.dx *= -0.5 # bouncing off but loosing speed
if self.pos[0] - self.rect.width/2 < self.area.left:
self.pos[0] = self.area.left + self.rect.width/2
self.dx *= -0.5 # bouncing off the side but loosing speed
if self.pos[1] + self.rect.height/2 > self.area.bottom:
self.pos[1] = self.area.bottom - self.rect.height/2
#self.dy *= -1 # bouncing off the ground
#if reaching the bottom, the birds get a boost and fly upward to the sky
#at the bottom the bird "refuel" a random amount of "fuel" (the boostime)
self.dy = 0 # break at the bottom
self.dx *= 0.3 # x speed is reduced at the ground
self.boosttime = self.boostmin + random.random()* (self.boostmax - self.boostmin)
if self.pos[1] - self.rect.height/2 < self.area.top:
self.pos[1] = self.area.top + self.rect.height/2
self.dy = 0 # stop when reaching the sky
#self.dy *= -1
self.hitpoints -= 1 # reaching the sky cost 1 hitpoint
def update(self, seconds):
#---make Bird only visible after waiting time
self.lifetime += seconds
if self.lifetime > (self.waittime):
self.waiting = False
if self.waiting:
self.rect.center = (-100,-100)
else: # the waiting time (Blue Fragments) is over
if self.boosttime > 0: # boost flying upwards ?
self.boosttime -= seconds
self.dy -= self.boostspeed # upward is negative y !
self.ddx = -math.sin(self.angle*GRAD)
self.ddy = -math.cos(self.angle*GRAD)
Smoke(self.rect.center, -self.ddx , -self.ddy )
self.speedcheck() # ------------- movement
self.pos[0] += self.dx * seconds
self.pos[1] += self.dy * seconds
self.areacheck() # ------- check if Bird out of screen
#--- calculate actual image: crasing, bigbird, both, nothing ?
self.image = Bird.image[self.crashing+self.big] # 0 for not crashing, 1 for crashing
self.image0 = Bird.image[self.crashing+self.big] # 0 for not crashing, 1 for crashing
#--------- rotate into direction of movement ------------
self.angle = math.atan2(-self.dx, -self.dy)/math.pi*180.0
self.image = pygame.transform.rotozoom(self.image0,self.angle,1.0)
#--- calculate new position on screen -----
self.rect.centerx = round(self.pos[0],0)
self.rect.centery = round(self.pos[1],0)
if self.hitpoints <= 0:
self.kill()
> important for Bird.image
Bird.__init__(self) # create a "little" Bird but do more than that
self.hitpoints = float(100)
self.hitpointsfull = float(100)
self.image = Bird.image[2] # big bird image
self.pos = [screen.get_width()/2, screen.get_height()/2]
self.rect = self.image.get_rect()
self.angle = 0
self.speed = 20.0 # base movement speed factor
self.rotatespeed = 1.0 # rotating speed
self.frags = 100
Lifebar(self)
self.cooldowntime = 0.08 #seconds
self.cooldown = 0.0
self.damage = 5 # how many damage one bullet inflict
self.shots = 0
self.radius = self.image.get_width() / 2.0
self.mass = 400.0
def kill(self):
bombsound.play()
Bird.kill(self)
def update(self, time):
"""BigBird has its own update method, overwriting the
update method from the Bird> (self.waittime):
self.waiting = False
if self.waiting:
self.rect.center = (-100,-100)
else:
#--- calculate actual image: crasing, bigbird, both, nothing ?
self.image = Bird.image[self.crashing+self.big] # 0 for not crashing, 2 for big
pressedkeys = pygame.key.get_pressed()
self.ddx = 0.0
self.ddy = 0.0
if pressedkeys[pygame.K_w]: # forward
self.ddx = -math.sin(self.angle*GRAD)
self.ddy = -math.cos(self.angle*GRAD)
Smoke(self.rect.center, -self.ddx , -self.ddy )
if pressedkeys[pygame.K_s]: # backward
self.ddx = +math.sin(self.angle*GRAD)
self.ddy = +math.cos(self.angle*GRAD)
Smoke(self.rect.center, -self.ddx, -self.ddy )
if pressedkeys[pygame.K_e]: # right side
self.ddx = +math.cos(self.angle*GRAD)
self.ddy = -math.sin(self.angle*GRAD)
Smoke(self.rect.center, -self.ddx , -self.ddy )
if pressedkeys[pygame.K_q]: # left side
self.ddx = -math.cos(self.angle*GRAD)
self.ddy = +math.sin(self.angle*GRAD)
Smoke(self.rect.center, -self.ddx , -self.ddy )
# ------------shoot-----------------
if self.cooldown > 0:
self.cooldown -= time
else:
if pressedkeys[pygame.K_SPACE]: # shoot forward
self.ddx = +math.sin(self.angle*GRAD)#recoil
self.ddy = +math.cos(self.angle*GRAD)
lasersound.play() # play sound
self.shots += 1
Bullet(self, -math.sin(self.angle*GRAD) ,
-math.cos(self.angle*GRAD) )
self.cooldown = self.cooldowntime
# ------------move------------------
if not self.waiting:
self.dx += self.ddx * self.speed
self.dy += self.ddy * self.speed
#self.speedcheck() # friction, maxspeed
self.pos[0] += self.dx * seconds
self.pos[1] += self.dy * seconds
# -- check if Bird out of screen
self.areacheck()
# ------------- rotate ------------------
if pressedkeys[pygame.K_a]: # left turn , counterclockwise
self.angle += self.rotatespeed
if pressedkeys[pygame.K_d]: # right turn, clockwise
self.angle -= self.rotatespeed
self.oldcenter = self.rect.center
self.image = pygame.transform.rotate(self.image, self.angle)
self.rect = self.image.get_rect()
self.rect.center = self.oldcenter
#--- calculate new position on screen -----
self.rect.centerx = round(self.pos[0],0)
self.rect.centery = round(self.pos[1],0)
if self.hitpoints <= 0: # ----- alive----
self.kill()
> self.lifetime:
self.kill()
self.pos[0] += self.dx * seconds
self.pos[1] += self.dy * seconds
self.rect.centerx = round(self.pos[0],0)
self.rect.centery = round(self.pos[1],0)
> 0:
quota = (float(hits)/player.shots )* 100
pygame.display.set_caption("fps: %.2f gravity: %s hits:%i shots:%i quota:%.2f%%" % (clock.get_fps(),
gravity, hits, player.shots, quota))
# ------ collision detection
for bird in birdgroup: # test if a bird collides with another bird
bird.crashing = False # make bird NOT blue
# check the Bird.number to make sure the bird is not crashing with himself
if not bird.waiting: # do not check birds outside the screen
crashgroup = pygame.sprite.spritecollide(bird, birdgroup, False )
for crashbird in crashgroup: # test bird with other bird collision
if crashbird.number > bird.number: #avoid checking twice
bird.crashing = True # make bird blue
crashbird.crashing = True # make other bird blue
if not (bird.waiting or crashbird.waiting):
elastic_collision(crashbird, bird) # change dx and dy of both birds
crashgroup = pygame.sprite.spritecollide(bird, bulletgroup, False)
for ball in crashgroup: # test for collision with bullet
if ball.boss.number != bird.number:
hitsound.play()
hits +=1
bird.hitpoints -= ball.boss.damage
factor = (ball.mass / bird.mass)
bird.dx += ball.dx * factor
bird.dy += ball.dy * factor
ball.kill()
crashgroup = pygame.sprite.spritecollide(bird, fragmentgroup, False)
for frag in crashgroup: # test for red fragments
bird.hitpoints -=1
factor = frag.mass / bird.mass
bird.dx += frag.dx * factor
bird.dy += frag.dy * factor
frag.kill()
if gravity: # ---- gravity check ---
for thing in gravitygroup:
thing.dy += FORCE_OF_GRAVITY # gravity suck down all kind of things
if len(birdgroup) < amount: # create enough SmallBirds
for _ in range(random.randint(1,3)):
SmallBird()
# ------game Over ? -------------
if (player.hitpoints < 1 or playtime > gametime) and not gameOver:
gameOver = True # do those things once when the game ends
screentext.newmsg("Game Over.
hits/shots: %i/%i quota: %.2f%%" % (hits, player.shots, quota), (255,0,0))
player.hitpoints = 0 # kill the player into a big explosion
if gameOver: # overtime to watch score, explosion etc
overtime -= seconds
if overtime < 0:
mainloop = False
else: # not yet gameOver
screentext.newmsg("Time left: %.2f" % (gametime - playtime))
# ----------- clear, draw , update, flip -----------------
allgroup.clear(screen, background)
allgroup.update(seconds)
allgroup.draw(screen)
pygame.display.flip()
if __name__ == "__main__":
game()
This Gist brought to you by gist-it.view rawpygame/017_turning_and_physic.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
018_pefect_collision_detection.py
pixel perfect collision detection for pygame sprites
url: http://thepythongamebook.com/en:part2:pygame:step018
author: horst.jens@spielend-programmieren.at
physic by Leonard Michlmayr
licence: gpl, see http://www.gnu.org/licenses/gpl.html
this code demonstrate the difference between
colliderect, collidecircle and collidemask
move the small babytux around with the keys w,a,s,d and q and e
fire with space, toggle gravity with g
toggle collision detection with c
Shoot on the giant monsters and watch the yellow impact "wounds"
works with python3.4 and python2.7
"""
#the next line is only needed for python2.x and not necessary for python3.x
from __future__ import print_function, division
def game(folder = "data"):
import pygame
import os
import random
import math
#------ starting pygame -------------
pygame.mixer.pre_init(44100, -16, 2, 2048) # setup mixer to avoid sound lag
pygame.init()
screen=pygame.display.set_mode((640,480)) # try out larger values and see what happens !
#winstyle = 0 # |FULLSCREEN # Set the display mode
print("pygame version", pygame.ver )
# ------- game constants ----------------------
#BIRDSPEEDMIN = 10
FRAGMENTMAXSPEED = 200
FRICTION =.991 # between 1 and 0.
1 means no friction at all (deep space)
FORCE_OF_GRAVITY = 2.81 # pixel per second square.
Earth: 9.81 m/s²
GRAD = math.pi / 180 # 2 * pi / 360 # math module needs Radiant instead of Grad
# ----------- functions -----------
def write(msg="pygame is cool", color=(0,0,0)):
"""write text into pygame surfaces"""
myfont = pygame.font.SysFont("None", 32)
mytext = myfont.render(msg, True, color)
mytext = mytext.convert_alpha()
return mytext
def getclassname(class_instance):
"""this function extract the>"
parts = text.split(".") # like [""]
return parts[-1][0:-2] # from the last (-1) part, take all but the last 2 chars
# -----------> BIRDSPEEDMAX:
# self.dx = BIRDSPEEDMAX * (self.dx/abs(self.dx)) # dx/abs(dx) is 1 or -1
#if abs(self.dy) > BIRDSPEEDMAX:
# self.dy = BIRDSPEEDMAX * (self.dy/abs(self.dy))
if abs(self.dx) > 0 :
self.dx *= FRICTION # make the Sprite slower over time
if abs(self.dy) > 0 :
self.dy *= FRICTION
def areacheck(self):
if not self.area.contains(self.rect):
#self.crashing = True # change colour later
# --- compare self.rect and area.rect
if self.pos[0] + self.rect.width/2 > self.area.right:
self.pos[0] = self.area.right - self.rect.width/2
self.dx *= -0.5 # bouncing off but loosing speed
if self.pos[0] - self.rect.width/2 < self.area.left:
self.pos[0] = self.area.left + self.rect.width/2
self.dx *= -0.5 # bouncing off the side but loosing speed
if self.pos[1] + self.rect.height/2 > self.area.bottom:
self.pos[1] = self.area.bottom - self.rect.height/2
self.dy *= -0.5
if self.pos[1] - self.rect.height/2 < self.area.top:
self.pos[1] = self.area.top + self.rect.height/2
self.dy *= -0.5 # stop when reaching the sky
def update(self, seconds):
self.speedcheck()
# ------------- movement
self.pos[0] += self.dx * seconds
self.pos[1] += self.dy * seconds
# -------------- check if Bird out of screen
self.areacheck()
# ------ rotating
if self.dx != 0 and self.dy!=0:
ratio = self.dy / self.dx
if self.dx > 0:
self.angle = -90-math.atan(ratio)/math.pi*180.0 # in grad
else:
self.angle = 90-math.atan(ratio)/math.pi*180.0 # in grad
#self.image = pygame.transform.rotozoom(self.image0,self.angle,1.0)
#--- calculate new position on screen -----
self.rect.centerx = round(self.pos[0],0)
self.rect.centery = round(self.pos[1],0)
if self.hitpoints <= 0:
self.kill()
> important for Bird.image
Bird.__init__(self,5) # create a "little" Bird but do more than that
self.hitpoints = float(100)
self.hitpointsfull = float(100)
self.pos = [screen.get_width()/2, screen.get_height()/2]
#print "my BigBirdNumber is", self.number # i have a number in the Bird> 0:
self.cooldown -= time
else:
if pressedkeys[pygame.K_SPACE]: # shoot forward
lasersound.play() # play sound
self.shots += 1
Bullet(self, -math.sin(self.angle*GRAD) ,
-math.cos(self.angle*GRAD) )
self.cooldown = self.cooldowntime
# ------------move------------------
self.dx += self.ddx * self.speed
self.dy += self.ddy * self.speed
#self.speedcheck() # friction, maxspeed
self.pos[0] += self.dx * seconds
self.pos[1] += self.dy * seconds
# -- check if Bird out of screen
self.areacheck()
# ------------- rotate ------------------
if pressedkeys[pygame.K_a]: # left turn , counterclockwise
self.angle += self.rotatespeed
if pressedkeys[pygame.K_d]: # right turn, clockwise
self.angle -= self.rotatespeed
self.oldcenter = self.rect.center
self.image = pygame.transform.rotate(self.image0, self.angle)
self.rect = self.image.get_rect()
self.rect.center = self.oldcenter
#--- calculate new position on screen -----
self.rect.centerx = round(self.pos[0],0)
self.rect.centery = round(self.pos[1],0)
if self.hitpoints <= 0: # ----- alive----
self.kill()
> self.lifetime:
self.kill()
self.pos[0] += self.dx * seconds
self.pos[1] += self.dy * seconds
self.rect.centerx = round(self.pos[0],0)
self.rect.centery = round(self.pos[1],0)
> 0:
self.angle = -90-math.atan(ratio)/math.pi*180.0 # in grad
else:
self.angle = 90-math.atan(ratio)/math.pi*180.0 # in grad
self.image = pygame.transform.rotozoom(self.image0,self.angle,1.0)
# ----------------- end of definitions ------------
# ----------------- background artwork -------------
background = pygame.Surface((screen.get_width(), screen.get_height()))
background.fill((255,255,255)) # fill white
background.blit(write("navigate with w,a,s,d and q and e "),(50,40))
background.blit(write("press SPACE to fire bullets"),(50,70))
background.blit(write("press g to toggle gravity"), (50, 100))
background.blit(write("press c to toggle collision detection."),(50,130))
background.blit(write("Press ESC to quit "), (50,160))
background = background.convert() # jpg can not have transparency
screen.blit(background, (0,0)) # blit background on screen (overwriting all)
#-----------------define sprite groups------------------------
birdgroup = pygame.sprite.Group()
textgroup = pygame.sprite.Group()
bargroup = pygame.sprite.Group()
stuffgroup = pygame.sprite.Group()
bulletgroup = pygame.sprite.Group()
fragmentgroup = pygame.sprite.Group()
gravitygroup = pygame.sprite.Group()
# only the allgroup draws the sprite, so i use LayeredUpdates() instead Group()
allgroup = pygame.sprite.LayeredUpdates() # more sophisticated, can draw sprites in layers
#-------------loading files from data subdirectory -------------------------------
try: # load images into> 0:
quota = (float(hits)/player.shots )* 100
pygame.display.set_caption("fps: %.2f gravity: %s hits:%i shots:%i quota:%.2f%%" % (clock.get_fps(),
gravity, hits, player.shots, quota))
# ------ collision detection
for bird in birdgroup:
if collision == "rect":
crashgroup = pygame.sprite.spritecollide(bird, bulletgroup, False, pygame.sprite.collide_rect)
elif collision == "circle":
crashgroup = pygame.sprite.spritecollide(bird, bulletgroup, False, pygame.sprite.collide_circle)
elif collision == "mask":
crashgroup = pygame.sprite.spritecollide(bird, bulletgroup, False, pygame.sprite.collide_mask)
else:
raise( SystemExit, "wrong/missing collisoin method")
for ball in crashgroup: # test for collision with bullet
if ball.boss.number != bird.number:
hitsound.play()
hits +=1
bird.hitpoints -= ball.boss.damage
#factor = (ball.mass / bird.mass)
#bird.dx += ball.dx * factor
#bird.dy += ball.dy * factor
Wound(ball.rect.center, bird)
ball.kill()
crashgroup = pygame.sprite.spritecollide(bird, fragmentgroup, False)
for frag in crashgroup: # test for red fragments
bird.hitpoints -=1
factor = frag.mass / bird.mass
bird.dx += frag.dx * factor
bird.dy += frag.dy * factor
frag.kill()
if gravity: # ---- gravity check ---
for thing in gravitygroup:
thing.dy += FORCE_OF_GRAVITY # gravity suck down all kind of things
#if len(birdgroup) < amount: # create enough SmallBirds
# for _ in range(random.randint(1,3)):
# SmallBird()
# ------game Over ? -------------
if (player.hitpoints < 1 or playtime > gametime) and not gameOver:
gameOver = True # do those things once when the game ends
screentext2.newmsg("")
screentext.newmsg("Game Over.
hits/shots: %i/%i quota: %.2f%%" % (hits, player.shots, quota), (255,0,0))
player.hitpoints = 0 # kill the player into a big explosion
if gameOver: # overtime to watch score, explosion etc
overtime -= seconds
if overtime < 0:
mainloop = False
else: # not yet gameOver
screentext.newmsg("Time left: %.2f" % (gametime - playtime))
# ----------- clear, draw , update, flip -----------------
allgroup.clear(screen, background)
allgroup.update(seconds)
allgroup.draw(screen)
pygame.display.flip()
if __name__ == "__main__":
game()
This Gist brought to you by gist-it.view rawpygame/018_perfect_collision_detection.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
019_homing_missiles.py
2-player game with homing missiles
url: http://thepythongamebook.com/en:part2:pygame:step019
author: horst.jens@spielend-programmieren.at
physic by Leonard Michlmayr
licence: gpl, see http://www.gnu.org/licenses/gpl.html
2 player can shoot at each other and/or at monster(s).
2 types of homing missiles (can also be shot down)
create new monsters with key m
works with python3.4 and python2.7
"""
#the next line is only needed for python2.x and not necessary for python3.x
from __future__ import print_function, division
def game(folder = "data"):
import pygame
import os
import random
import math
#------ starting pygame -------------
pygame.mixer.pre_init(44100, -16, 2, 2048) # setup mixer to avoid sound lag
pygame.init()
screen=pygame.display.set_mode((1800,1000)) # try out larger values and see what happens !
screenrect = screen.get_rect()
#winstyle = 0 # |FULLSCREEN # Set the display mode
#print "pygame version", pygame.ver
# ------- game constants ----------------------
GRAD = math.pi / 180 # 2 * pi / 360 # math module needs Radiant instead of Grad
# ----------- functions -----------
def write(msg="pygame is cool", color=(0,0,0)):
"""write text into pygame surfaces"""
myfont = pygame.font.SysFont("None", 32)
mytext = myfont.render(msg, True, color)
mytext = mytext.convert_alpha()
return mytext
def getclassname(class_instance):
"""this function extract the>"
parts = text.split(".") # like [""]
return parts[-1][0:-2] # from the last (-1) part, take all but the last 2 chars
def radians_to_degrees(radians):
return (radians / math.pi) * 180.0
def degrees_to_radians(degrees):
return degrees * (math.pi / 180.0)
def elastic_collision(sprite1, sprite2):
"""elasitc collision between 2 sprites (calculated as disc's).
The function alters the dx and dy movement vectors of both sprites.
The sprites need the property .mass, .radius, .pos[0], .pos[1], .dx, dy
pos[0] is the x postion, pos[1] the y position"""
# here we do some physics: the elastic
# collision
# first we get the direction of the push.
# Let's assume that the sprites are disk
# shaped, so the direction of the force is
# the direction of the distance.
dirx = sprite1.pos[0] - sprite2.pos[0]
diry = sprite1.pos[1] - sprite2.pos[1]
# the velocity of the centre of mass
sumofmasses = sprite1.mass + sprite2.mass
sx = (sprite1.dx * sprite1.mass + sprite2.dx * sprite2.mass) / sumofmasses
sy = (sprite1.dy * sprite1.mass + sprite2.dy * sprite2.mass) / sumofmasses
# if we sutract the velocity of the centre
# of mass from the velocity of the sprite,
# we get it's velocity relative to the
# centre of mass.
And relative to the
# centre of mass, it looks just like the
# sprite is hitting a mirror.
bdxs = sprite2.dx - sx
bdys = sprite2.dy - sy
cbdxs = sprite1.dx - sx
cbdys = sprite1.dy - sy
# (dirx,diry) is perpendicular to the mirror
# surface.
We use the dot product to
# project to that direction.
distancesquare = dirx * dirx + diry * diry
if distancesquare == 0:
# no distance? this should not happen,
# but just in case, we choose a random
# direction
dirx = random.randint(0,11) - 5.5
diry = random.randint(0,11) - 5.5
distancesquare = dirx * dirx + diry * diry
dp = (bdxs * dirx + bdys * diry) # scalar product
dp /= distancesquare # divide by distance * distance.
cdp = (cbdxs * dirx + cbdys * diry)
cdp /= distancesquare
# We are done.
(dirx * dp, diry * dp) is
# the projection of the velocity
# perpendicular to the virtual mirror
# surface.
Subtract it twice to get the
# new direction.
# Only collide if the sprites are moving
# towards each other: dp > 0
if dp > 0:
sprite2.dx -= 2 * dirx * dp
sprite2.dy -= 2 * diry * dp
sprite1.dx -= 2 * dirx * cdp
sprite1.dy -= 2 * diry * cdp
# -----------> self.boss.rocketsmax / 2:
self.color = (0,0,255)
else:
self.color = (0,0,128)
self.percent = self.boss.rockets / self.boss.rocketsmax * 1.0
> self.speedmax:
factor = self.speedmax / speed * 1.0
self.dx *= factor
self.dy *= factor
else:
self.color = (0,0,128)
self.percent = self.boss.rockets / self.boss.rocketsmax * 1.0
> self.speedmax:
factor = self.speedmax / speed * 1.0
self.dx *= factor
self.dy *= factor
#----------- friction -------------
if abs(self.dx) > 0 :
self.dx *= self.friction # make the Sprite slower over time
if abs(self.dy) > 0 :
self.dy *= self.friction
def areacheck(self):
"""if GameObject leave self.arena, it is bounced (self.areabounce) or stopped (self.areastop)"""
if (self.areastop or self.areabounce) and not self.area.contains(self.rect):
# --- compare self.rect and area.rect
if self.pos[0] + self.rect.width/2 > self.area.right:
self.pos[0] = self.area.right - self.rect.width/2
if self.areabounce:
self.dx *= self.bouncefriction # bouncing off but loosing speed
else:
self.dx = 0
if self.pos[0] - self.rect.width/2 < self.area.left:
self.pos[0] = self.area.left + self.rect.width/2
if self.areabounce:
self.dx *= self.bouncefriction # bouncing off but loosing speed
else:
self.dx = 0
if self.pos[1] + self.rect.height/2 > self.area.bottom:
self.pos[1] = self.area.bottom - self.rect.height/2
if self.areabounce:
self.dy *= self.bouncefriction # bouncing off but loosing speed
else:
self.dy = 0
if self.pos[1] - self.rect.height/2 < self.area.top:
self.pos[1] = self.area.top + self.rect.height/2
if self.areabounce:
self.dy *= self.bouncefriction # bouncing off but loosing speed
else:
self.dy = 0
def rotate_toward_moving(self, dx= None, dy=None):
if dx is None and dy is None:
dx = self.dx
dy = self.dy
return math.atan2(-dx, -dy)/math.pi*180.0
def kill(self):
GameObject.gameobjects[self.number] = None # delete sprite from dictionary
pygame.sprite.Sprite.kill(self) # kill the sprite
def update(self, seconds):
self.alivetime += seconds
# ------- killing --------------
if self.hitpoints <= 1:
self.kill()
if self.lifetime != -1:
if self.alivetime > self.lifetime:
self.kill() # end of natural lifetime
# --------- rotated ? -------------------
if self.angle != self.oldangle:
self.oldcenter = self.rect.center
self.image = pygame.transform.rotate(self.image0, self.angle)
self.rect = self.image.get_rect()
self.rect.center = self.oldcenter
self.oldangle = self.angle
#----------moving ----------------
self.pos[0] += self.dx * seconds
self.pos[1] += self.dy * seconds
self.speedcheck() # ------------- movement
self.areacheck() # ------- check if Bird out of screen
self.rect.centerx = round(self.pos[0],0)
self.rect.centery = round(self.pos[1],0)
> 0:
return random.choice(Monster.monsters)
else:
pass # ------
def update(self, seconds):
pressedkeys = pygame.key.get_pressed()
self.ddx = 0.0
self.ddy = 0.0
self.targetnumber = self.get_target_nr()
if self.playernumber == 0:
if pressedkeys[pygame.K_w]: # forward
self.ddx = -math.sin(self.angle*GRAD)
self.ddy = -math.cos(self.angle*GRAD)
Smoke(self.rect.center, -self.ddx , -self.ddy )
if pressedkeys[pygame.K_s]: # backward
self.ddx = +math.sin(self.angle*GRAD)
self.ddy = +math.cos(self.angle*GRAD)
Smoke(self.rect.center, -self.ddx, -self.ddy )
if pressedkeys[pygame.K_e]: # right side
self.ddx = +math.cos(self.angle*GRAD)
self.ddy = -math.sin(self.angle*GRAD)
Smoke(self.rect.center, -self.ddx , -self.ddy )
if pressedkeys[pygame.K_q]: # left side
self.ddx = -math.cos(self.angle*GRAD)
self.ddy = +math.sin(self.angle*GRAD)
Smoke(self.rect.center, -self.ddx , -self.ddy )
elif self.playernumber == 1:
if pressedkeys[pygame.K_KP8]: # forward
self.ddx = -math.sin(self.angle*GRAD)
self.ddy = -math.cos(self.angle*GRAD)
Smoke(self.rect.center, -self.ddx , -self.ddy )
if pressedkeys[pygame.K_KP5] or pressedkeys[pygame.K_KP2]: # backward
self.ddx = +math.sin(self.angle*GRAD)
self.ddy = +math.cos(self.angle*GRAD)
Smoke(self.rect.center, -self.ddx, -self.ddy )
if pressedkeys[pygame.K_KP9]: # right side
self.ddx = +math.cos(self.angle*GRAD)
self.ddy = -math.sin(self.angle*GRAD)
Smoke(self.rect.center, -self.ddx , -self.ddy )
if pressedkeys[pygame.K_KP7]: # left side
self.ddx = -math.cos(self.angle*GRAD)
self.ddy = +math.sin(self.angle*GRAD)
Smoke(self.rect.center, -self.ddx , -self.ddy )
# ------------shoot-----------------
self.peacetime += seconds # increase peacetime if no shot was fired
if self.cooldown > 0: # ------ can not shoot
self.cooldown -= seconds # pause between bullets
else: # --------can shoot
if ((self.playernumber == 1 and pressedkeys[pygame.K_KP0]) or
(self.playernumber == 0 and pressedkeys[pygame.K_SPACE])): # shoot forward
self.ddx = +math.sin(self.angle*GRAD)#recoil
self.ddy = +math.cos(self.angle*GRAD)
lasersound.play() # play sound
Bullet(self, None, self.max_abberation )
self.peacetime = 0 # reset peacetime
self.cooldown = self.cooldowntime
self.bullets_fired += 1
if self.rocketcooldown > 0:
self.rocketcooldown -= seconds
else:
if self.rockets > self.rocketsmax / 2: # heavy sliding rocket
if self.targetnumber is not None:
crysound.play()
Rocket(self,self.targetnumber,1,-30) #boss, target, type, launchangle
Rocket(self,self.targetnumber,1, 30) #boss, target, type, launchangle
self.rockets_fired +=2
self.rockets -= 2
self.rocketcooldown = self.rocketcooldowntime
elif self.rockets > 2: # weak seeking rocket
if self.targetnumber is not None:
crysound.play()
Rocket(self,self.targetnumber, 2, -80 )#boss, target, type
Rocket(self,self.targetnumber, 2, 80 )#boss, target, type
self.rockets_fired += 2
self.rockets -= 2
self.rocketcooldown = self.rocketcooldowntime
#----- add more rockets --------
if self.peacetime > self.rocketreloadtime:
self.rockets += 2
self.peacetime = 0
#-------------rotate----------------
if self.playernumber == 0:
if pressedkeys[pygame.K_a]: # left turn , counterclockwise
self.angle += self.rotatespeed
if pressedkeys[pygame.K_d]: # right turn, clockwise
self.angle -= self.rotatespeed
elif self.playernumber == 1:
if pressedkeys[pygame.K_KP4]: # left turn , counterclockwise
self.angle += self.rotatespeed
if pressedkeys[pygame.K_KP6]: # right turn, clockwise
self.angle -= self.rotatespeed
# ------------move------------------
self.dx += self.ddx * self.speed
self.dy += self.ddy * self.speed
# ----- move, rotate etc.
------------
GameObject.update(self, seconds)# ------- calll parent function
> 0: # 1+ Monsters are alive
mynumber = self.number
while mynumber == self.number:
mynumber = random.choice(Monster.monsters)
return mynumber
else:
return None
def update(self, seconds):
# each second, decide if to hunt player 0 or player 1
self.hunttime += seconds
if self.hunttime > 15:
self.hunttime = 0
self.targetnumber = self.choose_target_nr()
self.target = GameObject.gameobjects[self.targetnumber]
# hunting
self.image = Monster.image[0] # "normal" xmonster
if GameObject.gameobjects[self.targetnumber] is not None:
self.targetdistancex = self.target.pos[0] - self.pos[0]
self.targetdistancey = self.target.pos[1] - self.pos[1]
if self.targetdistancex > 0:
self.dx += 1
if self.targetdistancex > 100:
self.image = Monster.image[3] # look right
#self.image0 = Monster.image[3] # look right
elif self.targetdistancex <0:
self.dx -= 1
if self.targetdistancex < -100:
self.image = Monster.image[2] # look left
#self.image0 = Monster.image[2] # look left
if self.targetdistancey > 0:
self.dy += 1
elif self.targetdistancey < 0:
self.dy -= 1
# ----- shoot rockets --------
# ---- 4 different phases: bullets, heavy rockets, light rockets, pausing
# ---- chance to change into another phase each full second
self.phase = self.phases[int(self.alivetime) % 4 ] # fully cycle throug all phases each 3 seconds
if self.phase == "nothing":
pass # do not shoot
elif self.phase == "bullets":
if random.randint(1,2) == 1:
Bullet(self, GameObject.rotate_toward_moving(self, self.targetdistancex, self.targetdistancey), self.max_abberation)
self.bullets_fired += 1
self.firetime = 0.1
elif self.phase == "heavy rockets":
#self.angle = math.atan2(-self.dx, -self.dy)/math.pi*180.0
if random.randint(1,50) == 1:
Rocket(self, self.targetnumber, 1) # shoot a slow, heavy rocket
self.rockets_fired += 1
self.firetime = 0.25 # show fire image for this time
elif self.phase == "small rockets":
if random.randint(1,25) == 1:
Rocket(self, self.targetnumber, 2) # shoot a small fast rocket
self.rockets_fired += 1
self.firetime = 0.25 # show fire image for this time
if self.firetime > 0:
self.image = Monster.image[1] # show fire image for monster
self.firetime -= seconds
else:
self.firetime = 0
GameObject.update(self, seconds)
> self.lifetime:
self.kill()
self.pos[0] += self.dx * seconds
self.pos[1] += self.dy * seconds
self.rect.centerx = round(self.pos[0],0)
self.rect.centery = round(self.pos[1],0)
> self.lifetime:
self.kill()
if self.hitpoints < 1:
self.kill()
if GameObject.gameobjects[self.targetnr] is None:
self.kill()
if self.alivetime < self.starttime:
#--------- rotate into direction of movement ------------
self.angle = math.atan2(-self.dx, -self.dy)/math.pi*180.0
else:
#-------- rotate toward target
if self.target is not None:
ix = self.target.pos[0] - self.pos[0]
iy = self.target.pos[1] - self.pos[1]
self.angle = radians_to_degrees(math.atan2(iy,- ix))+90
self.ddx = -math.sin(self.angle*GRAD)
self.ddy = -math.cos(self.angle*GRAD)
if self.type == 1: # sliding
self.dx += self.ddx #* self.speed
self.dy += self.ddy #* self.speed
if random.randint(1,self.smokechance) ==1:
Smoke(self.pos, -self.ddx * 2, -self.ddy * 2)
elif self.type == 2: #seeking
self.dx = self.ddx * self.speed
self.dy = self.ddy * self.speed
if random.randint(1, self.smokechance) ==1:
Smoke(self.pos, -self.ddx, -self.ddy, 25, 75)
#----------- both ------------
GameObject.speedcheck(self)
oldrect = self.rect.center
self.image = pygame.transform.rotozoom(self.image0,self.angle,1.0)
self.rect = self.image.get_rect()
self.rect.center = oldrect
self.pos[0] += self.dx * seconds
self.pos[1] += self.dy * seconds
self.rect.centerx = round(self.pos[0],0)
self.rect.centery = round(self.pos[1],0)
#------------- end of> crashplayer.number:
elastic_collision(crashplayer, player) # impact on player
# player.hitpoints -= crashplayer.damage
# no damage ?
# test if player crash into enemy rocket
crashgroup = pygame.sprite.spritecollide(player, rocketgroup, False, pygame.sprite.collide_mask)
for rocket in crashgroup:
#if projectile.physicnumber > crashbody.physicnumber: #avoid checking twice
if rocket.boss.playernumber != player.playernumber: # avoid friendly fire
impactsound.play()
player.hitpoints -= rocket.damage
rocket.boss.rockets_hit += 1
Wound(rocket.pos[:])
elastic_collision(rocket, player)
rocket.kill()
for projectile in projectilegroup:
# rocket vs rocket vs bullet vs bullet
crashgroup = pygame.sprite.spritecollide(projectile, projectilegroup, False )
for crashthing in crashgroup:
if projectile.number > crashthing.number:
if crashthing.boss.playernumber != projectile.boss.playernumber:
projectile.hitpoints -= crashthing.damage
crashthing.hitpoints -= projectile.damage
elastic_collision(projectile, crashthing)
if gravity: # ---- gravity check ---
for thing in gravitygroup: # gravity suck down bullets, players, monsters
thing.dy += 2.81 # pixel per second square earth: 9.81 m/s
# ------game Over ? -------------
#if (playtime > gametime) and not gameOver:
# gameOver = True # do those things once when the game ends
if GameObject.gameobjects[0] is None and GameObject.gameobjects[1] is None:
gameOver = True # both players death
screentext1.newmsg("Game Over.
Time played: %.2f seconds" % playtime)
screentext2.newmsg("both players killed")
elif GameObject.gameobjects[0] is None or GameObject.gameobjects[1] is None:
if player1.hitpoints > 0:
textname = "Red Player"
textcolor = (255,0,0)
else:
textname = "Blue Player"
textcolor = (0,0,255)
if len(monstergroup) == 0:
gameOver = True # one player dead, all monsters dead
screentext2.newmsg("%s, you win!" % textname, textcolor)
elif len(monstergroup) == 1:
screentext2.newmsg("%s, fight the monster !" % textname, textcolor)
else:
screentext2.newmsg("%s, fight the monsters !" % textname, textcolor)
elif len(monstergroup) == 0:
Player.duel = True # both players alive, no monsters alive
screentext2.newmsg("Duel mode.
Both Players, fight each other!", (255,0,255))
elif len(monstergroup) == 1:
Player.duel = False
screentext2.newmsg("Both players, fight the monster", (255,0,255))
elif len(monstergroup) > 1:
Player.duel = False
screentext2.newmsg("Both players, fight the monsters", (255,0,255))
if gameOver: # overtime to watch score, explosion etc
overtime -= seconds
screentext1.newmsg("Game over.
Overtime: %.2f" % overtime)
if overtime < 0:
mainloop = False
else: # not yet gameOver
screentext1.newmsg("Time left: %.2f" % (gametime - playtime),(255,0,255))
#if player1.bullets_fired > 0:
screentext3.newmsg("Red player: bullets: %i hit: %i quota: %.2f%% rockets: %i hits: %i quota: %.2f%%"
% (player1.bullets_fired, player1.bullets_hit, player1.bullets_hit *100.0 / max(1, player1.bullets_fired),
player1.rockets_fired, player1.rockets_hit, player1.rockets_hit * 100.0 / max(1,player1.rockets_fired))
,(255,0,0))
screentext4.newmsg("Blue player: bullets: %i hit: %i quota: %.2f%% rockets: %i hits: %i quota: %.2f%%"
% (player2.bullets_fired, player2.bullets_hit, player2.bullets_hit *100.0 / max(1, player2.bullets_fired),
player2.rockets_fired, player2.rockets_hit, player2.rockets_hit * 100.0 / max(1,player2.rockets_fired))
,(0,0,255))
# ----------- clear, draw , update, flip -----------------
allgroup.clear(screen, background)
allgroup.update(seconds)
allgroup.draw(screen)
pygame.display.flip()
if __name__ == "__main__":
game()
This Gist brought to you by gist-it.view rawpygame/019_homing_missiles.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
020_shooting_from_tank.py
demo of tank game with rotating turrets
url: http://thepythongamebook.com/en:part2:pygame:step020
author: horst.jens@spielend-programmieren.at
licence: gpl, see http://www.gnu.org/licenses/gpl.html
demo of 2 tanks shooting bullets at the end of it's cannon
and shooting tracers at the end of it's bow Machine Gun
and from the turret-machine gun (co-axial with main gun)
works with pyhton3.4 and python2.7
"""
#the next line is only needed for python2.x and not necessary for python3.x
from __future__ import print_function, division
import pygame
import random
import math
GRAD = math.pi / 180 # 2 * pi / 360 # math module needs Radiant instead of Grad
class Config(object):
""" a> Bullet.maxlifetime:
self.kill()
# ------ calculate movement --------
self.pos[0] += self.dx * seconds
self.pos[1] += self.dy * seconds
# ----- kill if out of screen
if self.pos[0] < 0:
self.kill()
elif self.pos[0] > Config.width:
self.kill()
if self.pos[1] < 0:
self.kill()
elif self.pos[1] > Config.height:
self.kill()
#------- move -------
self.rect.centerx = round(self.pos[0],0)
self.rect.centery = round(self.pos[1],0)
class Tracer(Bullet):
"""Tracer is nearly the same as Bullet, but smaller
and with another origin (bow MG rect instead cannon.
Tracer inherits all methods of Bullet, but i overwrite
calculate_heading and calculate_origin"""
side = 15 # long side of bullet rectangle
vel = 200 # velocity
mass = 10
color = (200,0,100)
maxlifetime = 10.0 # seconds
def __init__(self, boss, turret=False):
self.turret = turret
Bullet.__init__(self,boss ) # this line is important
def calculate_heading(self):
"""overwriting the method because there are some differences
between a tracer and a main gun bullet"""
self.radius = Tracer.side # for collision detection
self.angle = 0
self.angle += self.boss.tankAngle
if self.turret:
self.angle = self.boss.turretAngle
self.mass = Tracer.mass
self.vel = Tracer.vel
image = pygame.Surface((Tracer.side, Tracer.side / 4)) # a line
image.fill(self.boss.color) # fill yellow ?
pygame.draw.rect(image, (0,0,0), (Tracer.side * .75, 0, Tracer.side, Tracer.side / 4)) # red dot at front
image.set_colorkey((128,128,128)) # grey transparent
self.image0 = image.convert_alpha()
self.image = pygame.transform.rotate(self.image0, self.angle)
self.rect = self.image.get_rect()
if self.turret:
# turret mg
self.dx = math.cos(degrees_to_radians(self.boss.turretAngle)) * self.vel
self.dy = math.sin(degrees_to_radians(-self.boss.turretAngle)) * self.vel
else:
# bow mg
self.dx = math.cos(degrees_to_radians(self.boss.tankAngle)) * self.vel
self.dy = math.sin(degrees_to_radians(-self.boss.tankAngle)) * self.vel
def calculate_origin(self):
"""overwriting because another point of origin is needed"""
# - spawn bullet at end of machine gun muzzle (bow or turret)
if self.turret:
self.pos[0] += math.cos(degrees_to_radians(-90+self.boss.turretAngle)) * 15
self.pos[1] += math.sin(degrees_to_radians(90-self.boss.turretAngle)) * 15
else:
self.pos[0] += math.cos(degrees_to_radians(30+self.boss.tankAngle)) * (Tank.side/2)
self.pos[1] += math.sin(degrees_to_radians(-30-self.boss.tankAngle)) * (Tank.side/2)
class Tank(pygame.sprite.Sprite):
""" A Tank, controlled by the Player with Keyboard commands.
This Tank draw it's own Turret (including the main gun)
and it's bow rectangle (slit for Tracer Machine Gun"""
side = 100 # side of the quadratic tank sprite
recoiltime = 0.75 # how many seconds the cannon is busy after firing one time
mgrecoiltime = 0.2 # how many seconds the bow mg (machine gun) is idle
turretTurnSpeed = 25 # turret
tankTurnSpeed = 8 # tank
movespeed = 25
maxrotate = 360 # maximum amount of degree the turret is allowed to rotate
book = {} # a book of tanks to store all tanks
number = 0 # each tank gets his own number
# keys for tank control, expand if you need more tanks
# player1, player2 etc
firekey = (pygame.K_k, pygame.K_DOWN)
mgfirekey = (pygame.K_LCTRL, pygame.K_KP_ENTER)
mg2firekey = (pygame.K_i, pygame.K_UP)
turretLeftkey = (pygame.K_j, pygame.K_LEFT)
turretRightkey = (pygame.K_l, pygame.K_RIGHT)
forwardkey = (pygame.K_w, pygame.K_KP8)
backwardkey = (pygame.K_s, pygame.K_KP5)
tankLeftkey = (pygame.K_a, pygame.K_KP4)
tankRightkey = (pygame.K_d, pygame.K_KP6)
color = ((200,200,0), (0,0,200))
msg = ["wasd LCTRL, ijkl", "Keypad: 4852, ENTER, cursor"]
def __init__(self, startpos = (150,150), angle=0):
self.number = Tank.number # now i have a unique tank number
Tank.number += 1 # prepare number for next tank
Tank.book[self.number] = self # store myself into the tank book
pygame.sprite.Sprite.__init__(self, self.groups) # THE most important line !
self.pos = [startpos[0], startpos[1]] # x,y
self.dx = 0
self.dy = 0
self.ammo = 30 # main gun
self.mgammo = 500 # machinge gun
self.msg = "player%i: ammo: %i/%i keys: %s" % (self.number+1, self.ammo, self.mgammo, Tank.msg[self.number])
Text((Config.width/2, 30+20*self.number), self.msg) # create status line text sprite
self.color = Tank.color[self.number]
self.turretAngle = angle #turret facing
self.tankAngle = angle # tank facing
self.firekey = Tank.firekey[self.number] # main gun
self.mgfirekey = Tank.mgfirekey[self.number] # bow mg
self.mg2firekey = Tank.mg2firekey[self.number] # turret mg
self.turretLeftkey = Tank.turretLeftkey[self.number] # turret
self.turretRightkey = Tank.turretRightkey[self.number] # turret
self.forwardkey = Tank.forwardkey[self.number] # move tank
self.backwardkey = Tank.backwardkey[self.number] # reverse tank
self.tankLeftkey = Tank.tankLeftkey[self.number] # rotate tank
self.tankRightkey = Tank.tankRightkey[self.number] # rotat tank
# painting facing north, have to rotate 90° later
image = pygame.Surface((Tank.side,Tank.side)) # created on the fly
image.fill((128,128,128)) # fill grey
if self.side > 10:
pygame.draw.rect(image, self.color, (5,5,self.side-10, self.side-10)) #tank body, margin 5
pygame.draw.rect(image, (90,90,90), (0,0,self.side//6, self.side)) # track left
pygame.draw.rect(image, (90,90,90), (self.side-self.side//6, 0, self.side,self.side)) # right track
pygame.draw.rect(image, (255,0,0), (self.side//6+5 , 10, 10, 5)) # red bow rect left
#pygame.draw.rect(image, (255,0,0), (self.side/2 - 5, 10, 10, 5)) # red bow rect middle
pygame.draw.circle(image, (255,0,0), (self.side//2,self.side//2), self.side//3 , 2) # red circle for turret
image = pygame.transform.rotate(image,-90) # rotate so to look east
self.image0 = image.convert_alpha()
self.image = image.convert_alpha()
self.rect = self.image0.get_rect()
#---------- turret ------------------
self.firestatus = 0.0 # time left until cannon can fire again
self.mgfirestatus = 0.0 # time until mg can fire again
self.mg2firestatus = 0.0 # time until turret mg can fire again
self.turndirection = 0 # for turret
self.tankturndirection = 0
self.movespeed = Tank.movespeed
self.turretTurnSpeed = Tank.turretTurnSpeed
self.tankTurnSpeed = Tank.tankTurnSpeed
Turret(self) # create a Turret for this tank
def update(self, seconds):
# no need for seconds but the other sprites need it
#-------- reloading, firestatus----------
if self.firestatus > 0:
self.firestatus -= seconds # cannon will soon be ready again
if self.firestatus <0:
self.firestatus = 0 #avoid negative numbers
if self.mgfirestatus > 0:
self.mgfirestatus -= seconds # bow mg will soon be ready again
if self.mgfirestatus <0:
self.mgfirestatus = 0 #avoid negative numbers
if self.mg2firestatus > 0:
self.mg2firestatus -= seconds # turret mg will soon be ready again
if self.mg2firestatus <0:
self.mg2firestatus = 0 #avoid negative numbers
# ------------ keyboard --------------
pressedkeys = pygame.key.get_pressed()
# -------- turret manual rotate ----------
self.turndirection = 0 # left / right turret rotation
if pressedkeys[self.turretLeftkey]:
self.turndirection += 1
if pressedkeys[self.turretRightkey]:
self.turndirection -= 1
#---------- tank rotation ---------
self.tankturndirection = 0 # reset left/right rotation
if pressedkeys[self.tankLeftkey]:
self.tankturndirection += 1
if pressedkeys[self.tankRightkey]:
self.tankturndirection -= 1
# ---------------- rotate tank ---------------
self.tankAngle += self.tankturndirection * self.tankTurnSpeed * seconds # time-based turning of tank
# angle etc from Tank (boss)
oldcenter = self.rect.center
oldrect = self.image.get_rect() # store current surface rect
self.image = pygame.transform.rotate(self.image0, self.tankAngle)
self.rect = self.image.get_rect()
self.rect.center = oldcenter
# if tank is rotating, turret is also rotating with tank !
# -------- turret autorotate ----------
self.turretAngle += self.tankturndirection * self.tankTurnSpeed * seconds + self.turndirection * self.turretTurnSpeed * seconds # time-based turning
# ---------- fire cannon -----------
if (self.firestatus ==0) and (self.ammo > 0):
if pressedkeys[self.firekey]:
self.firestatus = Tank.recoiltime # seconds until tank can fire again
Bullet(self)
self.ammo -= 1
self.msg = "player%i: ammo: %i/%i keys: %s" % (self.number+1, self.ammo, self.mgammo, Tank.msg[self.number])
Text.book[self.number].changemsg(self.msg)
# -------- fire bow mg ---------------
if (self.mgfirestatus ==0) and (self.mgammo >0):
if pressedkeys[self.mgfirekey]:
self.mgfirestatus = Tank.mgrecoiltime
Tracer(self, False) # turret mg = False
self.mgammo -= 1
self.msg = "player%i: ammo: %i/%i keys: %s" % (self.number+1, self.ammo, self.mgammo, Tank.msg[self.number])
Text.book[self.number].changemsg(self.msg)
# -------- fire turret mg ---------------
if (self.mg2firestatus ==0) and (self.mgammo >0):
if pressedkeys[self.mg2firekey]:
self.mg2firestatus = Tank.mgrecoiltime # same recoiltime for both mg's
Tracer(self, True) # turret mg = True
self.mgammo -= 1
self.msg = "player%i: ammo: %i/%i keys: %s" % (self.number+1, self.ammo, self.mgammo, Tank.msg[self.number])
Text.book[self.number].changemsg(self.msg)
# ---------- movement ------------
self.dx = 0
self.dy = 0
self.forward = 0 # movement calculator
if pressedkeys[self.forwardkey]:
self.forward += 1
if pressedkeys[self.backwardkey]:
self.forward -= 1
# if both are pressed togehter, self.forward becomes 0
if self.forward == 1:
self.dx = math.cos(degrees_to_radians(self.tankAngle)) * self.movespeed
self.dy = -math.sin(degrees_to_radians(self.tankAngle)) * self.movespeed
if self.forward == -1:
self.dx = -math.cos(degrees_to_radians(self.tankAngle)) * self.movespeed
self.dy = math.sin(degrees_to_radians(self.tankAngle)) * self.movespeed
# ------------- check border collision ---------------------
self.pos[0] += self.dx * seconds
self.pos[1] += self.dy * seconds
if self.pos[1] + self.side//2 >= Config.height:
self.pos[1] = Config.height - self.side//2
self.dy = 0 # crash into border
elif self.pos[1] -self.side/2 <= 0:
self.pos[1] = 0 + self.side//2
self.dy = 0
# ---------- paint sprite at correct position ---------
self.rect.centerx = round(self.pos[0], 0) #x
self.rect.centery = round(self.pos[1], 0) #y
class Turret(pygame.sprite.Sprite):
"""turret on top of tank"""
def __init__(self, boss):
pygame.sprite.Sprite.__init__(self, self.groups) # THE most important line !
self.boss = boss
self.side = self.boss.side
self.images = {} # how much recoil after shooting, reverse order of apperance
self.images[0] = self.draw_cannon(0) # idle position
self.images[1] = self.draw_cannon(1)
self.images[2] = self.draw_cannon(2)
self.images[3] = self.draw_cannon(3)
self.images[4] = self.draw_cannon(4)
self.images[5] = self.draw_cannon(5)
self.images[6] = self.draw_cannon(6)
self.images[7] = self.draw_cannon(7)
self.images[8] = self.draw_cannon(8) # position of max recoil
self.images[9] = self.draw_cannon(4)
self.images[10] = self.draw_cannon(0) # idle position
def update(self, seconds):
# painting the correct image of cannon
if self.boss.firestatus > 0:
self.image = self.images[int(self.boss.firestatus // (Tank.recoiltime / 10.0))]
else:
self.image = self.images[0]
# --------- rotating -------------
# angle etc from Tank (boss)
oldrect = self.image.get_rect() # store current surface rect
self.image = pygame.transform.rotate(self.image, self.boss.turretAngle)
self.rect = self.image.get_rect()
# ---------- move with boss ---------
self.rect = self.image.get_rect()
self.rect.center = self.boss.rect.center
def draw_cannon(self, offset):
# painting facing right, offset is the recoil
image = pygame.Surface((self.boss.side * 2,self.boss.side * 2)) # created on the fly
image.fill((128,128,128)) # fill grey
pygame.draw.circle(image, (255,0,0), (self.side,self.side), 22, 0) # red circle
pygame.draw.circle(image, (0,255,0), (self.side,self.side), 18, 0) # green circle
pygame.draw.rect(image, (255,0,0), (self.side-10, self.side + 10, 15,2)) # turret mg rectangle
pygame.draw.rect(image, (0,255,0), (self.side-20 - offset,self.side - 5, self.side - offset,10)) # green cannon
pygame.draw.rect(image, (255,0,0), (self.side-20 - offset,self.side - 5, self.side - offset,10),1) # red rect
image.set_colorkey((128,128,128))
return image
# ---------------- End of>021_targeting.py
demo of tank game with rotating turrets
url: http://thepythongamebook.com/en:part2:pygame:step021
author: horst.jens@spielend-programmieren.at
licence: gpl, see http://www.gnu.org/licenses/gpl.html
the player2 tank rotate it's turrent always to player1 tank
you can "teleport" player1 tank by mouse click
special thanks to Jonathan Persson for motivation to write this
"""
import pygame
import random
import math
GRAD = math.pi / 180 # 2 * pi / 360 # math module needs Radiant instead of Grad
class Config(object):
""" a> Bullet.maxlifetime:
self.kill()
# ------ calculate movement --------
self.pos[0] += self.dx * seconds
self.pos[1] += self.dy * seconds
# ----- kill if out of screen
if self.pos[0] < 0:
self.kill()
elif self.pos[0] > Config.width:
self.kill()
if self.pos[1] < 0:
self.kill()
elif self.pos[1] > Config.height:
self.kill()
#------- move -------
self.rect.centerx = round(self.pos[0],0)
self.rect.centery = round(self.pos[1],0)
class Tracer(Bullet):
"""Tracer is nearly the same as Bullet, but smaller
and with another origin (bow MG rect instead cannon.
Tracer inherits all methods of Bullet, but i overwrite
calculate_heading and calculate_origin"""
side = 15 # long side of bullet rectangle
vel = 200 # velocity
mass = 10
color = (200,0,100)
maxlifetime = 10.0 # seconds
def __init__(self, boss, turret=False):
self.turret = turret
Bullet.__init__(self,boss ) # this line is important
def calculate_heading(self):
"""overwriting the method because there are some differences
between a tracer and a main gun bullet"""
self.radius = Tracer.side # for collision detection
self.angle = 0
self.angle += self.boss.tankAngle
if self.turret:
self.angle = self.boss.turretAngle
self.mass = Tracer.mass
self.vel = Tracer.vel
image = pygame.Surface((Tracer.side, Tracer.side / 4)) # a line
image.fill(self.boss.color) # fill yellow ?
pygame.draw.rect(image, (0,0,0), (Tracer.side * .75, 0, Tracer.side, Tracer.side / 4)) # red dot at front
image.set_colorkey((128,128,128)) # grey transparent
self.image0 = image.convert_alpha()
self.image = pygame.transform.rotate(self.image0, self.angle)
self.rect = self.image.get_rect()
if self.turret:
# turret mg
self.dx = math.cos(degrees_to_radians(self.boss.turretAngle)) * self.vel
self.dy = math.sin(degrees_to_radians(-self.boss.turretAngle)) * self.vel
else:
# bow mg
self.dx = math.cos(degrees_to_radians(self.boss.tankAngle)) * self.vel
self.dy = math.sin(degrees_to_radians(-self.boss.tankAngle)) * self.vel
def calculate_origin(self):
"""overwriting because another point of origin is needed"""
# - spawn bullet at end of machine gun muzzle (bow or turret)
if self.turret:
self.pos[0] += math.cos(degrees_to_radians(-90+self.boss.turretAngle)) * 15
self.pos[1] += math.sin(degrees_to_radians(90-self.boss.turretAngle)) * 15
else:
self.pos[0] += math.cos(degrees_to_radians(30+self.boss.tankAngle)) * (Tank.side/2)
self.pos[1] += math.sin(degrees_to_radians(-30-self.boss.tankAngle)) * (Tank.side/2)
class Tank(pygame.sprite.Sprite):
""" A Tank, controlled by the Player with Keyboard commands.
This Tank draw it's own Turret (including the main gun)
and it's bow rectangle (slit for Tracer Machine Gun"""
side = 100 # side of the quadratic tank sprite
recoiltime = 0.75 # how many seconds the cannon is busy after firing one time
mgrecoiltime = 0.2 # how many seconds the bow mg (machine gun) is idle
turretTurnSpeed = 50 # turret
tankTurnSpeed = 80 # tank
movespeed = 80
#maxrotate = 360 # maximum amount of degree the turret is allowed to rotate
book = {} # a book of tanks to store all tanks
number = 0 # each tank gets his own number
# keys for tank control, expand if you need more tanks
# player1, player2 etc
firekey = (pygame.K_k, pygame.K_DOWN)
mgfirekey = (pygame.K_LCTRL, pygame.K_KP_ENTER)
mg2firekey = (pygame.K_i, pygame.K_UP)
turretLeftkey = (pygame.K_j, pygame.K_LEFT)
turretRightkey = (pygame.K_l, pygame.K_RIGHT)
forwardkey = (pygame.K_w, pygame.K_KP8)
backwardkey = (pygame.K_s, pygame.K_KP5)
tankLeftkey = (pygame.K_a, pygame.K_KP4)
tankRightkey = (pygame.K_d, pygame.K_KP6)
color = ((200,200,0), (0,0,200))
#msg = ["wasd LCTRL, ijkl", "Keypad: 4852, ENTER, cursor"]
def __init__(self, startpos = (150,150), angle=0):
self.number = Tank.number # now i have a unique tank number
Tank.number += 1 # prepare number for next tank
Tank.book[self.number] = self # store myself into the tank book
pygame.sprite.Sprite.__init__(self, self.groups) # THE most important line !
self.pos = [startpos[0], startpos[1]] # x,y
self.dx = 0
self.dy = 0
self.ammo = 30 # main gun
self.mgammo = 500 # machinge gun
self.color = Tank.color[self.number]
self.turretAngle = angle #turret facing
self.tankAngle = angle # tank facing
self.msg = "tank%i: x:%i y:%i facing: turret:%i tank:%i" % (self.number, self.pos[0], self.pos[1], self.turretAngle, self.tankAngle )
Text((Config.width/2, 30+20*self.number), self.msg) # create status line text sprite
self.firekey = Tank.firekey[self.number] # main gun
self.mgfirekey = Tank.mgfirekey[self.number] # bow mg
self.mg2firekey = Tank.mg2firekey[self.number] # turret mg
self.turretLeftkey = Tank.turretLeftkey[self.number] # turret
self.turretRightkey = Tank.turretRightkey[self.number] # turret
self.forwardkey = Tank.forwardkey[self.number] # move tank
self.backwardkey = Tank.backwardkey[self.number] # reverse tank
self.tankLeftkey = Tank.tankLeftkey[self.number] # rotate tank
self.tankRightkey = Tank.tankRightkey[self.number] # rotat tank
# painting facing north, have to rotate 90° later
image = pygame.Surface((Tank.side,Tank.side)) # created on the fly
image.fill((128,128,128)) # fill grey
if self.side > 10:
pygame.draw.rect(image, self.color, (5,5,self.side-10, self.side-10)) #tank body, margin 5
pygame.draw.rect(image, (90,90,90), (0,0,self.side/6, self.side)) # track left
pygame.draw.rect(image, (90,90,90), (self.side-self.side/6, 0, self.side,self.side)) # right track
pygame.draw.rect(image, (255,0,0), (self.side/6+5 , 10, 10, 5)) # red bow rect left
#pygame.draw.rect(image, (255,0,0), (self.side/2 - 5, 10, 10, 5)) # red bow rect middle
pygame.draw.circle(image, (255, 0, 0), (int(self.side/2), int(self.side/2)), int(self.side/3), 2) # red circle for turret
image = pygame.transform.rotate(image,-90) # rotate so to look east
self.image0 = image.convert_alpha()
self.image = image.convert_alpha()
self.rect = self.image0.get_rect()
#---------- turret ------------------
self.firestatus = 0.0 # time left until cannon can fire again
self.mgfirestatus = 0.0 # time until mg can fire again
self.mg2firestatus = 0.0 # time until turret mg can fire again
self.turndirection = 0 # for turret
self.tankturndirection = 0
self.movespeed = Tank.movespeed
self.turretTurnSpeed = Tank.turretTurnSpeed
self.tankTurnSpeed = Tank.tankTurnSpeed
Turret(self) # create a Turret for this tank
def update(self, seconds):
# no need for seconds but the other sprites need it
#-------- reloading, firestatus----------
if self.firestatus > 0:
self.firestatus -= seconds # cannon will soon be ready again
if self.firestatus <0:
self.firestatus = 0 #avoid negative numbers
if self.mgfirestatus > 0:
self.mgfirestatus -= seconds # bow mg will soon be ready again
if self.mgfirestatus <0:
self.mgfirestatus = 0 #avoid negative numbers
if self.mg2firestatus > 0:
self.mg2firestatus -= seconds # turret mg will soon be ready again
if self.mg2firestatus <0:
self.mg2firestatus = 0 #avoid negative numbers
# ------------ keyboard --------------
pressedkeys = pygame.key.get_pressed()
# -------- turret manual rotate ----------
self.turndirection = 0 # left / right turret rotation
if self.number == 1: # only for tank2
self.aim_at_player() # default aim at player0
else:
if pressedkeys[self.turretLeftkey]:
self.turndirection += 1
if pressedkeys[self.turretRightkey]:
self.turndirection -= 1
#---------- tank rotation ---------
self.tankturndirection = 0 # reset left/right rotation
if pressedkeys[self.tankLeftkey]:
self.tankturndirection += 1
if pressedkeys[self.tankRightkey]:
self.tankturndirection -= 1
# ---------------- rotate tank ---------------
self.tankAngle += self.tankturndirection * self.tankTurnSpeed * seconds # time-based turning of tank
# angle etc from Tank (boss)
oldcenter = self.rect.center
oldrect = self.image.get_rect() # store current surface rect
self.image = pygame.transform.rotate(self.image0, self.tankAngle)
self.rect = self.image.get_rect()
self.rect.center = oldcenter
# if tank is rotating, turret is also rotating with tank !
# -------- turret autorotate ----------
self.turretAngle += self.tankturndirection * self.tankTurnSpeed * seconds + self.turndirection * self.turretTurnSpeed * seconds # time-based turning
# ---------- fire cannon -----------
if (self.firestatus ==0) and (self.ammo > 0):
if pressedkeys[self.firekey]:
self.firestatus = Tank.recoiltime # seconds until tank can fire again
Bullet(self)
self.ammo -= 1
#self.msg = "player%i: ammo: %i/%i keys: %s" % (self.number+1, self.ammo, self.mgammo, Tank.msg[self.number])
#Text.book[self.number].changemsg(self.msg)
# -------- fire bow mg ---------------
if (self.mgfirestatus ==0) and (self.mgammo >0):
if pressedkeys[self.mgfirekey]:
self.mgfirestatus = Tank.mgrecoiltime
Tracer(self, False) # turret mg = False
self.mgammo -= 1
#self.msg = "player%i: ammo: %i/%i keys: %s" % (self.number+1, self.ammo, self.mgammo, Tank.msg[self.number])
#Text.book[self.number].changemsg(self.msg)
# -------- fire turret mg ---------------
if (self.mg2firestatus ==0) and (self.mgammo >0):
if pressedkeys[self.mg2firekey]:
self.mg2firestatus = Tank.mgrecoiltime # same recoiltime for both mg's
Tracer(self, True) # turret mg = True
self.mgammo -= 1
#self.msg = "player%i: ammo: %i/%i keys: %s" % (self.number+1, self.ammo, self.mgammo, Tank.msg[self.number])
#Text.book[self.number].changemsg(self.msg)
# ---------- movement ------------
self.dx = 0
self.dy = 0
self.forward = 0 # movement calculator
if pressedkeys[self.forwardkey]:
self.forward += 1
if pressedkeys[self.backwardkey]:
self.forward -= 1
# if both are pressed togehter, self.forward becomes 0
if self.forward == 1:
self.dx = math.cos(degrees_to_radians(self.tankAngle)) * self.movespeed
self.dy = -math.sin(degrees_to_radians(self.tankAngle)) * self.movespeed
if self.forward == -1:
self.dx = -math.cos(degrees_to_radians(self.tankAngle)) * self.movespeed
self.dy = math.sin(degrees_to_radians(self.tankAngle)) * self.movespeed
# ------------- check border collision ---------------------
self.pos[0] += self.dx * seconds
self.pos[1] += self.dy * seconds
if self.pos[1] + self.side/2 >= Config.height:
self.pos[1] = Config.height - self.side/2
self.dy = 0 # crash into border
elif self.pos[1] -self.side/2 <= 0:
self.pos[1] = 0 + self.side/2
self.dy = 0
self.rect.centerx = round(self.pos[0], 0) #x
self.rect.centery = round(self.pos[1], 0) #y
self.msg = "tank%i: x:%i y:%i facing: turret:%i tank:%i" % (self.number, self.pos[0], self.pos[1], self.turretAngle, self.tankAngle )
Text.book[self.number].changemsg(self.msg)
def aim_at_player(self, targetnumber=0):
#print "my pos: x:%.1f y:%.1f " % ( self.pos[0], self.pos[1])
#print "his pos: x:%.1f y:%.1f " % ( Tank.book[0].pos[0], Tank.book[0].pos[1])
deltax = Tank.book[targetnumber].pos[0] - self.pos[0]
deltay = Tank.book[targetnumber].pos[1] - self.pos[1]
angle = math.atan2(-deltax, -deltay)/math.pi*180.0
diff = (angle - self.turretAngle - 90) %360 #reset at 360
if diff == 0:
self.turndirection = 0
elif diff > 180:
self.turndirection = 1
else:
self.turndirection = -1
return diff
class Turret(pygame.sprite.Sprite):
"""turret on top of tank"""
def __init__(self, boss):
pygame.sprite.Sprite.__init__(self, self.groups) # THE most important line !
self.boss = boss
self.side = self.boss.side
self.images = {} # how much recoil after shooting, reverse order of apperance
self.images[0] = self.draw_cannon(0) # idle position
self.images[1] = self.draw_cannon(1)
self.images[2] = self.draw_cannon(2)
self.images[3] = self.draw_cannon(3)
self.images[4] = self.draw_cannon(4)
self.images[5] = self.draw_cannon(5)
self.images[6] = self.draw_cannon(6)
self.images[7] = self.draw_cannon(7)
self.images[8] = self.draw_cannon(8) # position of max recoil
self.images[9] = self.draw_cannon(4)
self.images[10] = self.draw_cannon(0) # idle position
def update(self, seconds):
# painting the correct image of cannon
if self.boss.firestatus > 0:
self.image = self.images[int(self.boss.firestatus / (Tank.recoiltime / 10.0))]
else:
self.image = self.images[0]
# --------- rotating -------------
# angle etc from Tank (boss)
oldrect = self.image.get_rect() # store current surface rect
self.image = pygame.transform.rotate(self.image, self.boss.turretAngle)
self.rect = self.image.get_rect()
# ---------- move with boss ---------
self.rect = self.image.get_rect()
self.rect.center = self.boss.rect.center
def draw_cannon(self, offset):
# painting facing right, offset is the recoil
image = pygame.Surface((self.boss.side * 2,self.boss.side * 2)) # created on the fly
image.fill((128,128,128)) # fill grey
pygame.draw.circle(image, (255,0,0), (self.side,self.side), 22, 0) # red circle
pygame.draw.circle(image, (0,255,0), (self.side,self.side), 18, 0) # green circle
pygame.draw.rect(image, (255,0,0), (self.side-10, self.side + 10, 15,2)) # turret mg rectangle
pygame.draw.rect(image, (0,255,0), (self.side-20 - offset,self.side - 5, self.side - offset,10)) # green cannon
pygame.draw.rect(image, (255,0,0), (self.side-20 - offset,self.side - 5, self.side - offset,10),1) # red rect
image.set_colorkey((128,128,128))
return image
# ---------------- End of>022_minimap.py
demo of tank game with rotating turrets and minimap
url: http://thepythongamebook.com/en:part2:pygame:step022
author: horst.jens@spielend-programmieren.at
licence: gpl, see http://www.gnu.org/licenses/gpl.html
the minimap displays tanks, traces and bullets even for elements
currently not visible on the playfield.
works with python3.4 and python2.7
"""
#the next line is only needed for python2.x and not necessary for python3.x
from __future__ import print_function, division
import pygame
import random
import math
GRAD = math.pi / 180 # 2 * pi / 360 # math module needs Radiant instead of Grad
class Config(object):
""" a> Bullet.maxlifetime:
self.kill()
# ------ calculate movement --------
self.pos[0] += self.dx * seconds
self.pos[1] += self.dy * seconds
# ----- kill if out of screen
if self.pos[0] < 0:
self.kill()
elif self.pos[0] > Config.bigmapwidth:
self.kill()
if self.pos[1] < 0:
self.kill()
elif self.pos[1] > Config.bigmapheight:
self.kill()
#------- move -------
self.rect.centerx = round(self.pos[0] - Config.cornerpoint[0],0)
self.rect.centery = round(self.pos[1] - Config.cornerpoint[1],0)
class Tracer(Bullet):
"""Tracer is nearly the same as Bullet, but smaller
and with another origin (bow MG rect instead cannon.
Tracer inherits all methods of Bullet, but i overwrite
calculate_heading and calculate_origin"""
side = 15 # long side of bullet rectangle
vel = 200 # velocity
mass = 10
color = (200,0,100)
maxlifetime = 10.0 # seconds
def __init__(self, boss, turret=False):
self.turret = turret
Bullet.__init__(self,boss ) # this line is important
self.tracer = True
def calculate_heading(self):
"""overwriting the method because there are some differences
between a tracer and a main gun bullet"""
self.radius = Tracer.side # for collision detection
self.angle = 0
self.angle += self.boss.tankAngle
if self.turret:
self.angle = self.boss.turretAngle
self.mass = Tracer.mass
self.vel = Tracer.vel
image = pygame.Surface((Tracer.side, Tracer.side // 4)) # a line
image.fill(self.boss.color) # fill yellow ?
pygame.draw.rect(image, (0,0,0), (Tracer.side * .75, 0, Tracer.side, Tracer.side // 4)) # red dot at front
image.set_colorkey((128,128,128)) # grey transparent
self.image0 = image.convert_alpha()
self.image = pygame.transform.rotate(self.image0, self.angle)
self.rect = self.image.get_rect()
if self.turret:
# turret mg
self.dx = math.cos(degrees_to_radians(self.boss.turretAngle)) * self.vel
self.dy = math.sin(degrees_to_radians(-self.boss.turretAngle)) * self.vel
else:
# bow mg
self.dx = math.cos(degrees_to_radians(self.boss.tankAngle)) * self.vel
self.dy = math.sin(degrees_to_radians(-self.boss.tankAngle)) * self.vel
def calculate_origin(self):
"""overwriting because another point of origin is needed"""
# - spawn bullet at end of machine gun muzzle (bow or turret)
if self.turret:
self.pos[0] += math.cos(degrees_to_radians(-90+self.boss.turretAngle)) * 15
self.pos[1] += math.sin(degrees_to_radians(90-self.boss.turretAngle)) * 15
else:
self.pos[0] += math.cos(degrees_to_radians(30+self.boss.tankAngle)) * (Tank.side/2)
self.pos[1] += math.sin(degrees_to_radians(-30-self.boss.tankAngle)) * (Tank.side/2)
class Radarmap(pygame.sprite.Sprite):
"""a> 10:
pygame.draw.rect(image, self.color, (5,5,self.side-10, self.side-10)) #tank body, margin 5
pygame.draw.rect(image, (90,90,90), (0,0,self.side//6, self.side)) # track left
pygame.draw.rect(image, (90,90,90), (self.side-self.side//6, 0, self.side,self.side)) # right track
pygame.draw.rect(image, (255,0,0), (self.side//6+5 , 10, 10, 5)) # red bow rect left
#pygame.draw.rect(image, (255,0,0), (self.side/2 - 5, 10, 10, 5)) # red bow rect middle
pygame.draw.circle(image, (255,0,0), (self.side//2,self.side//2), self.side//3 , 2) # red circle for turret
image = pygame.transform.rotate(image,-90) # rotate so to look east
self.image0 = image.convert_alpha()
self.image = image.convert_alpha()
self.rect = self.image0.get_rect()
#---------- turret ------------------
self.firestatus = 0.0 # time left until cannon can fire again
self.mgfirestatus = 0.0 # time until mg can fire again
self.mg2firestatus = 0.0 # time until turret mg can fire again
self.turndirection = 0 # for turret
self.tankturndirection = 0
self.movespeed = Tank.movespeed
self.turretTurnSpeed = Tank.turretTurnSpeed
self.tankTurnSpeed = Tank.tankTurnSpeed
Turret(self) # create a Turret for this tank
def update(self, seconds): # no need for seconds but the other sprites need it
#-------- reloading, firestatus----------
if self.firestatus > 0:
self.firestatus -= seconds # cannon will soon be ready again
if self.firestatus <0:
self.firestatus = 0 #avoid negative numbers
if self.mgfirestatus > 0:
self.mgfirestatus -= seconds # bow mg will soon be ready again
if self.mgfirestatus <0:
self.mgfirestatus = 0 #avoid negative numbers
if self.mg2firestatus > 0:
self.mg2firestatus -= seconds # turret mg will soon be ready again
if self.mg2firestatus <0:
self.mg2firestatus = 0 #avoid negative numbers
# ------------ keyboard --------------
pressedkeys = pygame.key.get_pressed()
# -------- turret manual rotate ----------
self.turndirection = 0 # left / right turret rotation
if self.number == 1: # only for tank2
self.aim_at_player() # default aim at player0
else:
if pressedkeys[self.turretLeftkey]:
self.turndirection += 1
if pressedkeys[self.turretRightkey]:
self.turndirection -= 1
#---------- tank rotation ---------
self.tankturndirection = 0 # reset left/right rotation
if pressedkeys[self.tankLeftkey]:
self.tankturndirection += 1
if pressedkeys[self.tankRightkey]:
self.tankturndirection -= 1
# ---------------- rotate tank ---------------
self.tankAngle += self.tankturndirection * self.tankTurnSpeed * seconds # time-based turning of tank
# angle etc from Tank (boss)
oldcenter = self.rect.center
oldrect = self.image.get_rect() # store current surface rect
self.image = pygame.transform.rotate(self.image0, self.tankAngle)
self.rect = self.image.get_rect()
self.rect.center = oldcenter
# if tank is rotating, turret is also rotating with tank !
# -------- turret autorotate ----------
self.turretAngle += self.tankturndirection * self.tankTurnSpeed * seconds + self.turndirection * self.turretTurnSpeed * seconds # time-based turning
# ---------- fire cannon -----------
if (self.firestatus ==0) and (self.ammo > 0):
if pressedkeys[self.firekey]:
self.firestatus = Tank.recoiltime # seconds until tank can fire again
Bullet(self)
self.ammo -= 1
#self.msg = "player%i: ammo: %i/%i keys: %s" % (self.number+1, self.ammo, self.mgammo, Tank.msg[self.number])
#Text.book[self.number].changemsg(self.msg)
# -------- fire bow mg ---------------
if (self.mgfirestatus ==0) and (self.mgammo >0):
if pressedkeys[self.mgfirekey]:
self.mgfirestatus = Tank.mgrecoiltime
Tracer(self, False) # turret mg = False
self.mgammo -= 1
#self.msg = "player%i: ammo: %i/%i keys: %s" % (self.number+1, self.ammo, self.mgammo, Tank.msg[self.number])
#Text.book[self.number].changemsg(self.msg)
# -------- fire turret mg ---------------
if (self.mg2firestatus ==0) and (self.mgammo >0):
if pressedkeys[self.mg2firekey]:
self.mg2firestatus = Tank.mgrecoiltime # same recoiltime for both mg's
Tracer(self, True) # turret mg = True
self.mgammo -= 1
#self.msg = "player%i: ammo: %i/%i keys: %s" % (self.number+1, self.ammo, self.mgammo, Tank.msg[self.number])
#Text.book[self.number].changemsg(self.msg)
# ---------- movement ------------
self.dx = 0
self.dy = 0
self.forward = 0 # movement calculator
if pressedkeys[self.forwardkey]:
self.forward += 1
if pressedkeys[self.backwardkey]:
self.forward -= 1
# if both are pressed togehter, self.forward becomes 0
if self.forward == 1:
self.dx = math.cos(degrees_to_radians(self.tankAngle)) * self.movespeed
self.dy = -math.sin(degrees_to_radians(self.tankAngle)) * self.movespeed
if self.forward == -1:
self.dx = -math.cos(degrees_to_radians(self.tankAngle)) * self.movespeed
self.dy = math.sin(degrees_to_radians(self.tankAngle)) * self.movespeed
# ------------- check border collision ---------------------
self.pos[0] += self.dx * seconds
self.pos[1] += self.dy * seconds
if self.pos[0] + self.side/2 >= Config.bigmapwidth:
self.pos[0] = Config.bigmawidth - self.side/2
self.dx = 0 # crash into border
elif self.pos[0] -self.side/2 <= 0:
self.pos[0] = 0 + self.side/2
self.dx = 0
if self.pos[1] + self.side/2 >= Config.bigmapheight:
self.pos[1] = Config.bigmapheight - self.side/2
self.dy = 0 # crash into border
elif self.pos[1] -self.side/2 <= 0:
self.pos[1] = 0 + self.side/2
self.dy = 0
self.rect.centerx = round(self.pos[0] - Config.cornerpoint[0], 0) #x
self.rect.centery = round(self.pos[1] - Config.cornerpoint[1], 0) #y
#self.msg = "tank%i: x:%i y:%i facing: turret:%i tank:%i" % (self.number, self.pos[0], self.pos[1], self.turretAngle, self.tankAngle )
def aim_at_player(self, targetnumber=0):
deltax = Tank.book[targetnumber].pos[0] - self.pos[0]
deltay = Tank.book[targetnumber].pos[1] - self.pos[1]
angle = math.atan2(-deltax, -deltay)/math.pi*180.0
diff = (angle - self.turretAngle - 90) %360 #reset at 360
diff -= 180
# to avoid a jittering canon introduce a tolerance range of 4 degrees
if abs(diff) < 2:
self.turndirection = 0
elif diff > 0:
self.turndirection = 1
else:
self.turndirection = -1
# return diff
class Turret(pygame.sprite.Sprite):
"""turret on top of tank"""
def __init__(self, boss):
pygame.sprite.Sprite.__init__(self, self.groups) # THE most important line !
self.boss = boss
self.side = self.boss.side
self.images = {} # how much recoil after shooting, reverse order of apperance
self.images[0] = self.draw_cannon(0) # idle position
self.images[1] = self.draw_cannon(1)
self.images[2] = self.draw_cannon(2)
self.images[3] = self.draw_cannon(3)
self.images[4] = self.draw_cannon(4)
self.images[5] = self.draw_cannon(5)
self.images[6] = self.draw_cannon(6)
self.images[7] = self.draw_cannon(7)
self.images[8] = self.draw_cannon(8) # position of max recoil
self.images[9] = self.draw_cannon(4)
self.images[10] = self.draw_cannon(0) # idle position
def update(self, seconds):
# painting the correct image of cannon
if self.boss.firestatus > 0:
self.image = self.images[int(self.boss.firestatus // (Tank.recoiltime / 10.0))]
else:
self.image = self.images[0]
# --------- rotating -------------
# angle etc from Tank (boss)
oldrect = self.image.get_rect() # store current surface rect
self.image = pygame.transform.rotate(self.image, self.boss.turretAngle)
self.rect = self.image.get_rect()
# ---------- move with boss ---------
self.rect = self.image.get_rect()
self.rect.center = self.boss.rect.center
def draw_cannon(self, offset):
# painting facing right, offset is the recoil
image = pygame.Surface((self.boss.side * 2,self.boss.side * 2)) # created on the fly
image.fill((128,128,128)) # fill grey
pygame.draw.circle(image, (255,0,0), (self.side,self.side), 22, 0) # red circle
pygame.draw.circle(image, (0,255,0), (self.side,self.side), 18, 0) # green circle
pygame.draw.rect(image, (255,0,0), (self.side-10, self.side + 10, 15,2)) # turret mg rectangle
pygame.draw.rect(image, (0,255,0), (self.side-20 - offset,self.side - 5, self.side - offset,10)) # green cannon
pygame.draw.rect(image, (255,0,0), (self.side-20 - offset,self.side - 5, self.side - offset,10),1) # red rect
image.set_colorkey((128,128,128))
return image
# ---------------- End of> Config.bigmapwidth - Config.width:
Config.cornerpoint[0] = Config.bigmapwidth - Config.width
scrollx = 0
if Config.cornerpoint[1] < 0:
Config.cornerpoint[1] = 0
scrolly = 0
elif Config.cornerpoint[1] > Config.bigmapheight - Config.height:
Config.cornerpoint[1] = Config.bigmapheight - Config.height
scrolly = 0
pygame.display.set_caption("%s FPS: %.2f playtime: %.1f " % ( Config.title,clock.get_fps(), playtime))
#screen.blit(background, (0,0)) # delete all
if scrollx == 0 and scrolly == 0: # only necessery if there was no scrolling
allgroup.clear(screen, background) # funny effect if you outcomment this line
else:
background = bigmap.subsurface((Config.cornerpoint[0],
Config.cornerpoint[1],
Config.width,
Config.height)) # take snapshot of bigmap
screen.blit(background, (0,0))
allgroup.update(seconds)
allgroup.draw(screen)
pygame.display.flip() # flip the screen 30 times a second
return 0
if __name__ == '__main__':
main()
This Gist brought to you by gist-it.view rawpygame/022_minimap.py