使用pygame开发游戏:合金弹头(3)

导读

Python的强大超出你的认知,Python的功能不止于可以做网络爬虫,数据分析,Python完全可以进行后端开发,AI,Python也可进行游戏开发,本文将会详细介绍Python使用pygame模块来开发一个名为“合金弹头”的游戏

请先阅读上篇:使用pygame开发游戏:合金弹头(2)

由于游戏界面上会出现很多怪物,因此需要额外定义一个怪物管理程序来专门负责管理怪物的随机产生、死亡等行为。

为了有效地管理游戏界面上所有活着的怪物和已死的怪物(保存已死的怪物是为了绘制死亡动画),为怪物管理程序定义如下两个变量。

# 保存所有死掉的怪物,保存它们是为了绘制死亡动画,绘制完成后清除这些怪物
die_monster_list = Group()
# 保存所有活着的怪物
monster_list = Group()

接下来在怪物管理程序中定义一个随机生成怪物的工具函数。

# 随机生成并添加怪物的函数
def generate_monster(view_manager):
    if len(monster_list) < 3 + randint(0, 2):
        # 创建新怪物
        monster = Monster(view_manager, randint(1, 3))
        monster_list.add(monster)

前面已经指出,当玩家控制游戏界面上的角色不断向右移动时,程序界面上的所有怪物、怪物的子弹都必须不断地左移,因此需要在monster_manager程序中定义一个控制所有怪物及其子弹不断左移的函数。

# 更新怪物与子弹的坐标的函数
def update_posistion(screen, view_manager, player, shift):
    # 定义一个list列表,保存所有将要被删除的怪物
    del_list = []
    # 遍历怪物Group
    for monster in monster_list.sprites():
        monster.draw_bullets(screen, view_manager)
        # 更新怪物、怪物所有子弹的位置
        monster.update_shift(shift)  # ①
        # 如果怪物的X坐标越界,则将怪物添加到del_list列表中
        if monster.x < 0:
            del_list.append(monster)
    # 删除del_list列表中的所有怪物
    monster_list.remove(del_list)
    del_list.clear()
    # 遍历所有已死的怪物Group
    for monster in die_monster_list.sprites():
        # 更新怪物、怪物所有子弹的位置
        monster.update_shift(shift)  # ②
        # 如果怪物的X坐标越界,则将怪物添加到del_list列表中
        if monster.x < 0:
            del_list.append(monster)
    # 删除del_list列表中的所有怪物
    die_monster_list.remove(del_list)

上面程序中的①号代码处于循环体之内,该循环将会控制把所有活着的怪物及其子弹全部左移shift距离,如果移动之后怪物的X坐标超出了屏幕范围,程序就会清除该怪物;②号代码同样处于循环体之内,其处理方式与①号代码的处理方式几乎是一样的,只是它负责处理的是界面上已死的怪物。

                                                                         

monster_manager还需要定义一个绘制所有怪物的函数。该函数的实现逻辑也非常简单,只要分别遍历该程序的die_monster_list和monster_list两个Group,并将Group中的所有怪物绘制出来即可。

对于die_monster_list中的怪物,它们都是将要死亡的怪物,因此,只要将它们的死亡动画帧都绘制一次,接下来就应该清除这些怪物了—当Monster实例的self.die_max_draw_count成员变量为0时,就代表所有的死亡动画帧都绘制了一次。

下面是draw_monster()函数的代码,该函数就负责绘制所有怪物。

# 绘制所有怪物的函数
def draw_monster(screen, view_manager):
    # 遍历所有活着的怪物,绘制活着的怪物
    for monster in monster_list.sprites():
        # 绘制怪物
        monster.draw(screen, view_manager)
    del_list = []
    # 遍历所有已经死亡的怪物,绘制已经死亡的怪物
    for monster in die_monster_list.sprites():
        # 绘制怪物
        monster.draw(screen, view_manager)
        # 当怪物的die_max_draw_count返回0时,表明该怪物已经死亡
        # 且该怪物的死亡动画的所有帧都播放完成,将它们彻底删除
        if monster.die_max_draw_count <= 0:  # ③
            del_list.append(monster)
    die_monster_list.remove(del_list)

上面函数中的第一行for循环代码负责遍历所有活着的怪物,并将它们绘制出来;

