找回密码
 立即注册
查看: 265|回复: 2

[笔记] Unity 创建一个网格地图

[复制链接]
发表于 2022-6-17 11:11 | 显示全部楼层 |阅读模式
前言

如果你玩过三国志、率土之滨这类战旗游戏或者模拟城市、部落冲突、海岛奇兵这种模拟经营类的游戏,那么你对网格地图一定不会陌生。在这些游戏中,地图场景中的物体往往都是基于整齐的网格来进行位移定位的
如果你对这些游戏不是很了解,那么俄罗斯方块或者贪吃蛇你一定不会陌生。在这些游戏中,你可以看到由一块块方格子组成的游戏场景,这些都是网格地图的一些应用实例。
如果你是小萌新,并且对网格地图感兴趣的话,可以学习本片文章,然后自己动手,去尝试创建自己的游戏

本文章的最终显示效果为:



1,创建组建出网格的基本单元

我们知道网格是由一个个格子组成的,所以在整个项目中的第一步工作是先创建出一个基本的格子模板:
创建一个脚本命名为Grid,并定义一些我们需要修改的属性,由于本案例我想要创建一个有障碍物的地图,用来作为A*寻路的地图。所以需要下面的信息:

  • 模板宽度
  • 模板高度
  • 模板颜色
  • 模板是否为障碍(由颜色标识)
  • 模板点击事件(模板颜色转换)
编写模板脚本:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

public class Grid : MonoBehaviour
{

    public float gridWidght;
    public float girdHeight;
    public bool isHinder;
    public Color color;
    public Action OnClick;   
    //当网格地图比较大时,每帧更新模板颜色比较消耗性能,可以修改为通过事件触发
    void Update()
    {
        gameObject.GetComponent<MeshRenderer>().material.color=color;
    }
    //委托绑定模板点击事件
    private void OnMouseDown()
    {
        OnClick?.Invoke();
    }

}
编写好脚本后,创建模板预制体,为了方便理解,本案例就使用一个简单的方块来作为演示案例。为了保证可以区分每一个方格,大小缩放到0.9,这样网格地图中会有空隙来分割不同的网格块,方便玩家感知
创建好方格后,将Grid脚本挂在到物体上,并设置相关的初始参数,具体如图:



2,编辑网格创建脚本

接下来我们就需要封装一个网格地图创建的脚本,创建一个脚本命名为GridMeshCreate,然后编写该脚本,为了实现创建网格地图的功能,我们需要获取到一些基本信息:

  • 创建网格的宽度:x轴Grid预制体的个数
  • 创建网格的高度:y轴Grid预制体的个数
  • 创建网格中Grid的位置:通过一个初始点,然后通过Grid的长宽计算
完成上面的信息的定义后,我们就可以编写脚本来实现网格创建的功能了,但是在此之前我们要思考一个问题,我们的创建的每一个Grid的会完全一摸一样吗。简单的思考一下,就知道答案肯定是不会。简单举一个例子来理解这事,在一些模拟经营的游戏中,一个物体可能会对周围的环境造成一些影响,为了标识其影响范围,往往会通过不同颜色的格子预制体来标识,具体案例如图所示:


在上面的图片中可以看出,我们需要对于不同区块的网格进行不同的信息展示,这就需要我们在网格创建时传入对应的处理逻辑。具体的代码结构为:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;


public class GridMeshCreate : MonoBehaviour
{
    [Serializable]
    public class MeshRange
    {
        public int widght;
        public int height;
    }
    //网格的宽高范围
    public MeshRange meshRange;
    //生成网格起始点
    public Vector3 startPos;
    //网格生成的父物体
    public Transform parentTran;
    //模板预制体
    public GameObject gridPre;
   
    private Grid[,] m_grids;
    public Grid[,] MeshGridData
    {
        get
        {
            return m_grids;
        }
    }
    //注册模板事件
    public Action<Grid> gridEvent;

