用python写游戏:《外星人入侵》---> 添加外星人

项目需求:

在游戏《外星人入侵》中,玩家控制着一艘最初出现在屏幕底部中央的飞船。玩家可以使用箭头键左右移动飞船,还可使用空格键进行射击。游戏开始时,一群外星人出现在天空中,他们在屏幕中向下移动。玩家的任务是射杀这些外星人。玩家将所有外星人都消灭干净后,将出现一群新的外星人,他们移动的速度。只要有外星人撞到了玩家的飞船或到达了屏幕底部,玩家就损失一艘飞船。玩家损失三艘飞船后,游戏结束。

在第一阶段中,我们创建了一艘可以左右移动的飞船,这艘飞船在用户按空格键时能够开火。设置好这种行为后,我们就能够将注意力转向外星人,并提高这款游戏的可玩性。

往期博客

关于:设置飞船的博客

关于:给飞船添加射击功能的博客


本章目标:

在本章中,我们将在《外星人入侵》中添加外星人。首先,我们在屏幕上边缘附近添加一个外星人,然后生成一群外星人。我们让这群外星人向两边和下面移动,并删除被子弹击中的外星人。最后,我们将显示玩家拥有的飞船数量,并在玩家的飞船用完后结束游戏。

设置关闭游戏的快捷键

在 game_functions.py文件的 check_keydown_events方法中:

def check_keydown_events(event,ai_settings,screen,ship,bullets):
	#在监听用户按键操作方法中添加 ctrl+q的快捷退出游戏
	elif event.key == pygame.K_q:
		sys.exit()
创建第一个外星人

在屏幕设置外星人的方式和设置飞船的方式类似。
我们设置一个Alien类来控制外星人,就像设置 ship 类控制飞船一样。

同样的,我们使用位图的方式来表示外星人,因为 pygame默认加载的就是位图。

由于 csdn中已有相关《外星人入侵》的图片,不能进行上传,我将图片放在这里:
在这里插入图片描述

创建 Alien 类
import pygame
from pygame.sprite import Sprite

class Alien(Sprite):
	def __init__(self,ai_settings,screen):
		#初始化外星人并设置其起始位置
		super(Alien,self).__init__()
		self.screen = screen
		self.ai_settings = ai_settings
		
		#加载外星人图像,并设置rect属性
		self.image = pygame.image.load('images/alien.bmp')
		self.rect = self.image.get_rect()
		
		#每个外星人都在屏幕左上角附近
		self.rect.x = self.rect.width
		self.rect.y = self.rect.height
		
		#存储外星人的准确位置
		self.x = float(self.rect.x)
		
	def blitme(self):
		self.screen.blit(self.image,self.rect)

定义好了控制外星人的类,我们要对其进行实例化。
alien_invasion.py:

from alien import Alien 

def run_game():
	#创建外星人
	alien = Alien(ai_settings,screen)

#并且在主循环的 gf.update_screen()调用处添加新的形参
gf.update_screen(ai_settings,screen,ship,alien,bullets)
让外星人出现在屏幕上

game_functions.py:

def update_screen(ai_settings,screen,ship,alien,bullets):
	alien.blitem()

在这里插入图片描述

现在,我们成功让外星人显示到屏幕中了,
我们经历了三个步骤:

  1. 设置管理外星人的 alien类,在该类中,我们先对其位置的初始化,然后在进行图片的加载,加载到图片和获得其属性后,设置它出现的位置,在进行精确位置的存储,绘制。
  2. 对 alien类进行实例化,对这个类进行实例化是为了在调用 game_functions模块中的update_screen()函数对其传递参数,有了该参数,可将其绘制到屏幕。
  3. 最后为了让外星人显示到屏幕上,在update_screen()中调用biltem()函数进行绘制。
创建一群外星人

要绘制一群外星人,需要确定一行能容纳多少个外星人以及要绘制多少行外星人。我们将首先计算外星人之间的水平间距,并创建一行外星人,再确定可用的垂直空间,并创建整群外星人。

确定一行可容纳多少个外星人

为了确定一行可容纳多少个外星人,我们来看看可用的水平空间有多大:

