Quantcast
Channel: Envato Tuts+ Game Development
Viewing all articles
Browse latest Browse all 728

How to Generate Shockingly Good 2D Lightning Effects in Unity (JavaScript)

$
0
0

There are plenty of uses for lightning effects in games, from background ambiance during a storm to the devastating lightning attacks of a sorcerer. In this tutorial, I'll explain how to programmatically generate awesome 2D lightning effects: bolts, branches, and even text.

This tutorial is written specifically for Unity, with all code snippets in JavaScript. The same tutorial is also available with C# code. If you don't use Unity, take a look at this platform-agnostic version of the same tutorial; it's written for XNA, but you should be able to use the same techniques and concepts in any gamedev engine and platform.

Basic Setup

To get started, you'll need to create a new 2D project in Unity. Name it whatever you'd like. In Unity, create four folders: Materials,Prefabs,Scripts,and Sprites.

Next, click the Main Camera and make sure that its Projection is set to Orthographic. Set the camera's Size to 10.

Right-click on the Materials folder and select Create > Material. Rename it to Additive. Select this material and change its Shader to Particles > Additive. This will help your lightning "pop" later on.

Step 1: Draw a Glowing Line

The basic building block we need to make lightning from is a line segment. Start by opening your favourite image editing software and drawing a straight line of lightning with a glow effect. Here's what mine looks like:

We want to draw lines of different lengths, so we will cut the line segment into three pieces as shown below (crop your image as necessary). This will allow us to stretch the middle segment to any length we like. Since we are going to be stretching the middle segment, we can save it as only a single pixel thick. Also, as the left and right pieces are mirror images of each other, we only need to save one of them; we can flip it in the code.

Drag your image files onto the Sprites folder in the Project panel. This will import the image files into the Unity project. Click on the sprites to view them in the Inspector panel. Make sure the Texture Type is set to Sprite(2D \ uGUI), and set the Packing Tag to Line.

The Packing Tag will help Unity save on draw calls when drawing our lightning, so make sure you give both sprites the same Packing Tag or else it won't improve performance.

Now, let's declare a new class to handle drawing line segments:

#pragma strict

class LineJS extends MonoBehaviour
{
    //Start
    public var A: Vector2;
    
    //End
    public var B: Vector2;
    
    //Thickness of line
    public var Thickness: float;
    
    //Children that contain the pieces that make up the line
    public var StartCapChild : GameObject;
    public var LineChild : GameObject;
    public var EndCapChild : GameObject;
    
    //Create a new line
    public function Line(a : Vector2, b : Vector2, thickness : float)
    {
        A = a;
        B = b;
        Thickness = thickness;
    }
    
    //Used to set the color of the line
    public function SetColor(color : Color)
    {
        StartCapChild.GetComponent(SpriteRenderer).color = color;
        LineChild.GetComponent(SpriteRenderer).color = color;
        EndCapChild.GetComponent(SpriteRenderer).color = color;
    }
    
    //...
}

A and B are the line's endpoints. By scaling and rotating the pieces of the line, we can draw a line of any thickness, length, and orientation.

Add the following Draw() method to the bottom of the LineJS class:

//Will actually draw the line
public function Draw()
{
    var difference : Vector2 = B - A;
    var rotation : float = Mathf.Atan2(difference.y, difference.x) * Mathf.Rad2Deg;
    //Set the scale of the line to reflect length and thickness
    LineChild.transform.localScale = new Vector3(100 * (difference.magnitude / LineChild.GetComponent(SpriteRenderer).sprite.rect.width), 
                                                 Thickness, 
                                                 LineChild.transform.localScale.z);
    StartCapChild.transform.localScale = new Vector3(StartCapChild.transform.localScale.x, 
                                                     Thickness, 
                                                     StartCapChild.transform.localScale.z);
    EndCapChild.transform.localScale = new Vector3(EndCapChild.transform.localScale.x, 
                                                   Thickness, 
                                                   EndCapChild.transform.localScale.z);
    //Rotate the line so that it is facing the right direction
    LineChild.transform.rotation = Quaternion.Euler(new Vector3(0,0, rotation));
    StartCapChild.transform.rotation = Quaternion.Euler(new Vector3(0,0, rotation));
    EndCapChild.transform.rotation = Quaternion.Euler(new Vector3(0,0, rotation + 180));
    //Move the line to be centered on the starting point
    LineChild.transform.position = new Vector3 (A.x, A.y, LineChild.transform.position.z);
    StartCapChild.transform.position = new Vector3 (A.x, A.y, StartCapChild.transform.position.z);
    EndCapChild.transform.position = new Vector3 (A.x, A.y, EndCapChild.transform.position.z);
    //Need to convert rotation to radians at this point for Cos/Sin
    rotation *= Mathf.Deg2Rad;
    //Store these so we only have to access once
    var lineChildWorldAdjust : float = LineChild.transform.localScale.x * LineChild.GetComponent(SpriteRenderer).sprite.rect.width / 2f;
    var startCapChildWorldAdjust : float = StartCapChild.transform.localScale.x * StartCapChild.GetComponent(SpriteRenderer).sprite.rect.width / 2f;
    var endCapChildWorldAdjust : float = EndCapChild.transform.localScale.x * EndCapChild.GetComponent(SpriteRenderer).sprite.rect.width / 2f;
    //Adjust the middle segment to the appropriate position
    LineChild.transform.position += new Vector3 (.01f * Mathf.Cos(rotation) * lineChildWorldAdjust, 
                                                 .01f * Mathf.Sin(rotation) * lineChildWorldAdjust,
                                                 0);
    //Adjust the start cap to the appropriate position
    StartCapChild.transform.position -= new Vector3 (.01f * Mathf.Cos(rotation) * startCapChildWorldAdjust, 
                                                     .01f * Mathf.Sin(rotation) * startCapChildWorldAdjust,
                                                     0);
    //Adjust the end cap to the appropriate position
    EndCapChild.transform.position += new Vector3 (.01f * Mathf.Cos(rotation) * lineChildWorldAdjust * 2, 
                                                   .01f * Mathf.Sin(rotation) * lineChildWorldAdjust * 2,
                                                   0);
    EndCapChild.transform.position += new Vector3 (.01f * Mathf.Cos(rotation) * endCapChildWorldAdjust, 
                                                   .01f * Mathf.Sin(rotation) * endCapChildWorldAdjust,
                                                   0);
}

