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

相关文章

【WebGPU Unleashed】1.1 绘制三角形

一部2024新的WebGPU教程,作者Shi Yan。内容很好,翻译过来与大家共享,内容上会有改动,加上自己的理解。更多精彩内容尽在 dt.sim3d.cn ,关注公众号【sky的数孪技术】,技术交流、源码下载请添加微信号:digital_twin123 在 3D 渲染领域,三角形是最基本的绘制元素。在这里,我们将学习如何绘制单个三角形。接下来我们将制作一个简单的着色器来定义三角形内的像素

Flutter 进阶:绘制加载动画

绘制加载动画:由小圆组成的大圆 1. 定义 LoadingScreen 类2. 实现 _LoadingScreenState 类3. 定义 LoadingPainter 类4. 总结 实现加载动画 我们需要定义两个类:LoadingScreen 和 LoadingPainter。LoadingScreen 负责控制动画的状态,而 LoadingPainter 则负责绘制动画。

利用matlab bar函数绘制较为复杂的柱状图,并在图中进行适当标注

示例代码和结果如下:小疑问:如何自动选择合适的坐标位置对柱状图的数值大小进行标注?😂 clear; close all;x = 1:3;aa=[28.6321521955954 26.2453660695847 21.69102348512086.93747104431360 6.25442246899816 3.342835958564245.51365061796319 4.87

Unity Post Process Unity后处理学习日志

Unity Post Process Unity后处理学习日志 在现代游戏开发中,后处理(Post Processing)技术已经成为提升游戏画面质量的关键工具。Unity的后处理栈(Post Processing Stack)是一个强大的插件,它允许开发者为游戏场景添加各种视觉效果,如景深、色彩校正、辉光、模糊等。这些效果不仅能够增强游戏的视觉吸引力,还能帮助传达特定的情感和氛围。 文档

YOLOv8/v10+DeepSORT多目标车辆跟踪(车辆检测/跟踪/车辆计数/测速/禁停区域/绘制进出线/绘制禁停区域/车道车辆统计)

01:YOLOv8 + DeepSort 车辆跟踪 该项目利用YOLOv8作为目标检测模型,DeepSort用于多目标跟踪。YOLOv8负责从视频帧中检测出车辆的位置,而DeepSort则负责关联这些检测结果,从而实现车辆的持续跟踪。这种组合使得系统能够在视频流中准确地识别并跟随特定车辆。 02:YOLOv8 + DeepSort 车辆跟踪 + 任意绘制进出线 在此基础上增加了用户

Unity协程搭配队列开发Tips弹窗模块

概述 在Unity游戏开发过程中,提示系统是提升用户体验的重要组成部分。一个设计良好的提示窗口不仅能及时传达信息给玩家,还应当做到不干扰游戏流程。本文将探讨如何使用Unity的协程(Coroutine)配合队列(Queue)数据结构来构建一个高效且可扩展的Tips弹窗模块。 技术模块介绍 1. Unity协程(Coroutines) 协程是Unity中的一种特殊函数类型,允许异步操作的实现

Unity 资源 之 Super Confetti FX:点亮项目的璀璨粒子之光

Unity 资源 之 Super Confetti FX:点亮项目的璀璨粒子之光 一,前言二,资源包内容三,免费获取资源包 一,前言 在创意的世界里,每一个细节都能决定一个项目的独特魅力。今天,要向大家介绍一款令人惊艳的粒子效果包 ——Super Confetti FX。 二,资源包内容 💥充满活力与动态,是 Super Confetti FX 最显著的标签。它宛如一位

Unity数据持久化 之 一个通过2进制读取Excel并存储的轮子(4)

本文仅作笔记学习和分享,不用做任何商业用途 本文包括但不限于unity官方手册,unity唐老狮等教程知识,如有不足还请斧正​​ Unity数据持久化 之 一个通过2进制读取Excel并存储的轮子(3)-CSDN博客  这节就是真正的存储数据了   理清一下思路: 1.存储路径并检查 //2进制文件类存储private static string Data_Binary_Pa

Unity Adressables 使用说明(一)概述

使用 Adressables 组织管理 Asset Addressables 包基于 Unity 的 AssetBundles 系统,并提供了一个用户界面来管理您的 AssetBundles。当您使一个资源可寻址(Addressable)时,您可以使用该资源的地址从任何地方加载它。无论资源是在本地应用程序中可用还是存储在远程内容分发网络上,Addressable 系统都会定位并返回该资源。 您

Unity Adressables 使用说明(六)加载(Load) Addressable Assets

【概述】Load Addressable Assets Addressables类提供了加载 Addressable assets 的方法。你可以一次加载一个资源或批量加载资源。为了识别要加载的资源,你需要向加载方法传递一个键或键列表。键可以是以下对象之一: Address:包含你分配给资源的地址的字符串。Label:包含分配给一个或多个资源的标签的字符串。AssetReference Obj