屏幕宽度存储在 ai_settings.screen_width中,但需要在屏幕两边都留下一定的边距,把它设置为外星人的宽度,由于有两个边距,因此可用于放置外星人的水平空间为屏幕宽度减去外星人宽度的两倍:

available_space_x = ai_settings.screen_width-(2 * alien_width)

在这里插入图片描述

我们还需要在外星人之间留出一定的空间,即外星人宽度。因此,显示一个外星人所需的水平空间为外星人宽度的两倍:一个宽度用于放置外星人,另一个宽度为外星人右边的空白区域。为确定一行可容纳多少个外星人,我们将可用空间除以外星人宽度的两倍:

number_aliens_x = available_space_x / (2 * alien_width)

这两个公式将用于 --> 创建外星人。

创建多行外星人

我们在 alien_invasion中创建一个名为 aliens 的空编组,用于存储全部外星人,
然后在调用 game_functions.py中创建外星人的函数去创建外星人:

	#创建外星人编组
	aliens =Group()
	#创建外星人群
	gf.crate_fleet(ai_settings,screen,aliens)

# while 循环来控制游戏
	while True:
		gf.update_screen(ai_settings,screen,ship,aliens,bullets)

由于我们不再在 alien_settings.py中直接创建外星人,
因此无需在这个文件中导入Alien类。

接下来,调用稍后将编写的函数 crate_fleet(),并将ai_settings、对象 screen和空编组aliens 传递给它。然后,修改对update_screen()的调用,让它能够访问外星人编组。

修改 update_screen():

def update_screen(ai_settings,screen,ship,aliens,bullets):
		#每次循环时都重绘屏幕
		screen.fill(ai_settings.bg_color)
		#在飞船和外星人后面重绘所有子弹
		for bullet in bullets.sprites():
			bullet.draw_bullet()
		ship.blitme()
		aliens.draw(screen)
		
		#让最近绘制的屏幕可见
		pygame.display.flip()

对编组调用 draw() 时,pygame自动绘制编组的每个元素,绘制位置由元素的属性rect决定。在这里,aliens.draw(screen)在屏幕上绘制编组中的每个外星人。

创建外星人群

现在可以创建外星人群了。下面是新函数 crate_flect(),我们将它放在game_functions.py的末尾,我们还需要导入Alien类,因此务必在文件 game_functions.py开头添加相应的 import 语句:

from alien import Alien

def crate_fleet(ai_settings,screen,aliens):
	#创建外星人群
	
	#创建一个外星人,并计算一行可容纳多少个外星人
	alien = Alien(ai_settings,screen)
	alien_width = alien.rect.width
	available_space_x = ai_settings.screen_width - 2 * alien_width
	#外星人间距为外星人宽度
	number_aliens_x = int(available_space_x /(2 * alien_width))
	
	#创建一行外星人
	for alien_number in range(number_aliens_x):
		#创建第一个外星人并将其加入当前行
		alien = Alien(ai_settings,screen)
		alien.x = alien_width + 2 * alien_width * alien_number
		alien.rect.x = alien.x
		aliens.add(alien)

在这里插入图片描述

小结

为了放置外星人,我们需要知道外星人的宽度和高度,因此在执行计算之前,我们先创建一个外星人。
alien = Alien(ai_settings,screen)
这个外星人不是外星人群的成员,因此没有将它加入到编组aliens中。
alien_width = alien.rect.width。在这句代码语句中,我们从外星人的rect属性中获取外星人的宽度,并将这个值存储到 alien_width中,以免反复访问属性rect。
available_space_x = ai_settings.screen_width - 2 * alien_width。这里,我们计算可用于放置外星人的水平空间,以及其中可容纳多少个外星人。
number_aliens_x = int(available_space_x /(2 * alien_width))。相比于之前,我们这里用了int()来确保计算得到的外星人数量为整数。
最后的循环,它从零数到要创建的外星人人数。循环的主体中,我们创建一个新的外星人,并通过设置x坐标将其加入当前行。将每个外星人都往右推一个外星人的宽度。接下来,我们将外星人宽度乘以2,得到每个外星人占据的空间,再据此计算当前外星人在当前行的位置。最后,我们将每个新创建的外星人都添加到编组aliens中。

重构 crate_fleet()

