Nubarron: The Adventure of a Procedural Lightning


 

Hello, i’m Juan Figueroa, also known as Pravus. I’m one of Nastycloud’s programmers and i’m here to show you the process we went through to make the procedural lightning we have right now in Nubarron: The Adventure Of An Unlucky Gnome.

Our methods are by no mean perfect and we know there are ways to improve the lightning’s performance, at the final part of this post we’ll explore some of them. Also if you come up with some improvement please don’t hesitate to tell us!

So, let me start by showing you the lightning we are making and how it works. It hits downwards at a configurable rate and triggers an event on every object it strikes.

thunder-2 thunder-1 thunder-3

thunder-run

Part 1: Research

As with anything new you need to implement, the first thing we did was research on real lightnings and other procedural lightning implementations.

In the images below you can see that lightnings have what we could identify as a “main branch” and other lesser branches, as well as an “electrical glow” on them.

thunder-example thunder-example2 thunder-example3

So, at least initially, we needed to be able to render a vertical line with multiple configurable points in it to position them at a randomly generated (capped) distance from each other every time we  executed a lightning in our game. Also we needed to have at least 2 little branches with the same logic to position them randomly on the main branch.

Luckily a guy called Michael Hoffman had already been experimenting with procedural lightnings in XNA and had created a post explaining his methods. Although we were using a different technology, his post helped us find our way faster into the implementation.

Part 2: Implementation (Unity3D)

To make Nubarron: The Adventure Of An Unlucky Gnome we are using Unity3D and the native component we found that fits our needs is the Line Renderer which renders a configurable line through a list of given positions in 3D space.

Line Renderer with equally positioned vertices

line-renderer-explained

Line Renderer with randomized vertices

line-renderer-randomized-explained

By using this concept we proceeded to make a script (known as behaviour in Unity3D) for our line renderer, which i will provide at the end of this post.

So let’s get started, let me introduce you to the most important attributes and methods of the class we created.

Behaviour Attributes

Important attributes the class in our script has:

  • A configurable (public) first vertex for the line renderer (firstVertexPosition)
  • A configurable (public) last vertex for the line renderer (lastVertexPosition)
  • An array of the vertices that will go into the line renderer (vertices)

By being able to set the firstVertexPosition and lastVertexPosition attributes we can make our lightning start and end at specific positions in 3D space (in Nubarron’s normal scenario it would start at the cloud and end at a “targeted point” below the cloud).

The vertices array in our class is to keep track and manipulate the vertices that will end up in our line renderer.

Part of the code (Attributes)

public class RecursiveLightning : MonoBehaviour {
	public int vertexCount = 17;
	public Vector3[] vertices;
	public Vector3 firstVertexPosition;
	public Vector3 lastVertexPosition;
	public float fadeOutTime = 0.3f;
	public bool strikeOnStart = false;
	public bool fadeOutAfterStrike = true;
	public RecursiveLightning leftBranch = null;
	public RecursiveLightning rightBranch = null;
	LineRenderer lineRenderer;
	int leftBranchVertex = -1;
	int rightBranchVertex = -1;

Unity Editor Attributes Configuration

RecursiveLightning-UnityEditor

Behaviour method StrikeLightning()

The most used method on our class is the StrikeLightning() method. Ultimately it does what i’m sure you are guessing it does, however to do that, a series of tasks are performed first:

  1. A random vertex is chosen from the vertices array to be the starting point of the left branch
  2. A random vertex is chosen from the vertices array to be the starting point of the right branch (it can even be the vertex from 1.)
  3. The vertices array is initialized
  4. The first and last elements of the vertices array are set with firstVertexPosition and lastVertexPosition values respectively
  5. The line renderer behaviour is enabled
  6. The first and last vertices of the line renderer’s positions array are set with firstVertexPosition and lastVertexPosition values respectively
  7. The rest of the line renderer vertices are recursively and binarily positioned (with a little random tweaking on their position). When processing the 2 vertices chosen to be the starting position for the left and right branches, an ending position is configurated for both branches and a StrikeLightning() is called on them.
  8. Queue some method to disable the line renderer (make the lightning disappear)

Part of the code (StrikeLightning() method)

	public void StrikeLightning(){
		if(!lineRenderer.enabled){
			if(leftBranch){
				leftBranchVertex = Random.Range(0, vertexCount - 1);
			}

			if(rightBranch){
				rightBranchVertex = Random.Range(0, vertexCount - 1);
			}

			vertices = new Vector3[vertexCount];

			vertices[0] = firstVertexPosition;
			vertices[vertexCount -1] = lastVertexPosition;

			lineRenderer.enabled = true;
			InsertFirstAndLastNode();
			InsertNodeBetween(0, vertexCount-1);

			if(fadeOutAfterStrike)
				FadeOut();
		}
	}

Behaviour method InsertVertexBetween(int start, int end)

Okay, so now we are getting deeper, this is the method that recursively and binarily positions the diferent vertex along the line renderer.

The following image is a representation of only the first 2 vertices processed to be positioned (remember that the first and last vertex are positioned outside this method and because of that we don’t process them here)

line-renderer-binary-insertion

So, basically what this method does is:

  1. Get the vertex number (index) of the current vertex, the one we will position. As it’s done using the binary method the vertex number is calculated as (start + end) /2
  2. If the current vertex number is different than the start variable value (this is what limits the recursiveness), the current vertex position is created and assigned (InsertNodeInLineRenderer() is what actually gets the vertex position value into the line renderer, remember that our array vertices is a model of the vertices that ultimately go into the line renderer). After this, the method gets called again twice but this time sending the start variable and end variable along with the current vertex number variable to position the above and below vertices.
  3. Check if the current vertex has been chosen to be the starting vertex of the left and/or right branch and if that’s the case, call a StrikeLightning on that branch object

Part of the code (InsertVertexBetween() method)

