python入门项目一:外星人入侵


在win10下安装第三方库pygame:

pip install pygame

文中代码均参考自python从入门到实践这一书,文中所用到的图片可到本书的官网python从入门到实践下载获取在这里插入图片描述
或者用百度网盘下载:
链接:https://pan.baidu.com/s/1aU5WvduNPlxXmU33KT6Tkg
提取码:4ijn

创建文件夹,可以任意命名,这里我把它命名成alien_invasion_game,后面项目所创建的模块都要统一放到plane_game中。

创建飞船模块 ship.py:

import pygame
from pygame.sprite import Sprite

class Alien(Sprite):
    def __init__(self, ai_settings, screen):
        super().__init__()
        self.screen = screen
        self.ai_settings = ai_settings

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

Sprite精灵组就是一个列表,里面存储了许多个相同的飞船(后面的外星人,子弹的精灵组也是一样),精灵组便于对里面的元素进行统一的管理

创建子弹模块 bullet.py:

import pygame
from pygame.sprite import Sprite

class Bullet(Sprite):
    def __init__(self, ai_setting, screen, ship):
        """在飞机所处位置创建子弹对象"""
        super().__init__()
        self.screen = screen

        #在(0, 0)处创建一个表示子弹的矩形,再设置其正确的位置
        self.rect = pygame.Rect(0, 0, ai_setting.bullet_width, ai_setting.bullet_height)
        # 子弹并非基于图像,就像是从游戏窗口中挖出的一部分像素,因此我们必须使用pygame.Rect()类从空白处
        # 开始创建一个矩形,必须提供矩形左上角的x,y坐标,和矩形的宽度和高度

        self.rect.centerx = ship.rect.centerx
        self.rect.top = ship.rect.top

        #存储子弹的位置
        self.y = float(self.rect.y)

        self.color = ai_setting.bullet_color
        self.speed_factor = ai_setting.bullet_speed_factor

        #持续发射子弹
        self.shoot_unstop = False

    def update(self):
        """更新子弹的位置"""
        self.y -= self.speed_factor
        self.rect.y = self.y

    def draw_bullet(self):
        pygame.draw.rect(self.screen, self.color, self.rect)
        # 与飞船在屏幕上显示的方式不同:子弹本身就是游戏窗口中的一部分(不是从游戏窗口外的图像导入的),
        # 使用pygame.draw.rect()方法来填充表示子弹的rect占据的屏幕部分


创建外星人模块 alien.py:

import pygame
from pygame.sprite import Sprite

class Alien(Sprite):
    def __init__(self, ai_settings, screen):
        super().__init__()
        self.screen = screen
        self.ai_settings = ai_settings

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

创建模块 settings.py :

用来管理游戏设置(管理游戏窗口的颜色,宽度,高度,可用飞船的艘树等等)

class Settings():
    def __init__(self):
        self.screen_width = 1000
        self.screen_height = 600
        self.bg_color = (230, 230, 230)

        self.ship_limit = 3 # 可用飞船的总数

        self.bullet_width = 1000
        self.bullet_height = 15
        self.bullet_color = (60, 60, 60)
        self.bullets_allowed = 5 # 可让最多个子弹在同一个屏幕上

        self.fleet_drop_speed = 100

        self.speed_scale = 1.1
        self.score_scale = 1.5 # 随着外星人速度的增加,其分值也随之增加

        self.initialize_dynamic_settings()

    def initialize_dynamic_settings(self):
    	"""随着游戏进行,这些属性会发生变化"""
        self.ship_speed_factor = 1.5
        self.bullet_speed_factor = 2
        self.alien_speed_factor = 1
        
        self.fleet_direction = 1 #表示飞船的运行方向,为什么不用文本 right, left
        #来描述?按照书中所述:若用文本表示,就需要使用if...else...来判断外形人的移
        #动方向,鉴于只有两个方向(左或者右),我们可使用 1 和 -1 来分别代表他们,两
        #者进行转换的时候只需 乘 -1,非常的方便
        
        self.alien_points = 50

    def increase_speed(self):
        self.ship_speed_factor *= self.speed_scale
        self.bullet_speed_factor *= self.speed_scale
        self.alien_speed_factor *= self.speed_scale
        self.alien_points = int(self.alien_points * self.alien_speed_factor)

创建 game_stats.py :

用来统计当前的分数,游戏历史的最高分数,此模块用来跟踪游戏的统计信息:

