目录

前言

项目需求

Prim算法生成迷宫

1 算法思路介绍

2 代码示例

3 用于测试的代码

预制体(预制件)

1 定义

2 构建迷宫墙体预制体

完善工程


前言

本节我们来做迷宫生成。生成迷宫的算法其实有很多种,最简单的比如递归。而这一节我们选择prim算法生成迷宫,因为这种算法生成的迷宫较为自然,并且算法原理简易。

项目需求

使用prim算法和预制体生成迷宫。

Prim算法生成迷宫

1 算法思路介绍

本部分中的所有图示采用RPG Maker XP建立。顺便吐槽一句,现在网上流传的有关prim算法生成迷宫的算法描述(那个据说来自维基的)简直是语文不及格产物。

Prim算法与图论有关(我在article105中介绍过基本的图搜索算法和一个使用Ruby书写的搜索模版),它最开始被用于在加权连通图里搜索一个最小生成树。个人认为它还有些像用堆实现的二叉树遍历算法。其具体资料请读者自行学习,笔者在此只给出一个用于迷宫生成场景下较为通俗的描述。

我们假设在区域内有一个N*M列的迷宫。对于迷宫的每个格子,它们只有两个状态:通路/墙。在初始状态下,这个迷宫里的所有格子都是墙。设定一个的类Wall,其数据为[Vector2Int pos, Vector2Int relative],也就是其位置向量pos与其相对格子的位置向量relative(在后续步骤中会解释)。同时,我们建立一个墙的空列表M。

然后我们选定一个起点A,将此位置的墙移除:

将与起点A相邻的所有墙加入M中,其中A作为这些墙的父格子。对于一面墙而言,我们称在其父格子另一边的格子为相对格子。它们的例子如图:

在M中随机选择一个元素x。对于x,如果其相对格子为墙,则移除相对格子和x的墙,然后将相对格子作为父格子,将相邻墙加入列表。否则则从M中移除x。如此循环直到M中为空。

2 代码示例

我们会创建一个墙的类Wall与生成迷宫的类Maze。

Wall.cs

public class Wall //内部类Wall{public Vector2Int pos; //位置向量public Vector2Int relative; //相对格子向量public Wall(Vector2Int pos,Vector2Int anc){this.pos=pos;ancToRelative(anc);}public void ancToRelative(Vector2Int ancestor){ //输入一个父格子,求出其相对格子并赋值this.relative=pos*2-ancestor; //int不能左乘Vector2Int(因为这里的*是Vector2Int的运算符重载)}}

Maze.cs

public class Maze //内部类Maze{private Vector2Int[] e={new Vector2Int(1,0),new Vector2Int(-1,0),new Vector2Int(0,1),new Vector2Int(0,-1),}; //四方向private int width;private int height; //迷宫矩阵的宽和高 默认[宽,高],Maze位置的游标在<[0..width],[0..height]>private Vector2Int origin; //起点public List<Wall> M; //listpublic List<Wall> W; //listpublic List<Vector2Int> maze; //最终的迷宫列表public Maze(int width,int height,Vector2Int origin){this.width=width;this.height=height;this.origin=origin;this.M=new List<Wall>();this.W=new List<Wall>();this.maze=new List<Vector2Int>();this.maze.Add(origin);}bool findMaze(Vector2Int pos){ //只判断此位置是否已移除墙if(this.maze.Contains(pos)){return true;}return false;}bool borderExam(Vector2Int pos){ //边界检查if(pos.x>=0 &&pos.x<width && pos.y>=0 &&pos.y<height){return true;}return false;}void createWall(Vector2Int anc){ //传入移除墙的格子,创建相邻墙foreach(Vector2Int i in this.e){Vector2Int index=anc+i; //上下左右格子的位置if(borderExam(index) && !findMaze(index)){ //检查确定这个位置确实是墙,并且没有越界this.M.Add(new Wall(index,anc)); //将墙加入M中}}}void funcM(){ //随机取出M中的墙,执行步骤int index=Random.Range(0,this.M.Count); Wall wall=this.M[index];if(findMaze(wall.pos)){ //之前已经移除了此墙this.M.RemoveAt(index);return;}if(findMaze(wall.relative) || !borderExam(wall.relative)){ //相对位置已移除墙,或者此墙的相对位置无效this.M.RemoveAt(index); //此墙移除}else{ this.M.RemoveAt(index);this.maze.Add(wall.pos);this.maze.Add(wall.relative); //后面填什么无所谓,给相对位置挖墙createWall(wall.relative); //加入新的墙}}public void exec(){ //入口?createWall(this.origin); //起点while(M.Count!=0){funcM();}}}

