用python写《外星人入侵》游戏:武装飞船 >1

昨天的文章,我们设置好了本地环境。

现在,我们有了 python,有了安装 pygame所需的pip,和pygame。接下来我们就开始进入项目阶段。

开始游戏项目

首先创建一个空的pygame窗口,供后面用来绘制游戏元素,如飞船和外星人。我们还将让这个游戏响应用户输入、设置背景颜色以及加载飞船图象。


创建 pygame 窗口以及响应用户输入
#此模块用于游戏退出
import sys 
#此模块用于开发游戏所需的功能
import pygame 

def run_game():
	#初始化背景设置
	pygame.init()
	#设置窗口
	screen = pygame.display.set_mode((1200,800))
	pygame.display.set_caption("兔C:外星人入侵")
	
	# while 循环来控制游戏
	while True:
		
		for event in pygame.event.get():
			if event.type == pygame.QUIT:
				sys.exit()
				
		pygame.display.flip()
		
run_game()

首先,导入了 sys和pygame模块。pygame包含开发游戏所需的功能。玩家退出时,我们将使用模块 sys来退出游戏。

run_game() 函数:

​ pygame.init() 初始化背景设置,让python能够正确地工作。

​ 调用 pygame.display.set_mode() 来创建一个名为screen的显示窗口,这个游戏的所有图形元素都将在其中绘制。实参(1200,800) 是一个元组,指定了游戏窗口的尺寸。通过将这些尺寸传递pygame.sisplay.set_mode(),我们创建了一个宽1200像素,高800像素的游戏窗口。

​ 对象 screen是一个 surface。在python中,surface是屏幕的一部分,用于显示游戏元素。在这个游戏中,每个元素(如外星人或飞船)都是一个 surface。display.set_mode()返回的surface表示整个游戏窗口。我们激活游戏的动画循环后,每经过一次循环都将自动重绘整个surface。

​ 这个游戏由一个while循环控制,其中包含一个事件循环以及管理屏幕更新的代码。事件是用户玩游戏时执行的操作,如按键或移动鼠标。为让程序响应事件,我们编写一个事件循环,以及监听事件,并根据发生的事件执行响应的任务。for循环就是一个事件循环。

​ 为访问pygame检测到的事件,我们使用方法 pygame.enent.get()。所有键盘和鼠标事件都将促使for循环运行。在这个循环中,我们将编写一系列的if语句来检测并响应特定的事件。例如,玩家单机游戏窗口的关闭按钮时,将检测到 pygame.QUIT事件,而我们调用sys.exit() 来退出游戏。

​ pygame.display.flip(),命令pygame让最近绘制的屏幕可见。在这里,它在每次执行 while 循环时都绘制一个空屏幕,并擦去旧屏幕,使得只有新屏幕可见。在我们移动游戏元素时,pygame.display.flip()将不断更新屏幕,以显示元素的新位置,并在原来的位置隐藏游戏,从而营造平滑移动的效果。

​ 在整个基本的游戏结构中,最后一行调用 run_game(),这讲初始化游戏并开始主循环。

​ 如果此时运行这段代码,我们将看到一个空的pygame窗口。

设置背景颜色

在这里插入图片描述

我们来给游戏背景做一些设置。

#在run_game()方法中,设置颜色参数
bg_color = (230,230,230)

#在循环中的每次更新屏幕前设置颜色
screen.fill(bg_color)

在这里我们将设置好的背景颜色存储在变量bg_color当中,该颜色只需指定一次,因此我们在进入主循环之前定义它。
在pygame中,颜色是以RGB值指定的。这种颜色由红色、绿色和蓝色值组成,其中每个值的可能取值范围都为0~255。颜色值(255,0,0)表示红色,(0,255,0)表示绿色,(0,0,255)表示蓝色。通过组合不同的RGB值,可创建1600万种颜色。在颜色值为(230,230,230)中,红色、蓝色和绿色量相同,它会将背景设置为一种浅灰色。
接下来,我们调用方法 screen.fill(),用背景色填充屏幕;这个方法只接收一个实参:一种颜色。

在这里插入图片描述


创建设置类

每次给游戏添加新功能时,通常也将引入一些新设置。下面我们编写一个名为 settings的模块,其中包含一个名为Settings的类,用于将所有设置存储在一个地方,以免在代码中到处添加设置。这样,我们就能传递一个设置对象,而不是众多不同的设置。另外,这让函数调用更简单,更便于修改。

