PyGame
Ubuntu Opportunistic Developers Week March 2010 - Create games with PyGame - Rick Spencer - Mar 3 2010
(02:01:34 PM) rickspencer3: hi all (02:01:40 PM) rickspencer3: ready for some pygame stuff? (02:02:10 PM) rickspencer3: great (02:02:40 PM) rickspencer3: as usual, I got my notes ready before hand and stuck them on the wiki: (02:02:41 PM) rickspencer3: https://wiki.ubuntu.com/UbuntuOpportunisticDeveloperWeek/PyGameClassNotes (02:03:02 PM) rickspencer3: so I'll go through each section and take questions (02:03:24 PM) rickspencer3: Pygame is a library for making arcade style sprite games (02:03:34 PM) rickspencer3: It features very easy blitting and colision detection (02:03:43 PM) rickspencer3: I have some sample code to help with understanding pygame here: (02:03:51 PM) rickspencer3: https://code.edge.launchpad.net/~rick-rickspencer3/pygame-template/trunk (02:03:57 PM) rickspencer3: I think you can go: (02:04:39 PM) rickspencer3: bzr branch lp:pygame-template too (02:05:01 PM) rickspencer3: note that some of this code is a bit old, and I need to reformat it and such (02:05:03 PM) rickspencer3: but it (02:05:07 PM) rickspencer3: s failry readable (02:05:14 PM) rickspencer3: however, it will eventually be a quickly template, hopefully in time for lucid (02:05:20 PM) rickspencer3: if you want to grab that it might help you follow the class a bit, but it is not required (02:05:28 PM) rickspencer3: it comes with some basic game play that you can study and modify (02:05:37 PM) rickspencer3: essentially, it is kind of like asteroids (02:05:42 PM) rickspencer3: you control the black triangle in the middle, and there are two enemies (02:05:49 PM) rickspencer3: Of course you will want some docs (02:05:54 PM) rickspencer3: Pygame has good docs here: (02:05:59 PM) rickspencer3: http://www.pygame.org/docs/ (02:06:15 PM) rickspencer3: before we discuss how to set up a game (02:06:17 PM) rickspencer3: any questions? (02:06:38 PM) rickspencer3: ok, then let us rock (02:06:54 PM) rickspencer3: so, you're going to set up a game (02:06:55 PM) rickspencer3: first thing you need to do is set up a game surface (02:07:08 PM) rickspencer3: and do other initalizations (02:07:10 PM) rickspencer3: so .. (02:07:11 PM) rickspencer3: initialize pygame libraries (02:07:16 PM) rickspencer3: pygame.font.init() (02:07:17 PM) rickspencer3: pygame.mixer.init() (02:07:28 PM) rickspencer3: (assuming you are using fonts and sound of course) (02:07:36 PM) rickspencer3: then create a screen (02:07:43 PM) rickspencer3: the screen will be the surface on which your sprites will be drawn, essentially where your game will occur (02:07:50 PM) rickspencer3: screen = pygame.display.set_mode((800, 480)) (02:08:04 PM) rickspencer3: that's for an 800 x 400 pixel game (02:08:17 PM) rickspencer3: also, create a couple of variable you will need later (02:08:22 PM) rickspencer3: like a background image (02:08:29 PM) rickspencer3: background = pygame.image.load(path_to_background) (02:08:39 PM) rickspencer3: notice the image is a PyGame image (02:08:45 PM) rickspencer3: and a clock (02:08:50 PM) rickspencer3: clock = pygame.time.Clock() (02:08:59 PM) rickspencer3: you'll see why we need the clock next (02:09:07 PM) rickspencer3: pygame games use a good old fashioned input loop (02:09:20 PM) rickspencer3: don't know if you all ever programmed with an event loop (02:09:43 PM) rickspencer3: but it was very common for writing GUI apps back around when dinosaurs used computers (02:09:50 PM) rickspencer3: essentially, you tell your app to keep looping (02:09:58 PM) rickspencer3: and whenever it goes through the loop do a few things (02:10:03 PM) rickspencer3: 1. respond to user input (02:10:11 PM) rickspencer3: 2.update data for any sprites on the screen (02:10:18 PM) rickspencer3: this includes seeing if any sprites have collided (02:10:33 PM) rickspencer3: 3. redraw everything (02:10:39 PM) rickspencer3: let's look at setting up a game loop (02:10:46 PM) rickspencer3: while 1: (02:10:46 PM) rickspencer3: clock.tick(15) (02:10:46 PM) rickspencer3: if ControllerTick() == 0: (02:10:46 PM) rickspencer3: return (02:10:46 PM) rickspencer3: if not game.paused: (02:10:46 PM) rickspencer3: ViewTick() (02:10:48 PM) rickspencer3: if CheckCollisions() == 0: (02:10:50 PM) rickspencer3: return (02:10:57 PM) rickspencer3: you can see this code in the crashteroids file at line 169 (02:11:05 PM) rickspencer3: so first, the loop just runs and runs until etiher ControllerTick or CheckCollisions returns 0 (02:11:14 PM) rickspencer3: the first line in the loop basically says to have 15 frames per second (02:11:21 PM) rickspencer3: this is not like a sleep function where the argument says how long to sleep (02:11:30 PM) rickspencer3: make sure that you pick a number that is low enough that a lower powered computer can keep up, if you care about that kind of thing (02:11:40 PM) rickspencer3: The next line calls ControllerTick() (02:11:45 PM) rickspencer3: This function checks for user input, which I will explain in a moment, and the updates all the game data (02:11:57 PM) rickspencer3: this is a function that *you* write (02:12:03 PM) rickspencer3: it's not built into PyGame (02:12:15 PM) rickspencer3: then next in the loop (02:12:22 PM) rickspencer3: assuming that the game is not paused, it will go ahead and update the view and do collision detection (02:12:30 PM) rickspencer3: The reason that controllertick is always called is because this function controlls pausing and unpausing of the game (02:12:41 PM) rickspencer3: if you didn't call it every time through, the user couldn't unpause (02:12:49 PM) rickspencer3: we'll look at each of these functions in more detail (02:12:56 PM) rickspencer3: but first, questions? (02:13:10 PM) ClassBot: mhall119 asked: what file is that in? (02:13:25 PM) rickspencer3: it's in crashteroids/bin/crashteroids (02:13:36 PM) ClassBot: ryzrecreel asked: What kind of image formats can you use? (02:13:39 PM) rickspencer3: dunno for sure (02:13:43 PM) rickspencer3: I always use png (02:14:02 PM) rickspencer3: ok, no more questions, so moving on (02:14:13 PM) rickspencer3: let's start by handling user input (02:14:20 PM) rickspencer3: pygame basically queues up input events (02:14:26 PM) rickspencer3: you can then loop through them and respond to each event (02:14:36 PM) rickspencer3: at line 93, you can see how the loop is set up (02:14:41 PM) rickspencer3: for event in pygame.event.get(): (02:14:41 PM) rickspencer3: if event.type == pygame.QUIT: (02:14:41 PM) rickspencer3: return 0 (02:14:51 PM) rickspencer3: pygame.QUIT is called when the window closes or such (02:14:57 PM) rickspencer3: of course you could also do something smart and save game state or such (02:15:04 PM) rickspencer3: but this code returns 0, which causes the main event loop to quit (02:15:13 PM) rickspencer3: now, let's say what I want to do is make the ship go forward when the user presses the j key (02:15:25 PM) rickspencer3: (which is what crashteroids does by default) (02:15:36 PM) rickspencer3: first I ask what kind of event it was (02:15:44 PM) rickspencer3: if event.type == pygame.KEYDOWN: (02:15:55 PM) rickspencer3: yup, I want to respond to key down events (02:16:06 PM) rickspencer3: so this I'll write some code here (02:16:10 PM) rickspencer3: and then if it was a key event, what key was pressed (02:16:17 PM) rickspencer3: if event.key == pygame.K_l: (02:16:18 PM) rickspencer3: #do something (02:16:25 PM) rickspencer3: oops (02:16:31 PM) rickspencer3: that's for l key :) (02:16:41 PM) rickspencer3: I guess l is for move, and j is to shoot (02:17:06 PM) rickspencer3: in any case, you can probably guess that the correct event.key is pygame.k_j to respond to the j key (02:17:07 PM) rickspencer3: ;) (02:17:12 PM) rickspencer3: you can see the whole tree of if, else statements starting at line 89 (02:17:23 PM) rickspencer3: there are also events for the mouse and joysticks even (02:17:29 PM) rickspencer3: so it's easy to create games that aren't only using the keyboard if you want (02:17:34 PM) rickspencer3: so that's how to handle input (02:17:39 PM) rickspencer3: let's look at sprites, but first, any questions? (02:18:08 PM) ClassBot: mhall119 asked: any kind of vector graphics? (02:18:17 PM) rickspencer3: could be, but PyGame is really sprite based (02:18:42 PM) rickspencer3: vector graphics is how games like asteroids and battlezone, and starcastle and stuff were created (02:18:50 PM) rickspencer3: but sprites are when you use images (02:19:05 PM) ClassBot: M49 asked: Does pygame have any dialog for key configuration? (02:19:09 PM) rickspencer3: not that I know of (02:19:22 PM) rickspencer3: that would be a nice thing to add to a quickly template though, wouldn't it (02:19:32 PM) rickspencer3: I would guess that you would just use PyGtk for this (02:19:40 PM) rickspencer3: create it yourself (02:19:50 PM) rickspencer3: ok (02:19:55 PM) rickspencer3: let's move on to sprites (02:20:08 PM) rickspencer3: A sprite is basically an image in an arcade game (02:20:13 PM) rickspencer3: pygame handles blitting for you, which is quite nice (02:20:17 PM) rickspencer3: I'll discuss blitting in a bit (02:20:29 PM) rickspencer3: If you wrote games long ago, you may remember rendering a sprite strip to an offscreen graphics world (02:20:37 PM) rickspencer3: and then selecting bits of that strip and copying them to the onscreen graphics world (02:20:48 PM) rickspencer3: pygame does not reqiure that at all (02:20:55 PM) rickspencer3: it is much more fun and easy (02:21:03 PM) rickspencer3: you just subclass the Sprite class (02:21:38 PM) rickspencer3: one key thing a sprite has is an Pygame.Image member, called image (02:21:44 PM) rickspencer3: I usually like to keep the original image around, as if you keep rotating the image and such, it will get more and more distorted over time (02:22:08 PM) rickspencer3: so when I create a sprite object, I set up the image member like this: (02:22:14 PM) rickspencer3: self.masterImage = pygame.image.load(path_to_image_file) (02:22:14 PM) rickspencer3: self.image = self.masterImage (02:22:30 PM) rickspencer3: this is all in the BastSprite class, btw, if you want to see the code in situ (02:22:37 PM) rickspencer3: a sprite also has a "virtual" function, called update (02:22:43 PM) rickspencer3: update is supposed to be called to tell a sprite to update it's data for a tick (02:22:50 PM) rickspencer3: you set things like the sprite's x, y coordinates in this function, and such (02:22:59 PM) rickspencer3: all your sprites should use this because you can then add a sprite to a RenderUpdates sprite group (02:23:23 PM) rickspencer3: don't add your own custom named method to do this, or you will lose a lot of benefits of PyGame (02:23:42 PM) rickspencer3: call update for that group, which will call update for each sprite (02:23:48 PM) rickspencer3: you create a sprite group like this: (02:23:54 PM) rickspencer3: enemies = pygame.sprite.RenderUpdates() (02:24:01 PM) rickspencer3: and then you can add an sprite to that group (02:24:05 PM) rickspencer3: enemies.add(create_enemy()) (02:24:13 PM) rickspencer3: you can also empty a sprite group (02:24:17 PM) rickspencer3: enemies.empty() (02:24:24 PM) rickspencer3: useful when you are resetting a level or something (02:24:29 PM) rickspencer3: as we'll see later, sprite groups also help lots with collision detection (02:24:43 PM) rickspencer3: back to sprites though (02:24:47 PM) rickspencer3: so you have an image on a sprite (02:24:53 PM) rickspencer3: you also need to define an x and y for your sprite (02:25:03 PM) rickspencer3: I in my sprite subclass, I might set x and y randomly like this: (02:25:03 PM) rickspencer3: self.x = random.randint(0,300) (02:25:03 PM) rickspencer3: self.y = random.randint(0,140) (02:25:14 PM) rickspencer3: (only when the sprite is created, of course) (02:25:22 PM) rickspencer3: but then on each call to update(), just change the x and/or y if the sprite is moving (02:25:31 PM) rickspencer3: sprites also have a "kill()" member function (02:25:38 PM) rickspencer3: calling kill causes the sprite to be removed from the playing surface (02:25:43 PM) rickspencer3: questions before we talk about updating the display? (02:26:14 PM) ClassBot: mhall119 asked: can you change the image on update? (02:26:17 PM) rickspencer3: yes (02:26:20 PM) rickspencer3: absolutely (02:26:30 PM) rickspencer3: you can also rotate it and other things (02:26:38 PM) rickspencer3: my sprites usually have an "explode" method (02:27:00 PM) rickspencer3: which causes a stage of an explosion to be displayed with each tick in update (02:27:20 PM) rickspencer3: so if you look at crashteroids, changing images on update is how I get the explosion effect (02:27:31 PM) ClassBot: mhall119 asked: Like, to animate your sprite (02:27:34 PM) rickspencer3: totally (02:27:38 PM) rickspencer3: like if you had a tank (02:27:44 PM) rickspencer3: and it was going to shoot a missle (02:28:06 PM) rickspencer3: you could have swap in images that make it look like a little guy loading the turret or something (02:28:18 PM) ClassBot: mhall119 asked: can you layer images to make a single sprite image? (02:28:21 PM) rickspencer3: hmmm (02:28:23 PM) rickspencer3: not that I know of (02:28:40 PM) rickspencer3: I don't think a sprite can composit images like that (02:29:13 PM) rickspencer3: ok (02:29:14 PM) rickspencer3: so now you have your event loop running, your game surface set up, and some sprites created (02:29:27 PM) rickspencer3: now we need to actually draw the sprites (02:29:34 PM) rickspencer3: for sprites that are not in a sprite group, we need to blit them (02:29:41 PM) rickspencer3: fortunately this does not involve the crazy gworld hackery of days of hold (02:29:48 PM) rickspencer3: pygame gives us a blit method (02:29:52 PM) rickspencer3: blitting is essentially the act of repainting parts of the screen that have changed (02:29:58 PM) rickspencer3: without blitting, the whole screen repaints and appears to flash (02:30:03 PM) rickspencer3: so to blit the blackgroud, we'll use the screen to blit (02:30:13 PM) rickspencer3: remember when we created teh screen object? now it's time to use it (02:30:20 PM) rickspencer3: screen.blit(background, [0,0]) (02:30:33 PM) rickspencer3: I think that blitting the background like this basically repaints the whole background image over everything (02:30:46 PM) rickspencer3: if you are tuning for a very slow platform, or a game with lots and lots of sprites, you may not want do it this way or you get flashing (02:30:57 PM) rickspencer3: in those cases you might need to track the parts of the background exposed by moving sprites (02:31:03 PM) rickspencer3: this is a bit of work, but probably will not be necessary for you to do (02:31:11 PM) rickspencer3: I certainly have never run into trouble with this (02:31:12 PM) mohi1 is now known as mohi_ (02:31:26 PM) rickspencer3: for all I know, blit() is smart enough to not cause flashing (02:31:31 PM) mohi_ is now known as mohi1 (02:31:32 PM) rickspencer3: to blit an individual sprite, you have to tell the screen the image to use and the rect, or xy coords and dimensions, for hte sprite to blit (02:31:41 PM) rickspencer3: So if we have a sprite object called "g", we blit it like this: (02:31:47 PM) rickspencer3: screen.blit(g.image, g.rect) (02:31:56 PM) rickspencer3: sprites in a sprite group are a bit easier to blit (02:32:01 PM) rickspencer3: you can tell the sprite group to blit them all: (02:32:06 PM) rickspencer3: enemies.draw(screen) (02:32:11 PM) rickspencer3: this blits all the enemies for you (02:32:25 PM) rickspencer3: we do this each time through the loop (02:33:03 PM) rickspencer3: in ViewTick() (02:33:10 PM) rickspencer3: question before we discuss collision detection? (02:33:25 PM) ClassBot: w1nGNUtz asked: will you explain anitmations? I'd like to understand how to animate an sprite using the clock (02:33:28 PM) rickspencer3: yeah (02:33:39 PM) rickspencer3: so let's say you want have an animation that shows a guy walking (02:33:54 PM) rickspencer3: create enough images to show taking a step smoothly (02:34:05 PM) rickspencer3: I'll guess it will take 8 frames to show smooth step (02:34:13 PM) rickspencer3: in the sprites update method (02:34:28 PM) rickspencer3: swap in the next image each time through (02:34:31 PM) rickspencer3: for example (02:34:37 PM) rickspencer3: when I show a guy exploding (02:34:49 PM) rickspencer3: I have 8 frames or so showing the explosion (02:35:05 PM) rickspencer3: named ship_explode_1.png to ship_explode_8.png (02:35:21 PM) rickspencer3: (or named similary, I forget exactly what I named them) (02:35:34 PM) rickspencer3: then I keep track in the update method of a counting variable (02:35:42 PM) rickspencer3: let me get the exact code, hold on (02:36:13 PM) rickspencer3: if self.exploding: (02:36:13 PM) rickspencer3: #do an explosion image for each tick (02:36:13 PM) rickspencer3: self.explodestage += 1 (02:36:13 PM) rickspencer3: e = self.explodestage (02:36:13 PM) rickspencer3: if e < 8:#there are 7 explosion images (02:36:13 PM) rickspencer3: e = str(e) (02:36:15 PM) rickspencer3: self.masterImage = pygame.image.load(crashteroidsconfig.guy_explode_stage + e + ".png") (02:36:17 PM) rickspencer3: self.updateImage() (02:36:45 PM) rickspencer3: updateImage is a function I wrote that rotates the image and does some other stuff (02:37:18 PM) rickspencer3: but essentially sets the sprites image member, like this: (02:37:18 PM) rickspencer3: self.image = pygame.transform.rotate(self.masterImage,self.orientation) (02:37:39 PM) rickspencer3: (if you weren't doing rotation and stuff, you could just assign the new image directly) (02:37:57 PM) ClassBot: mhall119 asked: does pygame offer any kind of "damage" data to tell what parts of the screen should be repainted? (02:38:02 PM) rickspencer3: I don't know (02:38:17 PM) rickspencer3: I always let the sprites blit() method take care of that for me (02:38:24 PM) rickspencer3: I don't think you'll to track this (02:38:39 PM) rickspencer3: back in the day, you would always track the previous rect for the sprites location (02:38:52 PM) rickspencer3: so that you could repaint it with the background (02:39:01 PM) rickspencer3: but I don't think PyGame requires this (02:39:22 PM) ClassBot: gooomba asked: Is pygame also suited for non-arcade games like chess for example? (02:39:25 PM) rickspencer3: yes (02:39:36 PM) rickspencer3: it wuold be good for board games for sure (02:40:09 PM) rickspencer3: ok, so collision detection (02:40:14 PM) rickspencer3: so let's say when an enemy rams our guy, we want both to be killed (02:40:24 PM) rickspencer3: but how to tell when an ememy hits? (02:40:32 PM) rickspencer3: detecting overlapping sprites is called "collision detection" and is handled quite well by PyGame (02:40:36 PM) rickspencer3: this saves you tons of work (02:40:57 PM) rickspencer3: (believe me, tracking rects and writing code to detect overlapping rects is not fun or easy!) (02:41:03 PM) rickspencer3: you can detect if two sprites have collided using pygame.sprite.spritecollide (), collideany, or groupcollide (02:41:13 PM) rickspencer3: so let's see if our guy, called g, has collided with an enemy (02:41:13 PM) rickspencer3: e = pygame.sprite.spritecollideany(g, enemies) (02:41:29 PM) rickspencer3: call this each tick (02:41:47 PM) rickspencer3: I always have specific function to handle collision detection (02:41:56 PM) rickspencer3: if the guy has not collided with any enemies, e will be None (02:42:02 PM) rickspencer3: but when e is not None, that means boom (02:42:05 PM) rickspencer3: "boom" (02:42:10 PM) rickspencer3: let's say your guys shoots bullets (02:42:15 PM) rickspencer3: those bullets will most likely be managed in a sprite group (02:42:21 PM) rickspencer3: so we should also see if any enemies were killed by bullets (02:42:26 PM) rickspencer3: hits_dict = pygame.sprite.groupcollide(bullets, enemies, False, False) (02:42:41 PM) rickspencer3: since we are asking for a collision between groups of sprites (02:42:46 PM) rickspencer3: here you get back a dictionary (02:42:50 PM) rickspencer3: the keys are sprites from the first sprite group (02:42:54 PM) rickspencer3: and the values are keys from the second sprite group (02:42:59 PM) rickspencer3: so then we can just go through and kill each bullet and enemy for the collision: (02:43:06 PM) rickspencer3: for b in hits_dict: (02:43:07 PM) rickspencer3: for e in hits_dict[b]: (02:43:07 PM) rickspencer3: e.kill() (02:43:07 PM) rickspencer3: b.kill() (02:43:17 PM) rickspencer3: ok, next and last section is on playing sounds (02:43:21 PM) rickspencer3: any questions first? (02:43:33 PM) ClassBot: mhall119 asked: does pygame have any kind of scene-graph, to animate sprites without writing a whole bunch of python code? (02:43:44 PM) rickspencer3: if I know what that meant, I would give you a pithy answer (02:43:54 PM) rickspencer3: unfortunately, I have to say "I don't know" (02:44:06 PM) ClassBot: M49 asked: Does it track only bounding boxes or does it use trasparency information from sprites? (02:44:13 PM) rickspencer3: the bounding box one (02:44:22 PM) rickspencer3: actually, you can set the rect for the sprite specifically (02:44:38 PM) rickspencer3: I often set the rect for the sprite to be slightly smaller than the image itself (02:44:49 PM) rickspencer3: this give a nice effect where two sprites can just touch (02:44:52 PM) rickspencer3: and not explode (02:44:59 PM) rickspencer3: it gives a nice "near miss" effect (02:45:18 PM) rickspencer3: it also lets you tune each rect to different shaped sprites (02:45:30 PM) rickspencer3: note that the rect is only used for collision detection so far as I know (02:45:47 PM) rickspencer3: so the whole image will always be displayed (02:46:25 PM) rickspencer3: ok, finally, sounds (02:46:26 PM) rickspencer3: we use the Pygame.mixer module to manage sounds (02:46:31 PM) rickspencer3: we need to initialize it before we use it: (02:46:34 PM) rickspencer3: pygame.mixer.init() (02:46:43 PM) rickspencer3: now I'll load an explosion sound to play when my guy explodes: (02:46:49 PM) rickspencer3: self.explosionSound = pygame.mixer.Sound(path_to_sound_file) (02:47:03 PM) rickspencer3: let's assume this sound is a bit loud, we can adjust the volume as we like: (02:47:07 PM) rickspencer3: self.explosionSound.set_volume(.2) (02:47:16 PM) rickspencer3: then we can play the sound: (02:47:21 PM) rickspencer3: self.explosionSound.play() (02:47:28 PM) rickspencer3: we can stop it too if it's going too long: (02:47:38 PM) rickspencer3: self.explosionSound.stop() (02:47:46 PM) rickspencer3: the mixer module is quite rich (02:47:52 PM) rickspencer3: there are channels, fades, and all kinds of stuff (02:48:01 PM) rickspencer3: so (02:48:12 PM) rickspencer3: that was the last "chapter" for the class (02:48:14 PM) rickspencer3: we're a bit early (02:48:17 PM) rickspencer3: any questions? (02:48:30 PM) ClassBot: gooomba asked: what are the "False"-arguments in groupcollide() for? (02:48:37 PM) rickspencer3: shucks, I forget (02:48:41 PM) ***rickspencer3 looks at docs (02:49:42 PM) rickspencer3: huh (02:49:43 PM) rickspencer3: http://www.pygame.org/docs/ref/sprite.html#pygame.sprite.groupcollide (02:49:48 PM) rickspencer3: no longer seems part of the API (02:49:58 PM) rickspencer3: I guess I can just remove that from the template (02:50:10 PM) ClassBot: mhall119 asked: what audio formats does mixer support? (02:50:14 PM) rickspencer3: I know ogg and wav (02:50:19 PM) rickspencer3: don't know what else, sorry
MeetingLogs/OpWeek1003/PyGame (last edited 2010-03-03 19:59:59 by pool-71-182-100-128)