迷宫随机生成算法

迷宫生成算法是游戏中各种随机生成都可能用到的最基本的算法。下图展示了一个随机场景生成器的迷宫生成部分。

依次经过了6个步骤:生成迷宫=》调节占空比=》自动手绘效果=》平滑=》腐蚀=》移除死胡同.

首先生成迷宫,比较有名的是prim算法,可以生成主路扭曲型 和自然分岔型迷宫。

主路扭曲型迷宫一般分岔较少,有一条明显的主干道,适合于闯关游戏。

自然分岔型迷宫则分岔较多,没有明显的主路,比较适合于rpg类游戏。

prim算法生成的迷宫有一个特点,所有的路和墙都只有一个格子的宽度,通常不适合直接用于游戏中,需要做一系列的后期处理。

调节占空比:将墙和路随机拓宽 

结合寻路算法拓宽主路:

添加手绘效果:

添加房间及斜向移动:

 其它 迷宫生成算法

随机筑线法:先将整个地图初始化为路,然后随机筑一些线墙

 噪声阈值法:先生成平滑的噪声图,噪声值小于阈值的地方挖出路径。

 tsp、mst算法:利用巡回销售员算法 或最小路径树算法开挖路径。这是一个效果相当不错的算法,当然也可以使用只能直角拐弯的tsp、mst算法。

递归分割算法 

圆形迷宫:

 3d迷宫:

截图见下一篇文章

横版迷宫

迷宫算法在地形生成中的运用:

迷宫生成 :

//========================================================
//  @Date:     2016.05
//  @File:     Include/Render/Maze.h
//  @Brief:     Maze
//  @Author:     LouLei
//  @Email:  twopointfive@163.com
//  @Copyright (Crapell) - All Rights Reserved
//========================================================

#pragma once

#ifndef  __Maze__H__
#define  __Maze__H__

#include "Math/MathLib.h"
#include "Texture.h"
 
enum MazeMethod
{
	LoadFromFile_,
	//随机筑路法 persent=road密度
	RandRoad,
	//随机筑墙法 persent=wall密度
	RandWall,

	//!方案一:自然分岔型  类似prim 但没有保存回退链表
	//不出现明显的主路,分岔路比较多,更复杂,即玩家需要做选择的次数可能比较多。
	NatureSingleMaze,

	//筑墙法 横版过关  persent=wall密度
	Horizontal,

	//乱线筑墙法 
	Line,

	//prim算法:自然分岔型  
	//不出现明显的主路,分岔路比较多,更复杂,即玩家需要做选择的次数可能比较多。
	//width 奇数  入口在奇数边上 为四边封闭
	//width 偶数  入口在偶数边上 为四边开放
	//其它为四边半开半闭
	PrimNature,
	PrimNature2,

	//prim算法:主路扭曲型
	//有一条明显的主路,即一条特别长、贯穿大部分区域的路线,适合rpg
	PrimTrunk,

	//prim算法:自然分岔型 带room
	PrimNatureRoom,

	//prim算法:主路扭曲型 带room
	PrimTrunkRoom,

	//prim算法:自然分岔型 带直行桥
	PrimNatureWeaver,

	//prim算法:主路扭曲型 带直行桥
	//PrimTrunkWeaver,

	//prim算法:自然分岔型 挖墙 可控直行几率
	//sameLikeLast-1~1保持挖墙方向不变的概率 -1复杂多变 1具有比较好的straight效果
	PrimTrunkStraight,

	//方案一:递归分割
	//比较横平竖直的大道 
	Straight,

	//方案一:递归分割
	//Split,

	//方案一:圆形迷宫 
	Circle,


	//连续不规则迷宫:柏林噪声 高度值构造迷宫
	//NoiseMaze,

	//噪声+模糊
	Noise,

	MazeMethodMax,

};
const char* MazeMethodToString(int enumeration);
const char* MazeMethodToString2(int enumeration);
bool StringToMazeMethod(const char* str,int& type);

class MazeDef;
 
class MazeBase
{
public:
	enum BlockType
	{
		Unused     = 0,  //-1,//255,
		Road       = 1,  //标准路 2D:四通 八通  3D:六通 十八通 二十六通
		Wall          ,  //墙

		//以下是非标准路

		//只能横向通过或只能纵向通过 这不是桥而是普通的路 另外两端被堵上了
		//只能右拐通过 这也不是桥而是普通的路 另外两端被堵上了
		Bridge     ,  //直行桥 2双通

		Transporter,  //传送点 