#定义settings.py模块的Settings类
class Settings():
	#存储《外星人入侵》的所有设置的类
	
	def __init__(self):
		self.screen_width = 1200
		self.screen_height = 800
		self.bg_color = (230,230,230)

现在有了设置类,就要去更改一下 alien_invasion 模块的代码了,
因为我们需要通过设置类的调用来设置参数了。

#此模块用于游戏退出
import sys 
#此模块用于开发游戏所需的功能
import pygame 
#导入设置类
from settings import Settings

def run_game():
	#初始化背景设置
	pygame.init()
	
	#将设置类进行实例化
	ai_settings = Settings()
	
	#设置窗口
	screen = pygame.display.set_mode((ai_settings.screen_width,ai_settings.screen_height))
	pygame.display.set_caption("兔C:外星人入侵")
	
	
	# while 循环来控制游戏
	while True:
		
		for event in pygame.event.get():
			if event.type == pygame.QUIT:
				sys.exit()
				
		screen.fill(ai_settings.bg_color)
		
		pygame.display.flip()
		
run_game()

在主程序文件中,我们导入 Settings类,调用pygame.init(),再创建一个Settings实例,并将其存储在变量 ai_settings中。在创建屏幕时,使用了 ai_settings的属性screen_width 和 screen_height;接下来填充屏幕时,也使用了ai_settings来访问背景颜色。

添加飞船图像

在游戏中几乎可以使用任何类型的图像文件,但使用位图(.bmp)文件最为简单,因为python默认加载位图。虽然可配置pygame以使用其他文件类型,但有些文件类型要求你在计算机上安装相应的图像库。大多数图像都为.jpg、.png或.gif格式,但可以使用 photoshop、GIMP、Paint等工具将其转换为位图。
注意:
在选择图像时,尽量选用背景透明的图像,因为这样可使用图像编辑器将其背景设置为任何颜色。

我们先在项目中创建一个文件夹,并将其命名为:images。
在将 飞船的图像放到images文件夹中。
在这里插入图片描述

创建 Ship 类

选择用于表示飞船的图像后,需要将其显示到屏幕上。
我们创建一个名为 ship 的模块,其中包含 Ship类,它负责管理飞船的大部分行为。

import pygame

class Ship():
	def __init__(self,screen):
		#初始化飞船并设置其初始值
		self.screen = screen
		
		#加载飞船图像并获取其外接矩形
		self.image = pygame.image.load('images/ship.bmp')
		self.rect = self.image.get_rect()
		#表示屏幕的矩形
		self.screen_rect = screen.get_rect()
		
		#将每艘新飞船放在屏幕底部中央
		self.rect.centerx = self.screen_rect.centerx
		self.rect.bottom = self.screen_rect.bottom
		
	def blitme(self):
		#在指定位置绘制飞船
		self.screen.blit(self.image,self.rect)

我们来说明一下这段代码,

在模块页的首行,仍然是pygame的导入。

​ Ship() 的方法 __ init __() 接受两个参数:引用 self 和 screen,其中后者指定了要将飞船绘制到什么地方。为加载图像,我们调用了 pygame.image.load(),这个函数将返回一个表示飞船的 surface,而我们将这个 surface存储到了self.image中。

​ 加载图像后,我们使用get_rect()获取相应surface的属性rect()。pygame的效率之所以高是因为:它让你能够像处理矩形(rect对象)一样处理游戏元素,即便它们的形状并非矩形。像处理矩形一样处理游戏元素之所以高是因为矩形是简单的几何形状。这种做法的效果非常好,游戏玩家几乎注意不到我们处理的不是游戏元素的实际形状。

​ 处理rect对象时,可使用矩形四角和中心的x和y坐标。可通过设置这些值来指定矩形的位置。

​ 要将游戏元素居中,可设置相应rect对象的属性center、centerx或centery。要让游戏元素与屏幕边缘对齐,可使用top、bottom、left或right;要调整游戏元素的水平或垂直位置,可使用属性x 和 y,它们分别是相应矩形左上角的 x 和 y 坐标。这些属性让你无需去做游戏开发人员原本需要手工完成的计算,你经常会用到这些属性。

.
注意:在pygame中,原点(0,0)位于屏幕左上角,向右下方移动时,坐标值将增大。在1200*800的屏幕上,原点位于左上角,而右下角的坐标为(1200,800)

