模块化编程概述与拦球动画

在上一节中,我们设计了Ball类。如果程序中还要设计其它类,那么程序就会越来越长。我们使用过from turtle import Turtle这样的代码,它是把Turtle类从海龟模块导入进来的意思。那么,我们也可以来一个from ball import Ball命令导入Ball类。这就需要建立ball.py模块。接下来我们还要建一个Board类。它用来生成一个拦板,以便拦球,让小球们反弹。这个Board类也放在一个单独的叫board.py的模块中。最后就是主程序模块了,一般把它叫main.py。不过,在这里为了便于描述,直接把它叫“拦球动画.py”了。

在本节的Ball类,依旧设计speed属性代表球的水平速度和垂直速度,它是一个列表。不同于上一节的Ball类。由于新建了拦板类,为了让每一个球都能访问拦板,所以给球类增加了一个叫board的参数。还增加了当球碰到拦板的方法,名叫bounce_on_board。在每次update球后,都会调用它,判断一次球是否碰到了拦板。如果碰到了,就让它的垂直速度反向。

球碰到拦板的原理,我们这里不做讨论。我们直接调用了pygame.sprite子模块提供的collide_rect命令判断两个角色是否发生碰撞。很显然,这是通过判断矩形是否重叠实现的。以下是ball.py的源代码:

"""
   Ball类的代码
"""

import pygame
from random import randint
from pygame.locals import *

class Ball(pygame.sprite.Sprite):
    def __init__(self,screen,board):
        """
           screen:所在的屏幕,board:球所要访问的拦板
        """
        self.board = board
        pygame.sprite.Sprite.__init__(self)# 初始化父类
        r = randint(30,50)
        self.radius = r
        self.sw = screen.get_width()       # 获取屏幕宽度
        self.sh = screen.get_height()      # 获取屏幕高度
        self.image = pygame.Surface((r*2,r*2),SRCALPHA)
        pos = r,r                          # 设定圆心坐标
        color = randint(0,255),randint(0,255),randint(0,255)
        pygame.draw.circle(self.image,color,pos,r)
        self.rect = self.image.get_rect()  # 获取矩形对象
        self.rect.center = self.sh//2,self.sw//2
        self.speed = [randint(-5,5),randint(-5,5)]
        
    def update(self):
        """根据speed更新矩形对象"""
        self.rect.move_ip(self.speed)      # 移动矩形对象
        self.bounce_on_board()             # 碰到板子就反弹
        self.bounce_on_edge()              # 碰到边缘就反弹        

    def bounce_on_edge(self):
        """碰到边缘就反弹"""
        # 如果矩形的最右边x坐标大于屏幕宽度或者最左边x坐标小于等于0        
        if self.rect.right>=self.sw or \
           self.rect.left<=0 : self.speed[0] = -self.speed[0]
           
        if self.rect.bottom>=self.sh or \
           self.rect.top<=0 : self.speed[1] = -self.speed[1]

    def bounce_on_board(self):
        """碰到板子就反弹"""
        tf = pygame.sprite.collide_rect(self,self.board)
        if tf :                             # 如果碰到板子
            self.speed[1] = -self.speed[1]

if __name__ == "__main__":

    pass

Board类就比较简单了,它是跟随着鼠标指针左右移动的。下面是board.py的代码

 

"""
   Board类的代码,跟随鼠标指针左右移动的矩形。
"""

import pygame

class Board(pygame.sprite.Sprite):
    def __init__(self,size,pos):
        """初始化方法,size:宽高,pos:坐标"""
        pygame.sprite.Sprite.__init__(self) # 初始化父类
        self.image = pygame.Surface(size)   # 新建图形
        self.image.fill((13,223,223))       # 填充图形
        self.rect = self.image.get_rect(center=pos)
  
    def update(self):
        """更新拦板的坐标"""
        mpos = pygame.mouse.get_pos() # 获取鼠标指针坐标
        self.rect.centerx = mpos[0]   # 设置矩形中央x坐标      

if __name__ == "__main__":

    pass

在主程序中,我们并没有给拦板单独设置一个容器,而是直接让它加入到了名为group的组中,其它代码基本和上一节的main函数的代码一样。以下是拦球动画.py的代码:

 

"""
   拦球动画,本程序导入Ball和Board类,让球碰到拦板会反弹。
"""

from ball import *
from board import *

def main():
    """
       主要函数
    """
    size = 480,360
    screen = pygame.display.set_mode(size)
    pygame.display.set_caption("碰到边缘就反弹的球类演示代码")

    # 生成拦板,前面是板子的宽高,后面是坐标
    b = Board((230,10),(200,size[1]-50))      # 生成拦板
    
    balls = [Ball(screen,b) for i in range(3)]# 生成3个球
    group = pygame.sprite.Group()
    [ball.add(group) for ball in balls]
    group.add(b)                             # 把板子加到组中

    clock = pygame.time.Clock()              # 新建时钟对象
    running = True
    while running:
        for event in pygame.event.get():
            if event.type == QUIT:running=False
            
        group.update()        # 调用组中所有对象的update方法
        
        screen.fill((110,10,60))
        group.draw(screen)   # 在screen上渲染组中的每个对象的surface 
        
        pygame.display.update()
        clock.tick(60)
    pygame.quit()
    
if __name__ == "__main__":

    main()

一个基本的拦球游戏雏形就编写好了。其它的封面,配音相信读者可以自己搞定?

练习:本节的拦板是用鼠标操作,请改成用键盘的左右方向箭头操作。