第二行for循环代码则负责遍历所有已经死亡的怪物,并将它们绘制出来。

程序中③号代码检测该怪物的self.die_max_draw_count是否为0,如果为0,则表明该怪物已经死亡,且该怪物的死亡动画的所有帧都播放完成,应该将它们彻底删除。

                                                              


                                                     实现“子弹”类

本游戏的子弹类比较简单,因此只需要定义如下属性即可。

  • 子弹的类型。

  • 子弹的X、Y坐标。

  • 子弹的射击方向(向左或向右)。

  • 子弹在垂直方向(Y方向)上的加速度。

本游戏中的子弹不会产生爆炸效果。对子弹的处理思路是:只要子弹打中目标,子弹就会自动消失。

基于上面分析,程序为Bullet类定义了如下构造器,该构造器用于初始化子弹的成员变量的值。
 

import pygame​
from pygame.sprite import Sprite
import player
# 定义代表子弹类型的常量(如果程序需要增加更多的子弹,则只需在此处添加常量即可)
BULLET_TYPE_1 = 1
BULLET_TYPE_2 = 2
BULLET_TYPE_3 = 3
BULLET_TYPE_4 = 4
# 子弹类
class Bullet(Sprite):
    def __init__ (self, tipe, x, y, pdir):
        super().__init__()
        # 定义子弹的类型
        self.type = tipe
        # 子弹的X、Y坐标
        self.x = x
        self.y = y
        # 定义子弹的射击方向
        self.dir = pdir
        # 定义子弹在Y方向上的加速度
        self.y_accelate = 0
        # 子弹是否有效
        self.is_effect = True
    ...

上面Bullet类的构造器用于对子弹的类型,X、Y坐标,方向执行初始化。

本游戏中不同怪物、角色发射的子弹各不相同,因此对不同类型的子弹将会采用不同的位图。

下面是Bullet类根据子弹类型来获取对应位图的方法。

 # 根据子弹类型来获取对应的位图
    def bitmap(self, view_manager): 
        return view_manager.bullet_images[self.type - 1]

从上面程序可以看出,根据子弹类型来获取对应位图的处理方式玩了一个小技巧——程序使用view_manager的bullet_images列表来管理所有子弹的位图,第一种子弹(type属性值为BULLET_TYPE_1)的位图正好对应bullet_images列表的第一个元素,因此直接通过子弹的type属性即可获取bullet_images列表中的位图。

                                                                           

接下来,程序还可以计算子弹在水平方向、垂直方向上的速度。下面的两个方法就是用于实现该功能的。

 # 根据子弹类型来计算子弹在X方向上的速度
    def speed_x(self):
            # 根据玩家的方向来计算子弹方向和移动方向
            sign = 1 if self.dir == player.DIR_RIGHT else -1
            # 对于第1种子弹,以12为基数来计算其速度
            if self.type == BULLET_TYPE_1:
                return 12 * sign
            # 对于第2种子弹,以8为基数来计算其速度
            elif self.type == BULLET_TYPE_2:
                return 8 * sign
            # 对于第3种子弹,以8为基数来计算其速度
            elif self.type == BULLET_TYPE_3:
                return 8 * sign
            # 对于第4种子弹,以8为基数来计算其速度
            elif self.type == BULLET_TYPE_4:
                return 8 * sign
            else:
                return 8 * sign
​
    # 根据子弹类型来计算子弹在Y方向上的速度
    def speed_y(self):
        # 如果self.y_accelate不为0,则以self.y_accelate作为Y方向上的速度
        if self.y_accelate != 0:
            return self.y_accelate
        # 此处控制只有第3种子弹才有Y方向上的速度(子弹会斜着向下移动)
        if self.type == BULLET_TYPE_1 or self.type == BULLET_TYPE_2 \
            or self.type == BULLET_TYPE_4:
            return 0
        elif self.type == BULLET_TYPE_3:
            return 6

从上面代码可以看出,当程序要计算子弹在X方向上的速度时,首先判断该子弹的射击方向是否向右,如果子弹的射击方向是向右的,那么子弹在X方向上的速度为正值(保证子弹不断地向右移动);如果子弹的射击方向是向左的,那么子弹在X方向上的速度为负值(保证子弹不断地向左移动)。

提示

