Python项目实战(一)《Python编程 从入门到实践》

项目一、外星人入侵:使用Python开发游戏

一、武装飞船

1、规划项目

开发大型项目时,制定好规划后再动手编写代码很重要。规划可确保你不偏离轨道,从而提高项目成功的可能性。
《外星人入侵》游戏的描述
在游戏《外星人入侵》中,玩家控制一艘最初出现在屏幕底部中央的飞船。玩家可以使用箭头键左右移动飞船,还可使用空格键射击。游戏开始时,一群外星人出现在天空中,并向屏幕下方移动。玩家的任务是射杀这些外星人。玩家将所有外星人都消灭干净后,将出现一群新的外星人,其移动速度更快。只要有外星人撞到玩家的飞船或到达屏幕底部,玩家就损失一艘飞船。玩家损失三艘飞船后,游戏结束。

开发的第一个阶段就是船舰一艘飞船,它可左右移动,并且能在用户按空格键时开火。

2、开始游戏项目(已经安装了Pygame)

  • 创建Pygame窗口及响应用户输入。
import sys

import pygame

class AlienInvasion:
	def __init__(self):
		"""管理游戏资源和行为的类"""
		pygame.init()
		#创建游戏窗口,指定窗口尺寸为1200X800像素,并将显示窗口赋给属性self.screen
		self.screen = pygame.display.set_mode((1200,800))
		pygame.display.set_caption("Alien Invasion")

	def run_game(self):
		"""开始游戏的主循环"""
		while True:
			#监视键盘和鼠标事件
			for event in pygame.event.get():
				if event.type==pygame.QUIT:
					sys.exit()

			#让最近绘制的屏幕可见
			pygame.display.flip()

if __name__=='__main__':
	#创建游戏实例并运行游戏
	ai = AlienInvasion()
	ai.run_game()

首先导入模块sys和pygame。模块pygame包含开发游戏所需的功能,玩家退出时,使用工具sys来退出游戏。
赋给属性self.screen的对象是一个surface。在Pygame中,surface是屏幕的一部分,用于显示游戏元素。在这个游戏中每个元素(如外星人或飞船)都是一个surface。pygame.display.set_mode()返回的surface表示整个游戏的窗口。激活游戏动画循环后,每经过一次循环都将自动重绘这个surface,将用户触发的所有变化都反映出来。
这个游戏由方法run_game()控制。该方法包含一个不断运行的while循环,而这个循环包含一个事件循环以及管理屏幕更新的代码。事件是用户玩游戏时执行的操作,如按键或移动鼠标。为程序响应事件,可编写一个事件循环,以侦听事件并根据发生的事件类型执行合适的任务。
函数pygame.event.get()用于访问Pygame检测到的事件,这个函数返回一个列表,其中包含它在上一次被调用后发生的所有之间。所有键盘或鼠标事件都将导致这个for循环运行。
函数pygame.display.flip(),命令Pygame让最近绘制的屏幕可见。它在每次执行while循环时都绘制一个空屏幕,并擦去就屏幕,使得只有新屏幕可见。

  • 设置背景色。在alien_invasion.py中设置背景色。
  • 创建设置类。setting类用于将所有设置都存储在一个地方,以免在代码中到处添加设置。这样,每当需要访问设置时,只需使用一个设置对象。另外,在项目增大时,这使得修改优秀的外观和行为更容易:要修改游戏,只需要修改settings.py中的一些值。
class Settings:
	"""存储游戏《外星人入侵》中所有设置的类"""

	def __init__(self):
		"""初始化游戏的设置"""
		#屏幕设置
		self.screen_width = 1200
		self.screen_height = 800
		self.bg_color = (230,230,230)

修改alien_invasion.py:

import sys

import pygame

from settings import Settings

