Alien invasion外星人入侵[Pygame上]

Alien invasion外星人入侵

武装飞船

1、规划项目

开发大型项目时,做好规划后再动手编写项目很重要。规划可确保你不偏离轨道,从而提高项目成功的可能性。

开发出来的效果

在游戏《外星人入侵》中,玩家控制着一艘最初出现在屏幕底部中央的飞船。玩家可以使用箭头键左右移动飞船,还可以使用空格键进行射击。

游戏开始时,一群外星人出现在天空中,他们在屏幕中向下移动。玩家的任务是射杀这些外星人。

2、安装Pygame

使用pip安装python包

# 安装pip window和osx下载方式不同
python get-pip.py

# 使用 pip 下载pygame 
python -m pip install --user pygame  


3、开始游戏项目

创建Pygame窗口以及响应用户输入

创建空的Pygame的窗口,编写游戏基本结构

  • 初始化背景设置
  • 游戏主体
  • 监听事件等

alien_invasion.py

import sys
import pygame

def run_game():
    # 初始化游戏并创建一个屏幕对象
    pygame.init()
    screen = pygame.display.set_mode((1200, 800))
    pygame.display.set_caption("Alien Invasion")

    # 开始游戏的主循环
    while True:

        # 监视键盘和鼠标事件
        for event in pygame.event.get():
            if event.type == pygame.quit():
                sys.exit()

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


run_game()
设置背景色

Pygame默认创建黑色屏幕


# 设置背景色 浅灰色
bg_color = (230, 230, 230)
    
# 每次循环时都重绘制屏幕
screen.fill(bg_color)
创建设置类

每次增加游戏新功能时,通常也将引入一些新设置,避免在项目中到处添加设置,项目增大时修改游戏外观更容易。要修改游戏,只需要修改settings.py的一些值,无需查找散步在文件中的不同设置。

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

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("Alien Invasion")
    
    # 开始游戏的主循环
    while True:

        # 监视键盘和鼠标事件
        for event in pygame.event.get():
            if event.type == pygame.quit():
                sys.exit()

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

调用pygame.init(), 再创建一个Settings实例,将其存储在变量ai_settings中

4、添加飞船图像

加载一幅图像,再使用Pygame方法blit()绘制它。

可以在此网站找 https://pixabay.com/

游戏中几乎可以使用任何图像文件,单使用位图(.bmp) 文件最为简单,Pygame默认加载位图。

选择图像时尽可能选择背景透明的图像。

[图片上传失败...(image-8cb399-1676467847121)]

创建Ship类

将其显示到屏幕上,它负责管理飞船的大部分行为

ship.py

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)

__init__() 接受两个参数,引用self和screen,screen指定了将飞船绘制到什么地方

pygame.image.load() 加载图像 返回一个表示飞船的surface,存储到self.image中

self.rect.centerx 飞船中心的x坐标

self.rect.bottom 飞船下边缘的y坐标

blitme方法将图像绘制到屏幕上

在屏幕上绘制飞船

alien_invasion.py

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("Alien Invasion")

    # 创建一艘飞船
    ship = Ship(screen)

    # 开始游戏的主循环
    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()

启动报错

"C:/Users/ss/code/PycharmProjects/crash/part2/alieninvasion/alien_invasion.py", line 36, in run_game
    ship.blitme()
  File "C:\Users\ss\code\PycharmProjects\crash\part2\alieninvasion\ship.py", line 29, in blitme
    self.screen_rect.blit(self.image, self.rect)
AttributeError: 'pygame.Rect' object has no attribute 'blit'

Process finished with exit code 1

AttributeError: 'pygame.Rect' object has no attribute 'blit'

self.screen_rectscreen.get_rect() 不能加载到bilt

错误原因代码引用错,参考其他博主也是如此错的 是screen不是screen_rect

self.screen.blit(self.image, self.rect)

修改之后便可正常启动项目

5、重构: 模块game_functions

在大型项目中,经常需要在添加新代码前重构既有的代码

重构旨在简化既有代码的结构,使其更容易扩展。

当前项目使用模块game_function,避免alien_invasion太长,使逻辑更容易理解。