上面程序用到了player程序中定义的一个常量,因此还需要一个player.py文件。player程序的具体内容可参考player.py文件。

接下来程序计算子弹在X方向上的速度就非常简单了。除第1种子弹以12为基数来计算X方向上的速度之外,其他子弹都是以8为基数来计算的,这意味着只有第1种子弹的速度是最快的。

在计算Y方向上的速度时,程序的计算逻辑也非常简单。如果该子弹的self.y_accelate不为0(Y方向上的加速度不为0),则直接以self.y_accelate作为子弹在Y方向上的速度。这是因为程序设定玩家在跳起的过程中发射的子弹应该是斜向上射出的;玩家在降落的过程中发射的子弹应该是斜向下射出的。

除此之外,程序还使用if语句对子弹的类型进行判断:如果是第3种子弹,其将具有Y方向上的速度(这意味着子弹会不断地向下移动)——这是因为程序设定飞机发射的是第3种子弹,这种子弹会模拟飞机投弹斜向下移动。

程序计算出子弹在X方向、Y方向上的移动速度之后,接下来控制子弹移动就非常简单了——使用X坐标加上X方向上的速度、Y坐标加上Y方向上的速度来控制。下面是控制子弹移动的方法。

# 定义控制子弹移动的方法
    def move(self):
        self.x += self.speed_x()
        self.y += self.speed_y()

                                                   


 

                                                     加载、管理游戏图片

为了统一管理游戏中所有的图片、声音资源,本游戏开发了一个ViewManager工具类,该工具类主要用于加载、管理游戏的图片资源,这样Monster、Bullet类就可以正常地显示出来。

ViewManager类定义了如下构造器来管理游戏涉及的图片资源。

import pygame
# 管理图片加载和图片绘制的工具类
class ViewManager:
    # 加载所有游戏图片、声音的方法
    def __init__ (self):
        self.screen_width = 1200
        self.screen_height = 600
        # 保存角色生命值的成员变量
        x = self.screen_width * 15 / 100
        y = self.screen_height * 75 / 100
        # 控制角色的默认坐标
        self.X_DEFAULT = x
        self.Y_DEFALUT = y
        self.Y_JUMP_MAX = self.screen_height * 50 / 100
