Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >用 PyGame 入门专业游戏开发(二)

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

作者头像
韩伟
发布于 2023-12-04 04:48:13
发布于 2023-12-04 04:48:13
44900
代码可运行
举报
文章被收录于专栏:韩伟的专栏韩伟的专栏
运行总次数: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 删除。

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

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
web渗透测试—-33、HttpOnly[通俗易懂]
HttpOnly是微软公司的Internet Explorer 6 SP1引入的一项新特性。这个特性为cookie提供了一个新属性,用以阻止客户端脚本访问Cookie,至今已经称为一个标准,几乎所有的浏览器都会支持HttpOnly。 下面示例显示了HTTP响应标头中HttpOnly使用的语法:
全栈程序员站长
2022/09/07
2.7K0
tomcat源码解读六 tomcat中的session生命历程
     session的作用是在一次会话中(从打开浏览器到关闭浏览器同当前服务器的交流)当客户端第一次请求session对象时候,服务器会为客户端创建一个session,并将通过特殊算法算出一个se
cfs
2018/03/08
1.5K0
tomcat源码解读六  tomcat中的session生命历程
JSP基础--会话跟踪技术、cookie、session
我们需要先了解一下什么是会话!可以把会话理解为客户端与服务器之间的一次会晤,在一次会晤中可能会包含多次请求和响应。例如你给10086打个电话,你就是客户端,而10086服务人员就是服务器了。从双方接通电话那一刻起,会话就开始了,到某一方挂断电话表示会话结束。在通话过程中,你会向10086发出多个请求,那么这多个请求都在一个会话中。
eadela
2019/09/29
9940
JSP基础--会话跟踪技术、cookie、session
浅谈Session与Cookie的区别与联系
一、Session的概念 Session 是存放在服务器端的,类似于Session结构来存放用户数据,当浏览器 第一次发送请求时,服务器自动生成了一个Session和一个Session ID用来唯一标识这个Session,并将其通过响应发送到浏览器。当浏览器第二次发送请求,会将前一次服务器响应中的Session ID放在请求中一并发送到服务器上,服务器从请求中提取出Session ID,并和保存的所有Session ID进行对比,找到这个用户对应的Session。 一般情况下,服务器会在一定时间内(默认30
mukekeheart
2018/07/04
3.1K0
Session会话与Cookie简单说明
会话(Session)跟踪是Web程序中常用的技术,用来跟踪用户的整个会话。常用的会话跟踪技术是Cookie与Session。Cookie通过在客户端记录信息确定用户身份,Session通过在服务器端记录信息确定用户身份。经常有人会疑惑:Session会话与Cookies的区别是什么?用户登录的原理是什么?网站是如何认证的?它怎么知道是哪个用户从哪儿登录进来的?下面将对这些问题进行一一解答。
洗尽了浮华
2022/03/29
1.9K0
Session会话与Cookie简单说明
HttpSession详解「建议收藏」
  需要注意的是,一个Session的概念需要包括特定的客户端,特定的服务器端以及不中断的操作时间。A用户和C服务器建立连接时所处的Session同B用户和C服务器建立连接时所处的Session是两个不同的Session。
全栈程序员站长
2022/09/07
1.4K0
HttpSession详解「建议收藏」
会话跟踪技术-session
javax.servlet.http.HttpSession接口表示一个会话,我们可以把一个会话内需要共享的数据保存到HttSession对象中!
星哥玩云
2022/09/14
4250
会话跟踪技术-session
Session和Cookie和Filter和Listener(最全 最精美)
Session的超时时间也可以在web.xml中修改。另外,通过调用Session的invalidate()方法可以使Session失效。 <session-config> <session-timeout>30</session-timeout> </session-config>
编程张无忌
2021/01/26
7720
Session和Cookie和Filter和Listener(最全 最精美)
Cookie 和 Session 的那点事!
我曾在HTTP文章中立下过这个flag,现在这篇就是来兑现的!温情提示,本文使用的测试浏览器为Firefox 83.0(64位)。
东边的大西瓜
2022/05/05
3700
Cookie 和 Session 的那点事!
开发中经常碰到的问题cookie和session问题,今天一并解决
会话可简单理解为:用户开一个浏览器,点击多个超链接,访问服务器多个web资源,然后关闭浏览器,整个过程称之为一个会话。
好好学java
2018/09/21
3K0
开发中经常碰到的问题cookie和session问题,今天一并解决
javaWeb中cookie和session的区别和使用场景
说到cookie和session先从二者的英文单词含义说起,cookie翻译为中文是小饼干的意思,session翻译成中文是会话的意思。从翻译就能看出来,cookie是服务器返回给浏览器的一些断断续续的东西,而session是一种会话机制。那么为什么要用cookie和session呢? 因为Http协议是一种无状态协议,服务端需要记录用户的状态时,就需要用某种机制来识具体的用户,这个机制就是Session,而session是如何实现状态保持的呢? ---- 这个时候cookie作用就体现出来了! 每次HT
神秘的寇先森
2018/05/30
6410
一文搞明白Cookie、Session与Token
HTTP 协议是一种无状态协议,即每次服务端接收到客户端的请求时,都是一个全新的请求,服务器并不知道客户端的历史请求记录;Session 和 Cookie 的主要目的就是为了弥补 HTTP 的无状态特性
中龙技术
2022/09/30
1.7K0
一文搞明白Cookie、Session与Token
Session、Cookie、Token三者关系理清了吊打面试官
HTTP 协议是一种无状态协议,即每次服务端接收到客户端的请求时,都是一个全新的请求,服务器并不知道客户端的历史请求记录;Session 和 Cookie 的主要目的就是为了弥补 HTTP 的无状态特性。
淘课之家
2020/04/09
2.2K0
Session、Cookie、Token三者关系理清了吊打面试官
javaWeb核心技术第八篇之Cookie和Session
会话技术: 会话是什么? 浏览器和服务器交互,浏览器打开网页访问服务器,会话开始,正常交互. 浏览器关闭,会话结束. 会话能干什么? 会话可以共享数据. Cook
海仔
2019/08/26
8180
session和cookie的区别和联系
大家都知道,session是存储在服务器端的,cookie是存储在客户端的,session依赖于cookie。
用户2141593
2019/02/20
7720
JavaWeb(二)会话管理之细说cookie与session
前言   前面花了几篇博客介绍了Servlet,讲的非常的详细。这一篇给大家介绍一下cookie和session。 一、会话概述 1.1、什么是会话?   会话可简单理解为:用户开一个浏览器,点击多个超链接,访问服务器多个web资源,然后关闭浏览器,整个过程称之为一个会话。   一次会话指的是:就好比打电话,A给B打电话,接通之后,会话开始,直到挂断电话,该次会话就结束了,而浏览器访问服务器,就跟打电话一样,浏览器A给服务器发送请求,访问web程序,该次会话就已经接通,   其中不管浏览器发送多少请求(就相
用户1195962
2018/01/18
1.4K0
JavaWeb(二)会话管理之细说cookie与session
Java学习之Cookie与Session篇
Cookie 和Session 不同的地方是 cookie是存在于客户端,而session是存在于服务器上。
全栈程序员站长
2022/07/13
1460
Cookie 和 Session 机制原理分析 & 区别对比
Web application servers are generally "stateless":
一个会写诗的程序员
2018/09/12
1.2K0
26. 会话技术-Session的使用
可以通过获取的 session ID 信息,我们可以知道 session 是同一个的。
Devops海洋的渔夫
2021/11/19
1.1K0
26. 会话技术-Session的使用
Spring Mvc boot解决静态url带jsessionid问题
Jsessionid只是tomcat的对sessionid的叫法,其实就是sessionid;在其它的容器也许就不叫jsessionid了。
王念博客
2019/07/24
3.2K0
相关推荐
web渗透测试—-33、HttpOnly[通俗易懂]
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验