案例选自python编程从入门到实践,从项目开发的角度体会面向对象思维,在此做一些记录和分享,希望对你有所帮助。不定期更新~
功能架构:
说在前面:
个人理解python入门的核心分为三大块:
数据类型:他构建了软件的血液。在实际的使用当中要着重体会不同数据类型的适用场景和局限。
面向对象:他构建了软件的组织。在实际的使用当中要着重体会对象的构建和使用对代码复用的贡献,比较不同方法和属性的差异。
第三方库:他为软件的运行提供了养料。在实际的使用当中要逐渐积累信息搜集和使用的经验,通常情况下常见问题可以在各大技术论坛中得到答案,当遇到信息资源匮乏的情况下,学会使用第三方库的官方文档Pygame第三方库使用官网
实现过程按照功能架构分成五个阶段:
阶段一:创建窗口
本阶段要创建一个游戏主界面,并检测鼠标和键盘事件;在主程序之外创建设置类存储所有的设置信息。
把设置抽象为一个类,便于我们在后期添加新功能的时候可以在一个文件内统一对主要参数信息进行修改。
alien_invasion.py :主程序
import sys ##sys库:维护与Python解释器相关的参数变量
import pygame ##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)) ##访问实例对象属性作为参数调用display创建screen窗口,在pygame中用surface代表屏幕的一部分用于显示游戏元素,display.set_mode()返回的是整个游戏窗口的元素
pygame.display.set_caption("Alien Invasion") #display.set_caption()用于设置窗口title
"""开始游戏主循环,主要涵盖两部分:事件循环+屏幕管理"""
while True:
#事件循环:监听事件+响应事件
for event in pygame.event.get(): ##for+event.get()持续监听
if event.type == pygame.QUIT: ##if+event.type分类响应
sys.exit() ##sys.exit()退出程序
#屏幕管理:每次循环时都重绘屏幕
screen.fill(ai_settings.bg_color) ##访问实例对象属性作为参数设置窗口背景
pygame.display.flip() ##不断更新屏幕
"""初始化游戏,并开始主循环"""
run_game()
恩……后期可以写几个博客专门介绍经典第三方库的常用属性及方法
settings.py :集中设置主要参数信息——对应各个对象的常量属性
class Settings():
"""存储所有设置的类"""
def __init__(self):
"""初始化游戏的设置"""
#屏幕设置:实例属性定义与赋值
self.screen_width = 1200
self.screen_height = 800
self.bg_color = (230,230,230)
阶段二:创建飞船
本阶段分两步来完成:
第一步实现图像元素在屏幕中摆放,并响应键盘操作改变图像元素位置。在实现的过程当中,对既有的代码块进行重构。
重构思想旨在简化既有代码的结构,方便后续添加新的代码功能。
ship.py:创建一个Ship类,负责管理飞船的大部分行为
import pygame
class Ship():
def __init__(self,screen):
"""初始化飞船,并设置其初始位置"""
self.screen = screen ##Ship类需要获取屏幕对象作为输入参数,screen由alien_invasion.py中display.set_mode()方法获取,并且将形参screen的值存储在一个实例属性当中,以便在blitme()中使用他
#加载飞船图像并获取其外接矩形
self.image = pygame.image.load('images/ship.bmp') ##image.load()加载图像返回飞船surface存储于实例属性self.image
self.rect = self.image.get_rect() ##get_rect()方法获取surface的rect属性(控制矩形的方式控制游戏元素)
self.screen_rect = screen.get_rect()
#将每艘飞船放在屏幕底部中央:rect对象的处理可以通过设置矩形四角和中心的x和y坐标来制定位置。边缘对齐:top\bottom\left\right属性,水平和垂直:x\y(矩形左上角)属性
self.rect.centerx = self.screen_rect.centerx
self.rect.bottom = self.screen_rect.bottom
def blitme(self):
"""在指定位置绘制飞船"""
self.screen.blit(self.image,self.rect) ##使screen、image、rect属性都指向Ship的实例对象self
alien_invation.py更新:ship类在主程序中实例化完成飞船的创建
import sys ##sys库:维护与Python解释器相关的参数变量
import pygame ##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)) ##访问实例对象属性作为参数调用display创建screen窗口,在pygame中用surface代表屏幕的一部分用于显示游戏元素,display.set_mode()返回的是整个游戏窗口的元素
pygame.display.set_caption("Alien Invasion") #display.set_caption()用于设置窗口title
#创建一艘飞船
ship = Ship(screen) ##实例化飞船类
"""开始游戏主循环,主要涵盖两部分:事件循环+屏幕管理"""
while True:
#事件循环:监听事件+响应事件
for event in pygame.event.get(): ##for+event.get()持续监听
if event.type == pygame.QUIT: ##if+event.type分类响应
sys.exit() ##sys.exit()退出程序
#屏幕管理:每次循环时都重绘屏幕
screen.fill(ai_settings.bg_color) ##访问实例对象属性作为参数设置窗口背景
ship.blitme() ##实例方法的调用--根据指定位置绘制图像
pygame.display.flip() ##不断更新屏幕
"""初始化游戏,并开始主循环"""
run_game()
接下来,响应用户的键盘操作左右控制飞船的移动。
在添加新的代码之前对之前的代码进行重构-创建game_function
game_function.py :存储大量游戏运行的函数,简化主程序alien_invation.py的复杂度。
import sys ##主程序文件不需要sys
import pygame ##主程序文件依然需要pygame的调用
def check_events():
"""重构alien_invasion.py中的事件循环:监听事件+响应事件"""
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
def update_screen(ai_settings,screen,ship):
"""重构alien_invasion.py中的屏幕管理:对应代码中存在的输入接口为screen,ship,aisettings,向内传递其属性及方法"""
#每次循环都重绘屏幕
screen.fill(ai_settings.bg_color) #背景
ship.blitme() #飞船
#让最近绘制的屏幕可见
pygame.display.flip() #之前是绘制,display.flip()是显示
alien_invation.py更新:引用game_function中的函数
import pygame ##pygame库:制作游戏和多媒体
from settings import Settings ##从外部文件导入类
from ship import Ship
import game_function as gf
"""主程序"""
def run_game():
"""初始化游戏并创建一个屏幕对象"""
pygame.init() ##初始化背景设置
ai_settings = Settings() ##实例化对象
screen = pygame.display.set_mode((ai_settings.screen_width,ai_settings.screen_height)) ##访问实例对象属性作为参数调用display创建screen窗口,在pygame中用surface代表屏幕的一部分用于显示游戏元素,display.set_mode()返回的是整个游戏窗口的元素
pygame.display.set_caption("Alien Invasion") #display.set_caption()用于设置窗口title
#创建一艘飞船
ship = Ship(screen) ##实例化飞船类
"""开始游戏主循环,主要涵盖两部分:事件循环+屏幕管理"""
while True:
"""函数间的相互调用:注意变量的作用域,以及方法的使用权限"""
gf.check_events()
gf.update_screen(ai_settings,screen,ship)
"""初始化游戏,并开始主循环"""
run_game()
重构完成后具体实现飞船根据键盘的响应左右移动的功能:
首先,左右移动属于对称的关系,可以先专注于一个方向进行设计,我们不妨选择向右。
按键的响应需求可以概括为从按下右键开始到松开右键结束过程当中,图像位置连续移动。
一方面:每个键盘鼠标动作都会在pygame当中注册一个事件,并通过pygame.event.get()获得,按下按键时注册KEYDOWN,松开按键时注册KEYUP,按下右键时注册K_RIGHT,按下左键时注册K_LEFTK_LEFT。因此我们可以在game_function.py 的check_events()方法中根据循环中的判断语句,持续监测键盘并作出对应动作请求;
另一方面:图像的位置移动需要改变ship对象的位置属性,因此我们需要在Ship类中添加一个位置属性更新的方法,还需要一个打开位置更新的开关。
小体会:方法就是行为(函数),属性就是状态(变量)。属性如果从功能的角度看分两种:开关型、数值型,开关型可以表达状态的切换,数值型可以表达状态的程度。形参的选择蕴含两层意思:你想改变谁?你想被谁改变?
game_function.py中的check_events()更新:根据键盘动作向ship类发出位置更新动作请求
import sys ##主程序文件不需要sys
import pygame ##主程序文件依然需要pygame的调用
def check_events(ship): #之所以调用ship形参,是因为要通过内部方法来改变ship对象的位置移动开关属性
"""重构alien_invasion.py中的事件循环:监听事件+响应事件"""
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
#以下实现在右键按下到松开期间ship的右移开关打开--moving_right==True
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
def update_screen(ai_settings,screen,ship):
"""重构alien_invasion.py中的屏幕管理:对应代码中存在的输入接口为screen,ship,aisettings,向内传递其属性及方法"""
#每次循环都重绘屏幕
screen.fill(ai_settings.bg_color) #背景
ship.blitme() #飞船
#让最近绘制的屏幕可见
pygame.display.flip() #之前是绘制,display.flip()是显示
ship.py更新:检测check_events()位置更新信号,添加位置更新方法
import pygame
class Ship():
def __init__(self,screen):
"""初始化飞船,并设置其初始位置"""
self.screen = screen ##Ship类需要获取屏幕对象作为输入参数,screen由alien_invasion.py中display.set_mode()方法获取,并且将形参screen的值存储在一个实例属性当中,以便在blitme()中使用他
#加载飞船图像并获取其外接矩形
self.image = pygame.image.load('images/ship.bmp') ##image.load()加载图像返回飞船surface存储于实例属性self.image
self.rect = self.image.get_rect() ##get_rect()方法获取surface的rect属性(控制矩形的方式控制游戏元素)
self.screen_rect = screen.get_rect()
#将每艘飞船放在屏幕底部中央:rect对象的处理可以通过设置矩形四角和中心的x和y坐标来制定位置。边缘对齐:top\bottom\left\right属性,水平和垂直:x\y(矩形左上角)属性
self.rect.centerx = self.screen_rect.centerx
self.rect.bottom = self.screen_rect.bottom
#移动开关
self.moving_right =False #初始化标志位False,在check_events()中对状态进行改变
def update(self):
"""根据移动开关调整飞船的位置"""
if self.moving_right:
self.rect.centerx += 1
def blitme(self):
"""在指定位置绘制飞船"""
self.screen.blit(self.image,self.rect) ##使screen、image、rect属性都指向Ship的实例对象self
alien_invation.py更新:调用函数实现位置更新的请求和动作
import pygame ##pygame库:制作游戏和多媒体
from settings import Settings ##从外部文件导入类
from ship import Ship
import game_function as gf
"""主程序"""
def run_game():
"""初始化游戏并创建一个屏幕对象"""
pygame.init() ##初始化背景设置
ai_settings = Settings() ##实例化对象
screen = pygame.display.set_mode((ai_settings.screen_width,ai_settings.screen_height)) ##访问实例对象属性作为参数调用display创建screen窗口,在pygame中用surface代表屏幕的一部分用于显示游戏元素,display.set_mode()返回的是整个游戏窗口的元素
pygame.display.set_caption("Alien Invasion") #display.set_caption()用于设置窗口title
#创建一艘飞船
ship = Ship(screen) ##实例化飞船类
"""开始游戏主循环,主要涵盖两部分:事件循环+屏幕管理"""
while True:
"""函数间的相互调用:注意变量的作用域,以及方法的使用权限"""
gf.check_events(ship) ##检测键盘鼠标事件并发出动作请求
ship.update() ##更新飞船位置属性,注意过程先后,只有先将位置更新后才能更新图像显示
gf.update_screen(ai_settings,screen,ship) ##更新图像
"""初始化游戏,并开始主循环"""
run_game()
最后,我们对飞船的移动提出更高的要求:左右移动、调整速度、限制活动范围
setting.py更新:设置速度属性,ship类的位置更新方法中调用该属性来更改飞船移动速度
class Settings():
"""存储所有设置的类"""
def __init__(self):
"""初始化游戏的设置"""
#屏幕设置:实例属性定义与赋值
self.screen_width = 1000
self.screen_height = 600
self.bg_color = (230,230,230)
#飞船设置
self.ship_speed_factor = 1.5
ship.py更新:位置更新增加预设条件的判断以此限制活动范围,并调用速度属性改变移动速度。根据右向添加左向
import pygame
class Ship():
def __init__(self,ai_settings,screen):
"""初始化飞船,并设置其初始位置"""
self.screen = screen ##Ship类需要获取屏幕对象作为输入参数,screen由alien_invasion.py中display.set_mode()方法获取
#加载飞船图像并获取其外接矩形
self.image = pygame.image.load('images/ship.bmp') ##image.load()加载图像返回飞船surface存储于实例属性self.image
self.rect = self.image.get_rect() ##get_rect()方法获取surface的rect属性(控制矩形的方式控制游戏元素)
self.screen_rect = screen.get_rect()
self.ai_settings = ai_settings ##获取ai_settings值并存储在属性当中,便于方法的调用
self.center = float(self.rect.centerx) ##rect.centerx只能存储整数值,因此做下数值变换,center用于存储飞船的中心位置属性
#将每艘飞船放在屏幕底部中央:rect对象的处理可以通过设置矩形四角和中心的x和y坐标来制定位置。边缘对齐:top\bottom\left\right属性,水平和垂直:x\y(矩形左上角)属性
self.rect.centerx = self.screen_rect.centerx
self.rect.bottom = self.screen_rect.bottom
#移动开关
self.moving_right = False #初始化标志位False,在check_events()中对状态进行改变
self.moving_left = False
def update(self):
"""根据移动开关调整飞船的位置"""
#更新飞船的center值,而不是rect值
if self.moving_right and self.rect.right < self.screen_rect.right:
self.center += self.ai_settings.ship_speed_factor
if self.moving_left and self.rect.left > 0:
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) ##使screen、image、rect属性都指向Ship的实例对象self
alien_invation.py更新:函数的调用引入新的参数
import pygame ##pygame库:制作游戏和多媒体
from settings import Settings ##从外部文件导入类
from ship import Ship
import game_function as gf
"""主程序"""
def run_game():
"""初始化游戏并创建一个屏幕对象"""
pygame.init() ##初始化背景设置
ai_settings = Settings() ##实例化对象
screen = pygame.display.set_mode((ai_settings.screen_width,ai_settings.screen_height)) ##访问实例对象属性作为参数调用display创建screen窗口,在pygame中用surface代表屏幕的一部分用于显示游戏元素,display.set_mode()返回的是整个游戏窗口的元素
pygame.display.set_caption("Alien Invasion") #display.set_caption()用于设置窗口title
#创建一艘飞船
ship = Ship(ai_settings,screen) ##实例化飞船类
"""开始游戏主循环,主要涵盖两部分:事件循环+屏幕管理"""
while True:
"""函数间的相互调用:注意变量的作用域,以及方法的使用权限"""
gf.check_events(ship) ##检测键盘鼠标事件并发出动作请求
ship.update() ##更新飞船位置属性,注意过程先后,只有先将位置更新后才能更新图像显示
gf.update_screen(ai_settings,screen,ship) ##更新图像
"""初始化游戏,并开始主循环"""
run_game()
此时随着游戏开发的继续进行,check_events()函数将会变得越来越长,因此对其进行重构
game_functions.py更新:键盘操作及响应分类打包
import sys ##主程序文件不需要sys
import pygame ##主程序文件依然需要pygame的调用
def check_keydown_events(event,ship):
"""响应按键"""
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): #之所以调用ship形参,是因为要通过内部方法来改变ship对象的位置移动开关属性
"""重构alien_invasion.py中的事件循环:监听事件+响应事件"""
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
#以下实现在右键按下到松开期间ship的右移开关打开--moving_right==True
elif event.type == pygame.KEYDOWN:
check_keydown_events(event,ship)
elif event.type == pygame.KEYUP:
check_keyup_events(event,ship)
def update_screen(ai_settings,screen,ship):
"""重构alien_invasion.py中的屏幕管理:对应代码中存在的输入接口为screen,ship,aisettings,向内传递其属性及方法"""
#每次循环都重绘屏幕
screen.fill(ai_settings.bg_color) #背景
ship.blitme() #飞船
#让最近绘制的屏幕可见
pygame.display.flip() #之前是绘制,display.flip()是显示
小体会:
1,软件开发是一个不断迭代的过程,往下推进的过程是一个“创建新的,处理旧的”的过程。
创建新的:
方法的集合?-game_functions.py中集成了大量自由函数
属性的集合?-setting.py中集成了大量系统设置的属性
方法+属性的集合?-ship.py集成了飞船相关的状态和动作
受谁影响?想影响谁?
【类属性和方法要经过实例化才能实传递】
-内部属性(self.)和外部属性(形参.)的相互影响
-类方法(实例化对象)和一般函数(直接导入)的相互调用
处理旧的:
函数重构-game_functions.py中对键盘事件分类打包
函数引用-主程序中的函数调用刷新
2,软件开发追求一定的灵活性:更改方便且可剪裁
好,下面进行第二步。
第二步要实现飞船的射击功能,具体来说:玩家按下空格键发射小矩形子弹,子弹向上穿行抵达屏幕后消失
有了上面添加飞船的经验,我们类比到子弹功能的设计当中去
首先我们针对子弹创建一个新的bullet类,包含了相关的大部分属性和方法。在创建的过程中要一直审查:他会影响谁,又会被谁影响
bullet.py:用于管理子弹的大部分状态和行为
import pygame
from pygame.sprite import Sprite
class Bullet(Sprite): #bullet继承sprite类使用精灵将元素编组,进而可以同时操控元素
"""一个对飞船发射的子弹进行管理的类"""
def __init__(self,ai_settings,screen,ship): ##子弹实例对象为self,他的属性或方法要接受ai_settings实例、screen实例、ship实例属性或方法的改变
#在飞船的位置创建一个子弹对象
super(Bullet,self).__init__()
self.screen = screen ##子弹在屏幕中的绘制需要调用pygame.rect.draw()方法,因此要将形参保存在实例属性当中并在方法中进行调用
#在(0,0)创建一个表示子弹的矩形,再设置正确的位置
self.rect = pygame.Rect(0,0,ai_settings.bullet_width,ai_settings.bullet_height) #子弹不是基于图像得到的,使用pygame.Rect()方法创建时,提供矩形左上角的xy坐标和长宽度
self.rect.centerx = ship.rect.centerx
self.rect.top = ship.rect.top
#存储用小数表示的子弹位置
self.y = float(self.rect.y)
self.color = ai_settings.bullet_color
self.speed_factor = ai_settings.bullet_speed_factor
def update(self):
"""向上移动子弹"""
self.y -= self.speed_factor ##rect只接受整数类型,要想通过小数进行微调需要采用中间传递的方式进行
self.rect.y = self.y
def draw_bullet(self):
"""在屏幕上绘制子弹"""
pygame.draw.rect(self.screen,self.color,self.rect)
settings.py更新:添加子弹的属性设置
class Settings():
"""存储所有设置的类"""
def __init__(self):
"""初始化游戏的设置"""
#屏幕设置:实例属性定义与赋值
self.screen_width = 1000
self.screen_height = 600
self.bg_color = (230,230,230)
#飞船设置
self.ship_speed_factor = 1.5
#子弹设置
self.bullet_speed_factor = 15
self.bullet_width = 3
self.bullet_height = 15
self.bullet_color = (60,60,60)
然后,在创建新的的同时考虑处理旧的
函数重构:
game_functions.py更新:按键检测和屏幕更新的部分要考虑子弹的引入
import sys ##主程序文件不需要sys
import pygame ##主程序文件依然需要pygame的调用
from bullet import Bullet ##从外部导入类
def check_keydown_events(event,ai_settings,screen,ship,bullets): ##增加了对空格键的检测和响应,在原来的基础上需要引入的参数是bullets及其相关的实例ai_settiings、screen
"""响应按键"""
if event.key == pygame.K_RIGHT:
ship.moving_right = True
elif event.key == pygame.K_LEFT:
ship.moving_left = True
elif event.key == pygame.K_SPACE:
#创建一颗子弹,并将其加入到编组bullets当中
new_bullet = Bullet(ai_settings,screen,ship)
bullets.add(new_bullet)
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(ai_settings,screen,ship,bullets): ##之所以调用ship形参,是因为要通过内部方法来改变ship对象的位置移动开关属性
"""重构alien_invasion.py中的事件循环:监听事件+响应事件"""
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
#以下实现在右键按下到松开期间ship的右移开关打开--moving_right==True
elif event.type == pygame.KEYDOWN:
check_keydown_events(event,ai_settings,screen,ship, bullets) ##刷新函数调用
elif event.type == pygame.KEYUP:
check_keyup_events(event,ship)
def update_screen(ai_settings,screen,ship,bullets): ##增加了子弹元素的显示,在原来基础上需引入bullets实例
"""重构alien_invasion.py中的屏幕管理:对应代码中存在的输入接口为screen,ship,aisettings,向内传递其属性及方法"""
#每次循环都重绘屏幕
screen.fill(ai_settings.bg_color) #背景
#在飞船和外星人后面重绘所有子弹
for bullet in bullets.sprites (): ##方法bullets.sprite()返回一个列表,包含编组中的所有精灵
bullet.draw_bullet()
ship.blitme() ##飞船
#让最近绘制的屏幕可见
pygame.display.flip() ##之前是绘制,display.flip()是显示
函数调用;
alien_invation.py更新:对于主要函数的调用要进行刷新
import pygame ##pygame库:制作游戏和多媒体
from settings import Settings ##从外部文件导入类
from ship import Ship
import game_function as gf
from pygame.sprite import Group ##引入编组(group)
"""主程序"""
def run_game():
"""初始化游戏并创建一个屏幕对象"""
pygame.init() ##初始化背景设置
ai_settings = Settings() ##实例化对象
screen = pygame.display.set_mode((ai_settings.screen_width,ai_settings.screen_height)) ##访问实例对象属性作为参数调用display创建screen窗口,在pygame中用surface代表屏幕的一部分用于显示游戏元素,display.set_mode()返回的是整个游戏窗口的元素
pygame.display.set_caption("Alien Invasion") #display.set_caption()用于设置窗口title
#创建一艘飞船
ship = Ship(ai_settings,screen) ##实例化飞船类
#创建一个用于存储子弹的编组
bullets = Group() ##创建Group实例,将有效子弹存储于编组中,便于管理所有发射的子弹
"""开始游戏主循环,主要涵盖两部分:事件循环+屏幕管理"""
while True:
"""函数间的相互调用:注意变量的作用域,以及方法的使用权限"""
gf.check_events(ai_settings,screen,ship,bullets) ##子弹对象的引入会影响到键盘事件的检测
ship.update() ##更新飞船位置属性,注意过程先后,只有先将位置更新后才能更新图像显示
bullets.update() ##对编组调用update()之后,编组会自动对其中每个精灵调用update()
gf.update_screen(ai_settings,screen,ship,bullets) ##更新图像,子弹对象的引入会影响屏幕的绘图显示,注意定义和使用函数的参数的顺序要保持一致
"""初始化游戏,并开始主循环"""
run_game()
最后我们对于子弹的功能实现进一步优化:
清除消失的子弹:
game_functions.py更新:当子弹到达屏幕顶端时将其从编组中移除并刷新子弹位置
def update_bullets(bullets):
"""更新子弹位置,并删除消失的子弹"""
#更新子弹的位置
bullets.update() ##对编组调用update()之后,编组会自动对其中每个精灵调用update()
#删除消失的子弹
for bullet in bullets.copy(): ##为了实现在循环中更改bullets,要遍历的对象应该是其副本
if bullet.rect.bottom <= 0:
bullets.remove(bullet)
alien_invation.py更新:更改相应的调用
"""开始游戏主循环,主要涵盖两部分:事件循环+屏幕管理"""
while True:
"""函数间的相互调用:注意变量的作用域,以及方法的使用权限"""
gf.check_events(ai_settings,screen,ship,bullets) ##子弹对象的引入会影响到键盘事件的检测
ship.update() ##更新飞船位置属性,注意过程先后,只有先将位置更新后才能更新图像显示
gf.update_bullets(bullets) ##调用子弹更新函数
gf.update_screen(ai_settings,screen,ship,bullets) ##更新图像,子弹对象的引入会影响屏幕的绘图显示
子弹的数量要进行限制:
settings.py更新:设置同时存在的子弹数量
#子弹设置
self.bullet_speed_factor = 15
self.bullet_width = 3
self.bullet_height = 15
self.bullet_color = (60,60,60)
self.bullets_allowed = 3
game_functions.py更新:生成新子弹时增加预设条件,并同时进行重构使空格直接调用函数,精简check_keydown_events()代码
def check_keydown_events(event,ai_settings,screen,ship,bullets): ##增加了对空格键的检测和响应,在原来的基础上需要引入的参数是bullets及其相关的ai_settiings、screen
"""响应按键"""
if event.key == pygame.K_RIGHT:
ship.moving_right = True
elif event.key == pygame.K_LEFT:
ship.moving_left = True
elif event.key == pygame.K_SPACE:
fire_bullet(ai_settings, screen, ship, bullets)
def fire_bullet(ai_settings,screen,ship,bullets):
"""如果没有达到限制就发射一发子弹"""
if len(bullets) <= ai_settings.bullets_allowed:
# 创建一颗子弹,并将其加入到编组bullets当中
new_bullet = Bullet(ai_settings, screen, ship)
bullets.add(new_bullet)
至此你实现了在游戏的主界面中通过左右键操控飞船左右移动,按下空格之后发射子弹的想法。如果你有耐心进行到这一步,说明你一定体会到了面向对象编程的魅力,之后我们会对功能进一步丰富。如果你是初学者,希望你能坚持下去,这个项目很大程度上能够成为你独立实现项目的垫脚石!
阶段三:创建外星人
外星人的创建我们类比于飞船的创建过程也分为两步进行
第一步创建一群外星人:
首先我们创建外星人类
alien.py:用于管理单个外星人的状态和行为
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)
单个外星人的类管理完成后,我们着手如何将其复制为外星人群体
将需求分解如下:
1.每行容纳多少个外星人?2.总共容纳多少行外星人?3.创建群体?4.显示群体?
1.每行个数:屏幕宽度减两个飞船宽度再除以二倍的飞船宽度
2.最终行数:屏幕高度减飞船高度减3倍外星人高度再除以二倍飞船高度
3.创建群体:内部循环创建一行,外部循环创建多行,创建的过程中将重复使用的基本单元抽象成函数进行调用
4.显示群体:屏幕更新函数中添加外星人的绘制,主程序中创建alien实例并进行创建和绘制外星人函数的调用
game_functions.py更新:增加1234函数
import sys ##主程序文件不需要sys
import pygame ##主程序文件依然需要pygame的调用
from bullet import Bullet ##从外部导入类
from alien import Alien ##alien实例的创建在这里已经完成,不必在主程序中重复创建
def get_number_aliens_x(ai_settings,alien_width): ##ai_settings传递屏幕宽度属性,alien_width由alien实例对象属性传递
"""计算每行容纳多少外星人"""
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 get_number_rows(ai_settings,ship_height,alien_height): ##ai_settings传递屏幕高度属性,ship_height,alien_height由ship和alien实例对象属性传递
"""计算可容纳多少行外星人"""
available_space_y = ai_settings.screen_height - ship_height - 3 * alien_height
number_rows = int(available_space_y / (2 * alien_height))
return number_rows
def create_alien(ai_settings,screen,aliens,alien_number,row_number): ##被create_fleet所调用,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_height = alien.rect.height
alien.y = alien_height + 2 * alien.rect.height * row_number
alien.rect.y = alien.y
aliens.add(alien)
def create_fleet(ai_settings,screen,ship,aliens): ##aliens由Group()创建实例传递
"""创建外星人群"""
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):
create_alien(ai_settings,screen,aliens,alien_number,row_number)
def check_keydown_events(event,ai_settings,screen,ship,bullets): ##增加了对空格键的检测和响应,在原来的基础上需要引入的参数是bullets及其相关的ai_settiings、screen
"""响应按键"""
if event.key == pygame.K_RIGHT:
ship.moving_right = True
elif event.key == pygame.K_LEFT:
ship.moving_left = True
elif event.key == pygame.K_SPACE:
fire_bullet(ai_settings, screen, ship, bullets)
def fire_bullet(ai_settings,screen,ship,bullets):
"""如果没有达到限制就发射一发子弹"""
if len(bullets) <= ai_settings.bullets_allowed:
# 创建一颗子弹,并将其加入到编组bullets当中
new_bullet = Bullet(ai_settings, screen, ship)
bullets.add(new_bullet)
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(ai_settings,screen,ship,bullets): ##之所以调用ship形参,是因为要通过内部方法来改变ship对象的位置移动开关属性
"""重构alien_invasion.py中的事件循环:监听事件+响应事件"""
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
#以下实现在右键按下到松开期间ship的右移开关打开--moving_right==True
elif event.type == pygame.KEYDOWN:
check_keydown_events(event,ai_settings,screen,ship, bullets) ##刷新函数调用
elif event.type == pygame.KEYUP:
check_keyup_events(event,ship)
def update_screen(ai_settings,screen,ship,aliens,bullets): ##增加了子弹元素的显示,在原来基础上需引入bullets实例
"""重构alien_invasion.py中的屏幕管理:对应代码中存在的输入接口为screen,ship,aisettings,向内传递其属性及方法"""
#每次循环都重绘屏幕
screen.fill(ai_settings.bg_color) #背景
#在飞船和外星人后面重绘所有子弹
for bullet in bullets.sprites (): ##方法bullets.sprite()返回一个列表,包含编组中的所有精灵
bullet.draw_bullet()
ship.blitme() ##飞船
aliens.draw(screen)
#让最近绘制的屏幕可见
pygame.display.flip() ##之前是绘制,display.flip()是显示
def update_bullets(bullets):
"""更新子弹位置,并删除消失的子弹"""
#更新子弹的位置
bullets.update() ##对编组调用update()之后,编组会自动对其中每个精灵调用update()
#删除消失的子弹
for bullet in bullets.copy(): ##为了实现在循环中更改bullets,要遍历的对象应该是其副本
if bullet.rect.bottom <= 0:
bullets.remove(bullet)
alien_invation.py更新:创建实例进行调用
import pygame ##pygame库:制作游戏和多媒体
from settings import Settings ##从外部文件导入类
from ship import Ship
import game_function as gf
from pygame.sprite import Group ##引入编组(group)
"""主程序"""
def run_game():
"""初始化游戏并创建一个屏幕对象"""
pygame.init() ##初始化背景设置
ai_settings = Settings() ##实例化对象
screen = pygame.display.set_mode((ai_settings.screen_width,ai_settings.screen_height)) ##访问实例对象属性作为参数调用display创建screen窗口,在pygame中用surface代表屏幕的一部分用于显示游戏元素,display.set_mode()返回的是整个游戏窗口的元素
pygame.display.set_caption("Alien Invasion") #display.set_caption()用于设置窗口title
#创建一艘飞船
ship = Ship(ai_settings,screen) ##实例化飞船类
#创建一个用于存储子弹的编组
bullets = Group() ##创建Group实例,将有效子弹存储于编组中,便于管理所有发射的子弹
#创建外星人群
aliens = Group()
gf.create_fleet(ai_settings, screen, ship, aliens)
"""开始游戏主循环,主要涵盖两部分:事件循环+屏幕管理"""
while True:
"""函数间的相互调用:注意变量的作用域,以及方法的使用权限"""
gf.check_events(ai_settings,screen,ship,bullets) ##子弹对象的引入会影响到键盘事件的检测
ship.update() ##更新飞船位置属性,注意过程先后,只有先将位置更新后才能更新图像显示
gf.update_bullets(bullets) ##调用子弹更新函数
gf.update_screen(ai_settings,screen,ship,aliens,bullets) ##更新图像,子弹对象的引入会影响屏幕的绘图显示
"""初始化游戏,并开始主循环"""
run_game()
第二步我们要实现的是让外星人群移动
具体来说:外星人群整体右移遇到边缘下移后左移,往复循环
将需求分解如下:
1.时刻检测是否有外星人碰到屏幕边缘
2.当有外星人碰到边缘,整体下移向相反方向移动
3.没有外星人碰到边缘,整体保持原水平方向移动
我们把单个外星人相关的方法和属性存放在alien类当中,把外星人群的相关方法和属性存放在game_functions中,并且后者对前者存在调用关系,因此我们做出如下安排:
在alien类中对外星人个体实现左右移及持续监测触碰屏幕边缘方法
在game_functions中调用alien监测方法持续监测,若有触发则整体下移并反向移动
小体会:软件功能的实现第一步要从需求进行分解,分解完成后要确定安放他们的实现域及各个域间的交互关系。
首先我们在alien类中添加相应方法:
alien.py更新:
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)
def check_edge(self):
"""如果外星人位于屏幕边缘,则返回True,满足改变移动方向的预设条件"""
screen_rect = self.screen.get_rect() ##screen实例属性的传递体现在这里
if self.rect.right >= screen_rect.right:
return True
elif self.rect.left <= screen_rect.left:
return True
def update(self): ##ai_settings实例属性的传递体现在这里
"""向左或向右移动外星人"""
self.x += self.ai_settings.alien_speed_factor * self.ai_settings.fleet_direction ##在这里方向的设置采用了正负1来进行表示,而没有采用文本值的方式进行。类似于双关的作用,既能用来表示方向的切换,用能直接用来参与坐标的大小变化过程当中。
self.rect.x = self.x
然后我们在game_functions中添加相应的方法:
game_function.py更新:
import sys ##主程序文件不需要sys
import pygame ##主程序文件依然需要pygame的调用
from bullet import Bullet ##从外部导入类
from alien import Alien ##alien实例的创建在这里已经完成,不必在主程序中重复创建
def get_number_aliens_x(ai_settings,alien_width): ##ai_settings传递屏幕宽度属性
"""计算每行容纳多少外星人"""
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 get_number_rows(ai_settings,ship_height,alien_height): ##ai_settings传递屏幕高度属性
"""计算可容纳多少行外星人"""
available_space_y = ai_settings.screen_height - ship_height - 3 * alien_height
number_rows = int(available_space_y / (2 * alien_height))
return number_rows
def create_alien(ai_settings,screen,aliens,alien_number,row_number): ##被create_fleet所调用
"""创建第一个外星人,并将其放在当前行"""
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_height = alien.rect.height
alien.y = alien_height + 2 * alien.rect.height * row_number
alien.rect.y = alien.y
aliens.add(alien)
def create_fleet(ai_settings,screen,ship,aliens): ##aliens由Group()创建实例传递
"""创建外星人群"""
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):
create_alien(ai_settings,screen,aliens,alien_number,row_number)
def update_aliens(ai_settings,aliens):
"""检查是否有外星人触碰边缘,并更新整群外星人的位置"""
check_fleet_edges(ai_settings,aliens) ##检测触碰并作出措施-下移反向移
aliens.update()
def check_fleet_edges(ai_settings,aliens):
"""检测触碰并作出措施"""
for alien in aliens.sprites():
if alien.check_edge():
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 check_keydown_events(event,ai_settings,screen,ship,bullets): ##增加了对空格键的检测和响应,在原来的基础上需要引入的参数是bullets及其相关的ai_settiings、screen
"""响应按键"""
if event.key == pygame.K_RIGHT:
ship.moving_right = True
elif event.key == pygame.K_LEFT:
ship.moving_left = True
elif event.key == pygame.K_SPACE:
fire_bullet(ai_settings, screen, ship, bullets)
def fire_bullet(ai_settings,screen,ship,bullets):
"""如果没有达到限制就发射一发子弹"""
if len(bullets) <= ai_settings.bullets_allowed:
# 创建一颗子弹,并将其加入到编组bullets当中
new_bullet = Bullet(ai_settings, screen, ship)
bullets.add(new_bullet)
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(ai_settings,screen,ship,bullets): ##之所以调用ship形参,是因为要通过内部方法来改变ship对象的位置移动开关属性
"""重构alien_invasion.py中的事件循环:监听事件+响应事件"""
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
#以下实现在右键按下到松开期间ship的右移开关打开--moving_right==True
elif event.type == pygame.KEYDOWN:
check_keydown_events(event,ai_settings,screen,ship, bullets) ##刷新函数调用
elif event.type == pygame.KEYUP:
check_keyup_events(event,ship)
def update_screen(ai_settings,screen,ship,aliens,bullets): ##增加了子弹元素的显示,在原来基础上需引入bullets实例
"""重构alien_invasion.py中的屏幕管理:对应代码中存在的输入接口为screen,ship,aisettings,向内传递其属性及方法"""
#每次循环都重绘屏幕
screen.fill(ai_settings.bg_color) #背景
#在飞船和外星人后面重绘所有子弹
for bullet in bullets.sprites (): ##方法bullets.sprite()返回一个列表,包含编组中的所有精灵
bullet.draw_bullet()
ship.blitme() ##飞船
aliens.draw(screen)
#让最近绘制的屏幕可见
pygame.display.flip() ##之前是绘制,display.flip()是显示
def update_bullets(bullets):
"""更新子弹位置,并删除消失的子弹"""
#更新子弹的位置
bullets.update() ##对编组调用update()之后,编组会自动对其中每个精灵调用update()
#删除消失的子弹
for bullet in bullets.copy(): ##为了实现在循环中更改bullets,要遍历的对象应该是其副本
if bullet.rect.bottom <= 0:
bullets.remove(bullet)
settings.py更新:添加外星人属性值
#外星人设置
self.alien_speed_factor = 1
self.fleet_drop_speed = 10
self.fleet_direction = 1 ##1表示向右增加坐标值,-1表示向左减小坐标值
alien_invation.py更新:更新函数调用
import pygame ##pygame库:制作游戏和多媒体
from settings import Settings ##从外部文件导入类
from ship import Ship
import game_function as gf
from pygame.sprite import Group ##引入编组(group)
"""主程序"""
def run_game():
"""初始化游戏并创建一个屏幕对象"""
pygame.init() ##初始化背景设置
ai_settings = Settings() ##实例化对象
screen = pygame.display.set_mode((ai_settings.screen_width,ai_settings.screen_height)) ##访问实例对象属性作为参数调用display创建screen窗口,在pygame中用surface代表屏幕的一部分用于显示游戏元素,display.set_mode()返回的是整个游戏窗口的元素
pygame.display.set_caption("Alien Invasion") #display.set_caption()用于设置窗口title
#创建一艘飞船
ship = Ship(ai_settings,screen) ##实例化飞船类
#创建一个用于存储子弹的编组
bullets = Group() ##创建Group实例,将有效子弹存储于编组中,便于管理所有发射的子弹
#创建外星人群
aliens = Group()
gf.create_fleet(ai_settings, screen, ship, aliens)
"""开始游戏主循环,主要涵盖两部分:事件循环+屏幕管理"""
while True:
"""函数间的相互调用:注意变量的作用域,以及方法的使用权限"""
gf.check_events(ai_settings,screen,ship,bullets) ##子弹对象的引入会影响到键盘事件的检测
ship.update() ##更新飞船位置属性,注意过程先后,只有先将位置更新后才能更新图像显示
gf.update_bullets(bullets) ##调用子弹更新函数
gf.update_aliens(ai_settings,aliens) ##更新外星人群的更新调用
gf.update_screen(ai_settings,screen,ship,aliens,bullets) ##更新图像,子弹对象的引入会影响屏幕的绘图显示
"""初始化游戏,并开始主循环"""
run_game()
如果你已经坚持到了这里,那么恭喜你整个游戏项目即将接近尾声,这一阶段希望你能体会到我们创造了软件从需求(关键词:分解)到架构(关键词:交互)再到详细设计(功能实现)的小缩影,这是一种很实用的项目开发思维
阶段四:战斗过程
阶段五:游戏结束
一张图梳理面向对象(半成品):