前言

大家好,又见面了,经过前几篇文章的连载,相信大家再通过自学,一定都掌握了Python的基础知识了吧。😄 今天开始,问哥开始带着大家逐步向图形化界面前进了。

说实话,玩游戏,更多的时候还是需要图形化界面的,这都2022年了,文字版游戏的年代早过去了。虽然问哥也无比怀念满屏黑底花花绿绿的文字刷屏的文字MUD年代,但也不得不承认,图形化的游戏,甚至3D游戏,才能算得上游戏。所以,GUI编程不可避免。

大家也不用担心,其实我们之前学的Python知识已经足够了,而GUI是通过其他的模块来实现的。比如Python内置的Tkinter,Turtle,第三方模块Pygame和PyZero(Pygame简化版),PyQT5等等等等。这些模块各有各的特点和用法,如果把Python比喻成颜料,那这些模块可以说是笔、圆规和直尺。没有办法只用一套规则就可以掌握所有工具。

但是这些工具各有所长,比如Pygame就是专门用来开发游戏的,所以我们也不需要全部掌握。不过考虑到Tkinter是Python自带的图形化工具,不需要另外安装。虽然功能有限,还是可以开发一些有趣的小程序。问哥决定还是先用Tkinter做几个游戏,再开始Pygame的学习。

首先想到的就是看图猜成语,实现起来比较简单,又可以了解到Tkinter的工作方式,还会接触到一点点面向对象的知识。不过碍于篇幅,问哥还是打算分成上下篇:

上篇 —— 游戏界面的搭建
下篇 —— 后台程序的实现


看图猜成语

1. 玩法简介

看图猜成语大家都玩过吧。规则很简单:打开程序,自动会展示出一张形容成语的图片,然后有若干个迷惑性的汉字。玩家要从里面找到正确的汉字组成成语,成功了即进入下一关。

游戏截图:
在这里插入图片描述

2. 游戏流程

GUI编程有些时候流程图会比较难画,因为键盘敲入、鼠标点击或移动等事件的执行往往不是顺序化的。但是如果能够把流程图画出来,即使很简单的流程图,一样会让我们程序实现起来更加容易和清晰。

看图猜成语的简易流程图如下:

No
Yes
Yes
No
游戏开始
画面展示
玩家选择汉字组成成语
判断玩家输入是否正确
清空玩家的选择
恭喜,是否进入下一关
重新选词
游戏结束

3. 搭建游戏界面

编写GUI游戏程序和纯文本程序有很多不同,其中我认为比较重要的是,游戏的外观设计所占的重要程度大大提高了。一款好的游戏,一定首先是要让人看起来有想要玩的欲望。所以好的UI设计才那么值钱。虽然独立程序猿很难身兼数职,既写代码,又搞美工,但至少可以让画出来的GUI界面简洁整齐一些,功能清楚一些。多尝试,至少要先找到自己看着舒服的排版最重要。

拿这个小项目为例,分析游戏的截图,可以看到游戏界面主要有以下7个部分:在这里插入图片描述

  1. 游戏背景:网上搜寻到的一张适合休闲游戏的背景图片素材
  2. 标题:用PS模板做出来的特效文字图片
  3. 成语图片:网上搜寻到的成语图片素材
  4. 20个干扰汉字:20个显示汉字的按钮(因为要实现点击的效果,所以问哥使用了按钮)
  5. 成语填空处:4个空白文本框(矩形),显示玩家选择的汉字
  6. 关卡说明:第N关的文字,随着游戏关卡变化
  7. 两个功能按钮:绑定了回调函数,实现两个小功能:1)清空用户的选择,2)由电脑随机提示一个汉字。

接下来我们先一步步把这个框架搭出来,程序实现的部分留到后面再改。

1). 初始化游戏窗口和背景

首先我们要有一个游戏窗口,不然上面说的这些素材也没地方放。所以我们可以使用tkinter模块先画出游戏窗口。

别忘记要先引用tikinter模块

