Unity3D自定义创建圆锥体

作者:hellemic 时间:2022-05-04 10:12:34 

前言

这几天琢磨着开发个个人作品的时候,发现原来Unity3D官方没有提供圆锥体的创建功能,就自己做了个编辑器扩展。鉴于之前搜索Mesh编程的时候很少有博客把自己的算法讲清楚,这里我抛砖引玉,尽我所能为一些初学者提供参考,当然,算法未必优,如有更好的算法并乐意知会我则不胜感激,我是大龄转行Unity3D开发,一路行来都是自己琢磨,比较辛苦,先行谢过。

软件环境

Win10 + Unity3D 2017.3.0f3

正文

基本思路是以原点为圆锥体底部圆的中心点,以其正上方1单元处的点为圆锥体锥尖顶点,其他点参照Cylinder为分布在半径为0.5单元的圆上,每20度一个点,这样总共加起来的顶点数量是38个,三角形索引数组数量是108个(锥体可以看作底部圆心上移,所以这两部分的三角形数量是相等的,而底部每20度一个点,那么就有18个三角形,所以结果就是18∗3∗2=10818∗3∗2=108)。

下面开始逐步分解实现。

编辑器扩展

首先,扩展编辑器,在GameObject/3D Object下新建一个Cone菜单,为了假装是亲生的,就和Cube等原生菜单放在一起好了。


[MenuItem("GameObject/3D Object/Cone",false,priority = 7)]
public static void CreateCone()
{
SpawnConeInHierarchy();
}

这里主要就是利用MenuItem特性来实现的,其中false表示该菜单不需要有效性验证,priority=7控制菜单显示的位置,可以参考这里: Unity扩展Hierachry的右键菜单

方便起见,我把图贴下面:

Unity3D自定义创建圆锥体

Unity3D自定义创建圆锥体

接下来实现SpawnConeInHierarchy方法:


private static void SpawnConeInHierarchy()
{
Transform[] selections = Selection.GetTransforms(SelectionMode.TopLevel | SelectionMode.ExcludePrefab);

if (selections.Length <= 0)
{
 GameObject cone = new GameObject("Cone");
 cone.transform.position = Vector3.zero;
 cone.transform.rotation = Quaternion.identity;
 cone.transform.localScale = Vector3.one;
 //SetMesh(cone);
 return;
}

foreach (Transform selection in selections)
{
 GameObject cone = new GameObject("Cone");
 cone.transform.SetParent(selection);
 cone.transform.localPosition = Vector3.zero;
 cone.transform.localRotation = Quaternion.identity;
 cone.transform.localScale = Vector3.one;
 //SetMesh(cone);
}
}

这里分两种情况,如果没有在Hierarchy面板选中任何物体,那么就在根目录下生成一个名字为”Cone”的GameObject,如果有选中物体,则生成的”Cone”会变为选中项的子物体。

PS:这里有个Bug,如果同时选中了多个物体,又是采用的Hierarchy面板右键菜单的方式,那么会在每个选中物体下都生成与选中物体数量相同的子物体,见下图。这个Bug应该不仅限于版本2017.3,因为我在网上有搜到一个同情况的帖子,时间是2016年8月。

Unity3D自定义创建圆锥体

目前这个Bug我已经提交给官方确认了,他们已转交给QA,不过不影响使用,避免办法就是不用右键菜单,而是点击菜单栏”GameObject”下的菜单。

到此为止,我们已经扩展了编辑器菜单,但是生成出来的是空物体,接下来我们实现SetMesh方法以创建Mesh,让圆锥体显示出来。

创建Mesh

分两部,首先绘出底部的圆。

绘制圆形底部

圆心已经确定为原点,半径为0.5f,圆上分布共20个点,那么每个点的坐标就可以用三角函数算出。