倘若我们创建了外星人群,也许应该让 crate_fleet() 保持原样,但鉴于创建外星人的工作还未完成,我们稍微清理一下这个函数。下面是crate_fleet()和两个新函数:get_number_aliens_x() 和 crate_alien()

game_functions.py

def get_number_aliens_x(ai_settings,alien_width):
	#计算可容纳多少个外星人
	available_space_x = ai_settings.screen_width - 2 * alien_width
	#外星人间距为外星人宽度
	number_aliens_x = int(available_space_x /(2 * alien_width))
	return number_aliens_x	

def crate_alien(ai_settings,screen,aliens,alien_number):
	#创建一个外星人在当前行
	alien = Alien(ai_settings,screen)
	alien_width = alien.rect.width
	alien.x = alien_width + 2 * alien_width * alien_number
	alien.rect.x = alien.x
	aliens.add(alien)

def crate_fleet(ai_settings,screen,aliens):
	#创建外星人群
	#创建一个外星人,计算每行可以容纳多少个外星人
	alien = Alien(ai_settings,screen)
	number_aliens_x = get_number_aliens_x(ai_settings,alien.rect.width)
	
	#创建一行外星人
	for alien_number in range(number_aliens_x):
		#创建第一个外星人并将其加入当前行
		crate_alien(ai_settings,screen,aliens,alien_number)
添加行

要创建外星人群,需要计算屏幕可容纳多少行,并创建一个外星人的循环重复相应的次数。为计算可容纳的行数,我们这样计算可用垂直空间:将屏幕高度减去第一行外星人的上边距(外星人高度)、飞船的高度以及最初外星人高度加上外星人边距(外星人高度的两倍):

alailable_space_y = ai_settings.screen_height- 3 * alien_height - ship_height

这将在飞船上方留出一定的空白区域,给玩家留出射杀外星人的时间。

每行下方都留出一定空白,并将其设置为外星人的高度。为计算可容纳的行数,我们将可用垂直空间除以外星人高度的两倍。

number_rows = alailable_space_y /(2 * alien_height)

game_functions.py:

def get_number_rows(ai_settings,ship_height,alien_height):
	#计算屏幕可容纳多少个外星人
	available_space_y = (ai_settings.screen_height- (3 * alien_height) - ship_height)
	number_rows =int(available_space_y /(2 * alien_height))
	return number_rows	

def crate_alien(ai_settings,screen,aliens,alien_number,row_number):
	#创建一个外星人在当前行
	alien = Alien(ai_settings,screen)
	alien_width = alien.rect.width
	alien.x = alien_width + 2 * alien_width * alien_number
	alien.rect.x = alien.x
	alien.rect.y = alien.rect.height + 2 * alien.rect.height * row_number
	aliens.add(alien)

def crate_fleet(ai_settings,screen,ship,aliens):
	#创建外星人群
	#创建一个外星人,计算每行可以容纳多少个外星人
	alien = Alien(ai_settings,screen)
	number_aliens_x = get_number_aliens_x(ai_settings,alien.rect.width)
	number_rows = get_number_rows(ai_settings,ship.rect.height,alien.rect.height) 
	
	#创建外星人群
	for row_number in range(number_rows):
		#创建一行外星人
		for alien_number in range(number_aliens_x):
			#创建第一个外星人并将其加入当前行
			crate_alien(ai_settings,screen,aliens,alien_number,row_number)

修改 alien_invasion.py调用处

gf.crate_fleet(ai_settings,screen,ship,aliens)

如果我们现在运行游戏,可以看到这样的效果:
在这里插入图片描述

让外星人移动

接下来,我们去实现让外星人群在屏幕上左右移动,撞到屏幕边缘后下移一定的距离,再沿相反的方向移动。我们将不断地移动所有的外星人,直到所有外星人都被消灭,有外星人撞上飞船,或有外星人抵达屏幕低端。

向右移动外星人

为移动外星人,我们将使用 alien.py中的方法 update(),且对外星人群中的每个外星人都调用它。首先,添加一个控制外星人速度的设置:

settings.py:

#外星人的设置
	self.alien_speed_factor = 1

然后,使用这个设置来实现 update():

alien.py:

def update(self):
	#向右移动外星人
	self.x += self.ai_settings.alien_speed_factor
	self.rect.x = self.x