在生成迷宫时,声明一个Maze类的实例并且调用其exec方法,就可得到一个迷宫。

3 用于测试的代码

此代码在Start方法内调用,可以在控制台生成一份迷宫示意图。

 string a="";Maze maze=new Maze(30,20,new Vector2Int(0,0));maze.exec();for(int i=0;i<30;i++){for(int j=0;j<20;j++){if(maze.maze.Contains(new Vector2Int(i,j))){a=a+"-";}else{a=a+"#";}}a=a+"\n";}print(a);

预制体(预制件)

-本节相关内容请读者参考:

-预制件 - Unity 手册,《预制件》

-Object-Instantiate - Unity 脚本 API,《Object.Instantiate

1 定义

预制体(英文为prefab,在unity手册中被称为预制件)可以看做是一个文件化的游戏对象,一个已经构建好了的样板。如果从编程的角度进行类比,预制体可以被看成一个用于派生类的基类,或者是一个单纯的被复制对象。预制体以实体文件(.prefab)的形式存在于unity中,并可以在有需要时动态加载进游戏场景。

预制体通常被应用于如下两个场景中:一,被经常使用的游戏对象(比如森林场景中的一棵树);二,在场景被加载时不应当存在于场景中的游戏对象(比如说闯关游戏中的通关画面)。

2 构建迷宫墙体预制体

首先确认预制体的形状和大小。由于迷宫的场地是正方形的,我们采用以正方形为底的四棱柱作为预制体的形状,则底边长设定为scale1单位,高度为scale2单位。

创建一个Cube,调整其大小和位置直到满足上文中的条件。然后,重命名Cube为prefab,选中prefab并将其拖动到Project选项卡下的游戏资源文件夹内部,可以看到出现了一个同名文件,即为预制体。

将预制体文件拖动到Hierarchy选项卡下,可以看到预制体又出现在场景中了。需要注意的是,如果要编辑预制体文件本身,请点击预制体右边的“>”,在场景中直接编辑的不是预制体,而只是其的一个副本。预制体在场景中以蓝色显示。

完善工程

将目的区域的大小调整到与球体相同的水平,也就是scale0.1大小(顺便要调整变色和音效生效的区域)。

创建一个空游戏对象MazeController,搭载用于生成迷宫的脚本MazeController.cs。

敲代码。

MazeController.cs

