前言
作为一个编程小白选手,经过一段时间的基础语法学习,终于迎来了首个项目学习,从一步步的模仿到理解到每个步骤的思路想法,每个类之间的联系与构造,还有些编程中的一些小细节,如何养成一个编程的好习惯。
分析
外星人入侵作为一个入门小游戏项目,整体来说思路是比较简单,代码行也不多,总计就是500+行,除去一些缩进空行,就没有多少了。当我们去做一个项目的时候,无论是难的还是简单的,都是需要进行一个分析,这样我们实际去写的时候,思路就会清晰很多,把一个项目的功能拆分出来,再各个实现,最后串联起来。
言归正传,这个项目主要分为9个模块,9个模块及每个模块的作用如下:
-
alien.py →class_Alien 主要是用来放绘制外星人的图像及位置信息
-
bullet.py→class_Bullet 主要是用来放置绘制飞船发射子弹图像及位置信息
-
button.py→class_Button 主要是用来放置绘制游戏开始按钮的图像及位置信息
-
game_stats.py→ class Game_stats 主要是游戏中的数据参数,如得分,游戏等级等等
-
scoreboard.py→ class Scoreboard 主要是用来放置游戏中各个计分板的图像绘制和位置信息
-
settings.py→class Settings 主要是用来放置游戏中初始化参数,像屏幕大小颜色等等
-
ship.py→class Ship 主要用来放置绘制飞船的图像及位置信息
-
alien_invasion.py 主要是用来放置运行游戏的主方法
-
game_functions.py 主要是用来放置游戏功能的实现,如屏幕的图像更新,飞船的移动,飞船如何发射子弹等等,基本功能代码都会集中在这个包里面
步骤如下:
创建游戏窗口→添加飞船图像→控制飞船移动→添加子弹图像及移动→
添加一个外星人图像→添加一群外星人图像及移动→检测外星人图像与子弹碰撞,与飞船碰撞和碰到屏幕底端的情况及反应→添加游戏开始按钮图像及反应→添加计分板图像:包括玩家得分,历史最高得分,难度级别显示,玩家剩余飞船数量
实现
创建游戏窗口
首先来创建游戏窗口,创建一个alien_invasion.py的包,导入pygame库(这个项目是基于pygame的库下完成的),创建一个def run_game()的主方法,pygame.init()初始化背景参数,创建一个screen的对象pygame.display.set_mode()函数传入我们设置好的宽高参数就可以创建窗口,用(1200(宽),650(高))一个元组的实参传入(具体参数可以根据自身屏幕大小设置),再用函数pygame.display.set_caption()传入str可以命名窗口,将显示在窗口的左上角。我们创建的screen对象是一个surface(面板),还有接下来我们创建的飞船,子弹,都是一个surface(面板)set_mode函数返回的就是一个surface。
接下来就是游戏需要的主循环,用来不断更新的屏幕的图像和玩家行为,直至玩家退出游戏,我们用while True来循环,并在里面设置一个检测pygame.QUIT的事件跳出循环,避免造成死循环。用函数pygame.event.get()可以得到玩家鼠标和按键响应事件,再用一个for in循环得到每个事件,并进行判断是否有pygame.QUIT事件,如果有就用sys.exit()函数来退出游戏跳出循环(这个时候我们就需要导入sys,在代码最上面import sys)最后再用pygame.display.flip()绘制的图像显示出来,这个是放于run_game()最后的地方。代码如下:
此时运行run_game(),我们将得到一个黑色的窗口:
接下来我们给窗口上色,用的是RGB值,用红色,绿色,蓝色组成,每个值取值范围为0到255,创建一个screen_color赋值(230,230,230),我们需要不断更新这个颜色,所以放在while循环里,用fill()函数,传入我们的screen_color实参,在我们创建的窗口屏幕screen上,所以代码如下:
def run_game():
pygame.init()
screen = pygame.display.set_mode((1200,650))
pygame.display.set_caption("Alien Invasion")
screen_color = (230,230,230)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
screen.fill(screen_color)
pygame.display.flip()
运行得到画面:
建议可以熟悉下pycharm的快捷键,这样会更快的提高,例如当你不熟悉一个函数的使用时候,可以选中之后按Ctrl + B或者Ctrl + 鼠标点击函数可以直接跳转到函数的声明,会帮助你更加理解函数的使用,变量也是可以这样操作,当你看完之后可以按Alt + 左键跳回就非常放便
快捷键使用
setting模块(存放游戏设置参数)
接下来可以创建一个setting.py的模块,里面创建一个Settting类,用于存放游戏参数,这样以来就方便引用,而且当你需要修改的时候不需要全部都去修改,而是只在Setting的类中修改即可,然后回到alien_invasion.py中导入Setting创建一个Setting的对象ai_settings,之后只需用对象.变量名即可引用,整理结果如下:
class Setting():
def __init__(self):
#初始化游戏的设置
#屏幕参数设置
self.screen_width = 1200
self.screen_height = 650
self.screen_color = (230,230,230)
import pygame
import sys
from setting import Setting
def run_game():
pygame.init()
ai_settings = Setting()
screen = pygame.display.set_mode(
(ai_settings.screen_width,ai_settings.button_height))
pygame.display.set_caption("Alien Invasion")
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
screen.fill(ai_settings.screen_color)
pygame.display.flip()
run_game()
创建飞船
接下来我们就要创建飞船了,使用位图.bmp文件,需要在项目文件夹中创建一个Images的文件夹,将图片放入文件夹中,再创建一个ship.py的模块,里面有一个class Ship类并进行初始化,def init(self,screen)这里需要传入我们创建的窗口screen,因为我们的飞船是出现在这个窗口屏幕上的,然后需要读取我们飞船的图片,创建一个self.image接收,用pygame.image.loda(Images’/ship.bmp)前面为文件夹名,后面会文件名,再用get_rect()函数来得到这个图片的矩形rect对象,这个rect对象会有一个坐标位置信息由四个参数组成(left,top,width,height),width和height又表示rect矩形的宽和高
图像都是从原点(0,0)创建的
- top和left可以理解成图像距离原点的坐标
- bottom是rect矩形下边缘距离x轴的距离,也可理解成y坐标
- width就是rect图像的宽度,height是rect图像的高度
*center是图像的中心,然后分出centerx(中心在x轴上的坐标)
centery(中心在y轴上的坐标) - right 可以理解成rect矩形图像右边缘x轴坐标
所以我们导入ship.bmp,创建ship_rect接受用函数get_rect()得到飞船rect矩形图像的坐标位置信息。要将飞船放于screen屏幕的底部中间,所以要将飞船的bottom等于屏幕的bottom,在将飞船的centerx等于screen屏幕的centerx,之后在创建一个绘制飞船图像的方法def blitme(),方法里面用blit()在屏幕上screen绘制飞船,传入飞船的图像self.image和飞船图像的self.rect,代码如下:
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)
然后在alien_invasion中导入Ship的类,创建一个Ship类对象,需要传入screen参数,最后在循环中使用ship.blitme()方法绘制飞船图像
ship == Ship(screen)
whlie True:
...
ship.blitme()
控制飞船
接下来就是控制飞船移动的,还是在遍历event.get()里用if语句判断,如果检测到事件是按下键盘事件,然后在用if语句判断,按下键盘事件的按钮是不是对应的方向键,根据上面的坐标图,向左键则是ship.rect.centerx -=1,向右则是ship.rect.centerx += 1;当玩家持续按下左右键的时候,就需要判断按下按键事件和松开按键事件,先在Ship类里面的init方法里添加两个变量self.moving_right 和self.moving_left并赋值False,在创建一个方法def update()用if语句判断如果为True的反应
def update():
if moving_right:
self.rect.centerx += 1
if moving_left:
self.rect.centerx -= 1
再回到for循环判断,按下和松开的响应:
while True:
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
接下来我们需要限制飞船的运行范围,从上面的坐标图来看,我们可以判断飞船图像的right如果小于screen窗口的right,那就是还可以继续向右移动,如果飞船的left是大于0的话,那就是还没有到屏幕坐边缘,还可以继续向左移动,所以我们可以在ship.update里面加多一个判断条件,用and连接,只有同时满足这两个条件,飞船才能继续移动,因为要用到飞船的rect和screen的rect,所以需要在update方法里面传入ship和screen作为参数,代码如下:
def update(self):
if moving_right and self.rect.right < self.screen_rect.right:
self.rect.centerx += 1
if moving_left and self.rect.left > 0:
self.rect.centerx -= 1
如果觉得飞船移动的太慢的话,可以设置一个飞船速度的变量,建议赋值浮点值,这样可以更细致的控制飞船速度,所以我们把+1-1换成+-我们嘎刚刚设置的飞船速度变量,但是rect是整数,整数不能和浮点值加减,所以先把rect.centerx转换为浮点值float赋给变量center,最后加减完之后再赋值给center赋值给rect.centerx
def __init__(self):
...
self.ship_centerx = float(self.rect.centerx)
def ship_update(self):
#更新飞船位置
if self.ship_moving_left and self.rect.left > 0:
self.ship_centerx -= self.ai_settings.ship_speed_factor
if self.ship_moving_right and self.rect.right < self.screen_rect.right:
self.ship_centerx += self.ai_settings.ship_speed_factor
self.rect.centerx = self.ship_centerx
注意这里飞船速度的变量是在Setting里创建的,所以我们在Ship里面要导入Setting类,并在init里面传入ai_settings对象,ship对象里面我们也要传入相应的实参ai_settings
class Ship():
def __init__(self,ai_settings,screen):
...
self.ai_settings = ai_settings
ship = Ship(ai_settings,screen)
game_functions的作用(整理代码,让代码更容易管理和解读)
while True里的循环现在代码比较多,也为了更好理解代码,现在创建一个game_functions的模块,用于存放功能代码,在里面创建def check_events(按键和鼠标响应事件)和screen_update(窗口更新,把需要在窗口上更新的图像归在里面),check_events里面还可以在分出响应按下和松开的两个方法。最后在alien_invasion.py中导入game_function(用import game_function as gs 导入,并简化为gs,通过gs来调用就可以了)如下:
import sys
import pygame
def check_event(ai_settings,screen,ship):
#响应按键和鼠标事件
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
check_keydown(event,ai_settings,screen,ship)
elif event.type == pygame.KEYUP:
check_keyup(event,ship)
def check_keydown(event,ai_settings,screen,ship):
#响应按下
if event.key == pygame.K_RIGHT:
ship.ship_moving_right = True
elif event.key == pygame.K_LEFT:
ship.ship_moving_left = True
def check_keyup(event,ship):
#响应松开
if event.key == pygame.K_RIGHT:
ship.ship_moving_right = False
if event.key == pygame.K_LEFT:
ship.ship_moving_left = False
def screen_update(ai_settings,ship,screen):
#更新屏幕图像
screen.fill(ai_settings.screen_color)
ship.blitme()
pygame.display.flip()
import game_functions as gs
...
while True:
#监听鼠标和按键事件
gs.check_event(ai_settings,screen,ship)
ship.ship_update()
gs.screen_update(ai_settings,ship,screen)
子弹的创建
接下来创建飞船需要的子弹,在bullet模块里创建,过程跟上面的飞船差不多,但是需要通过继承Sprite类使用精灵将游戏相关的元素编组并操作编组中的所有元素,同时子弹是没有bmp位图的,所有需要用pygame.Rect函数来绘制,需要传输x,y轴上的坐标和子弹的宽高,同时在Settging里面创建子弹需要的宽高和颜色,子弹的速度,我们在原点创建,之后在把子弹设置子弹飞船的中心,这样看起来就是子弹从飞船中射出,所以子弹的矩形图像就是:
from pygame.sprite import Sprite
class Bullet(Sprite):
def __init__(self,ai_settings,screen,ship):
'''
python2.7中需要super(self,Bullet).init()这种写法,python3也可以这样写,
但是也可省略,用下面这种写法
'''
super().init()
self.rect = pygame.Rect(0,0,ai_setting.bullet_width,ai_setting.bullet_height)
子弹的更新方法,因为是从飞船射出,所以是在屏幕从下往上的,所以self.rect.y -= 子弹速度,可以借鉴飞船移动速度用float值。
子弹的绘制用pygame.draw.rect(Surface,color,rect)或者Surface.fill(color,rect)都可以
在alien_invasion中创建一个子弹编组bullets = Group()用于存放每一个创建的子弹。在while循环里面通过编组bullets调用update方法可以对编组中每个元素调用update方法。
子弹的射击
子弹的射击同样通过按键响应事件判断,如果玩家按下的是空格键,就创建一个子弹对象,并将子弹加入子弹编组里面,在通过遍历子弹编组在屏幕上绘制出每个子弹,bullets.sprites()可以返回一个列表。
def screen_update(ai_settings,screen,ship,bullets):
...
for bullet in bullets.sprites():
bullet.draw_bullet()
子弹管理
会发现发射的子弹虽然到达了屏幕顶端之后消失,其实并没有消失,子弹位置还在不断往Y轴负数区域更新,这样会到导致子弹越来越多,可以用过bullets.copy()复制一个bullets,遍历判断子弹的位置,如果子弹的bottom位置超过屏幕也就是0,那就使用Group.remove()函数,将超过屏幕的子弹删除,同时我们限制子弹的数量,通过len(bullets)得到子弹编组的数量,用if语句判断如果小于我们设定的子弹数量就创建子弹
外星人创建
外星人的创建就和飞船的创建比较相似,因为都是有bmp位图的,就不需要我们自己创建,只需要将外星人的图片读取进来就可以了,唯一不同的就是外星人是一群的,所以我们要借鉴创建子弹的方法,继承Spirte类,创建一个外星人列表,同时要计算窗口可以容纳多少外星人,在使用for循环嵌套创建外星人群,最后在screen_update中用编组调用draw(surface)方法绘制
每个外星人最初都是从(0,0)的地方创建,所以我们将外星人的宽度设置成外星人的左边距,将外星人的高度设置为外星人的上边距
计算屏幕可以容纳多少个外星人,每行容纳的外星人空间可以通过窗口的宽度 - 两个外星人宽度(预设屏幕两端的空余空间),将其设置为available_space_x
同时两个外星人之间需要有一定空间,所以设定一个外星人实际需要的空间是外星人宽度的两倍,所以得出每行可以容纳的外星人数量为:
number_aliens_x = int(available_space_x/(2*alien_width))
我们在game_functions.py创建一个alien_fleet()方法,用于创建外星人群:
def alien_fleet():
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)
接下来计算屏幕可以放下几行外星人,存放外星人的空间 = 窗口高度 - 第一行外星人的上边距 - 外星人之间的间隔 - 留给玩家反应的距离 - 飞船的距离,将前面三种参数都设置成外星人高度,即为3*外星人高度 - 飞船高度,屏幕存放外星人的空间为: available_space_y = ai_settings.screen_height - 3 * alien_height - ship._height
可以存放的行数为:number_rows = int(available_space_y / (2 * alien_height)),结合起来代码:
def alien_fleet():
...
for row_number in range(number_rows):
for alien_number in range((number_aliens_x):
...
alien.rect.y = alien.rect.height + 2 * alien.rect.height * row_number
aliens.add(alien)
最后整理下代码,将其整理四个方法,def get_number_aliens_x();
def get_number_rows();def create_alien();def create_fleet(),分别完成获取外星人每行存放多少个外星人,获取可以存放几行外星人,for循环里面创造外星人及创建外星人群
外星人群的移动
设置外星人群的平行移动速度alien_speed_factor = 1,下降速度alien_drop_speed = 10,alien_direction = 1为左右移动(1为向右移动,-1为向左移动),通过外星人群的left和right属性判断是否有到达窗口边缘,如果有,那么外星人群alien.rect.y += 下降速度(根据坐标图可以看出下降y轴坐标是加大的),在alien类创建一个check_edges()方法和一个update方法
def check_edges(self):
if self.rect.right >= screen.rect.right:
return True
if self.rect.left <= 0:
return True
def updates(self):
self.x += self.settings.alien_speed_factor * self.settings.alien_fleet_direction
self.rect.x = self.x
在game_functions.py中创建check_fleet_edges()方法,根据check_edges()返回的结果,在进行一次if判断,如果为True,就for循环遍历一次aliens.sprites列表,将每个外星人都下移,并将alien_direciton *= -1,这样外星人的左右方向就会改变
检测子弹和外星人接触
在game_functions.py里按创建一个def check_alien_bullet_collisions()方法,使用函数pygame.sprite.groupcollide(groupa, groupb, dokilla, dokillb, collided=None),接受子弹编组和外星人编组,和两个True,是检测两个编组中的精灵的rect是有否重叠,两个True是删除碰撞的两个精灵。当所有外星人都被打完,就可以用len(aliens)判断,如果为等于0,就使用bullets.empy()函数清空编组然后调用create_fleet()创造新的外星人群
def bullet_update(bullets,ai_settings,screen,ship,aliens,stats,sb):
#更新子弹位置
bullets.update()
#删除子弹
for bullet in bullets.copy():
if bullet.rect.bottom <= 0:
bullets.remove(bullet)
check_bullets_aliens_collisions(ai_settings,screen,ship,bullets,aliens,stats,sb)
def check_bullets_aliens_collisions(ai_settings,screen,ship,bullets,aliens,stats,sb):
#检查子弹是否有击中外星人
#如果有击中,就删除对应的子弹和外星人
collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
接下来可以检测外星人与飞船碰撞的情况,函数pygame.sprite.spritecolideany(sprite, group, collided=None),接受一个精灵和一个编组,检测是否有重叠,并且返回一个布尔值,根据这个布尔值来进行if判断,飞船数量-1,子弹清空,外星人清空,重新创建外星人群,同时将飞船重新放回屏幕下端中央,并将这些功能整理成ship_hit函数,飞船的数量,需要创建一个专门管理游戏数据的类,命名game_stats.py→ class Game_stats。
class Setting():
def __init__(self):
...
self.ship_allow = 3
class Game_stats():
def __init__(self,ai_settings):
#初始化游戏数据统计
self.ai_settings = ai_settings
self.ships_break = self.ai_settings.ship_allow
from time import Sleep
def aliens_update(ai_settings,screen,ship,stats,aliens,sb,bullets):
#检测是否有外星人碰到边缘,并更新外星人位置
check_fleet_edges(ai_settings,aliens)
check_aliens_bottom(ai_settings,screen,ship,stats,aliens,bullets)
aliens.update()
if pygame.sprite.spritecollideany(ship,aliens):
#当外星人和飞船碰撞反应
ship_hit(ai_settings,screen,ship,stats,aliens,sb,bullets)
def ship_hit(ai_settings,screen,ship,stats,aliens,sb,bullets):
if stats.ships_break > 0:
#减少一只飞船的数量
stats.ships_break -= 1
#更新飞船记分牌
sb.prep_ships()
#清空飞船列表和子弹列表
aliens.empty()
bullets.empty()
#重新创造一组外星人,并把飞船放到屏幕下端中央
creat_fleet(ai_settings,screen,ship,aliens)
ship.ship_centerx = ship.screen_rect.centerx
#停止0.5秒,以便让玩家知道飞船撞到,或者外星人碰到底端
sleep(0.5)
遍历aliens编组,如果有外星人到达屏幕底端,也需要调用ship_hit方法
def check_aliens_bottom(ai_settings,screen,ship,stats,aliens,bullets):
#检查外星人是否有碰到底端及其反应
screen_rect = screen.get_rect()
for alien in aliens.sprites():
if alien.rect.bottom >= screen_rect.bottom:
ship_hit(ai_settings,screen,ship,stats,aliens,bullets)
break
def aliens_update(ai_settings,screen,ship,stats,aliens,sb,bullets):
...
check_aliens_bottom(ai_settings,screen,ship,stats,aliens,bullets)
游戏开始的按钮
首先在games_stats.py中设置游戏状态为True,可以ship_hit中加一个if,else的判断,如果飞船数量大于0就执行刚刚的代码,否则就游戏状态设置成为False,再回到alien_invasion.py中的while循环开头加上if判断,如果游戏状态为True才执行。
接下来就需要来绘制play按钮了,按钮的绘制与子弹一样,只不过要加上字,就需要用到pygame提供的将文字熏染成图像的函数,首先用函数pygame.font.SysFont(None,48)用一个变量接收,None位置传入的是字体,None使用默认字体,48为大小。再用变量调用render(self, text, antialias, color, background=None),text需要的文字,antlalia为抗锯齿选项,接收True和False,让文字更平滑,后面两个参数就是文字颜色和背景颜色。并且让文字的放于按钮的中间,最后创建draw_button方法,分别把背景和文字的图像绘制出来即可。
class Button():
def __init__(self,ai_settings,screen,msg):
#初始化按钮参数
self.screen = screen
self.ai_settings = ai_settings
self.screen_rect = screen.get_rect()
#设置按钮颜色及文本颜色、文本字体
self.color = ai_settings.button_color
self.msg_color = ai_settings.text_color
self.font = pygame.font.SysFont(None,48)
#设置按钮位置
self.rect = pygame.Rect(0,0,ai_settings.button_width,ai_settings.button_height)
self.rect.center = self.screen_rect.center
#设置文本位置
self.msg_image = self.font.render(msg,True,self.msg_color,self.color)
self.msg_image_rect = self.msg_image.get_rect()
self.msg_image_rect.center = self.rect.center
def draw_button(self):
#绘制按钮
self.screen.fill(self.color,self.rect)
self.screen.blit(self.msg_image,self.msg_image_rect)
检测鼠标点击play按钮
先把游戏状态设置为False,用if语句判断,如果为False,就绘制按钮。
在检测按键鼠标事件的if判断加上的鼠标判断。pygame.mouse.get_pos()函数可以检测鼠标点击,返回一个(x,y)的坐标。用函数
collidepoint(self,x,y),检测是否在rect矩形内有碰撞,传入参数x,y坐标,返回结果是布尔值。如果为Ture,就让游戏状态为True。
当结束之后,play的按钮会出现,这是会发现的情况是继续游戏,而不是重新开始,如果我们还需要加上一下重置游戏的设置,除了让参数回到初始化,还需要将子弹清空,外星人清空,并创建新的外星人群,并让飞船回到原来的位置。
def check_play_button(ai_settings,screen,ship,bullets,aliens,stats,sb,play_button,mouse_x,mouse_y):
button_clickde = play_button.rect.collidepoint(mouse_x,mouse_y)
#检测鼠标点击行为是否在paly按钮区域及反应
if button_clickde and not stats.game_state:
ai_settings.game_initialization()
stats.game_state = True
stats.reset_stats()
aliens.empty()
bullets.empty()
#创造外星人群并将飞船放于屏幕下端中央
creat_fleet(ai_settings, screen, ship, aliens)
ship.ship_centerx = ship.screen_rect.centerx
游戏开始之后会发现,现在点击按钮的位置,还是会重置游戏,所以在判断加上游戏状态为False的时候才重置,而且让鼠标光标在游戏开始之后隐藏起来,pygame.mouse.set_visible(False),并在游戏结束的时候将设置为True显示出来。
游戏难度的升级
可以设置一个倍数变量,在判断外星人数量为0的if判断里让原来的参数乘与这个倍数,同时也要在游戏开始判断里重置参数,达到重新开始的效果
游戏计分板
需要记录玩家游戏得分,最高得分记录,难度的等级,还有玩家的剩余飞船数量图标,需要设置初始得分为0,每个外星人的分值,初始难度为1。
- 还记得之前检测子弹和外星人碰撞的函数groupcollide,返回的是一个字典存储在collisions里,我们遍历collisions字典中的value值,就可以得到外星人被子弹碰撞的数量,将它乘以我们设置好的外星人的分值,并将分值用render函数渲染成图片绘制出来,同时做一个if判断,如果当前得分高过历史最高得分,就将它赋值给历史最高得分变量,注意的是这个历史最高得分变量不会因为游戏重新开始而重置,同样用render函数绘制出来。
- 难度等级可放于外星人群数量等于0的if判断中,如果为True,等级变量+1,同样用render函数绘制。
- 创建一个ships的编组,Ship类就需要继承Sprite,将玩家飞船数量遍历出来按照外星人群的方法绘制,并加到ships编组中。在飞船与外星人碰撞的if判断中绘制。
def check_bullets_aliens_collisions(ai_settings,screen,ship,bullets,aliens,stats,sb):
#检查子弹是否有击中外星人
#如果有击中,就删除对应的子弹和外星人
collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
if collisions:
#击中外星人玩家得分
for alines in collisions.values():
stats.score += ai_settings.alien_points * len(alines)
sb.prep_point()
#记录玩家最高得分
if stats.score > stats.hight_score:
stats.hight_score = stats.score
sb.prep_hightscore()
if len(aliens) == 0:
#删除现有的子弹并生成新的外星人
bullets.empty()
#升级难度级别
stats.level += 1
#更新难度级别记分牌,增加游戏难度,并创建新外星人群
sb.prep_level()
...
def ship_hit(ai_settings,screen,ship,stats,aliens,sb,bullets):
if stats.ships_break > 0:
#减少一只飞船的数量
stats.ships_break -= 1
#更新飞船记分牌
sb.prep_ships()
...
class Scoreboard():
def __init__(self,ai_settings,screen,stats):
...
#记分牌图像
self.prep_point()
self.prep_hightscore()
self.prep_level()
self.prep_ships()
def prep_point(self):
# rounded_score = int(round(self.stats.score,-1))
# self.score = str(self.stats.score)
#玩家得分转化为渲染图像
score ='{:,}'.format(self.stats.score)
self.score_image = self.font.render(score,True,self.point_color,self.ai_settings.screen_color)
#将得分记分牌放于屏幕左上角
self.score_rect = self.score_image.get_rect()
self.score_rect.right = self.screen_rect.right - 20
self.score_rect.top = 20
def prep_hightscore(self):
#将玩家最高得分转化为渲染的图像
hight_score = '{:,}'.format(self.stats.hight_score)
self.hight_score_image = self.font.render(hight_score,True,self.point_color,self.ai_settings.screen_color)
#将最高法记分牌放于屏幕上端中央
self.hight_score_rect = self.hight_score_image.get_rect()
self.hight_score_rect.centerx = self.screen_rect.centerx
self.hight_score_rect.top = 20
def prep_level(self):
#将等级转换为渲染的图像
self.level_str = str(self.stats.level)
self.level_image = self.font.render(self.level_str,True,self.point_color,self.ai_settings.screen_color)
#将等级记分牌放于得分牌下发
self.level_rect = self.level_image.get_rect()
self.level_rect.right = self.screen_rect.right - 20
self.level_rect.top = self.score_rect.bottom + 5
def prep_ships(self):
#显示飞船数量
self.ships = Group()
for ships_number in range(self.stats.ships_break):
ship = Ship(self.ai_settings,self.screen)
ship.rect.x = 10 + ships_number * ship.rect.width
ship.rect.top = self.screen_rect.top + 10
self.ships.add(ship)
def draw_score(self):
#绘制记分牌图像
self.screen.blit(self.score_image,self.score_rect)
self.screen.blit(self.hight_score_image, self.hight_score_rect)
self.screen.blit(self.level_image,self.level_rect)
self.ships.draw(self.screen)
易错点
小游戏项目完成了,现在就可以运行体验了。
整个编程下来,感觉容易出错导致报错的,往往都是一些非常细节的东西,例如:
- 调用类方法或者类对象的调用错误,或者传入的参数没有对不上,出现漏掉或者位置错误,当然也可以指定参数赋值,但是过于繁琐,建议写方法之前,可以先不用传参数,写完之后在将方法中需要的参数填上在复制粘贴统一即可。
- 代码的整洁性,代码块不易过长,会导致不好理解,而且修改的时候也不好修改,要养成写完一个功能代码之后整理一下,将功能细分成更多小功能代码块,这个需要日后多次项目经历培养形成。
*注释,对每个代码块进行注释,让阅读者更快的了解代码的作用性,注释的规范,可放于代码的上方或者下方,用#号键注释,最好注释之后可以空行,会更美观。