一篇文章教会你用Unity制作网格地图生成组件
作者:心之凌儿 发布时间:2023-09-05 04:44:36
前言
如果你玩过三国志这种类型的战旗游戏或者模拟城市、部落冲突、海岛奇兵这种模拟经营类的游戏,那么你对网格地图一定不会陌生。在这些游戏中,所有地图场景中的物体都是基于整齐的网格来记录位置等信息。如下图:
如果你还是感知不到什么是网格地图。俄罗斯方块或者贪吃蛇你一定不会陌生,物体的存在是依托于规整的网格地图而存在的。
还是一如既往,本篇文章为零基础小白文,如果你是小萌新,并且对网格地图感兴趣的话,可以学习本片文章,然后尝试创建自己的游戏吧!
本文章的最终显示效果为:
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 < 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
脚本挂载到场景中的物体上,并根据注释进行相关的赋值。如图:
完成脚本挂载后点击运行,进入游戏后,点击空格键就会创建一张地图,在地图中会有随机的障碍物,以红色来标识障碍物,不可被点击,而白色区域点击后颜色变为蓝色,具体效果如图所示:
来源:https://blog.csdn.net/xinzhilinger/article/details/119302097


猜你喜欢
- DialogFragment的基本用法1. 创建DialogFragmentpublic class DialogA extends Dia
- @ConfigurationProperties注入创建一个新的模板此过程就不在这介绍了,在我SpringBoot专栏里有详细过程。⭐⭐⭐注
- hive的表数据是可以同步到impala中去的。一般impala是提供实时查询操作的,像比较耗时的入库操作我们可以使用hive,然后再将数据
- 启动协程的基本方式1.GlobalScope.launch代码示例:fun testGlobalScope() {  
- xml里面配置标签:<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc./
- 本文实例为大家分享了java计算工作时间的具体代码,不包括节假日、双休日,供大家参考,具体内容如下package common.util;
- 一. SpringBoot集成liquibase项目集成liquibase作用对数据库表字段进行版本控制项目初始化部署时初始化数据库表和数据
- arthas能够更改日志的级别,下面是logger的用法查看logger信息,更新logger level查看所有logger信息logge
- 本文实例为大家分享了UnityShader百叶窗展示的具体代码,供大家参考,具体内容如下shader实现以上百叶窗效果,主要通过shader
- HTTPclient保持长连接首先解释一下什么是长连接当我们向一台服务器发起请求时,我们需要和对方建立一条通道,去传输数据,所谓的短连接,就
- @Resource和@Autowired是Spring Framework中两种常用的注入方式,它们的作用是在Spring容器中自动装配Be
- 在项目开发过程中,不可避免的会升级开发工具。这次我在旧项目版本升级到新版Unity2021.2.x时,出现Visual Studio无法定位
- 如果一个项目内有很多个界面,那么在layout下会有太多的activity***.xml文件,这个时候就需要使用文件夹对这些分别存放了。当然
- 本文主要记录JAVA中对象的初始化过程,包括实例变量的初始化和类变量的初始化以及final关键字对初始化的影响。另外,还讨论了由于继承原因,
- AnyChat(全名叫Anychat SDK),也叫音视频互动开发平台;是一套跨平台的即时通讯解决方案,基于先进的H.264视频编码标准、A
- 本文考虑把账单界面整理下,实现如下图中的功
- 前言 在android开发中,很多的app都有使用侧滑菜单,有的是自定义控件来实现侧滑菜单,但是android给我们提供了DrawerLa
- 记录下一个很实用的小控件EditTextWithDel,就是在Android系统的输入框右边加入一个小图标,点击小图标可以清除输入框里面的内
- 内存对齐的基本原则:结构(struct/class)的内置类型数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的起始位置
- 一、Statementimport java.sql.*;public class TestJDBC { public stati