The way we position the middle segment and the caps will make them join seamlessly when we draw them. The start cap is positioned at point A, the middle segment is stretched to the desired width, and the end cap is rotated 180° and drawn at point B.

Now we need to create a prefab for our LineJS class to work with. In Unity, from the menu, select GameObject > Create Empty. The object will appear in your Hierarchy panel. Rename it to LineJS and drag your LineJS script onto it. It should look something like the image below.

We'll use this object as a container for the pieces of our line segment.

Now we need to create objects for the pieces of our line segment. Create three Sprites by selecting GameObject > Create Other > Sprite from the menu. Rename them toStartCap, MiddleSegment, and EndCap. Drag them onto our LineJS object so that they become its children—this should look something like the image below.

Go through each child and set its Material in the Sprite Renderer to the Additive material we created earlier. Assign each child the appropriate sprite. (The two caps should get the cap sprite and the middle segment should get the line sprite.)

Click on the LineJS object so that you can see the script in the Inspector panel. Assign the children to their appropriate slots and then drag the LineJS object into the Prefabs folder to create a prefab for it. You can now delete the LineJS object from the Hierarchy panel.

Step 2: Create Jagged Lines

Lightning tends to form jagged lines, so we'll need an algorithm to generate these. We'll do this by picking points at random along a line, and displacing them a random distance from the line.

Using a completely random displacement tends to make the line too jagged, so we'll smooth the results by limiting how far from each other neighbouring points can be displaced—see the difference between the second and third lines in the figure below.

We smooth the line by placing points at a similar offset to the previous point; this allows the line as a whole to wander up and down, while preventing any part of it from being too jagged.

Let's create a LightningBoltJS class to handle creating our jagged lines.

#pragma strict
import System.Collections.Generic;
class LightningBoltJS extends MonoBehaviour
{    
    //List of all of our active/inactive lines
    public var ActiveLineObj : List.<GameObject>;
    public var InactiveLineObj : List.<GameObject>;
    //Prefab for a line
    public var LinePrefab : GameObject;
    //Transparency
    public var Alpha : float;
    //The speed at which our bolts will fade out
    public var FadeOutRate : float;
    //The color of our bolts
    public var Tint : Color;
    //The position where our bolt started
    public function Start()
    {
        var first : GameObject = ActiveLineObj[0];
        return first.GetComponent(LineJS).A;
    }
    //The position where our bolt ended
    public function End()
    {
        var last : GameObject = ActiveLineObj[ActiveLineObj.Count-1];
        return last.GetComponent(LineJS).B;
    }
    //True if the bolt has completely faded out
    public function IsComplete()
    { 
	    return Alpha <= 0;
    }
    public function Initialize(maxSegments : int)
    {
        //Initialize lists for pooling
        ActiveLineObj = new List.<GameObject>();
        InactiveLineObj = new List.<GameObject>();
        for(var i : int = 0; i < maxSegments; i++)
        {
            //instantiate from our Line Prefab
            var line : GameObject = GameObject.Instantiate(LinePrefab);
            //parent it to our bolt object
            line.transform.parent = transform;
            //set it inactive
            line.SetActive(false);
            //add it to our list
            InactiveLineObj.Add(line);
        }
    }
    public function ActivateBolt(source : Vector2, dest : Vector2, color : Color, thickness : float)
    {
        //for use in loops later
        var i : int;
        //Store tint
        Tint = color;
        //Store alpha
        Alpha = 1.5f;
        //Store fade out rate
        FadeOutRate = 0.03f;
        //actually create the bolt
        //Prevent from getting a 0 magnitude
        if(Vector2.Distance(dest, source) <= 0)
        {
            var adjust : Vector2 = Random.insideUnitCircle;
            if(adjust.magnitude <= 0) adjust.x += .1f;
            dest += adjust;
        }
        //difference from source to destination
        var slope : Vector2 = dest - source;
        var normal : Vector2 = (new Vector2(slope.y, -slope.x)).normalized;
        //distance between source and destination
        var distance : float = slope.magnitude;
        var positions : List.<float> = new List.<float>();
        positions.Add(0);
        for (i = 0; i < distance / 4; i++) 
        {
            //Generate random positions between 0 and 1 to break up the bolt
            //positions.Add (Random.Range(0f, 1f));
            positions.Add(Random.Range(.25f, .75f));
        }
        positions.Sort();
        var Sway : float = 80;
        var Jaggedness : float = 1 / Sway;
        //Affects how wide the bolt is allowed to spread
        var spread : float = 1f;
        //Start at the source
        var prevPoint : Vector2 = source;
        //No previous displacement, so just 0
        var prevDisplacement : float = 0;
        for (i = 1; i < positions.Count; i++)
        {
            //don't allow more than we have in the pool
            var inactiveCount : int = InactiveLineObj.Count;
            if(inactiveCount <= 0) break;
            var pos : float = positions[i];
            var prevPos : float = positions[i - 1];
            //used to prevent sharp angles by ensuring very close positions also have small perpendicular variation.
            var scale : float = (distance * Jaggedness) * (pos - prevPos);
            //defines an envelope. Points near the middle of the bolt can be further from the central line.
            var envelope : float = pos > 0.95f ? 20 * (1 - pos) : spread;
            //calculate the displacement
            var displacement : float = Random.Range(-Sway, Sway);
            displacement -= (displacement - prevDisplacement) * (1 - scale);
            displacement *= envelope;
            //Calculate the end point
            var point : Vector2 = source + (pos * slope) + (displacement * normal);
            activateLine(prevPoint, point, thickness);
            prevPoint = point;
            prevDisplacement = displacement;
        }
        activateLine(prevPoint, dest, thickness);
    }
    public function DeactivateSegments()
    {
        for(var i : int = ActiveLineObj.Count - 1; i >= 0; i--)
        {
            var line : GameObject = ActiveLineObj[i];
            line.SetActive(false);
            ActiveLineObj.RemoveAt(i);
            InactiveLineObj.Add(line);
        }
    }
    function activateLine(A : Vector2, B : Vector2, thickness : float)
    {
        //get the inactive count
        var inactiveCount : int = InactiveLineObj.Count;
        //only activate if we can pull from inactive
        if(inactiveCount <= 0) return;
        //pull the GameObject
        var lineObj : GameObject = InactiveLineObj[InactiveLineObj.Count - 1];
        //set it active
        lineObj.SetActive(true);
        //get the Line component
        var lineComponent : LineJS = lineObj.GetComponent(LineJS);
        lineComponent.SetColor(Color.white);
        lineComponent.A = A;
        lineComponent.B = B;
        lineComponent.Thickness = thickness;
        ActiveLineObj.Add(lineObj);
        InactiveLineObj.Remove(lineObj);
    }
    public function Draw()
    {
        //if the bolt has faded out, no need to draw
        if (Alpha <= 0) return;
        for(var i : int = 0; i < ActiveLineObj.Count; i++)
        {
            var obj : GameObject = ActiveLineObj[i];
            var lineComponent : LineJS = obj.GetComponent(LineJS);
            lineComponent.SetColor(Tint * (Alpha * 0.6f));
            lineComponent.Draw();
        }
    }
    public function Update()
    {
        Alpha -= FadeOutRate;
    }
    //...
}