 void InsertVertexBetween(int start, int end){
     int currentVertexNumber = (start + end) /2;

     if(currentVertexNumber != start){
          vertices[currentVertexNumber] = (vertices[start] + vertices[end]) /2 +
                                          new Vector3(
                                                      Random.Range(-0.1f, 0.1f),
                                                      Random.Range(-0.1f, 0.1f),
                                                      0
                                                     );

         InsertNodeInLineRenderer(currentVertexNumber, vertices[currentVertexNumber]);

         InsertVertexBetween(start, currentVertexNumber);
         InsertVertexBetween(currentVertexNumber, end);
     }

     if(leftBranchVertex == currentVertexNumber){
         ConfigureBranch(
                         leftBranch,
                         vertices[currentVertexNumber],
                         vertices[currentVertexNumber] + new Vector3(-0.5f,-1f,0)
                        );
         leftBranch.StrikeLightning();
     }

     if(rightBranchVertex == currentVertexNumber){
         ConfigureBranch(
                         rightBranch,
                         vertices[currentVertexNumber],
                         vertices[currentVertexNumber] + new Vector3(0.5f,-1f,0)
                        );
         rightBranch.StrikeLightning();
     }
 }

The Remaining Code

There are other methods on this class that are way easier to comprehend, you can check them out if you want, after downloading the script at the end of this post.

Part 3: “Looking Good”

Okay, by now you probably have been skipping the text and code and have only gazed at the images and if you have at least a bit of visual/art criteria you will agree with me that the line renderer lightning we made doesn’t look very nice haha.

Well, there are 4 things that make this lightning look good and powerful in Nubarron: The Adventure Of An Unlucky Gnome and those things are:

Timing

thunder-run3-numbered

Our lightning is being timed to disappear after 0.08 seconds of appearing, so the player doesn’t have a chance to stop and check out every milimeter of it (that and the fact that if you stop for a long time Gnome gets electro-exploded to pieces).
Also, in Nubarron, the constant lightning being shot by Cloud is a sequence of 3 consecutive lightnings (remember that each lightning strike called makes a new procedural lightning) that gives that feeling of a flickering evil lightning.

Glow

thunder-run2

To achieve the glowy effect we use a mix of 2 things:

  • A Material with an additive blending mode shader (Particles/Additive) for the line renderer
  • A Bloom post processing effect on the Camera (unity comes with a bloom behaviour to be used)

Size

As you may have noticed, our main character doesn’t take too much space in the scene. Not only because he is a little unlucky gnome but because there’s a certain feeling we wanted to achieve on the player and also a need to show the whole environment art that our excellent artist has created. However, being so little also contributes to the fast paced visualization of a lightning that strikes as fast as a hungry forest predator and as deadly as a…. well…. as a hungry forest predator.

running-thunder-screen

Surrounding Effects

When a lightning strikes, we use light, sound, camera behaviour (like the bloom behaviour) and camera position(e.g.: camera shake) animations. This rocks the player’s world briefly every time Cloud shoots a lightning and more intensely when the lightning is used in something more meaningful like turning on a machine.

turning-machine

Part 4: Thunderstruck!

After creating the lightning as you now know it we designed 2 more classes to make it functional according to the main mechanics of Nubarron: The Adventure Of An Unlucky Gnome.

Timer

thunder-run-numbered

We created a LightningTimer class that manages the constant lightning striking with some configurable values e.g.: the  constant strike delay.

Targeter

bridge-thunder

This is an interesting class: it controls the lightning targetting. This means that the LightningTargeter class is the one that targets points or objects around space so that when a lightning is struck, it hits the desired target.

Basically it casts a ray pointing to the desired target and if it collides with anything there (be it the target itself or an obstacle in the middle), after striking a lightning there it sends a message into the object the ray collided with. (in Unity you do this with a SendMessage(string methodToBeCalledOnObject) call on the desired object, which triggers the specified method/event on it)

Using Timer and Targeter

Right now the logic behind our lightningstrike is: target a point (Targeter) + wait some fractions of a second (Timer) + strike lightning (procedural lightning). This way we let Gnome escape death when he runs and the user learn the lightning strike delay in every situation.

Part 5: Improvements

Who needs improvements when you can strike the hell out of everything on the scene? I’m kidding, these behaviours we’ve created were born in the Game Jam we did at Nastycloud to start remaking Nubarron: The Adventure Of An Unlucky Gnome, so as you can probably see in the code, some things were done in a rather hasty way, although we put some effort in the important aspects of the code i’ve explained above (Part 2: Implementation).

These are improvements i’ve detected that we will eventually implement:

  • One time vertices initialization: Right now we are initializating the vertices array every time the StrikeLightning() is called, but it would be better to do this just once, at the scene start and then just reaccomodate the vertices in each StrikeLightning()
  • Chain Reaction: create a behaviour that messages other colliding objects about the lightning strike received to trigger something on them. e.g.: If we have a tall antenna with a metal plate lying at its side and the metal plate touches a lake at its other end, after striking a lightning on the antenna, those 3 objects could be electrified
  • A more explicit pre-lightning warning: the warning we have implemented is part of the scene illumination that changes when the lightning strikes, so it shows that the lightning is about to strike but it doesn’t actually show where. So we are working on some kind of electric ilumination/flicker on the ground at the targeted point to be struck

Thank You!

Thanks for reading, i hope this wasn’t boring or confusing, if you have any questions don’t hesitate to reach me at pravus@nastycloud.com

And if you enjoyed the post please share it and be sure to check out our Kickstarter and Greenlight campaigns

Downloads

[ProceduralLightningScripts]

Leave a comment

Your email address will not be published. Required fields are marked *