class AlienInvasion:
	def __init__(self):
		"""管理游戏资源和行为的类"""
		pygame.init()
		self.settings=Settings()
		self.screen = pygame.display.set_mode(
			(self.settings.screen_width, self.settings.screen_height))
		pygame.display.set_caption("Alien Invasion")

	def run_game(self):
		"""开始游戏的主循环"""
		while True:
			#监视键盘和鼠标事件
			for event in pygame.event.get():
				if event.type==pygame.QUIT:
					sys.exit()

			#每次循环时都重绘屏幕
			"""
			fill方法使用背景色填充屏幕,用于处理surface,只接受一种颜色
			"""
			self.screen.fill(self.settings.bg_color)

			#让最近绘制的屏幕可见
			pygame.display.flip()

if __name__=='__main__':
	#创建游戏实例并运行游戏
	ai = AlienInvasion()
	ai.run_game()

3、添加飞船图像

为了在屏幕上绘制玩家的飞船,我们将加载一幅图像,再使用Pygame方法blit()绘制它。
可以使用Pixabay网站提供的免费图形,无需授权许可即可使用并修改。
在游戏中机会可以使用任何类型的图像文件,但使用位图(.bmp)文件最为简单,因为Pygame默认加载位图。
选择图像时,要特别注意背景色,尽可能选择背景与透明或纯色的图像,便于使用图像编辑器将其背景替换为任意颜色。图像的背景色与游戏的背景色匹配时,游戏看起来最漂亮。

  • 创建Ship类。负责管理飞船的大部分行为。
import pygame

class Ship:
	"""管理飞船的类"""

	# ai_game是当前AlienInvasion的实例的引用
	def __init__(self, ai_game):
		"""初始化飞船并设置其初始位置"""
		self.screen = ai_game.screen
		self.screen_rect = ai_game.screen.get_rect()

		# 加载飞船图像并获取其外接矩形
		"""
		pygame.image.load()加载图像,并将飞船图像的位置传递给它
		该函数返回一个表示飞船的surface
		"""
		self.image = pygame.image.load('images/ship.bmp')
		"""
		使用get_rect()函数获取相应surface的属性rect
		以便后面能够使用它来指定飞船的位置
		"""
		self.rect = self.image.get_rect()

		# 对于每艘新飞船,都将其放在屏幕底部的中央
		self.rect.midbottom = self.screen_rect.midbottom

	def blitme(self):
		"""在指定位置绘制飞船"""
		self.screen.blit(self.image, self.rect)

Pygame之所以高效,是因为它让你能够像处理矩形(rect对象)一样处理所有的游戏元素,即便其形状并非矩形。像处理矩形一样处理游戏元素之所以高效,是因为矩形是简单的几何形状。例如,将游戏元素视为矩形,Pygame能够更快地判断出它们是否发生了碰撞。这种做法的效果通常很好,游戏玩家机会注意不到我们处理的并不是游戏元素的实际形状。在这个类中,我们将把飞船和屏幕作为矩形进行处理。
处理rect对象时,可使用矩形四角和中心的x坐标和y坐标。可以通过设置这些值来指定矩形的位置。要让游戏元素居中,可设置相应的rect对象的属性center、centerx或centery;要让游戏元素与屏幕边缘对齐,可使用属性top、bottom、left或right。除此之外,还有一些组合属性,如midbottom、midtop、midleft和midright。要调整游戏元素的水平或垂直位置,可使用属性x和y,分别时相应矩形左上角的x坐标和y坐标。
Notice:在Pygame中,原点(0,0)位于屏幕左上角,向右下方移动时,坐标值将增大。在1200x800的屏幕上,原点位于左上角,而右下角的坐标为(1200,800)。这些坐标对应的是游戏窗口而不是物理屏幕。

  • 在屏幕上绘制飞船。
import sys

import pygame

from settings import Settings
from ship import Ship

class AlienInvasion:
	def __init__(self):
		"""管理游戏资源和行为的类"""
		pygame.init()
		self.settings=Settings()
		self.screen = pygame.display.set_mode(
			(self.settings.screen_width, self.settings.screen_height))
		pygame.display.set_caption("Alien Invasion")
		self.ship=Ship(self)

	def run_game(self):
		"""开始游戏的主循环"""
		while True:
			#监视键盘和鼠标事件
			for event in pygame.event.get():
				if event.type==pygame.QUIT:
					sys.exit()

			#每次循环时都重绘屏幕
			"""
			fill方法使用背景色填充屏幕,用于处理surface,只接受一种颜色
			"""
			self.screen.fill(self.settings.bg_color)
			self.ship.blitme()

			#让最近绘制的屏幕可见
			pygame.display.flip()

