Python入门项目:外星人入侵

前言

作为一个编程小白选手,经过一段时间的基础语法学习,终于迎来了首个项目学习,从一步步的模仿到理解到每个步骤的思路想法,每个类之间的联系与构造,还有些编程中的一些小细节,如何养成一个编程的好习惯。

分析

外星人入侵作为一个入门小游戏项目,整体来说思路是比较简单,代码行也不多,总计就是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 主要是用来放置游戏功能的实现,如屏幕的图像更新,飞船的移动,飞船如何发射子弹等等,基本功能代码都会集中在这个包里面
    image.png
步骤如下:

创建游戏窗口→添加飞船图像→控制飞船移动→添加子弹图像及移动→
添加一个外星人图像→添加一群外星人图像及移动→检测外星人图像与子弹碰撞,与飞船碰撞和碰到屏幕底端的情况及反应→添加游戏开始按钮图像及反应→添加计分板图像:包括玩家得分,历史最高得分,难度级别显示,玩家剩余飞船数量

实现

创建游戏窗口

首先来创建游戏窗口,创建一个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()最后的地方。代码如下:


image.png

此时运行run_game(),我们将得到一个黑色的窗口:


image.png

接下来我们给窗口上色,用的是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()

运行得到画面:

image.png

建议可以熟悉下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矩形的宽和高

image.png

图像都是从原点(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)
易错点

小游戏项目完成了,现在就可以运行体验了。
整个编程下来,感觉容易出错导致报错的,往往都是一些非常细节的东西,例如:

  • 调用类方法或者类对象的调用错误,或者传入的参数没有对不上,出现漏掉或者位置错误,当然也可以指定参数赋值,但是过于繁琐,建议写方法之前,可以先不用传参数,写完之后在将方法中需要的参数填上在复制粘贴统一即可。
  • 代码的整洁性,代码块不易过长,会导致不好理解,而且修改的时候也不好修改,要养成写完一个功能代码之后整理一下,将功能细分成更多小功能代码块,这个需要日后多次项目经历培养形成。
    *注释,对每个代码块进行注释,让阅读者更快的了解代码的作用性,注释的规范,可放于代码的上方或者下方,用#号键注释,最好注释之后可以空行,会更美观。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容