		Slope_UL   ,  //左上开口的斜坡  四通的基础上局部多通  ?组合斜坡
		Slope_UR   ,
		Slope_DL   ,
		Slope_DR   ,
	};

	enum TextureUnitType
	{
		TexUnused = 0,   //
		TexRoad,
		TexStoneWall,//石墙,
		TexDirtWall,   
		TexRoadWall,//道路两旁的阻挡墙
		TexRoomWall,   //房间四壁墙
		TexWater,	
		TexDoor,		 //门,渲染时再区分朝向
 
		TexFloor,
		TexCorridor,	 //走廊,
		TexUpStairs,	 //楼上,
		TexDownStairs,	 //楼下,
		TexGround,		 //
		TexDirtWallShadow,

		TexBridge     ,  //直行桥 2双通

		TexTransporter,  //传送点 

		TexSlope_UL   ,  //左上开口的斜坡  四通的基础上局部多通  ?组合斜坡
		TexSlope_UR   ,
		TexSlope_DL   ,
		TexSlope_DR   ,

		TexNum,

	};

	enum DebugFlag
	{
		Debug           = 100,
		Debug_RemoveWall,
		Debug_Path_Start,
		Debug_Path_End,
		Debug_Path_Up   ,
		Debug_Path_Down ,
		Debug_Path_Left ,
		Debug_Path_Right,
		Debug_Path_LeftUp ,
		Debug_Path_LeftDown ,
		Debug_Path_RightUp,
		Debug_Path_RightDown,
	};


	enum Dir
	{
		UP   =0,
		DOWN ,
		LEFT ,
		RIGHT,
		UPLEFT,
		UPRIGHT,
		DOWNLEFT,
		DOWNRIGHT,
	};
	static vec2I DIR[8];


	class Block
	{
	public:
		char  type;
		char  debugFlag;
		char  texture; //texture unit
		char  cost;    //walk cost
		union
		{
			//传送到的位置 Transporter类型使用
			int   transporter;
			//int   wallFlag;
		};
		//int   seed;
	};

	virtual ~MazeBase(){};
};

class MazeDef
{
public:
	class TextureUnit
	{
	public:
		//!TextureUnitType;
		RectF texCoordRect[8];
		int   unitNum;
	};
	TextureUnit m_textureUnits[MazeBase::TexNum];
	bool  LoadFromFile(const char* filename);
	RectF GetTextureRect(MazeBase::TextureUnitType type,int index=-1);
	//!mat32d 获得矩阵将纹理坐标从【0,1】转到实际区域
	mat4  GetTextureMatrix(MazeBase::TextureUnitType type,int index=-1);
};


class Maze:public MazeBase
{
public:
	Maze();
	Maze(Maze& other);
	virtual ~Maze();
	Maze& operator=(/*const*/ Maze& other);

	bool  LoadFromFile(const char* fileName);
	bool  SaveToFile  (const char* fileName);

	//随机筑路法 persent=road密度
	bool  GenerateRandRoadMaze(int width,int height,float persent);
	//随机筑墙法 persent=wall密度
	bool  GenerateRandWallMaze(int width,int height,float persent);

	//!方案一:自然分岔型  类似prim 但没有保存回退链表
	//不出现明显的主路,分岔路比较多,更复杂,即玩家需要做选择的次数可能比较多。
	bool  GenerateNatureSingleMaze(int width,int height);

	//筑墙法 横版过关  persent=wall密度
	bool  GenerateHorizontalMaze(int width,int height,float persent);

	//乱线筑墙法 
	bool  GenerateLineMaze(int width,int height,float persent);

	//prim算法:自然分岔型  
	//不出现明显的主路,分岔路比较多,更复杂,即玩家需要做选择的次数可能比较多。
	//width 奇数  入口在奇数边上 为四边封闭
	//width 偶数  入口在偶数边上 为四边开放
	//其它为四边半开半闭
	bool  GeneratePrimNatureMaze(int width,int height);
	bool  GeneratePrimNatureMaze2(int width,int height);

	//prim算法:主路扭曲型
	//有一条明显的主路,即一条特别长、贯穿大部分区域的路线,适合rpg
	bool  GeneratePrimTrunkMaze(int width,int height);

	//prim算法:自然分岔型 带room
	bool  GeneratePrimNatureRoomMaze(int width,int height);

	//prim算法:主路扭曲型 带room
	bool  GeneratePrimTrunkRoomMaze(int width,int height);

	//prim算法:自然分岔型 带直行桥
	bool  GeneratePrimNatureWeaverMaze(int width,int height);