private static void SetMesh(GameObject go)
{
if (null == go)
 return;
//仿Cylinder参数
float myRadius = 0.5f;
int myAngleStep = 20;
Vector3 myTopCenter = new Vector3(0, 1, 0);
Vector3 myBottomCenter = Vector3.zero;
//构建顶点数组和UV数组
//每20度一个顶点,再加上圆心,得出顶点数组长度
Vector3[] myVertices = new Vector3[360 / myAngleStep + 1];
//因为uv数组和顶点数组是一一对应的,所以这里同时计算uv数组
Vector2[] myUV = new Vector2[myVertices.Length];
//将圆心作为第一个顶点,对应的uv设置为贴图正中
myVertices[0] = myBottomCenter;
myUV[0] = new Vector2(0.5f, 0.5f);
//循环计算其他顶点坐标
for (int i = 1; i <= myVertices.Length / 2; i++)
{
 float curAngle = i * myAngleStep * Mathf.Deg2Rad;
 float curX = myRadius * Mathf.Cos(curAngle);
 float curZ = myRadius * Mathf.Sin(curAngle);
 myVertices[i] = new Vector3(curX, 0, curZ);
 //顶点坐标范围是[-0.5,0.5],而uv坐标范围是[0,1],所以要进行转换
 myUV[i] = new Vector2(curX + 0.5f, curZ + 0.5f);
}

接下来,构建三角形索引数组,19个顶点,共18个三角形,所以数组长度是18 * 3 = 54。


int[] myTriangle = new int[(myVertices.Length - 1) * 3];
//每三个索引(即顶点数组中的顶点索引值)为一个三角形索引组
for (int i = 0; i <= myTriangle.Length - 3; i = i+3)
{
 //每组都以圆心起始
 myTriangle[i] = 0;
 //为能从圆锥底部看见物体,这里按逆时针顺序排列,也就是(0 1 2 0 2 3...)
 myTriangle[i + 1] = i / 3 + 1;
 //最后一个三角形时终点索引应为1
 myTriangle[i + 2] = i + 2 == myTriangle.Length / 2 - 1 ? 1 : i / 3 + 2;
 }
}

最后,分配mesh,赋值材质后就可以看到一个圆形物体了。


//构建mesh
Mesh myMesh = new Mesh();
myMesh.name = "Cone";
myMesh.vertices = myVertices;
myMesh.triangles = myTriangle;
myMesh.uv = myUV;
myMesh.RecalculateBounds();
myMesh.RecalculateNormals();
myMesh.RecalculateTangents();
//分配mesh
MeshFilter mf = go.AddComponent<MeshFilter>();
mf.mesh = myMesh;
//分配材质
MeshRenderer mr = go.AddComponent<MeshRenderer>();
Material myMat = new Material(Shader.Find("Standard"));
mr.sharedMaterial = myMat;

Unity3D自定义创建圆锥体

因为底部没光照,所以看起来是黑的,另外,上面的代码是我从最终代码中手动修改得到的,可能有错误,只是用于理解思路,完整代码会在最后给出。

完善锥体

底部圆既然已经绘制成功,锥体可以理解为将圆心上移即可,在顶点数量上,三角形索引数组上都相当于double了一份即可。

这里有个情况说明一下,我本来是想共用圆上顶点的,这样整个锥体的顶点数就是20,但经过测试是不可以的,我参考了Cube,顶点数是24,说明不同面的顶点是不能共用的,可能是因为法线方向等因素吧。
修改后的完整代码如下:


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System;

