一篇文章教会你用Unity制作网格地图生成组件

作者:心之凌儿 时间:2023-09-05 04:44:36 

前言

如果你玩过三国志这种类型的战旗游戏或者模拟城市、部落冲突、海岛奇兵这种模拟经营类的游戏,那么你对网格地图一定不会陌生。在这些游戏中,所有地图场景中的物体都是基于整齐的网格来记录位置等信息。如下图:

一篇文章教会你用Unity制作网格地图生成组件

如果你还是感知不到什么是网格地图。俄罗斯方块或者贪吃蛇你一定不会陌生,物体的存在是依托于规整的网格地图而存在的。

还是一如既往,本篇文章为零基础小白文,如果你是小萌新,并且对网格地图感兴趣的话,可以学习本片文章,然后尝试创建自己的游戏吧!

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

一篇文章教会你用Unity制作网格地图生成组件

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脚本挂在到物体上,并设置相关的初始参数,具体如图:

一篇文章教会你用Unity制作网格地图生成组件

2,编辑网格创建脚本

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

  • 创建网格的宽度:x轴Grid预制体的个数

  • 创建网格的高度:y轴Grid预制体的个数

  • 创建网格中Grid的位置:通过一个初始点,然后通过Grid的长宽计算

完成上面的信息的定义后,我们就可以编写脚本来实现网格创建的功能了,但是在此之前我们要思考一个问题,我们的创建的每一个Grid的会完全一摸一样吗。答案肯定是不会。比如说,在一些模拟经营的游戏中,一个物体可能会对周围的环境造成一些影响,为了标识其影响范围,就需要通过不同颜色的网格来表示,如图所示:

一篇文章教会你用Unity制作网格地图生成组件

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


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 < 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()
   {

gridMeshCreate.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),因为这样输出的结果只能为整数,即只能输出零

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

一篇文章教会你用Unity制作网格地图生成组件

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

一篇文章教会你用Unity制作网格地图生成组件

来源:https://blog.csdn.net/xinzhilinger/article/details/119302097

标签:unity,网格,地图
0
投稿

猜你喜欢

  • Android Vibrator调节震动代码实例

    2022-07-06 04:56:46
  • 浅析Bean Searcher 与 MyBatis Plus 区别介绍

    2022-08-28 03:25:41
  • Java 开启多线程常见的4种方法

    2023-11-23 02:30:10
  • Java动态代理之拦截器的应用

    2022-11-24 11:18:55
  • C++实现希尔排序(ShellSort)

    2022-03-03 22:29:13
  • Android 跨进程通Messenger(简单易懂)

    2022-03-24 00:48:25
  • C#中Hashtable和Dictionary的区别与用法示例

    2023-10-05 03:22:59
  • Java8处理集合的优雅姿势之Stream

    2023-03-14 09:31:48
  • 解决MySQL忘记密码问题的方法

    2022-04-05 07:55:43
  • 通过实例解析java过滤器和拦截器的区别

    2022-12-21 05:02:45
  • C#、vb.net及SQL判断指定年份是否为闰年的方法

    2023-05-18 09:49:55
  • RocketMQ producer同步发送单向发送源码解析

    2022-11-20 01:55:55
  • jvm细节探索之synchronized及实现问题分析

    2023-08-24 02:13:29
  • Android自定义View画圆功能

    2023-05-18 10:47:24
  • JPA配置方式+逆向工程映射到Entity实体类

    2023-07-28 12:09:48
  • SpringBoot可视化监控的具体应用

    2023-07-28 20:32:02
  • C# Winform消息通知之系统本地通知local toast notification

    2023-02-01 04:14:02
  • java实现一个简单的网络爬虫代码示例

    2021-08-05 13:59:12
  • javafx实现时钟效果

    2022-09-10 02:23:31
  • C#向线程中传递多个参数的解决方法(两种)

    2022-08-16 19:16:30
  • asp之家 软件编程 m.aspxhome.com