mirror of
https://github.com/cheetahlou/CategoryResourceRepost.git
synced 2025-10-21 01:13:45 +08:00
mod
This commit is contained in:
198
极客时间专栏/从0开始学游戏开发/第三章:UI和键盘鼠标/第16讲 | 如何在游戏中载入UI和菜单?.md
Normal file
198
极客时间专栏/从0开始学游戏开发/第三章:UI和键盘鼠标/第16讲 | 如何在游戏中载入UI和菜单?.md
Normal file
@@ -0,0 +1,198 @@
|
||||
|
||||
今天我们要在游戏中载入UI和菜单,在开始之前,我们先调整一下,我们之前讲过的游戏代码部分的内容。
|
||||
|
||||
首先我们需要更改游戏的背景图片,使之看起来更像是一款打飞机的游戏,而不是最早之前我们随便用的一幅山水图。我们先将游戏背景修改为正常的游戏背景,并且贴上飞机图像。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/3c/2c/3cbd72b8968c23eaeb57737a9393072c.jpg" alt="">
|
||||
|
||||
这里,我想到一个问题,之前有人留言问我,程序员需不需要有美术功底。我在这里说一下我的看法。如果你只是要做一个程序员,那我可以告诉你,不需要。但是,如果你不是只想做一个“码农”,你想有更多的发展,那各方面的知识,比如策划、美术,你都需要去了解。
|
||||
|
||||
## UI的两种呈现形式
|
||||
|
||||
言归正传,我们需要在这个游戏画面上面,加一系列的内容来代表UI的呈现。UI的呈现有两种形式:
|
||||
|
||||
- 第一种就是美术画好UI图片,然后直接贴图,用鼠标控制一系列的点击和按下操作;
|
||||
- 另外一种是自己画UI界面,比如画一个框,并且在框里面写上文字。
|
||||
|
||||
这两种方式各有利弊。
|
||||
|
||||
如果使用美术UI图贴图的方式,**优点**就是可以减少程序员的工作量。这样在版本迭代的时候,美术改一幅图就可以修改界面,方便快捷,你就不需要做多余的工作。但是这样的**缺点**就是,增加了游戏安装包的大小,毕竟UI是一幅图,只要是图就会有一定的体积,就会增加安装包的大小。
|
||||
|
||||
如果是程序员自己绘制的UI界面,**好处**就是主程序体积变得稍大一点,而游戏安装包不会变大。但是这样的**缺点**也很明显,就是程序员的工作量会增加很多。而且当游戏需要迭代的时候,或者界面需要更新的时候,程序员需要重新绘制或者重新编写UI源代码,大大增加了工作量。
|
||||
|
||||
我们现在是自己来开发,那我就讲一讲程序员绘制UI的方法。
|
||||
|
||||
我们通过模拟按钮的方式来摆放UI界面。首先,我们要在UI界面上摆放一系列字符,我们要实现的效果是,只要使用鼠标点击到这个字符,就会变换字符的内容。这个过程,我们会用到鼠标操作、绘制矩形、字体和字符绘制相关的知识,下面我就来具体给你讲。
|
||||
|
||||
## 鼠标操作
|
||||
|
||||
我们先来看一下鼠标操作的知识。在Pygame中,鼠标操作用到的模块是pygame.mouse。在这个模块里面,点击事件的函数是get_pressed.假如有返回按钮1、按钮2和按钮3等等很多按钮,我们随便选一个点,假如说选了按钮1,那代码可以这么写:
|
||||
|
||||
```
|
||||
pygame.mouse.get_pressed()[0]
|
||||
|
||||
```
|
||||
|
||||
这条语句不需要在事件语句里面操作,写在别的地方也可以,而且鼠标的操作在循环里一直是实时监测的。
|
||||
|
||||
## 绘制矩形
|
||||
|
||||
随后,我们要绘制矩形。 **绘制矩形的目的是为了模拟一个按钮。** 矩形绘制的代码是Rect,但是我们需要绘制在一个surface上,这个surface需要新建,然而在pygame中,如果使用pygame.surface.surface初始化一个surface的话,不能指定位置,x值是从0开始的。
|
||||
|
||||
所以在屏幕上看到的新建的surface是一个长条状的图层,所以我们需要将生成一个图层的子图层,并且,如果使用这个子图层的话,在blit的时候将会提示被锁定,所以,我们还需要将这个子图层进行拷贝。所以,我们的代码看起来是这个样子的。
|
||||
|
||||
```
|
||||
the_rect = Rect(200, 100, 150, 40)
|
||||
block_surface = screen.subsurface(the_rect).copy()
|
||||
|
||||
```
|
||||
|
||||
首先第一行代码是建立一个矩形,分别是左侧起始值是200,顶部从100开始,宽度150,长度40。随后,使用screen这个图层来建立一个子图层,子图层的大小按照the_rect这个矩形大小来建立。随后的一个copy函数,是将子图层进行拷贝,在后续的使用中,不会出现锁定图层的情况。
|
||||
|
||||
## 绘制字体和字符
|
||||
|
||||
之后我们要开始编写文字处理的代码,字体我们要用到pygame.font模块,我们先初始化一个字体,这个字体在安装pygame游戏库的时候就包含在了pygame里面,我们直接就可以拿来使用。现在我们初始化字体,并且将字体大小调整到25:
|
||||
|
||||
```
|
||||
fnt = pygame.font.Font('freesansbold.ttf',25)
|
||||
|
||||
```
|
||||
|
||||
其中,freesansbold.ttf是pygame安装的时候默认存在的ttf字体文件,随后,我们在第二个参数设置为大小25。
|
||||
|
||||
我们拿到了fnt对象,然后使用这个对象调用render函数。这其实就是渲染,将文字渲染在屏幕,并且形成一个文字图层,函数原型是这样的:
|
||||
|
||||
```
|
||||
Font.render(text, antialias, color, background=None)
|
||||
|
||||
```
|
||||
|
||||
其中第一个参数text是文字内容,第二个参数antialias是抗锯齿,第三个内容color是文字颜色,最后一个是背景颜色,默认可以忽略。
|
||||
|
||||
```
|
||||
tsurf = fnt.render(text, True, (255,255,255))
|
||||
|
||||
```
|
||||
|
||||
我们将颜色设置为白色,所以是(255,255,255)。
|
||||
|
||||
tsurf是render返回的一个文字的图层(surface),我们之后要按照这个图层,来确定它的矩形框。
|
||||
|
||||
```
|
||||
trect = tsurf.get_rect()
|
||||
|
||||
```
|
||||
|
||||
随后我们需要将文字摆在这个trect矩形框的中央,所以我们要进一步将trect确定在中央的位置,计算完中央的坐标值并且赋值过去。
|
||||
|
||||
```
|
||||
trect.center = ((block_surface.get_width()/2),(block_surface.get_height()/2))
|
||||
|
||||
```
|
||||
|
||||
我们将最开始的复制的子图层的宽度和高度除以2,就得到了中心点的位置。
|
||||
|
||||
最后我们要做的就是在封装的函数内将blit部分包含进去,现在我们来看一下完整的包装函数代码。
|
||||
|
||||
```
|
||||
def text_out(text):
|
||||
fnt = pygame.font.Font('freesansbold.ttf',25)
|
||||
tsurf = fnt.render(text, True, (255,255,255))
|
||||
trect = tsurf.get_rect()
|
||||
trect.center = ((block_surface.get_width()/2),(block_surface.get_height()/2))
|
||||
block_surface.blit(tsurf, trect)
|
||||
|
||||
```
|
||||
|
||||
我们看到,在函数最后,我们blit了block_surface这个被拷贝的图层。
|
||||
|
||||
随后我们在游戏的大循环里面,需要判断鼠标的点击事件,我们之前所定义的矩形,代码是这样:
|
||||
|
||||
```
|
||||
the_rect = Rect(200, 100, 150, 40)
|
||||
|
||||
```
|
||||
|
||||
所以这x的起始位置和结束位置是200和350,y轴的起始位置和结束位置是200和240,为什么y轴也是200开始呢?因为起始点 200既是x轴开始的点也是y轴开始的点。我们在代码里面这么判断鼠标的点:
|
||||
|
||||
```
|
||||
txt = "Pause"
|
||||
x, y = pygame.mouse.get_pos()
|
||||
if pygame.mouse.get_pressed()[0]:
|
||||
if x >=200 and x <= 350 and y >= 200 and y <= 240:
|
||||
txt = "Clicked
|
||||
|
||||
```
|
||||
|
||||
我们将txt定义为Pause字符串,并且判断是不是鼠标左键点击的。如果是的话,判断是不是在 x 轴和y轴进行点击,如果不是的话,就将txt改为Clicked。
|
||||
|
||||
随后我们绘制按钮框,并且将按钮框背景设置为绿色,然后输出文字,并且绘制。
|
||||
|
||||
```
|
||||
screen.blit(block_surface, (200, 200))
|
||||
block_surface.fill([0,20,0])
|
||||
text_out(txt)
|
||||
|
||||
```
|
||||
|
||||
将block_surface这个图层绘制在(200,200)的坐标点,并且将之涂为绿色,最后调用text_out函数,由于text_out里面已经编写了blit函数,所以不需要再次blit了。
|
||||
|
||||
我们来看一下效果图:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/fb/a4/fb4e358092d3ad315e54f61cb984fca4.jpg" alt="">
|
||||
|
||||
我们看到了一个绿色的按钮放置在屏幕上,并且有一个白色的Pause字样放在按钮上,如果是鼠标左键点击在这个按钮上,就会变成Clicked字样。
|
||||
|
||||
到这里,你可能会问了,为什么没有解释怎么输出中文呢?
|
||||
|
||||
在这种情况下,输出中文有两种解决方案。
|
||||
|
||||
<li>
|
||||
第一种是比较底层的方案,就是**根据中文进行点阵绘制**,这需要很底层的代码操作,效率也不太高,所以这种方案我们不作讨论。
|
||||
</li>
|
||||
<li>
|
||||
第二种就是**改变字体**,我们可以在初始化字体的时候,下载一些网上的中文字体先进行尝试,或者我们可以使用系统字体。在使用系统字体的时候,我们可以使用SysFont来初始化字体。
|
||||
</li>
|
||||
|
||||
```
|
||||
fnt = pygame.font.SysFont('微软雅黑',32)
|
||||
|
||||
```
|
||||
|
||||
另外,还需要修改一个地方。我们需要在Python代码最开始的地方添加编码方式,并且将中文文字前面添加u字样来告诉解释器是Unicode的。
|
||||
|
||||
```
|
||||
# encoding: utf-8
|
||||
txt = u"暂停"
|
||||
|
||||
```
|
||||
|
||||
我们来看一下效果:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/bb/d2/bba42a0cb409eedaab6a78f02cc456d2.jpg" alt="">
|
||||
|
||||
至此,中文的输出也已经完成了。
|
||||
|
||||
到现在为止,我们编写完了UI的按钮部分。至于菜单部分,我们也可以通过相同的方式来编写菜单效果。
|
||||
|
||||
作为菜单这些高级操作,比如点击出现下级菜单、隐藏菜单这些动态效果,可以使用图片的方式来制作菜单,并且进行载入。如果使用程序来编写菜单的效果,工作量就太大了。而如果是图片形式的话,只需要载入并且控制鼠标点击和绘制子菜单就可以了。
|
||||
|
||||
## 小结
|
||||
|
||||
今天我们学习了UI部分的编写以及文字的输出、鼠标的移动和抓取鼠标按键的实现,我来总结一下今天的内容。
|
||||
|
||||
<li>
|
||||
不管何种类型的游戏引擎,鼠标的操作基本有3个值需要控制,左键、中键和右键。
|
||||
</li>
|
||||
<li>
|
||||
按钮可以用常规的方法来绘制。如果想做出更好的效果,比如画框,可以在框里面再画一个小框,看起来像是有凸出感觉的样子。这就需要你平时多注意观察一些细节,然后去分析如何用我们讲过的简单的操作来实现这些内容。
|
||||
</li>
|
||||
<li>
|
||||
在2D游戏中,很多游戏引擎都不支持中文的输出。如果要输出中文,假如你的引擎支持,那你可以使用系统字体或者其他中文字体;如果引擎不支持,可以使用一个一个点阵绘制的方式在屏幕上绘制中文。最后一种方式,也就是比较极端的方式,那就是使用图片来直接贴上中文字符,这种方式直接粗暴,但是图片资源量太大,而且如果你要在游戏中进行网络聊天,这里面其实还是没有从根本上解决中文输出的问题。
|
||||
</li>
|
||||
|
||||
现在给你留一个小问题。
|
||||
|
||||
如果让你在上述的代码中,将按钮变成菜单,也就是点击按钮,就在下方出下一个下拉框,你会如何实现?
|
||||
|
||||
欢迎留言说出你的看法。我在下一节的挑战中等你!
|
185
极客时间专栏/从0开始学游戏开发/第三章:UI和键盘鼠标/第17讲 | 如何用鼠标和键盘来操作游戏?.md
Normal file
185
极客时间专栏/从0开始学游戏开发/第三章:UI和键盘鼠标/第17讲 | 如何用鼠标和键盘来操作游戏?.md
Normal file
@@ -0,0 +1,185 @@
|
||||
<audio id="audio" title="第17讲 | 如何用鼠标和键盘来操作游戏?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/a2/07/a2860a659da0f8cf1d9a02e777de6f07.mp3"></audio>
|
||||
|
||||
**如果一款游戏不能用鼠标和键盘操作,那我们只能当动画来看。**
|
||||
|
||||
所以在一款游戏中,鼠标和键盘的操作是必不可少的,有时候甚至鼠标和键盘都要同时操作,比如FPS游戏,比如即时战略等等。鼠标和键盘的操作在Pygame中需要进行实时检测,这个上一节我有提到过,然后我们就可以对游戏画面进行操作。
|
||||
|
||||
我们在Pygame中有两种方式可以检测和编写键盘事件,一种是使用**event事件**操作,一种是使用**keypressed函数**来进行键盘的判断操作。
|
||||
|
||||
我们先来尝试一下使用事件进行操作的键盘事件。我们在之前的代码中,已经使用事件来判断是不是退出,我们来看如下代码:
|
||||
|
||||
```
|
||||
for event in pygame.event.get():
|
||||
if event.type == QUIT:
|
||||
pygame.quit()
|
||||
|
||||
```
|
||||
|
||||
在这段代码里面,event.type的类型如果是QUIT的话,就让pygame退出,那么举一反三,我们也可以在里面写上如下代码:
|
||||
|
||||
```
|
||||
if event.type == KEYDOWN:
|
||||
if event.key == pygame.K_w:
|
||||
.....
|
||||
|
||||
```
|
||||
|
||||
在这里,我们判断事件的类型是KEYDOWN,也就是键盘按下的事件,随后我们再在下面的代码里,判断event所返回键盘key值,是pygame.K_w,这个K_w是pygame里面定义的虚拟键盘的按键,代表的是Key值为键盘w的按键,所以你只要按下w键,就会出现对应的操作。
|
||||
|
||||
我们来写下一系列的操作代码,在编写代码之前,我们首先要来定义一下规则。
|
||||
|
||||
我们的目的,是要让主角的飞机移动起来,所谓的飞机的移动,我们在前面几篇课程里面都有阐述。如果我们要让飞机在画面上移动起来,就需要修正飞机的x轴和y轴。
|
||||
|
||||
相应的,如果飞机往左侧飞,就需要减少飞机的x轴;如果飞机往右侧飞,就要增加飞机的x轴;如果往上面飞,就要减少飞机的y轴;如果往底下飞,就要增加飞机的y轴。我们先理清楚了这些内容之后,就可以编写键盘操作代码了。
|
||||
|
||||
我们先来修正飞机的x轴和y轴。我们要在游戏的循环之外,定义两个变量xx和yy,以修正键盘操作后的飞机坐标。
|
||||
|
||||
```
|
||||
xx = 0
|
||||
yy = 0
|
||||
|
||||
```
|
||||
|
||||
在定义完了这些内容后,我们再来看看按键的定义。
|
||||
|
||||
```
|
||||
if event.type == KEYDOWN:
|
||||
if event.key == pygame.K_w:
|
||||
yy -= 1
|
||||
if event.key == pygame.K_s:
|
||||
yy += 1
|
||||
if event.key == pygame.K_a:
|
||||
xx -= 1
|
||||
if event.key == pygame.K_d:
|
||||
xx += 1
|
||||
|
||||
```
|
||||
|
||||
首先,和普通的游戏一样,我们将电脑键盘上的WSAD按键用作上下左右的操作按键,所以我们判断了一系列的按键值,比如K_w,K_s等等,然后我们看到,xx, yy 的一系列操作,然后我们进行飞机的贴图和操作:
|
||||
|
||||
```
|
||||
screen.blit(pln, (100+xx, 300+yy))
|
||||
|
||||
```
|
||||
|
||||
我们看到,基础坐标值是(100,300)。我们经过键盘操作,对xx和yy进行位置的修正。到这里为止,我们可以看到,只要我们按下WSAD中的任意一个按键,飞机就会往指定的位置移动。
|
||||
|
||||
所以如果你认为到这里按键的内容就结束了,那就错了,就像我们今天开头所说的,Pygame下的键盘类,还有另外一种方式可以检测,你可以考虑下面的代码。
|
||||
|
||||
```
|
||||
key = pygame.key.get_pressed()
|
||||
if key[pygame.K_w]:
|
||||
yy -= 1
|
||||
if key[pygame.K_s]:
|
||||
yy += 1
|
||||
if key[pygame.K_a]:
|
||||
xx -= 1
|
||||
if key[pygame.K_d]:
|
||||
xx +=
|
||||
|
||||
```
|
||||
|
||||
是的,我们看到了pygame.key.get_pressed(); 函数。这个函数返回一个Key值。和event事件不同的是,我们直接可以在每一次循环内进行判断。返回的Key是一个tuple类型,在里面存放了各种按键对应的值。如果没有按键,所有值都是0;如果有按键,其中一个值是1。
|
||||
|
||||
再来看接下来的代码,如果key值的tuple里正好是pygame.K_w的话,那么判断结果就是真,我们来将这个内容打印出来看一下。
|
||||
|
||||
```
|
||||
print key
|
||||
|
||||
```
|
||||
|
||||
我们打印了key,并且按下w按键,随后,我们可以在游戏界面命令行看到如下内容输出:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/48/4a/480b8b53350ddb06e6a70e6987c84a4a.jpg" alt=""><br>
|
||||
找到那个1没有?那个1,就是对应的K_w值,在key[pygame.K_w]判断的时候,返回一个1,也就是True,那么就产生yy-=1的操作。接下来就是类似的代码了,我就不再作重复的阐述了。
|
||||
|
||||
看到这里,或许你就要问了,那有很多的游戏都有组合键,比如我按下Ctrl键,再按下w键,就会出现对应的操作,这又该怎么实现呢?
|
||||
|
||||
你思考一下,我们是不是可以把两个按键写在同一个判断语句下?是的,你没有猜错,确实可以这么写,这就是**组合键**的效果。
|
||||
|
||||
```
|
||||
if key[pygame.K_w] and key[pygame.K_LCTRL]:
|
||||
yy -= 2
|
||||
|
||||
```
|
||||
|
||||
在这里我们看到,只要同时按下了w和左侧CTRL,(LCTRL的意思是Left Control,就是左侧Control的意思),那么yy的坐标值就减去2,我们操作一下就知道结果了。所以,组合键可以在同一个判断里面,使用and连接起来。
|
||||
|
||||
然后,事情并没有到这里结束,请你将这些代码写在自己的电脑里,并做一下实验,第一种方式是事件判断,第二种方式是按键判断。这两种方式的区别是什么?
|
||||
|
||||
看到区别了吗?如果你按照我说的去做,你会发现,第一种方式,只要按下一个键,飞机就会往指定方向移动一格,然而如果你一直按着这个键,飞机是不会移动的,要等你再按下键盘才行。而第二种方式,只要你一直按着这个键,飞机就会一直不停往指定位置移动。
|
||||
|
||||
问题究竟出在哪里呢?
|
||||
|
||||
问题在于,事件判断首先判断了KEYDOWN,当你按下按键的时候,KEYDOWN已经被判断了,随后我们再进入event的按键类型的判断,但是如果你这时候一直按着键盘,KEYDOWN事件并没有被唤起,所以一直按着按键并没有起到作用,所以你要按下键盘,松开,再按下一次,飞机才会移动。
|
||||
|
||||
而第二种方式,在循环里面,只要键盘按下去,就会一直返回一个tuple给key,然后在继续做判断,所以,我们只要一直按着键盘,一直会做判断,直到按下的键盘是WSAD为止。
|
||||
|
||||
接下来,我们要做一下鼠标的操作。鼠标的操作我们在前几次的课程中也进行了介绍,我们再来温习一遍,并且添加一些新的内容进去。
|
||||
|
||||
我们今天要把一幅图片贴在鼠标的位置,并且随着鼠标的移动而移动,我们先来看下列代码:
|
||||
|
||||
```
|
||||
mouse = 'mouse.png'
|
||||
mouse_cursor = pygame.image.load(mouse).convert_alpha()
|
||||
mouse_scale = pygame.transform.scale(mouse_cursor, (40, 40))
|
||||
|
||||
While True:
|
||||
#获取 x, y 值
|
||||
x-= mouse_scale.get_width() / 2
|
||||
y-= mouse_scale.get_height() / 2
|
||||
screen.blit(mouse_scale, (x, y))
|
||||
...
|
||||
|
||||
```
|
||||
|
||||
首先我们要定义的一幅图片名叫 mouse.png,随后载入图片并且处理alpha混合,这在我们先前的课程中都有过阐述。
|
||||
|
||||
随后我们看到了一个叫pygame.transform.scale的函数。这个函数的意思是,我们要重新将mouse这个surface进行缩放,其中缩放的大小长宽是(40,40),并且返回一个新的 surface。
|
||||
|
||||
随后在循环里,我们获取到这个surface的中心点,也就是计算需要绘制鼠标的x,y值,我们需要得到图片的长宽,并且除以2,最后blit开始贴图,我们看到的效果是这样的。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/28/a1/2840dec856ac2ed69eb6d3c8956926a1.jpg" alt="">
|
||||
|
||||
那么我们如果要判断鼠标的按键怎么办呢?我们来温习一下上一次所讲的内容,鼠标的按键,也是类似的判断方式:
|
||||
|
||||
```
|
||||
x, y = pygame.mouse.get_pos()
|
||||
if pygame.mouse.get_pressed()[0]:
|
||||
....
|
||||
|
||||
```
|
||||
|
||||
前面那段代码用到的 x,y的值,在这里进行代码获取。
|
||||
|
||||
我们看到,pygame.mouse.get_pos() 函数,获取两个值,x和y坐标,后面一段代码就是获取鼠标点击的内容,其中 get_pressed函数下标0返回是不是左键点击,下标1返回是不是中键点击,下标2返回是不是右键点击,最后再做出判断。
|
||||
|
||||
## 小结
|
||||
|
||||
今天的内容基本到这里了,我带你将内容梳理并总结一下。
|
||||
|
||||
<li>
|
||||
首先是键盘事件判断,这里会出现按一下键盘做一下操作的情况,问题出在KEYDOWN事件判断上。但是如果你需要一直按键的判断,可以使用get_pressed函数。
|
||||
</li>
|
||||
<li>
|
||||
组合键可以写在同一个判断下,使用and连起来做判断。
|
||||
</li>
|
||||
<li>
|
||||
get_pressed会返回一个tuple,里面存放了所有的key值,只要判断key值是不是为True就是判断了有没有按键。
|
||||
</li>
|
||||
<li>
|
||||
鼠标操作也可以使用get_pressed函数,也是返回tuple,其中下标0、1、2分别代表了左、中、右三个按键。
|
||||
</li>
|
||||
|
||||
最后,给你留一个小问题。
|
||||
|
||||
如果将组合键写在第一个按键的判断下,会出现什么情况?
|
||||
|
||||
```
|
||||
if key[pygame.K_LCTRL]:
|
||||
if key[pygame.K_w]:
|
||||
...
|
||||
|
||||
```
|
||||
|
||||
欢迎留言说出你的看法,我在下一节的挑战中等你!
|
125
极客时间专栏/从0开始学游戏开发/第三章:UI和键盘鼠标/第18讲 | 如何判断客户端的网络连接?.md
Normal file
125
极客时间专栏/从0开始学游戏开发/第三章:UI和键盘鼠标/第18讲 | 如何判断客户端的网络连接?.md
Normal file
@@ -0,0 +1,125 @@
|
||||
<audio id="audio" title="第18讲 | 如何判断客户端的网络连接?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/21/a4/213efd8525d38e60060de7d47c4d5ea4.mp3"></audio>
|
||||
|
||||
由于涉及到网络、脚本语言等等,这一节起,我要开始讲一些基础的、看起来比较枯燥的知识。我会尽力写得有趣生动,但是,知识的获取并不容易,即便我已经在努力去讲解,还是需要你用更多的时间去摸索和学习。
|
||||
|
||||
我们在前面说了Pygame的一些客户端知识,如果你想让这款游戏能够在网络上传输数据,接下来,那就需要编写服务器端,并且在客户端做客户端连接的准备。
|
||||
|
||||
前面我们已经用Pygame讲解了很多内容,那我们客户端的网络连接,我也继续使用Python来编写,当然这已经和Pygame没有关系了。因为网络连接是独立的代码,所以只需要直接写在游戏里就可以了。
|
||||
|
||||
在开始编写网络部分之前,我们需要整理一下网络的基础知识。如果我们一上来就编写网络代码,对于你来说,可能会有一些概念上的模糊。
|
||||
|
||||
对于网络编程,或许你已经接触到了,或许你只是有点概念,或许你一点都没接触过。但是你或许听说过Socket套接字、TCP/IP协议、UDP协议、HTTP、HTTPS协议等等,那么这些协议是不是都属于网络编程范畴?还是这里面哪些部分是属于网络编程范畴呢?
|
||||
|
||||
网络,从模型上讲,一共分为七层,从底层到最上层,分别是:物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。
|
||||
|
||||
我来分别解释一下。
|
||||
|
||||
<li>
|
||||
**物理层**:所谓的物理层,你可以理解为我们看到的各种网络线,也就是人肉眼能看到的物理线路,包括光纤、以前连接调制解调器的电话线等等。这些线路就是物理层。物理层有物理层的规范,比如电流、编码、帧、连接头等等。你只需要知道物理层也是存在规范的,就可以了。**物理层最主要的功能就是网络的物理连接**。
|
||||
</li>
|
||||
<li>
|
||||
**数据链路层**:所谓的数据链路层,就是建立逻辑连接,然后进行硬件上的寻址操作、差错的校验,然后将二进制的字节进行组合,通过MAC地址进行访问,比如网卡、交换机等等。你需要记住的是,**在这一层,要通过MAC地址来进行访问,进行硬件寻址操作。**
|
||||
</li>
|
||||
<li>
|
||||
**网络层**:网络层进行逻辑地址的寻址操作和数据链路层不同。数据链路是使用硬件寻址操作,而网络层是使用逻辑地址寻址,比如路由器、防火墙、多层交换机等等。我们最熟悉的IPv4(202.101.253.233)、IPv6、ARP等等都属于这一层。你在这里需要记住的是,**网络层是逻辑寻址操作,会用到ARP、IPv4等等协议。**
|
||||
</li>
|
||||
<li>
|
||||
**传输层**:在编程中最常用到的TCP、UDP等等协议,都在这一层进行操作,它首先定义了数据传输的协议端口号以及一些错误的检测。
|
||||
</li>
|
||||
<li>
|
||||
**会话层**:会话层在传输层之上,它就在客户端和服务器端。严谨地说,就是本地机器和远端机器之间的会话,比如要进行断点续传这些操作,就属于会话层的范畴。
|
||||
</li>
|
||||
<li>
|
||||
**表示层**:表示层很容易理解,就是数据的传输,然后展现在电脑上。比如图片的传输和显示、网络地址的加密等等。
|
||||
</li>
|
||||
<li>
|
||||
**应用层**:应用层就是提供给电脑用户的各种网络应用,比如你自己编写的网络软件、FTP软件、Telnet、浏览器等等。
|
||||
</li>
|
||||
|
||||
以上这些点你要硬性记住的话,会比较困难。我教给你一个方法。
|
||||
|
||||
首先,我们想象一段从网线过来一段数据,网线就是“物理层”,那么数据需要找到一个门牌号,这个门牌号是一个硬件地址,可能是你的电脑网卡,也可能是你公司的交换机。这些数据需要把这些门牌地址连接起来,这就是“数据链路层”。
|
||||
|
||||
随后,这些数据找到门牌号后,就需要分发到逻辑地址,比如路由器或者你的IP地址,这些逻辑地址就是网络地址,这就是“网络层”。
|
||||
|
||||
经过网络层后,就要看这是什么数据,是TCP协议的,还是UDP协议的。知道了协议后,才可以传输数据,所以这个是“传输层”。
|
||||
|
||||
那么在传输的过程中,可能会中断,所以我们需要登录服务器,断点续传进行重新传输,这些属于机器和机器之间的会话,所以是“会话层”。
|
||||
|
||||
传输完数据后,我们就会在电脑里显示这个内容,是一幅图片呢,还是一段电影?这个需要表示出来,所以是“表示层”。
|
||||
|
||||
最后,我们将这个一整套的东西,写成了一个应用,这就是“应用层”。
|
||||
|
||||
虽然这么表述起来,有许多不精确不严谨的地方,但是通过这段话能让你很快记住这个七层网络模型,对你将来的编程有很大的帮助。
|
||||
|
||||
Python支持Socket编程,也就是支持TCP、UDP等协议的编程,也支持更上层的编程,比如HTTP协议等等。在今天的内容中,我们要用到TCP编程。至于为什么要使用TCP,有这样几个原因:
|
||||
|
||||
<li>
|
||||
TCP保证连接的**正确性**。在建立TCP连接的时候,需要经过三次握手,连接这一方发送SYN协议,被连接方返回SYN+ACK协议,最后连接方再返回ACK协议;
|
||||
</li>
|
||||
<li>
|
||||
TCP保证如果在一定时间内没有收到对方的信息,就重发消息,保证消息传输的**可靠性**;
|
||||
</li>
|
||||
<li>
|
||||
TCP可以进行**流量控制**。它拥有固定大小的缓冲池,TCP的接收方只允许另一方发送缓冲池内所接纳的数据大小。
|
||||
</li>
|
||||
|
||||
TCP还有其他更多的保证传输可靠性的内容和标准,我在这里不做更多的阐述。另外,使用TCP可以进行长时间的连接,在客户端和服务器端之间进行不停地交互。在交互过程中,服务器端发送数据给客户端,客户端就能做出相应的回应。
|
||||
|
||||
在Python中编写TCP协议的代码比之使用C/C++更为方便。因为C/C++需要初始化一系列的内容,然后进行顺序的流程化绑定,设置网络参数,最后进行发送和接收操作,在结束的时候进行资源的回收。而在Python这里,只需要设置协议和IP地址就可以实现TCP协议编程。我们来看一段代码。
|
||||
|
||||
```
|
||||
import socket
|
||||
class go_sock(object):
|
||||
ip = ""
|
||||
port = 0
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
def __init__(self):
|
||||
object.__init__(self)
|
||||
def connect(self, ip, port):
|
||||
self.ip = ip
|
||||
self.port = port
|
||||
self.sock.connect_ex((ip, port))
|
||||
def close(self):
|
||||
self.sock.close()
|
||||
|
||||
```
|
||||
|
||||
我在这里编写了一个类,这个类将TCP的内容封装在了类中,这样,我们的网络代码能在游戏中方便地初始化,使用起来就很方便。
|
||||
|
||||
首先,我们看到在类里面定义了ip、port、sock这三个变量,这三个变量分别是对应IP地址、端口号以及socket句柄。在类里,我们直接将sock初始化为socket类,其中socket类填写的内容中,参数1是服务器之间的网络通信,参数2是流Socket,这里指的是TCP协议。
|
||||
|
||||
在初始化完成了之后,我们看到connect函数。在函数里面,我们看到参数对变量的初始化,其中sock句柄调用了标准socket函数sock.connect_ex,这个函数负责与对方进行一个连接。
|
||||
|
||||
最后的函数是close关闭操作,在任务完成之后,你可以调用close函数进行socket句柄的关闭。
|
||||
|
||||
我们可以这样使用这个类。
|
||||
|
||||
```
|
||||
_inet = go_sock()
|
||||
_inet.connect("115.231.74.62", 21)
|
||||
_inet.sock.recv(100)
|
||||
|
||||
```
|
||||
|
||||
在这里,我们可以简单测试一下某些应用服务器,然后接收返回内容。这个类的封装工作到此就告一个段落,更多的网络服务和交互的编写,我将在下一节阐述。
|
||||
|
||||
## 小结
|
||||
|
||||
今天我们学习了网络的七层模型结构,以及我们将要在游戏中使用的TCP协议的编程。
|
||||
|
||||
<li>
|
||||
我用了一个传输过程介绍了七层每一层做的事情,这个你一定要牢记。
|
||||
</li>
|
||||
<li>
|
||||
我们使用Python封装了Socket库的细节内容,只需要直接编写connect代码就可以进行数据的接收和发送操作了。
|
||||
</li>
|
||||
<li>
|
||||
选择TCP协议是因为它安全可靠,能保证游戏传输的过程中不出错。
|
||||
</li>
|
||||
|
||||
现在,我给你留一个小问题吧。
|
||||
|
||||
如果我们要使用UDP来编写这个网络服务,该如何保证数据的准确性呢?选择UDP协议的优势在哪里?
|
||||
|
||||
欢迎留言说出你的看法。我在下一节的挑战中等你!
|
Reference in New Issue
Block a user