class GameStats():
    """跟踪游戏的统计信息"""
    def __init__(self, ai_settings):
        self.ai_settings = ai_settings
        self.reset_stats()
        self.game_active = False
        self.score = 0
        self.high_score = 0

    def reset_stats(self):
    	#当使用完一开始设定好艘数的飞船时,我们要重置游戏,使其恢复到最初状态
        self.ship_left = self.ai_settings.ship_limit
        self.score = 0
        self.level = 1

创建scoreboard.py 模块 :

把所统计的分数,当前飞船等级显示在游戏窗口中

import pygame.font
from pygame.sprite import Group
from ship import Ship

class Scoreboard():
    def __init__(self, ai_settings, screen, stats):
        self.screen = screen
        self.screen_rect = screen.get_rect()
        self.ai_settings = ai_settings
        self.stats = stats

        self.text_color = (30, 30, 30)
        self.font = pygame.font.SysFont(None, 35)

        self.prep_score()
        self.prep_high_score()
        self.prep_level()
        self.prep_ships()

    def prep_score(self):
        """把文字转换成图像"""
        rounded_score = int(round(self.stats.score, -1))
        score_str = "{:,}".format(rounded_score)

        self.score_image = self.font.render(score_str, True, self.text_color, self.ai_settings.bg_color)

        self.score_rect = self.score_image.get_rect()
        self.score_rect.right = self.screen_rect.right - 20
        self.score_rect.top = 10

    def prep_high_score(self):
        high_score = int(round(self.stats.high_score, -1))
        high_score_str = "{:,}".format(high_score)

        self.high_score_image = self.font.render(high_score_str, True, self.text_color, self.ai_settings.bg_color)

        self.high_score_rect = self.high_score_image.get_rect()
        self.high_score_rect.top = 10
        self.high_score_rect.centerx = self.screen_rect.centerx

    def prep_level(self):
        self.level_image = self.font.render(str(self.stats.level), True, self.text_color,
                                            self.ai_settings.bg_color)
        self.level_rect = self.level_image.get_rect()
        self.level_rect.right = self.score_rect.right
        self.level_rect.top = self.score_rect.bottom

    def prep_ships(self):
        self.ships = Group()
        for ship_number in range(self.stats.ship_left):
            ship = Ship(self.ai_settings, self.screen)
            ship.rect.x = 10 + ship_number*ship.rect.width
            ship.rect.y = 10
            self.ships.add(ship)

    def show_score(self):
        # 第一个参数表示要显示的内容,第二个参数表示要显示在那个位置
        self.screen.blit(self.score_image, self.score_rect)
        self.screen.blit(self.high_score_image, self.high_score_rect)
        self.screen.blit(self.level_image, self.level_rect)
        self.ships.draw(self.screen) #貌似精灵组都是用draw???

创建 button.py模块:

当游戏处于非活跃状态时,我么需要通过点击屏幕上的特定位置来使游戏启动

import pygame.font

class Button():
    def __init__(self, ai_settings, screen, msg):
        self.screen = screen
        self.screen_rect = screen.get_rect()

        self.width, self.height = 200, 50
        self.button_color = (0, 162, 232)
        self.text_color = (255, 255, 255)
        self.font = pygame.font.SysFont(None, 48)

        # 创建按钮的rect对象,并使其居中(相当于一个容纳字符串的容器)
        self.rect = pygame.Rect(0, 0, self.width, self.height)
        self.rect.center = self.screen_rect.center

        self.pre_msg(msg)

    def pre_msg(self, msg):
        """将要显示的字符串 渲染为图像来处理文本,并将字符串在按钮中居中显示"""
        self.msg_image = self.font.render(msg, True, self.text_color, self.button_color) # 使用font.render将文本转换成图像
        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.button_color, self.rect)
        self.screen.blit(self.msg_image, self.msg_image_rect)

创建 game_functions.py 模块:

为了使主模块函数(也就是启动游戏的函数)变得更简洁和更容易拓展并使其逻辑更容易理解,统一的管理游戏运行的函数

import sys
import pygame
from bullet import Bullet
from alien import Alien
from time import sleep

def check_keydown_events(event, ai_setting, screen, ship, bullets):
    #if event.type == pygame.KEYDOWN: #若事件类型是按下方向键盘的按键,但是当玩家持续按住方向键时
        if event.key == pygame.K_RIGHT:
             # ship.rect.x += 1    #飞船并不能持续移动
            ship.moving_right = True
        elif event.key == pygame.K_LEFT:
            ship.moving_left = True
        elif event.key == pygame.K_SPACE:
            # if len(bullets) < ai_setting.bullets_allowed:
            #     new_bullet = Bullet(ai_setting, screen, ship)
            #     bullets.add(new_bullet)
            fire_bullet(ai_setting, screen, ship, bullets)
        elif event.key == pygame.K_q:
            sys.exit()

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_play_button(stats, play_button, mouse_x, mouse_y, ai_settings, screen, sb, ship, aliens, bullets):
    button_clicked = play_button.rect.collidepoint(mouse_x, mouse_y)
    if button_clicked and not stats.game_active:
        pygame.mouse.set_visible(False)
        ai_settings.initialize_dynamic_settings()
        #重置游戏统计信息
        stats.reset_stats()
        stats.game_active = True

        #重置记分牌图像
        sb.prep_level()
        sb.prep_score()
        sb.prep_high_score()

        aliens.empty()
        bullets.empty()
        
        create_fleet(ai_settings, screen, ship, aliens)
        ship.center_ship()