using System.Collections;using System.Collections.Generic;using System.Text;using System.Threading.Tasks;using UnityEngine;using Random=UnityEngine.Random;public class Wall //内部类Wall{public Vector2Int pos; //位置向量public Vector2Int relative; //相对格子向量public Wall(Vector2Int pos,Vector2Int anc){this.pos=pos;ancToRelative(anc);}public void ancToRelative(Vector2Int ancestor){ //输入一个父格子,求出其相对格子并赋值this.relative=pos*2-ancestor; //int不能左乘Vector2Int(因为这里的*是Vector2Int的运算符重载)}}public class Maze //内部类Maze{private Vector2Int[] e={new Vector2Int(1,0),new Vector2Int(-1,0),new Vector2Int(0,1),new Vector2Int(0,-1),}; //四方向private int width;private int height; //迷宫矩阵的宽和高 默认[宽,高],Maze位置的游标在<[0..width],[0..height]>private Vector2Int origin; //起点public List<Wall> M; //listpublic List<Wall> W; //listpublic List<Vector2Int> maze; //最终的迷宫列表public Maze(int width,int height,Vector2Int origin){this.width=width;this.height=height;this.origin=origin;this.M=new List<Wall>();this.W=new List<Wall>();this.maze=new List<Vector2Int>();this.maze.Add(origin);}bool findMaze(Vector2Int pos){ //只判断此位置是否已移除墙if(this.maze.Contains(pos)){return true;}return false;}bool borderExam(Vector2Int pos){ //边界检查if(pos.x>=0 &&pos.x<width && pos.y>=0 &&pos.y<height){return true;}return false;}void createWall(Vector2Int anc){ //传入移除墙的格子,创建相邻墙foreach(Vector2Int i in this.e){Vector2Int index=anc+i; //上下左右格子的位置if(borderExam(index) && !findMaze(index)){ //检查确定这个位置确实是墙,并且没有越界this.M.Add(new Wall(index,anc)); //将墙加入M中}}}void funcM(){ //随机取出M中的墙,执行步骤int index=Random.Range(0,this.M.Count); Wall wall=this.M[index];if(findMaze(wall.pos)){ //之前已经移除了此墙this.M.RemoveAt(index);return;}if(findMaze(wall.relative) || !borderExam(wall.relative)){ //相对位置已移除墙,或者此墙的相对位置无效this.M.RemoveAt(index); //此墙移除}else{ this.M.RemoveAt(index);this.maze.Add(wall.pos);this.maze.Add(wall.relative); //后面填什么无所谓,给相对位置挖墙createWall(wall.relative); //加入新的墙}}public void exec(){ //入口?createWall(this.origin); //起点while(M.Count!=0){funcM();}}}//上略public class MazeController : MonoBehaviour{public GameObject wall; //墙体预制体public Transform destArea; //终点区域的位置const int Width=10;const int Height=10; // Start is called before the first frame updatevoid Start(){Vector3 correct1=new Vector3(0.5f,0,0.5f); //墙体中心修正Vector3 correct2=new Vector3(-5,0,-5); //平面偏移修正Vector2Int dest=new Vector2Int((int)(destArea.position.x+4.5f),(int)(destArea.position.y+4.5f)); //目的区域位置修正Maze maze=new Maze(Width,Height,dest); //按照平台的大小生成迷宫maze.exec();//可惜C#里没有Rangefor(int x=0;x<Width;x++){for(int y=0;y<Height;y++){if(!maze.maze.Contains(new Vector2Int(x,y))){ //本区域不为空Instantiate(wall,new Vector3(x,2,y)+correct1+correct2,Quaternion.identity); //Quaternion.identity是0角度}}}}// Update is called once per framevoid Update(){}}

运行游戏,如图所示:

Unity游戏教程初步(六):迷宫算法与预制体相关推荐

  1. Unity游戏教程初步(八):Animator的使用

    1 前言 本节中我们来介绍Unity的动画系统以及管理动画剪辑(Animation Clip)的组件Animator. 2 Unity中的动画系统 Unity的动画系统又称为Mecanim,是一个基于 ...

  2. Unity游戏教程初步(五):Resources与UI

    目录 前言 项目需求 增加音效 增加UI # TextMeshPro-Text 增加得分点 前言 在上一节中我们给球体添加了纹理,并且为场景设置了一个目的区域.当球体处于目的区域内时,其将变色.在这一 ...

  3. C#开发Unity游戏教程循环遍历做出判断及Unity游戏示例

    C#开发Unity游戏教程循环遍历做出判断及Unity游戏示例 Unity中循环遍历每个数据,并做出判断 很多时候,游戏在玩家做出判断以后,游戏程序会遍历玩家身上大量的所需数据,然后做出判断,即首先判 ...

  4. C#开发Unity游戏教程之判断语句

    C#开发Unity游戏教程之判断语句 游戏执行路径的选择--判断 玩家在游戏时,无时无刻不在通过判断做出选择.例如,正是因为玩家做出的选择不同,才导致游戏朝着不同的剧情发展,因此一个玩家可以对一个游戏 ...

  5. C#开发Unity游戏教程之游戏对象的行为逻辑方法

    C#开发Unity游戏教程之游戏对象的行为逻辑方法 游戏对象的行为逻辑--方法 方法(method),读者在第1章新建脚本时就见过了,而且在第2章对脚本做整体上的介绍时也介绍过,那么上一章呢,尽管主要 ...

  6. C#开发Unity游戏教程之使用脚本变量

    C#开发Unity游戏教程之使用脚本变量 使用脚本变量 本章前面说了那么多关于变量的知识,那么在脚本中要如何编写关于变量的代码,有规章可循吗?答案是有的.本节会依次讲解变量的声明.初始化.赋值和运算. ...

  7. Unity的使用(四):预制体,创建地形和地形导航

    前面介绍了Unity游戏引擎的基础功能,现在终于要进入到游戏开发中了.那么,一款游戏开发要有资源,这个一般是由美术提供的,我们只需要负责程序方面的事.那么,怎么将获得的资源应用起来呢? 一. 导入资源 ...

  8. unity游戏教程 space shooter (游戏控制器)

    为了更好地理解unity,模仿了教程,以下均为教程中的代码: GameController.cs: using System.Collections; using System.Collections ...

  9. Unity游戏制作(六)

    3D 游戏与编程 Homework 6 实验内容 智能巡逻兵 提交要求: 游戏设计要求: 创建一个地图和若干巡逻兵(使用动画): 每个巡逻兵走一个3~5个边的凸多边型,位置数据是相对地址.即每次确定下 ...

最新文章

  1. 面试再被问到 ConcurrentHashMap,把这篇文章甩给他!
  2. javascript eval函数解析json数据时为什加上圆括号eval((+data+))
  3. 【Problem solved】 error C2665: “loadimage”: 2 个重载中没有一个可以转换所有参数类型...
  4. Maximum XOR Sum 系列问题
  5. 20220201--CTF刷题MISC方向--第4题
  6. c#做端口转发程序支持正向连接和反向链接
  7. 引导页onboarding页面Snapkit实现
  8. 计算几何——扇形面积
  9. idea properties中文乱码uncode转中文
  10. Arduino UNO驱动DS1307数字实时时钟RTC
  11. css表格文字超数量就竖排_css实现文字竖排
  12. QQ三国七旗阵等级怎么计算?(附Excel计算器,输入队员等级即可计算)
  13. php socket accept,使用PHP Socket开发Yar TCP服务
  14. EXCEL根据两点经纬度计算距离
  15. 宇视摄像机默认用户名、密码、端口是多少
  16. 更换服务器IP有哪些步骤,如何操作。
  17. 计算机与机械专业 有什么大学排名,2017机械排名211大学排名
  18. 除了中国知网和谷歌文学还有哪些好的有权威的资源站?
  19. 每秒1.28万亿行,最快的分布式关系数据库MemSQL又破记录了!
  20. SSM框架的介绍与搭建

热门文章

  1. Android自定义View-滑动解锁按钮
  2. minio分布式文件存储 windows部署 和 api 使用
  3. C语言之容易想到的一种数组去重排序方法
  4. ImageView.ScaleType属性分析
  5. XZ_iOS 之 iTunes无法连接到此iPhone。无法分配资源。
  6. 智能家居的发展现状以及未来发展趋势的分析
  7. halcon MLP分类器
  8. Linux时间校准(ntpdate及NTP客户端代码校准示例)
  9. python 图片处理之水平镜像变换
  10. python中image什么意思_浅谈python图片处理Image和skimage的区别