1#!/usr/bin/env python 2""" pygame.examples.aliens 3 4Shows a mini game where you have to defend against aliens. 5 6What does it show you about pygame? 7 8* pg.sprite, the difference between Sprite and Group. 9* dirty rectangle optimization for processing for speed. 10* music with pg.mixer.music, including fadeout 11* sound effects with pg.Sound 12* event processing, keyboard handling, QUIT handling. 13* a main loop frame limited with a game clock from pg.time.Clock 14* fullscreen switching. 15 16 17Controls 18-------- 19 20* Left and right arrows to move. 21* Space bar to shoot 22* f key to toggle between fullscreen. 23 24""" 25 26import random 27import os 28 29# import basic pygame modules 30import pygame as pg 31 32# see if we can load more than standard BMP 33if not pg.image.get_extended(): 34 raise SystemExit("Sorry, extended image module required") 35 36 37# game constants 38MAX_SHOTS = 2 # most player bullets onscreen 39ALIEN_ODDS = 22 # chances a new alien appears 40BOMB_ODDS = 60 # chances a new bomb will drop 41ALIEN_RELOAD = 12 # frames between new aliens 42SCREENRECT = pg.Rect(0, 0, 640, 480) 43SCORE = 0 44 45main_dir = os.path.split(os.path.abspath(__file__))[0] 46 47 48def load_image(file): 49 """loads an image, prepares it for play""" 50 file = os.path.join(main_dir, "data", file) 51 try: 52 surface = pg.image.load(file) 53 except pg.error: 54 raise SystemExit('Could not load image "%s" %s' % (file, pg.get_error())) 55 return surface.convert() 56 57 58def load_sound(file): 59 """because pygame can be be compiled without mixer.""" 60 if not pg.mixer: 61 return None 62 file = os.path.join(main_dir, "data", file) 63 try: 64 sound = pg.mixer.Sound(file) 65 return sound 66 except pg.error: 67 print("Warning, unable to load, %s" % file) 68 return None 69 70 71# Each type of game object gets an init and an update function. 72# The update function is called once per frame, and it is when each object should 73# change it's current position and state. 74# 75# The Player object actually gets a "move" function instead of update, 76# since it is passed extra information about the keyboard. 77 78 79class Player(pg.sprite.Sprite): 80 """Representing the player as a moon buggy type car.""" 81 82 speed = 10 83 bounce = 24 84 gun_offset = -11 85 images = [] 86 87 def __init__(self): 88 pg.sprite.Sprite.__init__(self, self.containers) 89 self.image = self.images[0] 90 self.rect = self.image.get_rect(midbottom=SCREENRECT.midbottom) 91 self.reloading = 0 92 self.origtop = self.rect.top 93 self.facing = -1 94 95 def move(self, direction): 96 if direction: 97 self.facing = direction 98 self.rect.move_ip(direction * self.speed, 0) 99 self.rect = self.rect.clamp(SCREENRECT) 100 if direction < 0: 101 self.image = self.images[0] 102 elif direction > 0: 103 self.image = self.images[1] 104 self.rect.top = self.origtop - (self.rect.left // self.bounce % 2) 105 106 def gunpos(self): 107 pos = self.facing * self.gun_offset + self.rect.centerx 108 return pos, self.rect.top 109 110 111class Alien(pg.sprite.Sprite): 112 """An alien space ship. That slowly moves down the screen.""" 113 114 speed = 13 115 animcycle = 12 116 images = [] 117 118 def __init__(self): 119 pg.sprite.Sprite.__init__(self, self.containers) 120 self.image = self.images[0] 121 self.rect = self.image.get_rect() 122 self.facing = random.choice((-1, 1)) * Alien.speed 123 self.frame = 0 124 if self.facing < 0: 125 self.rect.right = SCREENRECT.right 126 127 def update(self): 128 self.rect.move_ip(self.facing, 0) 129 if not SCREENRECT.contains(self.rect): 130 self.facing = -self.facing 131 self.rect.top = self.rect.bottom + 1 132 self.rect = self.rect.clamp(SCREENRECT) 133 self.frame = self.frame + 1 134 self.image = self.images[self.frame // self.animcycle % 3] 135 136 137class Explosion(pg.sprite.Sprite): 138 """An explosion. Hopefully the Alien and not the player!""" 139 140 defaultlife = 12 141 animcycle = 3 142 images = [] 143 144 def __init__(self, actor): 145 pg.sprite.Sprite.__init__(self, self.containers) 146 self.image = self.images[0] 147 self.rect = self.image.get_rect(center=actor.rect.center) 148 self.life = self.defaultlife 149 150 def update(self): 151 """called every time around the game loop. 152 153 Show the explosion surface for 'defaultlife'. 154 Every game tick(update), we decrease the 'life'. 155 156 Also we animate the explosion. 157 """ 158 self.life = self.life - 1 159 self.image = self.images[self.life // self.animcycle % 2] 160 if self.life <= 0: 161 self.kill() 162 163 164class Shot(pg.sprite.Sprite): 165 """a bullet the Player sprite fires.""" 166 167 speed = -11 168 images = [] 169 170 def __init__(self, pos): 171 pg.sprite.Sprite.__init__(self, self.containers) 172 self.image = self.images[0] 173 self.rect = self.image.get_rect(midbottom=pos) 174 175 def update(self): 176 """called every time around the game loop. 177 178 Every tick we move the shot upwards. 179 """ 180 self.rect.move_ip(0, self.speed) 181 if self.rect.top <= 0: 182 self.kill() 183 184 185class Bomb(pg.sprite.Sprite): 186 """A bomb the aliens drop.""" 187 188 speed = 9 189 images = [] 190 191 def __init__(self, alien): 192 pg.sprite.Sprite.__init__(self, self.containers) 193 self.image = self.images[0] 194 self.rect = self.image.get_rect(midbottom=alien.rect.move(0, 5).midbottom) 195 196 def update(self): 197 """called every time around the game loop. 198 199 Every frame we move the sprite 'rect' down. 200 When it reaches the bottom we: 201 202 - make an explosion. 203 - remove the Bomb. 204 """ 205 self.rect.move_ip(0, self.speed) 206 if self.rect.bottom >= 470: 207 Explosion(self) 208 self.kill() 209 210 211class Score(pg.sprite.Sprite): 212 """to keep track of the score.""" 213 214 def __init__(self): 215 pg.sprite.Sprite.__init__(self) 216 self.font = pg.font.Font(None, 20) 217 self.font.set_italic(1) 218 self.color = pg.Color("white") 219 self.lastscore = -1 220 self.update() 221 self.rect = self.image.get_rect().move(10, 450) 222 223 def update(self): 224 """We only update the score in update() when it has changed.""" 225 if SCORE != self.lastscore: 226 self.lastscore = SCORE 227 msg = "Score: %d" % SCORE 228 self.image = self.font.render(msg, 0, self.color) 229 230 231def main(winstyle=0): 232 # Initialize pygame 233 if pg.get_sdl_version()[0] == 2: 234 pg.mixer.pre_init(44100, 32, 2, 1024) 235 pg.init() 236 if pg.mixer and not pg.mixer.get_init(): 237 print("Warning, no sound") 238 pg.mixer = None 239 240 fullscreen = False 241 # Set the display mode 242 winstyle = 0 # |FULLSCREEN 243 bestdepth = pg.display.mode_ok(SCREENRECT.size, winstyle, 32) 244 screen = pg.display.set_mode(SCREENRECT.size, winstyle, bestdepth) 245 246 # Load images, assign to sprite classes 247 # (do this before the classes are used, after screen setup) 248 img = load_image("player1.gif") 249 Player.images = [img, pg.transform.flip(img, 1, 0)] 250 img = load_image("explosion1.gif") 251 Explosion.images = [img, pg.transform.flip(img, 1, 1)] 252 Alien.images = [load_image(im) for im in ("alien1.gif", "alien2.gif", "alien3.gif")] 253 Bomb.images = [load_image("bomb.gif")] 254 Shot.images = [load_image("shot.gif")] 255 256 # decorate the game window 257 icon = pg.transform.scale(Alien.images[0], (32, 32)) 258 pg.display.set_icon(icon) 259 pg.display.set_caption("Pygame Aliens") 260 pg.mouse.set_visible(0) 261 262 # create the background, tile the bgd image 263 bgdtile = load_image("background.gif") 264 background = pg.Surface(SCREENRECT.size) 265 for x in range(0, SCREENRECT.width, bgdtile.get_width()): 266 background.blit(bgdtile, (x, 0)) 267 screen.blit(background, (0, 0)) 268 pg.display.flip() 269 270 # load the sound effects 271 boom_sound = load_sound("boom.wav") 272 shoot_sound = load_sound("car_door.wav") 273 if pg.mixer: 274 music = os.path.join(main_dir, "data", "house_lo.wav") 275 pg.mixer.music.load(music) 276 pg.mixer.music.play(-1) 277 278 # Initialize Game Groups 279 aliens = pg.sprite.Group() 280 shots = pg.sprite.Group() 281 bombs = pg.sprite.Group() 282 all = pg.sprite.RenderUpdates() 283 lastalien = pg.sprite.GroupSingle() 284 285 # assign default groups to each sprite class 286 Player.containers = all 287 Alien.containers = aliens, all, lastalien 288 Shot.containers = shots, all 289 Bomb.containers = bombs, all 290 Explosion.containers = all 291 Score.containers = all 292 293 # Create Some Starting Values 294 global score 295 alienreload = ALIEN_RELOAD 296 clock = pg.time.Clock() 297 298 # initialize our starting sprites 299 global SCORE 300 player = Player() 301 Alien() # note, this 'lives' because it goes into a sprite group 302 if pg.font: 303 all.add(Score()) 304 305 # Run our main loop whilst the player is alive. 306 while player.alive(): 307 308 # get input 309 for event in pg.event.get(): 310 if event.type == pg.QUIT: 311 return 312 if event.type == pg.KEYDOWN and event.key == pg.K_ESCAPE: 313 return 314 elif event.type == pg.KEYDOWN: 315 if event.key == pg.K_f: 316 if not fullscreen: 317 print("Changing to FULLSCREEN") 318 screen_backup = screen.copy() 319 screen = pg.display.set_mode( 320 SCREENRECT.size, winstyle | pg.FULLSCREEN, bestdepth 321 ) 322 screen.blit(screen_backup, (0, 0)) 323 else: 324 print("Changing to windowed mode") 325 screen_backup = screen.copy() 326 screen = pg.display.set_mode( 327 SCREENRECT.size, winstyle, bestdepth 328 ) 329 screen.blit(screen_backup, (0, 0)) 330 pg.display.flip() 331 fullscreen = not fullscreen 332 333 keystate = pg.key.get_pressed() 334 335 # clear/erase the last drawn sprites 336 all.clear(screen, background) 337 338 # update all the sprites 339 all.update() 340 341 # handle player input 342 direction = keystate[pg.K_RIGHT] - keystate[pg.K_LEFT] 343 player.move(direction) 344 firing = keystate[pg.K_SPACE] 345 if not player.reloading and firing and len(shots) < MAX_SHOTS: 346 Shot(player.gunpos()) 347 if pg.mixer: 348 shoot_sound.play() 349 player.reloading = firing 350 351 # Create new alien 352 if alienreload: 353 alienreload = alienreload - 1 354 elif not int(random.random() * ALIEN_ODDS): 355 Alien() 356 alienreload = ALIEN_RELOAD 357 358 # Drop bombs 359 if lastalien and not int(random.random() * BOMB_ODDS): 360 Bomb(lastalien.sprite) 361 362 # Detect collisions between aliens and players. 363 for alien in pg.sprite.spritecollide(player, aliens, 1): 364 if pg.mixer: 365 boom_sound.play() 366 Explosion(alien) 367 Explosion(player) 368 SCORE = SCORE + 1 369 player.kill() 370 371 # See if shots hit the aliens. 372 for alien in pg.sprite.groupcollide(aliens, shots, 1, 1).keys(): 373 if pg.mixer: 374 boom_sound.play() 375 Explosion(alien) 376 SCORE = SCORE + 1 377 378 # See if alien boms hit the player. 379 for bomb in pg.sprite.spritecollide(player, bombs, 1): 380 if pg.mixer: 381 boom_sound.play() 382 Explosion(player) 383 Explosion(bomb) 384 player.kill() 385 386 # draw the scene 387 dirty = all.draw(screen) 388 pg.display.update(dirty) 389 390 # cap the framerate at 40fps. Also called 40HZ or 40 times per second. 391 clock.tick(40) 392 393 if pg.mixer: 394 pg.mixer.music.fadeout(1000) 395 pg.time.wait(1000) 396 397 398# call the "main" function if running this script 399if __name__ == "__main__": 400 main() 401 pg.quit() 402