game_function.py

隔离事件管理循环,将事件管理与游戏的其他方法分离

import pygame
import sys


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



def update_screen(ai_setting, screen, ship):
    """更新屏幕上的图像,并切换到新屏幕"""
    # 每次循环时都重绘制屏幕
    screen.fill(ai_settings.bg_color)
    ship.blitme()

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

调用game_function.py 方法

import sys
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("Alien Invasion")

    # 创建一艘飞船
    ship = Ship(screen)

    # 开始游戏的主循环
    while True:

        # 监视键盘和鼠标事件
        gf.check_event()

        gf.update_screen(ai_settings, screen, ship)


run_game()

6、驾驶飞船

来让玩家能够左右移动飞船

在用户按左或者右箭头键时做出响应

在函数check_events() 中,指定事件类型,每次按下都被注册未一个KEYDOWN事件。

检测到KEYDOWN事件时,我们要检查按下的是否是特定的键。左、右等,就可以控制屏幕图像移动

响应按键

game_function.py

按下右箭头键,就增大飞船的rect.conterx值,飞船向右移动

def check_event(ship):
    """响应按键和鼠标事件"""
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            sys.exit()
        elif event.type == pygame.KEYDOWN:
            if event.type == pygame.K_RIGHT:
                # 向右移动飞船
                ship.rect.centex += 1

alien_invasion.py

# 开始游戏的主循环
while True:
    # 监视键盘和鼠标事件
    gf.check_event(ship)

    gf.update_screen(ai_settings, screen, ship)

允许不断移动

玩家按住右箭头不妨时,我们希望飞船不断地向右移动,知道玩家松开为止

KEYDOWN和KEYUOP事件 键盘的按下与松开

我们用标志来实现持续移动,飞船不动时,标志moving_right将为False。

飞船的属性Ship添加一个moving_right的属性和一个为update()的方法

ship.py

class Ship():

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

    
        # 移动标志 鼠标按下事件
        self.moving_right = False

    def update(self):
        """根据移动标志调整飞船的位置"""
        if self.moving_right:
            self.rect.centerx += 1

     

alien_invasion.py

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("Alien Invasion")

    # 创建一艘飞船
    ship = Ship(screen)

    # 开始游戏的主循环
    while True:
        # 监视键盘和鼠标事件
        gf.check_event(ship)
        ship.update()
        gf.update_screen(ai_settings, screen, ship)


run_game()

当前程序,运行起来之后,按住右箭头飞船将不断向右移动,知道松开为止。(也可以移动到屏幕外表,哈哈)

左右移动

同样的方式添加向作移动的逻辑

使用moving_left

def check_event(ship):
    """响应按键和鼠标事件"""
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            sys.exit()
        elif event.type == pygame.KEYDOWN:
            print("键盘事件: " + str(event.key))
            if event.key == pygame.K_RIGHT:
                # 向右移动飞船
                # ship.rect.centex += 1
                ship.moving_right = True
            if event.key == pygame.K_LEFT:
                ship.moving_left = True

        elif event.type == pygame.KEYUP:
            if event.key == pygame.K_RIGHT:
                ship.moving_right = False
            if event.key == pygame.K_LEFT:
                ship.moving_left = False

class Ship():

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

        # 移动标志 鼠标按下事件
        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
调整飞船的速度

当前执行while循环时,飞船最多移动1像素,但在Settings类中添加属性ship_speed_factor, 用于控制飞船的速度。

在settings.py 添加属性

class Settings():
    """储存《外星人入侵》的所有设置类"""

    def __init__(self):
        """初始化游戏的设置"""
        # 屏幕设置
        self.screen_width = 1000
        self.screen_height = 700
        # 灰 230, 230, 230 蓝 R:0 G:191 B:243
        self.bg_color = (0, 191, 243)
        self.bg_image = 'images/stars.bmp'

        # 飞船的设置
        self.ship_speed_factor = 1.5
