• 【毕业设计】 python小游戏设计 - 走迷宫游戏设计与实现



    0 前言

    🔥 这两年开始毕业设计和毕业答辩的要求和难度不断提升,传统的毕设题目缺少创新和亮点,往往达不到毕业答辩的要求,这两年不断有学弟学妹告诉学长自己做的项目系统达不到老师的要求。

    为了大家能够顺利以及最少的精力通过毕设,学长分享优质毕业设计项目,今天要分享的是

    🚩 基于python的走迷宫游戏设计与实现

    🥇学长这里给一个题目综合评分(每项满分5分)

    • 难度系数:3分
    • 工作量:3分
    • 创新点:3分

    🧿 选题指导, 项目分享:

    https://gitee.com/dancheng-senior/project-sharing-1/blob/master/%E6%AF%95%E8%AE%BE%E6%8C%87%E5%AF%BC/README.md

    1 课题背景

    人类建造迷宫已有5000年的历史。在世界的不同文化发展时期,这些奇特的建筑物始终吸引人们沿着弯弯曲曲、困难重重的小路吃力地行走,寻找真相。迷宫类小游戏应运而生。

    今天我们用pygame做一个走迷宫小游戏,游戏规则如下:

    玩家通过↑↓←→键控制主角行动,使主角从出发点(左上角)绕出迷宫,到达终点(右下角)即为游戏胜利。

    2 实现效果

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    3 Pygame介绍

    简介

    Pygame是一系列专门为编写电子游戏而设计的Python模块(modules)。Pygame在已经非常优秀的SDL库的基础上增加了许多功能。这让你能够用Python语言编写出丰富多彩的游戏程序。

    Pygame可移植性高,几乎能在任何平台和操作系统上运行。

    Pygame已经被下载过数百万次。

    Pygame免费开源。它在LGPL许可证(Lesser General Public License,GNU宽通用公共许可证)下发行。使用Pygame,你可以创造出免费开源,可共享,或者商业化的游戏。详情请见LGPL许可证。

    优点

    • 能够轻松使用多核CPU(multi core CPUs) :如今双核CPU很常用,8核CPU在桌面系统中也很便宜,而利用好多核系统,能让你在你的游戏中实现更多东西。特定的pygame函数能够释放令人生畏的python GIL(全局解释器锁),这几乎是你用C语言才能做的事。

    • 核心函数用最优化的C语言或汇编语言编写:C语言代码通常比Python代码运行速度快10-20倍。而汇编语言编写的代码(assembly code)比Python甚至快到100多倍。

    • 安装便捷:一般仅需包管理程序或二进制系统程序便能安装。

    • 真正地可移植:支持Linux (主要发行版), Windows (95, 98, ME, 2000, XP, Vista, 64-bit Windows,), Windows CE, BeOS, MacOS, Mac OS X, FreeBSD, NetBSD, OpenBSD, BSD/OS, Solaris, IRIX, and QNX等操作系统.也能支持AmigaOS, Dreamcast, Atari, AIX, OSF/Tru64, RISC OS, SymbianOS and OS/2,但是还没有受到官方认可。你也可以在手持设备,游戏控制台, One Laptop Per Child (OLPC) computer项目的电脑等设备中使用pygame.

    • 用法简单:无论是小孩子还是大人都能学会用pygame来制作射击类游戏。

    • 很多Pygame游戏已发行:其中包括很多游戏大赛入围作品、非常受欢迎的开源可分享的游戏。

    • 由你来控制主循环:由你来调用pygame的函数,pygame的函数并不需要调用你的函数。当你同时还在使用其他库来编写各种各种的程序时,这能够为你提供极大的掌控权。

    • 不需要GUI就能使用所有函数:仅在命令行中,你就可以使用pygame的某些函数来处理图片,获取游戏杆输入,播放音乐……

    • 对bug反应迅速:很多bug在被上报的1小时内就能被我们修复。虽然有时候我们确实会卡在某一个bug上很久,但大多数时候我们都是很不错的bug修复者。如今bug的上报已经很少了,因为许多bug早已被我们修复。

    • 代码量少:pygame并没有数以万计的也许你永远用不到的冗杂代码。pygame的核心代码一直保持着简洁特点,其他附加物诸如GUI库等,都是在核心代码之外单独设计研发的。

    • 模块化:你可以单独使用pygame的某个模块。想要换着使用一个别的声音处理库?没问题。pygame的很多核心模块支持独立初始化与使用。

    最小开发框架

    import pygame,sys #sys是python的标准库,提供Python运行时环境变量的操控
    
    pygame.init()  #内部各功能模块进行初始化创建及变量设置,默认调用
    size = width,height = 800,600  #设置游戏窗口大小,分别是宽度和高度
    screen = pygame.display.set_mode(size)  #初始化显示窗口
    pygame.display.set_caption("小游戏程序")  #设置显示窗口的标题内容,是一个字符串类型
    while True:  #无限循环,直到Python运行时退出结束
        for event in pygame.event.get():  #从Pygame的事件队列中取出事件,并从队列中删除该事件
            if event.type == pygame.QUIT:  #获得事件类型,并逐类响应
                sys.exit()   #用于退出结束游戏并退出          
        pygame.display.update()  #对显示窗口进行更新,默认窗口全部重绘
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    代码执行流程

    在这里插入图片描述

    4 具体实现

    4.1 创建迷宫

    首先,当然是创建迷宫啦,为了方便,这里采用随机生成迷宫的方式(人工设计真的费眼睛,弄到一半不想弄了,有兴趣的可以自行尝试。)。思路其实很简单,就是把游戏界面划分成多个cell,类似这样子:

    在这里插入图片描述
    然后设计算法遍历所有的cell,每个被遍历到的cell在某几个随机的方向上打开一堵墙(就是去掉分割cell的线条)就ok啦~

    算法如下:

    '''随机生成迷宫类'''
    class RandomMaze():
      def __init__(self, maze_size, block_size, border_size, **kwargs):
        self.block_size = block_size
        self.border_size = border_size
        self.maze_size = maze_size
        self.blocks_list = RandomMaze.createMaze(maze_size, block_size, border_size)
        self.font = pygame.font.SysFont('Consolas', 15)
      '''画到屏幕上'''
      def draw(self, screen):
        for row in range(self.maze_size[0]):
          for col in range(self.maze_size[1]):
            self.blocks_list[row][col].draw(screen)
        # 起点和终点标志
        showText(screen, self.font, 'S', (255, 0, 0), (self.border_size[0]-10, self.border_size[1]))
        showText(screen, self.font, 'D', (255, 0, 0), (self.border_size[0]+(self.maze_size[1]-1)*self.block_size, self.border_size[1]+self.maze_size[0]*self.block_size+5))
      '''创建迷宫'''
      @staticmethod
      def createMaze(maze_size, block_size, border_size):
        def nextBlock(block_now, blocks_list):
          directions = ['top', 'bottom', 'left', 'right']
          blocks_around = dict(zip(directions, [None]*4))
          block_next = None
          count = 0
          # 查看上边block
          if block_now.coordinate[1]-1 >= 0:
            block_now_top = blocks_list[block_now.coordinate[1]-1][block_now.coordinate[0]]
            if not block_now_top.is_visited:
              blocks_around['top'] = block_now_top
              count += 1
          # 查看下边block
          if block_now.coordinate[1]+1 < maze_size[0]:
            block_now_bottom = blocks_list[block_now.coordinate[1]+1][block_now.coordinate[0]]
            if not block_now_bottom.is_visited:
              blocks_around['bottom'] = block_now_bottom
              count += 1
          # 查看左边block
          if block_now.coordinate[0]-1 >= 0:
            block_now_left = blocks_list[block_now.coordinate[1]][block_now.coordinate[0]-1]
            if not block_now_left.is_visited:
              blocks_around['left'] = block_now_left
              count += 1
          # 查看右边block
          if block_now.coordinate[0]+1 < maze_size[1]:
            block_now_right = blocks_list[block_now.coordinate[1]][block_now.coordinate[0]+1]
            if not block_now_right.is_visited:
              blocks_around['right'] = block_now_right
              count += 1
          if count > 0:
            while True:
              direction = random.choice(directions)
              if blocks_around.get(direction):
                block_next = blocks_around.get(direction)
                if direction == 'top':
                  block_next.has_walls[1] = False
                  block_now.has_walls[0] = False
                elif direction == 'bottom':
                  block_next.has_walls[0] = False
                  block_now.has_walls[1] = False
                elif direction == 'left':
                  block_next.has_walls[3] = False
                  block_now.has_walls[2] = False
                elif direction == 'right':
                  block_next.has_walls[2] = False
                  block_now.has_walls[3] = False
                break
          return block_next
        blocks_list = [[Block([col, row], block_size, border_size) for col in range(maze_size[1])] for row in range(maze_size[0])]
        block_now = blocks_list[0][0]
        records = []
        while True:
          if block_now:
            if not block_now.is_visited:
              block_now.is_visited = True
              records.append(block_now)
            block_now = nextBlock(block_now, blocks_list)
          else:
            block_now = records.pop()
            if len(records) == 0:
              break
        return blocks_list
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81

    4.2 定义角色类

    角色类需要根据用户的操作进行上下左右的移动,同时,保证移动是不能跨越墙的就OK了~具体而言,以下两点:

    定义角色

    '''定义hero'''
    class Hero(pygame.sprite.Sprite):
      def __init__(self, imagepath, coordinate, block_size, border_size, **kwargs):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.image.load(imagepath)
        self.image = pygame.transform.scale(self.image, (block_size, block_size))
        self.rect = self.image.get_rect()
        self.rect.left, self.rect.top = coordinate[0] * block_size + border_size[0], coordinate[1] * block_size + border_size[1]
        self.coordinate = coordinate
        self.block_size = block_size
        self.border_size = border_size
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    移动设置

    '''移动'''
      def move(self, direction, maze):
        blocks_list = maze.blocks_list
        if direction == 'up':
          if blocks_list[self.coordinate[1]][self.coordinate[0]].has_walls[0]:
            return False
          else:
            self.coordinate[1] = self.coordinate[1] - 1
            return True
        elif direction == 'down':
          if blocks_list[self.coordinate[1]][self.coordinate[0]].has_walls[1]:
            return False
          else:
            self.coordinate[1] = self.coordinate[1] + 1
            return True
        elif direction == 'left':
          if blocks_list[self.coordinate[1]][self.coordinate[0]].has_walls[2]:
            return False
          else:
            self.coordinate[0] = self.coordinate[0] - 1
            return True
        elif direction == 'right':
          if blocks_list[self.coordinate[1]][self.coordinate[0]].has_walls[3]:
            return False
          else:
            self.coordinate[0] = self.coordinate[0] + 1
            return True
        else:
          raise ValueError('Unsupport direction <%s> in Hero.move...' % direction)
      '''绑定到屏幕'''
      def draw(self, screen):
        self.rect.left, self.rect.top = self.coordinate[0] * self.block_size + self.border_size[0], self.coordinate[1] * self.block_size + self.border_size[1]
        screen.blit(self.image, self.rect)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    绑定到屏幕

     '''绑定到屏幕'''
      def draw(self, screen):
        self.rect.left, self.rect.top = self.coordinate[0] * self.block_size + self.border_size[0], self.coordinate[1] * self.block_size + self.border_size[1]
        screen.blit(self.image, self.rect)
    
    • 1
    • 2
    • 3
    • 4

    4.3 界面切换

    最后要完成游戏主循环,只要每次载入一个随机生成的迷宫地图和实例化一个主角,不断进行按键检测,并根据按键检测的结果移动主角,最后根据行动结果更新界面数据就可以了

    若主角到达了终点,则进入关卡切换界面。

    具体而言,代码实现如下:

    '''主函数'''
    def main(cfg):
      # 初始化
      pygame.init()
      pygame.mixer.init()
      pygame.font.init()
      pygame.mixer.music.load(cfg.BGMPATH)
      pygame.mixer.music.play(-1, 0.0)
      screen = pygame.display.set_mode(cfg.SCREENSIZE)
      pygame.display.set_caption('Maze - 微信公众号: Charles的皮卡丘')
      font = pygame.font.SysFont('Consolas', 15)
      # 开始界面
      Interface(screen, cfg, 'game_start')
      # 记录关卡数
      num_levels = 0
      # 记录最少用了多少步通关
      best_scores = 'None'
      # 关卡循环切换
      while True:
        num_levels += 1
        clock = pygame.time.Clock()
        screen = pygame.display.set_mode(cfg.SCREENSIZE)
        # --随机生成关卡地图
        maze_now = RandomMaze(cfg.MAZESIZE, cfg.BLOCKSIZE, cfg.BORDERSIZE)
        # --生成hero
        hero_now = Hero(cfg.HEROPICPATH, [0, 0], cfg.BLOCKSIZE, cfg.BORDERSIZE)
        # --统计步数
        num_steps = 0
        # --关卡内主循环
        while True:
          dt = clock.tick(cfg.FPS)
          screen.fill((255, 255, 255))
          is_move = False
          # ----↑↓←→控制hero
          for event in pygame.event.get():
            if event.type == pygame.QUIT:
              pygame.quit()
              sys.exit(-1)
            elif event.type == pygame.KEYDOWN:
              if event.key == pygame.K_UP:
                is_move = hero_now.move('up', maze_now)
              elif event.key == pygame.K_DOWN:
                is_move = hero_now.move('down', maze_now)
              elif event.key == pygame.K_LEFT:
                is_move = hero_now.move('left', maze_now)
              elif event.key == pygame.K_RIGHT:
                is_move = hero_now.move('right', maze_now)
          num_steps += int(is_move)
          hero_now.draw(screen)
          maze_now.draw(screen)
          # ----显示一些信息
          showText(screen, font, 'LEVELDONE: %d' % num_levels, (255, 0, 0), (10, 10))
          showText(screen, font, 'BESTSCORE: %s' % best_scores, (255, 0, 0), (210, 10))
          showText(screen, font, 'USEDSTEPS: %s' % num_steps, (255, 0, 0), (410, 10))
          showText(screen, font, 'S: your starting point    D: your destination', (255, 0, 0), (10, 600))
          # ----判断游戏是否胜利
          if (hero_now.coordinate[0] == cfg.MAZESIZE[1] - 1) and (hero_now.coordinate[1] == cfg.MAZESIZE[0] - 1):
            break
          pygame.display.update()
        # 更新最优成绩
        if best_scores == 'None':
          best_scores = num_steps
        else:
          if best_scores > num_steps:
            best_scores = num_steps
        # 关卡切换
        Interface(screen, cfg, mode='game_switch')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

    5 最后

  • 相关阅读:
    Selenium 模拟浏览器操作案例
    【c++】weak_ptr和观察者模式
    java毕业设计社区医疗服务系统mybatis+源码+调试部署+系统+数据库+lw
    数据结构(c语言版) 树的双亲表示法
    【Python数据科学快速入门系列 | 04】Numpy四则运算、矩阵运算和广播机制的爱恨情仇
    面试题之在async await中如何捕获到reject的Promise?
    chrome V3插件入门到放弃,Plasmo不完全使用指南
    无法删除dll文件
    算法leetcode|81. 搜索旋转排序数组 II(rust重拳出击)
    WIFI简介 以及SDIO总线介绍
  • 原文地址:https://blog.csdn.net/HUXINY/article/details/126966726