根据游戏规则,两张相同图案的麻将,如果互相之间没有其他麻将牌被直线阻隔(中间的距离可以无限),可以通过先后点击选择这两张麻将,消除这两张牌。
要能实现“先后选中”的能力,因此要对鼠标点击的操作做出响应。
需要能控制显示、消失图像,用以表现“选中”麻将,以及显示“消除”的效果。
通过“桌子”的内置数据结构,对麻将牌的位置是否成直线、两个选中的麻将判断是否有阻隔。
对于麻将类 Mahjong 的 update() 方法,增加对于用户输入事件的检测和处理,就能完成“选中麻将”的功能:
第一篇介绍的 Director 类,会在每一帧,都通过 pygame 把所有的用户输入事件,存放到 Director.events 属性中,所以每个 Sprite 的子类对象,都可以在 update() 函数中去检测判断:用户有什么输入。
由于 mahjong.MainScenario 类,在 start() 方法中,构建 Table 这个 Group 的子类对象时,传入了 director 参数
table = Table(self.director)
因此每个 Mahjong 对象,都可以通过其 table 属性获得 director 对象,从而获得每帧更新的用户事件(们):self.table.director.events
桌上所有的 Mahjong 对象,由于存放在 Table 这个 Group 里面,所以每帧其 update() 都会被调用。也就是说,每帧、每个麻将对象,都可以在 update() 里检测一遍:“我”有没有被鼠标点中。用户在此刻的所有操作,会被 pygame 放入 events 列表,需要我们通过循环迭代语句,获取其中的每个事件。
通过 event.type 属性,判断 pygame.MOUSEBUTTONDOWN 就可以知道是否有鼠标按钮按下的事件;随后可以通过 pygame.mouse.get_pos() 可以获得鼠标当前的位置;最后通过 Sprite.rect.collidepoint(pos) 可以判断当前 Sprite 对象是否有“碰撞”到某个 pos 点位置。当前的 Sprite 就是麻将对象,所以我们就判断鼠标是否“点击”到了当前的麻将。
如果第一次选中麻将,在被选中的麻将上显示一个“框框”
被选中的麻将,需要以某个方式记录其坐标
如果已经有一个麻将被选中,选中第二个麻将后,“框框”消失
所有需要控制显示的对象,都继承 Sprite 实现一个类,通过构造器来实现加载某个图像数据。此对象的 image/rect 属性通过加载一个图片作为框框显示,这个图片需要是中间透明的,所以使用的 png 格式。
我们可以建立一个类 Edge,用来显示“选中框”。此类有的 pos 属性是一个数组,记录选中的麻将牌的桌上坐标。
Table 类添加一个属性 edge,持有此 Edge 对象;另外一个属性 is_show_edge 记录框框是否已经显示。
在每帧都调用的 Table.show() 方法里面,根据 is_show_edge 属性,来决定是否 add(self.edge),就可以实现根据 is_show_edge 来显示/消失这个框框。
由于每次显示 Edge 对象的位置不一样,所以在 Table 上增加了一个 show_edge() 方法,用来修改 Table.edge 的位置:
其中 loc 参数表示被选中麻将的坐标,会记录到 Edge.pos 上,同时根据此坐标计算并修改 edge.rect 的位置,并且对 is_show_edge 赋值为 True;当点击事件触发“点击第二张牌”的时候,此属性会被置为 False。
点击第二张牌后,需要判断是否可以消除,代码在 Mahjong.update():
由于 Table.show_edge() 会在 table.edge.pos 记录被选中的第一张麻将的坐标,所以第二张麻将被选中的时候,可以通过这个坐标(i,j)从 table.heap 这个二维数组获得被选中的麻将。
两个牌直接是否有阻隔
被选中的牌不能是空
两张牌的图案是一样的
不能选中两次是同一张牌
如果可以消除,通过对 heap[x][y] 的值赋值 None 就表示了消除。在 Table.show() 里面,会跳过为 None 的 heap 成员,因此就可以作为消除牌的功能实现。
如果不能消除,这里调用了一个 Table.show_text() 方法,用于显示提示文字,后续会介绍如何显示。
在上述逻辑中,通过了以下代码实现“显示”爆炸效果:
self.bomb.show(self.rect.left,self.rect.top)
selected.bomb.show(selected.rect.left,selected.rect.top)
由于在 MainSenario 的 start() 方法中,为每个麻将对象,都添加了一个爆炸对象属性 Mahjong.bomb,所以被选中的两个麻将对象,都可以调用 self.bomb.show() 这个方法,传入了需要显示的坐标。一旦调用这个方法,Bomb 类就会自己通过 Bomb.update() 方法,显示一段时间“爆炸”的图片。如果想内存占用的小一点,也可以在 MainSenario.start() 方法中只构造两个 Bomb 对象,然后在需要爆炸的时候,再显示到对应的位置。
修改自己的显示位置,把自己 add 到“特效层”的 effect 组里
设置一个倒计时属性 counter,需要显示多少帧时间,就设置为多少,这里是 30,也就是一秒,因为 director.fps 设置了 30
通过 update() 方法,每帧对 counter 减一,如果为 0,则从 effect 组里去掉(通过 Group.remove(Sprite) 方法),从而消失。由于 effect 组并不会每帧都清空所有成员,和 table 组不一样,所以不需要每次 update() 都去 add() 一次自己
文字提示,实际上也是一种 Sprite 对象,也需要对 image/rect 进行赋值,和上面的图像不同的是,文字的 image 需要通过选择字体和文字内容进行绘制。如果要显示一段文字在游戏画面上,只需要:
从上面的代码可以看出,我们可以选择文字的字体、颜色,还可以选择和其他内容共同“画”在一个图形上。
由于本游戏只需要在一个地方显示文字,而且字体只需要一种,所以在 Table 对象的属性中构造好字体对象 font、显示文字对象这两个对象 text_sprite。另外,这个提示文字需要自动消失,所以还需要两个属性来记录文字显示了几秒 show_text_time,以及何时开始 start_ticks。这个自动消失的功能和上面的爆炸特效功能类似,但是这里使用了不同方法,纯粹为了学习。
然后写一个 show_text() 的方法,用来在桌上显示文字:
这里需要注意的是 self.show_text_time = time 这句,是记录了当前文字要显示多少秒,这个值会在 update() 中逐渐减少,用以让文字自动消失。下面是 Table.show() 的代码段:
这里可以看到,每次 update() 调用 show(),然后都会判断一下 show_text_time,用以决定是否要显示文字提示。由于 self.start_ticks 记录了启动显示的时间,所以根据 pygame.time.get_ticks() 返回的当前时间(毫秒数),就能知道已经显示了多久。显示和消失也是用 add() 和 remove() 控制。由于 Table.show() 的第一行是 self.empty(),会清空所有在 table 这个 Group 里的 Sprite,所以下面要显示的内容,都必须要调用 self.add()。
从上面的代码可以看到,游戏程序的所有“动态能力”,基本实现思想都是:
因此,游戏系统的动画,也大多数是如此实现,是通过一帧帧的逻辑,来决定如何显示下一个画面,从而形成一个动画。由于 udpate() 函数每帧都要调用,所以尽量减少在这个函数中构建新的对象,或者进行特别慢的操作如等待加载磁盘文件、等待网络响应等。因为如果 update() 特别慢,整个游戏的运行就会感觉特别卡。
下一篇介绍如何实现麻将的移动动画,以及复杂的游戏逻辑判断。
评论区
共 条评论热门最新