Unity绘制六边形体

2024-03-01 01:04
文章标签 绘制 unity 六边形

本文主要是介绍Unity绘制六边形体,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

现在steam上面有很多下棋类/经营类的游戏都是用六边形的地形,比较美观而且实用,去年在版本末期我也自己尝试做了一个绘制六边体的demo,一年没接触unity竟然都要忘光了,赶紧在这边记录一下。
想cv代码可以直接拉到代码章节

功能

能够动态生成一系列可以“挖空中心”的六边形。指定innerWidth为0也可以生成实心的六边体。
在这里插入图片描述
在这里插入图片描述
能够生成平铺/直铺的六边形群,调整之间距离
在这里插入图片描述在这里插入图片描述在这里插入图片描述

绘制思路

将绘制一个六边形看成六个下面这种等腰体,绕中心旋转60度之后合并成一个。
在这里插入图片描述
在这里插入图片描述
一个这种等腰体又可以看成绘制四个面:上面的等腰梯形,内测的长方形,下面的等腰梯形,外侧的长方形,两边无需绘制,因为合并之后不会显示出来。
所以只需要通过三角函数计算出我们所需的所有点->拼出一个面->合成一个等腰体->合成一个六边体。

组件

我们需要一个MeshFilter来设置mesh,一个MeshRenderer来设置mesh的材质。同时需要对mesh所需的内置成员变量有些了解。
在这里插入图片描述

        m_meshFilter = GetComponent<MeshFilter>();m_meshRenderer = GetComponent<MeshRenderer>();m_mesh = new Mesh();m_mesh.name = "HexMesh";m_meshFilter.mesh = m_mesh;m_meshRenderer.material = m_material;//最终数据传入m_mesh.vertices = verticles.ToArray();m_mesh.triangles = tris.ToArray();m_mesh.uv = uvs.ToArray();m_mesh.RecalculateNormals();

具体计算

绘制某个点

