如何用 Python 打飞机 ?

前言:python 除了生孩子 ,啥都会 。包括打飞机 !今天小詹的一位读者就来教你如何用 python 打飞机 !

简述

相信小詹是一个单纯的孩子 ,也相信大家明白小詹说的打飞机是指啥意思 ,对吧 ?嗯 ,没毛病 。就是 pygame 实现一个打飞机的游戏 ,优秀的我啊 !

我们知道 pygame 框架可以用于管理图形 、动画声音等 ,能够利用它来轻松地开发复杂的游戏 ,可以让我们更加专注于面向对象编程 。此项目是基于 pygame 框架搭建的一个小游戏 ,在此文中将实现此项目的 50% ,后续会有跟进 ,源代码已经放在我的 GitHub 中 ,并在进行中将会不断对代码结构进行优化 ,对样式进行优化 。在本例中将会接触简单的面向对象编程和继承 。面向对象编程是提取项目中某种事物的关键属性进行抽象 ,抽象模型中包括数据和行为 ,类是对象的抽象 ,对象是类的实例 。

源代码获取方式见置顶留言 。

先送上一波效果图(被压缩的时间略短)

效果图 游戏简介 :在游戏《外星人入侵》中 ,玩家控制着一艘最初出现在屏幕底部中央的飞船 。玩家可以使用箭头键左右移动飞船 ,还可使用空格键进行射击 。游戏开始时 ,一群外星人出现在天空中 ,他们在屏幕中向下移动 。玩家的任务是射杀这些外星人 。玩家将所有外星人都消灭干净后 ,将出现一群新的外星人 ,他们移动的速度更快 。只要有外星人撞到了玩家的飞船或到达了屏幕底部 ,玩家就损失一艘飞船 。玩家损失三艘飞船后 ,游戏结束 。

游戏用例图(第一次画用例图,不是很准确……)

用例图 分析该项目 ,飞船 、子弹 、外星人可以分别划分为具有共同属性的一类 ,类中定义各自的属性 ,包括图像 、形状 、位置 、更新位置 、绘制图像等 ;在主函数中将对象实例化 ,对对象中的成员变量和函数进行调用实现移动 、击杀等操作 ;本节实现飞船和子弹类和基本功能 。

在构建子弹类时 ,用到了继承的概念 ,当在参与大型项目开发设计时 ,继承是一个非常关键的概念 。继承是对已有类的一种复用 ,子类继承父类 ,可以对父类中的方法和数据进行重写 ,也可以新定义只属于子类的成员变量 。当项目中有许多类具有相同的基本属性时 ,可以考虑将这些基本属性抽象为一个父类 ,子类通过继承父类而拥有父类中的数据和方法 ,这会提高代码的可读性 ,也省去很多重复的代码 。

当前新建以下几个文件 :

  1. alien-invasion.py 主函数
  2. ship.py 飞船类
  3. bullet.py 子弹类
  4. game_functions.py 许多主函数会调用的函数
  5. settings.py 配置文件 ,常量

其目录结构如下 :

代码运行步骤 : 方法 1——

  1. git clone https://github.com/AlisaBen/easycoding
  2. powershell进入到工程目录下(/fun_python>)
  3. cd ./alien_invasion
  4. python alien_invasion.py

方法2——

  1. 按照下面代码部分新建文件 ,并复制代码
  2. 在文件的同级目录下新建images目录
  3. 找飞船和子弹的图片分别命名为外星飞船.png生气.png.emmm…画风有点不对 ,怪我了 ,阔以自己找图片替代 ,对应修改飞船类和子弹类的文件名就好 ~
  4. 进入到代码的根目录下
  5. python alien_invasion.py

代码

