12月26日完成任务
chart
内)、开发思路、核心源码、12月25日开始整理笔记
这是我在利用课余时间在慕课网学习python做的第一个小项目,成功运行的时候还是非常有成就感的,这几天期末考完试,可以闲下来专心整理本次实践过程。现在是12月25日凌晨2时2分,预计在一周后写完这篇博客,并在文末提供CSDN站内下载和GitHub项目地址。
原实战地址为《Python零基础入门》第四章第二课:https://class.imooc.com/course/932
我自己的系统用的是MacOS,以下是需要使用到的开发环境
Python及第三方模块 | 开发工具 | 操作系统 |
---|---|---|
Python3.7 | Pycharm | MacOS/Windows |
Pygame1.9.6 |
我所使用的环境管理系统是conda,飞机大战是做一个简单的游戏开发,为了管理方便,新创建名为AircraftBattle
的虚拟环境,并且项目名保持一致
等待IDE创建好虚拟环境以后,点击打开IDE左下方的Terminal
,此时终端所在位置为当前虚拟环境,输入pip命令安装pygame工具包
pip install pygame
import pygame
等待安装完成后(我这里显示的是已经安装成功),在当前虚拟环境下先输入python命令,接着输入import pygame
进行验证安装。
import pygame 显示当前版本号为1.9.6,说明pygame安装成功,开发环境已经准备完成。
https://www.pygame.org/docs/
pygame的食用方法这里不做详细的说明,除了查看官方文档之外,也可以简单参考我的另一篇学习笔记《pygame游戏开发参考指南》
下图是在项目中的结构截图,其中game_test
包是我用来做测试的备份文件
import pygame
import sys
import constants
def main():
""" 游戏入口,main方法"""
# 初始化
pygame.init()
# 设置游戏标题栏
pygame.display.set_caption("飞机大战")
# 游戏界面的大小由背景图的大小决定
width, height = 480, 852
# 获取屏幕对象
screen = pygame.display.set_mode((width, height))
# 获取背景图片
# 游戏开始标题
# 获取游戏标题的宽度和高度
# 开始按钮
# 加载背景音乐
# 游戏状态
# 游戏主循环:
while True:
# 监听事件
for event in pygame.event.get():
# 退出游戏
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
# 更新游戏状态
# 更新屏幕
pygame.display.flip()
if __name__ == '__main__':
main()
"""
常量文件
"""
import os
import pygame
# 项目的根目录
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
# 静态文件的目录
ASSETS_DTR = os.path.join(BASE_DIR, "assets")
# 背景图片
BG_IMG = os.path.join(ASSETS_DTR, "images/background.png")
# 结束背景图片
BG_IMG_OVER = os.path.join(ASSETS_DTR, "images/game_over.png")
# 背景音乐
BG_MUSIC = os.path.join(ASSETS_DTR, "musics/game_bg_music.wav")
# 游戏分数文字颜色
TEXT_SCORE_COLOR = pygame.Color(255, 255, 0)
# 击中小型敌机10分
SCORE_SHOOT_SMALL = 10
# 游戏分数文件路径
SCORE_FILE = os.path.join(BASE_DIR, 'store/score.txt')
# 游戏标题
GAME_TITLE = os.path.join(ASSETS_DTR, "images/game_title.png")
# 开始游戏的按钮
START_BTN = os.path.join(ASSETS_DTR, "images/game_start.png")
# 我方飞机的静态资源 OurPlane类内引入
OUR_PLANE_IMG_LIST = [
os.path.join(ASSETS_DTR, "images/hero1.png"),
os.path.join(ASSETS_DTR, "images/hero2.png"),
]
# 飞机坠毁的静态资源 在我方飞机类引入
OUR_DESTROY_IMG_LIST = [
os.path.join(ASSETS_DTR, "images/hero_broken_n1.png"),
os.path.join(ASSETS_DTR, "images/hero_broken_n2.png"),
os.path.join(ASSETS_DTR, "images/hero_broken_n3.png"),
os.path.join(ASSETS_DTR, "images/hero_broken_n4.png"),
]
# 子弹的图片
BULLET_IMG = os.path.join(ASSETS_DTR, "images/bullet1.png")
# 子弹发射的声音
BULLET_SHOOT_MUSIC = os.path.join(ASSETS_DTR, "musics/bullet.wav")
# 敌方小型飞机图片
SMALL_ENEMY_PLANE_IMG_LIST = [os.path.join(ASSETS_DTR, "images/enemy1.png")]
# 敌方小型飞机坠毁的图片列表
SMALL_ENEMY_DESTROY_IMG_LIST = [
os.path.join(ASSETS_DTR, "images/enemy1_down1.png"),
os.path.join(ASSETS_DTR, "images/enemy1_down2.png"),
os.path.join(ASSETS_DTR, "images/enemy1_down3.png"),
os.path.join(ASSETS_DTR, "images/enemy1_down4.png")
]
# 小型飞机坠毁时的音乐
SMALL_ENEMY_DOWN_MUSIC = os.path.join(ASSETS_DTR, "musics/enemy1_down.wav")
"""
飞机的基类
1.我方飞机
2.敌方飞机 (小型,中型,大型)
"""
import random
import pygame
import constants
from game.bullet import Bullet
class Plane(pygame.sprite.Sprite):
"""
飞机的基类
"""
# list 用来保存飞机的图片
plane_img = []
# list 飞机爆炸的图片
destroy_img = []
# 飞机的状态 True 活的,False死的
active = True
# 飞机发射的子弹放在精灵组
bullets = pygame.sprite.Group()
# 坠毁的音乐地址
down_music_src = None
# 重写构造方法
def __init__(self, screen, speed=None):
super().__init__()
# 需要拿到屏幕对象
self.screen = screen
# 加载的静态资源
self.img_list = []
self._destroy_list = []
self.down_music = None
self.load_src()
# 飞机的速度 不传的时候默认为10
# 在游戏中就是图片移动的速度
self.speed = speed or 10
# 获取飞机所在位置,取飞机的第一张图片
self.rect = self.img_list[0].get_rect()
# 获取飞机的高度和宽度
self.plane_width, self.plane_height = self.img_list[0].get_size()
# 得到游戏窗口的宽和高
self.width, self.height = self.screen.get_size()
# 改变飞机的初始化位置,放在屏幕的下方
self.rect.left = int((self.width - self.plane_width) / 2)
self.rect.top = int(self.height / 2)
def load_src(self):
"""加载静态资源"""
# 飞机图像
for img in self.plane_img:
self.img_list.append(pygame.image.load(img))
# 飞机坠毁的图像
for img in self.destroy_img:
self._destroy_list.append(pygame.image.load(img))
# 飞机坠毁的音乐
if self.down_music_src:
self.down_music = pygame.mixer.Sound(self.down_music_src)
@property
def image(self):
return self.img_list[0]
# 在当前类绘制飞机
def blit_me(self):
self.screen.blit(self.image, self.rect)
def move_up(self):
"""飞机向上移动"""
self.rect.top -= self.speed # y值变小,不断减去速度
def move_down(self):
"""飞机向下移动"""
self.rect.top += self.speed # y值变大,不断加上速度
def move_left(self):
"""飞机向左移动"""
self.rect.left -= self.speed # x值变小
def move_right(self):
"""飞机向右移动"""
self.rect.left += self.speed # x值变大
def broken_down(self):
"""飞机坠毁"""
# 1.坠毁播放坠毁音乐
if self.down_music:
self.down_music.play() # 只需要播放一次
# 2.播放坠毁的动画
for img in self._destroy_list:
self.screen.blit(img, self.rect)
# 3.坠毁后飞机的状态
self.active = False
def shoot(self):
"""飞机都可以发射子弹"""
# 往精灵组内不断的添加子弹,然后再main()中改变精灵组的位置
bullet = Bullet(self.screen, self, 15)
self.bullets.add(bullet)
class OurPlane(Plane):
"""我方的飞机"""
# list 用来保存飞机的图片资源
plane_img = constants.OUR_PLANE_IMG_LIST # 放图片的地址,地址写在常量里
# list 飞机爆炸的图片
destroy_img = constants.OUR_DESTROY_IMG_LIST
# 坠毁的音乐地址
down_music_src = None
def update(self, war):
"""我方飞机的动画切换"""
# self.move(war.key_down)
# 1.飞机的动画效果,喷气式效果
if war.frame % 5:
self.screen.blit(self.img_list[0], self.rect)
else:
self.screen.blit(self.img_list[1], self.rect)
# 2.飞机撞机的检测
rest = pygame.sprite.spritecollide(self, war.enemies, False)
if rest:
# 1.撞机了,游戏结束 状态判断
war.status = war.OVER
# 2.游戏结束,敌方飞机消失 empty()移除精灵组
war.enemies.empty()
war.small_enemies.empty()
# 3.我方飞机坠毁的效果
self.broken_down()
# 4.统计游戏分数
def move(self, key):
"""飞机移动自动控制"""
if key == pygame.K_w or key == pygame.K_UP:
self.move_up()
elif key == pygame.K_a or key == pygame.K_LEFT:
self.move_left()
elif key == pygame.K_s or key == pygame.K_DOWN:
self.move_down()
elif key == pygame.K_d or key == pygame.K_RIGHT:
self.move_right()
# 重写的方法来控制我方飞机不能飞出边界
def move_up(self):
"""向上移动,超出范围重置为0"""
super().move_up()
if self.rect.top <= 0:
self.rect.top = 0
def move_down(self):
"""向下移动,超出范围归减去超出部分"""
super().move_down()
# 运动到最底下还需要减去飞机高度
if self.rect.top >= self.height - self.plane_height:
self.rect.top = self.height - self.plane_height
def move_left(self):
"""向左移动,超出范围归0"""
super().move_left()
if self.rect.left <= 0:
self.rect.left = 0
def move_right(self):
""""向右移动,超出范围减去"""
super().move_right()
if self.rect.left >= self.width - self.plane_width:
self.rect.left = self.width - self.plane_width
class SmallEnemyPlane(Plane):
"""敌方小型飞机类"""
plane_img = constants.SMALL_ENEMY_PLANE_IMG_LIST
# list 飞机爆炸的图片
destroy_img = constants.SMALL_ENEMY_DESTROY_IMG_LIST
# 坠毁的音乐地址
down_music_src = constants.SMALL_ENEMY_DOWN_MUSIC
def __init__(self, screen, speed):
"""敌方飞机从屏幕上方随机出现"""
super().__init__(screen, speed)
# 每次生成一架小型飞机的时候,随机出现在屏幕中
# 改变飞机的随机位置
self.init_pos()
def init_pos(self):
"""提高代码复用,改变飞机的随机位置"""
# 0到最右边的宽度减去飞机宽度 屏幕宽度-飞机宽度
self.rect.left = random.randint(0, self.width - self.plane_width)
# 屏幕之外飞机高度,后面随机摆五架飞机
self.rect.top = random.randint(-5 * self.plane_height, -self.plane_height)
def update(self, *args):
"""更新敌方飞机的移动"""
super().move_down()
# 画在屏幕上
self.blit_me()
# 超出范围,如何处理
# 1.重用
if self.rect.top >= self.height:
self.active = False
# self.kill()
self.reset()
# TODO 2.多线程、多进程
def reset(self):
"""重置飞机的状态,达到复用的效果"""
self.active = True
self.init_pos()
def broken_down(self):
"""重写爆炸效果"""
super().broken_down()
# 重置飞机状态
self.reset()
import pygame
import constants
""" 封装子弹 """
class Bullet(pygame.sprite.Sprite):
"""子弹类"""
# 子弹状态 True:活着,False:死的 超出屏幕或者发生碰撞
active = True
def __init__(self, screen, plane, speed=None):
super().__init__()
self.screen = screen
# 速度
self.speed = speed or 10
self.plane = plane
# 加载子弹的图片
self.img = pygame.image.load(constants.BULLET_IMG)
# 改变子弹的位置
self.rect = self.img.get_rect()
self.rect.centerx = plane.rect.centerx # 子弹中心等于飞机中心
self.rect.top = plane.rect.top # 子弹顶部等于飞机顶部
# 发射的音乐效果
self.shoot_music = pygame.mixer.Sound(constants.BULLET_SHOOT_MUSIC)
# 设置子弹发射声音大小
self.shoot_music.set_volume(0.3)
# 发射出去其实就是创建一个新的子弹对象,直接播放
self.shoot_music.play()
def update(self, war):
"""更新改变子弹的位置,我方飞机的子弹一定是向上移动的"""
self.rect.top -= self.speed
# 超出屏幕范围后
if self.rect.top < 0:
self.remove(self.plane.bullets)
# 验证精灵组超出范围移除
# print(self.plane.bullets)
# 绘制子弹
self.screen.blit(self.img, self.rect)
# 碰撞检测,检测子弹是否已经碰撞到了敌机 是一个列表
rest = pygame.sprite.spritecollide(self, war.enemies, False)
# 子弹打中
for r in rest:
# 1.子弹不能继续飞行
self.kill()
# 2.飞机坠毁效果
r.broken_down()
# 3.统计游戏成绩
war.rest.score += constants.SCORE_SHOOT_SMALL
# 保存历史记录
war.rest.set_history()
"""
用于分数统计
"""
import constants
class PlayRest(object):
"""玩家的结果"""
__score = 0 # 总分
@property
def score(self):
"""单词游戏分数"""
return self.__score
@score.setter
def score(self, value):
"""设置游戏分数"""
if value < 0:
return None
self.__score = value
def set_history(self):
"""记录最高分"""
# 1. 读取文件中存储的分数
# 2. 和新分数对比保存最大值
# 3. 存储分数,替换文件 w模式
if int(self.get_max_score()) < self.score:
with open(constants.SCORE_FILE, 'w') as f:
f.write('{0}'.format(self.score))
def get_max_score(self):
"""读取文件中的历史最高分"""
rest = 0
with open(constants.SCORE_FILE, 'r') as f:
temp = f.read()
if temp:
rest = temp
return rest
"""
飞机大战实现,主要功能类
"""
import pygame
import sys
import constants
from game.plane import OurPlane, SmallEnemyPlane
from store.result import PlayRest
class PlaneWar(object):
"""飞机大战,优化main函数入口"""
# 游戏状态
# 游戏准备中,游戏中,结束 0,1,2表示
READY = 0 # 游戏准备中
PLAYING = 1 # 正在游戏当中
OVER = 2 # 游戏结束
status = READY # 默认游戏状态
# 加入我方飞机 开始指定为None
our_plane = None
frame = 0 # 播放的帧数
clock = pygame.time.Clock()
# 一架飞机可以属于多个精灵组
# 所有小型敌机是一个精灵组
small_enemies = pygame.sprite.Group()
# 所有敌机是一个精灵组
enemies = pygame.sprite.Group()
# 游戏结果
rest = PlayRest()
def __init__(self):
# 初始化游戏
pygame.init()
# 设置游戏标题栏
pygame.display.set_caption("飞机大战")
# 游戏窗口大小
self.width, self.height = 480, 852
# 获取屏幕对象
self.screen = pygame.display.set_mode((self.width, self.height))
# 获取背景图片
self.bg = pygame.image.load(constants.BG_IMG)
# 游戏结束的背景
self.bg_over = pygame.image.load(constants.BG_IMG_OVER)
# 游戏开始标题
self.game_title = pygame.image.load(constants.GAME_TITLE)
self.game_title_rect = self.game_title.get_rect()
# 获取游戏标题的宽度和高度
t_width, t_height = self.game_title.get_size()
self.game_title_rect.topleft = (int((self.width - t_width) / 2),
int(self.height / 2 - t_height))
# 开始按钮
self.btn_start = pygame.image.load(constants.START_BTN)
self.btn_start_rect = self.btn_start.get_rect()
b_width, b_height = self.btn_start.get_size()
self.btn_start_rect.topleft = (int((self.width - b_width) / 2),
int(self.height / 2 + b_height))
# 游戏文字对象
self.score_font = pygame.font.SysFont('华文楷体', 40)
# 加载背景音乐
pygame.mixer.music.load(constants.BG_MUSIC)
pygame.mixer.music.play(-1) # 无限循环播放
pygame.mixer.music.set_volume(0.2) # 设置音量
# 我方飞机对象
self.our_plane = OurPlane(self.screen, speed=20)
self.clock = pygame.time.Clock()
# 上一次按的键盘上的某一个键,用来控制飞机
# self.key_down = None
def bind_event(self):
"""绑定事件"""
# 监听事件
for event in pygame.event.get():
# 退出游戏
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
elif event.type == pygame.MOUSEBUTTONDOWN:
# 鼠标点击进入游戏
# 游戏正在准备中,点击才能进入游戏
if self.status == self.READY:
self.status = self.PLAYING
elif self.status == self.OVER:
# 游戏结束以后点击继续开始
self.status = self.READY
# 点击继续开始以后递归调用run_game
# 需要再次添加飞机
self.add_small_enemies(6)
# 需要重置分数
self.rest.score = 0
self.run_game()
elif event.type == pygame.KEYDOWN:
# 键盘事件
# self.key_down = event.key
# 游戏正在进行中,才需要键盘控制 WASD四个键
if self.status == self.PLAYING:
if event.key == pygame.K_w or event.key == pygame.K_UP:
self.our_plane.move_up()
elif event.key == pygame.K_a or event.key == pygame.K_LEFT:
self.our_plane.move_left()
elif event.key == pygame.K_s or event.key == pygame.K_DOWN:
self.our_plane.move_down()
elif event.key == pygame.K_d or event.key == pygame.K_RIGHT:
self.our_plane.move_right()
# 空格键发射子弹
elif event.key == pygame.K_SPACE:
self.our_plane.shoot()
def add_small_enemies(self, num):
"""随机生成N架敌机"""
for i in range(num):
plane = SmallEnemyPlane(self.screen, 8)
plane.add(self.small_enemies, self.enemies)
def run_game(self):
"""游戏主循环"""
while True:
# 1.设置帧速率
self.clock.tick(60)
self.frame += 1 # 每循环一次,frame+1
# 游戏一直运行,frame值可能一直变大 当frame大于60 重置为0
if self.frame >= 60:
self.frame = 0
# 2.绑定事件
self.bind_event()
# 3.更新游戏的状态
if self.status == self.READY:
# 游戏正在准备中
# 绘制背景
self.screen.blit(self.bg, self.bg.get_rect())
# 正在准备中的标题
self.screen.blit(self.game_title, self.game_title_rect)
# 开始按钮
self.screen.blit(self.btn_start, self.btn_start_rect)
# self.key_down = None # 设置游戏结束以后重新开始不保留游戏状态
elif self.status == self.PLAYING:
# 表示游戏进行中
# 绘制背景
self.screen.blit(self.bg, self.bg.get_rect())
# 绘制飞机 调用绘制飞机的方法
self.our_plane.update(self)
# 绘制子弹
self.our_plane.bullets.update(self) # 子弹类传了war对象
# 绘制敌方飞机
self.small_enemies.update()
# 游戏分数
score_text = self.score_font.render(
'Score:{0}'.format(self.rest.score),
False,
constants.TEXT_SCORE_COLOR
)
# 不需要改变位置,直接放在左上角
self.screen.blit(score_text, score_text.get_rect())
elif self.status == self.OVER:
# 游戏结束的状态
# 游戏结束的背景
self.screen.blit(self.bg_over, self.bg_over.get_rect())
# 分数的统计
# 1.绘制总分
score_text = self.score_font.render(
'{0}'.format(self.rest.score),
False,
constants.TEXT_SCORE_COLOR
)
score_text_rect = score_text.get_rect()
text_width, text_height = score_text.get_size()
# 改变文字的位置
score_text_rect.topleft = (
int((self.width - text_height) / 2),
int(self.height / 2)
)
self.screen.blit(score_text, score_text_rect)
# 2.历史最高分
score_history = self.score_font.render(
'{0}'.format(self.rest.get_max_score()),
False,
constants.TEXT_SCORE_COLOR
)
self.screen.blit(score_history, (150, 40))
# 更新屏幕
pygame.display.flip()
"""
Title: AircraftBattle
Author:Wankcn
Created:2019-12-26
"""
from game.war import PlaneWar
def main():
""" 游戏入口,main方法"""
war = PlaneWar()
# 添加小型敌机
war.add_small_enemies(6)
war.run_game()
if __name__ == '__main__':
main()
使用面向对象的思想设计飞机大战,加深对类定义、属性与方法的理解与运用。
项目还可以继续扩展完善,引入多线程,多进程扩展敌机数量,游戏中还未加入中型大型敌机,不具备暂停与开始功能,飞机还可以添加血值生命等。
https://github.com/WanKcn/AircraftBattle
https://download.csdn.net/download/wankcn/12052799