12 坚持一百秒

图书简介可以看这里:

童晶:《Python游戏趣味编程》新书上架了

本章我们将编写一个坚持一百秒的游戏,玩家通过鼠标控制飞机躲避飞舞的小球,效果如图12-1所示。首先学习面向对象编程的知识,利用类和对象实现一个新版本的小球反弹程序;然后实现飞机控制与失败判定、生命显示、游戏音效等功能;最后学习继承的概念,快速添加一种新的智能小球。

本章案例最终代码一共99行,代码参看:配套资源\第12章\12-4.py,视频效果参看:配套资源\第12章\坚持一百秒.mp4。

import pgzrun  # 导入游戏库
import random  # 导入随机库
WIDTH = 600   # 设置窗口的宽度
HEIGHT = 800  # 设置窗口的高度
time = 0  # 游戏坚持的时间
hero = Actor('hero')  # 导入玩家飞机图片
live = 3 # 飞机一共3条命

livePics = []  # 在左上角显示生命符号
for i in range(live):
    livePic = Actor('hero_small')
    livePic.x = 40 + i*60
    livePic.y = 40
    livePics.append(livePic)

class Ball: # 定义小球类
    x = None  # 小球的x坐标
    y = None  # 小球的y坐标
    vx = None  # 小球x方向的速度
    vy = None  # 小球y方向的速度
    radius = None  # 小球的半径
    color = None  # 小球的颜色

    # 使用构造函数传递参数对对象初始化
    def __init__(self,x,y,vx,vy,radius,color):
        self.x = x
        self.y = y
        self.vx = vx
        self.vy = vy
        self.radius = radius
        self.color = color
    
    def draw(self): # 绘制函数
        # 绘制一个填充圆,坐标(x,y),半径radius,颜色color
        screen.draw.filled_circle((self.x, self.y), self.radius, self.color)

    def update(self): # 更新小球的位置、速度
        self.x += self.vx   # 利用x方向速度更新x坐标
        self.y += self.vy   # 利用y方向速度更新y坐标
        # 当小球碰到左右边界时,x方向速度反转
        if self.x > WIDTH-self.radius or self.x < self.radius:
            self.vx = -self.vx
        # 当小球碰到上下边界时,y方向速度反转
        if self.y > HEIGHT-self.radius or self.y < self.radius:
            self.vy = -self.vy

balls = []  # 存储所有小球的信息,初始为空列表

def draw():   # 绘制模块,每帧重复执行
    screen.fill('white')  # 白色背景
    hero.draw()  # 绘制玩家飞机
    for i in range(live): # 绘制还有几条生命
        livePics[i].draw()
    for ball in balls:
        ball.draw()   # 绘制小球
    screen.draw.text(str(time)+'秒', (270, 10), fontsize=50,
                     fontname='s', color='black')
    if live<=0:
        clock.unschedule(count)  # 结束函数的计划执行任务
        screen.draw.text("游戏结束!", (80, 300),
                         fontsize=100, fontname='s', color='red')

def update():  # 更新模块,每帧重复操作
    global live
    if live <=0: # 没有生命了,函数返回,不执行
        return
    for ball in balls:
        ball.update()  # 更新小球的位置、速度
        if abs(hero.x - ball.x) < 25 and abs(hero.y - ball.y) < 30:  # 玩家飞机和小球碰撞
            live -= 1 # 生命减1
            sounds.explode.play() # 爆炸一条命的音效
            ball.y = 10 # 当前小球立刻远离飞机,防止重复碰撞
    if live <= 0: # 生命减完了
        hero.image = 'blowup'  # 更换游戏玩家的图片为爆炸图片
        sounds.boom.play()  # 播放玩家飞机爆炸音效

def on_mouse_move(pos, rel, buttons):  # 当鼠标移动时执行
    if live > 0:  # 生命数大于0才执行
        hero.x = pos[0]  # 玩家飞机的x坐标设为鼠标的x坐标
        hero.y = pos[1]  # 玩家飞机的y坐标设为鼠标的y坐标

