欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Unity3D自定义创建圆锥体

程序员文章站 2023-12-11 07:59:27
前言 这几天琢磨着开发个个人作品的时候,发现原来unity3d官方没有提供圆锥体的创建功能,就自己做了个编辑器扩展。鉴于之前搜索mesh编程的时候很少有博客把自己的算法讲...

前言

这几天琢磨着开发个个人作品的时候,发现原来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自定义创建圆锥体

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。