每次更新外星人位置时,都将它向右移动,移动量为 alien_speed_factor的值。我们使用属性 self.x跟踪每个外星人的准确位置,这个属性可存储小数值。然后,我们使用self.x的值来更新外星人的rect的位置。

在主循环中调用update函数,更新每个外星人的位置:

alien_incasion.py:

while True:
	gf.update_alien(aliens)
	#在更新子弹的后面,更新外星人的位置
	#因为之后还要检查是否有子弹撞到了外星人

最后,在文件 game_function.py末尾处添加新函数update_aliens():

def update(aliens):
	#更新外星人群中所有外星人的位置
	aliens.updete()

下面来创建外星人撞到了屏幕右边缘后向下移动、再向左移动的设置。

创建表示外星人移动方向的设置

settings.py:

		#外星人的设置
		self.alien_speed_factor = 1
		self.fleet_drop_speed =10
		#fleet_direction为1表示向右移动,为-1表示向左移动
		self.fleet_direction =1

设置 fleet_drop_speed 指定了外星人撞到屏幕边缘时,外星人向下移动的速度。将这个速度与水平速度分开是有好处的,这样就可以分别调整这两种速度了。

要实现 fleet_direction设置,可以将其设置为文本值,如’left’或’right’,但是这样就必须编写 if-elif 语句来检查外星人群的移动方向。鉴于只有两个可能的方向,我们使用值 -1 和 1 来表示它们。并在外星人群改变方向时在这两个值之间切换。

检查外星人是否撞到了屏幕边缘

我们现在编写一个方法来检查是否有外星人撞到了屏幕的边缘,还需要更改update(),以让每个外星人都沿正确的方向移动:

alien.py:

def check_edges(self):
		#如果外星人位于屏幕边缘,就返回True
		screen_rect = self.screen.get_rect()
		if self.rect.right >= screen_rect.right:
			return True
		elif self.rect.left <= 0:
			return True	
		
	def update(self):
		#向右移动外星人
		self.x += (self.ai_settings.alien_speed_factor 
		* self.ai_settings.fleet_direction)
		self.rect.x = self.x

我们可对任何外星人调用方法 check_edges(),看看它是否位于屏幕左边缘或右边缘。如果外星人的rect的reght属性大于屏幕的rect的right属性,就说明外星人位于屏幕右边缘。如果外星人的rect的left属性小于或等于0,就说明外星人位于屏幕左边缘。
我们还修改了 update(),将移动量设置为外星人速度和fleet_direction的乘机,让外星人向左或向右移动。如果fleet_direction为1,就将外星人当前的x坐标增大alien_speed_factor,从而将外星人向右移动;如果外星人当前的x坐标减去alien_speed_factor,从而将外星人向左移。

向下移动外星人群并改变移动方向

有外星人到达屏幕边缘时,需要将整群外星人下移,并改变他们的移动方向。

我们需要对 game_functions.py 做重大修改,因为我们要在这里检查是否有外星人到大了左边缘或右边缘。为此,我们编写函数:check_fleet_edges()和change_fleet_direction(),并对update_aliens()进行修改:

def check_fleet_edges(ai_settings,aliens):
	#有外星人到达边缘时采取相应的措施
	for alien in aliens.sprites():
		if alien.check_edges():
			change_fleet_direction(ai_settings,aliens)
			break
			
def change_fleet_direction(ai_settings,aliens):
	#将整群外星人下移,并改变他们的方向
	for alien in aliens.sprites():
		alien.rect.y += ai_settings.fleet_drop_speed
	ai_settings.fleet_direction *= -1
	

def update_aliens(ai_settings,aliens):
	#检查是否有外星人位于屏幕边缘,并更新整群外星人的位置
	check_fleet_edges(ai_settings,aliens)
	
	#更新外新人群中所有外星人的位置
	aliens.update()
		

说明:

check_fleet_edges:

我们遍历外星人群,并对其中的每个外星人调用check_edges()。如果check_edges()返回True,我们就知道相应的外星人位于屏幕边缘,需要改变外星人群的方向,因此我们调用 change_fleet_direction() 并退出循环。

check_fleet_direction:

我们遍历所有外星人,将每个外星人下移fleet_drop_speed设置的值,然后,将fleet_direction的值修改为当前值与-1的乘机。