import tkinter as tk # 引入tkinter模块并简写成tk
root = tk.Tk() # 定义一个主窗口
root.geometry("500x300") # 主窗口的尺寸
root.resizable(0,0) # 主窗口不可改变大小
root.title('看图猜成语') # 主窗口的标题
root.mainloop() # 主窗口循环展示

有了上面这几条代码,我们就可以在电脑上画出一个宽500像素,高300像素的窗口了。
在这里插入图片描述
大家唯一要注意的是代码格式:

  1. geometry方法里的宽度和高度是字符串格式,而且横纵之比中间是小写字母x;
  2. title方法就是改变左上角的文字,当然左上角的小图标也可以改;
  3. resizable方法是规定玩家可以改变窗口大小的范围,横纵都设置为0表示玩家不可用鼠标改变窗口大小;
  4. mainloop方法放在所有程序的最后,表示窗体循环展示,没有这句代码的话窗口一闪而过,啥也看不见。而其他所有的代码都要放在mainloop之前,表示在循环之内。

接下来我们就要像这张空白的窗口里添加元素了。

2). 准备画布

虽然有很多种方法可以把图片插入到tkinter里,但问哥喜欢用的还是Canvas画布方法。因为画布可以有很大的灵活性,除了可以在上面插入图片,还可以绘制各种形状,而且其他组件也都可以通过定位准确地放在画布上。

**Canvas是Tkinter的一个组件(widget),可以理解为可以摆放在窗口里的一个小模块,就像可以摆在家里的家具一样。还有其他很多组件,如Label, Button, Entry等等,等到我们用到的时候再说。而每个组件其实在程序里就是一个类(面向对象),我们用创建实例的方法创建一个组件。

定义一个Canvas画布只要一条命令,比如下面这样就定义了一个和主窗口一样大小的白色画布:

cv=tk.Canvas(root,bg='white',width=500,height=300)

Canvas类需要的第一个参数就是主窗口,表示将要在哪里放置Canvas画布组件,我们已经在上面建立主窗口的时候定义了一个变量root,所以root就代表了主窗口。而其他的参数都是关键字传参。包括Canvas在内的每个组件都有很多关键字属性,不过常用的也就几样。这里我们就只是用到了bg(背景),width(宽度),height(高度)。

而每种组件还需要使用pack()等定位方法“堆”(pack)到主窗口上,才能被我们看见:

cv.pack()

在这里插入图片描述
但是,在使用pack()把Canvas画布放进窗口之前,我们必须先把其他的元素或组件绘制在画布上,这就好比我们必须先在纸上画好画,才能把它裱起来。不然就只有这样一张白色的画布,其他的东西也画不进去了。所以我们把pack()语句放在root的mainloop()前面,而且都放在最后。

cv.pack()
root.mainloop()

在这之前,我们先在画布上放置其他组件

3). 绘制游戏背景

首先找到一张背景图片。问哥是在网上搜的,大家也可以随便找一张自己喜欢的图片做背景。

需要指出的是,tkinter因为并不是为了支持动画而设计的,所以对图片的支持并不友好,如果你的tkinter的版本是8.5,将不能支持PNG图片。

可以在控制台窗口使用一下命令查看tkinter的版本

import tkinter
tkinter.TkVersion
8.6

Python3.7之后的版本默认都自带8.6版本的tkinter。即便如此,tkinter自己的方法也不支持缩小放大图片等功能。所以使用起来会比较麻烦,需要使用python的另一个内置模块PIL来处理图片。我们以后也会用到,不过基于目前这个小项目,我们可以使用不需要更改大小的PNG图片。

首先使用tkinter的PhotoImage方法将bg.png图片读取到内存中,取个名字叫bg。然后就可以使用Canvas的create_image方法可以将背景图片bg.png放在画布上,具体代码如下:

bg = tk.PhotoImage(file=r"images\bg.png")
cv_bg = cv.create_image(250,150,image = bg)

cv就是我们刚才创建的Canvas画布实例,调用它的create_image方法将内存中的图片bg加载到坐标(250,150)上。这个坐标是图片中心的位置。因为我们的窗口大小是500x300,所以为了让图片居中,就要把图片的中心放到窗口中心位置上,也就是横纵坐标各自减半。

