Pygame 外星人入侵(4)飞船移动

目录

  • Pygame 外星人入侵(4)飞船移动
    • 引言
    • 一、移动飞船
      • 1、响应玩家按键
      • 2、修改飞船的坐标
    • 二、优化1:连续移动
      • 1、设置飞船移动的状态属性
      • 2、响应事件以修改状态
      • 3、update() 修改飞船位置
      • 4、修改主模块代码
    • 三、左右移动
    • 四、优化2:调整飞船的速度
      • 1、设置速度参数
      • 2、完善飞船调速(有点啰嗦)
    • 五、优化3:限制飞船活动范围
    • 六、封装代码
    • 七、小结

引言

直到上一篇博文为止,我们实现了:
1、游戏屏幕的绘制
2、飞船的初始化和绘制
3、现有代码的封装和重构

实现效果:

在这篇博文中,我们将要实现通过键盘方向键来控制飞船左右移动。

一、移动飞船

根据需求来分析应该如何实现。
1、首先,我们应该在 事件响应 中增加对于玩家按下方向键这样的行为的响应,也就是 响应玩家按方向键。
2、其次,在玩家按下方向键后,改变飞船的坐标。
3、最后,绘制飞船到屏幕上,只是此时的飞船,可能已经是坐标改变后的飞船了。

1、响应玩家按键

在我们之前封装在 game_functions 模块中的 check_events() 函数中增加响应玩家按键的逻辑。
另外,应该响应的是方向键,而不是其他不管的按键,这里我们仅以右方向键为例:

def chekc_events(ship):for event in pygame.event.get():if event.type == QUIT:sys.exit()# 当检测到玩家按下键盘上某个按键时elif event.type == pygame.KEYDOWN:# 当玩家按下的键是 右方向键if event.key == K_RIGHT:print('玩家按下了右方向键')

这样我们在游戏运行时如果按下了右方向键,控制台就会打印出相应的提示语句。
即实现了响应玩家的操作。

2、修改飞船的坐标

现在,我们需要在响应玩家操作后,相应地修改飞船的坐标,使飞船在之后绘制屏幕的时候,显示在不一样的位置。
只需要修改响应后的业务就可以了:

def chekc_events(ship):for event in pygame.event.get():if event.type == QUIT:sys.exit()# 当检测到玩家按下键盘上某个按键时elif event.type == pygame.KEYDOWN:# 当玩家按下的键是 右方向键if event.key == K_RIGHT:# 修改飞船的横坐标,使其 +1ship.rect.centerx += 1

此时,在主模块中运行游戏,就可以在按下右方向键后,看到飞船向右移动一个像素的位置了。
(不知道博文里能不能发动图。。。)

二、优化1:连续移动

虽然实现了飞船移动,但是也发现了相应的问题,其实也不能说是问题,而是可以优化的点:
我们的程序只有在按下一次右方向键时才会让飞船动一下,如果按住右键不动,飞船只会动一下,不会一直移动直到我们松手。
在很多游戏中,这都是不合适的,我们会经常需要一次性移动一大段距离,现在我们写的程序,如果想移动一大段距离,需要连续多次敲击按键,这是我们需要优化的地方。

于是想到,我们不仅要响应玩家按下按键的事件,还要响应玩家松开按键的事件,且在玩家按下和松开按键之间,都要移动飞船。
基于此,可以通过一个状态值来标识飞船是否移动。

简单总结一下,我们需要做如下修改:
1、在飞船类中增加属性,表示飞船移动的状态,初始值为False
2、响应玩家按下按键后,将状态值变为True
3、响应玩家松开按键后,将状态值变为False
4、在飞船类中定义一个方法,可以通过状态值来移动飞船,当状态值为True时,移动飞船,否则不移动。

1、设置飞船移动的状态属性

class Ship():# 其他方法省略...self.moving_right = False

2、响应事件以修改状态