​
​
        self.map = pygame.image.load("images/map.jpg")
        self.map_back = pygame.image.load("images/game_back.jpg")
        self.map_back = pygame.transform.scale(self.map_back, (1200, 600))
        # 加载角色站立时腿部动画帧的图片
        self.leg_stand_images = []
        self.leg_stand_images.append(pygame.image.load("images/leg_stand.png"))
        # 加载角色站立时头部动画帧的图片
        self.head_stand_images = []
        self.head_stand_images.append(pygame.image.load("images/head_stand_1.png"))
        self.head_stand_images.append(pygame.image.load("images/head_stand_2.png"))
        self.head_stand_images.append(pygame.image.load("images/head_stand_3.png"))
        # 加载角色跑动时腿部动画帧的图片
        self.leg_run_images = []
        self.leg_run_images.append(pygame.image.load("images/leg_run_1.png"))
        self.leg_run_images.append(pygame.image.load("images/leg_run_2.png"))
        self.leg_run_images.append(pygame.image.load("images/leg_run_3.png"))
        # 加载角色跑动时头部动画帧的图片
        self.head_run_images = []
        self.head_run_images.append(pygame.image.load("images/head_run_1.png"))
        self.head_run_images.append(pygame.image.load("images/head_run_2.png"))
        self.head_run_images.append(pygame.image.load("images/head_run_3.png"))
        # 加载角色跳跃时腿部动画帧的图片
        self.leg_jump_images = []
        self.leg_jump_images.append(pygame.image.load("images/leg_jum_1.png"))
        self.leg_jump_images.append(pygame.image.load("images/leg_jum_2.png"))
        self.leg_jump_images.append(pygame.image.load("images/leg_jum_3.png"))
        self.leg_jump_images.append(pygame.image.load("images/leg_jum_4.png"))
        self.leg_jump_images.append(pygame.image.load("images/leg_jum_5.png"))
        # 加载角色跳跃时头部动画帧的图片
        self.head_jump_images = []
        self.head_jump_images.append(pygame.image.load("images/head_jump_1.png"))
        self.head_jump_images.append(pygame.image.load("images/head_jump_2.png"))
        self.head_jump_images.append(pygame.image.load("images/head_jump_3.png"))
        self.head_jump_images.append(pygame.image.load("images/head_jump_4.png"))
        self.head_jump_images.append(pygame.image.load("images/head_jump_5.png"))
        # 加载角色射击时头部动画帧的图片
        self.head_shoot_images = []
        self.head_shoot_images.append(pygame.image.load("images/head_shoot_1.png"))
        self.head_shoot_images.append(pygame.image.load("images/head_shoot_2.png"))
        self.head_shoot_images.append(pygame.image.load("images/head_shoot_3.png"))
        self.head_shoot_images.append(pygame.image.load("images/head_shoot_4.png"))
        self.head_shoot_images.append(pygame.image.load("images/head_shoot_5.png"))
        self.head_shoot_images.append(pygame.image.load("images/head_shoot_6.png"))
        # 加载子弹图片
        self.bullet_images = []
        self.bullet_images.append(pygame.image.load("images/bullet_1.png"))
        self.bullet_images.append(pygame.image.load("images/bullet_2.png"))
        self.bullet_images.append(pygame.image.load("images/bullet_3.png"))
        self.bullet_images.append(pygame.image.load("images/bullet_4.png"))
        self.head = pygame.image.load("images/head.png")
        # 加载第一种怪物(炸弹)未爆炸时动画帧的图片
        self.bomb_images = []
        self.bomb_images.append(pygame.image.load("images/bomb_1.png"))
        self.bomb_images.append(pygame.image.load("images/bomb_2.png"))
        # 加载第一种怪物(炸弹)爆炸时的图片
        self.bomb2_images = []
        self.bomb2_images.append(pygame.image.load("images/bomb2_1.png"))
        self.bomb2_images.append(pygame.image.load("images/bomb2_2.png"))
        self.bomb2_images.append(pygame.image.load("images/bomb2_3.png"))
        self.bomb2_images.append(pygame.image.load("images/bomb2_4.png"))
        self.bomb2_images.append(pygame.image.load("images/bomb2_5.png"))
        self.bomb2_images.append(pygame.image.load("images/bomb2_6.png"))
        self.bomb2_images.append(pygame.image.load("images/bomb2_7.png"))
        self.bomb2_images.append(pygame.image.load("images/bomb2_8.png"))
        self.bomb2_images.append(pygame.image.load("images/bomb2_9.png"))
        self.bomb2_images.append(pygame.image.load("images/bomb2_10.png"))
        self.bomb2_images.append(pygame.image.load("images/bomb2_11.png"))
        self.bomb2_images.append(pygame.image.load("images/bomb2_12.png"))
        self.bomb2_images.append(pygame.image.load("images/bomb2_13.png"))
        # 加载第二种怪物(飞机)的动画帧的图片
        self.fly_images = []
        self.fly_images.append(pygame.image.load("images/fly_1.gif"))
        self.fly_images.append(pygame.image.load("images/fly_2.gif"))
        self.fly_images.append(pygame.image.load("images/fly_3.gif"))
        self.fly_images.append(pygame.image.load("images/fly_4.gif"))
        self.fly_images.append(pygame.image.load("images/fly_5.gif"))
        self.fly_images.append(pygame.image.load("images/fly_6.gif"))
        # 加载第二种怪物(飞机)爆炸时动画帧的图片
        self.fly_die_images = []
        self.fly_die_images.append(pygame.image.load("images/fly_die_1.png"))
        self.fly_die_images.append(pygame.image.load("images/fly_die_2.png"))
        self.fly_die_images.append(pygame.image.load("images/fly_die_3.png"))
        self.fly_die_images.append(pygame.image.load("images/fly_die_4.png"))
        self.fly_die_images.append(pygame.image.load("images/fly_die_5.png"))
        self.fly_die_images.append(pygame.image.load("images/fly_die_6.png"))
        self.fly_die_images.append(pygame.image.load("images/fly_die_7.png"))
        self.fly_die_images.append(pygame.image.load("images/fly_die_8.png"))
        self.fly_die_images.append(pygame.image.load("images/fly_die_9.png"))
        self.fly_die_images.append(pygame.image.load("images/fly_die_10.png"))
        # 加载第三种怪物(人)活着时的动画帧的图片
        self.man_images = []
        self.man_images.append(pygame.image.load("images/man_1.png"))
        self.man_images.append(pygame.image.load("images/man_2.png"))
        self.man_images.append(pygame.image.load("images/man_3.png"))
        # 加载第三种怪物(人)死亡时的动画帧的图片
        self.man_die_images = []
        self.man_die_images.append(pygame.image.load("images/man_die_1.png"))
        self.man_die_images.append(pygame.image.load("images/man_die_2.png"))
        self.man_die_images.append(pygame.image.load("images/man_die_3.png"))
        self.man_die_images.append(pygame.image.load("images/man_die_4.png"))
        self.man_die_images.append(pygame.image.load("images/man_die_5.png"))

