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()

easygui menu

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