class Ship():

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

        # 在飞船的属性center中储存小数值
        self.center = float(self.rect.centerx)

        # 移动标志 鼠标按下事件
        self.moving_right = False
        self.moving_left = False

    def update(self):
        """根据移动标志调整飞船的位置"""
        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.centerx 更新rect对象
        self.rect.centerx = self.center

    def blitme(self):
        """在指定位置绘制飞船"""
        self.screen.blit(self.image, self.rect)
限制飞船的活动范围

当前飞船无线左右移动,只要不松开键盘,可以移动到宇宙之外

def update(self):
    """根据移动标志调整飞船的位置"""

    # 限制飞船活动范围
    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

向右移动不能超过屏幕最大宽度,向左移动要大于1

重构check_events()

把响应键按下和响应键松开独立出来

def check_event(ship):
    """响应按键和鼠标事件"""
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            sys.exit()
        elif event.type == pygame.KEYDOWN:
            print("键盘事件: " + str(event.key))
            check_keydown_event(event, ship)
        elif event.type == pygame.KEYUP:
            check_keyup_event(event, ship)


def check_keydown_event(event, ship):
    """键盘按下"""
    if event.key == pygame.K_RIGHT:
        # 向右移动飞船
        # ship.rect.centex += 1
        ship.moving_right = True
    if event.key == pygame.K_LEFT:
        ship.moving_left = True


def check_keyup_event(event, ship):
    """键盘松开"""
    if event.key == pygame.K_RIGHT:
        ship.moving_right = False
    if event.key == pygame.K_LEFT:
        ship.moving_left = False

7、简单回顾

4个py文件,作别作用

alien_invasion.py

主文件,整个游戏都要用到,包含游戏主题循环,调用check_enets(), ship.update()的while循环

其他文件也是直接或者间接引入

settings.py 包含Settings类,初始化控制游戏外观和飞船速度的属性

game_function.py 包含一系列函数,游戏大部分工作都是他们完成的。

检测事件check_enevts

8、射击

玩家按空格键时发射子弹(小矩形)。子弹将在屏幕中向上穿行,抵达屏幕上边缘后消失。

添加子弹设置

settings.py

# 子弹的设置
self.bullet_speed_factor = 1
self.bullet_width = 3
self.bullet_height = 15
self.bullet_color = 60, 60, 60

创建宽3像素,高15像素的深灰色子弹,子弹的速度比飞船稍低

创建Bullet类

bullet.py

import pygame
from pygame.sprite import Sprite


class Bullet(Sprite):
    """一个对飞船发射的子弹进行管理的类"""

    def __init__(self, ai_settings, screen, ship):
        """在飞船所处的位置创键一个子弹"""
        super(Bullet, self).__init__()
        
        self.screen = screen

        # 在(0,0)处创建一个表示子弹的矩形,再设置正确的位置
        self.rect = pygame.Rect(0, 0, ai_settings.bullet_width, ai_settings.bullet_height)
        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_

    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)

Bullet类继承Sprite类,通过使用精灵将游戏元素编组,同时操作编组中的所有元素。调用super().__init()来继承Sprite。

创建的子弹属性rect,并非基于图像,使用pagame.Rect()类从空白开始创建一个矩形。创建实例时,必须提供矩形左上角的x,y坐标和矩形的宽度高度,在(0,0)处创建这个矩形,再将其移到正确的位置

子弹的centerx设置为飞船的rect.centerx。子弹从飞船顶部射出,子弹的rect的top属性为飞船的rect的top属性,这样子弹看起来就像是从飞船中射出来的

方法update()管理子弹的位置。放射出去后,子弹在屏幕中向上移动,y坐标将不断减少,来更新子弹的位置,self.y中减去self.speed_factor的值。子弹发射后,x坐标始终不变,子弹沿直线垂直地往上穿行。

将子弹存储到编组中

创建编组group,用于储存所有有效的子弹,以便于能后管理发射出去的所有字段。

编组在屏幕上绘制子弹,以及更新每颗子弹的位置

from pygame.sprite import Group


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("Alien Invasion")

    # 创建一艘飞船
    ship = Ship(ai_settings, screen)

    # 创建储存子弹的编组
    bullets = Group()

    # 开始游戏的主循环
    while True:
        # 监视键盘和鼠标事件
        gf.check_event(ai_settings, screen, ship, bullets)
        ship.update()
        bullets.update()
        gf.update_screen(ai_settings, screen, ship, bullets)
