欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-07-09 13:09:12
文章目录一、明确目标1.面对对象分析2.pygame基础1.模块2.创建窗口3.处理事件1.鼠标事件(MOUSEMOTION)2.键盘事件4.显示与字体1.屏幕显示2.字体3.图像4.帧率FPS5.声音Sound对象Channel对象二、设计类1.一个游戏背景基类2.一个游戏实体基类以英雄机为例三、规则和方法四、设计游戏背景及规则飞机大战,是一个入门pygame的经典项目。前几个月仔细研究了一番之后,可能还有些不是很懂。这几个星期把python又重新看了一遍,那么我也再把飞机大战复盘,自己理解一遍。在此也...


飞机大战,是一个入门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)恒定的速度。这样的话,从起点到终点的时间点是一样的,最终的效果也就相同了,所差别的,只是流畅度。看下面的图试着理解一下~
日知录(七):python之理解pygame飞机大战
搬的是目光博客大佬的图片。

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用图表方式表示:
高亮的红色字体表示发生的事情。
日知录(七):python之理解pygame飞机大战

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()方法

接下来附上敌机和子弹的图示:
日知录(七):python之理解pygame飞机大战
日知录(七):python之理解pygame飞机大战

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