日知录(七):python之理解pygame飞机大战
文章目录
飞机大战,是一个入门pygame的经典项目。前几个月仔细研究了一番之后,可能还有些不是很懂。这几个星期把python又重新看了一遍,那么我也再把飞机大战复盘,自己理解一遍。在此也贴上自己的启蒙网站:目光博客。
https://eyehere.net/2011/python-pygame-novice-professional-1/
这位博主的飞机大战十分详尽,非常棒!
一、明确目标
1.面对对象分析
思考在飞机大战中需要哪几个对象,对象有什么属性和方法,即4W1H分析,Who When Where What How,
对象:英雄机、敌机、子弹
以英雄机为例:
who | 英雄机 |
---|---|
when | 游戏开始时 |
where | 在屏幕内随鼠标移动而移动,鼠标移出屏幕时游戏暂停 |
what | 躲避并击打敌机,在碰到敌机后生命值降低且显示飞机爆炸图片 |
how | 发射子弹 |
ho | 敌机 |
---|---|
when | 游戏开始时 |
where | 在一定时间间隔内,任意出现 |
what | 在碰到英雄机或被子弹击打到时显示飞机爆炸图片之后消失,英雄机的生命值随之也有改变 |
how | 向前行驶 |
who | 子弹 |
---|---|
when | 英雄机开始移动时或点击英雄机时 |
where | 在英雄机的上方发出子弹 |
what | 在碰到敌机时消失 |
how | 向前行驶 |
(1)飞机大战就是围绕着3个对象展开的,可以看看它们都有移动,出现,碰撞这3个方法,同时具备一些相同的初始属性,初始位置,图片大小,生命值等,所以,可以把这些具有共性的先写在一个class FlyObject() 里面。
(2)同时我们也需要一个sky()类,用来描述外部世界背景,比如:怎么让背景图片的天空动起来?
(3)游戏的运行状态:开始时,运行时,暂停时,结束时。设计到对鼠标事件的监听
(4)定时刷新界面,重绘图形,FPS帧率。
2.pygame基础
Pygame是一个利用SDL库的写就的游戏库,SDL是用C写的,不过它也可以使用C++进行开发,当然还有很多其它的语言,Pygame就是Python中使用它的一个库。Pygame已经存在很多时间了,许多优秀的程序员加入其中,把Pygame做得越来越好。
就产品而言,Pygame更致力于2D游戏的开发。
1.模块
模块名 | 功能 |
---|---|
pygame.cdrom | 访问光驱 |
pygame.cursors | 加载光标 |
pygame.display | 访问显示设备 |
pygame.draw | 绘制形状、线和点 |
pygame.event | 管理事件 |
pygame.font | 使用字体 |
pygame.image | 加载和存储图片 |
pygame.joystick | 使用游戏手柄或者 类似的东西 |
pygame.key | 读取键盘按键 |
pygame.mixer | 声音 |
pygame.mouse | 鼠标 |
pygame.movie | 播放视频 |
pygame.music | 播放音频 |
pygame.overlay | 访问高级视频叠加 |
pygame.rect | 管理矩形区域 |
pygame.sndarray | 操作声音数据 |
pygame.sprite | 操作移动图像 |
pygame.surface | 管理图像和屏幕 |
pygame.time | pygame.time |
pygame.transform | 缩放和移动图像 |
pygame | 就是我们在学的这个 |
2.创建窗口
background_image_filename='1.jpg'
mouse_image_fliename ='2.png'
# 指定图像文件名称
import pygame
# 导入pygame库
from pygame.locals import *
# 导入一些常用的函数和常量
from sys improt exit
# 向sys模块借一个exit函数用来退出程序
pygame .init()
#初始化pygame,为使用硬件做准备
screen =pygame.display.set_mode((640,480),0,32)
# 创建了一个窗口
pygame .display.set_caption('Hello,World!')
# 设置窗口标题
background=pygame.image,load(backgrounf_image_filename).covert()
mouse_cursor=pygame.image.load(mouse_image_filename).covert_alpha()
# 加载并转换图像
while True:
# 游戏主循环
for event in pygame .evenr.get():
if event.type ==QUIT:
exit()
#接收到退出事件后退出程序
screen .blit(background,(0,0))
#将背景图画上去
x,y=pygame .mouse.get_pos()
#获得鼠标位置
x-=mouse_cursor.get_sidth()/2
y-=mouse_cursor.get_height()/2
# 计算鼠标的左上角位置
screen .blit(mouse_sursor,(x,y))# 把光标画上去
pygame.display.update()
#刷新一下画面
3.处理事件
上面代码中的
if event.type ==QUIT
exit
其中QUIT就是一个事件。上面代码中的程序会一直运行下去,直到你关闭窗口而产生了一个QUIT事件,Pygame会接受用户的各种操作(比如按键盘,移动鼠标等)产生事件。
1.鼠标事件(MOUSEMOTION)
MOUSEBUTTONDOWN和MOUSEBUTTONUP
2.键盘事件
KEYDOWN和KEYUP
key – 按下或者放开的键值,是一个数字,估计地球上很少有人可以记住,所以Pygame中你可以使用K_xxx来表示,比如字母a就是K_a,还有K_SPACE和K_RETURN等。
4.显示与字体
1.屏幕显示
screen = pygame.display.set_mode((640, 480), FULLSCREEN, 32)
# 第二个参数设置为FULLSCREEN时,就能得到一个全屏窗口
同时,如果你把屏幕设置成全屏之后可能不太容易退出来。
background_image_filename = 'sushiplate.jpg'
import pygame
from pygame.locals import *
from sys import exit
pygame.init()
screen = pygame.display.set_mode((640, 480), 0, 32)
background = pygame.image.load(background_image_filename).convert()
Fullscreen = False
while True:
for event in pygame.event.get():
if event.type == QUIT:
exit()
if event.type == KEYDOWN:
if event.key == K_f:
Fullscreen = not Fullscreen
if Fullscreen:
screen = pygame.display.set_mode((640, 480), FULLSCREEN, 32)
else:
screen = pygame.display.set_mode((640, 480), 0, 32)
screen.blit(background, (0,0))
pygame.display.update()
此处设计了一个键盘事件的监听,用f 键控制是否全屏。
2.字体
my_font = pygame.font.SysFont("arial", 16)# 第一个参数是字体名,第二个自然就是大小
text_surface = my_font.render("Pygame is cool!", True, (0,0,0), (255, 255, 255))
# 第一个参数是写的文字;
# 第二个参数是个布尔值,以为这是否开启抗锯齿,就是说True的话字体会比较平滑,
# 不过相应的速度有一点点影响;第三个参数是字体的颜色;
# 第四个是背景色,如果你想没有背景色(也就是透明),那么可以不加这第四个参数。
3.图像
在游戏中我们往往使用RGBA图像,这个A是alpha,也就是表示透明度的部分,值也是0~255,0代表完全透明,255是完全不透明,而像100这样的数字,代表部分透明。
4.帧率FPS
先理解几个常用的量:一般的电视画面是24FPS;30FPS基本可以给玩家提供流程的体验了;LCD的话,60FPS是常用的刷新频率。
我觉得对于这个的理解非常重要,所以也搬点代码来看看。
让动画基于时间运作,我们需要知道上一个画面到现在经过多少时间,然后我们才能决定是否开始绘制下衣服。pygame.time 模块给我们提供了一个clock的对象,使我们可以轻易做到这一点
clock = pygame.time.Clock()
# 初始化了一个Clock对象
time_passed = clock.tick()
# 返回一个上次调用的时间(以毫秒计)
time_passed = clock.tick(30)
# 在每一个循环中加上它,那么给tick方法加上的参数就成为了游戏绘制的最大帧率
但是这仅仅是“最大帧率”,并不能代表用户看到的就是这个数字,有些时候机器性能不足,或者动画太复杂,实际的帧率达不到这个值,我们需要一种更有效的手段来控制我们的动画效果。
为了使得在不同机器上有着一致的效果,我们其实是需要给定物体(我们把这个物体叫做精灵,Sprite)恒定的速度。这样的话,从起点到终点的时间点是一样的,最终的效果也就相同了,所差别的,只是流畅度。看下面的图试着理解一下~
搬的是目光博客大佬的图片。
background_image_filename = 'sushiplate.jpg'
sprite_image_filename = 'fugu.png'
import pygame
from pygame.locals import *
from sys import exit
pygame.init()
screen = pygame.display.set_mode((640, 480), 0, 32)
background = pygame.image.load(background_image_filename).convert()
sprite = pygame.image.load(sprite_image_filename)
# Clock对象
clock = pygame.time.Clock()
x = 0.
# 速度(像素/秒)
speed = 250.
while True:
for event in pygame.event.get():
if event.type == QUIT:
exit()
screen.blit(background, (0,0))
screen.blit(sprite, (x, 100))
time_passed = clock.tick()
time_passed_seconds = time_passed / 1000.0
distance_moved = time_passed_seconds * speed
x += distance_moved
# 想一下,这里减去640和直接归零有何不同?
if x > 640.:
x -= 640.
pygame.display.update()
这样,人眼看起来,不同屏幕上的鱼的速度都是一致的了。请牢牢记住这个方法,在很多情况下,通过时间控制要比直接调节帧率好用的多。
5.声音
Sound对象
pygame.mixer.Sound()接受一个文件名,或者也可以使一个文件对象,不过这个文件必须是WAV或者OGG
Channel对象
也就是声道,可以被声卡混合(共同)播放的数据流。游戏中可以同时播放的声音应该是有限的,pygame中默认是8个,你可以通过pygame.mixer.get_num_channels()来得知当前系统可以同时播放的声道数,而一旦超过,调用sound对象的play方法就会返回一个None。
pygame.mixer并不适合播放长时间的音乐播放,我们要使用pygame.mixer.music。pygame.mixer.music用来播放MP3和OGG音乐文件。
pygame.mixer.music.load()来加载一个文件,然后使用pygame.mixer.music.play()来播放,
二、设计类
在设计一个类并且准备实例化的时候,要考虑实例的状态机。
状态定义了两个内容:
当前正在做什么
转化到下一件事时候的条件
状态同时还可能包含进入(entry)和退出(exit)两种动作,进入时间是指进入某个状态时要做的一次性的事情,比如上面的怪,一旦进入攻击状态,就得开始计算与玩家的距离,或许还得大吼一声“我要杀了你”等等;而退出动作则是与之相反的,离开这个状态要做的事情。
最先开始要做的事就是载入各个状态下的图片,比如飞机爆炸前后呀背景图···
设置一个窗口,大小1200*715
canvas = pygame.display.set_mode((1200, 715))
1.一个游戏背景基类
一个实体要存储它的名字,现在的位置坐标,速度以及一个图像。有些实体可能只有一部分属性,同时我们还需要准备进入和推出的函数供调用。
class Sky(object):
def __init__(self):
self.width = 480
self.height = 680
self.img = bgg
self.x1 = 0
self.y1 = 0
self.x2 = 0
self.y2 = -self.height
# 通过初始化方法:直接指定背景图片, paint画出背景,step移动背景图
def paint(self, view):
# view.blit(bgg,(0,0))
draw(self.img, self.x1, self.y1)
draw(self.img, self.x2, self.y2)
def step(self):
self.y1 = self.y1 + 1
self.y2 = self.y2 + 1
if self.y1 > self.height:
self.y1 = -self.height
if self.y2 > self.height:
self.y2 = -self.height
# def step(self)定义背景方法step(),判断是否移出屏幕,如果移出屏幕,将图像设置到屏幕的上方。
!!!step()方法实现了背景图片的向下滚动,从而是背景一直在动,从人眼观看效果来看就是飞机在动,运动是相对的啊。背景图片大小640*480.
2.一个游戏实体基类
class FlyingObject(object):
def __init__(self, x, y, width, height, life, frames, baseFrameCount):
self.x = x
self.y = y
self.width = width
self.height = height
self.life = life
# self.img = img
# 敌飞机移动的时间间隔
self.lastTime = 0
self.interval = 0.01
# 添加掉落属性和删除属性
self.down = False
self.canDelete = False
# 实现动画所需属性
self.frames = frames # 帧频
self.frameIndex = 0
self.img = self.frames[self.frameIndex]# 图像列表
self.frameCount = baseFrameCount
def paint(self, view):
draw(self.img, self.x, self.y)
def step(self):
# 判断是否到了移动的时间间隔
if not isActionTime(self.lastTime, self.interval):
return
self.lastTime = time.time()
# 控制移动速度
self.y = self.y + 5
# 或者指定初始随机速度self.speed = random.randint(1,3)
# 碰撞检测方法
def hit(self, component):
c = component
return c.x > self.x - c.width and c.x < self.x + self.width and c.y > self.y - c.height and c.y < self.y + self.height
# 处理碰撞发生后要做的事
def bang(self):
self.life -= 1
if self.life == 0:
# 生命值为0时将down置为True
self.down = True
# 将frameIndex切换为销毁动画的第一张
self.frameIndex = self.frameCount
# 因为现在没有销毁动画,所以死亡后立即删除
# if self.down == True:
# self.canDelete = True
if hasattr(self, "score"):
GameVar.score += self.score
# 越界处理
def outOfBounds(self):
return self.y > 650
# 实现动画
def animation(self):
if self.down:
# 销毁动画播放完后将canDelete置为True
if self.frameIndex == len(self.frames):
self.canDelete = True
else:
self.img = self.frames[self.frameIndex]
self.frameIndex += 1
else:
self.img = self.frames[self.frameIndex % self.frameCount]
self.frameIndex += 1
以英雄机为例
我列的状态机比较不成熟,不是一个闭环的事件。不过这样也很清楚在这个实例要做什么了。开头我列出的4W1H用图表方式表示:
高亮的红色字体表示发生的事情。
class Hero(FlyingObject):
def __init__(self, x, y, width, height, life, frames, baseFrameCount):
# 调用了父类的_init_
FlyingObject.__init__(self, x, y, width, height, life, frames, baseFrameCount)
self.width = 60
self.height = 75
self.x = 450 + 480 / 2 - self.width / 2
self.y = 650 - self.height - 30
# 射击时间间隔
self.shootLastTime = 0
self.shootInterval = 0.3
# 在开发子类的时候,如果子类的父类不是object基类,在初始化方法过程中需要主动调用父类的初始化方法。
def paint(self, view):
draw(self.img, self.x, self.y)
# 发射子弹方法
def shoot(self):
if not isActionTime(self.shootLastTime, self.shootInterval):
return
self.shootLastTime = time.time()
GameVar.bullets.append(Bullet(self.x + self.width / 2, self.y, 9, 21, 1, b, 1))
# 英雄需要水平移动,不能溢出屏幕,
# 增加子弹类,增加shoot()方法
接下来附上敌机和子弹的图示:
class Bullet(FlyingObject):
def __init__(self, x, y, width, height, life, frames, baseFrameCount):
FlyingObject.__init__(self, x, y, width, height, life, frames, baseFrameCount)
# 调用了父类的_init_
# 重写step方法
def step(self):
self.y = self.y - 10
# 子弹的移动方法
# 重写判断是否越界的方法,飞出屏幕后删除
def outOfBounds(self):
return self.y < -self.height
三、规则和方法
在上面的三幅图中,都设计到了无效组件的删除,那么我们再来定义一个函数
来清除无效组件:
# 删除无效组件,当敌机从屏幕下方飞出,不会再飞回到屏幕中,不然会造成内存浪费
def deleteComponent():
# 删除无效的敌飞机,如果敌机飞出屏幕或者被打中,
for i in range(len(GameVar.enemies) - 1, -1, -1):# 排序
x = GameVar.enemies[i]
if x.canDelete or x.outOfBounds():
GameVar.enemies.remove(x)
# 删除无效子弹
for i in range(len(GameVar.bullets) - 1, -1, -1):
x = GameVar.bullets[i]
if x.canDelete or x.outOfBounds():
GameVar.bullets.remove(x)
# 删除无效的英雄机
if GameVar.hero.canDelete == True:
GameVar.heroes -= 1
if GameVar.heroes == 0:
#renderText("游戏结束",(100, 200))
#print("游戏结束")
GameVar.state = GameVar.STATES["GAME_OVER"]
else:
GameVar.hero = Hero(0, 0, 60, 75, 1, h, 1)
检测是否碰撞:
def checkHit():
# 判断敌飞机是否和英雄机相撞
for enemy in GameVar.enemies:
# 如果当前飞机已经死亡则换下一架飞机
if enemy.down == True:
continue
if GameVar.hero.hit(enemy):
enemy.bang()
GameVar.hero.bang()
for bullet in GameVar.bullets:
# 如果当前子弹是无效的子弹则换下一颗子弹
if bullet.down == True:
continue
if enemy.hit(bullet):
enemy.bang()
bullet.bang()
子弹和敌机的移动:
def componentStep():
# 调用sky对象的step方法
GameVar.sky.step()
for enemy in GameVar.enemies:
enemy.step()
# 子弹移动
for bullet in GameVar.bullets:
bullet.step()
还有一个重绘图形方法以达到更好的视觉效果
四、设计游戏背景及规则
此外还需要一个对鼠标事件的监听函数
游戏运行状态的控制函数
判断鼠标是否溢出屏幕区域啊之类的
监听函数:
def handleEvent():
for event in pygame.event.get():
if event.type == QUIT or event.type == KEYDOWN and event.key == K_ESCAPE:
pygame.quit()
sys.exit()
# 监听鼠标移动事件,MOUSEMOTION/MOUSEBUTTONUP/MOUSEBUTTONDOWN
if event.type == pygame.MOUSEMOTION:
# 根据鼠标的坐标修改英雄机的坐标
# 使用get_width函数可以获取图片的宽度
if GameVar.state == GameVar.STATES["RUNNING"]:
GameVar.hero.x = event.pos[0] - GameVar.hero.width / 2
GameVar.hero.y = event.pos[1] - GameVar.hero.height / 2
# 鼠标移入移出事件切换状态
if isMouseOut(event.pos[0], event.pos[1]):
if GameVar.state == GameVar.STATES["RUNNING"]:
GameVar.state = GameVar.STATES["PAUSE"]
if isMouseOver(event.pos[0], event.pos[1]):
if GameVar.state == GameVar.STATES["PAUSE"]:
GameVar.state = GameVar.STATES["RUNNING"]
# 点击左键切换为运行状态
if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
if GameVar.state == GameVar.STATES["START"]:
GameVar.state = GameVar.STATES["RUNNING"]
if event.type == KEYDOWN and event.key == K_r:
if GameVar.state == GameVar.STATES["GAME_OVER"]:
GameVar.score = 0
GameVar.heroes = 4
GameVar.state = GameVar.STATES["START"]
# 上面程序
本文地址:https://blog.csdn.net/weixin_43883903/article/details/107287181