前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >用 PyGame 入门专业游戏开发(二)

用 PyGame 入门专业游戏开发(二)

作者头像
韩伟
发布于 2023-12-04 04:48:13
发布于 2023-12-04 04:48:13
37400
代码可运行
举报
文章被收录于专栏:韩伟的专栏韩伟的专栏
运行总次数:0
代码可运行

推麻将的玩法

上一篇介绍了一个游戏运行的最基本结构,本篇开始根据一个具体的游戏,做一个游戏关卡。下面要做的是一个叫“推麻将”的桌面玩法。现在介绍一下这个玩法的具体内容:

  1. 一副麻将随机放在桌上,共 8 行 14 列
  2. 任何两个相同的麻将,直线相连如果没有其他麻将阻隔,就可以消除掉
  3. 桌上如果有空位(有麻将消除了留下的空位),相邻的四个方向的麻将行列,都可以整队移动;但是移动之后,被推动的这队麻将,必须至少要有一个能被消除的麻将,否则不能移动
  4. 桌上所有麻将都被消除完就是胜利;
  5. 消除和推动麻将的移动,使用鼠标点击来操作

第一个关卡

根据上篇设计的关卡基类 Scenario,我们可以为了一个特定的游戏,建立一个子类 MainScenario。

编写 MainScenario 也很简单,主要就是实现一个 start() 方法。此方法所需要做的事情,就是多次调用基类的 add_group() 方法,把需要显示的游戏对象,都以 Group 的组织形式,添加到关卡中去。

Group 对象及其内部的 Sprite 对象,一旦被 add_group() 放到 MainScenario 后,由于 Director 的 run() 方法,就会每帧(每秒60次的)去调用 MainScenario 的 update() 方法,因此在 MainScenario 中的 Group 对象,以及 Sprite 对象的 update() 方法也会被调用。所以我们游戏逻辑的主要实现代码就是:

  1. 编写 MainScenario.start() :放置游戏关卡初始的所有游戏对象组 Group 以及需要的游戏对象 Sprite
  2. 编写游戏对象 Group 和 Sprite 的子类,通过实现 __init__() 和 update() 方法来完成各种游戏行为
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class MainScenario(scenario.Scenario):
    '''入口的局'''

    def __init__(self):
        scenario.Scenario.__init__(self)

    def start(self):
        '''剧幕开场'''

        # 建立各图层
        table = Table(self.director) # 桌子
        bg = pygame.sprite.Group() # 背景
        effect = pygame.sprite.Group() # 特效

        # 各图层放上舞台
        self.add_group("bg", bg)
        self.add_group("table", table)
        self.add_group("effect",effect)
        
        …………………………

上面的代码,在关卡中加入了三个 Group:

  • bg 代表背景,在上面的游戏中,是由一批带圆点花纹的 Sprite 组成的桌布
  • table 代表桌子,上面这个游戏是一个放了几十个麻将牌的桌子,其中每个麻将是一个 Sprite,桌子 Table 类则继承 Group
  • effect 代表特效层,特效层初始化的时候,没有任何的 Sprite 成员,而是在运行时添加和删除“爆炸特效” Sprite,用来显示“消除”麻将的效果。因此建立了 bomb1/bomb2 两个 Sprite 对象,先作为属性挂在每个麻将 Sprite 对象上。而 bomb1/bomb2 对象的类 Bomb,也会保存 effect 这个 Group 的对象,用以实现动画效果。

注意三个 Group 的 add_group() 的顺序:最先添加的,会被放在最底层显示,以此类推。所以 bg 作为背景是最底下,中间是 table 层,上面是特效 effect 层。三个 Group 对象通过 add_group() 放入到关卡 MainSenario 中。

然后根据游戏玩法我们设计了几个类,用来实现上述的玩法:

  1. Table:存放所有麻将的对象,会记录所有麻将的位置,每帧根据麻将的位置重绘画面,麻将移动过程也是通过 Table 显示。
  2. Mahjong:可以放在 Table 上显示,一个关卡中会有 8x14 共 112 个对象,每个对象保存自己的图案。点击麻将的事件处理也由此类处理。
  3. Edge:点击麻将后,显示的“选中”框,通过 effect 这个 Group 显示。Table 对象会记录 Edge 的位置,以记录当前选定的麻将。
  4. Point:桌面背景层,通过 bg 这个 Group 显示。点击 Point 会触发麻将的移动逻辑。桌面上也是由 112 个 Point 对象组成,因此被点击的 Point 是可以知道其坐标位置的。
  5. Bomb:消除麻将时显示的“爆炸”动画,每个麻将对象身上都有属性是 Bomb 对象(b1/b2),需要显示的时候直接加入 effect Group,过一段时间后消失,形成一个简单的动画效果。

最终,上面所有的 Sprite,都以所需的游戏逻辑构建,并且被放入 Group 中。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制

    def start(self):
        '''剧幕开场'''

        # 建立各图层
        table = Table(self.director) # 桌子
        bg = pygame.sprite.Group() # 背景
        effect = pygame.sprite.Group() # 特效

        # 各图层放上舞台
        self.add_group("bg", bg)
        self.add_group("table", table)
        self.add_group("effect",effect)

        # 画桌布背景
        for i in range(0, Table.cols):
            for j in range(0, Table.rows):
                point = Point(table)
                point.rect.left = point.rect.width*i
                point.rect.top = point.rect.height*j
                point.pos = [i, j]
                bg.add(point)

        # 生成 112 张牌
        heap = []
        for j in range(0, Mahjong.cols):
            for i in range(0, Mahjong.lines):
                for k in range(0, 4): # 每个图案生成 4 张麻将
                    if i != 3 or j == 5:  # 第 4 行素材只取红中
                        dot_one = Mahjong(table, [j, i])
                        heap.append(dot_one)
        table.put_in(heap)

对于游戏来说,为每个可以单独显示的“东西”设计一个类,是非常自然的做法;然而,有一些并不可见的逻辑,也应该考虑设计成一个类,譬如这里的 Table 类型。事实上,Table 对象保存了整个游戏程序中最重要的状态,就是所有麻将的位置。有了 Table 对象,其他所有的可显示对象,在处理“被鼠标点击”事件的时候,都能获得完整的所有麻将的状态,非常方便编写游戏业务逻辑。

加载图像资源

在处理完“桌子”之后,下来需要处理的最复杂的资源,就是麻将了。一般来说,游戏的图像资源,都是一个图片文件。很多图像都拼接在同一个文件上,如下图:

每个麻将需要获得这个文件中图像的某一块,需要有两个步骤:

  1. 把整个图片加载到内存中,变成一个对象(变量)
  2. 截取自己需要的那一部分图像,变成一个对象,存放到 Mahjong 对象的 image 属性和 Rect 属性上。image 属性是 Sprite 基类规定了,用来显示的图像内容属性。而 Rect 属性则决定此 Sprite 对象显示在屏幕上的位置和大小。
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class Mahjong(pygame.sprite.Sprite):
    '''麻将'''

    # 完整素材图包含了 9X4 的子图片,每个牌希望尺寸为 45x62
    bigImage = pygame.image.load("southeast.jpg")
    cols = 9
    lines = 4
    margin_width = 16
    margin_height = 8
    moving_speed = 5

    def __init__(self, table, symbol=[0, 0]):
        pygame.sprite.Sprite.__init__(self)
        self.symbol = symbol  # 麻将符号
        self.table = table
        self.pos = [0, 0]  # 在桌上的位置
        self.is_moving = False
        effect = table.director.current_scenario.stage_map["effect"] # 通过“名字”获取特效层
        self.bomb = Bomb(effect) # 爆炸特效

        # 从素材图中获取尺寸
        unitWidth = Mahjong.bigImage.get_rect().width/Mahjong.cols
        unitHeight = Mahjong.bigImage.get_rect().height/Mahjong.lines
        self.image = pygame.Surface(
            (unitWidth-2*Mahjong.margin_width, unitHeight-2*Mahjong.margin_height))
        self.rect = self.image.get_rect()

        # 选择具体牌,symbol 为第几行第几列的牌
        self.image.blit(Mahjong.bigImage, (0, 0),
                        (symbol[0]*unitWidth+Mahjong.margin_width, symbol[1]*unitHeight+Mahjong.margin_height, self.rect.width, self.rect.height))

上述代码的 pygame.image.load() 是作为类静态代码执行,只会执行一次,并不会每个 Mahjong 对象构造出来都运行一次。这行代码就是加载图片资源:一个由 36 个麻将组成的图片。

