迷宫生成算法是游戏中各种随机生成都可能用到的最基本的算法。下图展示了一个随机场景生成器的迷宫生成部分。
依次经过了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