Lightning Arc Creator
Procedural mesh generation and maniuplation
The above image is a sample of what the below script can be used to create. Developers can place the script on an object in Unity, and specify another object as the end point, and the script will create a lightning arc style effect out of a procedurally generated mesh. The number of points, the distance between the points, and the speed at which it animates are all easily settable within the Inspector. Upon moving either of the two points assigned as the start and end of the bolt, it will regenerate the mesh with the same settings and restart the animation process as well.

The code can be seen below, or downloda the file here.

CreateBolt.cs

```
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CreateBolt : MonoBehaviour {
public Transform endPoint;
MeshFilter filter;
MeshRenderer rend;
public Material mat;
public int totalMidPoints = 3;
bool boltMade = false;
public float minWidth = 0.25f;
public float maxWidth = 0.5f;
public float animTime = 0.15f;
Vector3 previousEnd, previousStart;
Vector3 perpendicularLine;
// Use this for initialization
void Start () {
filter = gameObject.AddComponent<MeshFilter>();
this.transform.hasChanged = false;
endPoint.transform.hasChanged = false;
previousEnd = endPoint.position;
previousStart = transform.position;
rend = gameObject.AddComponent<MeshRenderer>();
}
// Update is called once per frame
void Update () {
if (Input.GetKeyDown(KeyCode.Space) && !boltMade)
{
CreateLightning();
StartCoroutine(AnimateBolt());
boltMade = true;
}
if (this.transform.hasChanged || endPoint.transform.hasChanged) //Detect if either start or end has moved, and regenerated the bolt based on this.
{
UpdateMeshLocation();
this.transform.hasChanged = false;
endPoint.hasChanged = false;
}
}
public void UpdateMeshLocation()
{
StopAllCoroutines();
filter.mesh.Clear();
CreateLightning();
StartCoroutine(AnimateBolt());
}
public IEnumerator AnimateBolt()
{
while (true)
{
//Define the distance that you want the pairs to move.
//We only do this for the midpoints as we don't want to move the end values at all to maintain the illusion they touch the points.
List<Vector3> movementPairs = new List<Vector3>();
for (int i = 0; i < totalMidPoints; i++)
{
float dist = Random.Range(-1f, 1f);
Vector3 pair = dist * perpendicularLine;
movementPairs.Add(pair);
}
float timer = 0f;
//Grab the meshs vertices and store them in a local value.
List<Vector3> vertPairs = new List<Vector3>();
for (int i = 0; i < filter.mesh.vertices.Length; i++)
{
vertPairs.Add(filter.mesh.vertices[i]);
}
//Grab copies for making the points move
List<Vector3> newPairs = new List<Vector3>();
for (int i = 0; i < filter.mesh.vertices.Length; i++)
{
Vector3 newPos = filter.mesh.vertices[i];
newPairs.Add(newPos);
}
//Now we need to define where we want to move these points, so we create a variable to track which pair of verts we're manipulating.
//Then we loop through all the new pairs we want, again skipping the first and last points, and make the adjustments.
int movementPair = 0;
for (int i = 2; i < newPairs.Count-2; i += 2)
{
Vector3 vect1 = newPairs[i];
Vector3 vect2 = newPairs[i + 1];
vect1 += movementPairs[movementPair];
vect2 += movementPairs[movementPair];
newPairs[i] = vect1;
newPairs[i + 1] = vect2;
movementPair += 1;
}
//We zero out our timer, and begin moving these points.
timer = 0f;
while (timer < animTime)
{
timer += Time.deltaTime;
yield return null;
List<Vector3> currentPairs = new List<Vector3>();
foreach(Vector3 p in vertPairs)
{
Vector3 v = new Vector3();
v.x = p.x;
v.y = p.y;
v.z = p.z;
currentPairs.Add(v);
}
//grab each pair of points in the middle, and move them a percentage based on the amount of time that passed.
for(int i = 2; i < currentPairs.Count-2; i += 2)
{
int x = i / 2;
currentPairs[i] = Vector3.Lerp(vertPairs[i], newPairs[i], timer / animTime);
currentPairs[i + 1] = Vector3.Lerp(vertPairs[i+1], newPairs[i + 1], timer / animTime);
}
filter.mesh.SetVertices(currentPairs);
}
timer = 0f;
//To keep them close to their original points, we move them back to their start, and then begin the whole loop again.
while (timer < animTime)
{
timer += Time.deltaTime;
yield return null;
List<Vector3> currentPairs = new List<Vector3>();
foreach (Vector3 p in vertPairs)
{
Vector3 v = new Vector3();
v.x = p.x;
v.y = p.y;
v.z = p.z;
currentPairs.Add(v);
}
for (int i = 2; i < currentPairs.Count-2; i += 2)
{
int x = i / 2;
currentPairs[i] = Vector3.Lerp(newPairs[i], vertPairs[i], timer / animTime);
currentPairs[i +1] = Vector3.Lerp(newPairs[i + 1], vertPairs[i +1], timer / animTime);
}
filter.mesh.SetVertices(currentPairs);
}
}
}
public Vector3 GetPointOnLine(Vector3 start, Vector3 line, float percent, float lengthOfLine)
{
return start + ((line*lengthOfLine) * percent);
}
public void CreateLightning()
{
//Vertices are definied in local space, relative to the gameobject they are being created in.
//InverseTransformPoint gets the world position of the end point in local coordinates
Vector3 relativeStart = Vector3.zero;
Vector3 relativeEnd = transform.InverseTransformPoint(endPoint.position);
//The LENGTH and LINE values are used to help us define new points on the line between our start and end to create a new bolt of lightning.
//We pass them to the GetPointOnLine() function, to calculate where to place new vertices.
float length = Vector3.Distance(relativeStart, relativeEnd);
Vector3 line = relativeEnd - relativeStart;
line = line.normalized;
perpendicularLine = Vector3.Cross(line, Vector3.one);
Debug.Log(line + " " + perpendicularLine);
//Since we want our start and end points to have a smaller scale, we create them separately from the rest of the points.
//Using the GetPointOnLine() function, we get the start and end points of the line, and then move the y value of the top and bottom values just slightly apart.
Vector3 startBottom = GetPointOnLine(relativeStart, line, 0, length);
Vector3 startTop = startBottom;
startTop.y += 0.1f;
startBottom.y -= 0.1f;
Vector3 endBottom = GetPointOnLine(relativeStart, line, 1, length);
Vector3 endTop = endBottom;
endTop.y += 0.1f;
endBottom.y -= 0.1f;
List<Vector3> midPoints = new List<Vector3>();
float percentPerPoint = 1f / ((float)totalMidPoints+1f); //Calculate how far apart each point needs to be to create an even distribution.
//This loop creates all of the individual points required for the bolt, in between the start and end points.
//We use the above percentPerPoint value to find where the current position on the line is.
//Then we move the top and bottom a random amount away from each other
//And finally we add these values to a list of points.
//It's important to remember what order you put the points in the list. For our purposes, we did top point first, bottom point second.
for(int i = 0; i < totalMidPoints; i++)
{
Vector3 top = GetPointOnLine(relativeStart, line, (1 + i) * percentPerPoint, length);
Vector3 bottom = top;
bottom -= Random.Range(minWidth, maxWidth) * perpendicularLine;
top += Random.Range(minWidth, maxWidth) * perpendicularLine;
midPoints.Add(top);
midPoints.Add(bottom);
}
//Grab the mesh from our meshfilter, and assign the vertices we've created to it, starting with the two start points.
//Then loop through the midpoints and add them, and finally ad the two end points.
Mesh mesh = filter.mesh;
List<Vector3> verts = new List<Vector3>{
startTop, startBottom
};
foreach(Vector3 vect in midPoints)
{
verts.Add(vect);
}
verts.Add(endTop);
verts.Add(endBottom);
List<int> tris = new List<int>(
new int[]{0,2,1,
1,2,3
}
);
//Creating tris is a little tricky.
//If you imaging the face having a line drawn out of the center of it, it's normal line, your tri needs to be defined in a counter clockwise direction around this line.
//For our purposes, the below function creates quads between two pairs of top and bottom points.
for(int i = 0; i < totalMidPoints*2; i+=2)
{
int x = i + 3;
tris.Add(x);
tris.Add(x - 1);
tris.Add(x + 1);
tris.Add(x);
tris.Add(x + 1);
tris.Add(x + 2);
}
//Uvs are defined in 2D space, along the X and Y axis.
//We can grab our Y value by checking if the point is an even number, or a top point, or an odd number, a bottom point.
//getting the X value is a little more complicated, as we have to normalize the X value to be between 0 and 1
//We grab it's relative distance from the start, by dividing by two, then we divide that by the total possible distance
//For example, point 3 would fist come out to a value of 1, which we then would divide by totalMidPoints+2, which in our default cause would come out to 1/5, or 0.2
List<Vector2> uvs = new List<Vector2>();
for (int i = 0; i < verts.Count; i++)
{
float yval = i % 2 == 0 ? 0.75f : 0.25f;
float xval = i / 2;
xval = xval / ((float)totalMidPoints + 2f);
uvs.Add(new Vector2(xval, yval));
}
//Finally we set these values in the mesh filter, along with our predefined material
filter.mesh.SetVertices(verts);
filter.mesh.SetTriangles(tris, 0);
filter.mesh.SetUVs(0, uvs);
rend.material = mat;
}
}
```