The code may look a bit intimidating, but it's not so bad once you understand the logic. Before we continue on, understand that we've chosen to pool our line segments in the bolts (since constantly instantiating and destroying objects can be costly in Unity).

  • The Initialize() function will be called once on each lightning bolt and will determine how many line segments each bolt is allowed to use.
  • The activateLine() function will activate a line segment using the given position data.
  • The DeactivateSegments() function will deactivate any active line segments in our bolt.
  • The ActivateBolt() function will handle creating our jagged lines and will call the activateLine() function to activate our line segments at the appropriate positions.

To create our jagged lines, we start by computing the slope between our two points, as well as the normal vector to that slope. We then choose a number of random positions along the line and store them in our positions list. We scale these positions between 0 and 1, such that 0 represents the start of the line and 1 represents the end point., and then sort these positions to allow us to easily add line segments between them.

The loop goes through the randomly chosen points and displaces them along the normal by a random amount. The scale factor is there to avoid overly sharp angles, and theenvelope ensures the lightning actually goes to the destination point by limiting displacement when we're close to the end. The spread is to assist in controlling how far the segments deviate from the slope of our line; a spread of 0 will essentially give you a straight line.

So, like we did with our LineJS class, let's make this into a prefab. From the menu, select GameObject > Create Empty. The object will appear in your Hierarchy panel. Rename it to BoltJS, and drag a copy of the LightningBoltJS script onto it. Finally, click on the BoltJS object and assign the LineJS prefab, from the Prefabs folder, to the appropriate slot in the LightningBoltJS script. Once you're done with that, simply drag the BoltJS object into the Prefabs folder to create a prefab.

Step 3: Add Animation

Lightning should flash brightly and then fade out. This is what our Update() and Draw() functions in LightningBoltJS are for. Calling Update() will make the bolt fade out. Calling Draw() will update the bolt's color on the screen. IsComplete() will tell you when the bolt has fully faded out.

Step 4: Create a Bolt

Now that we have our LightningBoltJS class, let's actually put it to good use and set up a quick demo scene.

We're going to use an object pool for this demo, so we'll want to create an empty object to hold our active and inactive bolts (simply for organizational purposes). In Unity, from the menu, select GameObject > Create Empty. The object will appear in your Hierarchy panel. Rename it to LightningPoolHolder.

Right click on the Scripts folder and select Create > Javascript. Name your script DemoScriptJS and open it. Here's some quick code to get you started:

#pragma strict
import System.Collections.Generic;

class DemoScriptJS extends MonoBehaviour
{
    //Prefabs to be assigned in Editor
    public var BoltPrefab : GameObject;
    
    //For pooling
    var activeBoltsObj : List.<GameObject>;
    var inactiveBoltsObj : List.<GameObject>;
    var maxBolts : int = 1000;
    //For handling mouse clicks
    var clicks : int = 0;
    var pos1 : Vector2;
    var pos2 : Vector2;
    function Start()
    {
        //Initialize lists
        activeBoltsObj = new List.<GameObject>();
        inactiveBoltsObj = new List.<GameObject>();
        //for use later
        var tempV3 : Vector3;
        //Grab the parent we'll be assigning to our bolt pool
        var p : GameObject = GameObject.Find("LightningPoolHolder");
        //For however many bolts we've specified
        for(var i : int = 0; i < maxBolts; i++)
        {
            //create from our prefab
            var bolt : GameObject = Instantiate(BoltPrefab);
            //Assign parent
            bolt.transform.parent = p.transform;
            //Initialize our lightning with a preset number of max sexments
            bolt.GetComponent(LightningBoltJS).Initialize(25);
            //Set inactive to start
            bolt.SetActive(false);
            //Store in our inactive list
            inactiveBoltsObj.Add(bolt);
        }
    }
    function Update()
    {
        //Declare variables for use later
        var boltObj : GameObject;
        var boltComponent : LightningBoltJS;
        var i : int;
        var tempV3 : Vector3;
        var adjust : Vector2;
        //store off the count for effeciency
        var activeLineCount : int = activeBoltsObj.Count;
        //loop through active lines (backwards because we'll be removing from the list)
        for (i = activeLineCount - 1; i >= 0; i--)
        {
        	//pull GameObject
        	boltObj = activeBoltsObj[i];
        	//get the LightningBolt component
        	boltComponent = boltObj.GetComponent(LightningBoltJS);
        	//if the bolt has faded out
        	if(boltComponent.IsComplete())
        	{
                //deactive the segments it contains
                boltComponent.DeactivateSegments();
                //set it inactive
                boltObj.SetActive(false);
                //move it to the inactive list
                activeBoltsObj.RemoveAt(i);
                inactiveBoltsObj.Add(boltObj);
        	}
        }
        //If left mouse button pressed
        if(Input.GetMouseButtonDown(0))
        {
            //if first click
            if(clicks == 0)
            {
                //store starting position
                tempV3 = Camera.main.ScreenToWorldPoint(Input.mousePosition);
                pos1 = new Vector2(tempV3.x, tempV3.y);
            }
            else if(clicks == 1) //second click
            {
                //store end position
                tempV3 = Camera.main.ScreenToWorldPoint(Input.mousePosition);
                pos2 = new Vector2(tempV3.x, tempV3.y);
                CreatePooledBolt(pos1,pos2, Color.white, 1f);
            }
            //increment our tick count
            clicks++;
            //restart the count after 2 clicks
            if(clicks > 1) clicks = 0;
        }
        //update and draw active bolts
        for(i = 0; i < activeBoltsObj.Count; i++)
        {
            boltObj = activeBoltsObj[i];
            boltObj.GetComponent(LightningBoltJS).Update();
            boltObj.GetComponent(LightningBoltJS).Draw();
        }
    }
    function CreatePooledBolt(source : Vector2, dest : Vector2, color : Color, thickness : float)
    {
        //if there is an inactive bolt to pull from the pool
        if(inactiveBoltsObj.Count > 0)
        {
            //pull the GameObject
            var boltObj : GameObject = inactiveBoltsObj[inactiveBoltsObj.Count - 1];
            //set it active
            boltObj.SetActive(true);
            //move it to the active list
            activeBoltsObj.Add(boltObj);
            inactiveBoltsObj.RemoveAt(inactiveBoltsObj.Count - 1);
            //get the bolt component
            var boltComponent : LightningBoltJS =  boltObj.GetComponent(LightningBoltJS);
            //activate the bolt using the given position data
            boltComponent.ActivateBolt(source, dest, color, thickness);
        }
    }
}

