惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Martin Fowler
Martin Fowler
博客园 - 【当耐特】
GbyAI
GbyAI
M
MIT News - Artificial intelligence
Microsoft Azure Blog
Microsoft Azure Blog
A
About on SuperTechFans
罗磊的独立博客
Apple Machine Learning Research
Apple Machine Learning Research
腾讯CDC
F
Fortinet All Blogs
IT之家
IT之家
WordPress大学
WordPress大学
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Last Week in AI
Last Week in AI
Google DeepMind News
Google DeepMind News
Jina AI
Jina AI
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
D
DataBreaches.Net
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
H
Help Net Security
V
Visual Studio Blog
小众软件
小众软件
Y
Y Combinator Blog

机核

游戏性能旗舰最强之选,一加 Ace 6 至尊版国补到手价2999元起 6元钱自己更换电动车刹车线 《生化危机9:安魂曲》编剧Haris Orkin专访 摸金游戏?音乐游戏!暗区新赛季这把能弹的琴有何来历? 好评国产武侠SRPG《息风谷战略》免费DLC现已推出 | 机核 GCORES 碎片 《生化危机:安魂曲》将于5月实装首个免费更新内容 | 机核 GCORES 新锐东方游戏,谱写世界新章! 沉浸式恋爱视觉小说游戏《心象演算》免费试玩版现已正式上线 | 机核 GCORES 互动影游《代号三国:龙起》上线!穿越三国与曹操并肩、与佳人同行、与权谋博弈! 《老头收集梦想生活》,游戏酒桌会6,录音笔VOL.689 | 机核 GCORES 破界·共生——《白日梦:无限世界》五大核心游戏特点解析 价格已到史低,锐龙5 9600X/锐龙7 9700X正适合抄底 时间循环之旅即刻启程!《归环》一周目测试今日开启 热门在线韩游变魂游,洛奇衍生作能否打破“花瓶”魔咒? LG UltraGear evo 全新高端显示器系列:当“5K”遇见“AI”,不止强大,更懂热爱 反套路三国互动影游《代号三国:龙起》今日上线! 愿望单登记人数突破10万!备受瞩目的“女儿养成游戏” 《梦幻魔法公主》今日于Steam平台上线!限时八折优惠中 《黑神话:悟空》全球音乐会2026巡演将于4月29日12时开票 | 机核 GCORES “Snowguelike”生存肉鸽挖矿新作《蛙穿雪境》公布发售日期,5月7日正式上线 | 机核 GCORES 重塑移动办公、AI创作新境!全新华硕灵耀Air系列、ProArt 骁龙版震撼首发,创芯未来 均分88:《Saros》媒体评分汇总 | 机核 GCORES 上海烛龙公布合作遗迹探险游戏《吉时已到》首支预告片 | 机核 GCORES 首个独立游戏《萝薇日记》已上线Steam! 烛龙新IP《吉时已到》首曝,打造国内首款中式合作遗迹探险游戏 喜加一:《暗黑破坏神Ⅳ》国服现已开启限时免费领取本体活动 | 机核 GCORES 新版《生化危机》电影定于9月18日上映,官方网站现已上线 | 机核 GCORES 《冲就完事模拟器2》“星球大战”联动DLC正式宣布 | 机核 GCORES SteamController将于5月4日发售,售价99美元 | 机核 GCORES 事已至此,内存这么用也算省钱了,“2+1”非对称双通道应用实测
PyGame 入门游戏开发第二篇
韩大 · 2024-04-15 · via 机核

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

  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() 方法来完成各种游戏行为

上面的代码,在关卡中加入了三个 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 中。

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

加载图像资源

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

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

  1. 把整个图片加载到内存中,变成一个对象(变量)

  2. 截取自己需要的那一部分图像,变成一个对象,存放到 Mahjong 对象的 image 属性和 Rect 属性上。image 属性是 Sprite 基类规定了,用来显示的图像内容属性。而 Rect 属性则决定此 Sprite 对象显示在屏幕上的位置和大小。

上述代码的 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)。

随机生成一桌麻将

上述代码在 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 中。

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

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

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

Mahjong.show() 还有一个功能,就是显示麻将牌移动的动画效果。我们下一篇继续介绍。