Runtime Mesh Manipulation With Unity

One of the benefits of using Unity as your game development platform is its powerful 3D engine. In this tutorial, you’ll get an introduction to the world of 3D objects and mesh manipulation. By Sean Duffy.

4.8 (31) · 2 Reviews

Download materials
Save for later
Share
You are currently viewing page 3 of 5 of this article. Click here to view the first page.

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();
  1. Gets the target vertex position from the vertices array.
  2. Finds all the vertices that share the same position as the target vertex and puts their indices into a list.
  3. Loops through that list and updates the position of all related vertices.
  4. Assigns the updated vertices back to clonedMesh.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.

Gif of a Cube mesh that retains 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.

No math...

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.

Screenshot of the red sphere that you'll transform into a heart.

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
}
  1. This makes Unity draw the vertices of the mesh as buttons, so you can click on them.
  2. 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.

Gif showing how Selected Indices works.

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:

  1. Copy the current mesh vertices (before animation) to modifiedVertices.
  2. Do calculations and update values on modifiedVertices.
  3. 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.

  1. Radius of area affected by the targeted vertex.
  2. The strength of the pull.
  3. How long the animation will run.
  4. 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;
  1. Singles out the targetVertex from the originalVertices array to start the animation. Remember, each array item is a List of integer values.
  2. Sets the start time to the current time and changes isAnimate to true.

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:

  1. The FixedUpdate method runs on a fixed interval, meaning that it's frame rate independent. Read more about it here.
  2. If isAnimate is false, it won't do anything.
  3. Keeps track of how long the animation has been running.
  4. If the animation hasn't been running too long, it continues the animation by getting the world space coordinates of targetVertex and calling DisplaceVertices.
  5. Otherwise, time's up! Adds one to currentIndex to start processing the animation for the next selected vertex.
  6. Checks if all the selected vertices have been processed. If not, calls StartDisplacement with the latest vertex.
  7. Otherwise, you've reached the end of the list of selected vertices. This line makes a copy of the current mesh and sets isAnimate to false to stop the animation.