	//prim算法:主路扭曲型 带直行桥
	//bool  GeneratePrimTrunkWeaverMaze(int width,int height);

	//prim算法:自然分岔型 挖墙 可控直行几率
	//sameLikeLast-1~1保持挖墙方向不变的概率 -1复杂多变 1具有比较好的straight效果
	bool  GeneratePrimTrunkStraightMaze(int width,int height,float sameLikeLast);

	//方案一:递归分割
	//比较横平竖直的大道 
	bool  GenerateStraightMaze(int width,int height);

	//方案一:递归分割
	//bool  GenerateSplitMaze(int width,int height);

	//方案一:圆形迷宫 
	bool  GenerateCircleMaze(int width,int height);


	//连续不规则迷宫:柏林噪声 高度值构造迷宫
	//bool  GenerateNoiseMaze(int width,int height,int limit);

	//噪声+模糊
	bool  GenerateNoiseMaze(int width,int height);

	//
	bool  GenerateTspMaze(int width,int height);
	bool  GenerateTspSquareMaze(int width,int height,void* outPath=NULL);
	bool  GenerateMstMaze(int width,int height);

	void  AllocMaze(int width,int height,BlockType blockType,TextureUnitType tex);
	void  Free();
	//调试输出
	void  OutputMaze();

//后期处理
	//填充死胡同  如果不限制长度,整个迷宫将被填平 Accumulation (障碍堆积系数)
	bool  FillDeadEnd(int maxDepth);

	//Erosion(障碍侵蚀系数)
	bool  ErosionWall(float persent);

	//周围墙的数量大于4则为wall。否则为road。
	bool  SmoothMap();

	//移除孤立的wall road等 0~3  b不保证联通性  多次分小步骤调用效果不同
	bool  SmoothMap2(int limit);

	//调节占空比 适用于奇数列/行筑墙法产生的迷宫 偶数列、行可以任意拉宽
	//参数:奇偶行扩张的最小最大限制
	bool  AdjustSpace(int evenMin,int evenMax,int oddMin,int oddMax);

	//无需调节占空比 绘制时把奇数格子画窄点即可?

	//获得某点周围的数3*3
	int   GetSurroundingBlocks(int posX, int posY,int halfSize,BlockType type);

	//确保打通路径,delCorner是否挖单墙
	int   MakePath(const vec2I& enter,const vec2I& exit,bool delCorner = false);

	//调试打通路径
	int   DebugPath(const vec2I& enter,const vec2I& exit);
	//重置调试路径
	int   ClearDebugPath();
//
	//将四条边筑成墙
	void  MakeBoundWall();
	//生成unused减少构造墙体模型的面数
	void  MakeUnused();

	//生成45度斜坡 生成几率rat[0,1]
	void  MakeSlope45(float rat);
	//主干路+分支小路 寻路后使用粗画刷
	void  MakeBigRoad();
	//路的连接点处生成场地
	void  MakeJointRoom(float rat,int radius);

	//随机出入口
	void  RandEnterExit();

	//添加手绘效果
	void  HandPaint(int strength);

	//随机喷洒 将scr喷洒到tar
	void  SprayData(int num,BlockType scr,BlockType tar);
	void  SprayData(int num,TextureUnitType scr,TextureUnitType tar);

	//随机喷洒 bridge
	void  SprayBridge(int num);

	//随机喷洒 transporter
	void  SprayTransporter(int num);

	//
	void  BrushBlock(int x, int y,int radius,BlockType tar,TextureUnitType tex);
	void  BrushBlock(int startX, int startY,int endX,int endY,int radius,BlockType tar,TextureUnitType tex);

	//寻找路径
	int   FindPath(const vec2I& enter,const vec2I& exit);

	//选区区域内复合类型的随机点
	int   RandPoint(int l,int r,int t,int d,BlockType tar,int& outX,int& outY);


	inline bool       valid(int x,int y);
	inline Block&     mazeBlock(int x,int y);
	inline char&      mazeBlockType(int x,int y);
	inline void       setMazeBlock(int x,int y,Maze::BlockType type,Maze::TextureUnitType tex);
	//是否可以通行
	inline bool canWalk(int x,int y);

	//大小
	int   m_mazeWidth;									
	int   m_mazeHeight;

	bool  m_bX;

	//入口 出口
	vec2I   m_enter;
	vec2I   m_exit;

	Block* m_mazeData;
	char   m_debugTxt[512];
};