update_aliens:

在其中通过调用check_fleet_edges()来确定是否有外星人位于屏幕边缘。

alien_invasion.py:

# while 循环来控制游戏
	while True:
		gf.update_aliens(ai_settings,aliens)

如果你现在运行这个游戏,外星人群将在屏幕上来回移动,并在抵达屏幕边缘后向下移动。

现在可以开始射杀外星人,检查是否有外星人撞到飞船,或抵达了屏幕底端。

射杀外星人

我们创建了飞船和外星人群,但子弹击中外星人时,将穿过外星人,因为我们还没有检查碰撞。
在游戏编程中,碰撞指:游戏元素之间重叠在一起。要让子弹能够击败外星人,我们将使用sprite.groupcollide()检测两个编组的成员之间的碰撞。

检查子弹与外星人的碰撞

方法sprite.groupcollide()将每颗子弹的rect属性同外星人的rect属性进行比较,并返回一个字典,其中包含发生了碰撞的子弹和外星人。在这个字典中,每个键都是一颗子弹,而相应的值都是被击中的外星人。

函数 update_bullets()中,编写检查碰撞的代码:

def update_bullets(aliens,bullets):
	#更新子弹的位置,并删除已消失的子弹
	#更新子弹的位置
	bullets.update()
	
	#删除已消失的子弹
	for bullet in bullets.copy():
		if bullet.rect.bottom <= 0:
			bullets.remove(bullet)
			
	#检查是否有子弹击中外星人,如果是,删除相应子弹和外星人。
	pygame.sprite.groupcollide(bullets,aliens,True,True)

pygame.sprite.groupcollide(bullets,aliens,True,True) 说明:

这行代码遍历编组 bullets中的每颗子弹,再遍历编组 aliens中的每个外星人。
每当子弹的rect属性和外星人的rect属性重叠时,groupcollide()就在它返回的字典中添加一个键-值对。两个实参True告诉pygame删除发生碰撞的子弹和外星人。

最后,调用 update_bullets()时,传递实参 aliens:

gf.update_bullets(aliens,bullets)
生成新的外星人群

现在的外星人群被消灭后,不会有新的外星人产生,为了让外星人群实现在显示新的敌人,我们可以这样编码:

首先检查 aliens是否为空,如果为空,就调用 crate_fleet(),我们将在update_bullets()中执行这种检查,因为外星人都是在这里被消灭的:

def update_bullets(ai_settings,screen,ship,aliens,bullets):
	#更新子弹的位置,并删除已消失的子弹
	#更新子弹的位置
	bullets.update()
	
	#删除已消失的子弹
	for bullet in bullets.copy():
		if bullet.rect.bottom <= 0:
			bullets.remove(bullet)
			
	#检查是否有子弹击中外星人,如果是,删除相应子弹和外星人。
	pygame.sprite.groupcollide(bullets,aliens,True,True)
	
	if len(aliens) ==0:
		bullets.empty()
		crate_fleet(ai_settings,screen,ship,aliens)

在if语句中检查到 ,我们调用了 empty() 用这个方法删除编组中余下的所有精灵,从而删除现有的子弹,然后调用crate_fleet() 再次在屏幕上显示外星人群。

重构 update_bullets()

下面我们重构 update_bullets(),使其不在完成那么多的任务。我们将把处理子弹和外星人碰撞的代码移动一个独立的函数中:

def update_bullets(ai_settings,screen,ship,aliens,bullets):
	#更新子弹的位置,并删除已消失的子弹
	#更新子弹的位置
	bullets.update()
	
	#删除已消失的子弹
	for bullet in bullets.copy():
		if bullet.rect.bottom <= 0:
			bullets.remove(bullet)
			
	check_bullet_alien_collisions(ai_settings,screen,ship,aliens,bullets)
	
	
	
def check_bullet_alien_collisions(ai_settings,screen,ship,aliens,bullets):	
	#检查是否有子弹击中外星人,如果是,删除相应子弹和外星人。
	pygame.sprite.groupcollide(bullets,aliens,True,True)
	
	if len(aliens) ==0:
		bullets.empty()
		crate_fleet(ai_settings,screen,ship,aliens)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

兔C

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值