def chekc_events(ship):for event in pygame.event.get():if event.type == QUIT:sys.exit()# 当检测到玩家按下键盘上某个按键时elif event.type == pygame.KEYDOWN:# 当玩家按下的键是 右方向键if event.key == K_RIGHT:# 修改飞船移动状态,使其移动ship.moving_right = True# 当检测到玩家松开键盘上某个按键时elif event.type == pygame.KEYUP:# 当玩家松开的键是 右方向键if event.key == K_RIGHT:# 修改飞船移动状态,使其不再移动ship.moving_right = False

3、update() 修改飞船位置

此时,我们在按下和松开右方向键时,分别修改了飞船的状态属性值。
现在,就可以定义方法,通过状态值来移动飞船了

class Ship():# 其他方法和属性都省略...def update(self):# 当状态值为真时,移动飞船if moving_right == True:self.rect.centerx += 1# 当状态值为假时,什么都不用做,因此也不用写else语句了

4、修改主模块代码

修改飞船类的代码后,我们重新捋一下主模块中游戏循环代码的逻辑:
1、进入死循环
2、检查事件(这里根据事件已经修改了飞船的状态值,但飞船的坐标还没有修改)
3、改变飞船的坐标,即调用 update() 函数
4、绘制背景
5、绘制飞船
6、显示屏幕

    while True:# 事件检测,现在我们已经将事件循环的代码封装到函数中了,直接运行即可check_events(my_ship, my_screen, my_settings)# 每次执行完事件检测循环后,都更新飞船的位置my_ship.update()# 绘制游戏画面,包括屏幕背景、飞船,并将内容显示到游戏屏幕上update_screen(my_screen, my_settings, my_ship)

此时运行游戏,可以在玩家按住右方向键不放时,一直移动飞船,直到玩家松开按键。

三、左右移动

至此我们实现了飞船的右移,现在按照差不多的逻辑实现飞船的左移。
1、响应玩家按下和松开左方向键的事件
2、响应后移动飞船的业务

# 事件响应
def chekc_events(ship):for event in pygame.event.get():if event.type == QUIT:sys.exit()# 当检测到玩家按下键盘上某个按键时elif event.type == pygame.KEYDOWN:# 当玩家按下的键是 右方向键if event.key == K_RIGHT:# 修改飞船移动状态,使其移动ship.moving_right = True# 当玩家按下的键是 左方向键elif event.key == K_LEFT:# 修改飞船移动状态,使其移动ship.moving_left = True# 当检测到玩家松开键盘上某个按键时elif event.type == pygame.KEYUP:# 当玩家松开的键是 右方向键if event.key == K_RIGHT:# 修改飞船移动状态,使其不再移动ship.moving_right = False# 当玩家松开的键是 左方向键if event.key == K_LEFT:# 修改飞船移动状态,使其不再移动ship.moving_left = False
# 响应后的业务
class Ship():# 其他方法和属性都省略...def update(self):# 当状态值为真时,移动飞船if moving_right == True:self.rect.centerx += 1if moving_left == True:self.rect.centerx -= 1

值得注意的是,这里我们使用的是两个 if 语句 ,而不是一个 if elif 的语句,这是因为,当玩家同时按住左右键时,需要飞船保持不动。
如果用 if elif 语句的话,这里右移就会占“优势地位”,因为 if 条件满足后,就不会检查后面的 elif 条件了,所以就会一直右移。这显然是不合适的。
用两个 if 语句的话,保证了左右键都会被检查到,飞船在这种情况下就会保持不动。

四、优化2:调整飞船的速度

1、设置速度参数

之前的程序中,我们都设置飞船以 1 像素的距离来移动,如果我们想设置飞船的移动速度,那么就可以将这个参数放到设置类中去,比如我们此时想让飞船以每次 1.5 像素的距离移动:
1、在设置类中添加飞船速度的属性
2、飞船初始化时,传入设置参数
3、修改 update() 方法

class Settings():...self.ship_speed = 1.5
def __init__(self, screen, settings): ...# 为了能在飞船对象中使用设置中的参数self.settings = settings
def update(self):# 当状态值为真时,移动飞船if moving_right == True:self.rect.centerx += self.settings.ship_speedif moving_left == True:self.rect.centerx -= self.settings.ship_speed

2、完善飞船调速(有点啰嗦)

至此,好像是解决了飞船调速的问题。