if __name__=='__main__':
	#创建游戏实例并运行游戏
	ai = AlienInvasion()
	ai.run_game()

导入ship类,并在创建屏幕后创建一个Ship实例。调用Ship()时,必须提供一个参数:一个AlienInvasion实例,这里self指向的是当前AlienInvasion实例。这个参数让Ship能够访问游戏资源,如对象screen。

4、重构:方法_check_events()和_update_screen()。

在大型项目中,经常需要在添加新代码前重构既有代码。重构旨在简化既有代码的结构,使其更容易拓展。将越来越长的方法run_game()拆分成两个辅助方法(helper method)。辅助方法在类中执行任务,但并非是通过实例调用的。在Python中,辅助方法的名称以单个下划线打头。

  • 方法_check_events()。将管理事件的代码移到名为_check_events()的方法中,以简化run_game()并隔离事件管理循环。通过隔离事件循环,可将事件管理与游戏的其他方面(如更新屏幕)分离。增加check_events()并更改run_game()的代码后如下:
	def run_game(self):
		"""开始游戏的主循环"""
		while True:
			self._check_events()

			#每次循环时都重绘屏幕
			"""
			fill方法使用背景色填充屏幕,用于处理surface,只接受一种颜色
			"""
			self.screen.fill(self.settings.bg_color)
			self.ship.blitme()

			#让最近绘制的屏幕可见
			pygame.display.flip()

	def _check_events(self):
		#监视键盘和鼠标事件
		for event in pygame.event.get():
			if event.type==pygame.QUIT:
				sys.exit()
  • 方法_update_screen()。为进一步简化run_game(),将更新屏幕的代码移到一个名为_update_screen()的方法中:
	def run_game(self):
		"""开始游戏的主循环"""
		while True:
			self._check_events()
			self._update_screen()
			
	def _update_screen(self):
		#每次循环时都重绘屏幕
		"""
		fill方法使用背景色填充屏幕,用于处理surface,只接受一种颜色
		"""
		self.screen.fill(self.settings.bg_color)
		self.ship.blitme()
		#让最近绘制的屏幕可见
		pygame.display.flip()

此时已将绘制背景和飞船以及切换屏幕的代码移到了方法_update_screen()中。现在很容易看出在每次循环中都检测到了新发生的事件并更新了屏幕。
新手可以先编写尽可能简单的代码,等项目越来约复杂后对其进行重构。

5、驾驶飞船

编写代码,在用户按左或右箭头时做出相应。

  • 响应按键。每当用户按键时,都将在Pygame中注册一个事件。事件都是通过方法pygame.event.get()获取的,因此需要在方法_check_events()中指定要检查哪些类型的事件。每次按键都被注册为一个KEYDOWN事件。Pygame检测到KEYDOWN事件时,需要检查按下的是否是触发行动的键。
	def _check_events(self):
		#监视键盘和鼠标事件
		for event in pygame.event.get():
			if event.type==pygame.QUIT:
				sys.exit()
			elif event.type == pygame.KEYDOWN:
				if event.key == pygame.K_RIGHT:
					#向右移动飞船
					self.ship.rect.x+=1
  • 允许持续移动。玩家按住右箭头键不放时,我们希望飞船不断向右移动,直到玩家松开为止。结合使用KEYDOWN和KEYUP以及一个名为moving_right的标志来实现持续移动。当标志moving_right 为False时,飞船不会移动。玩家按下右箭头时,将该标志设为True,在玩家松开时将该标志重新设置为False。
    飞船的属性都由Ship类控制,因此给这个类添加一个名为moving_right 的属性和一个update()的方法:
import pygame