.
​ 我们将飞船放在屏幕底部中央。为此,首先将表示屏幕的矩形存储在self.screen_rect中,再将self.rect.centerx(飞船中心的x坐标)设置为表示屏幕的矩形的属性centerx,然后将self.rect.bottom(飞船下边缘的y坐标)设置为表示屏幕的矩形属性bottom。pygame将使用这些rect属性来放置飞船图像,使其与屏幕下边缘对其并且水平居中。

​ blitme() 方法根据self.rect指定的位置将图像绘制到屏幕上。

在屏幕上绘制飞船

接下来更新 alien_invasion.py,使其创建一艘飞船,并调用方法 blitme():

#此模块用于游戏退出
import sys 
#此模块用于开发游戏所需的功能
import pygame 
#导入设置类
from settings import Settings
from ship import Ship #飞船类

def run_game():
	#初始化背景设置
	pygame.init()
	
	#将设置类进行实例化
	ai_settings = Settings()
	
	#设置窗口
	screen = pygame.display.set_mode((ai_settings.screen_width,ai_settings.screen_height))
	pygame.display.set_caption("兔C:外星人入侵")
	
	#创建飞船
	ship = Ship(screen)
	
	# while 循环来控制游戏
	while True:
		
		for event in pygame.event.get():
			if event.type == pygame.QUIT:
				sys.exit()
				
		#每次循环时都重绘屏幕
		screen.fill(ai_settings.bg_color)
		ship.blitme()
		
		pygame.display.flip()
		
run_game()

我们导入 Ship类,并在创建屏幕后创建一个名为ship的Ship实例。必须在主while循环前面创建该实例,以免每次循环时都创建一艘飞船。填充背景后,我们调用 ship.blitme()将飞船绘制到屏幕上,确保它出现在背景前面。


重构模块 game_functions

在大型项目中,经常需要在添加新代码块前重构既有代码。重构旨在简短既有代码的结构,使其更容易扩展。我们需要创建一个名为 game_functions的新模块,它将存储大量让游戏《外星人入侵》运行的函数。通过创建模块 game_functions,可以避免alien_invasion.py太长,并使其逻辑更易于理解。

函数 check_events()

我们首项把管理事件的代码移到一个名为 check_events()的函数中,以简化 run_game()并隔离事件管理循环。通过隔离事件循环,可将事件管理与游戏的其他方面(如更新屏幕)分离。

将 check_events() 放在一个名为 game_functions 的模块中:

import sys
import pygame

def check_events():
     #响应按键和鼠标事件
	for event in pygame.event.get():
		if event == QUIT:
			sys.exit()

这个模块中导入了事件检查循环要使用的sys和pygame。当前,函数 check_events()不需要任何形参,其函数体复制了 alien_invasion.py 的事件循环。

下面,我们继续修改 alien_invasion.py,使其导入我们刚才创建的game_functions,并将事件循环替换为对函数check_events()的调用:

#此模块用于开发游戏所需的功能
import pygame 
#导入设置类
from settings import Settings
from ship import Ship #飞船类
import game_functions as gf

def run_game():
	#初始化背景设置
	pygame.init()
	
	#将设置类进行实例化
	ai_settings = Settings()
	
	#设置窗口
	screen = pygame.display.set_mode((ai_settings.screen_width,ai_settings.screen_height))
	pygame.display.set_caption("兔C:外星人入侵")
	
	#创建飞船
	ship = Ship(screen)
	
	# while 循环来控制游戏
	while True:
		
		gf.check_events()
				
		#每次循环时都重绘屏幕
		screen.fill(ai_settings.bg_color)
		ship.blitme()
		
		pygame.display.flip()
		
run_game()

在主程序文件中,不需要直接导入 sys,因为当前只在模块 game_fuctions中使用了它。出于简化的目的,我们给导入的 game_functions指定了别名 gf。

函数 update_screen()

为进一步简化run_game(),下面将更新屏幕的代码移到一个名为 update_game()的函数中,并将这个函数放在模块game_functions中:

// 为优化文章减少冗余情况,后面代码部分的内容只放置新添加的代码行。

def update_screen(ai_settings,screen,ship):
	#每次循环时都重绘屏幕
		screen.fill(ai_settings.bg_color)
		ship.blitme()
		
		#让最近绘制的屏幕可见
		pygame.display.flip()

新函数 update_screen() 包含三个形参:
ai_settings、screen、ship。现在需要将alien_invasion.py的while循环中更新屏幕的代码替换为对函数 update_screen() 的调用。

gf.update_screen(ai_settings,screen,ship)

