preface
This time I'll take you to write a little angry bird game that was popular for some time.
No more nonsense. Let's start happily~
development tool
**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
Here is a brief introduction to the implementation principle of the game. The first is the start interface of the game, which is probably long and simple:
It mainly includes two parts, namely, the title of the game, the start and exit buttons of the game. The code implementation of these two parts is as follows:
'''Button class''' class Button(pygame.sprite.Sprite): def __init__(self, screen, x, y, width, height, action=None, color_not_active=(189, 195, 199), color_active=(189, 195, 199)): pygame.sprite.Sprite.__init__(self) self.x = x self.y = y self.width = width self.height = height self.action = action self.screen = screen self.color_active = color_active self.color_not_active = color_not_active '''Add text''' def addtext(self, text, size=20, font='Times New Roman', color=(0, 0, 0)): self.font = pygame.font.Font(font, size) self.text = self.font.render(text, True, color) self.text_pos = self.text.get_rect() self.text_pos.center = (self.x + self.width / 2, self.y + self.height / 2) '''Is it selected by the mouse''' def selected(self): pos = pygame.mouse.get_pos() if (self.x < pos[0] < self.x + self.width) and (self.y < pos[1] < self.y + self.height): return True return False '''Draw to screen''' def draw(self): if self.selected(): pygame.draw.rect(self.screen, self.color_active, (self.x, self.y, self.width, self.height)) else: pygame.draw.rect(self.screen, self.color_not_active, (self.x, self.y, self.width, self.height)) if hasattr(self, 'text'): self.screen.blit(self.text, self.text_pos) '''Text label class''' class Label(pygame.sprite.Sprite): def __init__(self, screen, x, y, width, height): pygame.sprite.Sprite.__init__(self) self.x = x self.y = y self.width = width self.height = height self.screen = screen '''Add text''' def addtext(self, text, size=20, font='Times New Roman', color=(0, 0, 0)): self.font = pygame.font.Font(font, size) self.text = self.font.render(text, True, color) self.text_pos = self.text.get_rect() self.text_pos.center = (self.x + self.width / 2, self.y + self.height / 2) '''Draw to screen''' def draw(self): if hasattr(self, 'text'): self.screen.blit(self.text, self.text_pos) Copy code
In fact, the implementation is relatively simple. The button class is an additional function that changes the color after being selected by the mouse (that is, when the position of the mouse falls within the area of the button) to intuitively tell the player that the button has been selected.
If the player clicks the exit key (QUIT), he will exit the game:
def quitgame(): pygame.quit() sys.exit() Click the start game button to start the game: def startgame(): game_levels = GameLevels(cfg, screen) game_levels.start() Copy code
The game interface looks like this:
The way for players to win is to operate a limited number of birds to kill all the invading pigs. In other words, use a slingshot to launch birds and let the birds hit all the pigs on the field. If there are still pigs on the field that are not hit after all the birds are launched, the player will fail. The code for judging the relationship between victory and defeat of the game is actually quite simple. It is probably like this:
'''Game status''' def status(self, pigs, birds): status_codes = { 'gaming': 0, 'failure': 1, 'victory': 2, } if len(pigs) == 0: return status_codes['victory'] elif len(birds) == 0: return status_codes['failure'] else: return status_codes['gaming'] Copy code
Then, in order to implement the game, we first define all the game wizard classes we need. First, our protagonist, angry bird:
'''Bird''' class Bird(pygame.sprite.Sprite): def __init__(self, screen, imagepaths, loc_info, velocity=None, color=(255, 255, 255), **kwargs): pygame.sprite.Sprite.__init__(self) assert len(loc_info) == 3 assert len(imagepaths) == 1 # Set the necessary property constants self.color = color self.screen = screen self.loc_info = list(loc_info) self.imagepaths = imagepaths self.velocity = VelocityVector() if velocity is None else velocity self.type = 'bird' self.fly_path = [] self.is_dead = False self.elasticity = 0.8 self.is_loaded = False self.is_selected = False self.inverse_friction = 0.99 self.gravity = VelocityVector(0.2, math.pi) # Screen size self.screen_size = screen.get_rect().size self.screen_size = (self.screen_size[0], self.screen_size[1] - 50) # import images self.image = pygame.image.load(imagepaths[0]) '''Draw to screen''' def draw(self): if not self.is_loaded: for point in self.fly_path: pygame.draw.ellipse(self.screen, self.color, (point[0], point[1], 3, 3), 1) position = self.loc_info[0] - self.loc_info[2], self.loc_info[1] - self.loc_info[2] self.screen.blit(self.image, position) '''Determine whether it is selected by the mouse''' def selected(self): pos = pygame.mouse.get_pos() dx, dy = pos[0] - self.loc_info[0], pos[1] - self.loc_info[1] dist = math.hypot(dy, dx) if dist < self.loc_info[2]: return True return False '''Load on slingshot''' def load(self, slingshot): self.loc_info[0], self.loc_info[1] = slingshot.x, slingshot.y self.is_loaded = True '''Reset position''' def reposition(self, slingshot): pos = pygame.mouse.get_pos() if self.selected: self.loc_info[0], self.loc_info[1] = pos[0], pos[1] dx, dy = slingshot.x - self.loc_info[0], slingshot.y - self.loc_info[1] self.velocity.magnitude = min(int(math.hypot(dx, dy) / 2), 80) self.velocity.angle = math.pi / 2 + math.atan2(dy, dx) '''Displays the path to launch the bird''' def projectpath(self): if self.is_loaded: path = [] bird = Bird(self.screen, self.imagepaths, self.loc_info, velocity=self.velocity) for i in range(30): bird.move() if i % 5 == 0: path.append((bird.loc_info[0], bird.loc_info[1])) for point in path: pygame.draw.ellipse(self.screen, self.color, (point[0], point[1], 2, 2)) '''Moving birds''' def move(self): # Change the bird's velocity vector according to gravity self.velocity = VectorAddition(self.velocity, self.gravity) self.loc_info[0] += self.velocity.magnitude * math.sin(self.velocity.angle) self.loc_info[1] -= self.velocity.magnitude * math.cos(self.velocity.angle) self.velocity.magnitude *= self.inverse_friction # Width out of screen if self.loc_info[0] > self.screen_size[0] - self.loc_info[2]: self.loc_info[0] = 2 * (self.screen_size[0] - self.loc_info[2]) - self.loc_info[0] self.velocity.angle *= -1 self.velocity.magnitude *= self.elasticity elif self.loc_info[0] < self.loc_info[2]: self.loc_info[0] = 2 * self.loc_info[2] - self.loc_info[0] self.velocity.angle *= -1 self.velocity.magnitude *= self.elasticity # Height out of screen if self.loc_info[1] > self.screen_size[1] - self.loc_info[2]: self.loc_info[1] = 2 * (self.screen_size[1] - self.loc_info[2]) - self.loc_info[1] self.velocity.angle = math.pi - self.velocity.angle self.velocity.magnitude *= self.elasticity elif self.loc_info[1] < self.loc_info[2]: self.loc_info[1] = 2 * self.loc_info[2] - self.loc_info[1] self.velocity.angle = math.pi - self.velocity.angle self.velocity.magnitude *= self.elasticity Copy code
The main consideration to realize it is that there are actually five states:
Queue state, that is, the state when waiting to enter the catapult in the lower left corner, just stand still;
Ready state, that is, the state of being ready to launch into the catapult, needs to move continuously with the mouse, allowing the player to know what kind of angle and path he will be adjusting after the game player has been launched.
The flight state, that is, the state after being launched by the slingshot, needs to calculate its flight path and move continuously according to the gravity and the initial speed of the bird;
Collision state, i.e. when it strikes other objects such as pigs and wooden piles during flight, the motion state changes;
Static state, that is, the state when the bird finally stands still after completing the flight state.
Next, let's realize the pig:
'''pig''' class Pig(pygame.sprite.Sprite): def __init__(self, screen, imagepaths, loc_info, velocity=None, **kwargs): pygame.sprite.Sprite.__init__(self) assert len(loc_info) == 3 assert len(imagepaths) == 3 # Set the necessary property constants self.screen = screen self.loc_info = list(loc_info) self.imagepaths = imagepaths self.velocity = VelocityVector() if velocity is None else velocity self.type = 'pig' self.is_dead = False self.elasticity = 0.8 self.switch_freq = 20 self.animate_count = 0 self.inverse_friction = 0.99 self.gravity = VelocityVector(0.2, math.pi) # Screen size self.screen_size = screen.get_rect().size self.screen_size = (self.screen_size[0], self.screen_size[1] - 50) # import images self.pig_images = [] for imagepath in imagepaths: self.pig_images.append(pygame.image.load(imagepath)) # Set current image self.image = random.choice(self.pig_images[:2]) '''Draw to screen''' def draw(self): self.animate_count += 1 if (self.animate_count % self.switch_freq == 0) and (not self.is_dead): self.animate_count = 0 self.image = random.choice(self.pig_images[:2]) position = self.loc_info[0] - self.loc_info[2], self.loc_info[1] - self.loc_info[2] self.screen.blit(self.image, position) '''Mobile pig''' def move(self): # Change the pig's velocity vector according to gravity self.velocity = VectorAddition(self.velocity, self.gravity) self.loc_info[0] += self.velocity.magnitude * math.sin(self.velocity.angle) self.loc_info[1] -= self.velocity.magnitude * math.cos(self.velocity.angle) self.velocity.magnitude *= self.inverse_friction # Width out of screen if self.loc_info[0] > self.screen_size[0] - self.loc_info[2]: self.loc_info[0] = 2 * (self.screen_size[0] - self.loc_info[2]) - self.loc_info[0] self.velocity.angle *= -1 self.velocity.magnitude *= self.elasticity elif self.loc_info[0] < self.loc_info[2]: self.loc_info[0] = 2 * self.loc_info[2] - self.loc_info[0] self.velocity.angle *= -1 self.velocity.magnitude *= self.elasticity # Height out of screen if self.loc_info[1] > self.screen_size[1] - self.loc_info[2]: self.loc_info[1] = 2 * (self.screen_size[1] - self.loc_info[2]) - self.loc_info[1] self.velocity.angle = math.pi - self.velocity.angle self.velocity.magnitude *= self.elasticity elif self.loc_info[1] < self.loc_info[2]: self.loc_info[1] = 2 * self.loc_info[2] - self.loc_info[1] self.velocity.angle = math.pi - self.velocity.angle self.velocity.magnitude *= self.elasticity '''The pig died''' def setdead(self): self.is_dead = True self.image = self.pig_images[-1] Copy code
Pigs mainly include three states in the game:
Static state, that is, the state of being stationary somewhere when not hit;
The motion state after being hit, that is, the state when it runs together according to the principle of momentum conservation after being hit by other objects;
The static state after being hit, that is, the state when motion is generated due to being hit and then returns to static
The implementation principle of wood blocks in the game is similar to that of pigs:
'''Wooden blocks in the map''' class Block(pygame.sprite.Sprite): def __init__(self, screen, imagepaths, loc_info, velocity=None, **kwargs): pygame.sprite.Sprite.__init__(self) assert len(loc_info) == 3 assert len(imagepaths) == 2 # Set the necessary property constants self.type = 'block' self.screen = screen self.loc_info = list(loc_info) self.imagepaths = imagepaths self.velocity = VelocityVector() if velocity is None else velocity self.elasticity = 0.7 self.is_destroyed = False self.inverse_friction = 0.99 self.gravity = VelocityVector(0.2, math.pi) # import images self.block_images = [] for imagepath in imagepaths: self.block_images.append(pygame.transform.scale(pygame.image.load(imagepath), (100, 100))) # Screen size self.screen_size = screen.get_rect().size self.screen_size = (self.screen_size[0], self.screen_size[1] - 50) # Set current image self.image = self.block_images[0] self.rect = self.image.get_rect() self.rotate_angle = math.radians(0) '''Draw to screen''' def draw(self): pygame.transform.rotate(self.image, self.rotate_angle) self.screen.blit(self.image, (self.loc_info[0] - self.rect.width // 2, self.loc_info[1])) '''Set to damaged state''' def setdestroy(self): self.is_destroyed = True self.image = self.block_images[1] '''Moving wood block''' def move(self): # Change the velocity vector of the wood block according to gravity self.velocity = VectorAddition(self.velocity, self.gravity) self.loc_info[0] += self.velocity.magnitude * math.sin(self.velocity.angle) self.loc_info[1] -= self.velocity.magnitude * math.cos(self.velocity.angle) self.velocity.magnitude *= self.inverse_friction # Width out of screen if self.loc_info[0] > self.screen_size[0] - self.rect.width: self.loc_info[0] = 2 * (self.screen_size[0] - self.rect.width) - self.loc_info[0] self.velocity.angle *= -1 self.rotate_angle = -self.velocity.angle self.velocity.magnitude *= self.elasticity elif self.loc_info[0] < self.rect.width: self.loc_info[0] = 2 * self.rect.width - self.loc_info[0] self.velocity.angle *= -1 self.rotate_angle = -self.velocity.angle self.velocity.magnitude *= self.elasticity # Height out of screen if self.loc_info[1] > self.screen_size[1] - self.rect.height: self.loc_info[1] = 2 * (self.screen_size[1] - self.rect.height) - self.loc_info[1] self.velocity.angle = math.pi - self.velocity.angle self.rotate_angle = math.pi - self.velocity.angle self.velocity.magnitude *= self.elasticity elif self.loc_info[1] < self.rect.height: self.loc_info[1] = 2 * self.rect.height - self.loc_info[1] self.velocity.angle = math.pi - self.velocity.angle self.rotate_angle = math.pi - self.velocity.angle self.velocity.magnitude *= self.elasticity Copy code
Finally, let's implement the wall and slingshot:
'''Slingshot''' class Slingshot(pygame.sprite.Sprite): def __init__(self, screen, x, y, width, height, color=(66, 73, 73), line_color=(100, 30, 22), **kwargs): pygame.sprite.Sprite.__init__(self) self.x = x self.y = y self.color = color self.width = width self.height = height self.screen = screen self.line_color = line_color self.type = 'slingshot' '''Draw to screen''' def draw(self, bird=None): pygame.draw.rect(self.screen, self.color, (self.x, self.y + self.height * 1 / 3, self.width, self.height * 2 / 3)) if bird is not None and bird.is_loaded: pygame.draw.line(self.screen, self.line_color, (self.x, self.y + self.height / 6), (bird.loc_info[0], bird.loc_info[1] + bird.loc_info[2] / 2), 10) pygame.draw.line(self.screen, self.line_color, (self.x + self.width, self.y + self.height / 6), (bird.loc_info[0] + bird.loc_info[2], bird.loc_info[1] + bird.loc_info[2] / 2), 10) pygame.draw.rect(self.screen, self.color, (self.x - self.width / 4, self.y, self.width / 2, self.height / 3), 5) pygame.draw.rect(self.screen, self.color, (self.x + self.width * 3 / 4, self.y, self.width / 2, self.height / 3), 5) '''wall''' class Slab(pygame.sprite.Sprite): def __init__(self, screen, imagepaths, x, y, width, height, color=(255, 255, 255)): pygame.sprite.Sprite.__init__(self) self.x = x self.y = y self.color = color self.width = width self.height = height self.screen = screen self.imagepaths = imagepaths if self.width > self.height: self.image = pygame.image.load(self.imagepaths[0]) else: self.image = pygame.image.load(self.imagepaths[1]) self.image = pygame.transform.scale(self.image, (self.width, self.height)) self.type = 'wall' '''Draw to screen''' def draw(self): self.screen.blit(self.image, (self.x, self.y)) Copy code
Therefore, we have completed the definition of all game sprites and can start to realize the main cycle of the game. The specific code is as follows:
'''Start the game''' def start(self): # Import all game sprites game_sprites = self.loadlevelmap() birds, pigs, blocks, walls = game_sprites['birds'], game_sprites['pigs'], game_sprites['blocks'], game_sprites['walls'] slingshot = Slingshot(self.screen, 200, self.screen_size[1] - 200, 30, 200) birds[0].load(slingshot) score_label = Label(self.screen, 50, 10, 100, 50) score_label.addtext(f'SCORE: {self.score}', 25, self.cfg.FONTPATH['Comic_Kings'], (236, 240, 241)) birds_remaining_label = Label(self.screen, 120, 50, 100, 50) birds_remaining_label.addtext(f"BIRDS REMAINING: {len(birds)}", 25, self.cfg.FONTPATH['Comic_Kings'], (236, 240, 241)) pigs_remaining_label = Label(self.screen, 110, 90, 100, 50) pigs_remaining_label.addtext(f"PIGS REMAINING: {len(pigs)}", 25, self.cfg.FONTPATH['Comic_Kings'], (236, 240, 241)) carles_label = Label(self.screen, self.screen_size[0] - 270, self.screen_size[1] - 20, 300, 100) carles_label.addtext('CARLES', 60, self.cfg.FONTPATH['arfmoochikncheez'], (113, 125, 126)) # Game main loop clock = pygame.time.Clock() blocks_to_remove, pigs_to_remove = [], [] while True: # --Key detection for event in pygame.event.get(): if event.type == pygame.QUIT: self.quitgame() elif event.type == pygame.KEYDOWN: if event.key == pygame.K_q: self.quitgame() elif event.key == pygame.K_r: self.start() elif event.key == pygame.K_p or event.key == pygame.K_ESCAPE: self.pauseinterface() elif event.type == pygame.MOUSEBUTTONDOWN: if birds[0].selected(): birds[0].is_selected = True elif event.type == pygame.MOUSEBUTTONUP: if birds[0].is_selected: birds[0].is_selected = False birds[0].start_flying = True # --Background color fill color = self.cfg.BACKGROUND_COLOR for i in range(3): color = (color[0] + 5, color[1] + 5, color[2] + 5) pygame.draw.rect(self.screen, color, (0, i * 300, self.screen_size[0], 300)) pygame.draw.rect(self.screen, (77, 86, 86), (0, self.screen_size[1], self.screen_size[0], 50)) # --Judge whether the game is over. If not, import a new bird if (not birds[0].is_loaded) and self.still(pigs + birds + blocks): birds.pop(0) if self.status(pigs, birds) == 2: self.score += len(birds) * 100 self.switchlevelinterface() elif self.status(pigs, birds) == 1: self.failureinterface() birds[0].load(slingshot) birds[0].start_flying = False # --Reset the position of the bird if birds[0].is_selected: birds[0].reposition(slingshot) if hasattr(birds[0], 'start_flying') and birds[0].start_flying: birds[0].is_loaded = False # --Catapult slingshot.draw(birds[0]) # --Judge whether the pig hit the stake for i in range(len(pigs)): for j in range(len(blocks)): pig_magnitude_1, block_magnitude_1 = pigs[i].velocity.magnitude, blocks[j].velocity.magnitude pigs[i], blocks[j], is_collision = self.collision(pigs[i], blocks[j]) pig_magnitude_2, block_magnitude_2 = pigs[i].velocity.magnitude, blocks[j].velocity.magnitude if is_collision: if abs(pig_magnitude_2 - pig_magnitude_2) > 2: blocks_to_remove.append(blocks[j]) blocks[j].setdestroy() if abs(block_magnitude_2 - block_magnitude_1) > 2: pigs_to_remove.append(pigs[i]) pigs[i].setdead() # --Judge whether the bird hit the stake for i in range(len(birds)): if not (birds[i].is_loaded or birds[i].velocity.magnitude == 0): for j in range(len(blocks)): bird_magnitude_1, block_magnitude_1 = birds[i].velocity.magnitude, blocks[j].velocity.magnitude birds[i], blocks[j], is_collision = self.collision(birds[i], blocks[j]) bird_magnitude_2, block_magnitude_2 = birds[i].velocity.magnitude, blocks[j].velocity.magnitude if is_collision: if abs(bird_magnitude_1 - bird_magnitude_2) > 2: if blocks[j] not in blocks_to_remove: blocks_to_remove.append(blocks[j]) blocks[j].setdestroy() # --Judge whether the pig hit the pig or the pig hit the wall for i in range(len(pigs)): pigs[i].move() for j in range(i+1, len(pigs)): pig1_magnitude_1, pig2_magnitude_1 = pigs[i].velocity.magnitude, pigs[j].velocity.magnitude pigs[i], pigs[j], is_collision = self.collision(pigs[i], pigs[j]) pig1_magnitude_2, pig2_magnitude_2 = pigs[i].velocity.magnitude, pigs[j].velocity.magnitude if abs(pig1_magnitude_1 - pig1_magnitude_2) > 2: if pigs[j] not in pigs_to_remove: pigs_to_remove.append(pigs[j]) pigs[j].setdead() if abs(pig2_magnitude_1 - pig2_magnitude_2) > 2: if pigs[i] not in pigs_to_remove: pigs_to_remove.append(pigs[i]) pigs[i].setdead() for wall in walls: pigs[i] = self.collision(pigs[i], wall)[0] pigs[i].draw() # --Judge whether the bird hit the pig or the bird hit the wall for i in range(len(birds)): if (not birds[i].is_loaded) and (birds[i].velocity.magnitude): birds[i].move() for j in range(len(pigs)): bird_magnitude_1, pig_magnitude_1 = birds[i].velocity.magnitude, pigs[j].velocity.magnitude birds[i], pigs[j], is_collision = self.collision(birds[i], pigs[j]) bird_magnitude_2, pig_magnitude_2 = birds[i].velocity.magnitude, pigs[j].velocity.magnitude if is_collision: if abs(bird_magnitude_2 - bird_magnitude_1) > 2: if pigs[j] not in pigs_to_remove: pigs_to_remove.append(pigs[j]) pigs[j].setdead() if birds[i].is_loaded: birds[i].projectpath() for wall in walls: birds[i] = self.collision(birds[i], wall)[0] birds[i].draw() # --Judge whether the stake hit the stake or the stake hit the wall for i in range(len(blocks)): for j in range(i+1, len(blocks)): block1_magnitude_1, block2_magnitude_1 = blocks[i].velocity.magnitude, blocks[j].velocity.magnitude blocks[i], blocks[j], is_collision = self.collision(blocks[i], blocks[j]) block1_magnitude_2, block2_magnitude_2 = blocks[i].velocity.magnitude, blocks[j].velocity.magnitude if is_collision: if abs(block1_magnitude_2 - block1_magnitude_1) > 2: if blocks[j] not in blocks_to_remove: blocks_to_remove.append(blocks[j]) blocks[j].setdestroy() if abs(block2_magnitude_2 - block2_magnitude_1) > 2: if blocks[i] not in blocks_to_remove: blocks_to_remove.append(blocks[i]) blocks[i].setdestroy() blocks[i].move() for wall in walls: blocks[i] = self.collision(blocks[i], wall)[0] blocks[i].draw() # --Wall for wall in walls: wall.draw() # --Display text score_label.addtext(f'SCORE: {self.score}', 25, self.cfg.FONTPATH['Comic_Kings'], (236, 240, 241)) score_label.draw() birds_remaining_label.addtext(f"BIRDS REMAINING: {len(birds)}", 25, self.cfg.FONTPATH['Comic_Kings'], (236, 240, 241)) birds_remaining_label.draw() pigs_remaining_label.addtext(f"PIGS REMAINING: {len(pigs)}", 25, self.cfg.FONTPATH['Comic_Kings'], (236, 240, 241)) pigs_remaining_label.draw() carles_label.draw() # --Screen refresh pygame.display.update() clock.tick(self.cfg.FPS) # --Delete invalid element if self.still(birds + pigs + blocks): for pig in pigs_to_remove: if pig in pigs: pigs.remove(pig) self.score += 100 for block in blocks_to_remove: if block in blocks: blocks.remove(block) self.score += 50 pigs_to_remove = [] blocks_to_remove = [] Copy code
Key detection, collision detection and real-time update of game status such as some scores
This is the end of the article. Thank you for watching. The next article shares the Python memory flop game
In order to thank readers, I would like to share some of my recent collection of programming dry goods with you and give back to every reader. I hope I can help you.
For the complete source code, see the home page background private letter. I 666 click the link. If you contact me to obtain the source code!