开火

修改check_keydown_events() 玩家按空格键时发射一颗子弹,松开空格键时什么都不会发生。

def check_event(ai_settings, screen, ship, bullets):
    """响应按键和鼠标事件"""
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            sys.exit()
        elif event.type == pygame.KEYDOWN:
            print("键盘事件: " + str(event.key))
            check_keydown_event(event, ai_settings, screen, ship, bullets)
        elif event.type == pygame.KEYUP:
            check_keyup_event(event, ship)


def check_keydown_event(event, ai_settings, screen, ship, bullets):
    """键盘按下"""
    if event.key == pygame.K_RIGHT:
        # 向右移动飞船
        # ship.rect.centex += 1
        ship.moving_right = True
    if event.key == pygame.K_LEFT:
        ship.moving_left = True

    if event.key == pygame.K_SPACE:
        # 创建一颗子弹, 并将其加入到编组bullets中
        new_bullet = Bullet(ai_settings, screen, ship)
        bullets.add(new_bullet)
        
def update_screen(ai_settings, screen, ship, bullets):
    """更新屏幕上的图像,并切换到新屏幕"""
    # 每次循环时都重绘制屏幕
    screen.fill(ai_settings.bg_color)

    # 添加背景
    bg_image = pygame.image.load(ai_settings.bg_image).convert()
    screen.blit(bg_image, (0, 0))

    # 在飞船和外星人后面重新绘制子弹
    for bullet in bullets.sprites():
        bullet.draw_bullet()        

启动报错,查看Bullet类是否有初始化,调用super()继续Sprite

 File "C:\Users\shenshuaihu\lib\site-packages\pygame\sprite.py", line 160, in add_internal
    self.__g[group] = 0
AttributeError: 'Bullet' object has no attribute '_Sprite__g'

<img src="C:\Users\shenshuaihu\AppData\Roaming\Typora\typora-user-images\image-20220914144536965.png" alt="image-20220914144536965" style="zoom:50%;" />

删除以消失的子弹

当子弹到达屏幕顶端后消失,不仅仅是Pygame不能在屏幕绘制他们,子弹实际依然存在,y坐标为负数,越来越少。他们继续消耗内存

如不删除的话,游戏的无谓工作越来越多,进而变得越来越慢。

子弹的rect的bottom属性为零时,表面以及穿过屏幕顶端了

def run_game():
    # 初始化游戏并创建一个屏幕对象
    
    ....

    # 创建储存子弹的编组
    bullets = Group()

    # 开始游戏的主循环
    while True:
        # 监视键盘和鼠标事件
        gf.check_event(ai_settings, screen, ship, bullets)
        ship.update()
        bullets.update()

        # 删除已消失的子弹
        for bullet in  bullets.copy():
            if bullet.rect.bottom <= 0:
                bullets.remove(bullet)
        gf.update_screen(ai_settings, screen, ship, bullets)


run_game()

限制子弹数量

对屏幕上子弹数量进行限制,鼓励玩家有目标的射击

settings.py

self.bullets_allowed = 3
def check_keydown_event(event, ai_settings, screen, ship, bullets):
    """键盘按下"""
    if event.key == pygame.K_RIGHT:
        # 向右移动飞船
        # ship.rect.centex += 1
        ship.moving_right = True
    if event.key == pygame.K_LEFT:
        ship.moving_left = True

    if event.key == pygame.K_SPACE:
        # 创建一颗子弹, 并将其加入到编组bullets中
        if len(bullets) < ai_settings.bullets_allowed:
            new_bullet = Bullet(ai_settings, screen, ship)
            bullets.add(new_bullet)
创建函数

update_bullets()

子弹管理代码独立

game_funcitions.py

def update_bullets(bullets):
    """更新子弹的位置,并删除已消失的子弹"""
    bullets.update()

    # 删除已消失的子弹
    for bullet in bullets.copy():
        if bullet.rect.bottom <= 0:
            bullets.remove(bullet)