inline bool Maze::valid(int x,int y)
{
	if (x<0 || x>=m_mazeWidth || y<0 || y>=m_mazeHeight)
	{
		return false;
	}
	return true;
}
inline Maze::Block& Maze::mazeBlock(int x,int y)
{
	if (x<0 || x>=m_mazeWidth || y<0 || y>=m_mazeHeight)
	{
		assert(0);
		//return 0;
	}
	return m_mazeData[m_mazeWidth*(y) + (x)];
}

inline char& Maze::mazeBlockType(int x,int y)
{
	if (x<0 || x>=m_mazeWidth || y<0 || y>=m_mazeHeight)
	{
		assert(0);
		//return 0;
	}
	return m_mazeData[m_mazeWidth*(y) + (x)].type;
}

inline void Maze::setMazeBlock(int x,int y,Maze::BlockType type,Maze::TextureUnitType tex)
{
	if (x<0 || x>=m_mazeWidth || y<0 || y>=m_mazeHeight)
	{
		assert(0);
		//return 0;
	}
	Block* block = &m_mazeData[m_mazeWidth*(y) + (x)];
	block->type = type;
	block->texture = tex;
}
inline bool  Maze::canWalk(int x,int y)
{
	if (x<0 || x>=m_mazeWidth || y<0 || y>=m_mazeHeight)
	{
		//assert(0);
		return false;
	}
	char data = m_mazeData[m_mazeWidth*(y) + (x)].type;
	if(data == Wall
		||data == Unused
		)
		return false;
	return true;
}

namespace RendSys
{
	class MovieClip;
	class Frame;
}
//迷宫绘制  TreeWall 树墙土地(树根方块草) 草墙土沟  浮岛水面  砖墙地板
class MazeViewer:public Maze
{
public:
	class BlockRend
	{
	public:
		BlockRend();
		~BlockRend();
		RendSys::MovieClip*  movie;
		RendSys::Frame*      frame;
	};

	MazeViewer();
	virtual ~MazeViewer();

	bool  LoadFromFile(const char* filename);
	virtual void Render();

	inline BlockRend&   mazeBlockRend(int x,int y);
	inline Block&       mazeBlockf(float x,float y);
	inline vec2I        blockI(float x,float y);
	inline vec2         blockCen(int x,int y);
	int  CanWalk(vec3& fromPos);
	int  CanWalk(vec3& fromPos,vec3& toPos,vec3& newPos);
	void ClampPos(vec3& pos);

	float m_cellWidth;
	float m_cellHeight;
	float m_mapWidth;
	float m_mapHeight;
	float m_halfMapWidth;
	float m_halfMapHeight;

	TexturePtr m_textureTerrain;
	BlockRend* m_mazeRendData;

	RendSys::MovieClip* m_movieLib;
	MazeDef* m_mazeDef;
};
inline Maze::Block& MazeViewer::mazeBlockf(float x,float y)
{
	return mazeBlock((x+m_halfMapWidth)/m_cellWidth,(y+m_halfMapHeight)/m_cellWidth);
}

inline vec2I MazeViewer::blockI(float x,float y)
{
	return vec2I((x+m_halfMapWidth)/m_cellWidth,(y+m_halfMapHeight)/m_cellWidth);
}
inline vec2 MazeViewer::blockCen(int x,int y)
{
	return vec2((x+0.5f)*m_cellWidth-m_halfMapWidth,(y+0.5f)*m_cellWidth-m_halfMapHeight);
}
inline MazeViewer::BlockRend& MazeViewer::mazeBlockRend(int x,int y)
{
	if (x<0 || x>=m_mazeWidth || y<0 || y>=m_mazeHeight)
	{
		assert(0);
		//return 0;
	}
	return m_mazeRendData[m_mazeWidth*(y) + (x)];
}

class Dungeon:public MazeViewer
{
public:
	Dungeon();
	~Dungeon();


	bool makeCorridor(int x, int y, int size, int dir);
	bool makeRoom(int x, int y, int sizeMin, int sizeMax, int dir);

	//!	 生成地牢迷宫
	bool GenerateDungeonMaze(MazeDef* dungeonDef,int width, int height, int maxObj);
	void Render();
};


//寻路算法
class AStarPathFinder
{
public:
	enum NodeType
	{
		Road       =0,//双向通过
		Start	   =1,
		Target     =2,
		Barrier    =3,
		Bridge     ,  //直行桥

		Transporter,  //传送点
	};