alien-invasion.py

  • run_game()定义了主函数 ,首先绘制屏幕 , 对象 screen 是一个 surface ,在 pygame 中,surface 是屏幕的一部分 ,显示游戏元素 。每个元素 ,外星人或者飞船 ,子弹都是一个surface 。
  • ship = Ship(game_settings,screen)就是一个对象是类的实例的例子 ,arguments 是类初始化需要传入的参数 ,ship就是Ship类型的对象 ,可以访问Ship类中的数据和方法 。
  • 每个游戏循环中 ,更新飞船位置 ,子弹位置 ,子弹编组 ,重新渲染游戏界面 。
  • 子弹编组用来管理屏幕中的所有子弹 ,其实用列表来管理子弹对象也是可以实现的 ,但是 pygame 自带的 Group 已经定义了一些函数 ,更加方便 。
import pygame
from settings import Settings
from ship import Ship
import game_functions as gf
from pygame.sprite import Group
def run_game():
    pygame.init()
    # 获取配置
    game_settings = Settings()
    # 绘制屏幕
    screen = pygame.display.set_mode((game_settings.screen_width,game_settings.screen_height))
    backgound_color = (220,230,230)
    pygame.display.set_caption("Alien Invasion")
    # 飞船实例化
    ship = Ship(game_settings,screen)
    # 子弹编组,管理屏幕中所有的子弹,保存继承Sprite类的Bullet实例
    bullets = Group()
    while True:
        # 检测飞船事件:左移右移发射子弹
        gf.check_events(ship,game_settings,screen,bullets)  # 修改飞船移动标志
        ship.update()  # 根据飞船移动标志重新计算飞船中心位置
        # 更新所有子弹位置
        gf.update_bullets(bullets)
        # 绘制飞船子弹
        gf.update_screen(game_settings,screen,ship,bullets)  

run_game()

ship.py

  • 飞船类中主要涉及类初始化 ,飞船位置更新和渲染飞船surface 。
  • 在初始化中主要定义一些代表飞船属性的成员变量 ,如图像 ,飞船矩形外形 ,飞船中心 ,飞船左移标志和右移标志等 。
  • 涉及 pygame 的图像load()方法和get_rect()方法 ,主要是为了之后飞船和外星人进行碰撞计算 ,其实可以直接self.image = pygame.image.load('images/外星飞船.png')加载图像 ,但是图像太大 ,就pygame.transform.smoothscale方法修改图像大小 。