def count(): # 此函数每秒运行一次
    global time
    time += 1 # 计时,每秒钟时间+1
    # 每隔2秒,加一个小球,并且小球数目不超过20
    if time % 2 == 0 and len(balls) <= 20:
        x = WIDTH//2   # 设为小球的x坐标
        y = random.randint(5, HEIGHT//10)   # 设为小球的y坐标
        vx = random.choice([-3, -2, -1, 1, 2, 3])  # 小球x方向的速度
        vy = random.randint(1, 3)  # 小球y方向的速度
        r = 3      # 小球的半径
        color = 'black'  # 小球的颜色
        ball = Ball(x, y, vx, vy, r, color)  # 定义ball对象
        balls.append(ball)  # 把该小球的信息添加到balls中
        sounds.throw.play()
    clock.schedule_unique(count, 1)  # 下一次隔1秒调用count()函数

count()  # 调用函数运行
pgzrun.go()   # 开始执行游戏

练习12-2:尝试利用新定义的SmartBall类,改进坚持一百秒的游戏,实现效果如图12-5所示,红色小球即为可以主动向飞机靠近的SmartBall对象:

import pgzrun  # 导入游戏库
import random  # 导入随机库
WIDTH = 600   # 设置窗口的宽度
HEIGHT = 800  # 设置窗口的高度

time = 0  # 游戏坚持的时间
hero = Actor('hero')  # 导入玩家飞机图片
live = 3 # 飞机一共3条命

livePics = []  # 在左上角显示生命符号
for i in range(live):
    livePic = Actor('hero_small')
    livePic.x = 40 + i*60
    livePic.y = 40
    livePics.append(livePic)

class Ball: # 定义小球类
    x = None  # 小球的x坐标
    y = None  # 小球的y坐标
    vx = None  # 小球x方向的速度
    vy = None  # 小球y方向的速度
    radius = None  # 小球的半径
    color = None  # 小球的颜色

    # 使用构造函数传递参数对对象初始化
    def __init__(self,x,y,vx,vy,radius,color):
        self.x = x
        self.y = y
        self.vx = vx
        self.vy = vy
        self.radius = radius
        self.color = color
    
    def draw(self):
        # 绘制一个填充圆,坐标(x,y),半径radius,颜色color
        screen.draw.filled_circle((self.x, self.y), self.radius, self.color)

    def update(self): # 更新小球的位置、速度
        self.x += self.vx   # 利用x方向速度更新x坐标
        self.y += self.vy   # 利用y方向速度更新y坐标
        # 当小球碰到左右边界时,x方向速度反转
        if self.x > WIDTH-self.radius or self.x < self.radius:
            self.vx = -self.vx
        # 当小球碰到上下边界时,y方向速度反转
        if self.y > HEIGHT-self.radius or self.y < self.radius:
            self.vy = -self.vy

class SmartBall(Ball): # 定义聪明小球类,继承Ball而来
    targetX = None
    targetY = None
    # 使用构造函数传递参数对对象初始化
    def __init__(self, x, y, vx, vy, radius, color,targetX,targetY):
        super().__init__(x, y, vx, vy, radius, color)
        self.targetX = targetX
        self.targetY = targetY

    def updateVelforTarget(self): # 根据目标位置更新小球速度
        if self.targetX > self.x:
            self.vx = random.randint(1, 2)
        elif self.targetX < self.x:
            self.vx = random.randint(-2, -1)
        if self.targetY > self.y:
            self.vy = random.randint(1, 2)
        elif self.targetY < self.y:
            self.vy = random.randint(-2, -1)

balls = []  # 存储所有小球的信息,初始为空列表

def draw():   # 绘制模块,每帧重复执行
    screen.fill('white')  # 白色背景
    hero.draw()  # 绘制玩家飞机
    for i in range(live): # 绘制还有几条生命
        livePics[i].draw()
    for ball in balls:
        ball.draw()   # 绘制小球
    screen.draw.text(str(time)+'秒', (270, 10), fontsize=50,
                     fontname='s', color='black')
    if live<=0:
        clock.unschedule(count)  # 结束函数的计划执行任务
        screen.draw.text("游戏结束!", (80, 300),
                         fontsize=100, fontname='s', color='red')

def update():  # 更新模块,每帧重复操作
    global live
    if live <=0: # 没有生命了,函数返回,不执行
        return

    for ball in balls:
        ball.update()  # 更新小球的位置、速度
        if abs(hero.x - ball.x) < 25 and abs(hero.y - ball.y) < 30:  # 玩家飞机和小球碰撞
            live -= 1 # 生命减1
            sounds.explode.play() # 爆炸一条命的音效
            ball.y = 10 # 当前小球立刻远离飞机,防止重复碰撞

    if live <= 0: # 生命减完了
        hero.image = 'blowup'  # 更换游戏玩家的图片为爆炸图片
        sounds.boom.play()  # 播放玩家飞机爆炸音效

def on_mouse_move(pos, rel, buttons):  # 当鼠标移动时执行
    if live > 0:  # 生命数大于0才执行
        hero.x = pos[0]  # 玩家飞机的x坐标设为鼠标的x坐标
        hero.y = pos[1]  # 玩家飞机的y坐标设为鼠标的y坐标

def count():
    global time
    time += 1 # 计时,每秒钟时间+1
    
    x = WIDTH//2   # 设为小球的x坐标
    y = random.randint(5, HEIGHT//10)   # 设为小球的y坐标
    vx = random.choice([-3, -2, -1, 1, 2, 3])  # 小球x方向的速度
    vy = random.randint(1, 3)  # 小球y方向的速度    

    # 每隔2秒,加一个小球,并且小球数目不超过20
    if time % 2 == 0 and len(balls) <= 20:
        r = 3      # 小球的半径
        color = 'black'  # 小球的颜色
        ball = Ball(x, y, vx, vy, r, color)  # 定义ball对象
        balls.append(ball)  # 把该小球的信息添加到balls中
        sounds.throw.play()

    # 当普通小球数目达到20个时,再加一种智能小球
    if time % 20 == 0 and len(balls) >= 10:
        r = 5      # 小球的半径
        color = 'red'  # 小球的颜色
        smartball = SmartBall(x, y, vx, vy, r, color,
                              hero.x, hero.y)  # 定义smartball对象
        balls.append(smartball)  # 把该小球的信息添加到balls中
        sounds.shakeeyes.play()

    for smartball in balls: # 每秒钟更新一次智能小球的速度
        if isinstance(smartball, SmartBall):  # 判断是智能小球对象
            smartball.targetX = hero.x  # 智能小球的目标
            smartball.targetY = hero.y
            smartball.updateVelforTarget() # 通过目标更新智能小球的速度           

    clock.schedule_unique(count, 1)  # 下一次隔1秒调用count()函数

count()  # 调用函数运行
pgzrun.go()   # 开始执行游戏

练习12-3:尝试利用面向对象的知识,封装8-8-3.py中的角色行走动画功能,并定义两个不同的角色对象,实现如下的控制效果:

import pgzrun  # 导入游戏库
WIDTH = 1200    # 设置窗口的宽度
HEIGHT = 900   # 设置窗口的高度

class Player(): # 定义玩家控制的角色类,带分解动画和移动功能
    Anims = []  # 所有的分解动作图片,存在列表当中
    numAnims = None  # 分解动作图片的张数
    animIndex = None  # 需要显示的动作图片的序号
    animSpeed = None  # 用于控制行走动画速度
    player_x = None  # 玩家的x坐标
    player_y = None   # 玩家的y坐标
    vx = None   # 玩家x方向的速度

    # 使用构造函数传递参数对对象初始化,分解动作图像列表,x,y坐标,x方向速度
    def __init__(self, Anims, player_x, player_y,vx):
        self.Anims = Anims
        self.numAnims = len(Anims)  # 分解动作图片的张数
        self.player_x = player_x  # 设置角色的x坐标
        self.player_y = player_y  # 设置角色的y坐标
        self.vx = vx             # 设置玩家x方向的速度
        self.animIndex = 0  # 需要显示的动作图片的序号
        self.animSpeed = 0  # 用于控制行走动画速度
        for i in range(self.numAnims):
            self.Anims[i].x = player_x  # 设置所有分解动作图片的x坐标
            self.Anims[i].y = player_y  # 设置所有分解动作图片的y坐标

    def draw(self):  # 绘制函数
        self.Anims[self.animIndex].draw()  # 绘制玩家当前分解动作图片

    def MoveRight(self):  # 向右移动时的一些操作
        self.player_x += self.vx  # 角色向右移动
        for i in range(self.numAnims):  # 所有分解动作图片更新x坐标
            self.Anims[i].x = self.player_x
        if (self.player_x >= WIDTH):  # 角色走到最右边
            self.player_x = 0  # 再从最左边出现
        self.animSpeed += 1  # 用于控制动作动画速度
        if self.animSpeed % 5 == 0:  # 动作动画速度是移动速度的1/5
            self.animIndex += 1  # 每一帧分解动作图片序号加1
            if self.animIndex >= self.numAnims:  # 放完最后一个分解动作图片了
                self.animIndex = 0  # 再变成第一张分解动作图片

# 定义两个Player对象,并初始化
# 两个角色的分解动作图片、初始位置、速度都不一样
player1 = Player([Actor('阿短1'), Actor('阿短2'), Actor('阿短3'),
                  Actor('阿短4'), Actor('阿短5')], WIDTH/10, HEIGHT/4, 5)
player2 = Player([Actor('小可1'), Actor('小可2'), Actor('小可3'),
                  Actor('小可4'), Actor('小可5')], WIDTH/10, 3*HEIGHT/4, 4)

def draw():    # 绘制模块,每帧重复执
    screen.fill('gray')  # 灰色背景
    player1.draw()  # 绘制角色1
    player2.draw()  # 绘制角色2

def update():  # 更新模块,每帧重复操作
    if keyboard.right:  # 如果按下键盘右键
        player1.MoveRight()    # 角色1向右移动
        player2.MoveRight()    # 角色2向右移动

pgzrun.go()  # 开始执行游戏

 

分步骤代码、图片音效素材、讲解视频可以从异步社区下载:

https://www.epubit/bookDetails?id=UB72096d97d6149

分步骤代码也可以直接从这里下载:

联想Filez

 

这一章主要学习了面向对象编程,包括类和对象、成员变量、成员函数、构造函数、继承等概念,利用这些知识改进了小球反弹程序、实现了坚持一百秒的游戏。读者可以尝试在本章代码基础上继续改进:

1、根据游戏结束时得分的不同显示不同的结束效果;

2、游戏结束时增加游戏重玩的选项;

3、利用继承实现道具类,吃到道具后可以加命或小球速度减慢;

4、碰撞后给飞机一段时间的无敌状态。

读者也可以尝试应用面向对象的知识,改进之前章节的游戏案例。比如可以构建一个基础的飞机类,包括位置、速度、血量、图片、子弹数等成员变量,显示、更新位置速度、伤害减血、发射子弹等成员函数;然后可以利用继承,实现多种我机、敌机效果,实现更加丰富有趣的飞机大战游戏。

 

更多推荐

《Python游戏趣味编程》第12章 坚持一百秒