Electricity in Unity — Part 1 of 3
I first tried making lightning bolts in Unity a few months back using prefabs. It was a really good learning experience, but ultimately I tossed it aside. Using prefabs like that was too cumbersome, and ultimately limiting. I tried it again with Unity’s LineRenderer component, and the results turned out much better. I ended up building three effects in Unity, all covered in this series:
- Electrical arcs
- Jacob’s Ladder
- Lightning
However, before we get too far into this, let’s talk about how to pivot a line in some random direction. This will be essential for the first and third effects.
Vector Perturbation
A somewhat random appearance is required for selling these effects. To make a series of line segments follow a semi-random path, I will use vectors that I perturb. Once I do that, adding the first endpoint to the perturbed vector gives the second endpoint, and so on. I looked at some code online to perturb a vector, but decided on my own general-purpose solution, and ended up with two different approaches.
First Approach
The vector will be perturbed off of the original direction by a percentage of the vector’s magnitude.
In practice, given this vector, drawn in white with a green sphere at the origin:
Perturbing it away from the original by 10% of its length gives this, for example:
Running this 50 times might clarify:
Visual Breakdown
First we get the cross product of the vector with some other vector (I used up or right, depending). This gives us a vector perpendicular to the original, which we then scale by the percent (10% here):
Next we rotate that cross product around the original by some angle. Quaternion.AngleAxis does this for me. I used 30 degrees here, and the result is shown in cyan:
Now, if you took that green point in space, and added the original white vector and the cyan vector to it, it would be out where you see this red dot:
So the perturbed vector is just the white and cyan vectors added together, shown here in green:
This approach is more complicated than it needs to be. It is also wrong. It’s a little difficult to see here, but the ends of the perturbed vectors are coplanar with the original’s endpoint:
That means they are longer than the original. They are a little more perturbed than I had planned.
Second Approach
This is a little simpler, and more direct:
I get the cross product as before. I chose 30 degrees again for illustrative purposes. The yellow line is the cross product of the direction vector (shown in white) and green (up) vector:
Note that the degenerate case of passing in the up vector as direction is caught, and the right vector is used to get the cross. You could just use the right vector as the cross in the degenerate case, since it is technically at a right angle.
This cross product is then used to rotate the first vector out by the specified angle, giving us rotatedDirection in cyan:
At this point, all three vectors are coplanar. Note that rotatedDirection, in cyan, is not drawn to scale here. We perturb it once again by rotating this around the original by a random angle:
Note that in this version, the original vector’s length is preserved:
This makes sense, since the original vector was used from the beginning.
This length issue would probably not cause issues with arcs and lightning bolts, but if you intend to use this elsewhere, take the time to do it right.
For our lightning bolts and arcs, I will use a random between a min and max angle for the original rotate against the cross product.
OK that’s enough vector math for now. Let’s perturb some vectors to make something fun.
The Arc Scene
For starters, I am generating a fairly simple arc. I plan on having the arc originate from one point, and go to one of a set of receptor points on the ground.
I created an object for the origin point:
I put an empty GameObject called point at the bottom of the sphere for the arc origin:
A cylinder and sphere (called Knob) inside an empty GameObject serve as the receptors:
The center of the sphere is the end point of the arc.
I set up a scene for VR with single-action teleport, floated the origin up high, and put two receptors on the ground:
Basic Testing Script
I put an empty GameObject in the scene and attached the following script to it:
I dragged the point that I placed at the bottom of the big sphere into Origin, and the Knob (sphere) from the receptor on the right into the Transform fields. This line allows me to play this in unity without doing a build and run on my headset:
Keyboard.current.spaceKey.wasPressedThisFrame
My laptop’s graphics card isn’t good enough for me to play things directly — I have to do a Build and Run, and that takes some time.
The play test/sanity check did just what I wanted:
My game view, and the width of my line renderer segment from origin to the right receptor, is good enough for now.
First Strike Strategy
The plan is to create several segments traveling the space between the origin and receptor. Each of these segments will start off with one point where the last one left off, and its other point will be almost pointing at the end point. The end result would be something like this:
Not the best plan for an electrical arc, but it’s a start. Once I get this simple method working, I will attempt different styles that might look better.
New Strike
The new code is:
Result:
Okay-ish I guess.
Animated Arcs
My first thought about animating the strikes was to save the coords, and scale the output of Random.InsideUnitSphere to move them, then redraw the bolt. I might end up trying that later, but for now I will just define a shorter strike duration and run it a few times. In practice, the results are less than spectacular. The arcs, with their disjointed appearance, just don’t look right. When animated, they look even worse.
Better Arcs
I didn’t love the haphazard results I was getting with the line segments.
My next attempt was to take the points by threes and draw Bezier curves:
In this scenario, P2 in one segment becomes P0 in the next one.
Each segment will get its own line renderer. Storing a line renderer component in an empty GameObject stored as a prefab is the simplest way to generate an arbitrary number of line renderers.
This approach was also disappointing because you still get a discontinuity where the bezier curves meet each other.
Continuous Arcs
LlamAcademy has a good tutorial on Bezier curves here:
https://www.youtube.com/watch?v=u0yZb1xIyLA
This approach takes a LineRenderer of arbitrary length and creates a Bezier curve out of each segment. This produced the most believable arcs in my opinion.
One of the variables, smoothingLength, controls how much arc the Bezier curves get between their points. I randomized this variable and re-calculated the set of curves for each frame as animation.
The code for this is too lengthy to put here, and it’s available through LlamAcademy anyway, but interesting methods are:
Update:
The arc is started with spacebar, or the right trigger in VR. We have a variable for flash duration, and it is used here
GenerateCoords:
These coordinates define the turning points in the arc, which the Bezier curves will be built from.
RedrawArc:
This is called on each frame, and uses the coords defined for this strike and rebuilds the arc path each time.
The rest of the code is available in the GitLab project at https://gitlab.com/pkrouse_group/electricity.
The results are okay, but not as dramatic as I had hoped:
Some of the segments are short, and the angles might be a little too severe for them, making it look like there is a discontinuity in a couple of places.
In part 2 of this series, we get into the Jacob’s Ladder…