All this code does is give us a way to create bolts using object pooling. There are other ways you can set this up, but this is what we're going with! Once we've set it up, all you'll have to do is click twice to create a bolt on the screen: once for the start position and once for the end position.

We'll need an object to put our DemoScriptJS on. From the menu, select GameObject > Create Empty. The object will appear in your Hierarchy panel. Rename it to DemoScript and drag your DemoScriptJS script onto it. Click on the DemoScript object so we can view it in the Inspector panel. Assign the BoltJS prefab, from the Prefabs folder, to the matching slot in the DemoScriptJS.

That should be enough to get you going! Run the scene in Unity and try it out!

Step 5: Create Branch Lightning

You can use the LightningBoltJS class as a building block to create more interesting lightning effects. For example, you can make the bolts branch out as shown below:

To make the lightning branch, we pick random points along the lightning bolt and add new bolts that branch out from these points. In the code below, we create between three and six branches which separate from the main bolt at 30° angles.

#pragma strict
import System.Collections.Generic;

class BranchLightningJS extends MonoBehaviour
{
    //For holding all of our bolts in our branch
    public var boltsObj : List.<GameObject>;
    //If there are no bolts, then the branch is complete (we're not pooling here, but you could if you wanted)
    public function IsComplete()
    { 
        return boltsObj.Count <= 0;
    }
    //Start position of branch
    public var Start : Vector2;
    //End position of branch
    public var End : Vector2;
    static var rand : Random = new Random();
    public function Initialize(start : Vector2, end : Vector2, boltPrefab : GameObject)
    {
        //for use lateer
        var i : int;
        //store start and end positions
        Start = start;
        End = end;
        //create the  main bolt from our bolt prefab
        var mainBoltObj : GameObject = GameObject.Instantiate(boltPrefab);
        //get the LightningBolt component
        var mainBoltComponent : LightningBoltJS = mainBoltObj.GetComponent(LightningBoltJS);
        //initialize our bolt with a max of 5 segments
        mainBoltComponent.Initialize(5);
        //activate the bolt with our position data
        mainBoltComponent.ActivateBolt(start, end, Color.white, 1f);
        //add it to our list
        boltsObj.Add(mainBoltObj);
        //randomly determine how many sub branches there will be (3-6)
        var numBranches : int = Random.Range(3,6);
        //calculate the difference between our start and end points
        var diff : Vector2 = end - start;
        // pick a bunch of random points between 0 and 1 and sort them
        var branchPoints : List.<float> = new List.<float>();
        for(i = 0; i < numBranches; i++) branchPoints.Add(Random.value);
        branchPoints.Sort();
        //go through those points
        for (i = 0; i < branchPoints.Count; i++)
        {
            // Bolt.GetPoint() gets the position of the lightning bolt based on the percentage passed in (0 = start of bolt, 1 = end)
            var boltStart : Vector2 = mainBoltComponent.GetPoint(branchPoints[i]);
            //get rotation of 30 degrees. Alternate between rotating left and right. (i & 1 will be true for all odd numbers...yay bitwise operators!)
            var rot : Quaternion = Quaternion.AngleAxis(30 * ((i & 1) == 0 ? 1 : -1), new Vector3(0,0,1));
            var point : float = branchPoints[i];
            //calculate how much to adjust for our end position
            var adjust : Vector2 = rot * (Random.Range(.5f, .75f) * diff * (1 - point));
            //get the end position
            var boltEnd : Vector2 = adjust + boltStart;
            //instantiate from our bolt prefab
            var boltObj : GameObject = GameObject.Instantiate(boltPrefab);
            //get the LightningBolt component
            var boltComponent : LightningBoltJS = boltObj.GetComponent(LightningBoltJS);
            //initialize our bolt with a max of 5 segments
            boltComponent.Initialize(5);
            //activate the bolt with our position data
            boltComponent.ActivateBolt(boltStart, boltEnd, Color.white, 1f);
            //add it to the list
            boltsObj.Add(boltObj);
        }
    }
    public function Update()
    {
        //go through our active bolts
        for (var i : int = boltsObj.Count - 1; i >= 0; i--)
        {
            //get the GameObject
            var boltObj : GameObject = boltsObj[i];
            //get the LightningBolt component
            var boltComp : LightningBoltJS = boltObj.GetComponent(LightningBoltJS);
            //update/fade out the bolt
            boltComp.Update();
            //if the bolt has faded
            if(boltComp.IsComplete())
            {
                //remove it from our list
                boltsObj.RemoveAt(i);
                //destroy it (would be better to pool but I'll let you figure out how to do that =P)
                Destroy(boltObj);
            }
        }
    }
    //Draw our active bolts on screen
    public function Draw()
    {
        var boltObj : GameObject;
        for(var i : int; i < boltsObj.Count; i++)
        {
            boltObj = boltsObj[i];
            boltObj.GetComponent(LightningBoltJS).Draw();
        }
    }
}

This code works very similarly to our LightningBoltJS class with the exception that it does not use object pooling. Calling Initialize() is all you will need to do to create a branching bolt; after that, you will just need to call Update() and Draw(). I'll show you exactly how to do this in our DemoScriptJS later on in the tutorial.

You may have noticed the reference to a GetPoint() function in the LightningBoltJS class. We haven't actually implemented that function yet, so let's take care of that now.

Add the following function in the bottom of the LightningBoltJS class:

// Returns the point where the bolt is at a given fraction of the way through the bolt. Passing
// zero will return the start of the bolt, and passing 1 will return the end.
public function GetPoint(position : float)
{
    var start : Vector2 = Start();
    var length : float = Vector2.Distance(start, End());
    var dir : Vector2 = (End() - start) / length;
    position *= length;
    var line : LineJS;
    //find the appropriate line
    for(var i : int = 0; i < ActiveLineObj.Count; i++)
    {
        var x : GameObject = ActiveLineObj[i];
        if(Vector2.Dot(x.GetComponent(LineJS).B - start, dir) >= position)
        {
            line = x.GetComponent(LineJS);
            break;
        }
    }
    var lineStartPos : float = Vector2.Dot(line.A - start, dir);
    var lineEndPos : float = Vector2.Dot(line.B - start, dir);
    var linePos : float = (position - lineStartPos) / (lineEndPos - lineStartPos);
    return Vector2.Lerp(line.A, line.B, linePos);
}

Step 6: Create Lightning Text

Below is a video of another effect you can make out of the lightning bolts:

We'll need to do a little more setup for this one. First, from the Project panel, select Create > RenderTexture. Rename it to RenderText and set its Size to 256x256px. (It doesn't necessarily have to be that exact size, but the smaller it is, the faster the program will run.)

From the menu, select Edit > Project Settings > Tags and Layers. Then, in the Inspector panel, expand the Layers drop down and add Text into User Layer 8.

We'll then need to create a second camera. From the menu, select GameObject > Create Other > Camera. Rename it to TextCamera, and set its Projection to Orthographic and its Clear Flags to Solid Color. Set its Background color to(R: 0, G: 0, B: 0, A: 0) and set its Culling Mask to only be Text (the layer we just created). Finally, set its Target Texture to RenderText (the RenderTexture we created earlier). You'll probably need to play around with the camera's Size later, in order to get everything to fit on the screen.

Now we'll need to create the actual text we'll be drawing with our lightning. From the menu select GameObject > Create Other > GUI Text. Select the GUI Text object from the Hierarchy panel and set its Text to LIGHTNING, its Anchor to middle center, and its Alignment to center. Then, set its Layer to the Text layer we created earlier. You'll probably have to play around with the Font Size in order to fit the text on the screen.

Now select the Main Camera and set its Culling Mask to be everything but our Text layer. This will cause our GUI Text to apparently disappear from the screen, but it should be drawn on the RenderTexture we created earlier: select RenderText from the Project panel and you should be able to see the word LIGHTNING on the preview on the bottom of the panel.

If you can't see the word LIGHTNING, you'll need to play around with your positioning, font size, and (text) camera size. To help you position your text, click on TextCamera in the Hierarchy panel, and set its Target Texture to None. You'll now be able to see your GUI Text if you center it on the TextCamera. Once you have everything positioned, set the TextCamera's Target Texture back to RenderText.

Now for the code! We'll need to get the pixels from the text that we're drawing. We can do this by drawing our text to a RenderTarget and reading back the pixel data into aTexture2D with Texture2D.ReadPixels(). Then, we can store the coordinates of the pixels from the text as a List.<Vector2>.

Here's the code to do that:

//Capture the important points of our text for later
function TextCapture()
{
    //must wait until end of frame so something is actually drawn or else it will error
    yield WaitForEndOfFrame();
    //get the camera that draws our text
    var cam : Camera = GameObject.Find("TextCamera").GetComponent(Camera);
    //make sure it has an assigned RenderTexture
    if(cam.targetTexture != null) 
    {
        //pull the active RenderTexture
        RenderTexture.active = cam.targetTexture;
        //capture the image into a Texture2D
        var image : Texture2D = new Texture2D(cam.targetTexture.width, cam.targetTexture.height);
        image.ReadPixels(new Rect(0, 0, cam.targetTexture.width, cam.targetTexture.height), 0, 0);
        image.Apply();
        //calculate how the text will be scaled when it is displayed as lightning on the screen
        scaleText = 1 / (cam.ViewportToWorldPoint(new Vector3(1,0,0)).x - cam.ViewportToWorldPoint(Vector3.zero).x);
        //calculate how the text will be positioned when it is displayed as lightning on the screen (centered)
        positionText.x -= image.width * scaleText * .5f;
        positionText.y -= image.height * scaleText * .5f;
        //basically determines how many pixels we skip/check
        var interval : int = 2;
        //loop through pixels
        for(var y : int = 0; y < image.height; y += interval)
        {
            for(var x : int = 0; x < image.width; x += interval)
            {
                //get the color of the pixel
                var color : Color = image.GetPixel(x,y);
                //if the color has an r (red) value
                if(color.r > 0)
                {
                    //add it to our points for drawing
                    textPoints.Add(new Vector2(x,y));
                }
            }
        }
    }
}

Note: We'll have to run this function as a Coroutine at the start of our program in order for it to run correctly.

After that, each frame, we can randomly pick pairs of these points and create a lightning bolt between them. We want to design it so that the closer two points are to one another, the greater the chance is that we create a bolt between them.

There's a simple technique we can use to accomplish this: we'll pick the first point at random, and then we'll pick a fixed number of other points at random and choose the nearest.

Here's the code for that (we'll add it to our DemoScriptJS later):

//go through the points we capture earlier
for (var i1 : int = 0; i1 < textPoints.Count; i1++)
{
    var point : Vector2 = textPoints[i1];
    //randomly ignore certain points
    if(Random.Range(0,75) != 0) continue;
    //placeholder values
    var nearestParticle : Vector2 = Vector2.zero;
    var nearestDistSquared : float = float.MaxValue;
    for (i = 0; i < 50; i++)
    {
        //select a random point
        var other : Vector2 = textPoints[Random.Range(0, textPoints.Count)];
        //calculate the distance (squared for performance benefits) between the two points
        var distSquared : float = DistanceSquared(point, other);
        //If this point is the nearest point (but not too near!)
        if (distSquared < nearestDistSquared && distSquared > 3 * 3)
        {
            //store off the data
            nearestDistSquared = distSquared;
            nearestParticle = other;
        }
    }
    //if the point we found isn't too near/far
    if (nearestDistSquared < 25 * 25 && nearestDistSquared > 3 * 3)
    {
        //create a (pooled) bolt at the corresponding screen position
        CreatePooledBolt((point * scaleText) + positionText, (nearestParticle * scaleText) + positionText, new Color(Random.value,Random.value,Random.value,1f), 1f);
    }
}

