极客时间已完结课程限时免费阅读

第9讲 | 如何绘制游戏背景?

第9讲 | 如何绘制游戏背景?-极客时间

第9讲 | 如何绘制游戏背景?

讲述:蔡能

时长08:30大小3.92M

我在之前的文章中描述了各种基础知识,然后梳理了开发流程,并带你创建了一个窗体,现在我们要做的就是朝这个窗体里添加东西。
我会随着进度逐渐提升难度。就现阶段来讲,我们涉及的只是一些基础知识,并且只需要将大部分的关注点放在我们要做的游戏内容上,并不需要关注过多的底层逻辑代码
做事情都有先后顺序,做游戏开发自然也是。为什么要学习先绘制游戏背景而不是别的什么,很简单,因为只有先绘制了游戏背景,才能进行后续的游戏图像遮挡、图形图像的显示等等操作。
不管你有没有玩过《超级玛丽》《魂斗罗》《雷电》之类的游戏,但一定对其画面不陌生。和我们要开始做的打飞机游戏一样,这种类型的 2D 游戏,其背景不是左右卷轴,就是上下卷轴。所谓左右卷轴,就是游戏画面是横向的、左右运动的,而上下卷轴就是游戏画面是竖直对的、上下运动的。
像《雷电》这样的经典飞机游戏,就是属于上下卷轴的。上下卷轴的飞机游戏有一个特点,就是它是在空中,从凌驾于飞机之上的视角,往地面俯瞰的。因为是俯视角,所以我们可以很方便地看到游戏的整体地图,包括地面上的敌人、空中的敌人等等,层次感会很强。
因此,可以确定,我们要做的打飞机,也是一个上下卷轴的游戏。这样,我们就可以着手将需要的图片添加进去了。
我们要使用 Pygame,先读取一个图片,让该图片成为游戏背景并载入进去。当下阶段,我们的图片从哪儿获得并不重要,因为在一个完整的游戏开发团队里面,都有专业的美术团队负责作图,但是现在我们没有,所以我就自己贴一幅图来代替正式的游戏背景。所以你现在只需要知道背景是如何贴上去的就好了。
和前面的文章说过的一样,我们需要先载入 Pygame 模块,并且定义一个变量 background。我们将一幅名为 lake,jpg 的图片文件赋值给 backgroud 变量。
import pygame
background = 'lake.jpg'
然后,我们先把 Pygame 的所有组件都初始化。接下来,我们调用 display 类里的 set_mode 函数来对屏幕进行一个初始化。
pygame.init()
screen = pygame.display.set_mode((640, 480), 0, 32)
pygame.display.set_caption("pygame game")
这里一共有三个参数,第一个参数是分辨率,比如我这里编写的是 640x480 的分辨率;第二个参数是 flag,flag 的参数我放在下面这个表里了;第三个参数是 32,32 代表的是颜色深度,这里是 32 位的意思。
在设置完了窗体模式之后,后面的一段代码就是设置窗体的抬头文字,这里显示的是 pygame game。
随后,我们要载入背景的图片。
bg = pygame.image.load(background).convert()
我在前面的文章中也说过,这句话的意义是,载入 backgroud 图片。但是 pygame.image.load 这个函数返回的是一个 surface,而.convert 函数是来自于 surface 对象。你可以参考下面的代码来理解。
surface_temp = pygame.image.load(background)
bg = surface_temp.convert()
其次,bg 这个变量也是一个 surface,而 convert 函数的作用是改变一副图片的像素格式。convert 有四个相同名字的重载函数。如果就像我们的代码里所示,convert 没有任何参数,则表示直接返回一个 surface 对象。
好了,现在我们设置完了背景 bg 的 surface,我们按照上面的文章,开始写一个大循环,并且在循环里面进行检测鼠标事件是不是退出操作,这是最基本的一项检测。
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
和前面的文章一样,我们从 event 里取出事件列表,然后把每一个 event 的类型进行对比,如果发现有 QUIT 事件(鼠标点击 X 关闭按钮后),就直接退出游戏。完成这一步之后,就可以开始使用 blit 函数进行绘制屏幕的操作。
screen.blit(bg, (0,0))
这句话的意思是,使用 blit 将 bg 在以游戏屏幕 x,y 轴为(0,0)的坐标位置在 screen 对象上绘制背景图像。然后我们需要 update 刷新屏幕,添加下面这行代码。
pygame.display.update()
upadate 这个函数是 pygame.display.flip 函数的优化版。因为 pygame,display.flip 是更新整块屏幕,所以如果加载的资源多,效率并不是很高,而 update 如果传递一个矩形值得参数的话,它会只更新这块矩形的内容,所以效率会比较高,但是不传递参数的话,默认还是会更新整块屏幕,但是这个函数不能用在 set_mode 的时候设置为 OpenGL 的模式下。
好了,我们该做的事情基本都做完了,现在我们来运行一下,看看效果。
好了,背景是贴上去了。现在问题来了,要想让背景动起来该怎么做呢?如果在 blit 的时候,改变坐标是不是就可以移动背景图的位置了呢?你再开动脑筋想想,该怎么做才能让背景移动起来?
对的,我们只需要写一个循环,就可以将背景移动起来。
我们来修改一下大循环开始的代码。
y_move = 0
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
screen.blit(bg, (0,y_move))
y_move-=1
我们在大循环开始之前,在这段代码里定义了一个 y 值移动的变量,而我们每循环一次,blit 就绘制一次屏幕,y 值都会被减去 1,所以我们每次看到的图片,都会不停往上移动,我们来看一下效果。
发现问题了没有,在移动的过程中,下方的图案居然没有被刷新,直接黏在了屏幕上,看起来是不是很恶心的样子?
我们应该怎么做才能达到正常的效果呢?也就是说,请你思考一下,应该怎样做,我们才可以将这个令人头疼的图像在移动的时候变得正常呢?
我们先来回顾一下,我们在循环里面做了哪些步骤:
检测退出事件;
在屏幕上绘制 bg 对象,坐标初始为(0, y);
飞机每移动一格,坐标 y 减 1;
更新屏幕。
看起来似乎没有什么问题,我再来带你梳理一下。
首先我们初始化的时候,屏幕是黑屏一块,没有任何图像,然后我们进入大循环,将 bg 对象绘制到屏幕上的时候,你觉得这时候我们的眼睛看到绘制的图像了吗?
如果你说是的话,那就大错特错了,因为这个 blit 的动作,仅仅是绘制,而不是显示。请记住这个区别:绘制不等于显示
那你可能就要问了,既然绘制了,为什么不显示呢?要什么时候才能显示呢?答案是,要在 update 一次屏幕的时候,才会显示,这就是“更新”的作用。就像电影是一帧一帧的,如果没有下一帧更新,电影就会永远定格在某一秒。
所以问题逐渐就暴露出来了,我们再来重新梳理一下流程:
检测退出事件;
在屏幕上绘制 bg 对象,坐标初始为(0, y)(注意是绘制,不是显示);
飞机每移动一格,坐标 y 就减 1;
更新屏幕,将第二步绘制的 bg 对象呈现在屏幕上,严谨地说,应该是将在 update 函数之前所有的绘制操作都更新一次并呈现在屏幕上)。
好了,问题很清楚了,update 函数只是将屏幕更新了一次,并未进行填充颜色或者“擦除”背景的操作,也就是我们在移动 y 值的时候,整个屏幕不停地更新,然而没有擦除。那么应该怎么将移动后的画面进行清理呢?
我们在 update 代码之后填入下面的代码。
screen.fill([0,0,0])
fill 操作拥有三个参数,其中第一个参数是填充颜色;第二个参数是填充某一块区域(如果不填入第二个参数,就会填充整个屏幕);第三个参数是 blit 操作的特殊参数,我们暂时可以不用管它。
所以,我们在代码里填充了黑色到整个屏幕,这样一来我们的屏幕操作变成这样:
检测退出事件;
在屏幕上绘制 bg 对象,坐标初始为(0, y);
坐标的 y 减 1;
更新屏幕;
填充屏幕区域为黑色。
我们再运行一下看一下效果。
嗯,这下看起来正常了,屏幕不断往上移,并且没有拖着尾巴一样的图案了。