上面代码比较简单,程序为每组图片创建一个list列表,然后使用该list列表来管理pygame.image加载的图片。

提示

随着游戏规模的加大,游戏可能需要添加更多的怪物、更多的角色,那么此处加载动画帧的代码将会更多。


                                                      让游戏“动”起来

现在,我们已经完成了Monster和monster_manager程序,将它们组合起来即可在界面上生成、绘制怪物;同时也创建了Bullet类,这样Monster即可通过Bullet来发射子弹。

下面开始创建游戏界面,并使用monster_manager在界面上添加怪物。

先看主程序代码。

import pygame
import sys
from view_manager import ViewManager
import game_functions as gf
import monster_manager as mm

def run_game():
    # 初始化游戏
    pygame.init()
    # 创建ViewManager对象
    view_manager = ViewManager()
    # 设置显示屏幕,返回Surface对象
    screen = pygame.display.set_mode((view_manager.screen_width, 
        view_manager.screen_height))
    # 设置标题
    pygame.display.set_caption('合金弹头')
​
​
    while(True):
        # 处理游戏事件
        gf.check_events(screen, view_manager)
        # 更新游戏屏幕
        gf.update_screen(screen, view_manager, mm)
run_game()

上面主程序定义了一个run_game()函数,该函数的第一行pygame.init()代码用于初始化pygame,这是使用pygame开发游戏必须做的第一件事;第二行pygame.display.set_mode()代码设置游戏界面的宽和高,set_mode函数将会返回代表游戏界面的Surface对象。

在初始化游戏界面之后,程序使用一个死循环(while(True))不断地处理游戏的交互事件、屏幕刷新。本游戏使用game_functions程序来处理游戏的交互事件、屏幕刷新。下面是game_functions程序的代码。
 

import sys
import pygame
def check_events(screen, view_manager):
    ''' 响应按键和鼠标事件 '''
    for event in pygame.event.get():
        # 处理游戏退出
        if event.type == pygame.QUIT:
            sys.exit()
def update_screen(screen, view_manager, mm):
    ''' 处理更新游戏界面的方法 '''
    # 随机生成怪物
    mm.generate_monster(view_manager)
    # 绘制背景图片
    screen.blit(view_manager.map, (0, 0))
    # 绘制怪物
    mm.draw_monster(screen, view_manager)
    # 更新屏幕显示,放在最后一行
    pygame.display.flip()

上面程序先定义了一个简单的事件处理函数check_events(),该函数判断如果游戏获得的事件是pygame.QUIT(程序退出),程序就调用sys.exit()退出游戏。

上面程序中update_screen的第一行代码调用monster_manager程序的generate_monster()函数来生成怪物;第二行代码在screen上绘制图片作为地图;第三行代码调用monster_manager程序的draw_monster()函数来绘制怪物。

至此,已经完成了该游戏最基础的部分:绘制地图,在地图上绘制怪物。运行上面的metal_slug程序,将可以看到如图1所示的有多个怪物的游戏界面。

                                                                     图1  monster_manager自动生成多个怪物

                                                                                           未完待续

另外本人还开设了个人公众号:JiandaoStudio ,会在公众号内定期发布行业信息,以及各类免费代码、书籍、大师课程资源。

                                            

扫码关注本人微信公众号,有惊喜奥!公众号每天定时发送精致文章!回复关键词可获得海量各类编程开发学习资料!

例如:想获得Python入门至精通学习资料,请回复关键词Python即可。

猜你喜欢

转载自blog.csdn.net/weixin_41213648/article/details/90764847