最后可以将创建好的背景取个名字叫cv_gb),我们后面可以直接调用这个名字更改背景(如果有必要的话)。

*******插入图片

4). 插入游戏标题

然后我们需要写上“看图猜成语”的标题。这里我们可以直接写文字,但这样太难看了,于是问哥最后还是决定插入一张图片。大家可以从网上找到各种各样的ps字体文件,找一张自己喜欢的,然后把文字换成“看图猜成语”。由于问哥对ps也不擅长,这里就不过多介绍。总之,得到一张“title.png”的图片,图片内容
再使用刚才插入背景的方法把这张标题图片放进画布。

title = tk.PhotoImage(file=r"images\title.png")
cv_tt = cv.create_image(250,30,image = title)

放入的坐标(250,30)表示横轴居中(窗口宽度500的一半),纵向从上面向下30个像素。

图形化界面的坐标总是以左上角为原点(0, 0),横轴像素向右递增,纵轴像素向下递增。

在这里插入图片描述

注意: 一定要注意放入的先后顺序,如果先放入标题再放入背景,背景图片就会把标题盖住了。在画布上绘制其他组件的时候也是要注意这个“先放的在下面”的顺序。

5). 显示成语图片

现在我们就可以把准备好的成语图片,也如法炮制,插入到画布的合适位置。如何随机显示成语图片,我们在下篇代码实现的部分再讲解。这里我们就选取第一张图片放进来。

img = tk.PhotoImage(file=f"images\words\一帆风顺.png")
cv_word = cv.create_image(150,120,image = img)

位置坐标是问哥多次调整选择的最佳位置,大家不用纠结,可以自己改动坐标看看位置怎样才顺眼。

这时我们发现,因为成语图片是透明的(PNG格式),所以背景图片也显示出来了。这样不一定不好,但容易让成语图片里的元素和背景图片混淆起来,使得成语看起来不那么清楚。

于是我们还在在成语图片的下面,背景图片的上面,“垫”上一层“布”。而我们可以在Canvas画布上面绘制一个矩形的图片,来充当这层“布”的作用。

可以调用Canvas类的create_rectangle方法来绘制这样一个矩形。

cv.create_rectangle(90,60,210,180,fill='moccasin',outline = '')

下面来讲解一下create_rectangle方法的几个常用参数。

  1. 前面4个数字代表的是矩形的左上角横纵坐标右下角横纵坐标。大家注意这与绘制图片的位置坐标是不同的,图片的坐标是代表图片中心位置。而绘制矩形使用左上角与右下角坐标,也就代表着可以随意改变矩形的大小。这里为了可以完美地“垫”在成语图片下面,我们需要绘制一个大小和位置都和成语图片相同的矩形。所以要做一点小算数。已知成语图片的大小是120x120,中心坐标是(150, 120),所以横轴方向150减去120的一半,得到90,纵轴方向120减去120的一半,得到60,就是矩形的左上角的坐标;同理,(150+120/2,120+120/2)就是矩形右下角的坐标。这样就画出一个与成语图片大小和位置都相同的矩形了。
  2. outline参数时矩形边框的颜色,默认为黑色。问哥是想实现一个“牛皮纸”的效果,所以不需要边框,这里就设置为空了。
  3. fill参数表示矩形填充的颜色。这里可以使用颜色的16进制编码,也可以使用tkinter内置的一些定义好的颜色名称。tkinter内置的颜色名称可以参考下图:

在这里插入图片描述
问哥刚才说过,想要实现一个牛皮纸的效果,所以找了个鹿皮色代替了 😄
在这里插入图片描述
记住,刚才说过,画图顺序很重要,我们要把这一层矩形放在成语图片和背景图片之间,所以绘制的顺序,也是先画背景,再画矩形,最后画成语图片。看起来样子是这样的:
在这里插入图片描述
由于我们后面并不需要改变这个矩形的位置等属性,所以不用定义一个变量来代表这个矩形。

