Python游戏开发,Pygame模块,Python从零开始

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动

前言

这一期我们会带大家进一步复现我们的魔塔小游戏,主要内容为进一步完成和其他更为复杂的地图元素接触时可以触发的事件。

废话不多说,让我们愉快地开始吧~

开发工具

Python版本: 3.7.4

相关模块:

pygame模块;

以及一些python自带的模块。

环境搭建

安装Python并添加到环境变量,pip安装需要的相关模块即可。

原理简介

上一期,我们实现了一些简单的勇士和地图元素接触时会触发的事件,就像下图这样:

事件

显然,这个效果图是不完美的,比如左侧面板的时间显示和当前层的显示没有添加,这里我们可以先写几行代码添加一下:

# --左侧面板栏
font = pygame.font.Font(self.cfg.FONTPATH_CN, 20)
font_renders = [
    self.hero.font.render(str(self.map_level_pointer), True, (255, 255, 255)),
    font.render('游戏时间: ' + str(pygame.time.get_ticks() // 60000) + ' 分 ' + str(pygame.time.get_ticks() // 1000 % 60) + ' 秒', True, (255, 255, 255)),
]
rects = [fr.get_rect() for fr in font_renders]
rects[0].topleft = (150, 530)
rects[1].topleft = (75, 630)
for fr, rect in zip(font_renders, rects):
    screen.blit(fr, rect)
复制代码

添加了这部分代码之后的效果是这样子的:

部分代码

接着,就是上一期我们说的,在原版的游戏中,勇士和这个仙女碰撞的时候,会出现对话框,类似这样:

对话框

这部分内容该如何实现呢?首先,可以肯定的是对话框是由四个部分组成的,即矩形、矩形内填充的底色、左上角的人物图标以及文字内容。他们的实现思路分别应该是:

矩形: 调用pygame画矩形的函数pygame.draw.rect;
底色填充: 导入背景图中黑色的地砖来填充对话框;
左上角人物图标: pygame.image.load导入后画到对应的位置即可;
文字: 主要调用pygame.font.Font实现.
复制代码

具体而言,我们的代码实现如下:

'''仙女和勇士对话'''
def showconversationheroandfairy(self, screen, scenes):
    # 对话框指针
    conversation_pointer = 0
    # 定义所有对话
    conversations = [
        ['......'], 
        ['你醒了!'], 
        ['......', '你是谁? 我在哪里?'],
        ['我是这里的仙子, 刚才你被这里的', '小怪打晕了.'],
        ['......', '剑, 剑, 我的剑呢?'],
        ['你的剑被他们抢走了, 我只来得及', '将你救出来.'],
        ['那, 公主呢? 我是来救公主的.'],
        ['公主还在里面, 你这样进去是打不', '过里面的小怪的.'],
        ['那我怎么办, 我答应了国王一定要', '把公主救出来的,那我现在应该怎', '么办呢?'],
        ['放心吧, 我把我的力量借给你, 你', '就可以打赢那些小怪了. 不过, 你', '得先去帮我去找一样东西,找到', '了再来这里找我.'],
        ['找东西? 找什么东西?'],
        ['是一个十字架, 中间有一颗红色的', '宝石.'],
        ['那个东西有什么用吗?'],
        ['我本是这座塔守护者, 可不久前, ', '从北方来了一批恶魔, 他们占领了', '这座塔,并将我的魔力封在了这', '个十字架里面, 如果你能将它带出', '塔来, 那我的魔力便会慢慢地恢复, ', '到那时我便可以把力量借给你去', '救公主了. 要记住, 只有用我的魔力', '才可以打开二十一层的门.'],
        ['......', '好吧,我试试看'],
        ['刚才我去看过了, 你的剑被放在三', '楼, 你的盾在五楼上, 而那个十字', '架被放在七楼. 要到七楼, 你得', '先取回你的剑和盾. 另外在塔里的', '其他楼层上, 还有一些存放了好几百', '年的宝剑和宝物,如果得到它们,', '对于你对付这里面的怪物将有很大', '的帮助.'],
        ['可是, 我怎么进去呢?'],
        ['我这里有三把钥匙, 你先拿去, 在', '塔里面还有很多这样的钥匙, 你一', '定要珍惜使用. 勇敢的去吧,勇士!']
    ]
    # 主循环
    clock = pygame.time.Clock()
    while True:
        screen.fill((0, 0, 0))
        screen.blit(self.background_images['gamebg'], (0, 0))
        self.map_parser.draw(screen)
        for scene in scenes:
            screen.blit(scene[0], scene[1])
        self.hero.draw(screen)
        # --按键检测
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_SPACE:
                    conversation_pointer += 1
                    if conversation_pointer >= len(conversations): return
        # --画对话框
        conversation = conversations[conversation_pointer]
        font = pygame.font.Font(self.cfg.FONTPATH_CN, 20)
        # ----勇士
        if conversation_pointer % 2 == 0:
            left, top, width, height = 510, 430, 7, 2
            pygame.draw.rect(screen, (199, 97, 20), (left - 4, top - 4, self.cfg.BLOCKSIZE * width + 8, self.cfg.BLOCKSIZE * height + 8), 7)
            id_image = self.hero.images['down']
        # ----仙子
        else:
            left, top, width, height = 300, 250, 7, 2
            if len(conversation) > 3: height = 3
            if len(conversation) > 5: height = 4
            if len(conversation) > 7: height = 5
            pygame.draw.rect(screen, (199, 97, 20), (left - 4, top - 4, self.cfg.BLOCKSIZE * width + 8, self.cfg.BLOCKSIZE * height + 8), 7)
            id_image = pygame.image.load(self.cfg.MAPELEMENTSPATHS['24'][0])
        # ----底色
        filepath = self.cfg.MAPELEMENTSPATHS['0'][0]
        for col in range(width):
            for row in range(height):
                image = pygame.image.load(filepath)
                image = pygame.transform.scale(image, (self.cfg.BLOCKSIZE, self.cfg.BLOCKSIZE))
                screen.blit(image, (left + col * self.cfg.BLOCKSIZE, top + row * self.cfg.BLOCKSIZE))
        # ----左上角图标
        screen.blit(id_image, (left + 10, top + 10))
        # ----对话框中的文字
        for idx, text in enumerate(conversation):
            font_render = font.render(text, True, (255, 255, 255))
            rect = font_render.get_rect()
            rect.left, rect.top = left + self.cfg.BLOCKSIZE + 20, top + 10 + idx * 30
            screen.blit(font_render, rect)
        # --刷新
        pygame.display.flip()
        clock.tick(self.cfg.FPS)
复制代码

具体的效果图如下:

效果图

接下来,我们需要实现的就是和怪物战斗的场景啦。按照原版游戏的设定,当勇士接触到地图上的怪物时,如果勇士可以击败该怪物,则自动触发战斗模式:

战斗模式

要实现这个功能,我们需要先定义地图上所有怪物的生命值,攻击力和防御力数值。具体而言,代码定义如下:

# 地图上所有怪物的属性: 名字, 生命值, 攻击力, 防御力
self.monsters_dict = {
    '40': ('绿头怪', 50, 20, 1),
    '41': ('红头怪', 70, 15, 2),
    '42': ('小蝙蝠', 100, 20, 5),
    '43': ('青头怪', 200, 35, 10),
    '44': ('骷髅人', 110, 25, 5),
    '45': ('骷髅士兵', 150, 40, 20),
    '46': ('兽面人', 300, 75, 45),
    '47': ('初级卫兵', 450, 150, 90),
    '48': ('大蝙蝠', 150, 65, 30),
    '49': ('红蝙蝠', 550, 160, 90),
    '50': ('白衣武士', 1300, 300, 150),
    '51': ('怪王', 700, 250, 125),
    '52': ('红衣法师', 500, 400, 260),
    '53': ('红衣魔王', 15000, 1000, 1000),
    '54': ('金甲卫士', 850, 350, 200),
    '55': ('金甲队长', 900, 750, 650),
    '56': ('骷髅队长', 400, 90, 50),
    '57': ('灵法师', 1500, 830, 730),
    '58': ('灵武士', 1200, 980, 900),
    '59': ('冥灵魔王', 30000, 1700, 1500),
    '60': ('麻衣法师', 250, 120, 70),
    '61': ('冥战士', 2000, 680, 590),
    '62': ('冥队长', 2500, 900, 850),
    '63': ('初级法师', 125, 50, 25),
    '64': ('高级法师', 100, 200, 110),
    '65': ('石头怪人', 500, 115, 65),
    '66': ('兽面战士', 900, 450, 330),
    '67': ('双手剑士', 1200, 620, 520),
    '68': ('冥卫兵', 1250, 500, 400),
    '69': ('高级卫兵', 1500, 560, 460),
    '70': ('影子战士', 3100, 1150, 1050),
    '188': ('血影', 99999, 5000, 4000),
    '198': ('魔龙', 99999, 9999, 5000),
}
复制代码

其中,字典的键值和第一期童年经典回忆 | 从零开始带大家撸一个魔塔小游戏呀(1)中定义地图的地图文件里的数字对应的地图元素的含义是一致的。接下来,为了实现和原版一样的功能,我们需要写个函数判断一下勇士当前的状态是否可以击败怪物:

'''判断勇士是否可以打赢怪物'''
def winmonster(self, monster):
    # 如果攻击力低于怪物防御力, monster: [名字, 生命值, 攻击力, 防御力]
    if self.attack_power <= monster[3]: return False
    # 如果防御力高于怪物攻击力
    if self.defense_power >= monster[2]: return True
    # 我方打怪物一次扣多少血
    diff_our = self.attack_power - monster[3]
    # 怪物打我方一次扣多少血
    diff_monster = monster[2] - self.defense_power
    # 计算谁可以win
    if round(monster[1] / diff_our) <= round(self.life_value / diff_monster):
        return True
    return False
复制代码

如果可以,则触发战斗场景。具体而言,我们已经做好了战斗的基础面板,每次只需要把对应的怪物、勇士以及他们各自的状态画上去即可,原理类似下图:

原理类

主要的代码实现如下:

'''战斗画面'''
def battle(self, monster, monster_image, map_parser, screen):
    monster = list(monster).copy()
    # 我方打怪物一次扣多少血
    diff_our = self.attack_power - monster[3]
    # 怪物打我方一次扣多少血
    diff_monster = monster[2] - self.defense_power
    # 更新战斗面板的频率
    update_count, update_interval, update_hero = 0, 10, False
    # 主循环
    clock = pygame.time.Clock()
    font = pygame.font.Font(self.cfg.FONTPATH_CN, 40)
    while True:
        screen.fill((0, 0, 0))
        screen.blit(self.background_images['gamebg'], (0, 0))
        map_parser.draw(screen)
        for scene in self.cur_scenes:
            screen.blit(scene[0], scene[1])
        self.draw(screen)
        # --按键检测
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
        # --更新战斗面板
        update_count += 1
        if update_count > update_interval:
            update_count = 0
            if update_hero:
                self.life_value = self.life_value - (monster[2] - self.defense_power)
            else:
                monster[1] = max(monster[1] - (self.attack_power - monster[3]), 0)
            update_hero = not update_hero
            if monster[1] <= 0: return
        screen.blit(self.background_images['battlebg'], (20, 40))
        screen.blit(monster_image, (90, 140))
        font_renders = [
            font.render(str(monster[1]), True, (255, 255, 255)),
            font.render(str(monster[2]), True, (255, 255, 255)),
            font.render(str(monster[3]), True, (255, 255, 255)),
            font.render(str(self.life_value), True, (255, 255, 255)),
            font.render(str(self.attack_power), True, (255, 255, 255)),
            font.render(str(self.defense_power), True, (255, 255, 255)),
        ]
        rects = [fr.get_rect() for fr in font_renders]
        for idx in range(3):
            rects[idx].top, rects[idx].left = 78 + idx * 95, 320
        for idx in range(3, 6):
            rects[idx].top, rects[idx].right = 78 + (idx - 3) * 95, 655
        for fr, rect in zip(font_renders, rects):
            screen.blit(fr, rect)
        # --刷新
        pygame.display.flip()
        clock.tick(self.cfg.FPS)
复制代码

原理其实很简单,由我方的勇士先发起一次攻击,然后再由怪物发起一次攻击,以此类推,该过程中战斗面板实时更新当前勇士和怪物的状态。最终的效果如下图所示:

效果

本期完整源代码详情个人主页简介获取~

文章到这里就结束了,感谢你的观看,Python29个小游戏系列,下篇文章分享魔塔小游戏呀(4)

为了感谢读者们,我想把我最近收藏的一些编程干货分享给大家,回馈每一个读者,希望能帮到你们。