class Ship:
	"""管理飞船的类"""

	def __init__(self, ai_game):
		"""初始化飞船并设置其初始位置"""
		self.screen = ai_game.screen
		self.screen_rect = ai_game.screen.get_rect()

		# 加载飞船图像并获取其外接矩形
		self.image = pygame.image.load('images/ship.bmp')
		self.rect = self.image.get_rect()

		# 对于每艘新飞船,都将其放在屏幕底部的中央
		self.rect.midbottom = self.screen_rect.midbottom

		#移动标志
		self.moving_right=False

	def update(self):
		if self.moving_right:
			self.rect.x+=1

	def blitme(self):
		"""在指定位置绘制飞船"""
		self.screen.blit(self.image, self.rect)

同时修改_check_events以及run_game:

	def run_game(self):
		"""开始游戏的主循环"""
		while True:
			self._check_events()
			self.ship.update()
			self._update_screen()

	def _check_events(self):
		#监视键盘和鼠标事件
		for event in pygame.event.get():
			if event.type==pygame.QUIT:
				sys.exit()
			elif event.type == pygame.KEYDOWN:
				if event.key == pygame.K_RIGHT:
					#向右移动飞船
					self.ship.moving_right = True
					#玩家松开右箭头
				elif event.type == pygame.KEYUP:
					if event.key == pygame.K_RIGHT:
						self.ship.moving_right = False
  • 左右移动。与右移类似,添加左移逻辑。
  • 调整飞船速度。在Settings类中添加属性ship_speed,用于控制飞船速度。
  • 限制飞船的活动范围。飞船不能飞出屏幕之外(0,screen_rect.right)。
  • 重构_check_events()。随着游戏的开发,_check_events()方法将越来越长,因此将其部分达目放在两个方法中,一个处理KEYDOWN事件,一个处理KEYUP事件。
	def _check_events(self):
		#监视键盘和鼠标事件
		for event in pygame.event.get():
			if event.type==pygame.QUIT:
				sys.exit()
			elif event.type == pygame.KEYDOWN:
				self._check_keydown_enents(event)
			elif event.type == pygame.KEYUP:
				self._check_keyup_enents(event)

	def _check_keydown_events(self,event):
		#玩家按下箭头
		if event.key == pygame.K_RIGHT:
			#向右移动飞船
			self.ship.moving_right = True
		elif event.key == pygame.K_LEFT:
			#向左移动飞船
			self.ship.moving_left = True

	def _check_keyup_events(self,event):
		#玩家松开箭头
		if event.key == pygame.K_RIGHT:
			self.ship.moving_right = False
		elif event.key == pygame.K_LEFT:
			self.ship.moving_left = False
  • 按Q键退出。在_check_keydown_events中添加一个代码块,用于在玩家按Q键时结束游戏。
	def _check_keydown_events(self,event):
		#玩家按下箭头
		if event.key == pygame.K_RIGHT:
			#向右移动飞船
			self.ship.moving_right = True
		elif event.key == pygame.K_LEFT:
			#向左移动飞船
			self.ship.moving_left = True
		elif event.key == pygame.K_q:
			sys.exit()
  • 在全屏模式下运行游戏。更改__init__()函数如下:
	def __init__(self):
		"""管理游戏资源和行为的类"""
		pygame.init()
		self.settings=Settings()
		#在全屏模式下运行游戏
		self.screen = pygame.display.set_mode((0,0),pygame.FULLSCREEN)
		self.settings.screen_width = self.screen.get_rect().width
		self.settings.screen_height = self.screen.get_rect().height
		pygame.display.set_caption("Alien Invasion")
		self.ship=Ship(self)

Notice:在全屏模式下运行这款游戏之前,请确认能够按Q键退出,因为Pygame默认不提供在全屏模式下退出游戏的方式。

6、射击

射击功能:在玩家按空格键时发射子弹(用小矩形表示),子弹将在屏幕中向上飞行,抵达屏幕边缘后消失。

  • 添加子弹设置。更新Settings类。
		#子弹设置
		self.bullet_speed = 1.0
		self.bullet_width = 3
		self.bullet_height = 15
		self.bullet_color = (60,60,60)
  • 创建bullet类。
