【unity实战】随机生成2d横版平台地图

最终效果

在这里插入图片描述

前言

现在网上随机生成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 相比,有以下几点意义和区别:

  1. 动态调整生成概率

    • 通过 j < (this.width + 3) 条件,可以根据列 j 的位置动态调整生成地面的概率。在地图的前几列生成地面时,概率由 groundStartPro 控制;在地图的后几列,稍微增加了生成地面的概率,由 groundStartPro + 0.05f 控制。这样可以模拟地图生成时,前后区域地形的变化和自然性。
  2. 增加地图的多样性

    • 地图的前后部分有不同的生成概率,可以使得生成的地图更加多样化和有趣。前几列可以设置稍低的生成概率,使得这部分地形可能更开阔或更复杂;而后几列则可以设置稍高的生成概率,增加地形的密度或者连续性。
  3. 控制地图生成的均匀性

    • 直接使用 Random.value <= groundStartPro 可能会导致地图生成的均匀性较强,随机性不足。而使用条件运算符根据列 j 的位置调整概率,可以在保持随机性的同时,控制生成地形的均匀性和自然性。
  4. 调整生成算法的灵活性

    • 如果后续需要调整生成地图的策略或算法,使用条件运算符的方式更加灵活。可以根据实际需求简单地调整生成概率的变化规则,而不需要大幅度修改代码逻辑。

综上所述,使用 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是生活!如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_36303853/article/details/140733616