这个练习是一个小游戏程序,如果要是给它起个名字的话,应该叫:快躲,香蕉。主要的游戏内容就是,游戏开始会从屏幕上方不断随便的掉一些铁块,在屏幕下方有一个小香蕉是受你控制的,你需要不断的左右移动来躲避铁块。在你躲避完一定数量的铁块之后,就会进入下一关。下一关依然是让你躲铁块,不过铁块下降的速度就快了很多。在游戏中你可以按下任意键暂停,再次按则继续,按下ESC键退出。这就是全部的功能了,下面我们来看游戏的实现。
无论是在实现功能时还是在代码分析的时候,分类归纳总是一个好习惯,这里自然也不例外。
首先对所有代码分类, 1、整体上代码有一个配置模块,来对游戏的速度、屏幕的宽度、香蕉移动速度、字体大小、各个物体的图片等进行配置。 2、然后是有一个元素模块,即游戏中的两个元素落下来的铁块以及被砸的香蕉,其中还要包含他们具有的行为。 3、然后还有游戏中的各种状态模块,状态模块中的类继承关系稍微多一些,处于家谱最上方的就是state类,由它来衍生其他的所有状态,它的直接子类是Level和Pause,其中Pause有衍生出子类Info、levelCleared、GameOver、StartUp。 4、最后就是游戏的主模块,用来让其他模块协调工作的。
然后再来看一个整体图:
上代码:
config.py:
<span style="font-size:14px;"># Configuration file for Squish # ----------------------------- # Feel free to modify the configuration variables below to taste. # If the game is too fast or too slow, try to modify the speed # variables. # Change these to use other images in the game: banana_image = 'banana.png' weight_image = 'weight.png' splash_image = 'weight.png' # Change these to affect the general appearance: screen_size = 800, 600 background_color = 255, 255, 255 margin = 30 full_screen = 0 font_size = 48 # These affect the behavior of the game: drop_speed = 1 banana_speed = 1 speed_increase = 1 weights_per_level = 10 banana_pad_top = 40 banana_pad_side = 20</span>
objects.py:
<span style="font-size:14px;"> import pygame, config, os from random import randrange "This module contains the game objects of the Squish game." class SquishSprite(pygame.sprite.Sprite): """ Generic superclass for all sprites in Squish. The constructor takes care of loading an image, setting up the sprite rect, and the area within which it is allowed to move. That area is governed by the screen size and the margin. """ def __init__(self, image): pygame.sprite.Sprite.__init__(self) self.image = pygame.image.load(image).convert() self.rect = self.image.get_rect() screen = pygame.display.get_surface() shrink = -config.margin * 2 # -60 size = screen.get_rect(); #(0,0,800,600) self.area = screen.get_rect().inflate(shrink, shrink) #(30,30,740,540) print(self.area) class Weight(SquishSprite): """ A falling weight. It uses the SquishSprite constructor to set up its weight image, and will fall with a speed given as a parameter to its constructor. """ def __init__(self, speed): SquishSprite.__init__(self, config.weight_image) self.speed = speed self.reset() def reset(self): """ Move the weight to the top of the screen (just out of sight) and place it at a random horizontal position. """ # random between (30,770) x = randrange(self.area.left, self.area.right) self.rect.midbottom = x, 0 def update(self): """ Move the weight vertically (downwards) a distance corresponding to its speed. Also set the landed attribute according to whether it has reached the bottom of the screen. """ self.rect.top += self.speed self.landed = self.rect.top >= self.area.bottom class Banana(SquishSprite): """ A desperate banana. It uses the SquishSprite constructor to set up its banana image, and will stay near the bottom of the screen, with its horizontal position governed by the current mouse position (within certain limits). """ def __init__(self): SquishSprite.__init__(self, config.banana_image) self.rect.bottom = self.area.bottom # These paddings represent parts of the image where there is # no banana. If a weight moves into these areas, it doesn't # constitute a hit (or, rather, a squish): self.pad_top = config.banana_pad_top self.pad_side = config.banana_pad_side def update(self): """ Set the Banana's center x-coordinate to the current mouse x-coordinate, and then use the rect method clamp to ensure that the Banana stays within its allowed range of motion. """ self.rect.centerx = pygame.mouse.get_pos()[0] self.rect = self.rect.clamp(self.area) def touches(self, other): """ Determines whether the banana touches another sprite (e.g., a Weight). Instead of just using the rect method colliderect, a new rectangle is first calculated (using the rect method inflate with the side and top paddings) that does not include the 'empty' areas on the top and sides of the banana. """ # Deflate the bounds with the proper padding: bounds = self.rect.inflate(-self.pad_side, -self.pad_top) # Move the bounds so they are placed at the bottom of the Banana: bounds.bottom = self.rect.bottom # Check whether the bounds intersect with the other object's rect: return bounds.colliderect(other.rect)</span>
<span style="font-size:14px;">import os, sys, pygame from pygame.locals import * import objects, config import time "This module contains the main game logic of the Squish game." class State: """ A generic game state class that can handle events and display itself on a given surface. """ def handle(self, event): """ Default event handling only deals with quitting. """ if event.type == QUIT: sys.exit() if event.type == KEYDOWN and event.key == K_ESCAPE: sys.exit() def firstDisplay(self, screen): """ Used to display the State for the first time. Fills the screen with the background color. """ screen.fill(config.background_color) # Remember to call flip, to make the changes visible: pygame.display.flip() def display(self, screen): """ Used to display the State after it has already been displayed once. The default behavior is to do nothing. """ pass class Level(State): """ A game level. Takes care of counting how many weights have been dropped, moving the sprites around, and other tasks relating to game logic. """ def __init__(self, number=1): self.number = number # How many weights remain to dodge in this level? self.remaining = config.weights_per_level speed = config.drop_speed # One speed_increase added for each level above 1: speed += (self.number-1) * config.speed_increase # Create the weight and banana: self.weight = objects.Weight(speed) self.banana = objects.Banana() both = self.weight, self.banana # This could contain more sprites... self.sprites = pygame.sprite.RenderUpdates(both) def update(self, game): "Updates the game state from the previous frame." # Update all sprites: self.sprites.update() # If the banana touches the weight, tell the game to switch to # a GameOver state: if self.banana.touches(self.weight): game.nextState = GameOver() # Otherwise, if the weight has landed, reset it. If all the # weights of this level have been dodged, tell the game to # switch to a LevelCleared state: elif self.weight.landed: self.weight.reset() self.remaining -= 1 if self.remaining == 0: game.nextState = LevelCleared(self.number) def display(self, screen): """ Displays the state after the first display (which simply wipes the screen). As opposed to firstDisplay, this method uses pygame.display.update with a list of rectangles that need to be updated, supplied from self.sprites.draw. """ screen.fill(config.background_color) updates = self.sprites.draw(screen) pygame.display.update(updates) class Paused(State): """ A simple, paused game state, which may be broken out of by pressing either a keyboard key or the mouse button. """ finished = 0 # Has the user ended the pause? image = None # Set this to a file name if you want an image text = '' # Set this to some informative text def handle(self, event): """ Handles events by delegating to State (which handles quitting in general) and by reacting to key presses and mouse clicks. If a key is pressed or the mouse is clicked, self.finished is set to true. """ State.handle(self, event) if event.type in [MOUSEBUTTONDOWN, KEYDOWN]: self.finished = 1 def update(self, game): """ Update the level. If a key has been pressed or the mouse has been clicked (i.e., self.finished is true), tell the game to move to the state represented by self.nextState() (should be implemented by subclasses). """ if self.finished: game.nextState = self.nextState() def firstDisplay(self, screen): """ The first time the Paused state is displayed, draw the image (if any) and render the text. """ # First, clear the screen by filling it with the background color: screen.fill(config.background_color) # Create a Font object with the default appearance, and specified size: font = pygame.font.Font(None, config.font_size) # Get the lines of text in self.text, ignoring empty lines at # the top or bottom: lines = self.text.strip().splitlines() # Calculate the height of the text (using font.get_linesize() # to get the height of each line of text): height = len(lines) * font.get_linesize() # Calculate the placement of the text (centered on the screen): center, top = screen.get_rect().center # 为400,300 top -= height // 2 # 260 # If there is an image to display... if self.image: # load it: image = pygame.image.load(self.image).convert() # get its rect: r = image.get_rect() #为rect(0,0,166,132) # move the text down by half the image height: top += r.height // 2 #326 # place the image 20 pixels above the text: r.midbottom = center, top - 20 # 400,306 # blit the image to the screen: screen.blit(image, r) antialias = 1 # Smooth the text black = 0, 0, 0 # Render it as black # Render all the lines, starting at the calculated top, and # move down font.get_linesize() pixels for each line: for line in lines: text = font.render(line.strip(), antialias, black) r = text.get_rect() # 0,0,312,37 r.midtop = center, top # 400,326 screen.blit(text, r) top += font.get_linesize() # Display all the changes: pygame.display.flip() class Info(Paused): """ A simple paused state that displays some information about the game. It is followed by a Level state (the first level). """ nextState = Level text = ''' In this game you are a banana, trying to survive a course in self-defense against fruit, where the participants will "defend" themselves against you with a 16 ton weight.''' class StartUp(Paused): """ A paused state that displays a splash image and a welcome message. It is followed by an Info state. """ nextState = Info image = config.splash_image text = ''' Welcome to Squish, the game of Fruit Self-Defense''' class LevelCleared(Paused): """ A paused state that informs the user that he or she has cleared a given level. It is followed by the next level state. """ def __init__(self, number): self.number = number self.text = '''Level %i cleared Click to start next level''' % self.number def nextState(self): return Level(self.number+1) class GameOver(Paused): """ A state that informs the user that he or she has lost the game. It is followed by the first level. """ nextState = Level text = ''' Game Over Click to Restart, Esc to Quit''' class Game: """ A game object that takes care of the main event loop, including changing between the different game states. """ def __init__(self, *args): # Get the directory where the game and the images are located: path = os.path.abspath(args[0]) #当前代码文件路径 dir = os.path.split(path)[0] #代码目录 # Move to that directory (so that the images files may be # opened later on): os.chdir(dir) #cd到代码目录 # Start with no state: self.state = None # Move to StartUp in the first event loop iteration: self.nextState = StartUp() def run(self): """ This method sets things in motion. It performs some vital initialization tasks, and enters the main event loop. """ pygame.init() # This is needed to initialize all the pygame modules # Decide whether to display the game in a window or to use the # full screen: flag = 0 # Default (window) mode if config.full_screen: flag = FULLSCREEN # Full screen mode screen_size = config.screen_size screen = pygame.display.set_mode(screen_size, flag) pygame.display.set_caption('Fruit Self Defense') pygame.mouse.set_visible(False) # The main loop: while True: # (1) If nextState has been changed, move to the new state, and # display it (for the first time): if self.state != self.nextState: self.state = self.nextState self.state.firstDisplay(screen) # (2) Delegate the event handling to the current state: for event in pygame.event.get(): self.state.handle(event) # (3) Update the current state: self.state.update(self) # (4) Display the current state: # time.sleep( 0.5 ) self.state.display(screen) if __name__ == '__main__': #print(sys.argv) game = Game(*sys.argv) game.run() </span>