alien_invasion.py

# 开始游戏的主循环
while True:
    # 监视键盘和鼠标事件
    gf.check_event(ai_settings, screen, ship, bullets)
    ship.update()
    gf.update_bullets(bullets)

fire_bullet()

发射子弹的代码移动到一个独立的函数中

外星人

项目第二篇,从屏幕上方边缘添加一个外星人,然后生成一群外星人,外星人向两边和下面移动,并删除被子弹击中的外星人,最后,我们将显示玩家拥有飞船的数量,在玩家的飞船用完之后变结束游戏。

创建一个外星人

在平时上放置类似飞船的,创建Alien类,像Ship那样的。

创建alien类

alien.py

"""
======================
@title: game_functions
@description: 游戏方法
@author: elijah
@date: 2022/9/9 10:10
=====================
"""

import pygame
import sys
from bullet import Bullet


def check_event(ai_settings, screen, ship, bullets):
    """响应按键和鼠标事件"""
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            sys.exit()
        elif event.type == pygame.KEYDOWN:
            print("键盘事件: " + str(event.key))
            check_keydown_event(event, ai_settings, screen, ship, bullets)
        elif event.type == pygame.KEYUP:
            check_keyup_event(event, ship)


def check_keydown_event(event, ai_settings, screen, ship, bullets):
    """键盘按下"""

    if event.key == pygame.K_q:
        sys.exit()

    if event.key == pygame.K_RIGHT:
        # 向右移动飞船
        # ship.rect.centex += 1
        ship.moving_right = True
    if event.key == pygame.K_LEFT:
        ship.moving_left = True

    if event.key == pygame.K_SPACE:
        # 创建一颗子弹, 并将其加入到编组bullets中
        fire_bullet(ai_settings, screen, ship, bullets)
    if event.key == pygame.K_RETURN:
        # 加大活力
        fire_full_bullet(ai_settings, screen, ship, bullets)



def check_keyup_event(event, ship):
    """键盘松开"""
    if event.key == pygame.K_RIGHT:
        ship.moving_right = False
    if event.key == pygame.K_LEFT:
        ship.moving_left = False


def update_screen(ai_settings, screen, ship, alien, bullets):
    """更新屏幕上的图像,并切换到新屏幕"""
    # 每次循环时都重绘制屏幕
    screen.fill(ai_settings.bg_color)

    # 添加背景
    bg_image = pygame.image.load(ai_settings.bg_image).convert()
    screen.blit(bg_image, (0, 0))

    # 在飞船和外星人后面重新绘制子弹
    for bullet in bullets.sprites():
        bullet.draw_bullet()

    ship.blitme()
    alien.blitme()

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


def update_bullets(bullets):
    """更新子弹的位置,并删除已消失的子弹"""
    bullets.update()

    # 删除已消失的子弹
    for bullet in bullets.copy():
        if bullet.rect.bottom <= 0:
            bullets.remove(bullet)


def fire_bullet(ai_settings, screen, ship, bullets):
    """如果没有达到限制,就发射一颗子弹"""
    # 创建一颗子弹, 并将其加入到编组bullets中
    if len(bullets) < ai_settings.bullets_allowed:
        new_bullet = Bullet(ai_settings, screen, ship)
        bullets.add(new_bullet)


def fire_full_bullet(ai_settings, screen, ship, bullets):
    """加大火力"""
    # 创建一颗子弹, 并将其加入到编组bullets中
    i = 0
    while i < 80:
        # new_ship = ship.copy()
        # new_ship.rect.centerx = ship.rect.centerx
        ai_settings.ship_full_fire = True
        ai_settings.ship_full_fire_num = i * 2 + i

        new_bullet = Bullet(ai_settings, screen, ship)
        bullets.add(new_bullet)
        i += 1
创建Alien实例

在alien_invasion.py 中创建Alien实例


from alien import Alien