def check_events(ai_setting, screen, sb, ship, bullets, aliens, stats, play_button):
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            sys.exit()

        elif event.type == pygame.MOUSEBUTTONDOWN:
            mouse_x, mouse_y = pygame.mouse.get_pos()
            check_play_button(stats, play_button, mouse_x, mouse_y, ai_setting, screen, sb, ship, aliens, bullets)

        elif event.type == pygame.KEYDOWN: # 表示按下键盘
            check_keydown_events(event, ai_setting, screen, ship, bullets)

        elif event.type == pygame.KEYUP:
            check_keyup_events(event, ship)

def update_bullets(ai_settings, screen,stats, sb, ship, aliens, bullets):
    bullets.update()
    for bullet in bullets.copy(): #子弹越过游戏窗口后并没有从内存中消失,只是其y轴坐标不断减小,
                    # 在屏幕中没有显示而已, 必须要将越过游戏窗口的子弹从内存中删除,
        if bullet.rect.bottom <=0: # 注意删除列表的方式!!!
            bullets.remove(bullet)
    check_bullet_alien_collision(ai_settings, screen,stats, sb, ship, aliens, bullets)

    # 检查是否有子弹集中了外形人
    # collisions = pygame.sprite.groupcollide(bullets, aliens, False, True)
    #这行代码先遍历bullets中的每颗子弹,再遍历aliens中的每个外星人,当有子弹和外星人的rect重叠时,groupcollide()
    #就在它返回的字典中添加一个键-值对,两个实参True表示要将子弹和外星人删除

def check_bullet_alien_collision(ai_settings, screen, stats, sb, ship, aliens, bullets):
        collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)

        if collisions:
            # stats.score += ai_settings.alien_points #这种情况会导致宽子弹击中多个外星人时分数也只能增加击中
            # sb.prep_score()                   #击中一个外星人的分数
            for aliens in collisions.values():
                stats.score += ai_settings.alien_points * len(aliens)#collisions是一个字典,bullet是字典中的键
                sb.prep_score() #键对应的值是一个列表,列表内存储的是该bullet所击中的外星人
            check_high_score(stats, sb)

        if len(aliens) == 0:
            bullets.empty()
            ai_settings.increase_speed()
            stats.level += 1
            sb.prep_level()
            create_fleet(ai_settings, screen, ship, aliens)


def fire_bullet(ai_setting, screen, ship, bullets):
    if len(bullets) < ai_setting.bullets_allowed:
        new_bullet = Bullet(ai_setting, screen, ship)
        bullets.add(new_bullet)

def update_screen(ai_setting, screen, ship, bullets, aliens, play_button, stats, sb):
    screen.fill(ai_setting.bg_color) # 该方法只接受颜色这一个参数,每次循环都重回屏幕
    for bullet in bullets: # 不能按照书本中的 for bullet in bullets.sprites()
        bullet.draw_bullet()

    sb.show_score()
    ship.blitme() # 飞船必须放在背景色的后面,否则,每次屏幕被刷新后,飞机都会被覆盖
    aliens.draw(screen)

    if not stats.game_active:
        play_button.draw_button()

    pygame.display.flip() #该命令只显示 Pygame 最近绘制的屏幕,该命令在 while 循环中,使得屏幕不断更新,每次都是显示
    #元素的最新位置,从而营造出平滑移动的效果

def get_number_aliens_x(ai_settings, alien_width):
    availabe_space_x = ai_settings.screen_width - 2*alien_width
    num_alien_x = int(availabe_space_x / (2*alien_width))
    return num_alien_x

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

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_number*alien_width
    alien.rect.x = alien.x
    alien.rect.y = alien.rect.height + 2*alien.rect.height*row_number
    aliens.add(alien)