但是,这种处理其实是有问题的,问题出在 pygame 中 ,Surface 对象的rect 属性的值,是 int 型的。
也就是说,坐标只能存储整数,当我们每次都以浮点数进行加减时,实际上加减的距离是去掉小数部分的。因此,当我们把速度调整为1.5后,每一次移动飞船,仍然是以 1 的速度来移动的。
所以像我们刚才这样处理调速的逻辑,只能接受整数的速度值,浮点数的速度值,会自动忽略小数点部分。
而这显然不符合我们的预期。但是可以通过飞船的一个临时变量来保存飞船的坐标,这个临时变量是浮点型的,当我们移动飞船时,坐标的加减,先作用于这个临时变量上,这样是不会造成小数点被忽略的。而当玩家松开按键后,将这个临时变量的最终值,传递回飞船真正的坐标属性上。

这一段我描述的可能有些不清楚,我还是会在下面写上代码,如果还是不太清楚,可以在评论区留言。(有营销号内味儿了)

def update(self):# 当状态值为真时,移动飞船if moving_right == True:# self.x 用来作为飞船坐标的临时值,它是浮点型的self.x += self.settings.ship_speedif moving_left == True:self.x -= self.settings.ship_speed# 被浮点数的速度值加减后,最终的结果再返回给坐标self.centerx = self.x

在这之前,我们需要做如下准备工作:
1、在飞船初始化时,创建这个临时变量
2、这个临时变量的值,和飞船的坐标值相等
3、这个临时变量必须确保是浮点型的

class Ship():...def __init__(self,screen,settings):...# 一行代码,搞定上述的3点要求self.x = float(self.rect.centerx)

至此,就实现了比较完善的飞船调速功能,以后需要调速,只需要在设置类中修改相应参数即可,且可以接受浮点数的速度值。

这里之所以说“比较完善”,是因为其实还是有一些缺陷的,因为即使我们通过临时变量获得了准确的坐标偏移量,但最后赋值给 centerx 时,由于坐标变量只接受整型数的特性,仍然会丢失偏移量的小数部分。只不过,这个误差比起优化前要小上不少,算是可以接受吧。

举个例子,比如我们分别以 1 和 1.5 的速度移动飞船 3 次。
优化前:
速度为 1 时的偏移量:int(1) + int(1) + int(1) = 3;误差为0
速度为 1.5 时的偏移量:int(1.5) + int(1.5) + int(1.5) = 3,误差为1.5
优化后:
速度为 1 时的偏移量:int(1 + 1 + 1) = 3 ;误差为0
速度为 1.5 时的偏移量:int(1.5+1.5+1.5) = 4;误差为0.5

还是之前说的,这部分我描述得可能不太好,也有点啰嗦。。。有好的解释方法也可以评论留言。

五、优化3:限制飞船活动范围

我们在移动飞船时,会发现一直往一个方向移动后,飞船会飞出屏幕。其实就是因为飞船的坐标超过了游戏屏幕的大小(显示范围)。因此需要修改一下飞船移动方法的逻辑:
1、在响应玩家按键事件后,移动飞船前,判断飞船的位置
2、只有飞船在屏幕右边界内时,才允许继续右移
3、只有飞船在屏幕左边界内时,才允许继续左移

    def update(self):# 当飞船正在向右移动时,飞船的横坐标递增 # 而且需要防止飞船飞出屏幕外if self.moving_right and self.rect.right < self.screen_rect.right:self.x += self.speed# 当飞船正在向左移动时,飞船的横坐标递减# 而且需要防止飞船飞出屏幕外if self.moving_left and self.rect.left > 0:self.x -= self.speed# 将根据小数递增的坐标值给到飞船的坐标变量上self.rect.centerx = self.x

六、封装代码

还是书上的老规矩,每次实现一些功能后,先不着急继续往后实现新功能,而是审视已有的代码,看能不能重构或者封装一部分代码。

这里我们在事件检测中对按键按下和按键松开两“种”事件进行了检测,随着之后可能会有越来越多的按键被我们使用,因此,将“按键检测“ 和 ”松键检测“这两部分分别封装是很有必要的。
1、封装”按键检测“
2、封装”松键检测“
3、修改”事件检测“方法的代码