    /// <summary>
    /// 基于挂载组件的初始数据创建网格
    /// </summary>
    public void CreateMesh()
    {
        if (meshRange.widght == 0 || meshRange.height == 0)
        {
            return;
        }
        ClearMesh();
        m_grids = new Grid[meshRange.widght, meshRange.height];
        for (int i = 0; i < meshRange.widght; i++)
        {
            for (int j = 0; j < meshRange.height; j++)
            {
                CreateGrid(i, j);

            }
        }
    }

    /// <summary>
    /// 重载,基于传入宽高数据来创建网格
    /// </summary>
    /// <param name="height"></param>
    /// <param name="widght"></param>
    public void CreateMesh(int height,int widght)
    {
        if (widght == 0 || height == 0)
        {
            return;
        }
        ClearMesh();
        m_grids = new Grid[widght, height];
        for (int i = 0; i < widght; i++)
        {
            for (int j = 0; j < span class="n">height; j++)
            {
                CreateGrid(i, j);
            }
        }
    }

    /// <summary>
    /// 根据位置创建一个基本的Grid物体
    /// </summary>
    /// <param name="row">x轴坐标</param>
    /// <param name="column">y轴坐标</param>
    public void CreateGrid(int row,int column)
    {
        GameObject go = GameObject.Instantiate(gridPre, parentTran);
        Grid grid = go.GetComponent<Grid>();

        float posX = startPos.x + grid.gridWidght * row;
        float posZ = startPos.z + grid.girdHeight * column;
        go.transform.position = new Vector3(posX, startPos.y, posZ);
        m_grids[row, column] = grid;
        gridEvent?.Invoke(grid);
    }

    /// <summary>
    /// 删除网格地图,并清除缓存数据
    /// </summary>
    public void ClearMesh()
    {
        if (m_grids == null || m_grids.Length == 0)
        {
            return;
        }
        foreach (Grid grid in m_grids)
        {
            if (grid.gameObject != null)
            {
                Destroy(grid.gameObject);
            }
        }
        Array.Clear(m_grids, 0, m_grids.Length);
    }
}
关于上面的脚本,有下面的两个关键点:

  • 创建网格
  • 对外暴露处理Grid逻辑的方法
关于网格的创建,在脚本中,我们写了一个重载的方法public void CreateMesh(int height,int widght),传入了网格宽和高,来方便通过后期通过脚本灵活的修改网格的宽和高(注意这里的宽和高指的是x轴与y轴格子的个数)
而对于Grid逻辑对外暴露的实现,是利用在创建预制体时,为其添加一个委托事件。这样就可以在我们其他脚本创建时写入逻辑方法,而不需要对于这个封装好的网格地图创建类进行修改,而关于委托的一些知识,可以查看我之前的文章:
关于委托的文章:


  • C# 委托基础与入门

3,地图生成案例

在我们封装好网格创建的脚本后,就可以通过该脚本来做一个简单的网格地图来演示其用法
创建脚本命名为MainRun ,并进行编辑:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MainRun : MonoBehaviour
{
    //获取网格创建脚本
    public GridMeshCreate gridMeshCreate;
    //控制网格元素grid是障碍的概率
    [Range(0,1)]
    public float probability;

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            Run();
        }
    }
    private void Run()
    {
        
        gidMeshCreate.gridEvent = GridEvent;
        gridMeshCreate.CreateMesh();
    }

    /// <summary>
    /// 创建grid时执行的方法,通过委托传入
    /// </summary>
    /// <param name="grid"></param>
    private void GridEvent(Grid grid)
    {
        //概率随机决定该元素是否为障碍
        float f = Random.Range(0, 1.0f);
        Debug.Log(f.ToString());
        grid.color = f <= probability ? Color.red : Color.white;
        grid.isHinder = f <= probability;
        //模板元素点击事件
        grid.OnClick = () => {
            if (!grid.isHinder)
                grid.color = Color.blue;
        };

    }
}
可以看到,在Run方法中是对于我们网格创建框架的一个调用,而在GridEvent(Grid grid)中我们就可以写入我们的逻辑,并通过修改Grid脚本中的代码来辅助完成我们需要的效果,比如本案例中在Grid写入了一个点击事件,就可以在创建时通过委托定义该事件。
注意:


  • 在脚本里面用到了Random.Range(0, 1.0f)来生成一个概率,注意不要写成Random.Range(0, 1),因为这样输出的结果只能为整数,即只能输出零


