魂斗罗游戏中包含很多个物体,现在要对这些物体进行总结
类名 | 包含的操作 | 包含的属性 |
---|---|---|
玩家1类 | 跳跃、行走、落下、更新 | 生命,速度,伤害,方向,类型 |
玩家2类 | 跳跃、行走、落下、更新 | 生命,速度,伤害,方向,类型 |
敌人类 | 跳跃、行走、落下、更新 | 生命,速度,伤害,方向,类型 |
子弹类 | 移动,显示 | 方向,伤害,发射源,速度 |
桥类、陷阱类、奖励类、BOSS类 | … | … |
音效类 | 播放,停止,设置音乐 | - |
爆炸效果类 | 显示 | 是否可以摧毁 |
主类 | … | … |
物体总结完毕后,规划一下窗口的大小,下面是我设置的窗口大小
由于地图素材的大小限制,下面这个窗口的大小刚刚合适
那么我们开始来编写魂斗罗吧
同样地,先写主类,主类是整个游戏运作的类,我们也可以不用这样的方式,可以使用函数代替
import pygameclass MainGame:window = Nonedef __init__(self):passdef run(self):passif __name__ == '__main__':pass
魂斗罗游戏不像坦克大战,坦克大战中坦克移动是把图片放到不同的位置,而魂斗罗中,人物移动有移动的动画,跳跃也有跳跃的动画,我们需要设置帧率,通过控制帧率来实现动画效果
首先创建一个常量Constants.py文件,用于存放游戏中使用的常量
from enum import Enum# 玩家的四种状态
class State(Enum):STAND = 1WALK = 2JUMP = 3FALL = 4# 玩家的方向
class Direction(Enum):RIGHT = 1LEFT = 2# 设置窗口大小
SCREEN_HEIGHT = 600
SCREEN_WIDTH = 800
GROUND_HEIGHT = 63# 玩家x方向速度
PLAYER_X_SPEED = 3
设置遍历和创建窗口
def __init__(self):# 初始化展示模块pygame.display.init()SCREEN_SIZE = (SCREEN_WIDTH, SCREEN_HEIGHT)# 初始化窗口MainGame.window = pygame.display.set_mode(SCREEN_SIZE)# 设置窗口标题pygame.display.set_caption('魂斗罗角色')# 是否结束游戏self.isEnd = False# 获取按键self.keys = pygame.key.get_pressed()# 帧率self.fps = 60self.clock = pygame.time.Clock()
修改一下run()函数
def run(self):while not self.isEnd:# 设置背景颜色pygame.display.get_surface().fill((0, 0, 0))# 更新窗口pygame.display.update()# 设置帧率self.clock.tick(self.fps)fps = self.clock.get_fps()caption = '魂斗罗 - {:.2f}'.format(fps)pygame.display.set_caption(caption)else:sys.exit()
完整主类代码
import sys
import pygame
from Constants import *class MainGame:window = Nonedef __init__(self):# 初始化展示模块pygame.display.init()SCREEN_SIZE = (SCREEN_WIDTH, SCREEN_HEIGHT)# 初始化窗口MainGame.window = pygame.display.set_mode(SCREEN_SIZE)# 设置窗口标题pygame.display.set_caption('魂斗罗角色')# 是否结束游戏self.isEnd = False# 获取按键self.keys = pygame.key.get_pressed()# 帧率self.fps = 60self.clock = pygame.time.Clock()def run(self):while not self.isEnd:# 设置背景颜色pygame.display.get_surface().fill((0, 0, 0))# 更新窗口pygame.display.update()# 设置帧率self.clock.tick(self.fps)fps = self.clock.get_fps()caption = '魂斗罗 - {:.2f}'.format(fps)pygame.display.set_caption(caption)else:sys.exit()if __name__ == '__main__':MainGame().run()
运行一下,看看窗口
在魂斗罗中,玩家移动是必不可少的操作,因此键盘事件响应是必不可少的
def getPlayingModeEvent(self):# 获取事件列表for event in pygame.event.get():# 点击窗口关闭按钮if event.type == pygame.QUIT:self.isEnd = True# 键盘按键按下elif event.type == pygame.KEYDOWN:self.keys = pygame.key.get_pressed()# 键盘按键抬起elif event.type == pygame.KEYUP:self.keys = pygame.key.get_pressed()
获取窗口中的事件,用于玩家角色移动、发射子弹等操作
在这个游戏中,我规定aswd操控玩家移动,j攻击,k跳跃
import sys
import pygame
from Constants import *class MainGame:window = Nonedef __init__(self):# 初始化展示模块pygame.display.init()SCREEN_SIZE = (SCREEN_WIDTH, SCREEN_HEIGHT)# 初始化窗口MainGame.window = pygame.display.set_mode(SCREEN_SIZE)# 设置窗口标题pygame.display.set_caption('魂斗罗角色')# 是否结束游戏self.isEnd = False# 获取按键self.keys = pygame.key.get_pressed()# 帧率self.fps = 60self.clock = pygame.time.Clock()def run(self):while not self.isEnd:# 设置背景颜色pygame.display.get_surface().fill((0, 0, 0))# 获取窗口中的事件self.getPlayingModeEvent()# 更新窗口pygame.display.update()# 设置帧率self.clock.tick(self.fps)fps = self.clock.get_fps()caption = '魂斗罗 - {:.2f}'.format(fps)pygame.display.set_caption(caption)else:sys.exit()def getPlayingModeEvent(self):# 获取事件列表for event in pygame.event.get():# 点击窗口关闭按钮if event.type == pygame.QUIT:self.isEnd = True# 键盘按键按下elif event.type == pygame.KEYDOWN:self.keys = pygame.key.get_pressed()# 键盘按键抬起elif event.type == pygame.KEYUP:self.keys = pygame.key.get_pressed()if __name__ == '__main__':MainGame().run()
创建一个玩家类,实现玩家的移动和射击,玩家类需要继承
pygame.sprite.Sprite类,用来实现物体的碰撞检测
import pygame
from Constants import *class PlayerOne(pygame.sprite.Sprite):def __init__(self, currentTime):pygame.sprite.Sprite.__init__(self)passdef update(self, playerBulletList, keys, currentTime):pass
创建玩家类后,先把玩家图片加载进来,这里需要写一个函数,用来处理图片
在常量Constants.py文件中,写一个函数,用来加载图片
def loadImage(filename, hReverse = False):image = pygame.image.load(filename)if hReverse:image = pygame.transform.flip(image, True, False)rect = image.get_rect()image = pygame.transform.scale(image,(int(rect.width * 2.5), int(rect.height * 2.5)))image = image.convert_alpha()return image
这个函数用来设置图片放缩和水平翻转
因为素材中大多数图片都是向右的,如果要得到向左的,必须要进行翻转,为了方便,我就没有处理素材,直接使用了翻转
文件中翻转和缩放代码,在学习 Python 之 Pygame 开发魂斗罗(一)中已经见过了
完整的常量文件
from enum import Enum
import pygameclass State(Enum):STAND = 1WALK = 2JUMP = 3FALL = 4SQUAT = 5class Direction(Enum):RIGHT = 1LEFT = 2def loadImage(filename, hReverse = False):image = pygame.image.load(filename)if hReverse:image = pygame.transform.flip(image, True, False)rect = image.get_rect()image = pygame.transform.scale(image,(int(rect.width * 2.5), int(rect.height * 2.5)))image = image.convert_alpha()return image# 设置窗口大小
SCREEN_HEIGHT = 600
SCREEN_WIDTH = 800
GROUND_HEIGHT = 63# 玩家x方向速度
PLAYER_X_SPEED = 3
这里需要注意一点,由于玩家移动时有动画效果,所有我们载入的图片是连续的好几张
下面是图片的素材
链接:https://pan.baidu.com/s/1X7tESkes_O6nbPxfpHD6hQ?pwd=hdly
提取码:hdly
有了素材,开始读取图片吧,修改玩家类的__init__()函数
def __init__(self, currentTime):pygame.sprite.Sprite.__init__(self)# 加载角色图片self.standRightImage = loadImage('../Image/Player/Player1/Right/stand.png')self.standLeftImage = loadImage('../Image/Player/Player1/Left/stand.png')self.upRightImage = loadImage('../Image/Player/Player1/Up/upRight(small).png')self.upLeftImage = loadImage('../Image/Player/Player1/Up/upLeft(small).png')self.downRightImage = loadImage('../Image/Player/Player1/Down/down.png')self.downLeftImage = loadImage('../Image/Player/Player1/Down/down.png', True)self.obliqueUpRightImages = [loadImage('../Image/Player/Player1/Up/rightUp1.png'),loadImage('../Image/Player/Player1/Up/rightUp2.png'),loadImage('../Image/Player/Player1/Up/rightUp3.png'),]self.obliqueUpLeftImages = [loadImage('../Image/Player/Player1/Up/rightUp1.png', True),loadImage('../Image/Player/Player1/Up/rightUp2.png', True),loadImage('../Image/Player/Player1/Up/rightUp3.png', True),]self.obliqueDownRightImages = [loadImage('../Image/Player/Player1/ObliqueDown/1.png'),loadImage('../Image/Player/Player1/ObliqueDown/2.png'),loadImage('../Image/Player/Player1/ObliqueDown/3.png'),]self.obliqueDownLeftImages = [loadImage('../Image/Player/Player1/ObliqueDown/1.png', True),loadImage('../Image/Player/Player1/ObliqueDown/2.png', True),loadImage('../Image/Player/Player1/ObliqueDown/3.png', True),]# 角色向右的全部图片self.rightImages = [loadImage('../Image/Player/Player1/Right/run1.png'),loadImage('../Image/Player/Player1/Right/run2.png'),loadImage('../Image/Player/Player1/Right/run3.png')]# 角色向左的全部图片self.leftImages = [loadImage('../Image/Player/Player1/Left/run1.png'),loadImage('../Image/Player/Player1/Left/run2.png'),loadImage('../Image/Player/Player1/Left/run3.png')]# 角色跳跃的全部图片self.upRightImages = [loadImage('../Image/Player/Player1/Jump/jump1.png'),loadImage('../Image/Player/Player1/Jump/jump2.png'),loadImage('../Image/Player/Player1/Jump/jump3.png'),loadImage('../Image/Player/Player1/Jump/jump4.png'),]self.upLeftImages = [loadImage('../Image/Player/Player1/Jump/jump1.png', True),loadImage('../Image/Player/Player1/Jump/jump2.png', True),loadImage('../Image/Player/Player1/Jump/jump3.png', True),loadImage('../Image/Player/Player1/Jump/jump4.png', True),]self.rightFireImages = [loadImage('../Image/Player/Player1/Right/fire1.png'),loadImage('../Image/Player/Player1/Right/fire2.png'),loadImage('../Image/Player/Player1/Right/fire3.png'),]self.leftFireImages = [loadImage('../Image/Player/Player1/Right/fire1.png', True),loadImage('../Image/Player/Player1/Right/fire2.png', True),loadImage('../Image/Player/Player1/Right/fire3.png', True),]
图片读取好后,我们需要设置一些索引,用来记录当前应该加载哪一张图片
举个简单的例子,人物向右行走的图片有三张,如下图
我们需要在人物跑动的时候,循环加载这三张图片,所以要设置一个索引,用来记录当前加载到了哪一张,如果是第三张,那么把索引再变为0,这样下次就又开始加载第一张图片了,循环下去,就可以看到人物跑动的动画了
好,那我们就来设置索引
def __init__(self, currentTime):...# 角色左右移动下标self.imageIndex = 0# 角色跳跃下标self.upImageIndex = 0# 角色斜射下标self.obliqueImageIndex = 0...
有了下标还要考虑加载图片的间隔,这个游戏中,在前面我们已经设置了游戏的帧率,为60,就是一秒加载60张图片,如果不设置时间间隔,玩家移动的时候,加载的图片是闪的,因为太快了,看不清楚,所以要设置间隔
首先记录下上次加载的图片的时间,然后在显示玩家图片时,用当前的时间减去上次加载图片的时间,如果相差一定的间隔,那么就让索引+1,超过图片个数就把索引置为0
下面是设置上一次加载图片的时间变量
def __init__(self, currentTime):...# 上一次显示图片的时间self.runLastTimer = currentTimeself.fireLastTimer = currentTime...
这里要区分一下,因为玩家移动的时候也可以射击,所以移动射击的图片和移动不射击的图片要分开索引,防止混淆
之后设置一下玩家的其他属性
# 选择当前要显示的图片self.image = self.standRightImage# 获取图片的rectself.rect = self.image.get_rect()# 设置角色的状态self.state = State.FALL# 角色的方向self.direction = Direction.RIGHT# 速度self.xSpeed = PLAYER_X_SPEEDself.ySpeed = 0self.jumpSpeed = -11# 人物当前的状态标志self.isStanding = Falseself.isWalking = Falseself.isJumping = Trueself.isSquating = Falseself.isFiring = False# 重力加速度self.gravity = 0.7# 玩家上下的朝向self.isUp = Falseself.isDown = False
角色一开始是降落的状态,是因为开始的时候角色从天而降
# 人物当前的状态标志
self.isStanding = False
self.isWalking = False
self.isJumping = True
self.isSquating = False
self.isFiring = False# 玩家上下的朝向
self.isUp = False
self.isDown = False
这两组属性,用来画出当前角色的图片
完整的角色类代码
import pygame
from Constants import *class PlayerOne(pygame.sprite.Sprite):def __init__(self, currentTime):pygame.sprite.Sprite.__init__(self)# 加载角色图片self.standRightImage = loadImage('../Image/Player/Player1/Right/stand.png')self.standLeftImage = loadImage('../Image/Player/Player1/Left/stand.png')self.upRightImage = loadImage('../Image/Player/Player1/Up/upRight(small).png')self.upLeftImage = loadImage('../Image/Player/Player1/Up/upLeft(small).png')self.downRightImage = loadImage('../Image/Player/Player1/Down/down.png')self.downLeftImage = loadImage('../Image/Player/Player1/Down/down.png', True)self.obliqueUpRightImages = [loadImage('../Image/Player/Player1/Up/rightUp1.png'),loadImage('../Image/Player/Player1/Up/rightUp2.png'),loadImage('../Image/Player/Player1/Up/rightUp3.png'),]self.obliqueUpLeftImages = [loadImage('../Image/Player/Player1/Up/rightUp1.png', True),loadImage('../Image/Player/Player1/Up/rightUp2.png', True),loadImage('../Image/Player/Player1/Up/rightUp3.png', True),]self.obliqueDownRightImages = [loadImage('../Image/Player/Player1/ObliqueDown/1.png'),loadImage('../Image/Player/Player1/ObliqueDown/2.png'),loadImage('../Image/Player/Player1/ObliqueDown/3.png'),]self.obliqueDownLeftImages = [loadImage('../Image/Player/Player1/ObliqueDown/1.png', True),loadImage('../Image/Player/Player1/ObliqueDown/2.png', True),loadImage('../Image/Player/Player1/ObliqueDown/3.png', True),]# 角色向右的全部图片self.rightImages = [loadImage('../Image/Player/Player1/Right/run1.png'),loadImage('../Image/Player/Player1/Right/run2.png'),loadImage('../Image/Player/Player1/Right/run3.png')]# 角色向左的全部图片self.leftImages = [loadImage('../Image/Player/Player1/Left/run1.png'),loadImage('../Image/Player/Player1/Left/run2.png'),loadImage('../Image/Player/Player1/Left/run3.png')]# 角色跳跃的全部图片self.upRightImages = [loadImage('../Image/Player/Player1/Jump/jump1.png'),loadImage('../Image/Player/Player1/Jump/jump2.png'),loadImage('../Image/Player/Player1/Jump/jump3.png'),loadImage('../Image/Player/Player1/Jump/jump4.png'),]self.upLeftImages = [loadImage('../Image/Player/Player1/Jump/jump1.png', True),loadImage('../Image/Player/Player1/Jump/jump2.png', True),loadImage('../Image/Player/Player1/Jump/jump3.png', True),loadImage('../Image/Player/Player1/Jump/jump4.png', True),]self.rightFireImages = [loadImage('../Image/Player/Player1/Right/fire1.png'),loadImage('../Image/Player/Player1/Right/fire2.png'),loadImage('../Image/Player/Player1/Right/fire3.png'),]self.leftFireImages = [loadImage('../Image/Player/Player1/Right/fire1.png', True),loadImage('../Image/Player/Player1/Right/fire2.png', True),loadImage('../Image/Player/Player1/Right/fire3.png', True),]# 角色左右移动下标self.imageIndex = 0# 角色跳跃下标self.upImageIndex = 0# 角色斜射下标self.obliqueImageIndex = 0# 上一次显示图片的时间self.runLastTimer = currentTimeself.fireLastTimer = currentTime# 选择当前要显示的图片self.image = self.standRightImage# 获取图片的rectself.rect = self.image.get_rect()# 设置角色的状态self.state = State.FALL# 角色的方向self.direction = Direction.RIGHT# 速度self.xSpeed = PLAYER_X_SPEEDself.ySpeed = 0self.jumpSpeed = -11# 人物当前的状态标志self.isStanding = Falseself.isWalking = Falseself.isJumping = Trueself.isSquating = Falseself.isFiring = False# 重力加速度self.gravity = 0.7self.isUp = Falseself.isDown = Falsedef update(self, playerBulletList, keys, currentTime):pass
先根据状态读取下一次操作,再更新玩家位置,再根据方向和状态标志觉得玩家显示的图片
def update(self, keys, currentTime):# 更新站或者走的状态# 根据状态响应按键if self.state == State.STAND:self.standing(keys, currentTime)elif self.state == State.WALK:self.walking(keys, currentTime)elif self.state == State.JUMP:self.jumping(keys, currentTime)elif self.state == State.FALL:self.falling(keys, currentTime)# 更新位置# 记录前一次的位置坐标pre = self.rect.xself.rect.x += self.xSpeedself.rect.y += self.ySpeed# 如果x位置小于0了,就不能移动,防止人物跑到屏幕左边if self.rect.x <= 0:self.rect.x = pre
根据状态标志设置角色图片
# 更新动画# 跳跃状态if self.isJumping:# 根据方向if self.direction == Direction.RIGHT:# 方向向右,角色加载向右跳起的图片self.image = self.upRightImages[self.upImageIndex]else:# 否则,方向向左,角色加载向左跳起的图片self.image = self.upLeftImages[self.upImageIndex]# 角色蹲下if self.isSquating:if self.direction == Direction.RIGHT:# 加载向右蹲下的图片self.image = self.downRightImageelse:# 加载向左蹲下的图片self.image = self.downLeftImage# 角色站着if self.isStanding:if self.direction == Direction.RIGHT:if self.isUp:# 加载向右朝上的图片self.image = self.upRightImageelif self.isDown:# 加载向右蹲下的图片self.image = self.downRightImageelse:# 加载向右站着的图片self.image = self.standRightImageelse:# 向左也是同样的效果if self.isUp:self.image = self.upLeftImageelif self.isDown:self.image = self.downLeftImageelse:self.image = self.standLeftImage# 角色移动if self.isWalking:if self.direction == Direction.RIGHT:if self.isUp:# 加载斜右上的图片self.image = self.obliqueUpRightImages[self.obliqueImageIndex]elif self.isDown:# 加载斜右下的图片self.image = self.obliqueDownRightImages[self.obliqueImageIndex]else:# 加载向右移动的图片,根据开火状态是否加载向右开火移动的图片if self.isFiring:self.image = self.rightFireImages[self.imageIndex]else:self.image = self.rightImages[self.imageIndex]else:if self.isUp:self.image = self.obliqueUpLeftImages[self.obliqueImageIndex]elif self.isDown:self.image = self.obliqueDownLeftImages[self.obliqueImageIndex]else:if self.isFiring:self.image = self.leftFireImages[self.imageIndex]else:self.image = self.leftImages[self.imageIndex]
完整的update()函数
def update(self, playerBulletList, keys, currentTime):# 更新站或者走的状态# 根据状态响应按键if self.state == State.STAND:self.standing(keys, currentTime, playerBulletList)elif self.state == State.WALK:self.walking(keys, currentTime, playerBulletList)elif self.state == State.JUMP:self.jumping(keys, currentTime, playerBulletList)elif self.state == State.FALL:self.falling(keys, currentTime, playerBulletList)# 更新位置# 记录前一次的位置坐标pre = self.rect.xself.rect.x += self.xSpeedself.rect.y += self.ySpeed# 如果x位置小于0了,就不能移动,防止人物跑到屏幕左边if self.rect.x <= 0:self.rect.x = pre# 更新动画# 跳跃状态if self.isJumping:# 根据方向if self.direction == Direction.RIGHT:# 方向向右,角色加载向右跳起的图片self.image = self.upRightImages[self.upImageIndex]else:# 否则,方向向左,角色加载向左跳起的图片self.image = self.upLeftImages[self.upImageIndex]# 角色蹲下if self.isSquating:if self.direction == Direction.RIGHT:# 加载向右蹲下的图片self.image = self.downRightImageelse:# 加载向左蹲下的图片self.image = self.downLeftImage# 角色站着if self.isStanding:if self.direction == Direction.RIGHT:if self.isUp:# 加载向右朝上的图片self.image = self.upRightImageelif self.isDown:# 加载向右蹲下的图片self.image = self.downRightImageelse:# 加载向右站着的图片self.image = self.standRightImageelse:# 向左也是同样的效果if self.isUp:self.image = self.upLeftImageelif self.isDown:self.image = self.downLeftImageelse:self.image = self.standLeftImage# 角色移动if self.isWalking:if self.direction == Direction.RIGHT:if self.isUp:# 加载斜右上的图片self.image = self.obliqueUpRightImages[self.obliqueImageIndex]elif self.isDown:# 加载斜右下的图片self.image = self.obliqueDownRightImages[self.obliqueImageIndex]else:# 加载向右移动的图片,根据开火状态是否加载向右开火移动的图片if self.isFiring:self.image = self.rightFireImages[self.imageIndex]else:self.image = self.rightImages[self.imageIndex]else:if self.isUp:self.image = self.obliqueUpLeftImages[self.obliqueImageIndex]elif self.isDown:self.image = self.obliqueDownLeftImages[self.obliqueImageIndex]else:if self.isFiring:self.image = self.leftFireImages[self.imageIndex]else:self.image = self.leftImages[self.imageIndex]
之后就是对四个状态函数进行完善啦