Unity3D自定义创建圆锥体

前言

这几天琢磨着开发个个人作品的时候,发现原来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的右键菜单

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

接下来实现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月。

目前这个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;

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

完善锥体

底部圆既然已经绘制成功,锥体可以理解为将圆心上移即可,在顶点数量上,三角形索引数组上都相当于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设置比较简单,所以对贴图也特定要求,不然图片会比较扭曲,需要的朋友可以自行修改。

结果

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

(0)

相关推荐

  • Unity3D创建圆柱体的方法

    看到这篇文章你可能好奇unity自带圆柱体组件,直接就可以生成,为什么我们还要用代码生成.其实是最近领导对项目有一个要求,就是我们要在自写编辑器内操作圆管,也就是圆柱体.功能类似3DMax里的拉伸管线.刚开始看到这个要求我内心是拒绝的,mmp我是unity程序员不是图像学程序员啊,这看着有点底层啊.但是心想,这也是自我学习提升的机会,于是我就给领导个面子将它实现吧.我们知道如果想像3Dmax里那样操作管线,就必须用代码创建圆柱体并用代码控制他的顶点位置才能实现我们的需求.所以第一步就是用代码创建

  • Unity3D网格功能生成球体网格模型

    本文实例为大家分享了Unity3D网格功能生成球体网格模型的具体代码,供大家参考,具体内容如下 前面已经讲过怎样使用mesh生成一个自己的网格,那么本文将会讲述怎样将这个网格变换成自己想要的形状,比如一个球体. 我们需要知道一个从平面坐标到球体坐标的映射公式.假设平面坐标是(x,y),球体坐标是(x0,y0,z0),则 球体坐标(x0,y0,z0)可以通过以下代码得到,(x,y) 对应vertices[i].x和vertices[i].y. v.x = r * Mathf.Cos(vertice

  • Unity3D自定义创建圆锥体

    前言 这几天琢磨着开发个个人作品的时候,发现原来Unity3D官方没有提供圆锥体的创建功能,就自己做了个编辑器扩展.鉴于之前搜索Mesh编程的时候很少有博客把自己的算法讲清楚,这里我抛砖引玉,尽我所能为一些初学者提供参考,当然,算法未必优,如有更好的算法并乐意知会我则不胜感激,我是大龄转行Unity3D开发,一路行来都是自己琢磨,比较辛苦,先行谢过. 软件环境 Win10 + Unity3D 2017.3.0f3 正文 基本思路是以原点为圆锥体底部圆的中心点,以其正上方1单元处的点为圆锥体锥尖顶

  • 微信小程序 自定义创建详细介绍

    微信小程序 自定义创建,最近自己捣鼓微信小程序的东西,这里对自定义创建做一个简单的资料整理,也许可以帮助大家.  微信小程序  自定义创建 自定义创建与默认创建完全相同, 只是不要勾选quick start即可 淡定(不要看到报错就紧张, 一定要淡定) 看看它说了什么, no such file or directory(没有文件或目录), 没哪个文件, 哦 原来是 app.json. 还记得 .json是什么文件不, 对喽, 全局配置文件. 没有配置文件就像没有汽油的布加迪, 跑起来才怪. 话

  • 微信小程序  自定义创建详细介绍

    微信小程序 自定义创建,最近自己捣鼓微信小程序的东西,这里对自定义创建做一个简单的资料整理,也许可以帮助大家.  微信小程序  自定义创建 自定义创建与默认创建完全相同, 只是不要勾选quick start即可 淡定(不要看到报错就紧张, 一定要淡定) 看看它说了什么, no such file or directory(没有文件或目录), 没哪个文件, 哦 原来是 app.json. 还记得 .json是什么文件不, 对喽, 全局配置文件. 没有配置文件就像没有汽油的布加迪, 跑起来才怪. 话

  • python中web框架的自定义创建

    一.什么是框架 框架的本质就是一个socket服务,可以完成不同主机之间的通信.它是一个半成品的项目,其中可能已经封装好了基本的功能,比如路由,模型,模板,视图功能都已完善,又可能它只封装好了基本的路由功能,其他的所有都需要程序员来完善. 优点:节省了开发时间,节约了开发人力,提高了开发效率 二.框架的种类 目前python开发市场上最常用的有三大框架,Django,flask与tornado.其中,Django是最常用的,它是一个重量级框架,其中的大部分功能都已经被封装完成,只需小小的逻辑代码

  • iOS 控制器自定义动画跳转方法(模态跳转)

    参考资料: Apple 开发文档 Customizing the Transition Animations WWDC 2013 Custom Transitions Using View Controllers 图例: 跳转的动画有很多,全部可以自定义 创建自定义跳转必须遵循的三个步骤: 1.创建一个类,并实现了 UIViewControllerAnimatedTransitioning 协议 2.创建一个类作为 UIViewControllerTransitioningDelegate 过渡

  • 解决django同步数据库的时候app models表没有成功创建的问题

    问题描述: 在django中创建了一个app,而且在app中自定义创建了几个数据表,在同步的时候系统自带的表可以成功,但是models中的没有生效,而且进入对应app下的migrations目录,发现为空,应该如何解决呢! 解决方式: python3 manage.py makemigrations --empty managerbook  # managerbook就是你的app名字,此处要写成自己的app名字 python3 manage.py makemigrations   # 再次正常

  • js中script的上下放置区别,Dom的增删改创建操作实例分析

    本文实例讲述了js中script的上下放置区别,Dom的增删改创建操作.分享给大家供大家参考,具体如下: 回顾 javascript分为三部分: 1.ECMAScript5.0 es6(阮一峰) es7 es8    es6中有类的概念 声明变量 var  let(es6中语法) 内置函数 Date Math.random if else  switch while do-while  for 2.DOM  Document Object Model 获取DOM事件的三种方式 getElemen

  • SpringBoot 如何实现自定义Redis序列化

    目录 问题 环境 入口点 实现自定义序列化 小结 问题 在使用RedisTemplate存储对象时,如果采用JDK默认的序列化方式,数据会出现许多编码字符,辨析度不高.比如一个空的User对象,存储到redis后如下: 这些使用JDK默认序列化方式序列化后的数据简直惨不忍睹,在使用命令行查询数据时会很头疼. 如何使数据更容易辨别呢? 一种办法是使用StringRedisTemplate,在存入redis前先将数据处理成字符串格式再存入redis,但这种方式的缺点就是每次存入数据前都要手动对非字符

  • Vue中自定义标签及其使用方式

    目录 问题 需求 自定义 创建组件的vue文件 编辑组件 使用 导入 申明使用 使用 整个代码 效果 问题 我们在使用Vue开发移动端应用程序时,常常使用Vant Weapp中的组件库,但是我们想过它是怎么实现的吗?是否我们也可以自己编写并且调用呢? 这里我将要讲解我在自定义标签方面的使用. 需求 这里我想要建一个名叫:<test>的组件. 功能是:组成两个按钮,且两个按钮颜色不同. 注意:这里的组件名.功能都是可以自定义的,按自己需求来取舍. 自定义 创建组件的vue文件 我们更目录设置为s

  • Elasticsearch mapping 概念及自动创建示例

    目录 正文 1.什么是Mapping 2.es 自动创建mapping 3. mapping 类型自动识别 4. 自定义创建mapping 5. mapping 属性设置analyzer 分词器 6. mapping 属性设置 boost 权重 7. mapping 属性设置 copy_to 8. mapping 属性设置 index 9. mapping 设置 属性 null_value 默认值 10. mapping 设置 dynamic 10.1 dynamic false 10.2 dy

随机推荐