- PVSM.RU - https://www.pvsm.ru -
Одно из преимуществ Unity в качестве платформы для разработки игр — её мощный 3D-движок. В этом туториале вы познакомитесь с миром 3D-объектов и манипуляций мешами.
В связи с ростом технологий виртуальной и дополненной реальности (VR/AR) большинство разработчиков сталкивается со сложными концепциями 3D-графики. Пусть этот туториал будет для них отправной точкой. Не волнуйтесь, здесь не будет сложной 3D-математики — только сердца, рисунки, стрелки и куча интересного!
Примечание: этот туториал предназначен для пользователей, знакомых с IDE Unity и имеющих определённый опыт программирования на C#. Если у вас нет таких знаний, то изучите сначала туториалы Introduction to Unity UI [1] и Introduction to Unity Scripting [2].
Вам понадобится версия Unity не ниже 2017.3.1. Последнюю версию Unity можно скачать здесь [3]. В этом туториале используются custom editor, подробнее о них можно узнать из туториала Extending the Unity Editor [4].
Для начала познакомьтесь с основными терминами 3D-графики, которые позволят вам лучше понять туториал.
Базовые технические термины 3D-графики:
Анатомия 3D-объекта начинается с его меша. Создание этого меша начинается с его вершин. Невидимые линии, соединяющие эти вершины, образуют треугольники, которые определяют базовую форму объекта.
Затем нормали и UV-данные задают затенение, цвет и текстуру. Данные меша хранятся в mesh filter, а mesh renderer использует эти данные для отрисовки объекта в сцене.
То есть псевдокод создания 3D-модели выглядит так:
Разобравшись с основами, скачайте проект [5], распакуйте файлы и запустите заготовку проекта в Unity. Посмотрите на структуру папок в окне Project:
Описание папок:
В следующем разделе мы создадим custom editor, чтобы визуализировать создание 3D-меша.
Откройте 01 Mesh Study Demo, находящееся в папке Scenes. В окне Scene вы увидите 3D-куб:
Прежде чем приступать к мешу, давайте взглянем на скрипт custom editor.
Выберите папку Editor в окне Project. Скрипты в этой папке добавляют функционал к редактору (Editor) во время разработки и недоступны в режиме Build.
Откройте MeshInspector.cs и просмотрите исходный код. Все скрипты Editor должны реализовывать класс Editor
, его атрибут CustomEditor
сообщает классу Editor
, для какого типа объекта он является редактором. OnSceneGUI()
— это метод события, позволяющий выполнять отрисовку в окне Scene; OnInspectorGUI()
позволяет добавить в Inspector дополнительные элементы GUI.
В MeshInspector.cs перед началом класса MeshInspector
добавим следующее:
[CustomEditor(typeof(MeshStudy))]
Объяснение кода: атрибут CustomEditor
сообщает Unity, какой тип объекта может изменять класс custom editor.
В OnSceneGUI()
перед EditMesh()
добавим следующее:
mesh = target as MeshStudy;
Debug.Log("Custom editor is running");
Объяснение кода: класс Editor
имеет стандартную переменную target
. Здесь target
является преобразованием в MeshStudy
. Теперь custom editor будет отрисовывать в окне Scene все GameObject и прикреплёнными к ним MeshStudy.cs. Добавление отладочных сообщений позволяет убедиться в консоли, что custom editor действительно выполняется.
Сохраним (Save) файл и вернёмся в Unity. Перейдите в папку Scripts и перетащите MeshStudy.cs на GameObject Cube в Hierarchy, чтобы прикрепить его.
Теперь в консоли должно выводиться сообщение «Custom editor is running», и это означает, что мы всё сделали верно! Можете удалить отладочное сообщение, чтобы оно не мешало нам в консоли.
При работе с 3D-мешем в режиме Edit при помощи custom editor будьте аккуратны, чтобы не перезаписать меш Unity по умолчанию. Если это произойдёт, то придётся перезапускать Unity.
Чтобы безопасно клонировать меш без перезаписи исходной формы, создадим копию меша из свойства MeshFilter.sharedmesh
и присвоим его снова mesh filter.
Для этого дважды щёлкните на MeshStudy.cs в папке Scripts, чтобы открыть файл в редакторе кода. Этот скрипт наследует от класса MonoBehaviour
, и его функция Start()
не выполняется в режиме Edit.
В MeshStudy.cs перед началом класса MeshStudy
добавим следующее:
[ExecuteInEditMode]
Объяснение кода: после добавления этого атрибута функция Start()
будет выполняться и в режиме Play, и в режиме Edit. Теперь мы сначала можем создать экземпляр объекта меша и клонировать его.
В InitMesh()
добавим следующий код:
oMeshFilter = GetComponent<MeshFilter>();
oMesh = oMeshFilter.sharedMesh; //1
cMesh = new Mesh(); //2
cMesh.name = "clone";
cMesh.vertices = oMesh.vertices;
cMesh.triangles = oMesh.triangles;
cMesh.normals = oMesh.normals;
cMesh.uv = oMesh.uv;
oMeshFilter.mesh = cMesh; //3
vertices = cMesh.vertices; //4
triangles = cMesh.triangles;
isCloned = true;
Debug.Log("Init & Cloned");
Объяснение кода:
oMesh
из компонента MeshFilter
.cMesh
.
Сохраните файл и вернитесь в Unity. В консоли отладки должно отображаться сообщение «Init & Cloned». Выберите GameObject Cube
в Hierarchy и проверьте его свойства в Inspector. В Mesh Filter должен отображаться ассет меша под названием clone. Отлично! Это означает, что мы успешно клонировали меш.
В папке Editor перейдите к MeshInspector.cs. В OnInspectorGUI()
, после второй строки кода добавьте следующее:
if (GUILayout.Button("Reset")) //1
{
mesh.Reset(); //2
}
Объяснение кода:
Reset()
.
Сохраните файл, откройте MeshStudy.cs и добавьте в функцию Reset()
следующий код:
if (cMesh != null && oMesh != null) //1
{
cMesh.vertices = oMesh.vertices; //2
cMesh.triangles = oMesh.triangles;
cMesh.normals = oMesh.normals;
cMesh.uv = oMesh.uv;
oMeshFilter.mesh = cMesh; //3
vertices = cMesh.vertices; //4
triangles = cMesh.triangles;
}
Объяснение кода:
cMesh
на исходный меш.cMesh
oMeshFilter
.Сохраните файл и вернитесь в Unity. В Inspector нажмите на кнопку Test Edit, чтобы исказить меш куба. Далее нажмите кнопку Reset; куб должен вернуться к исходной форме.
Меш состоит из вершин, соединённых рёбрами в треугольники. Треугольники задают базовую форму объекта.
Класс Mesh:
- Вершины хранятся как массив значений
Vector3
.- Треугольники хранятся как массив integer, соответствующих индексам массива вершин.
То есть в простом меше Quad, состоящем из четырёх вершин и двух треугольников, данные меша будут выглядеть так:
Здесь мы хотим отобразить вершины куба в виде голубых точек.
В MeshInspector.cs зайдём в функцию EditMesh()
и добавим следующее:
handleTransform = mesh.transform; //1
handleRotation = Tools.pivotRotation == PivotRotation.Local ? handleTransform.rotation : Quaternion.identity; //2
for (int i = 0; i < mesh.vertices.Length; i++) //3
{
ShowPoint(i);
}
Объяснение кода:
handleTransform
получает из mesh
значения Transform.handleRotation
получает режим Rotation текущего шарнира.ShowPoint()
.
В функции ShowPoint()
, сразу после комментария //draw dot
добавим следующее:
Vector3 point = handleTransform.TransformPoint(mesh.vertices[index]);
Объяснение кода: эта строка преобразует локальную позицию вершины в координату в мировом пространстве.
В той же функции, в блоке if
сразу после только что добавленной строки кода добавим следующее:
Handles.color = Color.blue;
point = Handles.FreeMoveHandle(point, handleRotation, mesh.handleSize, Vector3.zero, Handles.DotHandleCap);
Объяснение кода:
Handles
.Handles.FreeMoveHandle()
создаёт манипулятор неограниченного движения, упрощающий операцию перетаскивания, которая пригодится нам в следующем разделе.Сохраните файл и вернитесь в Unity. Проверьте свойство куба в Inspector и убедитесь, что опция Move Vertex Point включена. Теперь вы должны увидеть, что меш на экране помечен несколькими голубыми точками. Вот и они — вершины меша куба! Попробуйте проделать это с другими 3D-объектами и понаблюдайте за результатами.
Начнём с самого простого шага манипуляций с мешем — перемещения отдельной вершины.
Перейдите в MeshInspector.cs. Внутри функции ShowPoint()
, сразу после комментария //drag
и прямо перед закрывающими скобками блока if
добавьте следующее:
if (GUI.changed) //1
{
mesh.DoAction(index, handleTransform.InverseTransformPoint(point)); //2
}
Объяснение кода:
GUI.changed
отслеживает все изменения, происходящие с точками, и хорошо работает вместе с Handles.FreeMoveHandle()
для распознавания операции перетаскивания.mesh.DoAction()
получает в качестве параметров её индекс и значения Transform. Так как значения Transform вершины находятся в мировом пространстве, мы преобразуем их в локальное пространство с помощью InverseTransformPoint()
.
Сохраните файл скрипта и перейдите в MeshStudy.cs. В DoAction()
, после открывающих скобок добавим следующее:
PullOneVertex(index, localPos);
Затем добавим в функцию PullOneVertex()
следующее:
vertices[index] = newPos; //1
cMesh.vertices = vertices; //2
cMesh.RecalculateNormals(); //3
Объяснение кода:
newPos
.cMesh.vertices
.RecalculateNormals()
пересчитываем и перерисовываем меш, чтобы он соответствовал изменениям.Сохраняем файл и возвращаемся в Unity. Попробуйте перетаскивать точки на кубе; увидели ли вы сломанный меш?
Похоже, что некоторые из вершин имеют одинаковую позицию, поэтому когда мы перетаскиваем только одну, остальные вершины остаются за ней, и меш ломается. В следующем разделе мы устраним эту проблему.
Визуально меш куба состоит из восьми вершин, шести сторон и 12 треугольников. Давайте проверим, так ли это.
Откроем MeshStudy.cs, взглянем в место перед функцией Start()
и найдём переменную vertices
. Мы увидим следующее:
[HideInInspector]
public Vector3[] vertices;
Объяснение кода: [HideInInspector]
скрывает общую переменную от окна Inspector.
Закомментируем этот атрибут:
//[HideInInspector]
public Vector3[] vertices;
Примечание: сокрытие значений вершин помогает
[HideInInspector]
в случае более сложных 3D-мешей. Так как размер массива вершин может достигать тысяч элементов, то это может приводить к торможению Unity при попытке просмотра значения массива в Inspector.
Сохраните файл и вернитесь в Unity. Перейдите в Inspector. Теперь под компонентом скрипта Mesh Study появилось свойство vertices. Нажмите на значок стрелки рядом с ним; так вы развернёте массив элементов Vector3
.
Можно увидеть, что размер массива равен 24, то есть существуют вершины, имеющие одинаковую позицию! Перед тем, как продолжать работу, не забудьте раскомментировать [HideInInspector]
.
Поэтому расчёт таков: 6 x 4 = 24 вершины.
Можете поискать и другие ответы. Но пока достаточно просто знать, что у некоторых мешей будут вершины, имеющие одинаковую позицию.
В MeshStudy.cs заменим весь код внутри функции DoAction()
на следующий:
PullSimilarVertices(index, localPos);
Перейдём в функцию PullSimilarVertices()
и добавим следующее:
Vector3 targetVertexPos = vertices[index]; //1
List<int> relatedVertices = FindRelatedVertices(targetVertexPos, false); //2
foreach (int i in relatedVertices) //3
{
vertices[i] = newPos;
}
cMesh.vertices = vertices; //4
cMesh.RecalculateNormals();
Объяснение кода:
FindRelatedVertices()
.newPos
.vertices
обратно cMesh.vertices
. Затем вызываем RecalculateNormals()
для перерисовки меша с новыми значениями.Сохраните файл и вернитесь в Unity. Перетащите любую из вершин; теперь меш должен сохранять форму и не разрушаться.
Теперь, когда мы выполнили первый шаг в манипуляции мешами, сохраним сцену и перейдём к следующему разделу.
В этом разделе вы узнаете о манипулировании мешами в реальном времени. Существует множество способов, но в этом туториале мы рассмотрим наиболее простой вид манипуляций мешами, а именно перемещение заранее созданных вершин меша.
Начнём с выбора вершин, которые будем перемещать в реальном времени.
Откройте сцену 02 Create Heart Mesh из папки Scenes. В окне Scene вы увидите красную сферу. Выберите Sphere в Hierarchy и перейдите в Inspector. Вы увидите, что к объекту прикреплён компонент скрипта Heart Mesh.
Теперь нам нужно, чтобы скрипт Editor для этого объекта отображал вершины меша в окне Scene. Перейдите в папку Editor и дважды щёлкните на HeartMeshInspector.cs.
В функции ShowHandle()
, внутри блока if
добавьте следующее:
Handles.color = Color.blue;
if (Handles.Button(point, handleRotation, mesh.pickSize, mesh.pickSize, Handles.DotHandleCap)) //1
{
mesh.selectedIndices.Add(index); //2
}
Объяснение кода:
Handles.Button
.mesh.selectedIndices
.
В OnInspectorGUI()
, перед закрывающей скобкой, добавим следующее:
if (GUILayout.Button("Clear Selected Vertices"))
{
mesh.ClearAllData();
}
Объяснение кода: так мы добавляем в Inspector кнопку Reset для вызова mesh.ClearAllData()
.
Сохраните файл и откройте HeartMesh.cs из папки Scripts. В функцию ClearAllData()
добавьте следующее:
selectedIndices = new List<int>();
targetIndex = 0;
targetVertex = Vector3.zero;
Объяснение кода: код очищает значения в selectedIndices
и targetIndex
. Также он обнуляет targetVertex
.
Сохраните файл и вернитесь в Unity. Выберите Sphere и перейдите в Inspector к компоненту скрипта HeartMesh. Разверните Selected Indices, нажав на значок стрелки рядом с ним. Это позволит нам отслеживать каждую вершину, добавляемую в список.
Включите Is Edit Mode с помощью флажка рядом с ним. Благодаря этому в окне Scene будут отрисовываться вершины меша. При нажатии на синие точки в Selected Indices должны соответствующим образом меняться значения. Также протестируйте кнопку Clear Selected Vertices, чтобы убедиться, что она очищает все значения.
Примечание: в изменённом custom Inspector у нас есть опция для отображения/скрытия манипулятора transform с помощью Show Transform Handle. Так что не паникуйте, если не найдёте в других сценах манипулятор Transform! Перед выходом включайте его.
Изменение вершин меша в реальном времени по сути состоит из трёх этапов:
mVertices
.mVertices
.mVertices
при изменении на каждом этапе и позволяем Unity автоматически вычислять нормали.
Откройте HeartMesh.cs и перед функцией Start()
следующие переменные:
public float radiusofeffect = 0.3f; //1
public float pullvalue = 0.3f; //2
public float duration = 1.2f; //3
int currentIndex = 0; //4
bool isAnimate = false;
float starttime = 0f;
float runtime = 0f;
Объяснение кода:
selectedIndices
.
В функции Init()
перед блоком if
добавим следующее:
currentIndex = 0;
Объяснение кода: в начале игры currentIndex
присваивается значение 0 — первый индекс списка selectedIndices
.
В той же функции Init()
перед закрывающей скобкой блока else
добавим следующее:
StartDisplacement();
Объяснение кода: запускаем функцию StartDisplacement()
, если isEditMode
имеет значение false.
Внутрь функции StartDisplacement()
добавим следующее:
targetVertex = oVertices[selectedIndices[currentIndex]]; //1
starttime = Time.time; //2
isAnimate = true;
Объяснение кода:
targetVertex
, чтобы начать анимацию.isAnimate
на true.
После функции StartDisplacement()
создадим функцию FixedUpdate()
со следующим кодом:
void FixedUpdate() //1
{
if (!isAnimate) //2
{
return;
}
runtime = Time.time - starttime; //3
if (runtime < duration) //4
{
Vector3 targetVertexPos = oFilter.transform.InverseTransformPoint(targetVertex);
DisplaceVertices(targetVertexPos, pullvalue, radiusofeffect);
}
else //5
{
currentIndex++;
if (currentIndex < selectedIndices.Count) //6
{
StartDisplacement();
}
else //7
{
oMesh = GetComponent<MeshFilter>().mesh;
isAnimate = false;
isMeshReady = true;
}
}
}
Объяснение кода:
FixedUpdate()
выполняется в цикле с фиксированным FPS.isAnimate
имеет значение false, то пропускаем следующий код.runtime
анимации.runtime
находится в пределах duration
, то получаем мировые координаты targetVertex
и DisplaceVertices()
, охватывая целевую вершину параметрами pullvalue
и radiusofeffect
.currentIndex
единицу.currentIndex
среди selectedIndices
. Переходим к следующей вершине в списке с помощью StartDisplacement()
.oMesh
на текущий меш и присваиваем isAnimate
значение false, чтобы остановить анимацию.
В DisplaceVertices()
добавим следующее:
Vector3 currentVertexPos = Vector3.zero;
float sqrRadius = radius * radius; //1
for (int i = 0; i < mVertices.Length; i++) //2
{
currentVertexPos = mVertices[i];
float sqrMagnitute = (currentVertexPos - targetVertexPos).sqrMagnitude; //3
if (sqrMagnitute > sqrRadius)
{
continue; //4
}
float distance = Mathf.Sqrt(sqrMagnitute); //5
float falloff = GaussFalloff(distance, radius);
Vector3 translate = (currentVertexPos * force) * falloff; //6
translate.z = 0f;
Quaternion rotation = Quaternion.Euler(translate);
Matrix4x4 m = Matrix4x4.TRS(translate, rotation, Vector3.one);
mVertices[i] = m.MultiplyPoint3x4(currentVertexPos);
}
oMesh.vertices = mVertices; //7
oMesh.RecalculateNormals();
Объяснение кода:
sqrMagnitude
между currentVertexPos
и targetVertexPos
.sqrMagnitude
превышает sqrRadius
, то переходим к следующей вершине.falloff
, зависящее от расстояния distance
текущей вершины от центральной точки области действия.Vector3
и применяем её Transform к текущей вершине.oMesh
изменённые значения mVertices
, и заставляем Unity пересчитать нормали.Источник техники Falloff
Исходная формула взята из файла пакета ассетов Procedural Examples [6], который можно бесплатно скачать из Unity Asset Store.
Сохраните файл и вернитесь в Unity. Выберите Sphere, перейдите к компоненту HeartMesh и попробуйте добавить несколько вершин в свойство Selected Indices. Отключите Is Edit mode и нажмите Play, чтобы посмотреть на результат своей работы.
Поэкспериментируйте со значениями Radiusofeffect, Pullvalue и Duration, чтобы получить разные результаты. Когда будете готовы, измените настройки в соответствии с показанным ниже скриншотом.
Нажмите на Play. Превратилась ли ваша сфера в сердце?
Поздравляю! В следующем разделе мы сохраним меш в префаб для дальнейшего использования.
Для сохранения процедурного меша в форме сердца в режиме Play необходимо подготовить префаб, дочерним элементом которого будет 3D-объект, а затем заменить его ассет меша новым с помощью скрипта.
В окне Project найдите CustomHeart в папке Prefabs. Нажмите на значок стрелки, чтобы развернуть его содержимое и выберите Child. Теперь вы видите в окне превью Inspector объект Sphere. Это префаб, который будет хранить данные нового меша.
Откройте HeartMeshInspector.cs. Внутри функции OnInspectorGUI()
, перед закрывающей скобкой добавьте следующее:
if (!mesh.isEditMode && mesh.isMeshReady)
{
string path = "Assets/Prefabs/CustomHeart.prefab"; //1
if (GUILayout.Button("Save Mesh"))
{
mesh.isMeshReady = false;
Object pfObj = AssetDatabase.LoadAssetAtPath(path, typeof(GameObject)); //2
Object pfRef = AssetDatabase.LoadAssetAtPath (path, typeof(GameObject));
GameObject gameObj = (GameObject)PrefabUtility.InstantiatePrefab(pfObj);
Mesh pfMesh = (Mesh)AssetDatabase.LoadAssetAtPath(path, typeof(Mesh)); //3
if (!pfMesh)
{
pfMesh = new Mesh();
}
else
{
pfMesh.Clear();
}
pfMesh = mesh.SaveMesh(); //4
AssetDatabase.AddObjectToAsset(pfMesh, path);
gameObj.GetComponentInChildren<MeshFilter>().mesh = pfMesh; //5
PrefabUtility.ReplacePrefab(gameObj, pfRef, ReplacePrefabOptions.Default); //6
Object.DestroyImmediate(gameObj); //7
}
}
Объяснение кода:
path
значение пути к объекту префаба CustomHeart.pfObj
), второй — как ссылки (pfRef
).pfMesh
. Если он не найден, создаёт новый меш, в противном случае очищает имеющиеся данные.pfMesh
новыми данными меша, а затем добавляет его как ассет в CustomHeart.
gameObj
значением pfMesh
.gameObj
сопоставляя ранее существовавшие соединения.gameObj
.
Сохраните файл и перейдите в HeartMesh.cs. В общем методе SaveMesh()
, после создания экземпляра nMesh
добавьте следующее:
nMesh.name = "HeartMesh";
nMesh.vertices = oMesh.vertices;
nMesh.triangles = oMesh.triangles;
nMesh.normals = oMesh.normals;
Объяснение кода: возвращает ассет меша со значениями из меша в форме сердца.
Сохраните файл и вернитесь в Unity. Нажмите Play. После завершения анимации в Inspector появится кнопка Save Mesh. Нажмите на кнопку, чтобы сохранить новый меш, а затем остановите проигрыватель.
Перейдите в папку Prefabs и посмотрите на префаб CustomHeart. Вы должны увидеть, что теперь в объекте префаба CustomHeart есть совершенно новый меш в форме сердца.
Отличная работа!
В предыдущей сцене функция DisplaceVertices()
использовала формулу Falloff для определения силы перетаскивания, которая прикладывалась к каждой вершине в пределах заданного радиуса. Точка «затухания» (fall off), в которой сила перетаскивания начинает снижаться, зависит от использованного типа Falloff: Linear, Gaussian или Needle. Каждый тип создаёт в меше разные результаты.
В этом разделе мы рассмотрим другой способ манипулирования вершинами: с помощью заданной кривой. Взяв правило, что скорость равна расстоянию, поделённому на время (d=(v/t)), мы можем определить позицию вектора, ссылаясь на его расстояние, поделённое на время.
Сохраните текущую сцену и откройте 03 Customize Heart Mesh из папки Scenes. Вы увидите в Hierarchy экземпляр префаба CustomHeart. Нажмите на значок стрелки рядом с ним, чтобы развернуть его содержимое и выберите Child.
Просмотрите его свойства в Inspector. Вы увидите компонент Mesh Filter с ассетом Heart Mesh. Прикрепите к Child в качестве компонента скрипт Custom Heart. Теперь ассет должен смениться с HeartMesh на clone.
Далее откройте CustomHeart.cs из папки Scripts. Перед функцией Start()
добавьте следующее:
public enum CurveType
{
Curve1, Curve2
}
public CurveType curveType;
Curve curve;
Объяснение кода: здесь создаётся общее перечисление (enum) под названием CurveType
, после чего оно делается доступным из Inspector.
Перейдите в CurveType1()
и добавьте следующее:
Vector3[] curvepoints = new Vector3[3]; //1
curvepoints[0] = new Vector3(0, 1, 0);
curvepoints[1] = new Vector3(0.5f, 0.5f, 0);
curvepoints[2] = new Vector3(1, 0, 0);
curve = new Curve(curvepoints[0], curvepoints[1], curvepoints[2], false); //2
Объяснение кода:
Curve()
и присваиваем её значения curve
. Рисуемая кривая может отображаться в превью, если в качестве последнего параметра указать true.
Перейдите в CurveType2()
и добавьте следующее:
Vector3[] curvepoints = new Vector3[3]; //1
curvepoints[0] = new Vector3(0, 0, 0);
curvepoints[1] = new Vector3(0.5f, 1, 0);
curvepoints[2] = new Vector3(1, 0, 0);
curve = new Curve(curvepoints[0], curvepoints[1], curvepoints[2], false); //2
Объяснение кода:
Curve()
и присваиваем её значения curve
. Рисуемая кривая может отображаться в превью, если в качестве последнего параметра указать true.
В StartDisplacement()
, перед закрывающей скобкой добавим следующее:
if (curveType == CurveType.Curve1)
{
CurveType1();
}
else if (curveType == CurveType.Curve2)
{
CurveType2();
}
Объяснение кода: здесь мы проверяем выбранную пользователем опцию curveType
и соответствующим образом генерируем curve
.
В DisplaceVertices()
, внутри оператора цикла for
перед закрывающими скобками добавим следующее:
float increment = curve.GetPoint(distance).y * force; //1
Vector3 translate = (vert * increment) * Time.deltaTime; //2
Quaternion rotation = Quaternion.Euler(translate);
Matrix4x4 m = Matrix4x4.TRS(translate, rotation, Vector3.one);
mVertices[i] = m.MultiplyPoint3x4(mVertices[i]);
Объяснение кода:
distance
и умножаем её значение y
на force
, чтобы получить increment
.Vector3
для хранения новой позиции текущей вершины и соответствующим образом применяем её Transform.Сохраните файл и вернитесь в Unity. Проверьте свойства в компоненте CustomHeart игрового объекта Child. Вы увидите раскрывающийся список, в котором можно выбрать Curve Type. В раскрывающемся списке Edit Type выберите Add Indices или Remove Indices, чтобы обновить список вершин и поэкспериментировать с разными настройками.
Чтобы увидеть подробные результаты для разных типов кривых, введите значения в соответствии со скриншотом:
Для списка Curve Type выберите значение Curve1, убедитесь, что для Edit Type выбрано None и нажмите Play. Вы должны увидеть, что меш расходится в паттерн. Покрутите модель, чтобы увидеть её в виде сбоку, и сравните результаты для обоих типов кривых. Здесь вы видите, как выбранный Curve Type влияет на смещение меша.
Вот и всё! Можете нажать на Clear Selected Vertices, чтобы сбросить Selected Indices и поэкспериментировать с собственными паттернами. Но не забывайте, что есть и другие факторы, которые будут влиять на конечный результат меша, а именно:
Файлы готового проекта находятся в архиве проекта [5] туториала.
Не останавливайтесь на этом! Попробуйте использовать более сложные техники, применяемые в туториале «Процедурная генерация лабиринтов в Unity» [7].
Надеюсь, вам понравился этот туториал, а информация оказалась полезной. Особую благодарность я выражаю Джасперу Флику [8] из Catlike Coding [9] за его отличные туториалы, которые помогли мне собрать демо для моего проекта.
Автор: PatientZero
Источник [10]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/unity3d/298205
Ссылки в тексте:
[1] Introduction to Unity UI: https://www.raywenderlich.com/149464/introduction-to-unity-ui-part-1
[2] Introduction to Unity Scripting: https://www.raywenderlich.com/127672/introduction-unity-scripting
[3] здесь: https://unity3d.com/get-unity/download
[4] Extending the Unity Editor: https://www.raywenderlich.com/130721/extend-unity3d-editor
[5] проект: https://koenig-media.raywenderlich.com/uploads/2018/05/HeartShaper-2.zip
[6] Procedural Examples: https://assetstore.unity.com/packages/essentials/tutorial-projects/procedural-examples-5141
[7] «Процедурная генерация лабиринтов в Unity»: https://habr.com/post/353104/
[8] Джасперу Флику: http://catlikecoding.com/about/
[9] Catlike Coding: http://catlikecoding.com/
[10] Источник: https://habr.com/post/428796/?utm_campaign=428796
Нажмите здесь для печати.