import pygame
class Ship(object):
    """docstring for Ship"""
    def __init__(self, game_settings,screen):
        super(Ship, self).__init__()
        self.game_settings = game_settings
        self.screen = screen  # screen游戏界面
        self.image = pygame.image.load('images/外星飞船.png').convert_alpha()  # 加载飞船图像
        # 修改图像大小
        self.width,self.height = self.image.get_size()
        self.image = pygame.transform.smoothscale(self.image,(self.width//2,self.height//2))
        self.rect = self.image.get_rect()
        self.screen_rect = screen.get_rect()
        # 获取游戏界面的中心x坐标和底部位置便可确定飞船位置
        self.rect.centerx = self.screen_rect.centerx
        self.rect.bottom = self.screen_rect.bottom - game_settings.screen_height / 5
        self.center = float(self.rect.centerx)  # centerx中只能表示整数
        # 移动标志
        self.moving_right = False
        self.moving_left = False

    def update(self):
        """
        根据移动标志调整飞船位置
        更改为根据self.center更新位置
        """
        if self.moving_right and self.rect.right < self.screen_rect.right:
            # self.rect.centerx += 1
            self.center += self.game_settings.ship_speed_factor
        if self.moving_left and self.rect.left > 0:
            # self.rect.centerx -= 1
            self.center -= self.game_settings.ship_speed_factor
        # 更新centerx,因为绘制图形是根据self.rect()
        # self.rect()定位需要根据centerx,bottom
        self.rect.centerx = self.center  

    def blitme(self):
        """在self.rect位置绘制图像"""
        self.screen.blit(self.image,self.rect)

bullet.py 在子弹类中尤为需要说明的是该类继承了Sprite精灵类 ,继承精灵类中的方法 。

import pygame
from pygame.sprite import Sprite

# 继承Sprite类,完善自己的代码
class Bullet(Sprite):
    """docstring for Bullet"""
    def __init__(self, game_settings,screen,ship):
        super(Bullet, self).__init__()
        self.screen = screen
        # # 在0,0位置创建bullet,并设置宽高
        # self.rect = pygame.Rect(0,0,game_settings.bullet_width,game_settings.bullet_height)

        # # 调整子弹位置到飞船所在位置,调整top相同,中心x坐标相同
        # self.rect.centerx = ship.rect.centerx
        # self.rect.top = ship.rect.top
        # # 将y坐标存储为小数值,以便能够微调子弹的速度
        # self.y = float(self.rect.y)
        # self.color = game_settings.bullet_color
        # self.speed_factor = game_settings.bullet_speed_factor
        # 把子弹换成一个好玩的图片,把图片转换成位图
        self.image = pygame.image.load('images/生气.png').convert_alpha()
        # 修改图像大小
        self.width,self.height = self.image.get_size()
        self.image = pygame.transform.smoothscale(self.image,(self.width//10,self.height//10))
        self.rect = self.image.get_rect()
        # 调整子弹位置到飞船所在位置,调整top相同,中心x坐标相同
        self.rect.centerx = ship.rect.centerx
        self.rect.top = ship.rect.top
        # 将y坐标存储为小数值,以便能够微调子弹的速度
        self.y = float(self.rect.y)
        self.speed_factor = game_settings.bullet_speed_factor

    def update(self):
        """向上移动子弹"""
        # 更新一次坐标y向上移动speed_factor个像素
        self.y -= self.speed_factor
        # 更新子弹的rect位置
        self.rect.y = self.y

    def draw_bullet(self):
        # """
        # API:rect(Surface,color,Rect,width=0)->Rect
        # draw a rectangular shape on the Surgace
        # The width argument is the thickness to draw the outer edge. 
        # If width is zero then the rectangle will be filled.
        # """
        # pygame.draw.rect(self.screen,self.color,self.rect)
        """在self.rect位置绘制图像"""
        self.screen.blit(self.image,self.rect)

game_functions.py

  • 该文件的作用主要是集成主函数逻辑函数 ,判断飞船事件 ,更新子弹编组和更新屏幕
  • fill函数填充屏幕严肃 ,参数rgb
  • 需要说明的是 bullets 子弹编组需要更新判断子弹是否超出屏幕 ,以从编组中删除 ,否则会影响效率
  • 鼠标和键盘监听事件pygame.event.get();事件键pygame.K_RIGHT pygame.K_LEFT pygame.K_SPACE 事件类型pygame.KEYDOWN pygame.KEYUP
import pygame
import sys
from bullet import Bullet

def check_keydown_events(event,ship,game_settings,screen,bullets):
    """
    按下键盘事件:右移,左移,发射子弹
    """
    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(game_settings,screen,ship,bullets)

def fire_bullet(game_settings,screen,ship,bullets):
    """
    如果没有超过当前屏幕显示的最多子弹数,实例化子弹,添加到子弹编组中
    """
    if(len(bullets) < game_settings.bullet_allowed):
        bullet = Bullet(game_settings,screen,ship)
        bullets.add(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(ship,game_settings,screen,bullets):
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            sys.exit()
        elif event.type == pygame.KEYDOWN:
            check_keydown_events(event,ship,game_settings,screen,bullets)

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

def update_screen(game_settings,screen,ship,bullets):
    screen.fill(game_settings.background_color)
    ship.blitme()
    """
    API:pygame.sprite.Group.sprites
    sprites() -> sprite_list
    bullet迭代器
    """
    for bullet in bullets.sprites():
        bullet.draw_bullet()
    pygame.display.flip()


def update_bullets(bullets):
    bullets.update()
    # 超出屏幕边界移除子弹
    # 子弹编组副本
    for bullet in bullets.copy():
        if bullet.rect.bottom <= 0:
            bullets.remove(bullet)
    # 注释掉,调试用,耗时
    # print(len(bullets))

settings.py配置文件 将常量变量继承在配置文件中 ,当需要修改常量提高游戏体验时 ,直接修改该文件即可

class Settings(object):
    """docstring for Settings"""
    def __init__(self):
        super(Settings, self).__init__()
        self.screen_width = 1200
        self.screen_height = 800
        self.background_color = (230,230,230)
        self.ship_speed_factor = 1.5  # 飞船一个while循环走1.5像素

        # 子弹配置
        self.bullet_width = 3
        self.bullet_height = 15
        self.bullet_color = (120,120,120)
        self.bullet_speed_factor = 3
        self.bullet_allowed = 5  # 最大子弹数

pygame接口文档总结

API

接口

描述

pygame.init()

init() -> None

pygame.display.set_mode()

set_mode(resolution=(0,0), flags=0, depth=0) -> Surface

Initialize a window or screen for display

pygame.display.set_caption()

set_caption(title, icontitle=None) -> None

pygame.event.get()

pygame.image.load()

load(filename) -> Surface

get_rect()

blit()

pygame.transform.smoothscale()

smoothscale(Surface, (width, height), DestSurface = None) -> Surface

pygame.display.flip()

flip() -> None

Update the full display Surface to the screen

原文发布于微信公众号 - 小詹学Python(xiaoxiaozhantongxue)

原文发表时间:2018-09-01

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏24K纯开源

ChartDirector应用笔记(一)

ChartDirector介绍 ChartDirector是一款小巧精细的商业图表库。其适用的语言范围非常广泛,包括.Net, Java, Asp, VB, ...

2607
来自专栏向治洪

React Native之轻量级存储AsyncStorage

AsyncStorage是一个简单的、异步的、持久化的以键值对形式进行数据存储的存储系统,对于App来说是全局性的。它的作用等价于iOS的NSUserDefal...

3736
来自专栏性能与架构

将 Redis 作为图数据库

1. 简介 Redis 在 4.0 中正式支持了Module模块系统,使其可以进行丰富的扩展 图数据库的应用越来越广泛,RedisGraph 就是一个 Redi...

4476
来自专栏向治洪

React Native之轻量级存储AsyncStorage

AsyncStorage是一个简单的、异步的、持久化的以键值对形式进行数据存储的存储系统,对于App来说是全局性的。它的作用等价于iOS的NSUserDefal...

3266
来自专栏hotqin888的专栏

microstation vba起步——建立实体

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/hotqin888/article/det...

1253
来自专栏前端杂货铺

不要使用浏览器嗅探,尽量使用特性检测和特性模拟

平淡的描述   在js中,能使用特征监测就尽量不要使用浏览器嗅探。嗅探浏览器目的是判断可否使用这个对象或者API,但是抛开浏览器 的各个版本的userAgent...

3055
来自专栏程序员互动联盟

【专业文章】六种常见的HTML5写法误用(二)

四、figure元素的常见错误 figure以及figcaption的正确使用,确实是难以驾驭。让我们来看看一些常见的错误, 不是所有的图片都是figure 上...

2885
来自专栏葡萄城控件技术团队

前端代码标准最佳实践:CSS篇

上一篇《前端代码标准最佳实践:javascript》发表后,大家讨论还是很热烈,从侧面体现了前端工程师对写标准的前端代码的重视程度很高。这些最佳标准实践并不是那...

21510
来自专栏葬爱家族

Android高级动画(3)

上一篇文章我们讲了Android中的矢量动画,虽然文中展示的Demo并不多,但是相信大家还是体会到了矢量动画的强大。这里再做一个温故总结:

1611
来自专栏UAI人工智能

RLLAB 入门

4203

扫码关注云+社区

领取腾讯云代金券