def create_fleet(ai_settings, screen, ship, aliens):
    # alien = Alien(ai_setting, screen) # 只是为了获得外星人的高度与宽度等属性,它并不是外星人群的成员
    alien = Alien(ai_settings, screen)
    number_rows = get_number_rows(ai_settings, ship.rect.height, alien.rect.height)
    num_alien_x = get_number_aliens_x(ai_settings, alien.rect.width)
    for row_number in range(number_rows):
        for alien_number in range(num_alien_x):
            create_alien(ai_settings, screen, aliens, alien_number, row_number)

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_fleet_edges(ai_settings, aliens):
    for alien in aliens.sprites():
        if alien.check_edges():
            change_fleet_direction(ai_settings, aliens)
            break

def update_aliens(ai_settings, stats, screen, sb, ship, aliens, bullets):
    check_fleet_edges(ai_settings, aliens)
    aliens.update()
    if pygame.sprite.spritecollideany(ship, aliens):
        # spritecollideany接受两个参数:一个精灵组和一个编组,它遍历精灵组是否有成员与编组发生碰撞,有的话就停止遍历
        # 并返回与编组发生碰撞的精灵组成员,若没有碰撞则返回None
        # 检测子弹和外星人碰撞用的groupcollide,接受的两个参数都是精灵组
        ship_hit(ai_settings, stats, screen, sb, ship, aliens, bullets)
    check_aliens_bottom(ai_settings, stats, screen, sb, ship, aliens, bullets)

def ship_hit(ai_settings, stats, screen, sb, ship, aliens, bullets):
    """当飞船与外星人碰撞时,就创建一群新的外星人,并将飞船重置到屏幕低端中央"""
    if stats.ship_left > 0:
        stats.ship_left -= 1
        sb.prep_ships()
        aliens.empty()
        bullets.empty()

        create_fleet(ai_settings, screen, ship, aliens)
        ship.center_ship()

        sleep(0.5)
    else:
        stats.game_active = False
        pygame.mouse.set_visible(True)

def check_aliens_bottom(ai_settings, stats, screen, sb, 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, sb, ship, aliens, bullets)
            break

def check_high_score(stats, sb):
    if stats.score > stats.high_score:
        stats.high_score = stats.score
        sb.prep_high_score()

创建alien_invasion.py 模块:

然后就是我们的启动游戏函数了,它存放在了 alien_invasion.py 模块中:

"""""
import pygame
from settings import Settings
from ship import Ship
import game_functions as gf
from pygame.sprite import Group
from game_stats import GameStats
from button import Button
from scoreboard import Scoreboard

def run_game():
    pygame.init()
    ai_setting = Settings()

    screen = pygame.display.set_mode((ai_setting.screen_width, ai_setting.screen_height)) #创建一个名为screen的显示窗口
    pygame.display.set_caption("Alien Invasion")
    play_button = Button(ai_setting, screen, "Play")

    ship = Ship(ai_setting, screen)
    bullets = Group()
    aliens = Group()
    gf.create_fleet(ai_setting, screen, ship, aliens) # 这个创建外星人群千万不要放到了while循环内
    stats = GameStats(ai_setting)
    sb = Scoreboard(ai_setting, screen, stats)


    while True:
        gf.check_events(ai_setting, screen, sb, ship, bullets, aliens, stats, play_button)

        if stats.game_active:
            ship.update()
            # bullets.uprdate()
            #
            # for bullet in bullets.copy(): #子弹越过游戏窗口后并没有从内存中消失,只是其y轴坐标不断减小,在屏幕中没有显示而已
            #                             # 必须要将越过游戏窗口的子弹从内存中删除,
            #     if bullet.rect.bottom <=0:
            #         bullets.remove(bullet)
            gf.update_bullets(ai_setting, screen, stats, sb, ship, aliens, bullets)
            gf.update_aliens(ai_setting, stats, screen, sb, ship, aliens, bullets)

        # screen.fill(ai_setting.bg_color) # 该方法只接受颜色这一个参数,每次循环都重回屏幕
        # ship.blitme() # 飞船必须放在背景色的前面,否则,每次屏幕被刷新后,飞机都会被覆盖
        #
        # pygame.display.flip() #该命令只显示 Pygame 最近绘制的屏幕,该命令在 while 循环中,使得屏幕不断更新,每次都是显示
        # #元素的最新位置,从而营造出平滑移动的效果
        gf.update_screen(ai_setting, screen, ship, bullets, aliens, play_button, stats, sb)

run_game()

最终的游戏效果:
在这里插入图片描述

最后做个总结的话:我最大的体会就是深刻地认识到了重构这一设计理念,真的是大大地提高了代码的拓展,代码堆在一起确实不好理解,也可能是本人的描述不行,如果各位看官有兴趣的话,你们也可以去找原书仔细阅读,肯定会比看此文收获地更多。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值