def run_game():
    # 初始化游戏并创建一个屏幕对象
   
   ...

    # 创建一艘飞船
    ship = Ship(ai_settings, screen)

    # 创建储存子弹的编组
    bullets = Group()

    # 创建一个外星人
    alien = Alien(ai_settings, screen)

    # 开始游戏的主循环
    while True:
        # 监视键盘和鼠标事件
        gf.check_event(ai_settings, screen, ship, bullets)
        ship.update()
        gf.update_bullets(bullets)
        gf.update_screen(ai_settings, screen, ship, alien, bullets)


run_game()

在外星人出现在屏幕上

game_funcitons.py

def update_screen(ai_settings, screen, ship, alien, bullets):
    """更新屏幕上的图像,并切换到新屏幕"""
    # 每次循环时都重绘制屏幕
    screen.fill(ai_settings.bg_color)

    # 添加背景
    bg_image = pygame.image.load(ai_settings.bg_image).convert()
    screen.blit(bg_image, (0, 0))

    # 在飞船和外星人后面重新绘制子弹
    for bullet in bullets.sprites():
        bullet.draw_bullet()

    ship.blitme()
    alien.blitme()

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

启动项目,第一个外星人变出现了

创建一群外星人

简单计算空间

首先需要确定一行可以容纳多少个外星人

屏幕的宽度为ai_setting.screen_width,但屏幕两边需要留空白,两个外边距,放置外星人的水平空间为屏幕宽度减去外星人宽度的两倍

available_space_x = ai_setting.screen_width - (2 * alien_width)

外星人直接也需要流出空白,一个外星人水平需要的宽度是两个外星人的宽度,一行容纳多少外星人可以 可利用空间除以一个外星人的宽度

number_aliens_x = available_space_x / (2 * alien_width)

创建多行外星人

先创建aliens的编组,用于储存外星人,再调用创建函数create_fleet

alien_invasion.py

def run_game():
    # 初始化游戏并创建一个屏幕对象
   ...

    # 创建一艘飞船
    ship = Ship(ai_settings, screen)

    # 创建储存子弹的编组
    bullets = Group()

    # 一个外星人编组
    aliens = Group()

    # 创建外星人群
    gf.create_fleet(ai_settings, screen, aliens)

    # 开始游戏的主循环
    while True:
        # 监视键盘和鼠标事件
        ....
        gf.update_screen(ai_settings, screen, ship, aliens, bullets)


run_game()

game_funcitons.py的update_screen() 修改参数名称 aliens

创建外星人群
def create_fleet(ai_settings, screen, aliens):
    """创建外星人舰队"""

    # 创建一个外星人 并计算一行容纳多少人外星人
    # 外星人间距为外星人宽度
    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)

新增运行游戏即可以看到一行外星人了

重构create_fleet()

创建两个新函数

game_functions.py

def create_fleet(ai_settings, screen, aliens):
    """创建外星人舰队"""

    # 创建一个外星人 并计算一行容纳多少人外星人
    # 外星人间距为外星人宽度
    alien = Alien(ai_settings, screen)
    alien_width = alien.rect.width
    number_aliens_x = get_number_aliens_x(ai_settings, alien_width)

    # 创建一行外星人
    for alien_number in range(number_aliens_x):
        # 创建一个外星人并将其加入当前行
        create_alien(ai_settings, screen, aliens, alien_number)


