日知录(七):python之理解pygame飞机大战


飞机大战,是一个入门pygame的经典项目。前几个月仔细研究了一番之后,可能还有些不是很懂。这几个星期把python又重新看了一遍,那么我也再把飞机大战复盘,自己理解一遍。在此也贴上自己的启蒙网站: 目光博客飞机大战系列,这位博主的飞机大战十分详尽,非常棒!

一、明确目标

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.timepygame.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"]
# 上面程序
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值