根据前面需要绘制的等腰梯形,设A是梯形长边的点,B是梯形短边的点,易得平面内某个点的计算方式
在这里插入图片描述
定义一个CreatePoint接口,根据width和y轴高度height来生成某个点的三维向量,(注意unity下生成图中y轴实际上是三维空间的z轴)

  private Vector3 CreatePoint(float distance, float height, float angle){float rad = angle * Mathf.Deg2Rad; //Mathf接收的参数需要是弧度制return new Vector3(distance * Mathf.Cos(rad), height, distance * Mathf.Sin(rad));}

生成面所需的数据

上文提到的等腰体四个不同面实际上都是四个顶点组成的,并且都是两个点组成的平行的线段,所以我们可以提供一个接口,只需指定高度和半径,就可以画出这四种不同的面,同时存在上下和内外两侧面的朝向是相反的,所以提供reverse接口来进行反向。

    /// <summary>/// 上下底面的单独一个等腰梯形/// </summary>/// <param name="innerRad">内径</param>/// <param name="outerRad">外径</param>/// <param name="heightA">外高</param>/// <param name="heightB">内高</param>/// <param name="point">顺序</param>/// <param name="reverse">连接方向</param>/// <returns></returns>private Face CreateFace(float innerRad, float outerRad, float heightA, float heightB, int point, bool reverse = false){float angle1 = point * 60;float angle2 = angle1 + 60;if (!isFlat){ //竖着排布,初始角度是-30angle1 -= 30;angle2 -= 30;}List<Vector3> verticals = new List<Vector3>();//.......C.//..B.......//..........//...A......Dverticals.Add(CreatePoint(innerRad, heightA, angle1));verticals.Add(CreatePoint(innerRad, heightA, angle2));verticals.Add(CreatePoint(outerRad, heightB, angle2));verticals.Add(CreatePoint(outerRad, heightB, angle1));List<int> tris = new List<int> { 0, 1, 2, 2, 3, 0};List<Vector2> uv = new List<Vector2> { new Vector2(0, 0),new Vector2(1,0),new Vector2(1,1),new Vector2(0,1) };//vertical顺序颠倒,就会按照顺时针绘制。if(reverse){verticals.Reverse();}return new Face(verticals, tris, uv);}

这里有一些关于mesh的基础知识,首先是三个顶点能够组成一个面,从上往下看如果点之间是逆时针顺序的话,就是面向我们的。这里我们添加了四个点。tirs指定其顺序,每三个一组将会连成一个面,uvs代表是渲染的时候的uv坐标,这里如果六边体有规范的话,就需要根据需求设置对应的uv值,这里就不关注这个了。

      List<int> tris = new List<int> { 0, 1, 2, 2, 3, 0};List<Vector2> uv = new List<Vector2> { new Vector2(0, 0),new Vector2(1,0),new Vector2(1,1),new Vector2(0,1) };
public struct Face
{//顶点位置数组public List<Vector3> verticles { get; private set; }//三角形顶点索引数组,按给定的顺序连接顶点,为顺时针三个一组的顺序public List<int> triangles { get; private set; }public List<Vector2> uvs { get; private set; }public Face(List<Vector3> verticles, List<int> triangles, List<Vector2> uvs){this.verticles = verticles;this.triangles = triangles;this.uvs = uvs;}
}

这样能够生产出一个面,接下来我们批量生产所需的面,只需要不断让角度偏移60度(忘记了可以去看上面计算A点坐标),重复刚才的步骤,将所有的面的数据都生成

 private void DrawFaces(){m_faces = new List<Face>();//上表面for(int point = 0; point < 6; point ++){m_faces.Add(CreateFace(innerWidth, outerWidth, height / 2, height / 2, point));}//下表面for (int point = 0; point < 6; point++){m_faces.Add(CreateFace(innerWidth, outerWidth,- height / 2, -height / 2, point,true));}//侧面for (int point = 0; point < 6; point++){m_faces.Add(CreateFace(outerWidth, outerWidth, height / 2, -height / 2, point));}//里侧面for (int point = 0; point < 6; point++){m_faces.Add(CreateFace(innerWidth, innerWidth, height / 2, -height / 2, point,true));}}

组装

刚才我们将数据填入Face,但是Face是不能直接使用的,我们要将刚才生成的顶点信息,uv信息,三角形信息等一次灌入Mesh中,
Mesh提供了成员变量来接收这些数据。
顶点和uv直接添加就可以,注意三角形数据需要根据顶点数据来加下标。

    private void CombineFaces(){List<Vector3> verticles = new List<Vector3>();List<int> tris = new List<int>();List<Vector2> uvs = new List<Vector2>();for(int i = 0; i < m_faces.Count; i++){verticles.AddRange(m_faces[i].verticles); //AddRange方法可以把list中所有数据从头到尾添加到新的listuvs.AddRange(m_faces[i].uvs);//注意:这里需要依次指定指定所有顶点在最终mesh的三角形顺序,由于每个face里面包括四个顶点,每次+4int offset = (4 * i);foreach(int triangle in m_faces[i].triangles){tris.Add(triangle + offset);}}m_mesh.vertices = verticles.ToArray();m_mesh.triangles = tris.ToArray();m_mesh.uv = uvs.ToArray();m_mesh.RecalculateNormals();}

排布

要让游戏能玩,肯定需要一系列整齐布局的六边形,所以我们需要一个动态创建六边形的管理器。

纵向排布

在这里插入图片描述
前面我们生成面的时候发现有个isFlat变量,这个变量就是控制了第一个面的生成角度,所以横向的时候能保证六边形是横着的。

    private Face CreateFace(float innerRad, float outerRad, float heightA, float heightB, int point, bool reverse = false){float angle1 = point * 60;float angle2 = angle1 + 60;if (!isFlat){ //竖着排布,初始角度是-30angle1 -= 30;angle2 -= 30;}......

问题是如何计算出每个六边形的中心点在哪。这里用三角函数也非常容易看出来
下面是六边体“直立“”情况下,设两个六边形之间间隔为d,六边形中心到外顶点的距离为L
可以发现Y轴方向每个六边形之间距离为(L * cos(30°) * 2 + d)* sin60°
X轴方向每个六边形之间距离为(L*(cos(30°)*2 + d)
同时注意距离偶数行的X轴要添加一个(L * cos(30°) * 2 + d)*sin30°的偏移

具体计算就初中级别的数学,就不一步步画图了
在这里插入图片描述

横向排布

同理横向布局也很好计算
可以发现Y轴方向每个六边形之间距离为(L * cos(30°) * 2 + d)
X轴方向每个六边形之间距离为(L*(cos(30°)*2 + d) *sin60°
同时注意距离偶数行的Y轴要添加一个(L * cos(30°) * 2 + d)*sin30°的偏移

在这里插入图片描述
万事具备,我们只需要计算每一行每列的点即可生成蜂窝了。

    public void SetInterval(){centerDistance = outterWidth * 2 * Mathf.Sin(60 * Mathf.Deg2Rad) + interval;}private void UpdateGrid(GameObject[][] girds){if (girds.Length <= 0) return;bool shouldOffset = false;for (int j = 0; j < heightCount; j++){if (!isFlat){shouldOffset = j % 2 != 0;}for (int i = 0; i < widthCount; i++){if (isFlat){shouldOffset = i % 2 != 0;}HexagonRenderer render = girds[i][j].GetComponent<HexagonRenderer>();//计算六边形位置Vector3 pos = Getpos(i, j, shouldOffset);Debug.Log(pos);render.SetAtrributes(innerWidth, outterWidth, height, pos, matrial, isFlat);render.DrawMesh();}}}private Vector3 Getpos(int i, int j, bool shouldOffset){float angle60 = 60 * Mathf.Deg2Rad;float angle30 = 30 * Mathf.Deg2Rad;if (isFlat){if (shouldOffset){return new Vector3(i * centerDistance * Mathf.Sin(angle60) , transform.position.y, j * centerDistance +centerDistance * Mathf.Sin(angle30));}else{return new Vector3(i * centerDistance * Mathf.Sin(angle60), transform.position.y, j * centerDistance);}}else{if (shouldOffset){return new Vector3(i * centerDistance + centerDistance * Mathf.Sin(angle30), transform.position.y, j * centerDistance * Mathf.Sin(angle60));}else{return new Vector3(i * centerDistance, transform.position.y, j * centerDistance * Mathf.Sin(angle60));}}}

完整代码

在场景中创建一个空物体,将GenerateMap.cs挂载在其身上即可,将会自动生成一系列身上挂载HexagonRenderer.cs的物体

GenerateMap.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class GenerateMap : MonoBehaviour
{[Header("Grid Settings")]public int widthCount;public int heightCount;[Header("Layout Settings")]public float innerWidth;public float outterWidth;public float height;public bool isFlat;public Material matrial;/// <summary>/// 六边形之间的间隔/// </summary>public float interval;private float centerDistance;/// <summary>/// 存储所有的六边形/// </summary>private GameObject[][] girds;private bool hasGenerate = false;public void Start(){girds = new GameObject[widthCount][];for (int i = 0; i < girds.Length; i++){girds[i] = new GameObject[heightCount];}SetInterval();GenerateGrid();LayoutGrid();}public void SetInterval(){centerDistance = outterWidth * 2 * Mathf.Sin(60 * Mathf.Deg2Rad) + interval;}/// <summary>/// 设置六边形布局,从左下角生成/// </summary>private void LayoutGrid(){UpdateGrid(girds);}private void GenerateGrid(){if (hasGenerate == true) return;for (int j = 0; j < heightCount; j++){for (int i = 0; i < widthCount; i++){GameObject single = new GameObject($"HEX:({i},{j})", typeof(HexagonRenderer)); //$代表string.formatgirds[i][j] = single;single.transform.SetParent(transform, true);}}hasGenerate = true;}private void UpdateGrid(GameObject[][] girds){if (girds.Length <= 0) return;bool shouldOffset = false;for (int j = 0; j < heightCount; j++){if (!isFlat){shouldOffset = j % 2 != 0;}for (int i = 0; i < widthCount; i++){if (isFlat){shouldOffset = i % 2 != 0;}HexagonRenderer render = girds[i][j].GetComponent<HexagonRenderer>();//计算六边形位置Vector3 pos = Getpos(i, j, shouldOffset);Debug.Log(pos);render.SetAtrributes(innerWidth, outterWidth, height, pos, matrial, isFlat);render.DrawMesh();}}}private Vector3 Getpos(int i, int j, bool shouldOffset){float angle60 = 60 * Mathf.Deg2Rad;float angle30 = 30 * Mathf.Deg2Rad;if (isFlat){if (shouldOffset){return new Vector3(i * centerDistance * Mathf.Sin(angle60) , transform.position.y, j * centerDistance +centerDistance * Mathf.Sin(angle30));}else{return new Vector3(i * centerDistance * Mathf.Sin(angle60), transform.position.y, j * centerDistance);}}else{if (shouldOffset){return new Vector3(i * centerDistance + centerDistance * Mathf.Sin(angle30), transform.position.y, j * centerDistance * Mathf.Sin(angle60));}else{return new Vector3(i * centerDistance, transform.position.y, j * centerDistance * Mathf.Sin(angle60));}}}
}

HexagonRenderer.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public struct Face
{//顶点位置数组public List<Vector3> verticles { get; private set; }//三角形顶点索引数组,按给定的顺序连接顶点,为顺时针三个一组的顺序public List<int> triangles { get; private set; }public List<Vector2> uvs { get; private set; }public Face(List<Vector3> verticles, List<int> triangles, List<Vector2> uvs){this.verticles = verticles;this.triangles = triangles;this.uvs = uvs;}
}
[RequireComponent(typeof(MeshFilter))]
[RequireComponent(typeof(MeshRenderer))]public class HexagonRenderer : MonoBehaviour
{private Mesh m_mesh;private MeshFilter m_meshFilter;private MeshRenderer m_meshRenderer;private List<Face> m_faces;private bool isFlat = true;public Material m_material;public float innerWidth;public float outerWidth;public float height;private void Awake(){m_meshFilter = GetComponent<MeshFilter>();m_meshRenderer = GetComponent<MeshRenderer>();m_mesh = new Mesh();m_mesh.name = "HexMesh";m_meshFilter.mesh = m_mesh;m_meshRenderer.material = m_material;}public void SetAtrributes(float innerWidth, float outerWidth, float height, Vector3 position, Material material, bool isFlat){this.innerWidth = innerWidth;this.outerWidth = outerWidth;this.isFlat = isFlat;this.height = height;transform.position = position;m_material = material;m_meshRenderer.material = m_material;DrawMesh();}private void OnEnable(){DrawMesh();}//渲染整个六边形体public void DrawMesh(){DrawFaces();CombineFaces();}private void OnValidate(){}private void DrawFaces(){m_faces = new List<Face>();//上表面for (int point = 0; point < 6; point++){m_faces.Add(CreateFace(innerWidth, outerWidth, height / 2, height / 2, point));}//下表面for (int point = 0; point < 6; point++){m_faces.Add(CreateFace(innerWidth, outerWidth, -height / 2, -height / 2, point, true));}//侧面for (int point = 0; point < 6; point++){m_faces.Add(CreateFace(outerWidth, outerWidth, height / 2, -height / 2, point));}//里侧面for (int point = 0; point < 6; point++){m_faces.Add(CreateFace(innerWidth, innerWidth, height / 2, -height / 2, point, true));}}private void CombineFaces(){List<Vector3> verticles = new List<Vector3>();List<int> tris = new List<int>();List<Vector2> uvs = new List<Vector2>();for (int i = 0; i < m_faces.Count; i++){verticles.AddRange(m_faces[i].verticles);AddRange方法可以把list中所有数据从头到尾添加到新的listuvs.AddRange(m_faces[i].uvs);//注意:这里需要依次指定指定所有顶点在最终mesh的三角形顺序,由于每个face里面包括四个顶点,每次+4int offset = (4 * i);foreach (int triangle in m_faces[i].triangles){tris.Add(triangle + offset);}}m_mesh.vertices = verticles.ToArray();m_mesh.triangles = tris.ToArray();m_mesh.uv = uvs.ToArray();m_mesh.RecalculateNormals();}/// <summary>/// 上下底面的单独一个等腰梯形/// </summary>/// <param name="innerRad">内径</param>/// <param name="outerRad">外径</param>/// <param name="heightA">外高</param>/// <param name="heightB">内高</param>/// <param name="point">顺序</param>/// <param name="reverse">连接方向</param>/// <returns></returns>private Face CreateFace(float innerRad, float outerRad, float heightA, float heightB, int point, bool reverse = false){float angle1 = point * 60;float angle2 = angle1 + 60;if (!isFlat){angle1 -= 30;angle2 -= 30;}List<Vector3> verticals = new List<Vector3>();//.......C.//..B.......//..........//...A......Dverticals.Add(CreatePoint(innerRad, heightA, angle1));verticals.Add(CreatePoint(innerRad, heightA, angle2));verticals.Add(CreatePoint(outerRad, heightB, angle2));verticals.Add(CreatePoint(outerRad, heightB, angle1));List<int> tris = new List<int> { 0, 1, 2, 2, 3, 0 };List<Vector2> uv = new List<Vector2> { new Vector2(0, 0), new Vector2(1, 0), new Vector2(1, 1), new Vector2(0, 1) };if (reverse){verticals.Reverse();}return new Face(verticals, tris, uv);}/// <summary>/// 创造一个顶点/// </summary>/// <param name="distance">距离坐标原点距离</param>/// <param name="height">y轴高度</param>/// <param name="angle">和坐标轴所成夹角</param>/// <returns></returns>private Vector3 CreatePoint(float distance, float height, float angle){float rad = angle * Mathf.Deg2Rad;return new Vector3(distance * Mathf.Cos(rad), height, distance * Mathf.Sin(rad));}
}

这篇关于Unity绘制六边形体的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/760667

相关文章

Python绘制TSP、VRP问题求解结果图全过程

《Python绘制TSP、VRP问题求解结果图全过程》本文介绍用Python绘制TSP和VRP问题的静态与动态结果图,静态图展示路径,动态图通过matplotlib.animation模块实现动画效果... 目录一、静态图二、动态图总结【代码】python绘制TSP、VRP问题求解结果图(包含静态图与动态图

Unity新手入门学习殿堂级知识详细讲解(图文)

《Unity新手入门学习殿堂级知识详细讲解(图文)》Unity是一款跨平台游戏引擎,支持2D/3D及VR/AR开发,核心功能模块包括图形、音频、物理等,通过可视化编辑器与脚本扩展实现开发,项目结构含A... 目录入门概述什么是 UnityUnity引擎基础认知编辑器核心操作Unity 编辑器项目模式分类工程

C#和Unity中的中介者模式使用方式

《C#和Unity中的中介者模式使用方式》中介者模式通过中介者封装对象交互,降低耦合度,集中控制逻辑,适用于复杂系统组件交互场景,C#中可用事件、委托或MediatR实现,提升可维护性与灵活性... 目录C#中的中介者模式详解一、中介者模式的基本概念1. 定义2. 组成要素3. 模式结构二、中介者模式的特点

使用Python绘制3D堆叠条形图全解析

《使用Python绘制3D堆叠条形图全解析》在数据可视化的工具箱里,3D图表总能带来眼前一亮的效果,本文就来和大家聊聊如何使用Python实现绘制3D堆叠条形图,感兴趣的小伙伴可以了解下... 目录为什么选择 3D 堆叠条形图代码实现:从数据到 3D 世界的搭建核心代码逐行解析细节优化应用场景:3D 堆叠图

QT6中绘制UI的两种方法详解与示例代码

《QT6中绘制UI的两种方法详解与示例代码》Qt6提供了两种主要的UI绘制技术:​​QML(QtMeta-ObjectLanguage)​​和​​C++Widgets​​,这两种技术各有优势,适用于不... 目录一、QML 技术详解1.1 QML 简介1.2 QML 的核心概念1.3 QML 示例:简单按钮

Python使用Matplotlib绘制3D曲面图详解

《Python使用Matplotlib绘制3D曲面图详解》:本文主要介绍Python使用Matplotlib绘制3D曲面图,在Python中,使用Matplotlib库绘制3D曲面图可以通过mpl... 目录准备工作绘制简单的 3D 曲面图绘制 3D 曲面图添加线框和透明度控制图形视角Matplotlib

使用Python绘制蛇年春节祝福艺术图

《使用Python绘制蛇年春节祝福艺术图》:本文主要介绍如何使用Python的Matplotlib库绘制一幅富有创意的“蛇年有福”艺术图,这幅图结合了数字,蛇形,花朵等装饰,需要的可以参考下... 目录1. 绘图的基本概念2. 准备工作3. 实现代码解析3.1 设置绘图画布3.2 绘制数字“2025”3.3

使用Python绘制可爱的招财猫

《使用Python绘制可爱的招财猫》招财猫,也被称为“幸运猫”,是一种象征财富和好运的吉祥物,经常出现在亚洲文化的商店、餐厅和家庭中,今天,我将带你用Python和matplotlib库从零开始绘制一... 目录1. 为什么选择用 python 绘制?2. 绘图的基本概念3. 实现代码解析3.1 设置绘图画

Python绘制土地利用和土地覆盖类型图示例详解

《Python绘制土地利用和土地覆盖类型图示例详解》本文介绍了如何使用Python绘制土地利用和土地覆盖类型图,并提供了详细的代码示例,通过安装所需的库,准备地理数据,使用geopandas和matp... 目录一、所需库的安装二、数据准备三、绘制土地利用和土地覆盖类型图四、代码解释五、其他可视化形式1.

如何用Python绘制简易动态圣诞树

《如何用Python绘制简易动态圣诞树》这篇文章主要给大家介绍了关于如何用Python绘制简易动态圣诞树,文中讲解了如何通过编写代码来实现特定的效果,包括代码的编写技巧和效果的展示,需要的朋友可以参考... 目录代码:效果:总结 代码:import randomimport timefrom math