# 封装事件循环中,所有 按下 键盘按键的事件
def check_keydown_events(event, ship, screen, settings):# 当玩家在键盘上按下的是 右方向键时,飞船开始向右移动if event.key == pygame.K_RIGHT:ship.moving_right = True# 当玩家在键盘上按下的是 左方向键时,飞船开始向左移动elif event.key == pygame.K_LEFT:ship.moving_left = True
# 封装事件循环中,所有 按下 键盘按键的事件
def check_keyup_events(event, ship):# 当玩家在键盘上松开的是 右方向键时,飞船停止向右移动if event.key == pygame.K_RIGHT:ship.moving_right = False# 当玩家在键盘上松开的是 左方向键时,飞船停止向左移动elif event.key == pygame.K_LEFT:ship.moving_left = False
def check_events(ship, screen, settings, bullets):# 循环监视事件的发生for event in pygame.event.get():# 当玩家点击关闭按钮时,退出游戏if event.type == pygame.QUIT:sys.exit()# 当玩家 按下键盘 发出事件时elif event.type == pygame.KEYDOWN:check_keydown_events(event, ship, screen, settings)# 当玩家 松开键盘 发出事件时elif event.type == pygame.KEYUP:check_keyup_events(event, ship)

七、小结

在这篇博文中,我们实现了飞船的左右移动,且完善了这一功能:
1、长按长移,直到松手。
2、调速移动,接收小数。
3、限制范围,防止出界。

在学习开发这款游戏的过程中,我遇到一些不能从书上马上理解的东西,这个时候,我会停下来,先看一看自己已经掌握了哪些知识。
对于不能理解的内容,我的做法是先去 Pygame 官方文档中查看一些关键术语的解释,我每次会看一个板块,一个板块包括 Pygame 的一个模块或一个类。我会阅读自己已经见到过和使用过的模块和类。这是我的阅读顺序:
1、display 模块。因为最开始创建游戏屏幕时使用的模块就是这个。
2、Surface 类。因为 display 模块中提到,我们创建的屏幕本质上就是一个 Surface 对象,因此需要知道 Surface 到底是什么,能做什么。
3、image 模块。因为 Surface 类是用于控制图片的类,而导入图片是通过image模块来实现的。
4、Rect 类。Surface类的一个子类,每个 Surface 类都有一个 Rect 对象属性。
5、Color 类。这个比较简单,一笔带过。

值得一提的是,最初看书时,优化调速部分的叙述我不太能理解,不知道通过临时变量“绕一圈”的意义何在;但是我姑且记住了这种处理方法。而当我阅读了 Rect 类的官方文档后,发现了 坐标值 只接收整数的特性,这个时候,马上就联想到了之前不能理解的部分,也就迅速地返回去重新看一遍书上的叙述,发现此时的自己已经能看懂了。所以说,遇到不会的东西,先放一放,给自己充充电,说不定什么时候就会了。

另外,一个飞船移动的功能,自己实现的话,应该也能实现出来。但问题是怎样才能更好地实现这个功能。这篇博文通篇其实只是实现了一个功能,但绝大部分内容都是对功能地优化。所以,在自己写代码时,也要时刻注意这些细节,旨在写出高水平地代码。

