Related documents
Pay attention to Xiaobian and receive private letter Xiaobian!
Of course, don't forget a triple~~
Yes, we can pay attention to the official account of Xiaobian yo ~ ~
Python log
development environment
Python version: 3.6.4
Related modules:
pygame module;
And some Python built-in modules.
Environment construction
Install Python and add it to the environment variable. pip can install the relevant modules required.
Principle introduction
Rules of the game:
The player controls the character Zelda (green) action through the ↑↓←→ key. When the player presses the space bar, he can place a bomb at the current position. Other characters (dk and batman) are randomly controlled by computers. When all characters are burned by the flame generated by the bomb (including their own bombs), they will lose a certain amount of life; When all characters eat fruit, they can restore a certain amount of health. In addition, the wall can prevent the further spread of the flame generated by the bomb.
When our character zelda's HP is 0, the game fails; When the health value of all characters on the computer side is 0, the game wins and enters the next level.
Step by step:
First, let's clarify which game sprites are included in the game:
- Bomb class
- Role class
- Wall class
- Background class
- Fruits
The wall class and background class are well defined. You only need to import the picture and bind the picture to the specified location:
'''Wall class''' class Wall(pygame.sprite.Sprite): def __init__(self, imagepath, coordinate, blocksize, **kwargs): pygame.sprite.Sprite.__init__(self) self.image = pygame.image.load(imagepath) self.image = pygame.transform.scale(self.image, (blocksize, blocksize)) self.rect = self.image.get_rect() self.rect.left, self.rect.top = coordinate[0] * blocksize, coordinate[1] * blocksize self.coordinate = coordinate self.blocksize = blocksize '''Draw to screen''' def draw(self, screen): screen.blit(self.image, self.rect) return True '''Background class''' class Background(pygame.sprite.Sprite): def __init__(self, imagepath, coordinate, blocksize, **kwargs): pygame.sprite.Sprite.__init__(self) self.image = pygame.image.load(imagepath) self.image = pygame.transform.scale(self.image, (blocksize, blocksize)) self.rect = self.image.get_rect() self.rect.left, self.rect.top = coordinate[0] * blocksize, coordinate[1] * blocksize self.coordinate = coordinate self.blocksize = blocksize '''Draw to screen''' def draw(self, screen): screen.blit(self.image, self.rect) return True
In fact, the definition of fruits is similar, but different fruits can help characters restore different values of health:
'''Fruits''' class Fruit(pygame.sprite.Sprite): def __init__(self, imagepath, coordinate, blocksize, **kwargs): pygame.sprite.Sprite.__init__(self) self.kind = imagepath.split('/')[-1].split('.')[0] if self.kind == 'banana': self.value = 5 elif self.kind == 'cherry': self.value = 10 else: raise ValueError('Unknow fruit <%s>...' % self.kind) self.image = pygame.image.load(imagepath) self.image = pygame.transform.scale(self.image, (blocksize, blocksize)) self.rect = self.image.get_rect() self.rect.left, self.rect.top = coordinate[0] * blocksize, coordinate[1] * blocksize self.coordinate = coordinate self.blocksize = blocksize '''Draw to screen''' def draw(self, screen): screen.blit(self.image, self.rect) return True
The definition of bomb class and character class is a little more complicated. Characters need to move up, down, left and right according to the instructions of the player or computer. At the same time, they can generate bombs in their own position and restore a certain amount of health after eating fruit:
'''Role class''' class Hero(pygame.sprite.Sprite): def __init__(self, imagepaths, coordinate, blocksize, map_parser, **kwargs): pygame.sprite.Sprite.__init__(self) self.imagepaths = imagepaths self.image = pygame.image.load(imagepaths[-1]) self.image = pygame.transform.scale(self.image, (blocksize, blocksize)) self.rect = self.image.get_rect() self.rect.left, self.rect.top = coordinate[0] * blocksize, coordinate[1] * blocksize self.coordinate = coordinate self.blocksize = blocksize self.map_parser = map_parser self.hero_name = kwargs.get('hero_name') # Life value self.health_value = 50 # Bomb cooldown self.bomb_cooling_time = 5000 self.bomb_cooling_count = 0 # Random movement cooling time (for AI computers only) self.randommove_cooling_time = 100 self.randommove_cooling_count = 0 '''Character movement''' def move(self, direction): self.__updateImage(direction) if direction == 'left': if self.coordinate[0]-1 < 0 or self.map_parser.getElemByCoordinate([self.coordinate[0]-1, self.coordinate[1]]) in ['w', 'x', 'z']: return False self.coordinate[0] = self.coordinate[0] - 1 elif direction == 'right': if self.coordinate[0]+1 >= self.map_parser.width or self.map_parser.getElemByCoordinate([self.coordinate[0]+1, self.coordinate[1]]) in ['w', 'x', 'z']: return False self.coordinate[0] = self.coordinate[0] + 1 elif direction == 'up': if self.coordinate[1]-1 < 0 or self.map_parser.getElemByCoordinate([self.coordinate[0], self.coordinate[1]-1]) in ['w', 'x', 'z']: return False self.coordinate[1] = self.coordinate[1] - 1 elif direction == 'down': if self.coordinate[1]+1 >= self.map_parser.height or self.map_parser.getElemByCoordinate([self.coordinate[0], self.coordinate[1]+1]) in ['w', 'x', 'z']: return False self.coordinate[1] = self.coordinate[1] + 1 else: raise ValueError('Unknow direction <%s>...' % direction) self.rect.left, self.rect.top = self.coordinate[0] * self.blocksize, self.coordinate[1] * self.blocksize return True '''Random action(AI Computer use)''' def randomAction(self, dt): # Cooling down countdown if self.randommove_cooling_count > 0: self.randommove_cooling_count -= dt action = random.choice(['left', 'left', 'right', 'right', 'up', 'up', 'down', 'down', 'dropbomb']) flag = False if action in ['left', 'right', 'up', 'down']: if self.randommove_cooling_count <= 0: flag = True self.move(action) self.randommove_cooling_count = self.randommove_cooling_time elif action in ['dropbomb']: if self.bomb_cooling_count <= 0: flag = True self.bomb_cooling_count = self.bomb_cooling_time return action, flag '''Generate bomb''' def generateBomb(self, imagepath, digitalcolor, explode_imagepath): return Bomb(imagepath=imagepath, coordinate=copy.deepcopy(self.coordinate), blocksize=self.blocksize, digitalcolor=digitalcolor, explode_imagepath=explode_imagepath) '''Draw to screen''' def draw(self, screen, dt): # Cooling down countdown if self.bomb_cooling_count > 0: self.bomb_cooling_count -= dt screen.blit(self.image, self.rect) return True '''eat fruit''' def eatFruit(self, fruit_sprite_group): eaten_fruit = pygame.sprite.spritecollide(self, fruit_sprite_group, True, None) for fruit in eaten_fruit: self.health_value += fruit.value '''Update character orientation''' def __updateImage(self, direction): directions = ['left', 'right', 'up', 'down'] idx = directions.index(direction) self.image = pygame.image.load(self.imagepaths[idx]) self.image = pygame.transform.scale(self.image, (self.blocksize, self.blocksize))
Bombs need to have a countdown prompt function, and generate flame effects within the killing range of bombs after the countdown (poor, it is estimated that the special effects T_T worth only 10 cents, please bear the burden):
'''Bomb class''' class Bomb(pygame.sprite.Sprite): def __init__(self, imagepath, coordinate, blocksize, digitalcolor, explode_imagepath, **kwargs): pygame.sprite.Sprite.__init__(self) self.image = pygame.image.load(imagepath) self.image = pygame.transform.scale(self.image, (blocksize, blocksize)) self.explode_imagepath = explode_imagepath self.rect = self.image.get_rect() # Pixel position self.rect.left, self.rect.top = coordinate[0] * blocksize, coordinate[1] * blocksize # Coordinates (unit length of element block) self.coordinate = coordinate self.blocksize = blocksize # Explosion countdown self.explode_millisecond = 6000 * 1 - 1 self.explode_second = int(self.explode_millisecond / 1000) self.start_explode = False # Explosion duration self.exploding_count = 1000 * 1 # Bomb damage ability self.harm_value = 1 # Does the bomb still exist self.is_being = True self.font = pygame.font.SysFont('Consolas', 20) self.digitalcolor = digitalcolor '''Draw to screen''' def draw(self, screen, dt, map_parser): if not self.start_explode: # Explosion countdown self.explode_millisecond -= dt self.explode_second = int(self.explode_millisecond / 1000) if self.explode_millisecond < 0: self.start_explode = True screen.blit(self.image, self.rect) text = self.font.render(str(self.explode_second), True, self.digitalcolor) rect = text.get_rect(center=(self.rect.centerx-5, self.rect.centery+5)) screen.blit(text, rect) return False else: # Continuous countdown to explosion self.exploding_count -= dt if self.exploding_count > 0: return self.__explode(screen, map_parser) else: self.is_being = False return False '''Explosion effect''' def __explode(self, screen, map_parser): explode_area = self.__calcExplodeArea(map_parser.instances_list) for each in explode_area: image = pygame.image.load(self.explode_imagepath) image = pygame.transform.scale(image, (self.blocksize, self.blocksize)) rect = image.get_rect() rect.left, rect.top = each[0] * self.blocksize, each[1] * self.blocksize screen.blit(image, rect) return explode_area '''Calculate explosion area''' def __calcExplodeArea(self, instances_list): explode_area = [] # The area calculation rule is that the wall can prevent the explosion from spreading, and the explosion range is only within the range of the game map for ymin in range(self.coordinate[1], self.coordinate[1]-5, -1): if ymin < 0 or instances_list[ymin][self.coordinate[0]] in ['w', 'x', 'z']: break explode_area.append([self.coordinate[0], ymin]) for ymax in range(self.coordinate[1]+1, self.coordinate[1]+5): if ymax >= len(instances_list) or instances_list[ymax][self.coordinate[0]] in ['w', 'x', 'z']: break explode_area.append([self.coordinate[0], ymax]) for xmin in range(self.coordinate[0], self.coordinate[0]-5, -1): if xmin < 0 or instances_list[self.coordinate[1]][xmin] in ['w', 'x', 'z']: break explode_area.append([xmin, self.coordinate[1]]) for xmax in range(self.coordinate[0]+1, self.coordinate[0]+5): if xmax >= len(instances_list[0]) or instances_list[self.coordinate[1]][xmax] in ['w', 'x', 'z']: break explode_area.append([xmax, self.coordinate[1]]) return explode_area
Because the bomb class and character class are bound to the game screen every frame, some countdown operations are written into the draw function. Of course, it is best to rewrite a function to realize this function, so the code structure will look clearer.
Next, we're in Design our game map in the map file:
Then it is parsed through a map parsing class Map file, so that you only need to import a new one each time you switch levels The map file is OK, which is also convenient for subsequent expansion of the game:
'''.map file parser ''' class mapParser(): def __init__(self, mapfilepath, bg_paths, wall_paths, blocksize, **kwargs): self.instances_list = self.__parse(mapfilepath) self.bg_paths = bg_paths self.wall_paths = wall_paths self.blocksize = blocksize self.height = len(self.instances_list) self.width = len(self.instances_list[0]) self.screen_size = (blocksize * self.width, blocksize * self.height) '''Draw the map on the screen''' def draw(self, screen): for j in range(self.height): for i in range(self.width): instance = self.instances_list[j][i] if instance == 'w': elem = Wall(self.wall_paths[0], [i, j], self.blocksize) elif instance == 'x': elem = Wall(self.wall_paths[1], [i, j], self.blocksize) elif instance == 'z': elem = Wall(self.wall_paths[2], [i, j], self.blocksize) elif instance == '0': elem = Background(self.bg_paths[0], [i, j], self.blocksize) elif instance == '1': elem = Background(self.bg_paths[1], [i, j], self.blocksize) elif instance == '2': elem = Background(self.bg_paths[2], [i, j], self.blocksize) else: raise ValueError('instance parse error in mapParser.draw...') elem.draw(screen) '''Random acquisition of an open space''' def randomGetSpace(self, used_spaces=None): while True: i = random.randint(0, self.width-1) j = random.randint(0, self.height-1) coordinate = [i, j] if used_spaces and coordinate in used_spaces: continue instance = self.instances_list[j][i] if instance in ['0', '1', '2']: break return coordinate '''Get element type from coordinates''' def getElemByCoordinate(self, coordinate): return self.instances_list[coordinate[1]][coordinate[0]] '''analysis.map file''' def __parse(self, mapfilepath): instances_list = [] with open(mapfilepath) as f: for line in f.readlines(): instances_line_list = [] for c in line: if c in ['w', 'x', 'z', '0', '1', '2']: instances_line_list.append(c) instances_list.append(instances_line_list) return instances_list
OK, after these preparations, you can start to write the main cycle of the game:
'''Game main program''' def main(cfg): # initialization pygame.init() pygame.mixer.init() pygame.mixer.music.load(cfg.BGMPATH) pygame.mixer.music.play(-1, 0.0) screen = pygame.display.set_mode(cfg.SCREENSIZE) pygame.display.set_caption('Q Version bubble Hall - more daring') # Start interface Interface(screen, cfg, mode='game_start') # Game main loop font = pygame.font.SysFont('Consolas', 15) for gamemap_path in cfg.GAMEMAPPATHS: # -Maps map_parser = mapParser(gamemap_path, bg_paths=cfg.BACKGROUNDPATHS, wall_paths=cfg.WALLPATHS, blocksize=cfg.BLOCKSIZE) # -Fruit fruit_sprite_group = pygame.sprite.Group() used_spaces = [] for i in range(5): coordinate = map_parser.randomGetSpace(used_spaces) used_spaces.append(coordinate) fruit_sprite_group.add(Fruit(random.choice(cfg.FRUITPATHS), coordinate=coordinate, blocksize=cfg.BLOCKSIZE)) # -Our Hero coordinate = map_parser.randomGetSpace(used_spaces) used_spaces.append(coordinate) ourhero = Hero(imagepaths=cfg.HEROZELDAPATHS, coordinate=coordinate, blocksize=cfg.BLOCKSIZE, map_parser=map_parser, hero_name='ZELDA') # -Computer Hero aihero_sprite_group = pygame.sprite.Group() coordinate = map_parser.randomGetSpace(used_spaces) aihero_sprite_group.add(Hero(imagepaths=cfg.HEROBATMANPATHS, coordinate=coordinate, blocksize=cfg.BLOCKSIZE, map_parser=map_parser, hero_name='BATMAN')) used_spaces.append(coordinate) coordinate = map_parser.randomGetSpace(used_spaces) aihero_sprite_group.add(Hero(imagepaths=cfg.HERODKPATHS, coordinate=coordinate, blocksize=cfg.BLOCKSIZE, map_parser=map_parser, hero_name='DK')) used_spaces.append(coordinate) # -bomb bomb_sprite_group = pygame.sprite.Group() # -flag used to judge the victory or failure of the game is_win_flag = False # -Main cycle screen = pygame.display.set_mode(map_parser.screen_size) clock = pygame.time.Clock() while True: dt = clock.tick(cfg.FPS) for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() sys.exit(-1) # --↑↓←→ key to control up, down, left and right, space bar to drop bomb elif event.type == pygame.KEYDOWN: if event.key == pygame.K_UP: ourhero.move('up') elif event.key == pygame.K_DOWN: ourhero.move('down') elif event.key == pygame.K_LEFT: ourhero.move('left') elif event.key == pygame.K_RIGHT: ourhero.move('right') elif event.key == pygame.K_SPACE: if ourhero.bomb_cooling_count <= 0: bomb_sprite_group.add(ourhero.generateBomb(imagepath=cfg.BOMBPATH, digitalcolor=cfg.YELLOW, explode_imagepath=cfg.FIREPATH)) screen.fill(cfg.WHITE) # --Computer Hero random action for hero in aihero_sprite_group: action, flag = hero.randomAction(dt) if flag and action == 'dropbomb': bomb_sprite_group.add(hero.generateBomb(imagepath=cfg.BOMBPATH, digitalcolor=cfg.YELLOW, explode_imagepath=cfg.FIREPATH)) # --Eat fruit and add HP (as long as Hero can add) ourhero.eatFruit(fruit_sprite_group) for hero in aihero_sprite_group: hero.eatFruit(fruit_sprite_group) # --Game elements are bound to the screen map_parser.draw(screen) for bomb in bomb_sprite_group: if not bomb.is_being: bomb_sprite_group.remove(bomb) explode_area = bomb.draw(screen, dt, map_parser) if explode_area: # --Hero's health within the explosion flame range will continue to decrease if ourhero.coordinate in explode_area: ourhero.health_value -= bomb.harm_value for hero in aihero_sprite_group: if hero.coordinate in explode_area: hero.health_value -= bomb.harm_value fruit_sprite_group.draw(screen) for hero in aihero_sprite_group: hero.draw(screen, dt) ourhero.draw(screen, dt) # --The upper left corner displays health pos_x = showText(screen, font, text=ourhero.hero_name+'(our):'+str(ourhero.health_value), color=cfg.YELLOW, position=[5, 5]) for hero in aihero_sprite_group: pos_x, pos_y = pos_x+15, 5 pos_x = showText(screen, font, text=hero.hero_name+'(ai):'+str(hero.health_value), color=cfg.YELLOW, position=[pos_x, pos_y]) # --If our player's HP is less than or equal to 0 / the computer player's HP is less than or equal to 0, the game is judged to be over if ourhero.health_value <= 0: is_win_flag = False break for hero in aihero_sprite_group: if hero.health_value <= 0: aihero_sprite_group.remove(hero) if len(aihero_sprite_group) == 0: is_win_flag = True break pygame.display.update() clock.tick(cfg.FPS) if is_win_flag: Interface(screen, cfg, mode='game_switch') else: break Interface(screen, cfg, mode='game_end')
Effect display
Start interface
Game interface
The logic is very simple, that is, after initialization, import the level map to start the game. After the end of a level, judge whether the game wins or fails. If the game wins, enter the next level, otherwise exit the main cycle and let the player choose whether to restart the game. I can understand the specific details by looking at the code. I've added all the necessary comments.
Many children always encounter some problems and bottlenecks when learning python. They have no sense of direction and don't know where to start to improve. I have sorted out some materials and hope to help them. You can add Python learning and communication skirt: 773162165
Well, today's game is for everyone here, Amway, what do not understand, you can comment below, you need to find the source code can be found Xiaobian yo, remember to pay attention to Xiaobian official account ha.