在编写完成脚本后,返回Unity场景中,将GridMeshCreate脚本挂载到场景中的物体上,并根据注释进行相关的赋值。如图:



完成脚本挂载后点击运行,进入游戏后,点击空格键就会创建一张地图,在地图中会有随机的障碍物,以红色来标识障碍物,不可被点击,而白色区域点击后颜色变为蓝色,具体效果如图所示:


后续

在使用网格地图创建的代码时,也许你发现了一个不方便的地方,就是里面的一个变量类型是类名Grid,这样就会有局限性,我的预制体脚本只能挂载Grid,这显然是不方便的,因此后续我对其进行了一些修改,将Grid替换为GameObject修改后的脚本为:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
public class GridMeshCreate : MonoBehaviour
{
    [Serializable]
    public class MeshRange
    {
        public int horizontal;
        public int vertical;
    }
    [Header("网格地图范围")]
    public MeshRange meshRange;
    [Header("网格地图起始点")]
    private Vector3 startPos;
    [Header("创建地图网格父节点")]
    public Transform parentTran;
    [Header("网格地图模板预制体")]
    public GameObject gridPre;
    [Header("网格地图模板大小")]
    public Vector2 scale;


    private GameObject[,] m_grids;
    public GameObject[,] grids
    {
        get
        {
            return m_grids;
        }
    }
    //注册模板事件
    <span class="k">public Action<GameObject, int, int> gridEvent;

    /// <summary>
    /// 基于挂载组件的初始数据创建网格
    /// </summary>
    public void CreateMesh()
    {
        if (meshRange.horizontal == 0 || meshRange.vertical == 0)
        {
            return;
        }
        ClearMesh();
        m_grids = new GameObject[meshRange.horizontal, meshRange.vertical];
        for (int i = 0; i < meshRange.horizontal; i++)
        {
            for (int j = 0; j < meshRange.vertical; j++)
            {
                CreateGrid(i, j);

            }
        }
    }

    /// <summary>
    /// 重载,基于传入宽高数据来创建网格
    /// </summary>
    /// <param name="height"></param>
    /// <param name="widght"></param>
    public void CreateMesh(int height, int widght)
    {
        if (widght == 0 || height == 0)
        {
            return;
        }
        ClearMesh();
        m_grids = new GameObject[widght, height];
        for (int i = 0; i < widght; i++)
        {
            for (int j = 0; j < height; j++)
            {
                CreateGrid(i, j);

            }
        }
    }

    /// <summary>
    /// 根据位置创建一个基本的Grid物体
    /// </summary>
    /// <param name="row">x轴坐标</param>
    /// <param name="column">y轴坐标</param>
    public void CreateGrid(int row, int column)
    {
        GameObject go = GameObject.Instantiate(gridPre, parentTran);
        //T grid = go.GetComponent<T>();

        float posX = startPos.x + scale.x * row;
        float posZ = startPos.z + scale.y * column;
        go.transform.position = new Vector3(posX, startPos.y, posZ);
        m_grids[row, column] = go;
        gridEvent?.Invoke(go, row, column);
    }
    /// <summary>
    /// 删除网格地图,并清除缓存数据
    /// </summary>
    public void ClearMesh()
    {
        if (m_grids == null || m_grids.Length == 0)
        {
            return;
        }
        foreach (GameObject go in m_grids)
        {
            if (go != null)
            {
                Destroy(go);
            }
        }
        Array.Clear(m_grids, 0, m_grids.Length);
    }

}


总结

这里只是介绍了一个简单的案例,如果你觉得有用的话,可以尝试基于GridMeshCreate脚本创建自己的网格地图生成方法,来做出自己想要的效果!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
发表于 2022-6-17 11:18 | 显示全部楼层
大佬牛逼!!!!!
发表于 2022-6-17 11:28 | 显示全部楼层
低调低调
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-5-3 12:11 , Processed in 1.003188 second(s), 27 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表