上述代码的 self.image.blit() 就是从一个 pygame.surface.Surface 对象上,截取某一块图像作为内容。至于需要截取哪一块图像,由 symbol 参数决定,这个参数以一个二位数组,标识一个麻将花色。这个数值的内容,也代表了在图形文件 southeast.jpg 上具体某一行、列的麻将图像。从此,Mahjong 对象有了可以显示的内容,只要把此对象 add() 到一个 Group 上,屏幕就会显示一个麻将牌了。

通过 symbol 的数值,可以计算出 southeast.jpg 图像文件上具体的图像的位置。并且通过设定的空白边的高、宽,准确截取想要的图像。以上的加载图像代码,包含了 cols/lines/margin_width/magin_height 这些常量,这些数值是和 southeast.jpg 绑定的。在 Unity 等游戏引擎中,通常会有一些图形文件处理工具,来帮你以可视化的方式,切割一整个图形文件,然后生成你需要的各个游戏对象(Sprite)。

随机生成一桌麻将

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        # 生成 112 张牌
        heap = []
        for j in range(0, Mahjong.cols):
            for i in range(0, Mahjong.lines):
                for k in range(0, 4): # 每个图案生成 4 张麻将
                    if i != 3 or j == 5:  # 第 4 行素材只取红中
                        dot_one = Mahjong(table, [j, i])
                        heap.append(dot_one)
        table.put_in(heap)

上述代码在 MainScenario.start() 中,对于 9x4 的图形资源,每取出一个,就生成 4 个相同的 Mahjong 对象。循环中的 [j, i] 变量,代表了麻将的图案。然后把这 112 个麻将放在一个数组中,通过 Table.put_in() 放到桌上。

一般来说,麻将的图案和麻将美术资源应该是解耦的,上面代码中的 Mahjong.cols, Mahjong.lines 这两个常量,决定了生成的 Mahjong 对象的 symbol 属性的值,如 [0,1] 代表“二筒”、[1,2] 代表“三条”。按专业的做法,这个值(如 [0,1],[1,2])是不应该是根据 southeast.jpg 这个图片上对应图案的“坐标”来确定的,而应该有另外一个配置文件,写下每个麻将图案代表的数值(可能是从 0-36),对应美术资源 southeast.jpg 文件上的位置坐标。但是这个游戏比较简单,麻将的图形文件也不太可能更换,所以代码中这么写也可以接受。因此 Mahjong.symbol 属性就是由两个 int 组成的数组。这样使用美术资源的图像坐标,代表麻将图案,由于是一个两个元素的数组变量,让代码的理解也变得困难了一些。

Table 对象通过一个属性 heap 记录每个麻将的位置,heap 是一个 14x8 的二维数组,下标是桌上麻将的行、列数字,元素则是 Mahjong 对象。如果某个位置没有麻将,这个坐标所对应的值是 None。 由于需要随机打乱位置,所以 Table.put_in() 必须要使用随机数来实现这个功能:

  1. 用一个数组 mahjiongs 存放“未放入”的麻将堆
  2. 用一个数组 random_symbol 存放“打乱顺序”的麻将堆
  3. 随机从 mahjiongs 抽出一个麻将,加入到 random_symbol 中,直到 mahjiongs 变空
  4. 用 random_symbol 的顺序,一个个放入 Table 的 14x8 的数组 heap 中。
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    def put_in(self, maijiangs: list[Mahjong]):
        '''打乱放入桌子'''
        random_symbol = []
        while len(maijiangs) != 0:
            index = random.randint(0, len(maijiangs))
            a = maijiangs.pop(index-1)
            random_symbol.append(a)

        index = 0
        for i in range(0, Table.cols):
            for j in range(0, Table.rows):
                theMajiang = random_symbol[index]  # 取出打乱队列中的麻将
                index = index+1
                self.heap[i][j] = theMajiang
                theMajiang.pos = [i, j] # 让麻将知道自己的坐标

Table 通过 heap 属性,记录所有的麻将,然后通过对 Majiong.pos 赋值,传入其所在 heap 数组的坐标,让每个 Mahjong 自己调整 Rect 属性,从而实现按预定桌面位置进行显示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    def show(self):
        self.empty() # 清空 Group 里所有 Sprite,以便下面重新画

        # 画麻将牌
        for i in range(0, Table.cols):
            for j in range(0, Table.rows):
                theMajiang = self.heap[i][j]
                if theMajiang == None:
                    continue
                theMajiang.show() #根据位置调整自己的Rect
                self.add(theMajiang) # Sprite加入到Group中显示

