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));}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/499170.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

面试官:谈一谈Cookie和Session的区别?

我先解释一下Cookie&#xff0c;它是客户端浏览器用来保存服务端数据的一种机制&#xff0c;当我们通过浏览器去进行网页访问的时候&#xff0c;服务器可以把一些状态数据以key-value的形式写入到Cookie里面&#xff0c;存储到客户端浏览器。下一次这个浏览器再访问服务器的时候…

JavaEE——简单认识JavaScript

文章目录 一、简单认识 JavaScript 的组成二、基本的输入输出和简单语法三、变量的使用四、JS 中的动态类型图示解释常见语言的类型形式 五、JS中的数组六、JS 中的函数七、JS 中的对象 一、简单认识 JavaScript 的组成 对于 JavaScript &#xff0c;其中的组成大致分为下面的…

【嵌入式——QT】日期与定时器

日期 QTime&#xff1a;时间数据类型&#xff0c;仅表示时间&#xff0c;如 16:16:16&#xff1b;QDate&#xff1a;日期数据类型&#xff0c;仅表示日期&#xff0c;如2024-1-22&#xff1b;QDateTime&#xff1a;日期时间数据类型&#xff0c;表示日期和时间&#xff0c;如2…

软件测试有哪些常用的测试方法?

软件测试是软件开发过程中重要组成部分&#xff0c;是用来确认一个程序的质量或者性能是否符合开发之前提出的一些要求。软件测试的目的有两方面&#xff0c;一方面是确认软件的质量&#xff0c;另一方面是提供信息&#xff0c;例如&#xff0c;给开发人员或者程序经理反馈意见…

MATLAB环境下脑电信号EEG的谱分析

脑电信号一直伴随着人类的生命&#xff0c;脑电波是脑神经细胞发生新陈代谢、离子交换时细胞群兴奋突触电位总和&#xff0c;脑电信号的节律性则和丘脑相关&#xff0c;含有丰富的大脑活动信息。通常我们所接触的脑电图都是头皮脑电图&#xff0c;在有些特殊场合还需要皮下部位…

(正规api接口代发布权限)短视频账号矩阵系统实现开发--技术全自动化saas营销链路生态

短视频账号矩阵系统实现开发--技术全自动化saas营销链路生态源头开发&#xff08;本篇禁止抄袭复刻&#xff09; 一、短视频矩阵系统开发者架构 云罗短视频矩阵系统saas化系统&#xff0c;开发层将在CAP原则基础上使用分布式架构,对此网站的整体架构采用了基于B/S三层架构模式…

Python算法题集_组合总和

Python算法题集_组合总和 题39&#xff1a;组合总和1. 示例说明2. 题目解析- 题意分解- 优化思路- 测量工具 3. 代码展开1) 标准求解【值传递回溯】2) 改进版一【引用传递堆栈回溯】3) 改进版二【过程值列表缓存遍历后检索】 4. 最优算法5. 相关资源 本文为Python算法题集之一的…

鸿蒙OS应用编程实战:构建未来应用的基石

&#x1f482; 个人网站:【 海拥】【神级代码资源网站】【办公神器】&#x1f91f; 基于Web端打造的&#xff1a;&#x1f449;轻量化工具创作平台&#x1f485; 想寻找共同学习交流的小伙伴&#xff0c;请点击【全栈技术交流群】 引言 鸿蒙OS&#xff08;HarmonyOS&#xff0…

TCP/UDP模型:2024/2/29

作业1&#xff1a;TCP模型 服务器端&#xff1a; #include <myhead.h> #define SER_IP "192.168.199.129" #define SER_PORT 8899int main(int argc, const char *argv[]) {//1.创建用于连接的套接字文件int sfdsocket(AF_INET,SOCK_STREAM,0);if(sfd-1){per…

docker save 命令 docker load 命令 快速复制容器

docker save 命令 docker load 命令 1、docker save 命令2、docker load 命令 1、docker save 命令 docker save 命令用于在系统上把正在使用的某个容器镜像 导出成容器镜像文件保存下载&#xff0c;以便在其他系统上导入这个容器镜像文件 以便快速在其他服务器上启动相同的容…

16. QML中的一些粒子特效

1.说明 在使用unity开发游戏时&#xff0c;都会涉及到一些特效的开发。实际上在QML中也提供了一些可以做特效的控件&#xff0c;称之为粒子系统。本篇博客主要记录一些使用粒子做特效的方式。 特效–火焰效果&#xff1a; 2. 案例汇总 2.1 案例1 效果展示&#xff1a; 粒子…

一文速览深度伪造检测(Detection of Deepfakes):未来技术的守门人

一文速览深度伪造检测&#xff08;Detection of Deepfakes&#xff09;&#xff1a;未来技术的守门人 前言一、Deepfakes技术原理卷积神经网络&#xff08;CNN&#xff09;&#xff1a;细致的艺术学徒生成对抗网络&#xff08;GAN&#xff09;&#xff1a;画家与评审的双重角色…