6). 显示成语填空处

下面是要在成语图片下面做几个填汉字用的空白。因为在这个项目里,我们不需要玩家使用键盘输入汉字,所以并不需要使用文本框。所以只要绘制四个空白的矩形,预留好汉字的位置。等到后面代码实现的时候,由玩家选择按钮来填充对应的汉字就可以了。

所以我们如法炮制,绘制4个矩形:

cv.create_rectangle(50,210,86,246,fill='ivory')
cv.create_rectangle(100,210,136,246,fill='ivory')
cv.create_rectangle(150,210,186,246,fill='ivory')
cv.create_rectangle(200,210,236,246,fill='ivory')

这些坐标是问哥多次尝试出来的较为合适的位置,大家也可以自己改动,看看效果如何。

问哥觉得这四个矩形保留边框比较合适,看起来更像是填空,所以就没有指定outline参数。而颜色则使用了上面颜色列表里的象牙白(ivory)。同样,我们也不需要再定义额外的变量来代表这四个矩形,只需要画一遍就可以了。

然后我们注意到,其实这四个矩形都是平行的,然后横向间距相等,所以可以利用一个4次的循环语句,是代码看起来简洁一些。如下代码实现的是相同的效果:

for i in range(4):
    cv.create_rectangle(50*i+50,210,50*i+86,246,fill='ivory')

效果是这样:
在这里插入图片描述
至于玩家如何实现选择汉字插入,我们在下篇再介绍。

7). 插入20个按钮

对于右边放置可供玩家选择的汉字字库,我们可以使用刚才填空的方法绘制矩形,也可以使用按钮。因为问哥考虑到后面我们要实现玩家点击的效果,所以这里就用tkinter的按钮来实现了。

由于Canvas自己没有按钮,我们要使用tkinter的按钮组件,声明方式也和我们开始时声明一个Canvas画布组件类似,就是实例化tkinter模块的Button类。下面这句代码就是创建一个Button按钮。

btn = tk.Button(root, font =('方正楷体简体',11),width=2,relief='flat',background='lightyellow')

现在简要介绍一下定义Button时的几个常用参数:

  1. background参数。也可以简写为bg,和Canvas的bg参数功能一样,就是定义按钮的背景颜色。问哥在这里选择了lightyellow,浅黄色。

  2. width宽度。定义按钮的宽度,这里的数字并不是以像素为单位,而是必须是整数,且最小为0(负数取绝对值),表示一个单位宽度。如果省去这个参数,按钮宽度将自动随着按钮上的文字大小改变宽度。

  3. relief参数。按钮的样式。tkinter自带的按钮提供了6种样式,分别是flat, groove, raised, ridge, solid, 和sunken。默认为raised,也就是我们平时常见的按钮样式:
    在这里插入图片描述
    flat:
    在这里插入图片描述
    groove:
    在这里插入图片描述
    ridge:
    在这里插入图片描述
    solid:
    在这里插入图片描述
    sunken:
    在这里插入图片描述
    这里喜欢那个就用哪个,问哥使用了flat样式,你也可以选择更喜欢的。

  4. font字体参数。font参数用于定义按钮上文字显示的一些属性,在这个参数里我们可以赋值一个元组,里面是字体的名称、大小、粗斜体等等。查看电脑里有哪些字体可以被tkinter调用,可以输入下面这条命令,注意,一定要先定义一个主窗口,才可以查看字体。

from tkinter import Tk, font
root = Tk()
font.families()

按钮创建好了,我们要把它们绘制在画布上。由于Button按钮是和Canvas同级的类,所以我们要使用Canvas的create_window方法,把按钮这个组件放在画布的哪个位置。

btn_window = cv.create_window(300, 75, window=btn)

和刚才放置填空的矩形一样,我们找到规律以后,就可以使用双层循环来布置一个5行4列的等距按钮,代码如下:

for i in range(4):
    for j in range(5):
        btn = tk.Button(root, font =('方正楷体简体',11),width=2,relief='flat',bg='lightyellow')
        btn_window = cv.create_window(300+40*i, 75+35*j, window=btn)