	struct AStarNode
	{
		int        x;	// 坐标
		int        y;
		int        g;	// 起点到此点的距离( f=g+h )
		int        h;	// 启发函数预测的到终点的距离
		NodeType   type;// 结点类型:终点,障碍物
		AStarNode* parent;	// 父节点
		AStarNode* next;	// 路径指向
		int        transporter;	// 传送点
		bool       inClose;	// 是否在close表中
		bool       inOpen;	// 是否在open表中
	};

	AStarPathFinder();
	~AStarPathFinder();

	void Free();
	int  InitMap (int w,int h,const char* mapData);
	int  InitMap (Maze& maze);
	int  FindPath(int enterX, int enterY,int exitX,int exitY);
	int  FindPath(int enterX, int enterY,NodeType exitType);
	//int  FindPathf(const vec2& enter,const vec2& exit);

	// 交换两个元素
	void swap( int idx1, int idx2 );  

	// 堆调整
	void adjust_heap( int nIndex );  

	// 插入open表
	void InsertToOpenTable( int x, int y, AStarNode* currNode, AStarNode* endNode, int w );

	// 邻居点插入open表
	void InsertNeighbors( AStarNode* currNode, AStarNode* endNode );

	inline AStarNode& mazeBlock(int x,int y)
	{
		if (x<0 || x>=m_mazeWidth || y<0 || y>=m_mazeHeight)
		{
			assert(0);
			static AStarNode invalid;
			return invalid;
		}
		return m_mazeData[m_mazeWidth*(y) + (x)];
	}

	int         m_mazeWidth;									
	int         m_mazeHeight;
	AStarNode*  m_mazeData;	   //结点数组
	AStarNode** m_openTable;   //open表
	AStarNode** m_closeTable;  //close表
	int	        m_openCount;   //open表节点数

	int         m_pathSize;    //搜索到的路径节点数
	bool        m_bX;

};

//广度优先搜索(BFS)具有完备性,能够保证搜索到最优路径。但路径只有向上/下/左/右移动这四个动作,无法实现能够对角移动的路径规划(只能用于无权图的路径规划),

//带权图的图搜索算法——Dijkstra算法(狄克斯特拉算法)。

//墨水扩散寻路算法 类似Dijkstra算法

//拟人寻路算法
class AIPathFinder
{
public:
	//记忆力 最大记忆量随着时间会增长  有记号笔的情况下记忆量无穷大
	//超出记忆量时部分路点标记会被擦除  多次走过的和刚刚才走过的路点记忆会更牢固 不易被擦除
	//死胡同的记忆会标记重要度

	//视野 格子数

	//先小量的广度优先搜索  取较好的路深度优先搜索

	//随搜随走

	//回退时可能会迷路

	enum NodeType
	{
		Road       =0,//双向通过
		Start	   =1,
		Target     =2,
		Barrier    =3,
		Bridge     ,  //直行桥

		Transporter,  //传送点
	};

	struct AINode
	{
		int        x;	// 坐标
		int        y;
		int        visited;	// 是否访问过
		int        deadend;	// 是否死胡同
		NodeType   type;// 结点类型:终点,障碍物
		AINode* parent;	// 父节点
		AINode* next;	// 路径指向
		int        transporter;	// 传送点
		float      memoryStrength;	//记忆强度,是否容易被擦除
		bool       inOpen;	// 记忆衰减曲线
	};

	AIPathFinder();
	~AIPathFinder();

	void Free();
	int  InitMap (int w,int h,const char* mapData);
	int  InitMap (Maze& maze);
	int  FindPath(int enterX, int enterY,int exitX,int exitY);
	int  FindPath(int enterX, int enterY,NodeType exitType);


	// 插入
	bool InsertToStack( int x, int y, AINode* currNode, AINode* endNode);

	// 邻居点插入open表
	bool InsertNeighbors( AINode* currNode, AINode* endNode );

	inline AINode& mazeBlock(int x,int y)
	{
		if (x<0 || x>=m_mazeWidth || y<0 || y>=m_mazeHeight)
		{
			assert(0);
			static AINode invalid;
			return invalid;
		}
		return m_mazeData[m_mazeWidth*(y) + (x)];
	}

	int         m_mazeWidth;									
	int         m_mazeHeight;
	AINode*     m_mazeData;	   //结点数组
	AINode**    m_nodeStack;   //节点栈
	int	        m_stackTop;    //栈顶

	int         m_pathSize;    //搜索到的路径节点数

	//智能参数
	int         m_senseOfDir;  //方向感
	bool        m_bX;

};

#endif

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值