以上的 Table.show() 方法,会在 Table.update() 中调用,索引每帧都会刷新显示桌面上所有麻将的位置。这样游戏逻辑,只需要修改 Table.heap 的内容,就能自由控制桌面上需要显示的麻将了。

上面的 theMajiang.show(),实际上是根据 Mahjong.pos 属性去设置自己的 Rect 数值,以确定显示位置的。而 Mahjong.pos 属性,在 Table.put_in() 的时候已经正确赋值了。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    def show(self):
        '''显示麻将牌'''
        if self.is_moving == False:
            self.rect.left = self.rect.width*self.pos[0]
            self.rect.top = self.rect.height*self.pos[1]
            return

Mahjong.show() 还有一个功能,就是显示麻将牌移动的动画效果,这个在下一篇再讲。后续会附带上完整的代码 mahjiong.py。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-11-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 韩大 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
用 PyGame 入门专业游戏开发(三)
根据游戏规则,两张相同图案的麻将,如果互相之间没有其他麻将牌被直线阻隔(中间的距离可以无限),可以通过先后点击选择这两张麻将,消除这两张牌。
韩伟
2023/12/12
2060
用 PyGame 入门专业游戏开发(三)
用 PyGame 入门专业游戏开发(四)
根据游戏逻辑,麻将被选中后,是可以再点击桌面上的空位,进行移动的。要实现麻将的移动,需要有以下几点功能需要实现:
韩伟
2023/12/12
2670
用 PyGame 入门专业游戏开发(四)
Pygame基础4-多阶段
本章是对第2章的延续。我们希望为游戏添加多个阶段,比如开始界面、游戏界面、结束界面等。
一只大鸽子
2024/04/11
1040
Pygame基础4-多阶段
python--从入门到实践--chapter 12 pygame_Alien_Invasion
2.创建一个程序,显示一个空屏幕。在事件循环中,每当检测到 pygame.KEYDOWN 事件时都打印属性event.key。运行这个程序,并按各种键,看看Pygame如何响应
Michael阿明
2021/02/20
7060
python--从入门到实践--chapter 12 pygame_Alien_Invasion
【Python的魅力】:利用Pygame实现游戏坦克大战——含完整源码
本游戏主要分为两个对象,分别是我方坦克和敌方坦克。用户可以通过控制我方的坦克来摧毁敌方的坦克保护自己的“家”,把所有的敌方坦克消灭完达到胜利。敌方的坦克在初始的时候是默认 5 个的(这可以自己设置),当然,如果我方坦克被敌方坦克的子弹打中,游戏结束。从面向对象分析该项目有以下类组成:
爱喝兽奶的熊孩子
2024/04/30
9230
【Python的魅力】:利用Pygame实现游戏坦克大战——含完整源码
用Python实现打地鼠游戏
打地鼠是一种经典的娱乐游戏,通过在屏幕上出现和隐藏地鼠,测试用户的反应速度和准确性。本篇技术博客将使用Python语言实现一个简单的打地鼠游戏,让我们一起来看看具体的实现过程。
大盘鸡拌面
2024/02/23
4330
《Java从入门到失业》第四章:类和对象(4.3):一个完整的例子带你深入类和对象
       到此为止,我们基本掌握了类和对象的基础知识,并且还学会了String类的基本使用,下面我想用一个实际的小例子,逐步来讨论类和对象的一些其他知识点。