这两个函数让 while循环更简单,并让后续开发更容易:在模块 game_functions而不是run_game()中完成大部分工作。

驾驶飞船

接下来我们要让飞船实现可以左右移动,为此,我们将编写代码,在用户按左或按右箭头键时作出响应。

我们先来实现向右移动,再去实现向左移动。

响应按键

每当用户按键时,都将在 python中注册一个事件。事件都是通过方法 pygame.event.get() 获取的。因此在 check_events()中,我们需要指定要检查哪些类的事件。每次按键都被注册为一个 KEYDOWN事件。当检测到 KEYDOWN 事件时,我们需要检查按下的是否是特定的键。例如,如果按下的是右箭头键,我们就增大飞船的rect.centerx值,将飞船向右移动。

在这里,我们只需要在 game_functions模块中添加新的监听逻辑就好了。

#game_functions模块的 check_events 方法
def check_events(ship):
	for event in pygame.event.get():
		
		if event.type == pygame.QUIT:
			sys.exit()
			
		elif event.type == pygame.KEYDOWN:
			if event.key == pygame.K_RIGHT:
				ship.rect.centerx += 1

我们给 check_events设置了一个形参,所以在调用处就需要传递一个实参了。别忘记修改一下调用处的参数。
之所以添加这个参数是因为监听到玩家的按键动作时,我们需要让飞船执行对应的移动操作。 这里我们添加 elif代码块,以便在 python
检测到KEYDOWN 事件时作出响应。
读取event.key属性,以检查按下的是否是右箭头键(pygame.K_RIGHT)。如果按下的是右箭头键,就将
ship.rect.centerx +1。

如果现在运行我们的 aliven_invasion.py,则每按右箭头键一次,飞船都将向右移动1像素。这是一个开端,但并非控制飞船的高效方式。下面来改进控制方式,允许持续移动。

允许不断移动

玩家按住右箭头键不放时,我们希望飞船不断地向右移动,直到玩家松开为止。我们让游戏检测pygame.KEYUP事件,以便玩家松开右箭头键时我们能够知道这一点;然后,我们将结合使用KEYDOWN和KEYUP事件,以及一个名为moving_right的标准来实现持续移动。
飞船不动时,标志moving_right将为 False。玩家按下右箭头键时,我们将这个标志设置为True;而玩家松开时,我们将这个标志重新设置为 False。
飞船的属性都由ship类控制,因此我们将给这个类添加一个名为moving_right的属性和一个名为update()的方法。方法update()检查标志 moving_right的状态,如果这个状态为True,就调整飞船的位置。每当需要调整飞船的位置时,我们都调用这个方法。

修改 ship 类:

class Ship():
		
	def __init__(self,screen):
		#初始化飞船并设置其初始值
		self.screen = screen
		
		#加载飞船图像并获取其外接矩形
		self.image = pygame.image.load('images/ship.bmp')
		self.rect = self.image.get_rect()
		self.screen_rect = screen.get_rect()
		
		#将每艘新飞船放在屏幕底部中央
		self.rect.centerx = self.screen_rect.centerx
		self.rect.bottom = self.screen_rect.bottom
		
		#移动标志
		relf.moving_right = False
		
	def update(self):
		#根据移动标志调整飞船的位置
		if self.moving_right:
			self.rect.centerx +=1	
		
	def blitme(self):

下面我们继续修改 check_enents(),
使其在玩家按下右箭头键时将moving_right设置为True,并在玩家松开时将 moving_rigth设置为 Fasle:

def check_events(ship):
	for event in pygame.event.get():
		
		if event.type == pygame.QUIT:
			sys.exit()
			
		elif event.type == pygame.KEYDOWN:
			if event.key == pygame.K_RIGHT:
				ship.moving_right = True
		elif event.type == pygame.KEYUP:
			if event.key == pygame.K_RIGHT:
				ship.moving_right = False

我们先修改了游戏在玩家按下右键箭头时响应的方式:不直接调整飞船的位置,而只是将moving_right设置为True。然后我们又添加了一个新的elif代码块,用于响应KEYUP事件;玩家松开右箭头键(K_RIGHT)时,我们将moving_right设置为False。

最后,
我们修改 alien_invasion.py中的while循环,以便每次执行循环时都调用飞船的方法 update():

# while 循环来控制游戏
	while True:
		
		gf.check_events(ship)
		ship.update()		
		gf.update_screen(ai_settings,screen,ship)
左右移动