/* The code above uses the following function 
 * It'll need to be placed appropriately
--------------------------------------------- 
//calculate distance squared (no square root = performance boost)
public function DistanceSquared(a : Vector2, b : Vector2)
{
    return ((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
---------------------------------------------*/

The number of candidate points we test will affect the look of the lightning text; checking a larger number of points will allow us to find very close points to draw bolts between, which will make the text very neat and legible, but with fewer long lightning bolts between letters. Smaller numbers will make the lightning text look wilder but less legible.

Step 7: Try Other Variations

We've discussed making branch lightning and lightning text, but those certainly aren't the only effects you can make. Let's look at a couple other variations on lightning you may want to use.

Moving Lightning

You may often want to make a moving bolt of lightning. You can do this by adding a new short bolt each frame at the end point of the previous frame's bolt.

//Will contain all of the pieces for the moving bolt
var movingBolt : List.<GameObject>;

//used for actually moving the moving bolt
var lightningEnd : Vector2 = new Vector2(100, 100);
var lightningVelocity : Vector2 = new Vector2(1, 0);

function Update()
{
    //loop through all of our bolts that make up the moving bolt
    for(i = movingBolt.Count - 1; i >= 0; i--)
    {
        boltObj = movingBolt[i];
        //get the bolt component
        boltComponent = boltObj.GetComponent(LightningBoltJS);
        //if the bolt has faded out
        if(boltComponent.IsComplete())
        {
            //destroy it
            Destroy(movingBolt[i]);
            //remove it from our list
            movingBolt.RemoveAt(i);
            //on to the next one, on on to the next one
            continue;
        }
        //update and draw bolt
        boltComponent.Update();
        boltComponent.Draw();
    }
    //if our moving bolt is active
    if(movingBolt.Count > 0)
    {
        boltObj = movingBolt[movingBolt.Count-1];
        //calculate where it currently ends
        lightningEnd = boltObj.GetComponent(LightningBoltJS).End();
        //if the end of the bolt is within 25 units of the camera
        if(Vector2.Distance(lightningEnd,Camera.main.transform.position) < 25)
        {
            //instantiate from our bolt prefab
            boltObj = GameObject.Instantiate(BoltPrefab);
            //get the bolt component
            boltComponent = boltObj.GetComponent(LightningBoltJS);
            //initialize it with a maximum of 5 segments
            boltComponent.Initialize(5);
            //activate the bolt using our position data (from the current end of our moving bolt to the current end + velocity) 
            boltComponent.ActivateBolt(lightningEnd,lightningEnd + lightningVelocity, Color.white, 1f);
            //add it to our list
            movingBolt.Add(boltObj);
            //update and draw our new bolt
            boltComponent.Update();
            boltComponent.Draw();
        }
    }
}

Burst Lightning

This variation offers a dramatic effect that shoots lightning out in a circle from the centre point:

//get the difference between our two positions (destination - source = vector from source to destination)
var diff : Vector2 = pos2 - pos1;

function Update()
{
    //define how many bolts we want in our circle
    var boltsInBurst : int = 10;
    
    for(i = 0; i < boltsInBurst; i++)
    {
        //rotate around the z axis to the appropriate angle
        var rot : Quaternion = Quaternion.AngleAxis((360f/boltsInBurst) * i, new Vector3(0,0,1));
        adjust = rot * diff;
        //Calculate the end position for the bolt
        var boltEnd : Vector2 = adjust + pos1;
        //create a (pooled) bolt from pos1 to boltEnd
        CreatePooledBolt(pos1, boltEnd, Color.white, 1f);
    }
}

Step 8: Put It All Together in DemoScriptJS

You're going to want to be able to try out all of these fancy effects we've created so far, so let's put all of them into the DemoScriptJS we made earlier. You'll be able to toggle between effects by hitting the number keys on your keyboard to select the effect, and then just clicking twice like we did with our bolts before.

Here's the full code:

#pragma strict
#pragma strict
import System.Collections.Generic;

class DemoScriptJS extends MonoBehaviour
{
    //Prefabs to be assigned in Editor
    public var BoltPrefab : GameObject;
    public var BranchPrefab : GameObject;
    