用户7801119
2020/09/27
9960
用Python实现谷歌的小恐龙游戏:p
让我们来依次定义一下这些游戏元素类。对于云,路面以及仙人掌来说,定义起来很简单,我们只需要加载对应的游戏元素图片:
Crossin先生
2020/12/22
2.3K0
用Python实现谷歌的小恐龙游戏:p
用 PyGame 入门专业游戏开发(一)
一般来说学习编程都会先写 hello world,然而游戏的 hello world 应该是怎样的呢?这就需要先搞清楚游戏和普通的 hello world 程序有什么不同。
韩伟
2023/11/29
3550
用 PyGame 入门专业游戏开发(一)
PyGame:Python 游戏编程入门-1
pygame是SDL 库的 Python 包装器,它代表Simple DirectMedia 层。SDL 提供对系统底层多媒体硬件组件(例如声音、视频、鼠标、键盘和操纵杆)的跨平台访问。作为停滞不前的PySDL 项目pygame的替代品开始了生活。SDL 的跨平台特性意味着您可以为支持它们的每个平台编写游戏和丰富的多媒体 Python 程序!pygame
苏州程序大白
2022/09/16
2.2K0
PyGame:Python 游戏编程入门-1
Pygame基础2-精灵类 Sprite
在PyGame中,精灵类(Sprite) 是一个常用的类。精灵类有以下好处/用处:
一只大鸽子
2024/03/25
3200
Pygame基础2-精灵类 Sprite
【python游戏编程之旅】第九篇---嗷大喵快跑小游戏开发实例
本系列博客介绍以python+pygame库进行小游戏的开发。有写的不对之处还望各位海涵。
马三小伙儿
2018/09/12
3K0
【python游戏编程之旅】第九篇---嗷大喵快跑小游戏开发实例
Python 游戏编程之实现飞机大战(含源代码)
第一次接触打飞机的时候作者本人是身心愉悦的,因为周边的朋友都在打飞机, 每次都会下意识彼此较量一下,看谁打得更好。打飞机也是需要有一定的技巧的,熟练的朋友一把能打上半个小时,生疏的则三五分钟就败下阵来。
吾非同
2020/11/23
21.4K0
Python 游戏编程之实现飞机大战(含源代码)
Python 玩出花儿了!一文教你用 Python 制作吃豆人游戏! | 附代码
近几年来Python语言得到了快速发展,而Pygame作为Python开发应用和游戏必备的库更是展现了Python的优越性。
AI科技大本营
2020/06/28
4.7K0
Python 玩出花儿了!一文教你用 Python 制作吃豆人游戏! | 附代码
pygame-游戏开发学习笔记(二)–模块表与背景图样例。
if pygame.font is None: print "The font module is not available!" exit()
十四君
2019/11/28
1.3K0
Python游戏开发,pygame模块,Python实现过打地鼠小游戏
打地鼠的游戏规则相信大家都知道,这里就不多介绍了,反正就是不停地拿锤子打洞里钻出来的地鼠~
玖柒的小窝
2021/12/14
8770
用Python实现坦克大战游戏 | 干货贴
《坦克大战》是1985年日本南梦宫Namco游戏公司在任天堂FC平台上,推出的一款多方位平面射击游戏。游戏以坦克战斗及保卫基地为主题,属于策略型联机类。同时也是FC平台上少有的内建关卡编辑器的几个游戏之一,玩家可自己创建独特的关卡,并通过获取一些道具使坦克和基地得到强化。而今天我们就将利用python还原以下坦克大战的制作。
小白学视觉
2020/10/26
1.2K0
用Python实现坦克大战游戏 | 干货贴
Python游戏开发,pygame模块,Python实现扫雷小游戏
游戏界面左上角的数字代表所有方格中埋有雷的数目,右上角是一个计时器。你要做的就是根据提示找出方格中所有的雷。
玖柒的小窝
2021/12/14
1.9K0
Python游戏开发,pygame模块,Python实现扫雷小游戏
python小游戏设计入门5-捡金币游戏(下)
本系列课程是针对无基础的,争取用简单明了的语言来讲解,学习前需要具备基本的电脑操作能力,准备一个已安装python环境的电脑。如果觉得好可以分享转发,有问题的地方也欢迎指出,在此先行谢过。
叶子陪你玩
2020/04/23
1.7K0
python小游戏设计入门5-捡金币游戏(下)
Python pygame库的应用
今天想用pygame库写一个击打外星人飞船的python程序 这个游戏的效果是操纵一个位于屏幕底端的飞船,通过上下左右控制飞船移动方向,按空格发射子弹。游戏中击杀一批飞船后进入下一关卡。每一关卡击打飞船获得的得分递增。若外星飞船触碰飞船或屏幕底端则减一条命。最终计算总得分。 游戏中主要包括了:飞船对象,alien对象,子弹对象,游戏设置,游戏状态,游戏控制按钮,计分板。 1.飞船部分
py3study
2020/01/17
1.1K0
推荐阅读
相关推荐
用 PyGame 入门专业游戏开发(三)
更多 >
领券
社区富文本编辑器全新改版!诚邀体验~
全新交互,全新视觉,新增快捷键、悬浮工具栏、高亮块等功能并同时优化现有功能,全面提升创作效率和体验
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文