飞船能够不断地向右移动后,添加向左移动的逻辑很容易。我们将再次修改 ship类和函数check_events()。下面显示了对 Ship类的方法__ init __()和update所做的相关修改。

	#ship 类的 init方法中添加一个记录向左移动状态的标志
		#移动标志
		self.moving_right = False
		self.moving_left =False
		
	def update(self):
		#根据移动标志调整飞船的位置
		if self.moving_right:
			self.rect.centerx += 1	
		if self.moving_left:
			self.rect.centerx -= 1

调整 check_events():

def check_events(ship):
	for event in pygame.event.get():
		
		if event.type == pygame.QUIT:
			sys.exit()
			
		elif event.type == pygame.KEYDOWN:
			if event.key == pygame.K_RIGHT:
				ship.moving_right = True
			elif event.key == pygame.K_LEFT:
				ship.moving_left = True
		elif event.type == pygame.KEYUP:
			if event.key == pygame.K_RIGHT:
				ship.moving_right = False
			elif event.key == pygame.K_LEFT:
				ship.moving_left = False
调整飞船的速度

当前,每次执行 while 循环时,飞船最多移动1像素,但我们可以在 Settings 类中添加属性
ship_speed_factor,用于控制飞船到的速度。我们将根据这个属性决定飞船在每次循环时最多移动多少距离。下面我们开始在
settings.py中添加这个新的属性:

class Settings():
	#存储《外星人入侵》的所有设置的类
	
	def __init__(self):
		self.screen_width = 1200
		self.screen_height = 800
		self.bg_color = (230,230,230)
		
		#飞船的设置
		self.ship_speed_factor = 1.5

我们将 ship_speed_factor 的初始值设置成了 1.5。需要移动飞船时,我们将移动1.5像素而不是1像素。

​ 通过将速度设置指定为小数值,可在后面加快游戏的节奏时更细致地控制飞船的速度。然而,rect的centerx等属性只能存储整数值,因此我们需要对
Ship类做些修改:

import pygame

class Ship():
		
	def __init__(self,ai_settings,screen):
		#初始化飞船并设置其初始值
		self.screen = screen
		self.ai_settings = ai_settings
		
		#加载飞船图像并获取其外接矩形
		self.image = pygame.image.load('images/ship.bmp')
		self.rect = self.image.get_rect()
		self.screen_rect = screen.get_rect()
		
		#将每艘新飞船放在屏幕底部中央
		self.rect.centerx = self.screen_rect.centerx
		self.rect.bottom = self.screen_rect.bottom
		
		#在飞船的属性centerx中存储小数值
		self.center = float(self.rect.centerx)
		
		#移动标志
		self.moving_right = False
		self.moving_left =False
		
	def update(self):
		#根据移动标志调整飞船的位置
			#更新飞船的centerx值,而不是rect
		if self.moving_right:
			self.center += self.ai_settings.ship_speed_factor	
		if self.moving_left:
			self.center -= self.ai_settings.ship_speed_factor	
			
		#根据self.center更新rect值
		self.rect.centerx = self.center
		
	def blitme(self):
		#在指定位置绘制飞船
		self.screen.blit(self.image,self.rect)

在ship类中,我们首先在 __ init __() 方法中添加了ai_settings,让飞船能够获取其速度设置。接下来,我们将形参
ai_settings的值存储在一个属性中,以便能够在update()中使用它。鉴于现在调整飞船的位置时,将增加或减去一个单位为像素的小数值,因此需要将位置存储在一个能够存储小数值的变量中。可以使用小数来设置rect的属性,但rect将只存储这个值的整数部分。为准确地储存处飞船的位置,我们定义了一个可存储小数值的新属性
self.center。我们使用函数 float()
将self.rect.centerx的值转换为小数,并将结果存储到self.center中。
.
现在在update()中调整飞船的位置时,将self.center的值增加或减去ai_settings.ship_speed_factor的值。更新self.center后,我们再根据它来更新控制飞船位置的self.rect.centerx。self.rect.centerx将只存储self.center的整数部分,但对显示飞船而言,这问题不大。

最后不要忘记在 alien_invasion.py模块的调用处传递实参。

只要ship_speed_factor的值大于1,飞船的移动速度就会比以前更快。这有助于让飞船的反应速度足够快,能够将外星人射下来,还让我们能够随着游戏的进行加快游戏的节奏。

猜你喜欢

转载自blog.csdn.net/tianlei_/article/details/129528204