def get_number_aliens_x(ai_settings, alien_width):
    """计算每行容纳多少外星人"""
    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 create_alien(ai_settings, screen, aliens, alien_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
    aliens.add(alien)
添加行

计算可以容纳外形人的有限高度是多少, 垂直空间,屏幕高度减去第一行外星人的上边距,飞船高度和最初外星人高度加上外星人间距

屏幕高度 - 两处留白的位置 - 第一行外星人 - 飞船

available_space_y = ai_settings.screen_height - (3 * alien_height) - ship_height

可容纳多少行

number_rows = available_space_y / (2 * alien_height)

game_functions.py

def create_fleet(ai_settings, screen, ship, aliens):
    """创建外星人舰队"""

    # 创建一个外星人 并计算一行容纳多少人外星人
    # 外星人间距为外星人宽度
    alien = Alien(ai_settings, screen)
    alien_width = alien.rect.width
    number_aliens_x = get_number_aliens_x(ai_settings, alien_width)
    number_rows = get_number_rows(ai_settings, ship.rect.height, alien.rect.height)

    # 创建一群外星人
    for number_row in range(number_rows):
        for alien_number in range(number_aliens_x):
            # 创建一个外星人并将其加入当前行
            create_alien(ai_settings, screen, aliens, alien_number, number_row)


def get_number_aliens_x(ai_settings, alien_width):
    """计算每行容纳多少外星人"""
    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 create_alien(ai_settings, screen, aliens, 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.rect.y = alien.rect.height + 2 * alien.rect.height * row_number
    aliens.add(alien)


def get_number_rows(ai_settings, ship_height, alien_height):
    """计算屏幕可容纳多少行外星人"""
    # 屏幕高度 - 两处留白的位置 - 第一行外星人 - 飞船
    available_space_y = ai_settings.screen_height - (3 * alien_height) - ship_height
    number_rows = int(available_space_y / (2 * alien_height))
    return number_rows

创建多行时,需要两个嵌套的循环,内部循环场景一行外星人,外部循环从零数到要创建的行数

调用方法

# 创建外星人群
gf.create_fleet(ai_settings, screen, ship, aliens)

现在运行游戏变可以看到外星人。

也可以使用随机数来创建

# 随机创建
random_number = randint(0, 10)
if alien_number == random_number or number_row == random_number:
    continue

让外星人群移动

让外星人在屏幕上向右移动,撞到屏幕边缘后下移一定距离,再沿相反的方向移动。

向右移动
 # 开始游戏的主循环
    while True:
        # 监视键盘和鼠标事件
        gf.check_event(ai_settings, screen, ship, bullets)
        ship.update()
        gf.update_bullets(bullets)
        gf.update_aliens(aliens)
        gf.update_screen(ai_settings, screen, ship, aliens, bullets)

game_functions.py


def update_aliens(aliens):
    """更新外星人群中的所有外星人位置"""
    aliens.update()

alien.py

def update(self):
    """向右移动外星人"""
    self.x += self.ai_settings.alien_speed_factor
    self.rect.x = self.x

settings.py

# 外星人设置
self.alien_speed_factor = 1
移动方向控制
# 外星人设置
self.alien_speed_factor = 1
self.fleet_drop_speed = 10
# fleet_direction 为1表示向右移 -1表示向左移动
self.fleet_direction = 1

由于是两个方向,使用-1或者1就不会影响两个方向切换

检查是否撞到了屏幕边缘
def check_edges(self):
    """如果外星人位于屏幕边缘 就返回True"""
    screen_rect = self.screen.get_rect()
    if self.rect.right >= screen_rect.right:
        return True
    elif self.rect.left <= 0:
        return True

def update(self):
    """向右移动外星人"""
    self.x += (self.ai_settings.alien_speed_factor * self.ai_settings.fleet.direction)
    self.rect.x = self.x
向下移动并改变方向

调用change_fleet_direction后,跳出循环,不然会一直向下

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_edges():
            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

类似的可以做成雨点,下雨天

射杀外星人

sprite.groupcollide() 将每颗子弹的rect同每个外星人rect进行比较,并返回一个字典,包含碰撞的外星人和子弹,相应的值都是被击中的外星人, 两个True是删除响应的元素,如果是高能子弹的话 第一个参数可以为false不用删除。

# 检查是否有子弹击中外星人 如果是这样的 删除相应的子弹和外星人
collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)

可以改变子弹的宽度来提高射杀率

self.bullet_width = 300
生成新的外星人
if len(aliens) == 0:
    # 删除现有的子弹并新建一群外星人
    bullet.empty()
    create_fleet(ai_settings, screen, ship, aliens)

提交子弹的速度,厚礼等

重构update_bullets()

结束游戏

增加趣味性

检测外星人和飞船碰撞
def update_aliens(ai_settings, ship, aliens):
    """
    检查是否有外星人位于屏幕边缘
    更新外星人群中的所有外星人位置
    """
    ...

    # 检测外形人和飞船之间碰撞
    if pygame.sprite.spritecollideany(ship, aliens):
        print("--------------- ship hit!!! ------------------")

响应外星人和飞船碰撞

碰撞时,不销毁ship实例并创建一个新的ship,通过追踪游戏统计信息来记录飞船被撞的次数,便于后续记分

新建GameStats 用于跟踪游戏统计信息的类

game_stats.py

class GameStats():
    """跟踪游戏的统计信息"""

    def __init__(self, ai_settings):
        """初始化统计信息"""
        self.ai_settings = ai_settings
        self.reset_stats()

    def reset_stats(self):
        """初始化在游戏运行期间可能变化信息统计"""
        self.ships_left = self.ai_settings.ship_limit
# 创建一个用于储存游戏统计信息的实例
stats = GameStats(ai_settings)

# 开始游戏的主循环
while True:
    # 监视键盘和鼠标事件
  
    gf.update_aliens(ai_settings, ship, aliens, stats)

响应被外星人撞到的飞船

撞击之后清空子弹和外星人, 创建新的外星人,把飞船放在屏幕中间,暂停1s

game_functions.py

def ship_hit(ai_settings, stats, screen, ship, aliens, bullets):
    """响应被外星人撞到的飞船"""
    # 将ships_left减1
    stats.ships_left -= 1

    # 清空外星人列表和子弹
    aliens.empty()
    bullets.empty()

    # 创建一群新的外星人 并将飞船放到屏幕底端中央
    create_fleet(ai_settings, screen, ship, aliens)
    ship.center_ship()

    # 暂停
    sleep(1)
def update_aliens(ai_settings, stats, screen, ship, aliens, bullets):
    """
    检查是否有外星人位于屏幕边缘
    更新外星人群中的所有外星人位置
    """
    check_fleet_edges(ai_settings, aliens)
    aliens.update()

    # 检测外形人和飞船之间碰撞
    if pygame.sprite.spritecollideany(ship, aliens):
        print("--------------- ship hit!!! ------------------")
        ship_hit(ai_settings, stats, screen, ship, aliens, bullets)

ship.py

def center_ship(self):
    """让飞船在屏幕上居中"""
    self.center = self.screen_rect.centerx

有外星人到达屏幕底端

也做出与外星人撞击屏幕相同的响应

def check_aliens_bottom(ai_settings, stats, screen, ship, aliens, bullets):
    """检查是否有外星人到达屏幕底端"""
    screen_rect = screen.get_rect()
    for alien in aliens.sprites():
        if alien.rect.bottom >= screen_rect.bottom:
            """像飞船撞击一样处理"""
            ship_hit(ai_settings, stats, screen, ship, aliens, bullets)
游戏结束

给定一个游戏状态,飞船用完后结束游戏

game_stats.py

class GameStats():
    """跟踪游戏的统计信息"""

    def __init__(self, ai_settings):
        """初始化统计信息"""
        # 游戏启动处于活动状态
        self.game_active = True

game_function.py

def ship_hit(ai_settings, stats, screen, ship, aliens, bullets):
    """响应被外星人撞到的飞船"""
    if stats.ships_left > 0:
        # 将ships_left减1
        stats.ships_left -= 1
        ...
        # 暂停
        sleep(1)
    else:
        # game over
        stats.game_active = False

alien_invasion.py

    # 开始游戏的主循环
    while True:
        # 监视键盘和鼠标事件
        gf.check_event(ai_settings, screen, ship, bullets)

        if stats.game_active:
            ship.update()
            gf.update_bullets(ai_settings, screen, ship, bullets, aliens)
            gf.update_aliens(ai_settings, stats, screen, ship, aliens, bullets)

        gf.update_screen(ai_settings, screen, ship, aliens, bullets)


run_game()

当前游戏主体已经完成了,只是确认积分系统,游戏关卡,交互性比较好的UI。

也是托着连续失眠疲惫身体,坚持着,也盼望着可以看到我们所期待有意义的样子

2022/09/22 chengdu work in home!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,651评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,468评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,931评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,218评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,234评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,198评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,084评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,926评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,341评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,563评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,731评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,430评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,036评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,676评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,829评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,743评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,629评论 2 354

推荐阅读更多精彩内容