├── README.md ├── config.py ├── demo └── screenshot1.png ├── gameRole.py ├── resource.py ├── resource ├── image │ ├── background.png │ ├── gameover.png │ ├── shoot.pack │ └── shoot.png └── sound │ ├── bullet.wav │ ├── enemy1_down.wav │ ├── enemy2_down.wav │ ├── enemy3_down.wav │ ├── game_music.wav │ ├── game_over.wav │ ├── get_bomb.wav │ └── use_bomb.wav └── shooter.py /README.md: -------------------------------------------------------------------------------- 1 | # PythonShootGame 2 | A improved shoot game based on https://github.com/Kill-Console/PythonShootGame. 3 | * add two types of enemy planes 4 | * add two types of weapons (bomb, power bullet) 5 | * improve plane collide check 6 | * enemy plane can appear from left,right and top side 7 | * enemy plane can shoot bullet 8 | * provide config.py to modify game difficulty 9 | 10 | # Introduce 11 | This project only include four simple .py files: 12 | 13 | * shooter.py: initialization and main loop of the game. 14 | * gameRole.py: class of the game role and helper class to manage game role 15 | * resource.py: image and music related initialization 16 | * config.py: provide parameters to modify game difficulty 17 | 18 | # Requirement 19 | * Python 3.7 20 | * Python-Pygame 1.9 21 | 22 | # How To Start Game 23 | $ python shooter.py 24 | 25 | # How to Play 26 | * use UP/DOWN/LEFT/RIGHT key to control plane 27 | * use SPACE key to shoot bomb 28 | * use z key to pause or resume game 29 | 30 | # Demo 31 | ![screenshot](https://raw.githubusercontent.com/marblexu/PythonSimpleShoot/master/demo/screenshot1.png) 32 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Wed Feb 13 15:45:00 2019 4 | 5 | @author: marble_xu 6 | """ 7 | 8 | # screen size 9 | SCREEN_WIDTH = 480 10 | SCREEN_HEIGHT = 640 11 | 12 | FRAME_RATE = 60 13 | # enemy or hero image change frequency of tick unit 14 | ANIMATE_CYCLE = 30 15 | 16 | ######### Game Difficulty Setting ######### 17 | 18 | # enemy plane appear in y range (0, ENEMY_APPEAR_HEIGHT) when come from left or right side 19 | # easy:SCREEN_HEIGHT//3, hard:SCREEN_HEIGHT//2, dead:(SCREEN_HEIGHT*2//3) 20 | ENEMY_APPEAR_HEIGHT = (SCREEN_HEIGHT//3) 21 | 22 | # create enemy and gift frequency of tick unit 23 | # easy:60, hard:30 24 | CREATE_CYCLE = 60 25 | 26 | # hero shoot frequency of tick unit 27 | # easy:10, hard:15, dead:30 28 | SHOOT_CYCLE = 15 29 | # easy <= 5, hard: (6,8) 30 | SHOOT_SPEED = 8 31 | 32 | # enemy shoot frequency of tick unit 33 | # easy:120, hard:90, dead:60 34 | ENEMY_SHOOT_CYCLE = 120 35 | # ENEMY_SHOOT_SPEED easy:1, hard:2, dead = 3 36 | ENEMY_SHOOT_SPEED = 2 37 | 38 | ######### End of Game Difficulty Setting ######### -------------------------------------------------------------------------------- /demo/screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marblexu/PythonShootGame/871a3df4d7b0d852d1a2b704d681db8a61db1748/demo/screenshot1.png -------------------------------------------------------------------------------- /gameRole.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Wed Feb 13 15:45:00 2019 4 | 5 | @author: marble_xu 6 | """ 7 | import pygame 8 | from random import randint 9 | from enum import Enum 10 | from config import * 11 | 12 | class GameGift(Enum): 13 | Bomb = 0 14 | PowerBullet = 1 15 | Laser = 2 16 | 17 | class BulletType(Enum): 18 | OneWay = 0 19 | TwoWay = 1 20 | ThreeWay = 2 21 | 22 | class EnemyType(Enum): 23 | EnemyType1 = 1 24 | EnemyType2 = 2 25 | EnemyType3 = 3 26 | 27 | 28 | bullet_type = [BulletType.OneWay, BulletType.TwoWay, BulletType.ThreeWay] 29 | 30 | class Weapon(pygame.sprite.Sprite): 31 | def __init__(self, weapon_surface, weapon_init_pos, direction): 32 | pygame.sprite.Sprite.__init__(self) 33 | self.image = weapon_surface 34 | self.rect = self.image.get_rect() 35 | self.rect.topleft = weapon_init_pos 36 | self.direction = direction 37 | 38 | def update(self): 39 | #direction[0]:x , direction[1]:y 40 | if self.direction[0] == 0: 41 | self.rect.y += self.direction[1] 42 | if self.direction[1] > 0: 43 | if self.rect.y > SCREEN_HEIGHT: 44 | self.kill() 45 | else: 46 | if self.rect.y < -self.rect.height: 47 | self.kill() 48 | else: 49 | self.rect.x += self.direction[0] 50 | if self.direction[0] > 0: 51 | if self.rect.x > SCREEN_WIDTH: 52 | self.kill() 53 | else: 54 | if self.rect.x < -self.rect.width: 55 | self.kill() 56 | 57 | class Enemy(pygame.sprite.Sprite): 58 | def __init__(self, enemy_surface, enemy_init_pos, direction, weapon_group): 59 | pygame.sprite.Sprite.__init__(self) 60 | self.image = enemy_surface 61 | self.rect = self.image.get_rect() 62 | self.rect.topleft = enemy_init_pos 63 | self.direction = direction 64 | self.down_index = 0 65 | self.damage = 0 66 | self.is_down = 0 67 | self.is_hit = 0 68 | self.ticks = 0 69 | self.weapon_group = weapon_group 70 | 71 | def update(self, enemy_surface, hit_surface=0): 72 | def shootWeapon(weapon_group, position, direction): 73 | weapon_group.shootWeapon(position, direction) 74 | 75 | #direction[0]:x , direction[1]:y 76 | should_kill = False 77 | self.rect.x += self.direction[0] 78 | self.rect.y += self.direction[1] 79 | if self.rect.x > SCREEN_WIDTH or self.rect.x < -self.image.get_width(): 80 | should_kill = True 81 | if self.rect.y > SCREEN_HEIGHT or self.rect.y < -self.image.get_height(): 82 | should_kill = True 83 | 84 | if should_kill: 85 | self.kill() 86 | else: 87 | if self.ticks >= ENEMY_SHOOT_CYCLE: 88 | self.ticks = 0 89 | 90 | if self.is_hit: 91 | self.is_hit -= 1 92 | self.image = hit_surface 93 | elif len(enemy_surface) >= 2: 94 | self.image = enemy_surface[self.ticks//(ENEMY_SHOOT_CYCLE//2)] 95 | else: 96 | self.image = enemy_surface[0] 97 | 98 | self.ticks += 1 99 | if self.weapon_group is not None: 100 | if self.ticks % ENEMY_SHOOT_CYCLE == 0: 101 | shootWeapon(self.weapon_group, [self.rect.centerx, self.rect.y + self.image.get_height()], [0, ENEMY_SHOOT_SPEED]) 102 | 103 | class Gift(pygame.sprite.Sprite): 104 | def __init__(self, gift_surface, gift_init_pos, speed): 105 | pygame.sprite.Sprite.__init__(self) 106 | self.image = gift_surface 107 | self.rect = self.image.get_rect() 108 | self.rect.topleft = gift_init_pos 109 | self.speed = speed 110 | 111 | def update(self): 112 | self.rect.top += self.speed 113 | if self.rect.top > SCREEN_HEIGHT: 114 | self.kill() 115 | 116 | class Hero(pygame.sprite.Sprite): 117 | def __init__(self, hero_surface, hero_down_surface, hero_init_pos, weapon_groups, life): 118 | pygame.sprite.Sprite.__init__(self) 119 | self.surface = hero_surface 120 | self.down_surface = hero_down_surface 121 | self.image = hero_surface[0] 122 | self.rect = self.image.get_rect() 123 | self.rect.topleft = hero_init_pos 124 | self.hero_init_pos = hero_init_pos 125 | self.weapon_groups = weapon_groups 126 | self.life = life 127 | self.reset() 128 | 129 | def reset(self): 130 | self.ticks = 0 131 | self.is_hit = False 132 | self.down_index = 0 133 | self.bullet_type_index = 0 134 | self.bomb_num = 0 135 | self.use_bomb = 0 136 | self.immune_ticks = 0 137 | 138 | def move(self, offset): 139 | x = self.rect.left + offset[pygame.K_RIGHT] - offset[pygame.K_LEFT] 140 | y = self.rect.top + offset[pygame.K_DOWN] - offset[pygame.K_UP] 141 | if x < 0: 142 | self.rect.left = 0 143 | elif x > SCREEN_WIDTH - self.rect.width: 144 | self.rect.left = SCREEN_WIDTH - self.rect.width 145 | else: 146 | self.rect.left = x 147 | 148 | if y < 0: 149 | self.rect.top = 0 150 | elif y > SCREEN_HEIGHT - self.rect.height: 151 | self.rect.top = SCREEN_HEIGHT - self.rect.height 152 | else: 153 | self.rect.top = y 154 | 155 | def useBomb(self): 156 | if self.bomb_num: 157 | self.use_bomb = 1 158 | 159 | def getBombNum(self): 160 | return self.bomb_num 161 | 162 | def addGift(self, type): 163 | if type == GameGift.Bomb: 164 | self.bomb_num += 3 165 | elif type == GameGift.PowerBullet: 166 | self.bullet_type_index = self.bullet_type_index + 1 if self.bullet_type_index < (len(bullet_type)-1) else self.bullet_type_index 167 | #elif type == GameGift.Laser: 168 | 169 | def isHeroCrash(self): 170 | if self.immune_ticks <= 0 and not self.is_hit: 171 | self.is_hit = True 172 | self.life -= 1 173 | return 1 174 | else: 175 | return 0 176 | 177 | def restart(self): 178 | self.reset() 179 | self.immune_ticks = 180 180 | self.rect.topleft = self.hero_init_pos 181 | 182 | def play(self): 183 | def getBulletPosition(rect, position_type): 184 | if position_type == 0: 185 | return [rect.centerx - 3, rect.centery] 186 | elif position_type == 1: 187 | return [rect.x + 10, rect.centery] 188 | else: 189 | return [rect.x + rect.width - 20, rect.centery] 190 | 191 | def shootWeapon(type, rect, weapon_groups): 192 | weapon_index = 0 193 | weapon_direction = [[0, -SHOOT_SPEED],[-SHOOT_SPEED, 0], [SHOOT_SPEED, 0]] 194 | if type == BulletType.OneWay: 195 | weapon_groups[weapon_index].shootWeapon(getBulletPosition(self.rect, 0), weapon_direction[0]) 196 | elif type == BulletType.TwoWay: 197 | weapon_groups[weapon_index].shootWeapon(getBulletPosition(self.rect, 1), weapon_direction[0]) 198 | weapon_groups[weapon_index].shootWeapon(getBulletPosition(self.rect, 2), weapon_direction[0]) 199 | elif type == BulletType.ThreeWay: 200 | weapon_groups[weapon_index].shootWeapon(getBulletPosition(self.rect, 0), weapon_direction[0]) 201 | weapon_groups[weapon_index].shootWeapon(getBulletPosition(self.rect, 1), weapon_direction[0]) 202 | weapon_groups[weapon_index].shootWeapon(getBulletPosition(self.rect, 2), weapon_direction[0]) 203 | 204 | if self.immune_ticks > 0: 205 | self.immune_ticks -= 1 206 | if not self.is_hit: 207 | if self.use_bomb: 208 | self.use_bomb = 0 209 | if self.bomb_num > 0: 210 | self.bomb_num -= 1 211 | self.weapon_groups[2].shootWeapon(self.rect.midtop, [0, -SHOOT_SPEED]) 212 | elif self.ticks % SHOOT_CYCLE == 0: 213 | shootWeapon(bullet_type[self.bullet_type_index], self.rect, self.weapon_groups) 214 | 215 | for weapon_group in self.weapon_groups: 216 | weapon_group.update() 217 | 218 | if self.ticks >= ANIMATE_CYCLE: 219 | self.ticks = 0 220 | if self.is_hit: 221 | assert self.down_index < len(self.down_surface) 222 | self.image = self.down_surface[self.down_index] 223 | if self.ticks % (ANIMATE_CYCLE//2) == 0: 224 | self.down_index += 1 225 | else: 226 | self.image = self.surface[self.ticks//(ANIMATE_CYCLE//2)] 227 | self.ticks += 1 228 | 229 | class EnemyGroup(): 230 | def __init__(self, surface, hit_surface, down_surface, down_sound, score, health, speed, enemy_type, weapon_group): 231 | self.surface = surface 232 | self.hit_surface = hit_surface 233 | self.down_surface = down_surface 234 | self.group = pygame.sprite.Group() 235 | self.down_group = pygame.sprite.Group() 236 | self.down_sound = down_sound 237 | self.score = score 238 | self.health = health 239 | self.speed = speed 240 | self.enemy_type = enemy_type 241 | self.weapon_group = weapon_group 242 | 243 | def createEnemy(self): 244 | def getDirection(surface, speed, enemy_type): 245 | if enemy_type == EnemyType.EnemyType3: 246 | enemy_init_pos = [randint(0, SCREEN_WIDTH - surface.get_width()), -surface.get_height()] 247 | direction = [0, speed] 248 | else: 249 | # enemy can appear from top side, left side, and right side 250 | appearSide = randint(0, 2) 251 | if appearSide == 0: # from top side 252 | enemy_init_pos = [randint(0, SCREEN_WIDTH - surface.get_width()), -surface.get_height()] 253 | direction = [0, speed] 254 | elif appearSide == 1: # from left side 255 | enemy_init_pos = [-surface.get_width(), randint(0, (ENEMY_APPEAR_HEIGHT - surface.get_height()))] 256 | direction = [randint(1, speed), randint(1, speed)] 257 | elif appearSide == 2: # from right side 258 | enemy_init_pos = [SCREEN_WIDTH, randint(0, (ENEMY_APPEAR_HEIGHT - surface.get_height()))] 259 | direction = [randint(-speed, -1), randint(1, speed)] 260 | return (enemy_init_pos, direction) 261 | 262 | (enemy_init_pos, direction) = getDirection(self.surface[0], self.speed, self.enemy_type) 263 | enemy = Enemy(self.surface[0], enemy_init_pos, direction, self.weapon_group) 264 | self.group.add(enemy) 265 | 266 | def update(self): 267 | self.group.update(self.surface, self.hit_surface) 268 | if self.weapon_group is not None: 269 | self.weapon_group.update() 270 | 271 | def draw(self, screen): 272 | self.group.draw(screen) 273 | if self.weapon_group is not None: 274 | self.weapon_group.draw(screen) 275 | 276 | def checkBulletCollide(self, bullets, screen, ticks): 277 | score = 0 278 | self.down_group.add(pygame.sprite.groupcollide(self.group, bullets.group, False, True)) 279 | for enemy_down in self.down_group: 280 | if enemy_down.is_down: 281 | screen.blit(self.down_surface[enemy_down.down_index], enemy_down.rect) 282 | if ticks % (ANIMATE_CYCLE//2) == 0: 283 | if enemy_down.down_index < (len(self.down_surface)-1): 284 | if enemy_down.down_index == 0: 285 | self.down_sound.play() 286 | enemy_down.down_index += 1 287 | else: 288 | self.down_group.remove(enemy_down) 289 | score += self.score 290 | else: 291 | enemy_down.damage += bullets.damage 292 | enemy_down.is_hit = ANIMATE_CYCLE//3 293 | if enemy_down.damage >= self.health: 294 | enemy_down.is_down = 1 295 | self.group.remove(enemy_down) 296 | else: 297 | self.down_group.remove(enemy_down) 298 | return score 299 | 300 | def checkHeroCollide(self, hero): 301 | enemy_down_list = pygame.sprite.spritecollide(hero, self.group, False) 302 | collide = False 303 | if len(enemy_down_list) > 0: 304 | for enemy_down in enemy_down_list: 305 | if pygame.sprite.collide_circle_ratio(0.7)(enemy_down, hero): 306 | self.group.remove(enemy_down) 307 | self.down_group.add(enemy_down) 308 | enemy_down.is_down = 1 309 | collide = True 310 | 311 | if not collide and self.weapon_group is not None: 312 | bullet_hit_list = pygame.sprite.spritecollide(hero, self.weapon_group.group, False) 313 | if len(bullet_hit_list) > 0: 314 | for bullet_hit in bullet_hit_list: 315 | if pygame.sprite.collide_circle_ratio(0.7)(bullet_hit, hero): 316 | self.weapon_group.group.remove(bullet_hit) 317 | collide = True 318 | 319 | return collide 320 | 321 | class GiftGroup(): 322 | def __init__(self, surface, gift_sound, speed, type): 323 | self.surface = surface 324 | self.group = pygame.sprite.Group() 325 | self.gift_sound = gift_sound 326 | self.speed = speed 327 | self.type = type 328 | 329 | def update(self): 330 | self.group.update() 331 | 332 | def draw(self, screen): 333 | self.group.draw(screen) 334 | 335 | def createGift(self): 336 | gift = Gift(self.surface, [randint(0, SCREEN_WIDTH - self.surface.get_width()), -self.surface.get_height()], self.speed) 337 | self.group.add(gift) 338 | 339 | def checkHeroCollide(self, hero): 340 | gift_hit_list = pygame.sprite.spritecollide(hero, self.group, False) 341 | if len(gift_hit_list) > 0: 342 | for gift in gift_hit_list: 343 | if pygame.sprite.collide_circle_ratio(0.8)(gift, hero): 344 | self.group.remove(gift) 345 | self.gift_sound.play() 346 | hero.addGift(self.type) 347 | 348 | class WeaponGroup(): 349 | def __init__(self, weapon_surface, weapon_sound, damage): 350 | self.surface = weapon_surface 351 | self.group = pygame.sprite.Group() 352 | self.weapon_sound = weapon_sound 353 | self.damage = damage 354 | 355 | def shootWeapon(self, position, direction): 356 | weapon = Weapon(self.surface, position, direction) 357 | self.group.add(weapon) 358 | self.weapon_sound.play() 359 | 360 | def update(self): 361 | self.group.update() 362 | 363 | def draw(self, screen): 364 | self.group.draw(screen) 365 | 366 | class EnemyWeaponGroup(): 367 | def __init__(self, weapon_surface, weapon_sound, damage): 368 | self.surface = weapon_surface 369 | self.group = pygame.sprite.Group() 370 | self.weapon_sound = weapon_sound 371 | self.damage = damage 372 | 373 | def shootWeapon(self, position, direction): 374 | assert (direction[0] != 0) or (direction[1] != 0) 375 | weapon = Weapon(self.surface, position, direction) 376 | self.group.add(weapon) 377 | self.weapon_sound.play() 378 | 379 | def update(self): 380 | self.group.update() 381 | 382 | def draw(self, screen): 383 | self.group.draw(screen) 384 | -------------------------------------------------------------------------------- /resource.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Wed Feb 13 15:45:00 2019 4 | 5 | @author: marble_xu 6 | """ 7 | import pygame 8 | from gameRole import * 9 | 10 | 11 | shoot_img = pygame.image.load('resource/image/shoot.png') 12 | 13 | def initWeaponGroups(): 14 | weapon_groups = [] 15 | bullet1_surface = shoot_img.subsurface(pygame.Rect(1004, 987, 9, 21)) 16 | bullet_sound = pygame.mixer.Sound('resource/sound/bullet.wav') 17 | bullet_sound.set_volume(0.3) 18 | weapon_groups.append(WeaponGroup(bullet1_surface, bullet_sound, 1)) 19 | 20 | bullet2_surface = shoot_img.subsurface(pygame.Rect(69, 78, 9, 21)) 21 | weapon_groups.append(WeaponGroup(bullet2_surface, bullet_sound, 2)) 22 | 23 | bomb_surface = shoot_img.subsurface(pygame.Rect(828, 691, 28, 57)) 24 | bomb_sound = pygame.mixer.Sound('resource/sound/use_bomb.wav') 25 | bomb_sound.set_volume(0.3) 26 | weapon_groups.append(WeaponGroup(bomb_surface, bomb_sound, 9)) 27 | 28 | return weapon_groups 29 | 30 | 31 | def initHero(): 32 | hero_surface = [] 33 | hero_surface.append(shoot_img.subsurface(pygame.Rect(0, 99, 102, 126))) 34 | hero_surface.append(shoot_img.subsurface(pygame.Rect(165, 360, 102, 126))) 35 | 36 | hero_down_surface = [] 37 | hero_down_surface.append(shoot_img.subsurface(pygame.Rect(165, 234, 102, 126))) 38 | hero_down_surface.append(shoot_img.subsurface(pygame.Rect(330, 624, 102, 126))) 39 | hero_down_surface.append(shoot_img.subsurface(pygame.Rect(330, 498, 102, 126))) 40 | hero_down_surface.append(shoot_img.subsurface(pygame.Rect(432, 624, 102, 126))) 41 | hero_pos = [200, 500] 42 | 43 | return Hero(hero_surface, hero_down_surface, hero_pos, initWeaponGroups(), 3) 44 | 45 | 46 | def initEnemyGroups(): 47 | enemy_groups = [] 48 | 49 | bullet_sound = pygame.mixer.Sound('resource/sound/bullet.wav') 50 | bullet_sound.set_volume(0.3) 51 | bullet_surface = shoot_img.subsurface(pygame.Rect(69, 78, 9, 21)) 52 | enemy_weapon_group = EnemyWeaponGroup(bullet_surface, bullet_sound, 1) 53 | 54 | enemy1_surface = [] 55 | enemy1_surface.append(shoot_img.subsurface(pygame.Rect(534, 612, 57, 43))) 56 | enemy1_hit_surface = shoot_img.subsurface(pygame.Rect(534, 612, 57, 43)) 57 | enemy1_down_surface = [] 58 | enemy1_down_surface.append(shoot_img.subsurface(pygame.Rect(267, 347, 57, 43))) 59 | enemy1_down_surface.append(shoot_img.subsurface(pygame.Rect(873, 697, 57, 43))) 60 | enemy1_down_surface.append(shoot_img.subsurface(pygame.Rect(267, 296, 57, 43))) 61 | enemy1_down_surface.append(shoot_img.subsurface(pygame.Rect(930, 697, 57, 43))) 62 | enemy1_down_sound = pygame.mixer.Sound('resource/sound/enemy1_down.wav') 63 | enemy1_down_sound.set_volume(0.3) 64 | enemy_groups.append(EnemyGroup(enemy1_surface, enemy1_hit_surface, enemy1_down_surface, enemy1_down_sound, 1000, 1, 3, EnemyType.EnemyType1, None)) 65 | 66 | enemy2_surface = [] 67 | enemy2_surface.append(shoot_img.subsurface(pygame.Rect(0, 0, 69, 99))) 68 | enemy2_hit_surface = shoot_img.subsurface(pygame.Rect(432, 525, 69, 99)) 69 | enemy2_down_surface = [] 70 | enemy2_down_surface.append(shoot_img.subsurface(pygame.Rect(534, 655, 69, 95))) 71 | enemy2_down_surface.append(shoot_img.subsurface(pygame.Rect(603, 655, 69, 95))) 72 | enemy2_down_surface.append(shoot_img.subsurface(pygame.Rect(672, 653, 69, 95))) 73 | enemy2_down_surface.append(shoot_img.subsurface(pygame.Rect(741, 653, 69, 95))) 74 | enemy2_down_sound = pygame.mixer.Sound('resource/sound/enemy2_down.wav') 75 | enemy2_down_sound.set_volume(0.3) 76 | enemy_groups.append(EnemyGroup(enemy2_surface, enemy2_hit_surface, enemy2_down_surface, enemy2_down_sound, 3000, 3, 2, EnemyType.EnemyType2, enemy_weapon_group)) 77 | 78 | enemy3_surface = [] 79 | enemy3_surface.append(shoot_img.subsurface(pygame.Rect(335, 750, 169, 258))) 80 | enemy3_surface.append(shoot_img.subsurface(pygame.Rect(504, 750, 169, 258))) 81 | enemy3_hit_surface = shoot_img.subsurface(pygame.Rect(166, 750, 169, 258)) 82 | enemy3_down_surface = [] 83 | enemy3_down_surface.append(shoot_img.subsurface(pygame.Rect(0, 486, 165, 261))) 84 | enemy3_down_surface.append(shoot_img.subsurface(pygame.Rect(0, 255, 165, 261))) 85 | enemy3_down_surface.append(shoot_img.subsurface(pygame.Rect(839, 748, 165, 260))) 86 | enemy3_down_surface.append(shoot_img.subsurface(pygame.Rect(165, 486, 165, 261))) 87 | enemy3_down_surface.append(shoot_img.subsurface(pygame.Rect(673, 748, 166, 260))) 88 | enemy3_down_surface.append(shoot_img.subsurface(pygame.Rect(0, 747, 166, 261))) 89 | enemy3_down_sound = pygame.mixer.Sound('resource/sound/enemy3_down.wav') 90 | enemy3_down_sound.set_volume(0.3) 91 | enemy_groups.append(EnemyGroup(enemy3_surface, enemy3_hit_surface, enemy3_down_surface, enemy3_down_sound, 9000, 12, 1, EnemyType.EnemyType3, enemy_weapon_group)) 92 | 93 | return enemy_groups 94 | 95 | 96 | def initGiftGroups(): 97 | gift_groups = [] 98 | gift1_surface = shoot_img.subsurface(pygame.Rect(101, 120, 60, 104)) 99 | gift1_sound = pygame.mixer.Sound('resource/sound/get_bomb.wav') 100 | gift1_sound.set_volume(0.3) 101 | gift_groups.append(GiftGroup(gift1_surface, gift1_sound, 1, GameGift.Bomb)) 102 | 103 | gift2_surface = shoot_img.subsurface(pygame.Rect(265, 400, 60, 85)) 104 | gift_groups.append(GiftGroup(gift2_surface, gift1_sound, 1, GameGift.PowerBullet)) 105 | 106 | return gift_groups 107 | 108 | 109 | def initGame(): 110 | pygame.mixer.music.load('resource/sound/game_music.wav') 111 | pygame.mixer.music.play(-1, 0.0) 112 | pygame.mixer.music.set_volume(0.2) 113 | 114 | background = pygame.image.load('resource/image/background.png') 115 | gameover = pygame.image.load('resource/image/gameover.png') 116 | 117 | game_over_sound = pygame.mixer.Sound('resource/sound/game_over.wav') 118 | game_over_sound.set_volume(0.3) 119 | 120 | bomb_surface = pygame.transform.scale(shoot_img.subsurface(pygame.Rect(828, 691, 28, 57)), (19,40)) 121 | plane_surface = pygame.transform.scale(shoot_img.subsurface(pygame.Rect(5, 99, 96, 96)), (36,36)) 122 | return (background, gameover, game_over_sound, bomb_surface, plane_surface) 123 | -------------------------------------------------------------------------------- /resource/image/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marblexu/PythonShootGame/871a3df4d7b0d852d1a2b704d681db8a61db1748/resource/image/background.png -------------------------------------------------------------------------------- /resource/image/gameover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marblexu/PythonShootGame/871a3df4d7b0d852d1a2b704d681db8a61db1748/resource/image/gameover.png -------------------------------------------------------------------------------- /resource/image/shoot.pack: -------------------------------------------------------------------------------- 1 | 2 | shoot.png 3 | format: RGBA8888 4 | filter: Nearest,Nearest 5 | repeat: none 6 | enemy3_down6 7 | rotate: false 8 | xy: 0, 747 9 | size: 166, 261 10 | orig: 166, 261 11 | offset: 0, 0 12 | index: -1 13 | enemy3_hit 14 | rotate: false 15 | xy: 166, 750 16 | size: 169, 258 17 | orig: 169, 258 18 | offset: 0, 0 19 | index: -1 20 | enemy3_n1 21 | rotate: false 22 | xy: 335, 750 23 | size: 169, 258 24 | orig: 169, 258 25 | offset: 0, 0 26 | index: -1 27 | enemy3_n2 28 | rotate: false 29 | xy: 504, 750 30 | size: 169, 258 31 | orig: 169, 258 32 | offset: 0, 0 33 | index: -1 34 | enemy3_down1 35 | rotate: false 36 | xy: 0, 486 37 | size: 165, 261 38 | orig: 165, 261 39 | offset: 0, 0 40 | index: -1 41 | enemy3_down2 42 | rotate: false 43 | xy: 0, 225 44 | size: 165, 261 45 | orig: 165, 261 46 | offset: 0, 0 47 | index: -1 48 | enemy3_down5 49 | rotate: false 50 | xy: 673, 748 51 | size: 166, 260 52 | orig: 166, 260 53 | offset: 0, 0 54 | index: -1 55 | enemy3_down3 56 | rotate: false 57 | xy: 839, 748 58 | size: 165, 260 59 | orig: 165, 260 60 | offset: 0, 0 61 | index: -1 62 | enemy3_down4 63 | rotate: false 64 | xy: 165, 486 65 | size: 165, 261 66 | orig: 165, 261 67 | offset: 0, 0 68 | index: -1 69 | hero1 70 | rotate: false 71 | xy: 0, 99 72 | size: 102, 126 73 | orig: 102, 126 74 | offset: 0, 0 75 | index: -1 76 | hero2 77 | rotate: false 78 | xy: 165, 360 79 | size: 102, 126 80 | orig: 102, 126 81 | offset: 0, 0 82 | index: -1 83 | hero_blowup_n1 84 | rotate: false 85 | xy: 165, 234 86 | size: 102, 126 87 | orig: 102, 126 88 | offset: 0, 0 89 | index: -1 90 | hero_blowup_n2 91 | rotate: false 92 | xy: 330, 624 93 | size: 102, 126 94 | orig: 102, 126 95 | offset: 0, 0 96 | index: -1 97 | hero_blowup_n3 98 | rotate: false 99 | xy: 330, 498 100 | size: 102, 126 101 | orig: 102, 126 102 | offset: 0, 0 103 | index: -1 104 | hero_blowup_n4 105 | rotate: false 106 | xy: 432, 624 107 | size: 102, 126 108 | orig: 102, 126 109 | offset: 0, 0 110 | index: -1 111 | enemy2 112 | rotate: false 113 | xy: 0, 0 114 | size: 69, 99 115 | orig: 69, 99 116 | offset: 0, 0 117 | index: -1 118 | enemy2_hit 119 | rotate: false 120 | xy: 432, 525 121 | size: 69, 99 122 | orig: 69, 99 123 | offset: 0, 0 124 | index: -1 125 | ufo2 126 | rotate: false 127 | xy: 102, 118 128 | size: 60, 107 129 | orig: 60, 107 130 | offset: 0, 0 131 | index: -1 132 | enemy2_down1 133 | rotate: false 134 | xy: 534, 655 135 | size: 69, 95 136 | orig: 69, 95 137 | offset: 0, 0 138 | index: -1 139 | enemy2_down2 140 | rotate: false 141 | xy: 603, 655 142 | size: 69, 95 143 | orig: 69, 95 144 | offset: 0, 0 145 | index: -1 146 | enemy2_down3 147 | rotate: false 148 | xy: 672, 653 149 | size: 69, 95 150 | orig: 69, 95 151 | offset: 0, 0 152 | index: -1 153 | enemy2_down4 154 | rotate: false 155 | xy: 741, 653 156 | size: 69, 95 157 | orig: 69, 95 158 | offset: 0, 0 159 | index: -1 160 | ufo1 161 | rotate: false 162 | xy: 267, 398 163 | size: 58, 88 164 | orig: 58, 88 165 | offset: 0, 0 166 | index: -1 167 | bomb 168 | rotate: false 169 | xy: 810, 691 170 | size: 63, 57 171 | orig: 63, 57 172 | offset: 0, 0 173 | index: -1 174 | enemy1_down1 175 | rotate: false 176 | xy: 267, 347 177 | size: 57, 51 178 | orig: 57, 51 179 | offset: 0, 0 180 | index: -1 181 | enemy1_down2 182 | rotate: false 183 | xy: 873, 697 184 | size: 57, 51 185 | orig: 57, 51 186 | offset: 0, 0 187 | index: -1 188 | enemy1_down3 189 | rotate: false 190 | xy: 267, 296 191 | size: 57, 51 192 | orig: 57, 51 193 | offset: 0, 0 194 | index: -1 195 | enemy1_down4 196 | rotate: false 197 | xy: 930, 697 198 | size: 57, 51 199 | orig: 57, 51 200 | offset: 0, 0 201 | index: -1 202 | game_pause_nor 203 | rotate: false 204 | xy: 267, 251 205 | size: 60, 45 206 | orig: 60, 45 207 | offset: 0, 0 208 | index: -1 209 | game_pause_pressed 210 | rotate: false 211 | xy: 810, 646 212 | size: 60, 45 213 | orig: 60, 45 214 | offset: 0, 0 215 | index: -1 216 | enemy1 217 | rotate: false 218 | xy: 534, 612 219 | size: 57, 43 220 | orig: 57, 43 221 | offset: 0, 0 222 | index: -1 223 | bullet1 224 | rotate: false 225 | xy: 1004, 987 226 | size: 9, 21 227 | orig: 9, 21 228 | offset: 0, 0 229 | index: -1 230 | bullet2 231 | rotate: false 232 | xy: 69, 78 233 | size: 9, 21 234 | orig: 9, 21 235 | offset: 0, 0 236 | index: -1 237 | -------------------------------------------------------------------------------- /resource/image/shoot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marblexu/PythonShootGame/871a3df4d7b0d852d1a2b704d681db8a61db1748/resource/image/shoot.png -------------------------------------------------------------------------------- /resource/sound/bullet.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marblexu/PythonShootGame/871a3df4d7b0d852d1a2b704d681db8a61db1748/resource/sound/bullet.wav -------------------------------------------------------------------------------- /resource/sound/enemy1_down.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marblexu/PythonShootGame/871a3df4d7b0d852d1a2b704d681db8a61db1748/resource/sound/enemy1_down.wav -------------------------------------------------------------------------------- /resource/sound/enemy2_down.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marblexu/PythonShootGame/871a3df4d7b0d852d1a2b704d681db8a61db1748/resource/sound/enemy2_down.wav -------------------------------------------------------------------------------- /resource/sound/enemy3_down.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marblexu/PythonShootGame/871a3df4d7b0d852d1a2b704d681db8a61db1748/resource/sound/enemy3_down.wav -------------------------------------------------------------------------------- /resource/sound/game_music.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marblexu/PythonShootGame/871a3df4d7b0d852d1a2b704d681db8a61db1748/resource/sound/game_music.wav -------------------------------------------------------------------------------- /resource/sound/game_over.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marblexu/PythonShootGame/871a3df4d7b0d852d1a2b704d681db8a61db1748/resource/sound/game_over.wav -------------------------------------------------------------------------------- /resource/sound/get_bomb.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marblexu/PythonShootGame/871a3df4d7b0d852d1a2b704d681db8a61db1748/resource/sound/get_bomb.wav -------------------------------------------------------------------------------- /resource/sound/use_bomb.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marblexu/PythonShootGame/871a3df4d7b0d852d1a2b704d681db8a61db1748/resource/sound/use_bomb.wav -------------------------------------------------------------------------------- /shooter.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Wed Feb 13 15:45:00 2019 4 | 5 | @author: marble_xu 6 | """ 7 | import pygame 8 | from pygame.locals import * 9 | from sys import exit 10 | from random import randint 11 | from config import * 12 | from gameRole import * 13 | from resource import * 14 | 15 | BACKGROUND_IMAGE_HEIGHT = 800 16 | 17 | # display information like score, bomb number 18 | class ScreenInfo(): 19 | def __init__(self, bomb_surface, plane_surface): 20 | self.score = 0 21 | self.bomb_surface = bomb_surface 22 | self.plane_surface = plane_surface 23 | self.latest_score = 0 24 | 25 | def addScore(self, score): 26 | self.score += score 27 | self.latest_score += score 28 | 29 | def getScore(self): 30 | return self.score 31 | 32 | def shouldCreateGift(self): 33 | if self.latest_score > 20000: 34 | self.latest_score -= 20000 35 | return True 36 | return False 37 | 38 | def displayInfo(self, screen, game_over, bomb_num, plane_life): 39 | def displayScore(screen, score): 40 | score_font = pygame.font.Font(None, 36) 41 | score_text = score_font.render(str(score), True, (128,128,128)) 42 | text_rect = score_text.get_rect() 43 | text_rect.topleft = [SCREEN_WIDTH//2-35, 10] 44 | screen.blit(score_text, text_rect) 45 | 46 | displayScore(screen, self.score) 47 | if not game_over: 48 | bomb_rect = self.bomb_surface.get_rect() 49 | bomb_rect.topleft = [10, 0] 50 | screen.blit(self.bomb_surface, bomb_rect) 51 | 52 | bomb_font = pygame.font.Font(None, 36) 53 | bomb_text = bomb_font.render(" : "+str(bomb_num), True, (128,128,128)) 54 | text_rect = bomb_text.get_rect() 55 | text_rect.topleft = [30, 10] 56 | screen.blit(bomb_text, text_rect) 57 | 58 | for i in range(plane_life): 59 | plane_rect = self.plane_surface.get_rect() 60 | plane_rect.topleft = [SCREEN_WIDTH - 120 + (i * 40), 0] 61 | screen.blit(self.plane_surface, plane_rect) 62 | 63 | class Game(): 64 | def __init__(self, caption, hero, screen_info): 65 | self.screen = pygame.display.set_mode([SCREEN_WIDTH, SCREEN_HEIGHT]) 66 | pygame.display.set_caption(caption) 67 | self.hero = hero 68 | self.clock = pygame.time.Clock() 69 | self.screen_info = screen_info 70 | self.ticks = 0 71 | self.pause = False 72 | self.backgound_y = SCREEN_HEIGHT - background.get_height() # height of background image must be larger than SCREEN_HEIGHT 73 | 74 | # create a new enemy if match condition 75 | def createEnemy(self, enemy_groups, ticks, score): 76 | # limit enemy2 and enemy3 numbers 77 | def getEnemyIndex(enemy_groups, enemy_range): 78 | index = randint(0, enemy_range) 79 | if index == 2: 80 | if len(enemy_groups[index].group) > 0: 81 | index = 0 82 | elif index == 1: 83 | if len(enemy_groups[index].group) > 2: 84 | index = 0 85 | return index 86 | 87 | if ticks % CREATE_CYCLE == 0: 88 | if score < 10000: 89 | enemy_range = 0 90 | elif score < 20000: 91 | enemy_range = 1 92 | else: 93 | enemy_range = 2 94 | 95 | index = getEnemyIndex(enemy_groups, enemy_range) 96 | enemy_groups[index].createEnemy() 97 | 98 | # create a new gift if match condition 99 | def createGift(self, gift_groups, ticks, screen_info): 100 | if ticks % CREATE_CYCLE == 0 and screen_info.shouldCreateGift(): 101 | score = screen_info.getScore() 102 | if score < 20000: 103 | gift_range = 0 104 | elif score < 40000: 105 | gift_range = 1 106 | else: 107 | gift_range = 1 108 | 109 | gift_size = 0 110 | for group in gift_groups: 111 | gift_size += len(group.group) 112 | if gift_size == 0: 113 | if self.hero.bomb_num >= 3: 114 | index = randint(1, gift_range) 115 | else: 116 | index = randint(0, gift_range) 117 | gift_groups[index].createGift() 118 | 119 | def play(self, enemy_groups, gift_groups): 120 | def updateBackground(screen, image_height, current_y): 121 | if current_y <= 0: 122 | screen.blit(background, (0, 0), (0, -current_y, SCREEN_WIDTH, SCREEN_HEIGHT)) 123 | elif current_y < SCREEN_HEIGHT: 124 | screen.blit(background, (0, 0), (0, image_height - current_y, SCREEN_WIDTH, current_y)) 125 | screen.blit(background, (0, current_y), (0, 0, SCREEN_WIDTH, SCREEN_HEIGHT - current_y)) 126 | 127 | def checkBulletCollide(enemy_group, bullets_group, screen, ticks): 128 | score = 0 129 | for group in enemy_group: 130 | for bullet_group in bullets_group: 131 | score += group.checkBulletCollide(bullet_group, screen, ticks) 132 | return score 133 | 134 | def checkHeroCollide(hero, enemy_group): 135 | collide = False 136 | for group in enemy_group: 137 | if group.checkHeroCollide(hero): 138 | collide = True 139 | break; 140 | return collide 141 | 142 | self.clock.tick(FRAME_RATE) 143 | if self.backgound_y == SCREEN_HEIGHT: 144 | self.backgound_y = SCREEN_HEIGHT - background.get_height() 145 | updateBackground(self.screen, background.get_height(), self.backgound_y) 146 | self.backgound_y += 1 147 | 148 | if self.ticks >= FRAME_RATE: 149 | self.ticks = 0 150 | 151 | self.hero.play() 152 | 153 | self.createEnemy(enemy_groups, self.ticks, self.screen_info.getScore()) 154 | self.createGift(gift_groups, self.ticks, self.screen_info) 155 | 156 | self.screen_info.addScore(checkBulletCollide(enemy_groups, self.hero.weapon_groups, self.screen, self.ticks)) 157 | 158 | if checkHeroCollide(self.hero, enemy_groups): 159 | if self.hero.isHeroCrash(): 160 | game_over_sound.play() 161 | 162 | for gift_group in gift_groups: 163 | gift_group.checkHeroCollide(self.hero) 164 | 165 | for weapon_group in self.hero.weapon_groups: 166 | weapon_group.draw(self.screen) 167 | 168 | for enemy_group in enemy_groups: 169 | enemy_group.update() 170 | enemy_group.draw(self.screen) 171 | 172 | for gift_group in gift_groups: 173 | gift_group.update() 174 | gift_group.draw(self.screen) 175 | 176 | self.screen.blit(self.hero.image, self.hero.rect) 177 | self.ticks += 1 178 | 179 | self.screen_info.displayInfo(self.screen, 0, self.hero.bomb_num, self.hero.life) 180 | 181 | def isGameOver(self): 182 | if self.hero.down_index >= len(self.hero.down_surface): 183 | if self.hero.life <= 0: 184 | return 1 185 | else: 186 | self.hero.restart() 187 | return 0 188 | 189 | def showGameOver(self): 190 | self.screen.blit(gameover, (0,0)) 191 | self.screen_info.displayInfo(self.screen, 1, self.hero.bomb_num, self.hero.life) 192 | 193 | def setPause(self): 194 | self.pause = not self.pause 195 | 196 | def isPause(self): 197 | return self.pause 198 | 199 | 200 | offset = {pygame.K_LEFT:0, pygame.K_RIGHT:0, pygame.K_UP:0, pygame.K_DOWN:0} 201 | 202 | pygame.init() 203 | 204 | (background, gameover, game_over_sound, bomb_surface, plane_surface) = initGame() 205 | screen_info = ScreenInfo(bomb_surface, plane_surface) 206 | myGame = Game('Air Craft Shooter!', initHero(), screen_info) 207 | enemy_groups = initEnemyGroups() 208 | gift_groups = initGiftGroups() 209 | 210 | while True: 211 | if myGame.isGameOver(): 212 | myGame.showGameOver() 213 | elif not myGame.isPause(): 214 | myGame.play(enemy_groups, gift_groups) 215 | 216 | pygame.display.update() 217 | 218 | for event in pygame.event.get(): 219 | if event.type == pygame.QUIT: 220 | pygame.quit() 221 | exit() 222 | # get keyboard input 223 | if event.type == pygame.KEYDOWN: 224 | if event.key in offset: 225 | offset[event.key] = 3 226 | elif event.key == pygame.K_SPACE: 227 | myGame.hero.useBomb() 228 | # press z to pause or resume game 229 | elif event.key == pygame.K_z: 230 | myGame.setPause() 231 | elif event.type == pygame.KEYUP: 232 | if event.key in offset: 233 | offset[event.key] = 0 234 | 235 | if not myGame.hero.is_hit: 236 | myGame.hero.move(offset) 237 | 238 | --------------------------------------------------------------------------------