public class ConeCreatorEditor
{

[MenuItem("GameObject/3D Object/Cone",false,priority = 7)]
public static void CreateCone()
{
SpawnConeInHierarchy();
}

private static void SetMesh(GameObject go)
{
if (null == go)
 return;
//仿Cylinder参数
float myRadius = 0.5f;
int myAngleStep = 20;
Vector3 myTopCenter = new Vector3(0, 1, 0);
Vector3 myBottomCenter = Vector3.zero;
//构建顶点数组和UV数组
Vector3[] myVertices = new Vector3[360 / myAngleStep * 2 + 2];
//
Vector2[] myUV = new Vector2[myVertices.Length];
//这里我把锥尖顶点放在了顶点数组最后一个
myVertices[0] = myBottomCenter;
myVertices[myVertices.Length - 1] = myTopCenter;
myUV[0] = new Vector2(0.5f, 0.5f);
myUV[myVertices.Length - 1] = new Vector2(0.5f,0.5f);
//因为圆上顶点坐标相同,只是索引不同,所以这里循环一般长度即可
for (int i = 1; i <= (myVertices.Length -2) / 2; i++)
{
 float curAngle = i * myAngleStep * Mathf.Deg2Rad;
 float curX = myRadius * Mathf.Cos(curAngle);
 float curZ = myRadius * Mathf.Sin(curAngle);
 myVertices[i] = myVertices[i + (myVertices.Length - 2) / 2] = new Vector3(curX, 0, curZ);
 myUV[i] = myUV[i + (myVertices.Length - 2) / 2] = new Vector2(curX + 0.5f, curZ + 0.5f);

}
//构建三角形数组
int[] myTriangle = new int[(myVertices.Length - 2) * 3];
for (int i = 0; i <= myTriangle.Length - 3; i = i+3)
{
 if (i + 2 < myTriangle.Length / 2)
 {
 myTriangle[i] = 0;
 myTriangle[i + 1] = i / 3 + 1;
 myTriangle[i + 2] = i + 2 == myTriangle.Length / 2 - 1 ? 1 : i / 3 + 2;
 }
 else
 {
 //绘制锥体部分,索引组起始点都为锥尖
 myTriangle[i] = myVertices.Length - 1;
 //锥体最后一个三角形的中间顶点索引值为19
 myTriangle[i + 1] = i == myTriangle.Length - 3 ? 19 : i / 3 + 2;
 myTriangle[i + 2] = i / 3 + 1;
 }
}

//构建mesh
Mesh myMesh = new Mesh();
myMesh.name = "Cone";
myMesh.vertices = myVertices;
myMesh.triangles = myTriangle;
myMesh.uv = myUV;
myMesh.RecalculateBounds();
myMesh.RecalculateNormals();
myMesh.RecalculateTangents();
//分配mesh
MeshFilter mf = go.AddComponent<MeshFilter>();
mf.mesh = myMesh;
//分配材质
MeshRenderer mr = go.AddComponent<MeshRenderer>();
Material myMat = new Material(Shader.Find("Standard"));
mr.sharedMaterial = myMat;
}

private static void SpawnConeInHierarchy()
{
Transform[] selections = Selection.GetTransforms(SelectionMode.TopLevel | SelectionMode.ExcludePrefab);

if (selections.Length <= 0)
{
 GameObject cone = new GameObject("Cone");
 cone.transform.position = Vector3.zero;
 cone.transform.rotation = Quaternion.identity;
 cone.transform.localScale = Vector3.one;
 //设置创建操作可撤销
 Undo.RegisterCreatedObjectUndo(cone, "Undo Creating Cone");
 SetMesh(cone);
 return;
}

foreach (Transform selection in selections)
{
 GameObject cone = new GameObject("Cone");
 cone.transform.SetParent(selection);
 cone.transform.localPosition = Vector3.zero;
 cone.transform.localRotation = Quaternion.identity;
 cone.transform.localScale = Vector3.one;
 //设置创建操作可撤销
 Undo.RegisterCreatedObjectUndo(cone, "Undo Creating Cone");
 SetMesh(cone);
}
}
}

PS:这里的uv设置比较简单,所以对贴图也特定要求,不然图片会比较扭曲,需要的朋友可以自行修改。

结果

Unity3D自定义创建圆锥体

来源:https://blog.csdn.net/qq_34469717/article/details/78989320

标签:Unity3D,圆锥体
0
投稿

猜你喜欢

  • C#获取真实IP地址实现方法

    2022-01-05 11:35:49
  • Android N获取外置SD卡或挂载U盘路径的方法

    2023-07-14 22:21:03
  • C语言回溯法解八皇后问题(八皇后算法)

    2021-11-12 03:27:15
  • android实现歌词自动滚动效果

    2022-03-16 17:09:10
  • SpringCloud超详细讲解微服务网关Gateway

    2021-09-11 14:54:49
  • 深入剖析Java中String类的concat方法

    2023-06-02 07:07:13
  • C#多线程之线程池(ThreadPool)

    2022-05-02 07:12:15
  • Java Socket实现传输压缩对象的方法示例

    2022-12-18 06:52:30
  • C#如何使用Bogus创建模拟数据示例代码

    2023-10-11 10:57:50
  • 详解SpringBoot修改启动端口server.port的四种方式

    2022-03-10 20:28:48
  • Android开发仿扫一扫实现拍摄框内的照片功能

    2023-03-17 14:11:12
  • Java实现的最大匹配分词算法详解

    2021-12-30 00:50:04
  • Java函数式编程(六):Optional

    2021-07-04 21:44:05
  • 如何使用try-with-resource机制关闭连接

    2022-04-25 01:36:54
  • Servlet 过滤器详细介绍

    2021-10-21 13:51:57
  • Java8 如何移除两个相同的List对象

    2023-06-07 20:57:50
  • 读取Java文件到byte数组的三种方法(总结)

    2023-08-01 17:19:39
  • ListView用法中与滚动相关的需求实现

    2021-06-08 09:00:08
  • Java中Druid连接池连接超时获取不到连接的解决

    2022-09-15 04:49:59
  • springboot集成elasticsearch7的图文方法

    2022-05-15 19:46:11
  • asp之家 软件编程 m.aspxhome.com