小结

我们在写 2D 游戏的时候要注意一点,就是:
我们要想象游戏的每一帧就像电影的每一帧。每一帧做的事情,如果下一帧不去做,那么永远不会更新屏幕内容
所以,update 的功能是更新调用 update 之前的所有动作,这些动作可以有绘制图像操作,也可以有音乐播放,也可以有动画每一帧的操作等等。只要 update 一次,屏幕的画面就会往前行进一次。
给你留个小思考题吧,我们在 fill 屏幕的时候,怎么做才能让填充的颜色不停变幻呢?
欢迎留言说出你的看法。我在下一节的挑战中等你!
分享给需要的人,Ta购买本课程,你将得18
生成海报并分享

赞 3

提建议

上一篇
第8讲 | 如何区分图形和图像?
下一篇
第10讲 | 如何载入“飞机”和“敌人”?
 写留言

精选留言(12)

  • fenglinwan
    2018-06-19
    老师,我有一个问题,如果我的一台电脑运行速度快,一个循环很快就运行完了,另一台电脑慢! 岂不是一台背景滑动的快,另一台背景滑动的慢!

    作者回复: 所以要有帧速度控制。

    5
  • GS
    2018-10-18
    这样分段的代码。对于初学者,忘记缩进搞半天,最好是最后有个完整版的
    3
  • 三硝基甲苯
    2018-06-14
    while True: for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() screen.blit(bg, (0, y_move)) y_move -= 1 pygame.display.update() screen.fill([random.randint(0,255),random.randint(0,255),random.randint(0,255)]) pygame.time.wait(100) 可能会被 颜色变化闪瞎
    展开

    作者回复: 你可以做判断,变化的不要这么剧烈

    2
  • 裴海港
    2018-06-14
    在填充颜色的时候分别为RGB设置0-255的随机数字应该可以让颜色不停变换
    3
  • 观察与思考
    2018-06-14
    我觉得从逻辑顺序上,如果按15324,比较好理解
    3
  • 阿森
    2018-07-13
    先screen.blit(bg, (0,0)) update(),再while true,图像就出来了,按原文的顺序是黑屏的窗口,编程小白查错好困难啊
    1
  • 00000000
    2018-07-06
    15324的话第一个5不是浪费吗
    1
  • 青鸟
    2018-06-14
    独立开发游戏如何获取图片等资源,一定要自己学会画图吗?

    作者回复: 去网上搜,如果是程序员不需要学会画图

    1
  • 上弦月
    2020-07-30
    以下代码可实现图片上下滚动刷新,写在while死循环里,后面补上绘制与显示语句即可,初始化时y=0,screen_y_max为窗体的长 for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() if y == screen_y_max: dirction = "up" elif y == 0: dirction = "down" if dirction == "up": y-=rate #不断减小纵坐标的值以向上绘制&显示图片 elif dirction == "down": y+=rate #不断增加纵坐标的值以向下绘制&显示图片
    展开
  • 邢浩锋
    2018-06-19
    你好,按着原文的代码写,只是背景图片不同。最后运行出来的背景是一片漆黑,看不到图片。是跟背景图片的大小有关吗?还是什么原因?

    作者回复: 你看下绘制函数的调用顺序

  • Geek_King@技术爱好者
    2018-06-15
    为什么if event.type==pygame.QUIT:之后用pygame.quit()而不用sys.exit(),因为我运行的时候好像pygame.quit()以后还会执行后面的代码,出错才退出的

    作者回复: 执行后面的代码是因为循环外面还有代码,sys.exit是直接退出,虽然结果都是退出,但这种方式比较粗暴。

  • null
    2018-06-14
    为什么update要在fill之前调用呢?我的理解是,如果先调用update把要显示的东西都显示出来了,但是后面又调用了fill,这样不是就把前面显示的东西都覆盖掉了吗?不是先清屏再显示的吗(就是先调用fill再调用update)?

    作者回复: 看文中具体代码,如果fill后,update就覆盖了清除操作