import pygame
from pygame.sprite import Sprite

class Bullet(Sprite):
	#管理飞船所发射子弹的类

	def __init__(self, ai_game):
		#在飞船当前位置创建一个子弹对象
		super().__init__()
		self.screen = ai_game.screen
		self.settings = ai_game.settings
		self.color = self.settings.bullet_color

		#在(0,0)处创建一个表示子弹的矩形,再设置正确的位置
		self.rect =  pygame.Rect(0,0,self.settings.bullet_width,
			self.settings.bullet_height)
		#将子弹的初始位置设置为飞船的初始位置,这样子弹就是从飞船顶部出发的
		self.rect.midtop=ai_game.ship.rect.midtop

		#存储用小数表示的子弹位置
		self.y = float(self.rect.y)

继承了从模块pygame.sprite导入的Sprite类。通过使用精灵(sprite),可将游戏中相关的元素编组,进而同时操作编组中的所有元素。为创建子弹实例,init()还需要当前的AlienInvasion实例,还调用了super()来继承Sprite。
接下来写好管理子弹位置以及绘制子弹的方法:

	def update(self):
		self.y-=self.settings.bullet_speed
		self.rect.y=self.y

	def draw_bullet(self):
		pygame.draw.rect(self.screen,self.color,self.rect)
  • 将子弹存储到编组中。定义Bullet类和必要的设置后,便可编写在玩家每次按空格键时都射出一发子弹了。在AlienInvasion中创建一个编组(group),用于存储所有有效的子弹,以便管理发射出去的所有子弹。
    这个编组是pygame.sprite.Group的一个实例,它类似于列表,但提供了有助于开发游戏的额外功能。在主循环中,将使用这个编组在屏幕上绘制子弹以及更新每颗子弹的位置。
    在AlienInvasion中为子弹创建编组,并在run_game()的循环中更新子弹的位置。当编组调用update()时,编组自动对其中的每个精灵调用update()。
  • 开火。在AlienInvasion中,需要修改_check_keydown_events(),以便玩家按空格键时发射一颗子弹。
  • 删除消失的子弹。检测子弹的bottom属性是否为零,如果是,则表明子弹已飞过屏幕顶端,将其删除。
  • 限制子弹的数量。为了鼓励玩家有目标地进行设计,可以限制屏幕上子弹出现的数量。
  • 创建方法_update_bullets()。编写并检查子弹管理代码后,将其移动到一个独立的方法中。

二、外星人来了

开发大型项目时,要在进入每个阶段之前回顾以下开发计划,搞清楚接下来要通过代码来完成哪些任务。
在项目中添加新功能,还应审核既有代码。每进入一个新阶段,项目通常会更复杂,因此,最好对混乱或低效的代码进行清理(重构)。

1、创建第一个外星人

像创建Ship类那样创建Alien类,出于简化考虑,也将使用位图来表示外星人。

import pygame
from pygame.sprite import Sprite

class Alien(Sprite):
	def __init__( self, ai_game):
		#初始化为行人并设置其起始位置
		super().__init__()
		self.screen = ai_game.screen

		#加载外星人图像并设置其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)
  • 创建Alien实例。为了让第一个外星人在屏幕上现身,需要创建一个Alien实例。这属于设置工作,因此将把这些代码放在AlienInvasion类的方法__init__()末尾。最终会创建一群外星人,涉及的工作量不少,因此将新建一个名为create_fleet()的辅助方法。
	def _create_fleet(self):
		#创建外星人群
		#首先创建一个外星人
		alien = Alien(self)
		self.aliens.add(alien)

2、创建一群外星人

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

  • 确定一行可容纳多少外星人。屏幕两边要有边距(外星人的宽度),外星人之间的间距也是外星人的宽度,//为整除运算符,结果得到整数。
avaliable_space_x = self.settings.screen_width - (2*alien_width)
number_aliens_x = avaliable_space_x // (2*alien_width)
  • 创建一行外星人。重写_create_fleet()函数。

你可能感兴趣的:(Python,python)