Pygame 外星人入侵(4)飞船移动相关推荐

  1. python外星人入侵代码提示has no attri_【Python】pygame 外星人入侵, 出现AttributeError, 但是找不到错误?...

    <python 编程:从入门到实践>书中例子:外星人入侵,在飞船可以左右移动下,子弹设置好,每次按空格键都会出现: (使用python2.7) AttributeError: 'Bulle ...

  2. python pygame实战《飞船大战外星人》

    学了一个月的python,最后两天学了下pygame,以一个小游戏结尾这段旅程. 游戏规则如下: 玩家可以通过上下左右四个键控制飞船移动,而且按住键不放可以联系移动,而不需要不断地按键松键来控制. 玩 ...

  3. Pygame游戏之 飞船绕行

    这是一个让飞船绕着地球的小游戏 我们得先知道如何加载图片和把图片显示在我们的窗口屏幕上 1.加载位图:space = pygame.image.load("space.png") ...

  4. Python项目--外星人入侵--武装飞船

    武装飞船 开始游戏项目 创建Pygame窗口以及响应用户输入 首先,我们创建一个空的Pygame窗口.使用Pygame编写的游戏的基本结构如下: #alien_invasion.py import s ...

  5. 外星人入侵 python 飞船位置_《python从入门到实践》项目一:外星人入侵

    游戏编程思维导图 游戏思路 函数编写 第一次做思维导图,做的有点乱 解释:跟着<python编程从入门到实践>写完游戏程序后,写一下自己的心得体会.可以把这个游戏分为几块来理解.如果想要设 ...

  6. Pygame 外星人入侵(10)计分板

    目录 引言 一.游戏难度的提升 1.提升速度 2.何时提升速度 3.重置速度 二.计分板功能 1.玩家的得分 2.绘制玩家得分 3.提高玩家得分 4.优化1:正确计算每一只外星人的分数 5.优化2:外 ...

  7. python写的飞船游戏卡顿_关于pygame里让飞船连续移动的问题?

    我大概理解了你的意思. 原因在于pygame.KEYDOWN和pygame.KEYUP只表示按下的一瞬间执行. 第一个程序为什么能执行呢? 因为在你按下的时候,moving_right已经变成True ...

  8. [转]pygame外星人入侵

    最近在看一本<python:从入门到实践>,这本书是我看的第一本python书籍,也是一本非常推荐大家阅读的书籍,干货满满.现在让我带领你们走进pygame的世界. 外星人入侵 游戏开始前 ...

  9. Python 外星人入侵(一):武装飞船

    alien_invasion.py # -*- encoding:utf-8 -*- """ @作者:ZYH @文件名:alien_invasion.py @文档说明: ...

最新文章

  1. 仅模糊背景图像而不是前面的文本
  2. 今天是 OSChina 上线 6 周年!
  3. 中国内裤衬里行业市场供需与战略研究报告
  4. 计算机动画的教育应用研究,计算机动画技术在高校CAI课件制作中的应用研究
  5. pcie数据反_理解PCIE链路反转和极性反转
  6. threejs 特效,自定义发光墙体,贴图动画版本。发光围栏。
  7. c++实现的木叶忍者村管理
  8. 通过 TokenType(){}.getType()获取Persionlt;Tgt; 泛型T的类型和数值
  9. 如何从网上下载一段视频(iawia002)
  10. 国家开放大学计算机应用基础本科性考,精选国家开放大学电大本科《1200计算机应用基础》形考任务1试题及答案...
  11. PGP加密技术应用(含安装包)
  12. 聚美自建的“真品联盟”被京东捅破的窗户纸
  13. dorado 刷新_记录新建dorado项目更新规则中报错
  14. C#实现笔记本自带蓝牙与汇承HC-08(BLE)蓝牙模块通讯
  15. 『转】山世光导师致报考研究生的一封信
  16. [HNOI 2014]米特运输
  17. 什么模式下不可使用曝光补偿_难道手动模式下不能调整曝光补偿值吗?
  18. Acala 团队入选 UC Berkeley 2020 春季孵化加速器
  19. OPPO R9s刷机包_OPPO R9s线刷包救砖包教程下载
  20. springboot集成log4j2 附完整配置

热门文章

  1. Android实战项目(初级向) - 趣味数学 -下
  2. python编程入门电脑推荐_Python编程从入门到实践
  3. 基于员工管理权限系统的数据库设计完整版
  4. 深圳俱乐部3月活动《IT大讲堂---思想的盛筵》
  5. 《ASP.NET AJAX程序设计——第II卷:客户端Microsoft AJAX Library与异步通讯层》前言...
  6. 计算机在线应用字体,在线预览电脑中的字体
  7. c语言char197用%d输出变为-59,强制类型转换所导致的数据改变
  8. 2021全国职业技能大赛浙江省杭州市“网络空间安全赛项”赛题及赛题解析(超详细)
  9. 让MFC(c++)编译的程序支持高DPI
  10. 如何定义一个布尔类型的成员变量