    //For pooling
    var activeBoltsObj : List.<GameObject>;
    var inactiveBoltsObj : List.<GameObject>;
    var maxBolts : int = 1000;
    var scaleText : float;
    var positionText : Vector2;
    //Different modes for the demo
    class Mode
    {
        public static final var bolt : byte = 0;
        public static final var branch : byte = 1;
        public static final var moving : byte = 2;
        public static final var text : byte = 3;
        public static final var nodes : byte = 4;
        public static final var burst : byte = 5;
    }
    //The current mode the demo is in
    var currentMode : byte = Mode.bolt;
    //Will contain all of the pieces for the moving bolt
    var movingBolt : List.<GameObject>;
    //used for actually moving the moving bolt
    var lightningEnd : Vector2 = new Vector2(100, 100);
    var lightningVelocity : Vector2 = new Vector2(1, 0);
    //Will contain all of the pieces for the branches
    var branchesObj : List.<GameObject>;
    //For handling mouse clicks
    var clicks : int = 0;
    var pos1 : Vector2;
    var pos2 : Vector2;
    //For storing all of the pixels that need to be drawn by the bolts 
    var textPoints : List.<Vector2>;
    //true in text mode
    var shouldText : boolean = false;
    function Start()
    {
        //Initialize lists
        activeBoltsObj = new List.<GameObject>();
        inactiveBoltsObj = new List.<GameObject>();
        branchesObj = new List.<GameObject>();
        //for use later
        var tempV3 : Vector3;
        //Grab the parent we'll be assigning to our bolt pool
        var p : GameObject = GameObject.Find("LightningPoolHolder");
        //For however many bolts we've specified
        for(var i : int = 0; i < maxBolts; i++)
        {
            //create from our prefab
            var bolt : GameObject = Instantiate(BoltPrefab);
            //Assign parent
            bolt.transform.parent = p.transform;
            //Initialize our lightning with a preset number of max sexments
            bolt.GetComponent(LightningBoltJS).Initialize(25);
            //Set inactive to start
            bolt.SetActive(false);
            //Store in our inactive list
            inactiveBoltsObj.Add(bolt);
        }
        //Start up a coroutine to capture the pixels we'll be drawing from our text (need the coroutine or error)
        StartCoroutine(TextCapture());
    }
    function Update()
    {
        //Declare variables for use later
        var boltObj : GameObject;
        var boltComponent : LightningBoltJS;
        var i : int;
        var tempV3 : Vector3;
        var adjust : Vector2;
        var branchObj : GameObject;
        var branchComponent : BranchLightningJS;
        //store off the count for effeciency
        var activeLineCount : int = activeBoltsObj.Count;
        //loop through active lines (backwards because we'll be removing from the list)
        for (i = activeLineCount - 1; i >= 0; i--)
        {
            //pull GameObject
            boltObj = activeBoltsObj[i];
            //get the LightningBolt component
            boltComponent = boltObj.GetComponent(LightningBoltJS);
            //if the bolt has faded out
            if(boltComponent.IsComplete())
            {
                //deactive the segments it contains
                boltComponent.DeactivateSegments();
                //set it inactive
                boltObj.SetActive(false);
                //move it to the inactive list
                activeBoltsObj.RemoveAt(i);
                inactiveBoltsObj.Add(boltObj);
            }
        }
        //check for key press and set mode accordingly
        if(Input.GetKeyDown(KeyCode.Alpha1) || Input.GetKeyDown(KeyCode.Keypad1))
        {
            shouldText = false;
            currentMode = Mode.bolt;
        }
        else if(Input.GetKeyDown(KeyCode.Alpha2) || Input.GetKeyDown(KeyCode.Keypad2))
        {
            shouldText = false;
            currentMode = Mode.branch;
        }
        else if(Input.GetKeyDown(KeyCode.Alpha3) || Input.GetKeyDown(KeyCode.Keypad3))
        {
            shouldText = false;
            currentMode = Mode.moving;
        }
        else if(Input.GetKeyDown(KeyCode.Alpha4) || Input.GetKeyDown(KeyCode.Keypad4))
        {
            shouldText = true;
            currentMode = Mode.text;
        }
        else if(Input.GetKeyDown(KeyCode.Alpha5) || Input.GetKeyDown(KeyCode.Keypad5))
        {
            shouldText = false;
            currentMode = Mode.nodes;
        }
        else if(Input.GetKeyDown(KeyCode.Alpha6) || Input.GetKeyDown(KeyCode.Keypad6))
        {
            shouldText = false;
            currentMode = Mode.burst;
        }
        //If left mouse button pressed
        if(Input.GetMouseButtonDown(0))
        {
            //if first click
            if(clicks == 0)
            {
                //store starting position
                tempV3 = Camera.main.ScreenToWorldPoint(Input.mousePosition);
                pos1 = new Vector2(tempV3.x, tempV3.y);
            }
            else if(clicks == 1) //second click
            {
                //store end position
                tempV3 = Camera.main.ScreenToWorldPoint(Input.mousePosition);
                pos2 = new Vector2(tempV3.x, tempV3.y);
                //Handle the current mode appropriately
                switch (currentMode)
                {
                    case Mode.bolt:
                        //create a (pooled) bolt from pos1 to pos2
                        CreatePooledBolt(pos1,pos2, Color.white, 1f);
                    break;
                    case Mode.branch:
                        //instantiate from our branch prefab
                        branchObj = GameObject.Instantiate(BranchPrefab);
                        //get the branch component
                        branchComponent = branchObj.GetComponent(BranchLightningJS);
                        //initialize the branch component using our position data
                        branchComponent.Initialize(pos1, pos2, BoltPrefab);
                        //add it to the list of active branches
                        branchesObj.Add(branchObj);
                    break;
                    case Mode.moving:
                        //Prevent from getting a 0 magnitude (0 causes errors 
                        if(Vector2.Distance(pos1, pos2) <= 0)
                        {
                            //Try a random position
                            adjust = Random.insideUnitCircle;
                            //failsafe
                            if(adjust.magnitude <= 0) adjust.x += .1f;
                            //Adjust the end position
                            pos2 += adjust;
                        }
                        //Clear out any old moving bolt (this is designed for one moving bolt at a time)
                        for(i = movingBolt.Count - 1; i >= 0; i--)
                        {
                            Destroy(movingBolt[i]);
                            movingBolt.RemoveAt(i);
                        }
                        //get the "velocity" so we know what direction to send the bolt in after initial creation
                        lightningVelocity = (pos2 - pos1).normalized;
                        //instantiate from our bolt prefab
                        boltObj = GameObject.Instantiate(BoltPrefab);
                        //get the bolt component
                        boltComponent = boltObj.GetComponent(LightningBoltJS);
                        //initialize it with 5 max segments
                        boltComponent.Initialize(5);
                        //activate the bolt using our position data
                        boltComponent.ActivateBolt(pos1, pos2, Color.white, 1f);
                        //add it to our list
                        movingBolt.Add(boltObj);
                    break;
                    case Mode.burst:
                        //get the difference between our two positions (destination - source = vector from source to destination)
                        var diff : Vector2 = pos2 - pos1;
                        //define how many bolts we want in our circle
                        var boltsInBurst : int = 10;
                        for(i = 0; i < boltsInBurst; i++)
                        {
                            //rotate around the z axis to the appropriate angle
                            var rot : Quaternion = Quaternion.AngleAxis((360f/boltsInBurst) * i, new Vector3(0,0,1));
                            adjust = rot * diff;
                            //Calculate the end position for the bolt
                            var boltEnd : Vector2 = adjust + pos1;
                            //create a (pooled) bolt from pos1 to boltEnd
                            CreatePooledBolt(pos1, boltEnd, Color.white, 1f);
                        }
                    break;
                }
            }
            //increment our tick count
            clicks++;
            //restart the count after 2 clicks
            if(clicks > 1) clicks = 0;
        }
        //if in node mode
        if(currentMode == Mode.nodes)
        {
            //constantly create a (pooled) bolt between the two assigned positions
            CreatePooledBolt(pos1, pos2, Color.white, 1f);
        }
        //loop through any active branches
        for(i = branchesObj.Count - 1; i >= 0; i--)
        {
            branchObj = branchesObj[i];
            //pull the branch lightning component
            branchComponent = branchObj.GetComponent(BranchLightningJS);
            //If it's faded out already
            if(branchComponent.IsComplete())
            {
                //destroy it
                Destroy(branchesObj[i]);
                //take it out of our list
                branchesObj.RemoveAt(i);
                //move on to the next branch
                continue;
            }
            //draw and update the branch
            branchComponent.Update();
            branchComponent.Draw();
        }
        //loop through all of our bolts that make up the moving bolt
        for(i = movingBolt.Count - 1; i >= 0; i--)
        {
            boltObj = movingBolt[i];
            //get the bolt component
            boltComponent = boltObj.GetComponent(LightningBoltJS);
            //if the bolt has faded out
            if(boltComponent.IsComplete())
            {
                //destroy it
                Destroy(movingBolt[i]);
                //remove it from our list
                movingBolt.RemoveAt(i);
                //on to the next one, on on to the next one
                continue;
            }
            //update and draw bolt
            boltComponent.Update();
            boltComponent.Draw();
        }
        //if our moving bolt is active
        if(movingBolt.Count > 0)
        {
            boltObj = movingBolt[movingBolt.Count-1];
            //calculate where it currently ends
            lightningEnd = boltObj.GetComponent(LightningBoltJS).End();
            //if the end of the bolt is within 25 units of the camera
            if(Vector2.Distance(lightningEnd,Camera.main.transform.position) < 25)
            {
                //instantiate from our bolt prefab
                boltObj = GameObject.Instantiate(BoltPrefab);
                //get the bolt component
                boltComponent = boltObj.GetComponent(LightningBoltJS);
                //initialize it with a maximum of 5 segments
                boltComponent.Initialize(5);
                //activate the bolt using our position data (from the current end of our moving bolt to the current end + velocity) 
                boltComponent.ActivateBolt(lightningEnd,lightningEnd + lightningVelocity, Color.white, 1f);
                //add it to our list
                movingBolt.Add(boltObj);
                //update and draw our new bolt
                boltComponent.Update();
                boltComponent.Draw();
            }
        }
        //if in text mode
        if(shouldText)
        {
            //go through the points we capture earlier
            for (var i1 : int = 0; i1 < textPoints.Count; i1++)
            {
                var point : Vector2 = textPoints[i1];
                //randomly ignore certain points
                if(Random.Range(0,75) != 0) continue;
                //placeholder values
                var nearestParticle : Vector2 = Vector2.zero;
                var nearestDistSquared : float = float.MaxValue;
                for (i = 0; i < 50; i++)
                {
                    //select a random point
                    var other : Vector2 = textPoints[Random.Range(0, textPoints.Count)];
                    //calculate the distance (squared for performance benefits) between the two points
                    var distSquared : float = DistanceSquared(point, other);
                    //If this point is the nearest point (but not too near!)
                    if (distSquared < nearestDistSquared && distSquared > 3 * 3)
                    {
                        //store off the data
                        nearestDistSquared = distSquared;
                        nearestParticle = other;
                    }
                }
                //if the point we found isn't too near/far
                if (nearestDistSquared < 25 * 25 && nearestDistSquared > 3 * 3)
                {
                    //create a (pooled) bolt at the corresponding screen position
                    CreatePooledBolt((point * scaleText) + positionText, (nearestParticle * scaleText) + positionText, new Color(Random.value,Random.value,Random.value,1f), 1f);
                }
            }
        }
        //update and draw active bolts
        for(i = 0; i < activeBoltsObj.Count; i++)
        {
            boltObj = activeBoltsObj[i];
            boltObj.GetComponent(LightningBoltJS).Update();
            boltObj.GetComponent(LightningBoltJS).Draw();
        }
    }
    //calculate distance squared (no square root = performance boost)
    public function DistanceSquared(a : Vector2, b : Vector2)
    {
        return ((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
    }
    function CreatePooledBolt(source : Vector2, dest : Vector2, color : Color, thickness : float)
    {
        //if there is an inactive bolt to pull from the pool
        if(inactiveBoltsObj.Count > 0)
        {
            //pull the GameObject
            var boltObj : GameObject = inactiveBoltsObj[inactiveBoltsObj.Count - 1];
            //set it active
            boltObj.SetActive(true);
            //move it to the active list
            activeBoltsObj.Add(boltObj);
            inactiveBoltsObj.RemoveAt(inactiveBoltsObj.Count - 1);
            //get the bolt component
            var boltComponent : LightningBoltJS =  boltObj.GetComponent(LightningBoltJS);
            //activate the bolt using the given position data
            boltComponent.ActivateBolt(source, dest, color, thickness);
        }
    }
    //Capture the important points of our text for later
    function TextCapture()
    {
        //must wait until end of frame so something is actually drawn or else it will error
        yield WaitForEndOfFrame();
        //get the camera that draws our text
        var cam : Camera = GameObject.Find("TextCamera").GetComponent(Camera);
        //make sure it has an assigned RenderTexture
        if(cam.targetTexture != null) 
        {
            //pull the active RenderTexture
            RenderTexture.active = cam.targetTexture;
            //capture the image into a Texture2D
            var image : Texture2D = new Texture2D(cam.targetTexture.width, cam.targetTexture.height);
            image.ReadPixels(new Rect(0, 0, cam.targetTexture.width, cam.targetTexture.height), 0, 0);
            image.Apply();
            //calculate how the text will be scaled when it is displayed as lightning on the screen
            scaleText = 1 / (cam.ViewportToWorldPoint(new Vector3(1,0,0)).x - cam.ViewportToWorldPoint(Vector3.zero).x);
            //calculate how the text will be positioned when it is displayed as lightning on the screen (centered)
            positionText.x -= image.width * scaleText * .5f;
            positionText.y -= image.height * scaleText * .5f;
            //basically determines how many pixels we skip/check
            var interval : int = 2;
            //loop through pixels
            for(var y : int = 0; y < image.height; y += interval)
            {
                for(var x : int = 0; x < image.width; x += interval)
                {
                    //get the color of the pixel
                    var color : Color = image.GetPixel(x,y);
                    //if the color has an r (red) value
                    if(color.r > 0)
                    {
                        //add it to our points for drawing
                        textPoints.Add(new Vector2(x,y));
                    }
                }
            }
        }
    }
}

Conclusion

Lightning is a great special effect for sprucing up your games. The effects described in this tutorial are a nice starting point, but it's certainly not all you can do with lightning. With a bit of imagination you can make all kinds of awe-inspiring lightning effects! Download the source code and experiment on your own.


Viewing all articles
Browse latest Browse all 728

Trending Articles