游戏介绍:
使用Pygame工具包开发一款2D游戏,玩家控制飞船消灭一群向下移动的外星人,每消灭一群外星人,游戏难度提高一个等级,节奏加快。
内容
本文聚焦于游戏主体模块的设计和实现,暂不考虑交互过程。
框架设计
1.安装Pygame工具包(Window系统):
conda install Pygame -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com
2.模块设计
主模块:alien_invasion.py
- 搭建游戏界面窗口
- 循环检测并响应事件
- 刷新游戏界面内容
辅助模块:
-
存储整个游戏相关参数:settings.py
-
存储游戏相关函数:game_functions.py
-
飞船类:ship.py
-
外星人类:alien.py
-
子弹类:bullets.py
-
游戏统计信息类:game_stats.py
-
记分牌类:score_board.py
-
按钮类:button.py
代码实现
主模块的逻辑比较简单且清晰,基本上就是-“搭界面-检测事件-响应-更新界面”。
gf.check_events()、ship.update()、gf.update_bullets()、gf.update_aliens()四个重点方法会放在交互部分介绍(game_functions.py)
alien_invasion.py
import pygame
from pygame.sprite import Group
from settings import Settings
import game_functions as gf
from ship import Ship
from bullet import Bullet
from alien import Alien
#-------------暂时不用管-----------------
from game_stats import GameStats
from button import Button
from scoreboard import Scoreboard
def run_game():
'''初始化游戏并创建一个屏幕对象'''
#-----------------搭建游戏界面窗口 ------------------
#初始化pygame,设置屏幕对象
pygame.init()#初始化背景
ai_settings = Settings()#读取settings中的游戏设置
screen = pygame.display.set_mode((
ai_settings.screen_width,ai_settings.screen_height
))#设置游戏界面大小
pygame.display.set_caption(ai_settings.caption_name)#显示界面标题
#初始化游戏元素 #创建一艘飞船
ship = Ship(ai_settings,screen)
#创建一个用于存储子弹的编组,以便能够管理所有发射出去的子弹
bullets = Group()
#创建一个外星人编组
aliens = Group()
#创建外星人群
gf.create_fleet(ai_settings,screen,ship,aliens)
#-----------------循环检测并响应事件 ----------------
while True:
##检查用户输入并响应
gf.check_events(ai_settings,screen,stats,sb,play_button,ship,aliens,bullets)
if stats.game_active:#stats.game_active标记游戏处于运行状态,本文暂未涉及,可理解为TRUE即可
#更新游戏元素的位置参数
#更新飞船位置
ship.update()
#更新子弹位置
gf.update_bullets(ai_settings,screen,stats,sb,ship,aliens,bullets)
#更新外星人的位置
gf.update_aliens(ai_settings,screen,stats,sb,ship,aliens,bullets)
#刷新游戏界面,显示更新后的元素位置
#-----------------刷新游戏界面内容 ----------------
gf.update_screen(ai_settings,screen,stats,sb,ship,aliens,bullets,play_button)
if __name__ == "__main__":
#运行游戏
run_game()
------------------下面先来看一下辅助模块部分--------------------
settings.py中的内容:
所有的游戏设置都存储在该文件中,包括了游戏的界面窗口属性、子弹属性、外星人和飞船的部分属性(其他属性在对应的模板类中存储),同时该某块实现了两个方法:1.对可变参数的初始化;2.对可变参数的更新;
settings.py
class Settings():
'''存储《外星人入侵》的所有设置的类'''
def __init__(self):
'''初始化游戏的设置'''
#静态设置---游戏设置分为两组
#游戏界面设置
self.screen_width = 1200#宽
self.screen_height = 600#高
self.bg_color = (220,220,220)#背景色
self.caption_name = 'Alien Invasion'
#飞船的设置
# self.ship_speed_factor = 1.5
self.ship_limit = 3#可用飞船数
#子弹设置
# self.bullet_speed_factor = 1
self.bullet_width = 3#子弹宽
self.bullet_height = 15#子弹高
self.bullet_color = (60,60,60)#子弹颜色
self.bullets_allowed = 5#允许在界面中显示的子弹数量,限制用户发射子弹数量
#外星人的设置
# self.alien_speed_factor = 1
self.fleet_drop_speed = 10#外星人向下移动速度
# self.fleet_direction = 1#1表示向右移动,-1表示向左移动
#游戏过程中的动态设置
#以什么样的速度加快游戏节奏
self.speed_up_scale = 1.1
#击杀外星人点数的提高速度
self.score_scale = 1.5
self.initialize_dynamic_setting()#一些随游戏变动的参数的初始化
def initialize_dynamic_setting(self):
'''初始化随游戏进行而变化的设置'''
self.ship_speed_factor = 1.5#飞船移动初始速度
self.bullet_speed_factor = 3#子弹移动初始速度
self.alien_speed_factor = 1 #外星人移动初始速度
self.fleet_direction = 1#外星人移动方向标记,+1向右移动,-1向左移动
#每个外星人被击杀计分
self.alien_points = 50
def increase_speed(self):
'''难度提升:提高速度的设置和外星人点数'''
self.ship_speed_factor *= self.speed_up_scale#飞船移动速度
self.bullet_speed_factor *= self.speed_up_scale#子弹移动速度
self.alien_speed_factor *= self.speed_up_scale #外星人移动速度
self.alien_points = int(self.alien_points * self.score_scale)#难度提升,外星人点数得分也增加
接下来看一下飞船类ship.py的实现:
内容-加载飞船图像,返回关于飞船的pygame.Rect对象,包含了绘制飞船的方法,移动方向的调整和飞船位置的重置。
ship.py
import pygame
from pygame.sprite import Sprite
class Ship(Sprite):
'''飞船类,用于管理飞船的大部分行为'''
def __init__(self,ai_settings,screen):
'''初始化飞船并设置其初始位置'''
super().__init__()
self.screen = screen#要绘制的屏幕
self.ai_settings = ai_settings#Settings类的实例
#加载飞船图像并获取其外接矩形
self.image = pygame.image.load('images\ship.bmp')#返回飞船的surface
self.rect = self.image.get_rect()#得到飞船图片的矩形 rect对象
self.screen_rect = screen.get_rect()#得到屏幕的矩形
#将飞船放在屏幕底部中间
#!!!坐标值:界面窗口的左上角为(0,0),向右、下都是坐标值都是逐渐增大
self.rect.centerx = self.screen_rect.centerx#屏幕矩形的中间坐标值
self.rect.bottom = self.screen_rect.bottom#屏幕底部的坐标值
#在飞船的属性center中存储小数
self.center = float(self.rect.centerx)#rect.centerx只能存储整数
self.bottom = float(self.rect.bottom)
#移动标志,True表示向相应的方向移动
self.moving_right = False
self.moving_left = False
self.moving_up = False
self.moving_down = False
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.rect.centerx +=1
self.center += self.ai_settings.ship_speed_factor
# elif self.moving_left:#这里不用elif: 若用elif则右移始终处于优先事件,在按住左键不动时按右键仍然可以右移动,但反过来就不能左移!!!重要!!!
if self.moving_left and self.rect.left > 0: #此时两个左右按键同时按下的话飞船保持不动 #检查是否超出屏幕范围(左边界)
# self.rect.centerx -=1
self.center -= self.ai_settings.ship_speed_factor
if self.moving_up and self.bottom > self.rect.height: #此时上下两个按键同时按下的话飞船保持不动 #检查是否超出屏幕范围(上边界)
# self.rect.centerx -=1
self.bottom -= self.ai_settings.ship_speed_factor
if self.moving_down and self.rect.bottom < self.screen_rect.bottom: #此时两个按键同时按下的话飞船保持不动 #检查是否超出屏幕范围(下边界)
# self.rect.centerx -=1
self.bottom += self.ai_settings.ship_speed_factor
#根据self.center,bottom 跟新rect对象的坐标值
self.rect.centerx = self.center
self.rect.bottom = self.bottom
def center_ship(self):
'''把飞船放到屏幕底部中间'''
self.rect.centerx = self.screen_rect.centerx#屏幕矩形的中间坐标值
self.rect.bottom = self.screen_rect.bottom#屏幕底部的坐标值
#移动的累计值也要清零!
self.center = float(self.rect.centerx)#rect.centerx只能存储整数
self.bottom = float(self.rect.bottom)
然后是关于外星人alien.py的类:
内容:初始化加载外星人图像,返回关于外星人的pygame.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
#加载外星人图像,并设置其rect属性
self.image = pygame.image.load(r'images\alien.bmp')#r消除转义功能
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):
'''如果外星人位于屏幕(左右两边)边缘,就返回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.x += (self.ai_settings.alien_speed_factor*self.ai_settings.fleet_direction)#fleet_direction表示移动的方向+1向右,-1向左
self.rect.x = self.x
关于子弹bullets.py的类:
内容——初始化一个子弹对象并设置其位置,,包含位置更新和绘制子弹两个方法
bullets.py
import pygame
from pygame.sprite import Sprite
class Bullet(Sprite):
'''一个对飞船发射的子弹进行管理的类'''
def __init__(self,ai_settings,screen,ship):
'''在飞船所处的位置创建一个子弹对象'''
super().__init__()#继承Sprite,可将游戏中相关元素编组,进而同时对所有的编组元素进行操作
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#飞船中间的x坐标
self.rect.top = ship.rect.top#飞船上边界y坐标
#用小数存储子弹的位置 子弹运动过程中X坐标值不会改变,y会不断减小
self.y = float(self.rect.y)
self.color = ai_settings.bullet_color
self.speed_factor = ai_settings.bullet_speed_factor#子弹移动速度
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)
游戏统计信息类game_stats.py:
内容——记录可用的飞船数量,游戏得分,和累计的游戏最高得分
game_stats.py
# from settings import Settings
class GameStats():
'''跟踪游戏中的统计信息'''
def __init__(self,ai_settings):
''''初始化统计信息'''
self.ai_settings = ai_settings
self.reset_stats()
# self.game_active = True#标记游戏是否要结束
self.game_active = False#先设置为非活跃状态,由用点击开始游戏
#在任何情况下都不应重置最高得分
# self.high_score = 0#记录游戏最高得分,不会随游戏重置而更新
#从记录中读取游戏最高分
try:
with open('recode_high_score.txt','r') as f:
content = f.readline().strip()
if content:
self.high_score = int(content)
else:
self.high_score = 0
except FileNotFoundError:
self.high_score = 0
def reset_stats(self):
'''初始化再游戏运行期间可能变化的统计信息'''
self.ships_left = self.ai_settings.ship_limit#可用的飞船数目,初始为3
self.score = 0#记录游戏得分
self.level = 1
记分牌类scoreboard.py:
内容——用于在屏幕中显示当前得分,游戏最高分,等级,剩余飞船数。主要包含了 self.prep_score()、self.prep_high_score()、self.prep_level()、self.prep_ships()四个将文本/数据渲染成图像的方法,然后可以用show_score()进行图像绘制。
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 = (50,50,50)
self.font = pygame.font.SysFont(None,48)
#创建初始得分图像
self.prep_score()#将要显示的文本渲染成图片来显示
self.prep_high_score()
self.prep_level()
self.prep_ships()#创建飞船图像
def prep_score(self):
'''将得分转换为一副渲染的图像'''
# score_str = str(self.stats.score)
#将得分圆整
rounded_score = int(round(self.stats.score,-1))#round(number,-1)使得number圆整到最近得10得整数倍
score_str = "score:{:,}".format(rounded_score)#使用千位分隔符输出1,000,000
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 = 20
def prep_high_score(self):
'''将最高分渲染为图像'''
high_score = int(round(self.stats.high_score,-1))#round(number,-1)使得number圆整到最近得10得整数倍
high_score_str = "High_score:{:,}".format(high_score)#使用千位分隔符输出1,000,000
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.centerx = self.screen_rect.centerx
self.high_score_rect.top = self.score_rect.top
def prep_level(self):
'''将等级渲染为图像'''
self.level_image = self.font.render('Level:'+str(self.stats.level),True,self.text_color,self.ai_settings.bg_color)
#将最高得分放置在屏幕顶部中间
self.level_image_rect = self.high_score_image.get_rect()
self.level_image_rect.right = self.score_rect.right
self.level_image_rect.top = self.score_rect.bottom + 10
def prep_ships(self):
'''将飞船数目转化为飞船图像'''
self.ships = Group()
for ship_number in range(self.stats.ships_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_image_rect)
self.ships.draw(self.screen)#绘制编组中得每一艘飞船
按钮类button.py:
内容——初始化按钮对象,将要显示的文本渲染成图像prep_msg()并可调用draw_button()方法进行绘制。
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,255,0)
self.text_color = (255,255,255)
self.font = pygame.font.SysFont(None,48)
#创建按钮对象,并使其居中
self.rect = pygame.Rect(0,0,self.width,self.height)
self.rect.center = self.screen_rect.center
#按钮的标签只需创建一次
self.prep_msg(msg)#将要显示的文本渲染成图片来显示
def prep_msg(self,msg):
'''将msg渲染成图像,并使其在按钮上居中'''
self.msg_image = self.font.render(msg,True,self.text_color,self.button_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.button_color,self.rect)#绘制按钮矩形
self.screen.blit(self.msg_image,self.msg_image_rect)#绘制文本图像
项目小结:
1.尽量将要设置的参数存放到一个地方去,如setting.py,以便于修改和维护;
2.先实现逻辑,再不断对代码重构,实现代码复用。尽量让一个函数只做一件事;
3.函数的命名要能突出其作用/功能。
附上一个游戏界面截图: