目录
引言
创建一款什么样的游戏
需要实现什么样的功能
如何去实现基本的功能
背景模块
主角色模块
场景模块
逻辑模块
引言
最近在学习java之余,利用空余时间写了一款基于pygame的游戏,主要是想测试pygame各方面的性能,其次是因为python语言的简洁性,写起来是真的舒服,最后是拿来自娱自乐一下还挺有趣的(划重点)。声明:该游戏所有代码均为原创,纯手工制作而成。我会在文末放上游戏运行需要的完整文件包,需要的朋友请自取。
创建一款什么样的游戏由于pygame本身是一款轻量级的游戏开发工具,可以实现的功能也是比较的有限,因此为了简单化,便于操作等目的,本文通过创建一个基本矩形方块来实现各种目的,因为是以矩形方块作为主要操作对象,所以游戏命名为《The Box》。
需要实现什么样的功能作为一款测试开发游戏,需要实现的功能比较的基本,由于是一款二维平面游戏,因此需要实现一些较为传统的功能:
- 游戏主要操作对象的创建与交互
- 游戏中基本的键鼠交互
- 游戏中场景的构建与其他对象的交互
- 游戏中其他非环境对象的创建与交互
- 游戏中基本循环逻辑的实现
除了以上几个需要实现的基本功能,还需要考虑游戏的基本逻辑构建,所谓的逻辑构建就是为了实现游戏需要达到的目的,比如游戏能否足够有趣,是否具有可玩性,是否有足够的交互性等,当然这些都在不在本文的考虑范围之内(悲)。
如何去实现基本的功能目前只是初步的实现了一下一些基本功能,因为最近比较忙就暂停开发了,所以先发布一下看看自己有没有可能哪天能捡起来(笑)。废话不多说,先看目前的实现效果:
《 The Box》
看起来还行,就是太呆了(大笑)。好了关于如何实现的问题,其实我们只需要从模块化设计入手就行。这款游戏主要是通过几个基本模块来实现的:背景模块、主角色模块、场景模块、逻辑模块。上面几个模块主要实现的功能及代码如下:
背景模块class BackgroundWall(py.sprite.Sprite): def __init__(self, screen): py.sprite.Sprite.__init__(self) wallPath = '../素材库/背景墙素材/' self.index = 0 self.imageFiles = [os.path.join(wallPath, i) for i in os.listdir(wallPath)] self.images = [py.transform.smoothscale(py.image.load(name), (screen.get_width(), screen.get_height())) for name in self.imageFiles] self.image = self.images[self.index] self.rect = self.image.get_rect() self.rect.topleft = (0, 0) def update(self, game_time): timeH, timeM = divmod(game_time, 60) self.index = int(timeH) if self.index>17: self.index =17- self.index self.image = self.images[self.index] self.rect = self.image.get_rect() self.rect.topleft = (0, 0)
背景模块顾名思义是用来创建游戏的背景,本文在此实现的功能也是极其简单,主要通过调用游戏时间来实现游戏背景的自动切换。如果你想实现各种功能,比如说随着BGM切换背景,或者通过某种事件切换背景都是可以的,只需要在update函数中添加各种需要的功能即可。
主角色模块class CubeHuman(object): def __init__(self, screen): self.x = screen.get_width() self.y = screen.get_height() self.CubeBodyGroup = py.sprite.Group() self.CubeEyeballGroup = py.sprite.Group() self.CubeMouthGroup = py.sprite.Group() self.CubeEyesGroup = py.sprite.Group() self.cube_body = self.CubeBody() self.cube_eyes = self.CubeEyes() self.cube_eyeball = self.CubeEyeball() self.cube_mouth = self.CubeMouth() self.CubeBodyGroup.add(self.cube_body) self.CubeEyeballGroup.add(self.cube_eyeball) self.CubeMouthGroup.add(self.cube_mouth) self.CubeEyesGroup.add(self.cube_eyes) # 伤害,血量等自身参数 self.basicDamage = 10 # 基础伤害 self.basic = 10 # 基础防御 self.basicHp = 100 # 基础血量 self.basicMp = 100 # 基础蓝量 self.HP = 100 self.MP = 100 self.rate = 4 hpImage = py.image.load('../素材库/血条和蓝条/血条.png') hpRect = hpImage.get_rect() self.hpImage = py.transform.smoothscale(hpImage, (int(hpRect.width / self.rate), int(hpRect.height / self.rate))) self.hpRect = self.hpImage.get_rect() mpImage = py.image.load('../素材库/血条和蓝条/蓝条.png') self.mpImage = py.transform.smoothscale(mpImage, (self.hpRect.width, self.hpRect.height)) self.mpRect = self.mpImage.get_rect() hpWordImage = py.image.load('../素材库/血条和蓝条/HP.png') hpWordRect = hpWordImage.get_rect() self.hpWordImage = py.transform.smoothscale(hpWordImage, (int(hpWordRect.width / self.rate), int(hpWordRect.height / self.rate))) self.hpWordRect = self.hpWordImage.get_rect() mpWordImage = py.image.load('../素材库/血条和蓝条/MP.png') self.mpWordImage = py.transform.smoothscale(mpWordImage, (self.hpWordRect.width, self.hpWordRect.height)) self.mpWordRect = self.mpWordImage.get_rect() self.lossImage = py.image.load('../素材库/血条和蓝条/空白条.png') self.hpWordRect.topleft = (5, 5) self.mpWordRect.topleft = (5, self.hpWordRect.height + 10) self.hpRect.topleft = (self.hpWordRect.right, self.hpWordRect.top) self.mpRect.topleft = (self.mpWordRect.right, self.mpWordRect.top) def DrawAttributes(self, screen): rateHp = 1 - (self.HP / self.basicHp) rateMp = 1 - (self.MP / self.basicMp) lossHPImage = py.transform.smoothscale(self.lossImage, (int(self.hpRect.width * rateHp), self.hpRect.height)) lossHPRect = lossHPImage.get_rect() lossHPRect.topright = self.hpRect.topright lossMPImage = py.transform.smoothscale(self.lossImage, (int(self.mpRect.width * rateMp), self.mpRect.height)) lossMPRect = lossMPImage.get_rect() lossMPRect.topright = self.mpRect.topright screen.blit(self.hpWordImage, self.hpWordRect) screen.blit(self.mpWordImage, self.mpWordRect) screen.blit(self.hpImage, self.hpRect) screen.blit(self.mpImage, self.mpRect) screen.blit(lossHPImage, lossHPRect) screen.blit(lossMPImage, lossMPRect) class CubeBody(py.sprite.Sprite): def __init__(self): rate = 3 # 缩放比例,下同 py.sprite.Sprite.__init__(self) self.speed = 1 self.image = py.image.load('../素材库/人物素材/方块人/body/body_basic.png').convert_alpha() self.rect = self.image.get_rect() self.image = py.transform.smoothscale(self.image, (int(self.rect.width / rate), int(self.rect.height / rate))) self.rect = self.image.get_rect() self.rect.topleft = (0, 0) # 初始生成位置 self.gravity = 2 # 重力系数为2,表示相邻每帧图像下降2个像素 self.startJumpPos = 0 self.on_ground = False self.jump = False self.jumpMaxDis = 100 # 最大跳跃高度(像素) self.lastPos = None self.nowPos = None def KeyControl(self, key): if key[py.K_d]: if key[py.K_LSHIFT]: self.rect.x += self.speed + 1 else: self.rect.x += self.speed if key[py.K_a]: if key[py.K_LSHIFT]: self.rect.x -= self.speed + 1 else: self.rect.x -= self.speed if key[py.K_SPACE] and self.on_ground: self.gravity = -4 # 跳越速度 self.on_ground = False self.jump = True self.startJumpPos = self.rect.y self.lastPos = self.nowPos def update(self, floor, screen): if len(self.rect.collidelistall(floor.floorBody)) > 1: # 与墙体碰撞检测 self.rect.x = self.lastPos if self.rect.collidelist(floor.floorTop) == -1: # 与地面碰撞检测 self.on_ground = False if self.rect.right > screen.get_width() or self.rect.left < 0: # 防止移动超越屏幕边缘 self.rect.x = self.lastPos if not self.on_ground and not self.jump: self.rect.y += self.gravity if self.rect.collidelist(floor.floorTop) != -1: self.on_ground = True if not self.on_ground and self.jump and (self.startJumpPos - self.rect.y) <= self.jumpMaxDis: self.rect.y += self.gravity else: self.jump = False self.gravity = 2 self.startJumpPos = 0 self.nowPos = self.rect.x class CubeEyes(py.sprite.Sprite): def __init__(self): rate = 2 py.sprite.Sprite.__init__(self) self.image = py.image.load('../素材库/人物素材/方块人/eyes/eyes_black.png').convert_alpha() self.rect = self.image.get_rect() self.image = py.transform.smoothscale(self.image, (int(self.rect.width / rate), int(self.rect.width / rate))) self.rect = self.image.get_rect() def EyesMove(self, pos, mouse_pos, eyeball): x0, y0 = pos[0], pos[1] x1, y1 = mouse_pos[0], mouse_pos[1] R = eyeball.rect.width / 2 r = self.rect.width / 2 sita_Angel = np.sign(y1 - y0) * np.pi / 2 if x1 != x0: sita_Angel = np.arctan((y1 - y0) / (x1 - x0)) x2, y2 = x0 + np.sign(x1 - x0) * R * np.cos(sita_Angel), y0 + np.sign(x1 - x0) * R * np.sin(sita_Angel) x3, y3 = x2 - np.sign(x1 - x0) * r * np.cos(sita_Angel), y2 - np.sign(x1 - x0) * r * np.sin(sita_Angel) return x3, y3 def update(self, eyeball, body, screen): pos = eyeball.rect.center self.rect.center = pos rect = self.rect.copy() rect.center = (rect.center[0] + int(body.rect.width / 2), rect.center[1]) '''鼠标控制眼睛方向''' mousePos = py.mouse.get_pos() x1, y1 = self.EyesMove(self.rect.center, mousePos, eyeball) x2, y2 = self.EyesMove(rect.center, mousePos, eyeball) self.rect.center = (int(x1), int(y1)) rect.center = (int(x2), int(y2)) screen.blit(self.image, rect) class CubeEyeball(py.sprite.Sprite): def __init__(self): rate = 2 py.sprite.Sprite.__init__(self) self.image = py.image.load('../素材库/人物素材/方块人/eyeball/eyeball_basic.png').convert_alpha() self.rect = self.image.get_rect() self.image = py.transform.smoothscale(self.image, (int(self.rect.width / rate), int(self.rect.width / rate))) self.rect = self.image.get_rect() def update(self, body, screen): pos = body.rect.center self.rect.center = (pos[0] - int(body.rect.width / 4), pos[1] - int(body.rect.height / 4)) rect_right = self.rect.copy() rect_right.center = (rect_right.center[0] + int(body.rect.width / 2), rect_right.center[1]) screen.blit(self.image, rect_right) class CubeMouth(py.sprite.Sprite): def __init__(self): rate = 3 py.sprite.Sprite.__init__(self) self.image = py.image.load('../素材库/人物素材/方块人/mouth/mouth_basic.png').convert_alpha() self.rect = self.image.get_rect() self.image = py.transform.smoothscale(self.image, (int(self.rect.width / rate), int(self.rect.height / rate))) self.rect = self.image.get_rect() def update(self, body): pos = body.rect.center self.rect.center = (pos[0], pos[1] + int(body.rect.height / 4)) def update(self, floor, screen): self.DrawAttributes(screen) self.CubeBodyGroup.update(floor, screen) self.CubeBodyGroup.draw(screen) self.CubeEyeballGroup.update(self.cube_body, screen) self.CubeEyeballGroup.draw(screen) self.CubeMouthGroup.update(self.cube_body) self.CubeMouthGroup.draw(screen) self.CubeEyesGroup.update(self.cube_eyeball, self.cube_body, screen) self.CubeEyesGroup.draw(screen)
这段代码看起来十分冗长,主要是因为创建了几个内部类来实现主角色关于眼睛,身体,嘴巴等部位的控制,通过修改每个部件,可以实现极为复杂的功能,这里只是简单的实现了一下眼睛的鼠标控制功能,即眼睛跟随鼠标移动,如果你想的话甚至嘴巴也能跟随鼠标移动(笑)。其次是通过键盘控制物体的前后移动,比如按住shift可以加快物体移动,按下空格可以实现物体的跳跃等。最后关于物体与场景之间的交互,比如判断物体是否处于地面上,是否达到了游戏边界等。还有就是角色的一些属性的生成,比如血条什么的。
场景模块场景模块又分为以下几个小模块:
地板模块:
class FloorGen(object): def __init__(self, screen, cube): self.x = screen.get_width() self.y = screen.get_height() self.floorPos = [] self.floorImage = [] self.floorRect = [] self.floorTop = [] self.floorBody = [] self.jumpMaxDis = cube.cube_body.jumpMaxDis self.roadLength = random.randint(20, 25) # 游戏界面总长度 '''地板自身参数设置 分别表示单位地板宽度,高度''' self.width = int(self.x / 4) self.heightA = int(self.y / 25) self.heightB = int(self.heightA / 4) '''地板生成按照一定规律,相邻地板之间的最大距离差不超过方块的最大跳跃极限''' def floor_pos_gen(self): self.floorPos = [[(0, random.randint(int(self.y * (1 - 1 / 3)), self.y - self.heightB))]] for group in range(1, self.roadLength): body = [(group * self.width, random.randint(max(self.floorPos[group - 1][0][1] - self.jumpMaxDis, self.y / 2), min(self.floorPos[group - 1][0][1] + self.jumpMaxDis, self.y - 5)))] self.floorPos.append(body) for floor in self.floorPos: num = int(np.ceil(self.y - floor[0][1] - self.heightB) / self.heightA) + 1 head = floor[0][1] + self.heightB floor.append((floor[0][0], head)) for member in range(1, num): nextHead = head + self.heightA * member floor.append((floor[0][0], nextHead)) def floor_Image(self): floorImage = [] for i in range(5): locals()['image{}'.format(i)] = py.image.load('../素材库/地板素材/floor_lv{}.png'.format(i)).convert_alpha() floorImage.append(eval('image{}'.format(i))) floorImage[i] = py.transform.smoothscale(floorImage[i], (self.width, self.heightA)) floorImage[0] = py.transform.smoothscale(floorImage[0], (self.width, self.heightB)) self.floorImage = floorImage def generator(self, screen): self.floor_pos_gen() self.floor_Image() for floor in self.floorPos: rectLs = [] for i in range(len(floor)): floorPos = floor[i] image = [self.floorImage[-1] if i > len(self.floorImage) - 1 else self.floorImage[i]][0].copy() rect = image.get_rect() rect.topleft = floorPos screen.blit(image, rect) rectLs.append(rect) self.floorBody.append(rect) self.floorRect.append(rectLs) self.floorTop.append(rectLs[0]) # 更新所有地板图像 def update(self, screen): for floorL in self.floorRect: for i in range(len(floorL)): floorRect = floorL[i] floorImage = [self.floorImage[-1] if i > len(self.floorImage) - 1 else self.floorImage[i]][0].copy() screen.blit(floorImage, floorRect) def updatePre(self, cube, point): for floorL in self.floorRect: for floorRect in floorL: floorRect.x -= cube.cube_body.nowPos - cube.cube_body.lastPos cube.cube_body.rect.x = cube.cube_body.lastPos
地板模块用来创建地面。本文通过随机生成一定长度的地面用来实现游戏场景的移动,同时相邻地板之间的高度不能超过主角色的极限跳跃高度,这是为了避免方块无法穿越游戏场景。
云模块:
class Cloud(py.sprite.Sprite): def __init__(self, screen, game_time): py.sprite.Sprite.__init__(self) cloudPath = '../素材库/云/' rate = 3 self.speed = 1 # 移动速度 self.genTime = game_time self.imageFiles = [os.path.join(cloudPath, i) for i in os.listdir(cloudPath)] self.images = [py.image.load(name) for name in self.imageFiles] self.rects = [image.get_rect() for image in self.images] self.images = [ py.transform.smoothscale(self.images[i], (int(self.rects[i].width / rate), int(self.rects[i].height / rate))) for i in range(len(self.images))] self.index = random.randint(0, len(self.images) - 1) self.image = self.images[self.index] self.rect = self.image.get_rect() self.width = screen.get_width() self.height = screen.get_height() self.x = random.randint(int(self.width + self.rect.width / 2), int(self.width + self.rect.width / 2 + 100)) self.y = random.randint(int(self.rect.height / 2), int(self.height / 4)) self.rect.center = (self.x, self.y) def update(self): self.rect.x -= self.speed if self.rect.right < 0: self.kill()# 控制云朵生成速率def cloudGen(cloud_group, screen, game_time, ls): if len(cloud_group) == 0: cloud = Cloud(screen, game_time) cloud_group.add(cloud) elif 0 < len(cloud_group) < 3: spriteTime = list(cloud_group.spritedict.keys())[-1].genTime if len(ls) == 0: timeSlot = random.randint(3, 50) ls.append(timeSlot) else: timeSlot = ls[-1] timeWait = (game_time - spriteTime) if timeWait >= timeSlot: ls.pop(-1) cloud = Cloud(screen, game_time) cloud_group.add(cloud)
这个模块用来生成一些云朵,同时按照一定的速度进入和离开游戏界面,这样使得场景看起来更加具有动感。(别说贴图太丑了,这都是用wps一个个设计出来的)
太阳和月亮:
# 太阳class SunAndMoon(py.sprite.Sprite): def __init__(self): py.sprite.Sprite.__init__(self) self.rate = 3 self.MoonFilePath = '../素材库/月亮/' self.imageRaw = py.image.load('../素材库/太阳/sun.png').convert_alpha() self.MoonFiles = [os.path.join(self.MoonFilePath, i) for i in os.listdir(self.MoonFilePath)] self.imageMoonRaw = [py.image.load(name).convert_alpha() for name in self.MoonFiles] self.MoonRect = [image.get_rect() for image in self.imageMoonRaw] self.imageMoon = [py.transform.smoothscale(self.imageMoonRaw[i], (int(self.MoonRect[i].width / self.rate), int(self.MoonRect[i].width / self.rate))) for i in range(len(self.imageMoonRaw))] self.MoonRect = [image.get_rect() for image in self.imageMoonRaw] self.rect = self.imageRaw.get_rect() self.imageRaw = py.transform.smoothscale(self.imageRaw, (int(self.rect.width / self.rate), int(self.rect.width / self.rate))) self.rect = self.imageRaw.get_rect() self.y = self.rect.height self.image = self.imageRaw.copy() self.time = 0 def update(self, game_time, screen): hour, minute = divmod(game_time, 60) if hour <= 9: self.time += 1 self.image = py.transform.rotate(self.imageRaw, self.time).convert_alpha() self.rect = self.image.get_rect() self.rect.center = (int(screen.get_width() / 2), int(self.y / 2)) elif 17 >= hour > 9: self.image = self.imageMoon[int(hour) - 10] self.rect = self.MoonRect[int(hour) - 10] self.rect.center = (int(screen.get_width() / 2), int(self.y / 2))
这个模块实现了太阳的旋转,月亮的生成。
逻辑模块# 移动游戏镜头def moveCamera(cube, floor, screen): rate = 0.70 # 镜头移动触发区域为屏幕右端(1-rate)/2范围,和屏幕左端(1-rate)/2范围 triggerPointRight = screen.get_width() * (0.5 + rate / 2) triggerPointLeft = screen.get_width() * (1 - rate) / 2 if cube.cube_body.rect.right > triggerPointRight: if floor.floorTop[0].left > -floor.roadLength * floor.width + screen.get_width(): floor.updatePre(cube, triggerPointRight) elif cube.cube_body.rect.left < triggerPointLeft: if floor.floorTop[-1].right < floor.roadLength * floor.width: floor.updatePre(cube, triggerPointLeft) floor.update(screen)
这个模块用来控制屏幕的镜头的移动,所谓的移动实际上是相对的,因为屏幕本身不会移动因此只能通过移动地板来实现,通过创建两条边界线来检测触发移动,这里我同时设置无法移动镜头的情况,以免超过了游戏地板的生成界限。
启动程序:
# 时间在0-34之间def ControlTime(): gameTime = np.floor(py.time.get_ticks() / 1000) hour = np.floor(gameTime / 60) if hour <= 34: return gameTime elif hour > 34: loop, timeM = divmod(gameTime, 34 * 60) return timeMdef Main(): py.init() FPS = 200 # 游戏帧率 width, height = 800, 600 # 设置窗口大小 py.display.set_caption('THE BOX') # 设置游戏名称 screen = py.display.set_mode((width, height)) clock = py.time.Clock() backGround = BackgroundWall(screen) bgGroup = py.sprite.Group() bgGroup.add(backGround) cube = CubeHuman(screen) sunAndMoon = SunAndMoon() sunGroup = py.sprite.Group() sunGroup.add(sunAndMoon) floor = FloorGen(screen, cube) floor.generator(screen) cloudGroup = py.sprite.Group() cloudUpdateTimeLs = [] while True: GameTime = ControlTime() bgGroup.update(GameTime) bgGroup.draw(screen) sunGroup.update(GameTime, screen) sunGroup.draw(screen) cloudGen(cloudGroup, screen, GameTime, cloudUpdateTimeLs) cloudGroup.update() cloudGroup.draw(screen) key = py.key.get_pressed() # 获取键盘输入 cube.cube_body.KeyControl(key) cube.cube_body.update(floor, screen) moveCamera(cube, floor, screen) # 包含了地面刷新程序 if key[py.K_ESCAPE]: exit() # 退出设置 for event in py.event.get(): if event.type == py.QUIT: py.quit() sys.exit() '''''' cube.update(floor, screen) py.display.update() # 刷新 clock.tick(FPS)if __name__ == '__main__': Main()
启动程序中各位模块创建和刷新的先后顺序一定不要搞错。以上就是本文的全部的内容了,根据以往经验,我知道这篇文章大概率没什么人看,主要是为了以防万一哪天我想捡起来看看,故作此文。最后按照惯例给个懒人包:The Box 提取码:s2gj
PS:有时间我再去修改修改,比如写个启动界面什么的(恼),加个音乐包什么的,自动创建一个爬虫什么的(笑),也可能会写一个人工智能代替我玩(悲)。



