Worldspace Continue if Mouse Off Screen Unity
Update note: Sean Duffy updated this tutorial for Unity 2019.1.2. Arai Lanju wrote the original.
Welcome to the world of 3D objects and mesh manipulation! One of the benefits of using Unity as your game development platform is its powerful 3D engine. That 3D engine, along with Unity's ability to use custom editors, makes the development of 3D games and apps so much easier.
With the growth of virtual reality and augmented reality (VR/AR) technology, most developers will inadvertently find themselves wrestling with the gritty bits of 3D concepts. So let this tutorial be your starting point. Don't worry, there will be no complicated 3D math here — just lots of hearts, drawings, arrows and loads of fun!
Note: This tutorial is intended for users who are familiar with the Unity editor and have some experience with C# programming. If you need to brush up on these topics, check out Introduction to Unity UI and Introduction to Unity Scripting first.
You need to have at least Unity 2019.1.3 installed. You can download the latest version of Unity here.
This tutorial uses a custom editor. You can learn more about custom editors at Extending the Unity Editor.
Getting Started
Understanding Meshes
Time to start with the basic vocabulary of 3D rendering. The shape of a 3D object is defined by its mesh. A mesh is like a net of points, or vertices. The invisible lines that connect these vertices form triangles, which define the basic shape of the object.
But in addition to the shape, the engine needs to know how to draw the surface of the object. So a mesh's data also includes its normals, which are vectors that determine which way a particular triangle is facing, and thus how light bounces off of it. Finally, a UV Map maps a material to an object, specifying how textures wrap around the shape.
In Unity, there are two primary rendering components: The Mesh Filter, which stores the mesh data of a model, and the Mesh Renderer, which combines the mesh data with materials to render the object in the scene.
Got all that? Here's a cheat sheet for easy reference:
- Vertices: A vertex is a point in 3D space. Often abbreviated to "vert".
- Lines/Edges: The invisible lines that connect vertices to one another.
- Triangles: Formed when edges connect three vertices.
- UV Map: Maps a material to an object, specifying how textures wrap around the object's shape.
- Normals: The directional vector of a vertex or a surface. This characteristically points outward, perpendicular to the mesh surface, and helps determine how light bounces off of the object.
- Mesh: Holds all the vertices, edges, triangles, normals and UV data of a model.
Here are the basic steps (in pseudocode) to create a 3D mesh:
- Create a new mesh named "myMesh".
- Add data to myMesh's vertices and triangle properties.
- Create a new mesh filter named "myMeshFilter".
- Assign myMesh to myMeshFilter's mesh property.
Setting Up the Project
Now that you have the basics covered, download the project using the Download Materials button at the top or bottom of this page, then unpack the files and open the starter project in Unity. Check out the folder structure in the Project view:
- Prefabs: This contains a CustomHeart prefab, which you'll use to save your 3D mesh at runtime.
- Scenes: This contains the three scenes that you will use for the different parts of this tutorial.
- Editor: The scripts inside this folder give you special powers in the editor during development.
- Scripts: This contains runtime scripts, or components. When you attach these components to a GameObject, they'll execute when you click Play.
- Materials: This folder contains the material for the mesh you'll be working with.
In the next section, you will create a custom editor to visualize the parts of a 3D mesh.
Poking and Prodding Meshes With a Custom Editor
Open 01 Mesh Study Demo inside RW/Scenes. In the Scene view, you will see a humble cube:
You're going to build a custom editor to tear this poor cube apart! (And then you'll learn how to keep it in one piece.)
Customizing the Editor Script
Select the Editor folder in the Project view. Scripts in this special folder modify how the Unity editor works; they do not become part of the built game.
Open MeshInspector.cs and view the source code. Note that this class inherits from Unity's base Editor
class — that's what makes Unity understand this is a custom editor rather than a game script.
The first step is to tell Unity what kind of objects that this special editor should draw. Add this attribute on the line above the MeshInspector
class declaration:
[CustomEditor(typeof(MeshStudy))]
Now, when any GameObject with the Mesh Study component attached to it is visible in the Scene view, this class will handle drawing it. But right now, you won't know if that's happening.
OnSceneGUI
is an event method that Unity calls every time it renders the Scene view in the editor. It's your chance to modify how Unity draws your object in the scene. Add the following at the beginning of OnSceneGUI
:
mesh = target as MeshStudy; Debug.Log("Custom editor is running");
The base Editor
class provides a reference to the object you're customizing in the target
variable, which has type Object
. You can't do much that's useful with a plain vanilla Object
, so this code casts target
as the type MeshStudy
. Logging a message allows you to see in the console that the custom editor is indeed running.
Save the file and return to Unity. Go to the RW/Scripts folder and drag MeshStudy.cs onto the Cube GameObject in the Hierarchy to attach the component to it.
Look in the console and make sure your code is running. Then go ahead and remove the Debug.Log
line so it doesn't flood your console.
Cloning a Mesh
When you're working with a 3D mesh with a custom editor in Edit mode, it is easy to accidentally overwrite Unity's default mesh — that is, the built-in Sphere, Cube, Cylinder and so on. If that happens, you'll need to restart Unity.
To avoid this, you'll clone the mesh before making any changes to it in Edit mode.
Open MeshStudy.cs. This script inherits from MonoBehaviour
, so its Start
will not run in Edit mode. Luckily, that's easy to fix!
In MeshStudy, above the class declaration, add the following:
[ExecuteInEditMode]
When a class has this attribute, its Start
will fire in both Play mode and Edit mode. Now that you've added it, you can instantiate and clone your mesh object before changing anything.
Add the following code to InitMesh
:
meshFilter = GetComponent<MeshFilter>(); originalMesh = meshFilter.sharedMesh; //1 clonedMesh = new Mesh(); //2 clonedMesh.name = "clone"; clonedMesh.vertices = originalMesh.vertices; clonedMesh.triangles = originalMesh.triangles; clonedMesh.normals = originalMesh.normals; clonedMesh.uv = originalMesh.uv; meshFilter.mesh = clonedMesh; //3 vertices = clonedMesh.vertices; //4 triangles = clonedMesh.triangles; isCloned = true; //5 Debug.Log("Init & Cloned");
Here's what's happening:
- Grabs whatever mesh you've originally assigned in
MeshFilter
. - Creates a new mesh instance called
clonedMesh
and sets its properties by copying the first mesh. - Assigns the copied mesh back to the mesh filter.
- Updates local variables, which you'll need later.
- Sets
isCloned
totrue
; you'll reference this later.
Save the file and return to Unity. The console should show the message "Init & Cloned".
Select Cube
in the Hierarchy and look at the Inspector. The Mesh Filter will show a mesh named clone. Great! This means you have cloned the mesh successfully.
But notice there's no new Mesh asset in your project — the cloned mesh only lives in Unity's memory right now, and it will disappear if you close the scene. You'll learn how to save a Mesh later.
Resetting a Mesh
For now, you want to give yourself an easy way to reset your mesh so you can play around without fear. Go back to MeshInspector.cs.
OnInspectorGUI
lets you customize the Inspector for your object with extra GUI elements and logic. In OnInspectorGUI
, find the comment //draw reset button
and replace it with the following:
if (GUILayout.Button("Reset")) //1 { mesh.Reset(); //2 }
- This code draws a Reset button in the Inspector. The draw function returns
true
when it's pressed. - When pressed, the button calls
Reset
in MeshStudy.cs.
Save the file and return to MeshStudy.cs. Add the following to Reset
:
if (clonedMesh != null && originalMesh != null) //1 { clonedMesh.vertices = originalMesh.vertices; //2 clonedMesh.triangles = originalMesh.triangles; clonedMesh.normals = originalMesh.normals; clonedMesh.uv = originalMesh.uv; meshFilter.mesh = clonedMesh; //3 vertices = clonedMesh.vertices; //4 triangles = clonedMesh.triangles; }
Here's what this code does step-by-step:
- Checks that both the original mesh and the clone mesh exist, in case the object's mesh filter doesn't have any data in it.
- Resets all the properties of
clonedMesh
to those of the original mesh. - Assigns
clonedMesh
back to the Mesh Filter component. - Updates local variables.
Save the file and return to Unity.
In the Inspector, click on the Test Edit button to mess with the cube's mesh, then press the Reset button to restore it.
Understanding Vertices and Triangles With Unity
As you saw earlier, a mesh consists of vertices connected by edges to form triangles. Triangles define the basic shape of the object.
Note: Unity's Mesh
class keeps track of vertices and triangles with two arrays:
- It stores vertices as an array of
Vector3
. - It stores triangles as an array of integers. Each integer is the index of one of the vertices in the verts array, and each group of three consecutive integers represents one triangle.
For example, the group
triangles[0], triangles[1], triangles[2]
represents one triangle, the grouptriangles[3], triangles[4], triangles[5]
represents the next triangle and so on.
So, in a simple Quad mesh that consists of four vertices and two triangles, the Quad's mesh data would be:
Visualizing Vertices
It will be easier to see how this works if you can draw and move around the vertices on a mesh with handles. Handles are tools for working with objects in the Scene view, like the draggable sphere for the Rotate tool. Now, you're going to write your own handle!
In MeshInspector.cs, look for EditMesh
and add the following:
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); }
- Gets the mesh's transform, which you'll need to know where to draw the vertices in world space.
- Gets the current pivot Rotation mode, to draw the handle the same way as everything else in the scene.
- Loops through the mesh's vertices and draws dots with
ShowPoint
.
In ShowPoint
, replace the //draw dot
comment with:
Vector3 point = handleTransform.TransformPoint(mesh.vertices[index]); //1 Handles.color = Color.blue; point = Handles.FreeMoveHandle(point, handleRotation, mesh.handleSize, Vector3.zero, Handles.DotHandleCap); //2
- This line converts the vertex's local position into world space.
- Draws the dot using the
Handles
utility class.
Handles.FreeMoveHandle
makes an unconstrained movement handle, which you will use in the next section to enable dragging the points around.
Save the file and return to Unity.
Check the Cube's MeshInspector and make sure that you've checked Move Vertex Point.
You should now see the vertices of the mesh marked with blue dots on screen. Try attaching the script to other 3D objects and see the results for yourself! :]
Moving a Single Vertex
You'll start with the most basic kind of mesh manipulation: Moving a single vertex.
Open MeshInspector.cs. Inside ShowPoint
, replace the //drag
comment with the following:
if (GUI.changed) //3 { mesh.DoAction(index, handleTransform.InverseTransformPoint(point)); //4 }
-
GUI.changed
monitors any changes made to the dots, which works nicely withHandles.FreeMoveHandle
to detect a dragging action. - On dragging a vertex, call
mesh.DoAction
with the index of the vertex and the vertex's position as parameters. This line also converts the vertex's position back to local space withInverseTransformPoint
.
Save MeshInspector.cs and go to MeshStudy.cs. Add the following in DoAction
:
PullOneVertex(index, localPos);
Then add the following to PullOneVertex
:
vertices[index] = newPos; //1 clonedMesh.vertices = vertices; //2 clonedMesh.RecalculateNormals(); //3
- Updates the target vertex's position.
- Assigns the updated vertices array back to the cloned mesh.
- Tells Unity to re-draw the mesh to reflect the change.
Save the script and return to Unity. Try dragging one of the dots on the cube.
It seems like some of the vertices share the same position, so when you pull only one, the other vertices stay behind and your mesh breaks. You'll learn how to fix this problem shortly. :]
Looking at the Vertices Array
Visually, a cube mesh consists of eight vertices, six sides and 12 triangles. Time to see if Unity agrees.
Go to MeshStudy.cs, and before Start
, look for a variable named vertices
. You'll see that it has the [HideInInspector]
attribute.
Temporarily comment out that attribute for a quick peek into the array:
//[HideInInspector] public Vector3[] vertices;
Note: More complicated 3D meshes can have thousands of vertices. Unity will freeze up if it tries to show all those values in the Inspector, so in general, you'll hide the array with [HideInInspector]
. You're just peeking!
Save the file, return to Unity and look at your cube. You can now see the vertices property on Mesh Study. Click on the arrow icon beside it to show the array of Vector3
elements.
You can see that the array size is 24, which means that there are definitely vertices sharing the same position! Take a moment and think about why there might be multiple vertices in the same place.
[spoiler title = "Why 24 Vertices?"]
The simplest answer is:
A cube has six sides and each side has four vertices that form a plane. 6 × 4 = 24 vertices.
There are other ways to think about this, if this is hard to grasp. But for now, just know that some meshes will have vertices that share the same position.
[/spoiler]
Since you're done peeking into the verts array, go ahead and uncomment [HideInInspector]
.
Finding All Similar Vertices
You can see that manipulating a mesh is going to take more than just moving single vertices — you have to move all the vertices for a particular point in space in order to keep the mesh together. So you're now ready to unbreak your heart, er, mesh.
In MeshStudy.cs, replace all the code inside DoAction
with:
PullSimilarVertices(index, localPos);
Go to PullSimilarVertices
and add the following:
Vector3 targetVertexPos = vertices[index]; //1 List<int> relatedVertices = FindRelatedVertices(targetVertexPos, false); //2 foreach (int i in relatedVertices) //3 { vertices[i] = newPos; } clonedMesh.vertices = vertices; //4 clonedMesh.RecalculateNormals();
- Gets the target vertex position from the
vertices
array. - Finds all the vertices that share the same position as the target vertex and puts their indices into a list.
- Loops through that list and updates the position of all related vertices.
- Assigns the updated
vertices
back toclonedMesh.vertices
, then redraws the mesh.
Save the file and return to Unity. Click and drag any of the vertices; the mesh should now retain its form without breaking.
Save the scene. You've taken the first step towards being a mesh magician!
Manipulating Meshes
Editing meshes in Unity is fun, but what if you could add some "squish" to your game by deforming meshes at runtime? Next, you'll try that out in its most basic form — pushing and pulling some predefined vertices.
Collecting the Selected Indices
You'll start by making a custom editor that lets you select the vertices to move around in real time. Open up the 02 Create Heart Mesh scene inside RW/Scenes. You'll see a red sphere in the Scene view.
Select the Sphere in the Hierarchy and look at the Heart Mesh component. This is the script that will store the verts you select.
But right now, there aren't any verts shown in the scene. So next, you're going to fix that!
Open RW/Editor/HeartMeshInspector.cs. In ShowHandle
, inside the if
statement, add the following code:
Handles.color = Color.blue; if (Handles.Button(point, handleRotation, mesh.pickSize, mesh.pickSize, Handles.DotHandleCap)) //1 { mesh.selectedIndices.Add(index); //2 }
- This makes Unity draw the vertices of the mesh as buttons, so you can click on them.
- When you click the button, it adds the selected index to the
mesh.selectedIndices
list.
Add the following code at the end of OnInspectorGUI
, after the existing if
statement:
if (GUILayout.Button("Clear Selected Vertices")) { mesh.ClearAllData(); }
This adds a custom Reset button in the Inspector. Next, you'll write the code to clear out your selection.
Save the file and open RW/Scripts/HeartMesh.cs. In ClearAllData
, add the following:
selectedIndices = new List<int>(); targetIndex = 0; targetVertex = Vector3.zero;
This clears the values in the selectedIndices
list and sets targetIndex
to zero. It also resets the targetVertex
position.
Save the file and return to Unity. Select the Sphere and look at its HeartMesh component. Make sure you've checked Is Edit Mode so you can see the mesh's vertices in the Scene view. Then click on the arrow icon next to Selected Indices to show the array.
Click on a few blue dots and watch new entries appear in Selected Indices. Try out your Clear Selected Vertices button to make sure it clears all values correctly.
Note: You have the option to show/hide the transform handle with Show Transform Handle in the custom Inspector, since it can get in the way of selecting verts. Just remember not to panic when you find the Transform handle missing from your other scenes! Be sure to switch it back on before you exit.
Deforming the Sphere Into a Heart Shape
Updating mesh vertices in real time requires three steps:
- Copy the current mesh vertices (before animation) to
modifiedVertices
. - Do calculations and update values on
modifiedVertices
. - Copy
modifiedVertices
to the current mesh on every step change and get Unity to redraw the mesh.
Go to HeartMesh.cs and add the following variables before 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;
Moving a vertex should have some influence on the vertices around it to maintain a smooth shape. These variables control that effect.
- Radius of area affected by the targeted vertex.
- The strength of the pull.
- How long the animation will run.
- Current index of the
selectedIndices
list.
In Init
, before the if
statement, add:
currentIndex = 0;
This sets currentIndex
— the first index of the selectedIndices
list — to 0 at the beginning of the game.
Still in Init
, before the closing braces of the else
statement, add:
StartDisplacement();
StartDisplacement
is what actually moves the vertices. It only runs when isEditMode
is false.
Right now, this method does nothing, so add the following to StartDisplacement
:
targetVertex = originalVertices[selectedIndices[currentIndex]]; //1 startTime = Time.time; //2 isAnimate = true;
- Singles out the
targetVertex
from theoriginalVertices
array to start the animation. Remember, each array item is a List of integer values. - Sets the start time to the current time and changes
isAnimate
totrue
.
After StartDisplacement
, create a new method called FixedUpdate
with the following code:
protected void FixedUpdate() //1 { if (!isAnimate) //2 { return; } runTime = Time.time - startTime; //3 if (runTime < duration) //4 { Vector3 targetVertexPos = meshFilter.transform.InverseTransformPoint(targetVertex); DisplaceVertices(targetVertexPos, pullValue, radiusOfEffect); } else //5 { currentIndex++; if (currentIndex < selectedIndices.Count) //6 { StartDisplacement(); } else //7 { originalMesh = GetComponent<MeshFilter>().mesh; isAnimate = false; isMeshReady = true; } } }
Here's what the code is doing:
- The
FixedUpdate
method runs on a fixed interval, meaning that it's frame rate independent. Read more about it here. - If
isAnimate
is false, it won't do anything. - Keeps track of how long the animation has been running.
- If the animation hasn't been running too long, it continues the animation by getting the world space coordinates of
targetVertex
and callingDisplaceVertices
. - Otherwise, time's up! Adds one to
currentIndex
to start processing the animation for the next selected vertex. - Checks if all the selected vertices have been processed. If not, calls
StartDisplacement
with the latest vertex. - Otherwise, you've reached the end of the list of selected vertices. This line makes a copy of the current mesh and sets
isAnimate
tofalse
to stop the animation.
Making the Vertices Move Smoothly
In DisplaceVertices
, add the following:
Vector3 currentVertexPos = Vector3.zero; float sqrRadius = radius * radius; //1 for (int i = 0; i < modifiedVertices.Length; i++) //2 { currentVertexPos = modifiedVertices[i]; float sqrMagnitude = (currentVertexPos - targetVertexPos).sqrMagnitude; //3 if (sqrMagnitude > sqrRadius) { continue; //4 } float distance = Mathf.Sqrt(sqrMagnitude); //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); modifiedVertices[i] = m.MultiplyPoint3x4(currentVertexPos); } originalMesh.vertices = modifiedVertices; //7 originalMesh.RecalculateNormals();
This code loops over every vertex in the mesh and displaces those that are close to the ones you selected in the editor. It does some math tricks to create a smooth, organic effect, like pushing your thumb into clay. You'll learn more about this later on.
Here's a closer look at what this code does:
- Gets the square of the radius.
- Loops through every vertex in the mesh.
- Finds the distance between the current vertex and the target vertex and squares it.
- If this vertex is outside the area of effect, exits the loop early and continues to the next vertex.
- Otherwise, calculates the
falloff
value based on the distance. Gaussian functions create a smooth bell curve. - Calculates how far to move based on distance, then sets the rotation (direction of displacement) based on the result. This makes the vertex move "outward", that is, directly away from the targetVertex, making it seem to puff out from the center.
- On exiting the loop, stores the updated
modifiedVertices
in the original mesh and has Unity recalculate the normals.
Save your file and return to Unity. Select the Sphere, go to the HeartMesh component, and try adding some vertices into your Selected Indices property. Turn off Is Edit mode and press Play to preview your work.
Play around with the Radius Of Effect, Pull Value and Duration settings to see different results. When you are ready, update the settings per the screenshot below.
Press Play. Did your sphere balloon into a heart shape?
Congratulations! In the next section, you will learn how to save the mesh for further use.
Saving Your Mesh in Real Time
Right now, your heart comes and goes whenever you push the Play button. If you want a love that lasts, you need a way to write the mesh to a file.
A simple way is to set up a placeholder prefab that has a 3D object as its child, then replace its mesh asset with your heart (...er, your Heart mesh) via a script.
In the Project view, find Prefabs/CustomHeart. Double-click the prefab to open it in Prefab Editing mode.
Click on the Arrow icon to expand its contents in the Hierarchy and select Child. This is where you'll store your generated mesh.
Exit prefab editing mode and open HeartMeshInspector.cs. At the end of OnInspectorGUI
, before the closing braces, add the following:
if (!mesh.isEditMode && mesh.isMeshReady) { string path = "Assets/RW/Prefabs/CustomHeart.prefab"; //1 if (GUILayout.Button("Save Mesh")) { mesh.isMeshReady = false; Object prefabToInstantiate = AssetDatabase.LoadAssetAtPath(path, typeof(GameObject)); //2 Object referencePrefab = AssetDatabase.LoadAssetAtPath (path, typeof(GameObject)); GameObject gameObj = (GameObject)PrefabUtility.InstantiatePrefab(prefabToInstantiate); Mesh prefabMesh = (Mesh)AssetDatabase.LoadAssetAtPath(path, typeof(Mesh)); //3 if (!prefabMesh) { prefabMesh = new Mesh(); AssetDatabase.AddObjectToAsset(prefabMesh, path); } else { prefabMesh.Clear(); } prefabMesh = mesh.SaveMesh(prefabMesh); //4 AssetDatabase.AddObjectToAsset(prefabMesh, path); gameObj.GetComponentInChildren<MeshFilter>().mesh = prefabMesh; //5 PrefabUtility.SaveAsPrefabAsset(gameObj, path); //6 Object.DestroyImmediate(gameObj); //7 } }
Here's what the code does:
- Stores the CustomHeart prefab object asset path, which you need to be able to write to the file.
- Creates two objects from the CustomHeart prefab, one as a GameObject and the other as a reference.
- Creates an instance of the mesh asset
prefabMesh
from CustomHeart. If it finds the asset, it clears its data; otherwise, it creates a new empty mesh. - Updates
prefabMesh
with new mesh data and associates it with the CustomHeart asset. - Updates the GameObject's mesh asset with
prefabMesh
. - Creates a Prefab Asset at the given path from the given
gameObj
, including any children in the scene. This replaces whatever was in the CustomHeart prefab. - Destroys
gameObj
immediately.
Save your file and go to HeartMesh.cs. Replace the body of SaveMesh
with the following:
Mesh nMesh = new Mesh(); nMesh.name = "HeartMesh"; nMesh.vertices = originalMesh.vertices; nMesh.triangles = originalMesh.triangles; nMesh.normals = originalMesh.normals; return nMesh;
This will return a mesh asset based on the heart-shaped mesh.
Save the file and return to Unity. Press Play. When the animation ends, a Save Mesh button will appear in the Inspector. Click on the button to save your new mesh, then stop the player.
Find Prefabs/CustomHeart again in the Project view and open it in Prefab Editing mode. You will see a brand spanking new heart-shaped mesh has been saved in your prefab!
Putting It All Together
In the previous section, you learned how to modify a mesh by selecting individual vertices. While this is cool, you can do more interesting things if you know how to select vertices procedurally.
In the previous scene, DisplaceVertices
uses a Gaussian falloff formula to determine how much to "pull" each vertex within the radius of the effect. But there are other mathematical functions you can use to calculate the 'fall off' point; that is, where the pull strength starts decaying. Each function produces a different shape:
In this section, you will learn how to manipulate vertices using a calculated curve.
Based on the principle that velocity equals distance divided by time (v=(d/t)), you can determine the vector's position by referencing its distance divided by its time factor.
Using the Curve Method
Save your current scene, then open 03 Customize Heart Mesh from the Scenes folder.
Find the instance of your CustomHeart prefab in the hierarchy and click on the arrow icon next to it to expand its content. Select the Child object.
View its properties in the Inspector. You'll see Mesh Filter with the Heart Mesh asset. Attach Custom Heart to Child. The asset should now change from HeartMesh to clone.
Open CustomHeart.cs and add the following right above Start
:
public enum CurveType { Curve1, Curve2 } public CurveType curveType; Curve curve;
This creates a public enum named CurveType
and makes it available in the Inspector.
Go to CurveType1
and add the following:
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
What's going on?
- The basic curve consists of three points. This code sets and plots the points for the first curve.
- Generates the first curve with
Curve
and assigns its values tocurve
. You can set the last parameter totrue
to draw the curve as a preview.
Now go to CurveType2
and add the following:
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
This works much like CurveType1
.
- Sets and plots the points for the second curve.
- Generates the second curve with the
Curve
method and assigns its values tocurve
.
In StartDisplacement
, before the closing braces, add the following:
if (curveType == CurveType.Curve1) { CurveType1(); } else if (curveType == CurveType.Curve2) { CurveType2(); }
This will generate different curves depending on what you select as the Curve Type in the Custom Heart component.
In DisplaceVertices
, inside the for
loop, before the closing braces, add the following:
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); modifiedVertices[i] = m.MultiplyPoint3x4(modifiedVertices[i]);
This might look familiar — it's much like the code you added to HeartMesh
.
- Gets the curve's position at the given
distance
and multiplies itsy
value byforce
to getincrement
. - Creates a new
Vector3
calledtranslate
to store the new position for the current vertex and applies its Transform accordingly.
Save the file and return to Unity. Check out the properties in Custom Heart on the Child GameObject.
In the Edit Type drop-down menu, you can now select Add Indices or Remove Indices to update your list of vertices. Select None to exit Edit mode, then click Play see the results. Experiment with different settings and vertex selections.
To see an example of the different curve types, enter these values:
Set Curve Type to Curve1, check that Edit Type is set to None and press Play.
You should see how the mesh fans out into a pattern. Move the model around to its side-view so you can see the shape this curve produces. Exit Play and try it again with Curve 2 to compare the results of both curve types:
That's it! You can click Clear Selected Vertices to reset the Selected Indices and experiment with your own patterns. Don't forget that there are several factors that will affect the final shape of the mesh:
- The size of the radius.
- The spread of vertices within the area.
- The pattern position of the selected vertices.
- The method that you choose for displacement.
Where to Go From Here?
You can get all of the files for the final project by using the Download Materials button at the top and bottom of this tutorial.
Don't stop here! Try out more advanced techniques with Procedural Maze Generation.
I hope you have enjoyed this tutorial and found the information useful. Special credit to Jasper Flick from Catlike Coding for his great tutorials, which helped me put together the demos for this project.
Feel free to join the discussion forum below for any questions or comments!
Source: https://www.raywenderlich.com/3169311-runtime-mesh-manipulation-with-unity
Posting Komentar untuk "Worldspace Continue if Mouse Off Screen Unity"