最终效果
文章目录
前言
现在网上随机生成2d地图,基本都是俯视角RPG类型的,之前作业做过,感兴趣可以看看
【实现100个unity游戏之20】制作一个2d开放世界游戏,TileMap+柏林噪声生成随机地图(附源码)
【用unity实现100个游戏之16】Unity程序化生成随机2D地牢游戏1(附项目源码)
【unity实战】随机地下城生成1——随机生成地下城初稿(含源码)
这次我们来实现一个随机生成2d横版平台地图
TileMap基础
参考:【推荐100个unity插件之14】Unity2D TileMap的探究(最简单,最全面的TileMap使用介绍)
创建地面创建规则瓦片Rule Tile
具体按自己的素材配置即可
随机生成地图
首先创建TileMapManager空物体,在空物体下面加入脚本GroundTIleMap
生成瓦片的时候先随机生成瓦片起始点,再通过一个协程从瓦片起始点开始延长不等长度(因为是横版游戏,在横向上的延伸要求高)
public class GroundTileMap : MonoBehaviour
{
[Header("地图配置")]
public Tilemap ground_tilemap;//拖动获取地面瓦片地图
public TileBase groundTile;//拖动获取地面规则瓦片Rule Tile
public Vector2Int lowerLeftCoo = new Vector2Int(-18, -7);//地图起始左下角位置
public int width= 20;//地图宽度
public int length= 100;//地图长度
public float groundStartPro = 0.10f;//生成地面起始点的概率
public Vector2Int groundLenRan = new Vector2Int(3, 10);//起始地面点生成的长度范围
void Start()
{
CreateMap();
}
public void CreateMap(){
GroundStartPoi();
}
void GroundStartPoi()//生成地面起始点 用协程可以缓慢一步步生成地图,性能消耗少
{
ground_tilemap.ClearAllTiles();// 清空地面瓦片地图
for (int i = lowerLeftCoo.x; i < (this.length + lowerLeftCoo.x); i++)
{
for (int j = lowerLeftCoo.y; j < (this.width + lowerLeftCoo.y); j++)
{
bool IsGround = j < (this.width + 3) ? (Random.value <= groundStartPro) : (Random.value <= groundStartPro + 0.05);//三元表达式,地面三行更容易生成地面起始点
// bool IsGround = Random.value <= groundStartPro;
if (IsGround) GroundExtension(i,j);
}
}
}
void GroundExtension(int i,int j)//从地面起始点开始延长
{
int groundLen = Random.Range(groundLenRan.x, groundLenRan.y);
for (int m = i; m <= i + groundLen; m++)
{
//限制超出范围不延长
int x = Mathf.Clamp(m, lowerLeftCoo.x, this.length + lowerLeftCoo.x-1);
ground_tilemap.SetTile(new Vector3Int(x,j,0),groundTile);
}
}
}
配置
效果
解释
使用 j < (this.width + 3) ? (Random.value <= groundStartPro) : (Random.value <= groundStartPro + 0.05f);
的代码段与直接使用 Random.value <= groundStartPro
相比,有以下几点意义和区别:
-
动态调整生成概率:
- 通过
j < (this.width + 3)
条件,可以根据列j
的位置动态调整生成地面的概率。在地图的前几列生成地面时,概率由groundStartPro
控制;在地图的后几列,稍微增加了生成地面的概率,由groundStartPro + 0.05f
控制。这样可以模拟地图生成时,前后区域地形的变化和自然性。
- 通过
-
增加地图的多样性:
- 地图的前后部分有不同的生成概率,可以使得生成的地图更加多样化和有趣。前几列可以设置稍低的生成概率,使得这部分地形可能更开阔或更复杂;而后几列则可以设置稍高的生成概率,增加地形的密度或者连续性。
-
控制地图生成的均匀性:
- 直接使用
Random.value <= groundStartPro
可能会导致地图生成的均匀性较强,随机性不足。而使用条件运算符根据列j
的位置调整概率,可以在保持随机性的同时,控制生成地形的均匀性和自然性。
- 直接使用
-
调整生成算法的灵活性:
- 如果后续需要调整生成地图的策略或算法,使用条件运算符的方式更加灵活。可以根据实际需求简单地调整生成概率的变化规则,而不需要大幅度修改代码逻辑。
综上所述,使用 j < (this.width + 3) ? (Random.value <= groundStartPro) : (Random.value <= groundStartPro + 0.05f);
的方式相比直接使用 Random.value <= groundStartPro
,在地图生成的过程中提供了更多的控制和调整空间,能够更好地满足不同场景下地图生成的需求。
加快生成地图调试速度
一直启动才生成地图,太慢了,为了加快我们的调试节奏,可以实现未启动unity生成地图效果,我们需要新建一个Editor文件夹
书写MapCreateEditor脚本
using UnityEditor;
using UnityEngine;
[CustomEditor(typeof(GroundTileMap))]// 自定义编辑器,目标为我们前面创建的MapCreate
public class MapCreateEditor : Editor
{
public override void OnInspectorGUI()// 重写OnInspectorGUI方法
{
base.DrawDefaultInspector();// 绘制默认的检查器
if (GUILayout.Button("创建地图"))// 如果GUILayout的按钮被按下,按钮名为"创建地图"
{
((GroundTileMap)target).CreateMap();// 目标MapGenerator生成地图
}
}
}
查看
在不运行时,我们就可以点击创建地图按钮,快速生成随机地图
连通各个平台
可以发现,现在随机地图虽然生成了,但是很多地面都不连通,想要角色能畅通无阻,就需要清除一些瓦片,产生可行走通道
思路就是从底层有方块的地方,开始判断上面。如果没有方块的话,就清除俩层/三层的通道
//清除,产生通道,思路就是从底层有方块的地方,开始判断上面。如果没有方块的话,就清除俩层/三层的通道
void ClearChannel(){
for (int i = lowerLeftCoo.x; i < (this.length + lowerLeftCoo.x); i++)
{
for (int j = lowerLeftCoo.y; j < (this.width + lowerLeftCoo.y-1); j++)//最高层上面必然没有方块,不需要判断
{
if (ground_tilemap.GetTile(new Vector3Int(i, j, 0)) != null)//如果此处不为空方块
{
if (ground_tilemap.GetTile(new Vector3Int(i, j + 1, 0)) == null)//如果此处上方为空方块
{
ground_tilemap.SetTilesBlock(new BoundsInt(i-2, j + 1, 0,3,3,1),new TileBase[] {
null,null, null, null, null, null,null, null, null });//将上方3x3格子置空
}
}
}
}
}
当ground_tilemap.SetTilesBlock(new BoundsInt(i-2, j + 1, 0,3,3,1),new TileBase[] { null,null, null, null, null, null,null, null, null });
这段代码执行时,指定的 ground_tilemap 中以 (i-2, j+1) 为起点,大小为 3x3 的矩形区域内的所有 Tile 将会被设置为空。这意味着在游戏或应用中,这个区域内原来存在的 Tile 将被移除或隐藏,使得这个区域在视觉上没有任何图块显示。
效果,可以看到地图都清除了一些区域供人物行走
美化地图
和规则瓦片的创造方式一样,先单独创建tileMap,再创建ruleTile随机瓦片,配置不同的环境配饰花,草,石头等
注意需要修改环境瓦片地图的平铺轴和精灵图片的锚点位置,确保随机生成的精灵图是接触与地面而不是悬空的。(由于随机生成的环境精灵图大小不一样,但锚点都在中间,所以需要修改锚点)
修改代码
[Header("环境配置")]
public float environmentRich = 0.5f;//环境丰富度
public Tilemap environ_tilemap;
public TileBase environTile;//拖动获取环境规则瓦片
//开始生成花草
void GenerateEnviron()
{
environ_tilemap.ClearAllTiles();// 清空瓦片地图
for (int i = lowerLeftCoo.x; i < (this.length + lowerLeftCoo.x); i++)
{
for (int j = lowerLeftCoo.y; j < (this.width + lowerLeftCoo.y ); j++)
{
if (ground_tilemap.GetTile(new Vector3Int(i, j, 0)) ==groundTile&& ground_tilemap.GetTile(new Vector3Int(i, j+1, 0)) == null)//如果此处为地面,上面为空方块
{
if (Random.value < environmentRich)//随机
{
environ_tilemap.SetTile(new Vector3Int(i, j + 1, 0), environTile); }
}
}
}
}
配置
效果
效果
放置预制体 玩家敌人和物品
[Header("预制体配置")]
private List<Vector2> canPrefabVectorList = new List<Vector2>();//能放置预制体的坐标
public GameObject playerPrefab;//玩家
public List<GameObject> enemyPrefabList;//敌人
public List<GameObject> itemsPrefabList;//物品
//开始生成花草
void GenerateEnviron()
{
environ_tilemap.ClearAllTiles();// 清空瓦片地图
for (int i = lowerLeftCoo.x; i < (this.length + lowerLeftCoo.x); i++)
{
for (int j = lowerLeftCoo.y; j < (this.width + lowerLeftCoo.y ); j++)
{
if (ground_tilemap.GetTile(new Vector3Int(i, j, 0)) ==groundTile&& ground_tilemap.GetTile(new Vector3Int(i, j+1, 0)) == null)//如果此处为地面,上面为空方块
{
//保存能放置预制体的坐标
canPrefabVectorList.Add(ground_tilemap.CellToWorld(new Vector3Int(i, j + 1, 0)) + new Vector3(0.5f, 0, 0));
if (Random.value < environmentRich)//随机
{
environ_tilemap.SetTile(new Vector3Int(i, j + 1, 0), environTile); }
}
}
}
StartCoroutine(GenerateRandomObjects());
}
IEnumerator GenerateRandomObjects()
{
GenerateRandomPlayer();//创建主角
GenerateRandomEnemies();//创建敌人
GenerateRandomItems();//创建预制体
yield return null;
}
void GenerateRandomPlayer()
{
if(playerPrefab == null) return;
int index = Random.Range(0, canPrefabVectorList.Count);
Instantiate(playerPrefab, canPrefabVectorList[index], Quaternion.identity);
canPrefabVectorList.RemoveAt(index);
}
void GenerateRandomEnemies()
{
if(enemyPrefabList.Count == 0) return;
for (var i = 0; i < enemyPrefabList.Count; i++)
{
int index = Random.Range(0, canPrefabVectorList.Count);
Instantiate(enemyPrefabList[i], canPrefabVectorList[index], Quaternion.identity);
canPrefabVectorList.RemoveAt(index);
}
}
void GenerateRandomItems()
{
if(itemsPrefabList.Count == 0) return;
for (var i = 0; i < itemsPrefabList.Count; i++)
{
int index = Random.Range(0, canPrefabVectorList.Count);
Instantiate(itemsPrefabList[i], canPrefabVectorList[index], Quaternion.identity);
canPrefabVectorList.RemoveAt(index);
}
}
配置
效果
其他
关于物品排序,碰撞体就大家自己去配置调整了,都比较简单
参考
https://blog.csdn.net/qq_54263076/article/details/125777090
完结
赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注
,以便我第一时间收到反馈,你的每一次支持
都是我不断创作的最大动力。当然如果你发现了文章中存在错误
或者有更好的解决方法
,也欢迎评论私信告诉我哦!
好了,我是向宇
,https://xiangyu.blog.csdn.net
一位在小公司默默奋斗的开发者,出于兴趣爱好,最近开始自学unity,闲暇之余,边学习边记录分享,站在巨人的肩膀上,通过学习前辈们的经验总是会给我很多帮助和启发!php是工作,unity是生活!如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~