效果如图:
在这里插入图片描述
按钮上的汉字我们先留白,等到下篇代码实现的时候,再把汉字打乱,显示在按钮上。

8). 插入2个功能小按钮

在20个打乱的汉字下面,我们还想要提供两个小按钮,一个使玩家可以擦掉自己选错的汉字,另一个是可以使电脑提示一个汉字,这种类似作弊的选项。注意:我们并不需要再额外设置一个“提交”按钮让电脑来判断玩家的选择正确不正确,因为只要玩家选择了4个汉字,电脑就可以自动来判断。如果不对,则自动擦掉,让玩家重新选择,如果正确,则弹出对话框,询问是否进入下一关。

关于按钮的样式,我们在上一节已经详细解释了。可是看来看去,问哥没有选中心仪的按钮样式。比如,如果使用下面代码,选择默认的按钮样式,按钮会这样展现:

btn_clean=tk.Button(root, text='清空', width=5)
btn_submit=tk.Button(root, text='提示', width=5)
cv.create_window(320, 265, window=btn_clean)
cv.create_window(400, 265, window=btn_submit)

在这里插入图片描述
这样并不是不好,但问哥总是觉得看起来不太舒服,容易让人有那种死板的办公软件的感觉(可能是问哥自己的原因 😃)。所以我们可以使用tkinter的另一个子模块ttk来创建另一种风格的按钮。

注意代码的区别,多了一个字母 t :

from tkinter import ttk
btn_clean=ttk.Button(root, text='清空', width=5)
btn_submit=ttk.Button(root, text='提示', width=5)
cv.create_window(320, 265, window=btn_clean)
cv.create_window(400, 265, window=btn_submit)

ttk子模块风格的按钮是这个样子的:
在这里插入图片描述
而且鼠标移到按钮上面还会变色,使得按钮灵动许多。
在这里插入图片描述

9). 关卡说明

最后一个小细节就是在底部加上关卡说明文字,告诉玩家已经前进了多少关。我们可以直接在Canvas画布上面创建文字。同时创建一个变量level,用来记录当前的关卡号。

使用下面的代码可以把这段文字放在指定位置:

level=1
level_indicator = cv.create_text(150,270,text=f'第 {level} 关', fill='black', font=('微软正楷',9,'bold'))

和上面介绍过的Canvas的create_image,create_rectangle,create_window等方法类似,创建文本,我们可以使用create_text的方法。这个方法的几个常用参数如下:

  1. 坐标:和其他方法里的坐标一样,需要注意的是,这个坐标代表的也是文本中心的位置。
  2. fill参数:文字的颜色。
  3. font参数:和其他组件的font参数一样,指定文本的字体、大小、粗细等。

到此,我们已经完整得把整个游戏界面搭起来了:
在这里插入图片描述
虽然它还不能动,不能和玩家交互,但看起来也有那么点意思了。下篇,问哥会带着大家一点点把Python代码加进去,实现游戏背后的逻辑部分。

4. 知识点回顾

本篇虽然没有讲解游戏逻辑的代码实现部分,但关于如果使用tkinter绘制游戏界面,还是介绍了不少东西,希望大家都能动手试试:

  1. tkinter模块的引入
  2. Canvas类画布组件
    2.1 插入图片方法create_image
    2.2 绘制矩形方法create_rectangle
    2.3 插入tkinter里其它与Canvas同级的组件方法create_window
    2.4 插入文本方法create_text
  3. Button类按钮组件
  4. ttk 子模块的按钮样式

总结与思考

希望大家都能自己动手试试,虽然在代码中问哥已经将坐标设定好,但是在创作的过程中,还是需要反复修改坐标值,不断进行微调,才能找到令自己满意的位置。这个过程如同画画,还是需要一点耐心的。相比而言,编程语言反而变得次要了些。

而游戏是动态的,需要和玩家交互,所以我们需要用代码让这些组件工作起来。下篇内容问哥会带着大家使用代码实现这个小项目。

感谢大家读到这里,我们下次再见!

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