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

Trial a 14 Day Tuts+ Subscription for Free

$
0
0

Sample 400+ Video Courses

We've been building our library of courses for three years, and now have over 400 professional video courses. With a mix of screencast lessons and live video, it's a fantastic way to learn a new skill. Our courses cover:

Whether you're interested in learning to draw, becoming a Photoshop expert, improving your web design skills, learning a new programming language, or starting out as a photographer, you'll find a course to help further your passion.

In-Depth Video Training

If you regularly read and enjoy our tutorial content, we think you'll love the extra depth offered in video courses. You'll be taken through important concepts and techniques in detail, accompanied with practical examples and advice.

Here's an example from one of our recent photography lessons on flash photography:

Start Your Free 14 Day Trial

Our subscription plans start from just $15 per month, but we're now offering a completely free, 14 day trial! Head over to our pricing page to sign up, access all our 400+ courses, and begin learning straight away.

At any time, you can choose to upgrade to a Yearly or Yearly Pro subscription, giving you additional access to eBooks, downloadable videos, subscriber benefits, and much more.

We're confident that a Tuts+ subscription will help you further your education and career, so don't miss the opportunity to give it a try for free!


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.

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

$
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 C#. The same tutorial is also available with JavaScript 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:

using UnityEngine;
using System.Collections;

public class Line : MonoBehaviour
{
    //Start
    public Vector2 A;
    
    //End
    public Vector2 B;
    
    //Thickness of line
    public float Thickness;
    
    //Children that contain the pieces that make up the line
    public GameObject StartCapChild, LineChild, EndCapChild;
    
    //Create a new line
    public Line(Vector2 a, Vector2 b, float thickness)
    {
        A = a;
        B = b;
        Thickness = thickness;
    }
    
    //Used to set the color of the line
    public void 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 Line class:

//Will actually draw the line
public void Draw()
{
    Vector2 difference = B - A;
    float rotation = 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
    float lineChildWorldAdjust = LineChild.transform.localScale.x * LineChild.GetComponent<SpriteRenderer>().sprite.rect.width / 2f;
    float startCapChildWorldAdjust = StartCapChild.transform.localScale.x * StartCapChild.GetComponent<SpriteRenderer>().sprite.rect.width / 2f;
    float endCapChildWorldAdjust = 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 Line class to work with. In Unity, from the menu, select GameObject > Create Empty.  The object will appear in your Hierarchy panel. Rename it to Line and drag your Line 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 to StartCap, MiddleSegment, and EndCap. Drag them onto our Line 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 Line object so that you can see the script in the Inspector panel.  Assign the children to their appropriate slots and then drag the Line object into the Prefabs folder to create a prefab for it.  You can now delete the Line 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 LightningBolt class to handle creating our jagged lines.

using UnityEngine;
using System.Collections.Generic;

class LightningBolt : MonoBehaviour
{
    //List of all of our active/inactive lines
    public List<GameObject> ActiveLineObj;
    public List<GameObject> InactiveLineObj;
    //Prefab for a line
    public GameObject LinePrefab;
    //Transparency
    public float Alpha { get; set; }
    //The speed at which our bolts will fade out
    public float FadeOutRate { get; set; }
    //The color of our bolts
    public Color Tint { get; set; }
    //The position where our bolt started
    public Vector2 Start { get { return ActiveLineObj[0].GetComponent<Line>().A; } }
    //The position where our bolt ended
    public Vector2 End { get { return ActiveLineObj[ActiveLineObj.Count-1].GetComponent<Line>().B; } }
    //True if the bolt has completely faded out
    public bool IsComplete { get { return Alpha <= 0; } }
    public void Initialize(int maxSegments)
    {
        //Initialize lists for pooling
        ActiveLineObj = new List<GameObject>();
        InactiveLineObj = new List<GameObject>();
        for(int i = 0; i < maxSegments; i++)
        {
            //instantiate from our Line Prefab
            GameObject 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 void ActivateBolt(Vector2 source, Vector2 dest, Color color, float thickness)
    {
        //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)
        {
            Vector2 adjust = Random.insideUnitCircle;
            if(adjust.magnitude <= 0) adjust.x += .1f;
            dest += adjust;
        }
        //difference from source to destination
        Vector2 slope = dest - source;
        Vector2 normal = (new Vector2(slope.y, -slope.x)).normalized;
        //distance between source and destination
        float distance = slope.magnitude;
        List<float> positions = new List<float>();
        positions.Add(0);
        for (int 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();
        const float Sway = 80;
        const float Jaggedness = 1 / Sway;
        //Affects how wide the bolt is allowed to spread
        float spread = 1f;
        //Start at the source
        Vector2 prevPoint = source;
        //No previous displacement, so just 0
        float prevDisplacement = 0;
        for (int i = 1; i < positions.Count; i++)
        {
            //don't allow more than we have in the pool
            int inactiveCount = InactiveLineObj.Count;
            if(inactiveCount <= 0) break;
            float pos = positions[i];
            //used to prevent sharp angles by ensuring very close positions also have small perpendicular variation.
            float scale = (distance * Jaggedness) * (pos - positions[i - 1]);
            //defines an envelope. Points near the middle of the bolt can be further from the central line.
            float envelope = pos > 0.95f ? 20 * (1 - pos) : spread;
            float displacement = Random.Range(-Sway, Sway);
            displacement -= (displacement - prevDisplacement) * (1 - scale);
            displacement *= envelope;
            //Calculate the end point
            Vector2 point = source + (pos * slope) + (displacement * normal);
            activateLine(prevPoint, point, thickness);
            prevPoint = point;
            prevDisplacement = displacement;
        }
        activateLine(prevPoint, dest, thickness);
    }
    public void DeactivateSegments()
    {
        for(int i = ActiveLineObj.Count - 1; i >= 0; i--)
        {
            GameObject line = ActiveLineObj[i];
            line.SetActive(false);
            ActiveLineObj.RemoveAt(i);
            InactiveLineObj.Add(line);
        }
    }
    void activateLine(Vector2 A, Vector2 B, float thickness)
    {
        //get the inactive count
        int inactiveCount = InactiveLineObj.Count;
        //only activate if we can pull from inactive
        if(inactiveCount <= 0) return;
        //pull the GameObject
        GameObject line = InactiveLineObj[inactiveCount - 1];
        //set it active
        line.SetActive(true);
        //get the Line component
        Line lineComponent = line.GetComponent<Line>();
        lineComponent.SetColor(Color.white);
        lineComponent.A = A;
        lineComponent.B = B;
        lineComponent.Thickness = thickness;
        InactiveLineObj.RemoveAt(inactiveCount - 1);
        ActiveLineObj.Add(line);
    }
    public void Draw()
    {
        //if the bolt has faded out, no need to draw
        if (Alpha <= 0) return;
        foreach (GameObject obj in ActiveLineObj)
        {
            Line lineComponent = obj.GetComponent<Line>();
            lineComponent.SetColor(Tint * (Alpha * 0.6f));
            lineComponent.Draw();
        }
    }
    public void 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 the envelope 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 Line 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 Bolt, and drag a copy of the LightningBolt script onto it. Finally, click on the Bolt object and assign the Line prefab, from the Prefabs folder, to the appropriate slot in the LightningBolt script.  Once you're done with that, simply drag the Bolt 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 LightningBolt 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 LightningBolt 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 > C# Script.  Name your script DemoScript and open it.  Here's some quick code to get you started:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class DemoScript : MonoBehaviour 
{
    //Prefabs to be assigned in Editor
    public GameObject BoltPrefab;
    
    //For pooling
    List<GameObject> activeBoltsObj;
    List<GameObject> inactiveBoltsObj;
    int maxBolts = 1000;
    //For handling mouse clicks
    int clicks = 0;
    Vector2 pos1, pos2;
    void Start()
    {
        //Initialize lists
        activeBoltsObj = new List<GameObject>();
        inactiveBoltsObj = new List<GameObject>();
        //Grab the parent we'll be assigning to our bolt pool
        GameObject p = GameObject.Find("LightningPoolHolder");
        //For however many bolts we've specified
        for(int i = 0; i < maxBolts; i++)
        {
            //create from our prefab
            GameObject bolt = (GameObject)Instantiate(BoltPrefab);
            //Assign parent
            bolt.transform.parent = p.transform;
            //Initialize our lightning with a preset number of max sexments
            bolt.GetComponent<LightningBolt>().Initialize(25);
            //Set inactive to start
            bolt.SetActive(false);
            //Store in our inactive list
            inactiveBoltsObj.Add(bolt);
        }
    }
    void Update()
    {
        //Declare variables for use later
        GameObject boltObj;
        LightningBolt boltComponent;
        //store off the count for effeciency
        int activeLineCount = activeBoltsObj.Count;
        //loop through active lines (backwards because we'll be removing from the list)
        for (int i = activeLineCount - 1; i >= 0; i--)
        {
            //pull GameObject
            boltObj = activeBoltsObj[i];
            //get the LightningBolt component
            boltComponent = boltObj.GetComponent<LightningBolt>();
            //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
                Vector3 temp = Camera.main.ScreenToWorldPoint(Input.mousePosition);
                pos1 = new Vector2(temp.x, temp.y);
        	}
        	else if(clicks == 1) //second click
        	{
                //store end position
                Vector3 temp = Camera.main.ScreenToWorldPoint(Input.mousePosition);
                pos2 = new Vector2(temp.x, temp.y);
                //create a (pooled) bolt from pos1 to pos2
                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(int i = 0; i < activeBoltsObj.Count; i++)
        {
            activeBoltsObj[i].GetComponent<LightningBolt>().UpdateBolt();
            activeBoltsObj[i].GetComponent<LightningBolt>().Draw();
        }
    }
        void CreatePooledBolt(Vector2 source, Vector2 dest, Color color, float thickness)
        {
        //if there is an inactive bolt to pull from the pool
        if(inactiveBoltsObj.Count > 0)
        {
        	//pull the GameObject
        	GameObject boltObj = 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
        	LightningBolt boltComponent =  boltObj.GetComponent<LightningBolt>();
        	//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 DemoScript on.  From the menu, select GameObject > Create Empty.  The object will appear in your Hierarchy panel.  Rename it to DemoScript and drag your DemoScript script onto it.  Click on the DemoScript object so we can view it in the Inspector panel. Assign the Bolt prefab, from the Prefabs folder, to the matching slot in the DemoScript.

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 LightningBolt 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.

using UnityEngine;
using System.Collections.Generic;

class BranchLightning : MonoBehaviour
{
    //For holding all of our bolts in our branch
    List<GameObject> boltsObj = new List<GameObject>();
    //If there are no bolts, then the branch is complete (we're not pooling here, but you could if you wanted)
    public bool IsComplete { get { return boltsObj.Count == 0; } }
    //Start position of branch
    public Vector2 Start { get; private set; }
    //End position of branch
    public Vector2 End { get; private set; }
    static Random rand = new Random();
    public void Initialize(Vector2 start, Vector2 end, GameObject boltPrefab)
    {
        //store start and end positions
        Start = start;
        End = end;
        //create the  main bolt from our bolt prefab
        GameObject mainBoltObj = (GameObject)GameObject.Instantiate(boltPrefab);
        //get the LightningBolt component
        LightningBolt mainBoltComponent = mainBoltObj.GetComponent<LightningBolt>();
        //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)
        int numBranches = Random.Range(3,6);
        //calculate the difference between our start and end points
        Vector2 diff = end - start;
        // pick a bunch of random points between 0 and 1 and sort them
        List<float> branchPoints = new List<float>();
        for(int i = 0; i < numBranches; i++) branchPoints.Add(Random.value);
        branchPoints.Sort();
        //go through those points
        for (int 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)
            Vector2 boltStart = 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!)
            Quaternion rot = Quaternion.AngleAxis(30 * ((i & 1) == 0 ? 1 : -1), new Vector3(0,0,1));
            //calculate how much to adjust for our end position
            Vector2 adjust = rot * (Random.Range(.5f, .75f) * diff * (1 - branchPoints[i]));
            //get the end position
            Vector2 boltEnd = adjust + boltStart;
            //instantiate from our bolt prefab
            GameObject boltObj = (GameObject)GameObject.Instantiate(boltPrefab);
            //get the LightningBolt component
            LightningBolt boltComponent = boltObj.GetComponent<LightningBolt>();
            //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 void UpdateBranch()
    {
    	//go through our active bolts
    	for (int i = boltsObj.Count - 1; i >= 0; i--)
    	{
            //get the GameObject
            GameObject boltObj = boltsObj[i];
            //get the LightningBolt component
            LightningBolt boltComp = boltObj.GetComponent<LightningBolt>();
            //update/fade out the bolt
            boltComp.UpdateBolt();
            //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 void Draw()
    {
        foreach (GameObject boltObj in boltsObj)
        {
            boltObj.GetComponent<LightningBolt>().Draw();
        }
    }
}

This code works very similarly to our LightningBolt 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 DemoScript later on in the tutorial.

You may have noticed the reference to a GetPoint() function in the LightningBolt 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 LightningBolt 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 Vector2 GetPoint(float position)
{
    Vector2 start = Start;
    float length = Vector2.Distance(start, End);
    Vector2 dir = (End - start) / length;
    position *= length;
    //find the appropriate line
    Line line = ActiveLineObj.Find(x => Vector2.Dot(x.GetComponent<Line>().B - start, dir) >= position).GetComponent<Line>();
    float lineStartPos = Vector2.Dot(line.A - start, dir);
    float lineEndPos = Vector2.Dot(line.B - start, dir);
    float linePos = (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 a Texture2D 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
IEnumerator TextCapture()
{
    //must wait until end of frame so something is actually drawn or else it will error
    yield return new WaitForEndOfFrame();
    //get the camera that draws our text
    Camera cam = 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
        Texture2D image = 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
        const int interval = 2;
        //loop through pixels
        for(int y = 0; y < image.height; y += interval)
        {
            for(int x = 0; x < image.width; x += interval)
            {
                //get the color of the pixel
                Color color = image.GetPixel(x,y);
                //if the color has any 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 DemoScript later):

//go through the points we capture earlier
foreach (Vector2 point in textPoints)
{
    //randomly ignore certain points
    if(Random.Range(0,75) != 0) continue;
    //placeholder values
    Vector2 nearestParticle = Vector2.zero;
    float nearestDistSquared = float.MaxValue;
    for (int i = 0; i < 50; i++)
    {
        //select a random point
        Vector2 other = textPoints[Random.Range(0, textPoints.Count)];
        //calculate the distance (squared for performance benefits) between the two points
        float distSquared = 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 float DistanceSquared(Vector2 a, Vector2 b)
{
    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
List<GameObject> movingBolt = new List<GameObject>();

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

void Update()
{
    //loop through all of our bolts that make up the moving bolt
    for(int i = movingBolt.Count - 1; i >= 0; i--)
    {
        //get the bolt component
        boltComponent = movingBolt[i].GetComponent<LightningBolt>();
        //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.UpdateBolt();
        boltComponent.Draw();
    }

    //if our moving bolt is active
    if(movingBolt.Count > 0)
    {
        //calculate where it currently ends
        lightningEnd = movingBolt[movingBolt.Count-1].GetComponent<LightningBolt>().End;
        //if the end of the bolt is within 25 units of the camera
        if(Vector2.Distance(lightningEnd,(Vector2)Camera.main.transform.position) < 25)
        {
            //instantiate from our bolt prefab
            boltObj = (GameObject)GameObject.Instantiate(BoltPrefab);
            //get the bolt component
            boltComponent = boltObj.GetComponent<LightningBolt>();
            //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.UpdateBolt();
            boltComponent.Draw();
        }
    }
}

Burst Lightning

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

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

Step 8: Put It All Together in DemoScript

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 DemoScript 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: 

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class DemoScript : MonoBehaviour 
{
    //Prefabs to be assigned in Editor
    public GameObject BoltPrefab;
    public GameObject BranchPrefab;
    
    //For pooling
    List<GameObject> activeBoltsObj;
    List<GameObject> inactiveBoltsObj;
    int maxBolts = 1000;
    float scaleText;
    Vector2 positionText;
    //Different modes for the demo
    enum Mode : byte
    {
        bolt,
        branch,
        moving,
        text,
        nodes,
        burst
    }
    //The current mode the demo is in
    Mode currentMode = Mode.bolt;
    //Will contain all of the pieces for the moving bolt
    List<GameObject> movingBolt = new List<GameObject>();
    //used for actually moving the moving bolt
    Vector2 lightningEnd = new Vector2(100, 100);
    Vector2 lightningVelocity = new Vector2(1, 0);
    //Will contain all of the pieces for the branches
    List<GameObject> branchesObj;
    //For handling mouse clicks
    int clicks = 0;
    Vector2 pos1, pos2;
    //For storing all of the pixels that need to be drawn by the bolts 
    List<Vector2> textPoints = new List<Vector2>();
    //true in text mode
    bool shouldText = false;
    void Start()
    {
        //Initialize lists
        activeBoltsObj = new List<GameObject>();
        inactiveBoltsObj = new List<GameObject>();
        branchesObj = new List<GameObject>();
        //Grab the parent we'll be assigning to our bolt pool
        GameObject p = GameObject.Find("LightningPoolHolder");
        //For however many bolts we've specified
        for(int i = 0; i < maxBolts; i++)
        {
            //create from our prefab
            GameObject bolt = (GameObject)Instantiate(BoltPrefab);
            //Assign parent
            bolt.transform.parent = p.transform;
            //Initialize our lightning with a preset number of max sexments
            bolt.GetComponent<LightningBolt>().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());
    }
    void Update()
    {
        //Declare variables for use later
        GameObject boltObj;
        LightningBolt boltComponent;
        //store off the count for effeciency
        int activeLineCount = activeBoltsObj.Count;
        //loop through active lines (backwards because we'll be removing from the list)
        for (int i = activeLineCount - 1; i >= 0; i--)
        {
            //pull GameObject
            boltObj = activeBoltsObj[i];
            //get the LightningBolt component
            boltComponent = boltObj.GetComponent<LightningBolt>();
            //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
                Vector3 temp = Camera.main.ScreenToWorldPoint(Input.mousePosition);
                pos1 = new Vector2(temp.x, temp.y);
            }
            else if(clicks == 1) //second click
            {
                //store end position
                Vector3 temp = Camera.main.ScreenToWorldPoint(Input.mousePosition);
                pos2 = new Vector2(temp.x, temp.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
                    	GameObject branchObj = (GameObject)GameObject.Instantiate(BranchPrefab);
                    	//get the branch component
                    	BranchLightning branchComponent = branchObj.GetComponent<BranchLightning>();
                    	//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
                            Vector2 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(int 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)GameObject.Instantiate(BoltPrefab);
                    	//get the bolt component
                    	boltComponent = boltObj.GetComponent<LightningBolt>();
                    	//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)
                    	Vector2 diff = pos2 - pos1;
                    	//define how many bolts we want in our circle
                    	int boltsInBurst = 10;
                    	for(int i = 0; i < boltsInBurst; i++)
                    	{
                            //rotate around the z axis to the appropriate angle
                            Quaternion rot = Quaternion.AngleAxis((360f/boltsInBurst) * i, new Vector3(0,0,1));
                            //Calculate the end position for the bolt
                            Vector2 boltEnd = (Vector2)(rot * diff) + 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(int i = branchesObj.Count - 1; i >= 0; i--)
        {
            //pull the branch lightning component
            BranchLightning branchComponent = branchesObj[i].GetComponent<BranchLightning>();
            //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.UpdateBranch();
            branchComponent.Draw();
        }
        //loop through all of our bolts that make up the moving bolt
        for(int i = movingBolt.Count - 1; i >= 0; i--)
        {
            //get the bolt component
            boltComponent = movingBolt[i].GetComponent<LightningBolt>();
            //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.UpdateBolt();
            boltComponent.Draw();
        }
        //if our moving bolt is active
        if(movingBolt.Count > 0)
        {
            //calculate where it currently ends
            lightningEnd = movingBolt[movingBolt.Count-1].GetComponent<LightningBolt>().End;
            //if the end of the bolt is within 25 units of the camera
            if(Vector2.Distance(lightningEnd,(Vector2)Camera.main.transform.position) < 25)
            {
                //instantiate from our bolt prefab
                boltObj = (GameObject)GameObject.Instantiate(BoltPrefab);
                //get the bolt component
                boltComponent = boltObj.GetComponent<LightningBolt>();
                //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.UpdateBolt();
                boltComponent.Draw();
            }
        }
        //if in text mode
        if(shouldText)
        {
            //go through the points we capture earlier
            foreach (Vector2 point in textPoints)
            {
                //randomly ignore certain points
                if(Random.Range(0,75) != 0) continue;
                //placeholder values
                Vector2 nearestParticle = Vector2.zero;
                float nearestDistSquared = float.MaxValue;
                for (int i = 0; i < 50; i++)
                {
                    //select a random point
                    Vector2 other = textPoints[Random.Range(0, textPoints.Count)];
                    //calculate the distance (squared for performance benefits) between the two points
                    float distSquared = 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(int i = 0; i < activeBoltsObj.Count; i++)
        {
            activeBoltsObj[i].GetComponent<LightningBolt>().UpdateBolt();
            activeBoltsObj[i].GetComponent<LightningBolt>().Draw();
        }
    }
    //calculate distance squared (no square root = performance boost)
    public float DistanceSquared(Vector2 a, Vector2 b)
    {
        return ((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
    }
    void CreatePooledBolt(Vector2 source, Vector2 dest, Color color, float thickness)
    {
        //if there is an inactive bolt to pull from the pool
        if(inactiveBoltsObj.Count > 0)
        {
            //pull the GameObject
            GameObject boltObj = 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
            LightningBolt boltComponent =  boltObj.GetComponent<LightningBolt>();
            //activate the bolt using the given position data
            boltComponent.ActivateBolt(source, dest, color, thickness);
        }
    }
    //Capture the important points of our text for later
    IEnumerator TextCapture()
    {
        //must wait until end of frame so something is actually drawn or else it will error
        yield return new WaitForEndOfFrame();
        //get the camera that draws our text
        Camera cam = 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
            Texture2D image = 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
            const int interval = 2;
            //loop through pixels
            for(int y = 0; y < image.height; y += interval)
            {
                for(int x = 0; x < image.width; x += interval)
                {
                    //get the color of the pixel
                    Color color = image.GetPixel(x,y);
                    //if the color has any 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.

Students Save 75% on a Yearly Tuts+ Subscription

$
0
0

You have until September 30th to grab a student subscription at our special price of just $45! Thanks to our friends at PayPal, we’re pleased to bring you this fantastic discount to help you with your studies.

With over 400 video courses in a range of topics, we know you’ll find something to help you with your project. Learn a new skill, brush up on an old one, or discover a new hobby across a range of different categories:

  • Photography
  • Code
  • Web Design
  • Design & Illustration

Here's a quick example from one of our recent courses on photography:


Our Tuts+ Student Subscription entitles you to unlimited course access, removes ads from all our tutorials, and gives you five eBook downloads per month. All this, plus access to our special subscriber benefits and discounts.

To be eligible for this amazing offer, you must have a valid student email ending in .edu or .ac, or upload a picture of your current student ID.

Claim your subscription today here. Hurry, as there are less than 500 to go!

Student Icon designed by Wilson Joseph.

6 Games That Succeed Because They're Interesting, Not Because They're Fun

$
0
0

Imagine if you designed your games to be interesting, rather than fun. That might sound a little counter-intuitive; you might say, "if a game is fun, wouldn't that already make it interesting, in some respect"? That's true, but I'm talking about giving the player a drive to play the game outside of simply completing the goals you have set. The player also plays to experience and explore the game itself, to see what the world you have created has to offer. 

This has far more retaining power than straight up fun, and is amazing (thanks to the huge synergy bonus) when combined with fun.

It's hard to get a grasp on the fuzzy concept of "interesting", so rather than try to define it, let's look at six great examples of games that are "interesting", and at how they used "interest" to drive and support the game.

Note: This article contains spoilers for the following games:

If you haven't played them yet, I recommend you do so before reading on; they're all excellent experiences that deserve to be played through with fresh eyes—and they have plenty of lessons to teach any game designer.

The Stanley Parable

The Stanely Parable, on paper, is not a "fun" game. You walk through hallways, you get talked at the whole time, and you get different endings based on whether you walk down one hallway or another. Then you play again and walk down the hallway you didn't walk down before. Sounds boring. 

However, the designers used a powerful tool to keep the player interested in the game: player agency curiosity. It makes all the difference.

Player Agency Curiosity

This refers to the state where the player is engaged with the game by poking and prodding it—sometimes in hope of breaking the game, sometimes to see how it reacts, sometimes to find the limits or seams of the system, and so on. 

When trying to harness this style of engagement, you must be able to predict what the player will try to do and what they are thinking. When they try to do something, your game must react to it, and these reactions must also provoke some sort of reaction from the player: fear, laughter, mystery, a clue, or even just a little Easter egg.

The Stanley Parable makes amazing use of this. The design tightly restricts the possible interactions for the player, and so the designers are able to predict what the player will do at any point. Because of this, they are able to be proactive in their reactions to player choices. 

The best example of this is in the iconic two door room the player is presented with at the start of the game. The game tries to tell you the "story" of the game by saying "Stanley, when presented with two doors, walked through the left door." A player might say "Aha—nice try, game, but I won't let you control me!" and walk through the right door. However, the game recognizes this mindset and both acknowledges and responds to this by telling the player that they aren't following the proper story. 

It's a small thing, but it makes the player want to see how far they can go: if I do this, what will happen? If I do that, is anything going to happen?

Frog Fractions

Frog Fractions is an educational game about fractions... sort of. It doesn't look like anything you haven't seen before. The gameplay is predictable; you know what you're getting as soon as you start playing it. You're a frog that needs to eat some bugs. Simple. But the game knows this, and it uses whatever assumptions you may have made about its gameplay against you in a brilliant way, through its unfolding gameplay.

Unfolding Gameplay

This is the act of constantly introducing new concepts and mechanics into your game the farther into your game the player gets. Games that use this device generally start out boring or mundane or predictable, but they need to. This is because when the player is in that state of boredom, they will perk up to anything new, and anything unusual.

Once the player starts to see the game expanding, they begin to wonder what else it has to show them. They want to know where this rabbit hole of a game leads. And since the game starts out as mundane, stale, and boring, when more of the game is revealed it's a big juxtaposition against what they were playing previously. This allows mediocre gameplay to become so much more, because each new gameplay element is new, shiny, and interesting.

I really hope you played the game before you read to this point. This is a one-time surprise.

In Frog Fractions, this first instance of unfolding generally happens at the perfect moment. The gameplay mechanics seem to boil down to eat some bugs, catch some fruit, unlock new power-ups so you can be more efficient at the process. It's nothing new and is easy to understand (aside, perhaps, from the quirky humor the game has). 

However, that idea of the player having a full grasp on what the game is is quickly turned on its head as soon as the player moves downward just a little too far and bam. There are infinitely many pieces of fruit underwater! This immediately tells the player, "this game isn't about upgrading; it isn't about what you thought it was." Through this, the game creates a new rush of interest because now the game is broken—or is it? What is this game trying to do? The player continues to play to answer these questions.

Shadow of the Colossus

Shadow of the Colossus has a lot going on under the hood. Its a game about traversing over expansive lands that is home to the colossi (giant enemy beasts). The game doesn't have a lot of conflict; there are only 16 enemies in the whole game. For each enemy, you must do the same thing: travel to the colossus, figure out how to get on top of the beast, find its weak points, kill, repeat. 

Now, while that is simple when written, the game and how it was pieced together makes it insanely compelling because of differences in kind.

Differences in Kind

This is a term I picked up from the lovely Extra Credits team. It refers to the concept of a game changing tones throughout, in order to break up similar gameplay, to make sure that the player doesn't get fatigued from the gameplay—to give them a break of sorts. It is also used to make certain aspects of the game more impactful. Action, for instance, is much more refreshing when you are able to take a small break instead of having a constant barrage of endless fights until the game ends.

Shadow of the Colossus uses differences in kind beautifully. Since each of the battles with the colossi are intense puzzle/action segments, the player would quickly become tired if they were constantly playing this segment of the game. To prevent this, the fights are separated by calming, meditative traveling segments. These allow for the player to take a break from the action while also creating a sense of anticipation, adding additional value to each of the fights.

This is the absolute opposite of high stakes, intense puzzle-solving action.

Differences in kind, when used properly, can also create different "lenses" through which to view the game itself. In Shadow of the Colossus, you view the game through both the lenses of action and calm. It's a hard feeling to describe, but a game that can be viewed through multiple lenses feels fuller, like more of a complete world. Games that have one lens tend to be games that feel more traditionally game-like. 

While it's no bad thing to not have differences in kind, just know that it is a tool in your arsenal that won't (in most cases) diminish, only create additional value.

Corrypt

Corrypt is a weird game. It starts out normal enough: it's a down-to-earth Sokoban box game. Nothing special about that. But once you get far enough, you obtain magic. However, this magic actually ends up breaking the game, in some ways making it unplayable. This gives the player repercussions for agency.

Repercussions for Agency

What made this game absolutely brilliant for me was how it used a simple mechanic to expand the game in ways I didn't even know were possible before playing. You can use magic in order to permanently freeze a tile; when you go to any other room that tile will stay as what it was when you froze it.

This made me personally care about the repercussions of what I decided to do as a player. That is extremely important: the player themselves is invested in the decisions they make, but not through a binary "kill him or save him" situation where the outcome changes one ending to a different ending. No—this game made me stop and think, "if I freeze this tile what will happen? Is this smart? What rooms are going to be impossible to solve because of this?"

Some of the most nerve-wracking decisions I've ever made in a game.

While having the decisions themselves be important to the player is significant, nothing is more essential than having those decisions impact the gameplay itself. In this game, the player's choices can change the layout of every room from then on. However, the effects doesn't always have to be as prominent. For example, in that hypothetical "kill him or save him" scenario, perhaps the player's character could be mentally scarred if they were to choose to kill, which in turn could make their accuracy go down, giving the choice a meaningful gameplay repercussion. If the player chose to save him, he could become a merchant that sells you some valuable items. These repercussions are simple, but far more meaningful to a player than a selection of two cut scenes.

Papers, Please

In Papers, Please, you play as a border control worker who spends their days checking passports for authenticity. That is the game. It would have probably been completely mundane as well, if the developer hadn't put such care into making sure the game was constantly evolving, thus requiring perpetual mastery of the system from the player.

Perpetual Mastery of the System

This is when a game constantly changes how it is played. What makes this different from unfolding gameplay is that the genre and style of the game never changes. The actual changes are small—little additions to already established mechanics that make the player use what they've learned and apply it to different situations. Although they have mastered the actual mechanic itself, by being used in different situations the application of that mastered knowledge will change.

This isn't a new concept. Nearly any decent game uses this concept: Mario, Dark Souls, and The Last of Us are all good examples. However, this isn't something that every developer grasps. Artificial difficulty by way of increasing numbers does not create perpetual mastery; in these cases, the player has mastered the system but just has to continue executing what they have learned. This quickly becomes boring, and is a large part of why many games fail.

In Papers, Please the game requires perpetual mastery by changing how documents work. There's different paperwork for different countries, different occupations, and so on. As soon as the player has a grasp on the most recent twist on the mechanic, something new is thrown in. On top of that, there are a few special characters thrown in to make sure that the player never falls into a routine and can easily "game" the game.

Calm Time

Calm Time is a horror game with a unique twist. It isn't scary, in a normal sense. It's slow paced, twisted, and methodical. It places the player into the shoes of what would normally be the object of fear: a killer who has gathered their victims under the pretense of a dinner party. Throughout the game, you must kill each guest one by one, as they plead for their lives and run to live maybe just a second longer. It's a great example of forced perspective storytelling.

Forced Perspective Storytelling

This concept refers to when the game forces you, as the player, to tell the story of the game through the mechanics. This means that the mechanics and the player's use of them places the player in a certain mindset. Their goals align with the character's goals. 

In Calm Time this becomes unsettling—not because of the actions of the character, but because of the goals of the player. The player's goal is to kill all the guests they have invited to their home. As the game goes on, it might become fun, tedious, or just a mindless action to complete the game. But through this we also come to completely embody the mindset of the character. The character becomes a mirror onto ourselves. He is clearly insane because he find this act of murder fun, tedious, or just a mindless action.

I never actually finished this game; I got too creeped out.

This is different to roleplaying, since instead of us choosing our actions based on the character we want to be, our character is defined by the actions we take and our reasons for taking said actions.

Fun Without Purpose

There are plenty of games that feel like they should be fun on paper, but aren't when actually played. We see this a lot in clones, which end up copying the "fun" gameplay, without much else of what made the original interesting. The game has the same ideas, but the execution falls flat. Why is that? 

It's because these games mostly copy the games on a mechanical level, but they fail to understand how those mechanics were used in the games to make them compelling to play.

If you are going to try to remake a game, you must not only recreate the mechanics, you must also recreate the design. Games do not exist as a jumble of mechanics thrown together to make a game. Design is what gives the mechanics meaning, gives the player purpose, and creates the reason for the player to play your game.

Conclusion

Games can be fun, don't get me wrong, but it is important to remember that that's not all games can be. Games can be compelling in other ways, through use of intelligent design. Games can be interesting without having to overstimulate the player with explosions and the like. 

There are many ways to go about this; I have only listed but a fraction of what is possible in games. Don't restrict yourself by thinking, "people won't play my game if it isn't fun." If your game is interesting, people will enjoy it.

References

How to Run Your Own Game Jam

$
0
0

Game jams are a great way to get some game development experience, meet other developers, and, ever importantly, have some fun. No game jams in your area? No problem! This simple guide will have you running your own game jam in no time.

What Is This "Game Jam" You Speak Of?

Participants at a game jam

Simply put, a game jam is an event where people get together for a set period of time with the goal of creating a playable game. The details are open to a lot of creative variation: different lengths of time, online vs. physically located, etc. 

The most typical format that I'm personally familiar with is the 48-hour-long game jam that takes place in a physical location, over a weekend, from Friday evening until Sunday evening. This is the format of OrcaJam, the game jam that I've been helping run for the past few years in Victoria, BC, Canada, and it is the format that I'll be discussing here—but feel free to consider other variations that may fit your own needs more closely.

To Jam or Not to Jam?

A game jam in progress

Maybe you're considering starting up your own local game jam but you're wondering whether it will be worth all of your time and effort (and don't get me wrong, it will require some time and effort to pull it off successfully). Allow me to convince you. Here is a quick list of some of the potential benefits that come from a game jam:

  • You're free to make a game without outside restrictions.
  • It provides networking opportunities with other developers.
  • You can find potential collaborators for projects.
  • You'll practice your game making skills.
  • You can try out a different game creation role (artist, designer, programmer).
  • You'll get feedback on your game.
  • You can see how other gamedevs make games.
  • It's an opportunity to socialize.
  • It's an excuse to stay up really late.
  • You'll have some fun!

Did I mention fun? Making games is a ton of fun. Whether you're a novice game creator who's wondering what all the fuss is about, or a seasoned veteran who's been making games all her life, making games can be enormously enjoyable. And making games at a game jam can be even more enjoyable, because you're doing it for the sheer joy of it. 

In a game jam, there's no pressure to make something that will sell a million copies or even make a lot of sense to the average person. It's a great way to get back in touch with making games for fun, and to attempt those wacky projects that you wouldn't normally think twice about.

All right, you're probably thinking to yourself, that's great for attendees—but what do I get out of it as an organizer? 

That's a good question. As the game jam organizer, you not only get to experience the benefits that everyone else does (as listed above), you also get some other sweet perks. For one thing, you get the satisfaction of giving back to your local game community. 

If that's not enough, you also get a chance to experiment with the flavor and results of your game jam by setting the theme and activities within the jam itself. Witnessing the games created at the end of the jam is a pretty fantastic experience. Witnessing the resulting games created from the jam that you organized yourself is even better.

When to Host Your Game Jam

Orcajam Poster

Now that you're keen on starting a game jam, the first question that you should ask is: When should I run this crazy thing? You might be all keen to jump in and announce your game jam for next weekend, but that would be unwise. 

You'll need to give yourself enough time, not just to plan and organize all the details, but also to get the word out to potential participants so that they can dedicate a weekend to making games (which is not always easy, if you have family and other responsibilities to juggle). I recommend giving people at least a month's notice, and giving yourself even longer to work out the plan of attack.

Since game jams are typically indoor activities, you can easily book your jam for any season of the year. But keep in mind certain special dates and book around them to ensure good attendance:

  • Widely celebrated holidays
  • Dates of other popular game jams
  • Dates of major game conferences
  • Dates of major sporting events (Don't laugh. Try booking hotels when home games are on.)

For OrcaJam, we've booked weekends in August and September close to PAX Prime to get indies from out of town to drop by for our jam. Victoria is super-close to Seattle, so it's easy to get here from there.

Location, Location, Location

Game jam space

Now that you have your date worked out, you'll probably be asking: where should I put the darn thing? Finding the right space for a game jam can be tricky. Some things to consider when choosing a space: the number of potential attendees, your event budget, 24-hour accessibility, and so on.

One approach is to partner with educational institutions such as community colleges, universities, or trade schools. Look for ones that include game-related courses—computer science, art, game design—and approach those faculties directly, asking them to donate space to hold a game jam. They’re often willing to do so, but may have requirements for you to meet. For example, you may be required to hire extra security guards if you want the space to be accessible during off hours.

Other organizations that you might look at include not-for-profit organizations with a focus on technology, art, or business. For OrcaJam, we've partnered with a local outfit called the Victoria Advanced Technology Council (VIATeC) who have an interest in promoting the local technology sector. They've donated their space for our jams and helped promote it in the area. These organizations are usually easy to find with a bit of online searching or by talking with folks in the industry.

If you don't have any willing organizations nearby to partner with, hotels can be a good choice for game jams. They often have meeting or conference rooms that are large enough to hold a decent sized crowd, 24-hour access, and rooms with beds when consciousness is no longer an option. The downside is that they can cost a lot money, especially for the more upscale venues. 

A few years back OrcaJam was held at a local hotel at a cost of $1,200 CDN for the weekend, which was mid-range for the city of Victoria, where the prices range from $400 to $2,500 CDN. In addition to room rental cost, some venues will require spending a mandatory minimum amount on catering, which brings us to our next subject: food.

Fuel for the Gray Cells

People eating at a game jam

Providing food isn't a requirement of game jams, but it's a welcome option if it fits into your budget. At least providing snacks and beverages is a good idea. If you're permitted outside food at your venue, you may want to check your local grocery store for prepared food trays featuring cold cuts, cheeses, crackers, fresh fruit and the like: satisfying finger food that isn't too messy. You’ll want to try to minimize any clean-up that you need to do after the fact.

If you go the full meal route, remember that that will probably include two breakfasts, two lunches, and three dinners over the course of 48 hours, depending on how you schedule things. Keep in mind that your jammers may have special nutritional needs, so plan to have some gluten-free, vegetarian, and similar options available. 

For the last OrcaJam, we bought pizza, sushi, sandwich and wrap platters, muffin and pastry platters, fresh fruit and vegetables, and lots of pop, juice, coffee, and tea. For 80 attendees, it cost around $2,700 CDN.

Warning: Hotels often have a “No Outside Food” policy, so if you want to provide anything for your jammers, you’ll need to make use of their internal catering services. Catered food can be pretty expensive, often equalling the cost of the room rental, so you’ll want to be sure to budget for it. I recommend trying to get a deal with the hotel when booking the space to receive catering at a discount. More often than not, businesses will give you a deal, especially if you can guarantee a good turnout; and besides, it never hurts to ask.

Funding Fundamentals

Scene from OrcaJam 2012 game jam

You may be asking yourself how you're going to pay for all of this. Selling tickets to the jam is one way to help cover your costs, but finding the right amount to charge can be tricky. You want to be sure that you charge enough to recover some of your costs, and also make it appealing to prospective jammers.

Charge too much and people won’t pay it, but charge too little and people will think that your jam isn’t worthwhile. For OrcaJam, we charged $20 per ticket and offered a free event T-shirt. 

Tip: Offering early-bird bonuses like ticket discounts or prizes is a good way to get an idea early on of how many people will be attending. For past OrcaJam ticket sales, we've used Meetup.com and Eventbrite and had a good experience with each. I recommend doing a little research on these and other sites to make sure you find one that fits your needs.

A great way to fund your jam is to find sponsors. Sponsorship can come from any number of different sources. OrcaJam has received funds from schools, local game companies, and other non-game companies. Besides money, companies can also donate jam space, hardware, game development software, and door prizes for the jammers. In return, they usually only want to be listed as sponsors at the event and have their logos prominently displayed. The last OrcaJam had six local game companies as sponsors who each contributed $1,000 to the cause.

But how do you find sponsors in the first place? Sometimes, all that’s required is putting the word out to folks who are part of your local game development community. If you're involved with a local group and have been getting out to some game events and conferences and networking with people there, you should have a pretty good idea of who to talk to. 

Some of the past OrcaJam sponsors just came from chance encounters with people who happened to be looking for sponsorship opportunities. It goes to show: never doubt the power of networking. Sometimes it can pay off in a big way.

Volunteers Are Your Friends

Running a game jam can be a lot of work, especially if you try to do everything yourself. Why not save your health and sanity and enlist some help? Often other people will be happy to help out in exchange for a free pass to the game jam and a little recognition. Some folks will be looking for some volunteer experience to jazz up their resumes. 

OrcaJam typically has about a dozen volunteers who help out over the weekend. We have at least two people staffing the registration desk at the entrance most of the time and at least one person there for the night shift. Other folks help with setup and cleanup, laying out meals, and whatever else needs doing. Their contributions make the whole event run more smoothly and make it a lot more fun for everyone involved. 

Special Features

Speaker panel at a game jam

Let's see, you've got a game jam location, some funding, a bit of food planned, and some volunteers to help out. You've got the makings of a good jam. But what can you add to make it a great jam?

Theme

You should decide on a one-time theme for your jam. It can be anything you like, from one-word themes such as "Preserves" to abstract ideas like "We don't see things as they are, we see them as we are." (These were actual game jam themes.) Themes help to foster creativity by providing restrictions that can inspire and challenge the teams to make fun projects that incorporate them.

Keynote Speaker

Bringing in someone at the beginning of the jam to talk about making games and get the jammers excited about it is a great way to start things off. Think of someone you know who makes games and is a decent speaker, and send them an invitation. We had Christer "McFunkypants" Kaitila give a keynote last time and he really got the party started.  

Panels/Talks

It's useful to give your jammers an occasional break during the weekend to keep them fresh. I find a great way to do this is to have a couple of events scheduled, one type being a panel or talk featuring game creators. Popular subjects include game design tips, DIY game publishing, and whatever else strikes your fancy. 

Note: You may wish to not schedule anything for Sunday, as usually the groups are too focused on getting the last features in before the big reveal.

These are just a few ideas that you can adopt for your own jam. Bear in mind that anything that you add will require more organizing on your part, so be sure not to overload yourself.

Grand Finale

Game showcase at a game jam

One of the best parts of a game jam is when everyone finally puts their tools down and gets ready to show off the results of all of their hard work. Each group should be introduced and then allowed to do a brief show and tell of their game, perhaps getting volunteers from the audience to try playing it. 

Be sure to book plenty of time for this, as you don't want people to feel rushed. For OrcaJam, we not only scheduled dinner right before this, but we also brought in a keg of beer for this time so that all jammers (of legal drinking age) could relax, have some food, enjoy a beer if they wished, and check out the great games. It's a great way to end a fun weekend.

In a Nutshell

Finale of a game jam

So there you have it: all you need to get your own game jam under way. If you have more questions, there are plenty of resources on the internet, including pages for existing and well-known jams such as Ludum Dare and OrcaJam

If you’re looking for a game jam to jump into and be a part of, the Global Game Jam is just around the corner in January. You might want to check out its website to find locations near you. Or, if none are close by, you may just want to host one yourself. Because now you can.

We’re Translating Tuts+!

$
0
0
Announcing the Tuts Translation Project

Up until recently, all the tutorials on Tuts+ (there are nearly twenty thousand!) had one thing in common: they were all published in English. And that’s strange when you think about it, because our community is nothing if not global. 

We often get requests for translations via social channels, email, and in tutorial comments, and we also know that community members are already translating our tutorials so they can share them with their fellow countrymen and women. In response, we’ve launched the Tuts+ Translation Project, to meet the demand and share the hard work of our generous community members.

Now it’s possible for volunteers to translate whichever written tutorials they please. We take the translations, give them the once-over, then publish them so that they’re accessible via the original posts.

Round of Applause

The response has been phenomenal. After a few initial requests via social media, over 60 volunteers have been in touch, publishing almost 200 translations in more than a dozen languages between them!

It’s Yours; Use It

Every written tutorial on the Tuts+ network is open for translation. If you feel you can help others by converting one into your mother tongue, find the link in the sidebar of a post you’ve enjoyed and let us know!

This project does rely on the generous nature of our community members (thank you) though translators are given full credit for their work. In fact, many have said that translating Tuts+ content is the best way to learn, so there’s certainly something to be gained by taking part.

Table Toppers

We’re very grateful to all who have volunteered, but a special mention has to go to these three individuals who’ve gone above and beyond in helping out with the project:

  1. Erick Patrick (Portuguese) 35 translations
  2. Lourdes Lopez Jimenez (Spanish) 11 translations
  3. Thoriq Firdaus (Indonesian) 8 translations

Languages

We’re continually adding more languages and translations to the network. Here are some of our favourites (with their ISO codes handily noted next to them):

More Multilingualism

To find out more take a look at the Tuts+ translation project information page, sign up for newsletter updates and please let us know if you have ideas for improving the project further!

Understanding Licenses, or, "Can I Use This Asset In My Game?"

$
0
0

Asset licensing is serious business. In a multi-billion dollar industry where thousands of hands come together under a single banner to create a single product, there should be no doubt as to who owns the art, code, and sounds associated with a game. But what about smaller developers that have to rely on externally licensed game assets? 

Being an independent developer, you cannot afford to take any legal risks with licensed content. The rising popularity of asset stores and open source libraries has created an even larger need for a better understanding of game asset licensing. In this article, we take a look at the basics of copyright licensing for independent game developers.

Disclaimer

I want to preface this article by stating that I am not a lawyer, I did not go to law school, and this is not legal advice. If you are seriously concerned about any licensing issues that may affect your game, then you should contact a legal professional. This article can only get you acquainted with the basics of licensing and is not meant to serve as a comprehensive legal resource.

You should always seek independent financial advice and thoroughly read terms and conditions relating to any insurance, tax, legal, or financial issue, service, or product. This article is intended as a guide only.

Can You Use This Asset in Your Game?

There are many intricacies involved with copyright licensing, but you can ask yourself a few important questions at the start before diving into the complexities of individual license requirements:

1. Did You Retrieve This Asset From a Reputable Source?

A reputable source will provide you with the proper licensing and attribution info for your asset as well as general information and FAQs regarding asset licensing. Examples of reputable sources include OpenGameArt.org, the Unity Asset Store, and Envato Market. Examples of non-reputable sources include Google image search and random forum posts.

1a. No, I Retrieved This Asset From a Non-Reputable Source

If the asset you want to use was not retrieved from a reputable source, then you should check to see whether that same asset is available from one of the many reputable asset sources available. You should also try to contact the creator of the asset directly, if you cannot verify the legitimacy of the asset. 

If you are unable to verify the license of this asset, then you cannot legally use it in your game.

1b. Yes, I Retrieved This Asset From a Reputable Source

Continue to the next question.

2. Was a License Provided With the Asset?

A reputable source will usually provide you with the proper licensing information for the asset. The type of license supplied will determine how you are allowed to use the asset.

2a. No, a License Was Not Provided With the Asset

Contact the creator of the asset and/or the host of the asset for licensing information. If your are unable to obtain licensing information, then you cannot legally use the asset in your game.

2b. Yes, a License Was Provided With the Asset

You can legally use this asset in your game as long as you follow the individual terms of the licensing agreement. Continue reading for information on the types of licenses your are most likely to encounter.

Copyright: The Basics

Copyright is a bundle of rights that govern intellectual property. As an example, a spaceship sprite drawn by an artist on your favorite asset store is the intellectual property of that artist. That sprite is legally protected by copyright law. Through a public licensing agreement, that artist can grant non-exclusive permissions that will allow you to use that sprite in your creation (a derivative work). 

Non-exclusive public license agreements do not require a written contract, but the licenses are still protected under copyright law. By using the asset, you are thereby agreeing to the licensing agreement terms.

Creative Commons (CC) Licenses

By using assets from asset stores or an online asset library, you will mostly be subject to a particular form of public copyright license known as a Creative Commons (CC) license. Creative Commons is the most common type of non-exclusive public license and can never be revoked by the creator. CC licenses consist of a combination of different conditions:

Attribution (BY)– You can use this asset (or a version of it that you have modified) in your game if you give proper credit to the creator/licensor. Proper credit in this case is defined by the creator/licensor.

Share-Alike (SA)– You can use this asset (or a version of it that you have modified) in your game, but your game must be released under a similar license to the asset in question.

Non-Commercial (NC)– You can use this asset (or a version of it that you have modified) in your game, but only in completely non-commercial situations.

No Derivative Works (ND)– You can use this asset in your game, but only if the original work is not modified in any way.

An example of a CC license utilizing a combination of these conditions would be aCC BY-NC license, where the creator of the asset requires proper credit and the derivative work may not be used to generate a profit.

Visit the official Creative Commons website for a complete overview of CC licenses.

The Importance of Attribution

If your CC licensed asset requires attribution, you are required to give proper credit to the creator/licensor somewhere in your game. The creator/licensor gets to determine how the asset is credited, and you will be in violation of the license agreement if you do not adhere to the requirements. 

When obtaining licensed content from an asset store or library, you will be provided with the details of the license. These details should also include the attribution requirements. If the attribution requirements are not provided with a CC-BY licensed asset, it is up to you to contact the creator/licensor for clarification. If you have exhausted all efforts to attain the original attribution requirements for an asset, you can follow the official Creative Commons best practices for attribution guidelines.

Software Licenses

Aside from licensing art and music, you may also be interested in licensing actual code/software for your game. For software licenses, you will be subject to any number of software licenses, including these free licenses.

GNU General Public License 3.0 (GPL)

The GPL is a free software license that grants users the right to modify and share the original work with a few important requirements. If you release a game that contains any contented licensed by the GPL, then you must also release the source code for your game and provide users with a copy of the GNU GPL license terms and copyright notice.

There are three major versions of the GPL license. Be sure to verify the particular conditions of your license.

Official GPL website

MIT License

This is a free software license used in Mono development libraries, jQuery, Ruby on Rails, Lua, Node.js, and Expat, among others. Software and code licensed under the MIT license can be reused if a copy of the MIT license terms and copyright notice are included with the end product. The MIT license is GPL-compatible.

Official MIT License website

Apache License

Version 2.0 of the Apache license is a GPL-compatible software license, much like the MIT license, that requires the inclusion of the copyright notice and a specific disclaimer.

Official Apache License website

WTFPL

The WTFPL is a license that was created as an alternative to other software licenses. Under this license, you are allowed to "Do what the F you want to" with the licensed content. This is the least restrictive license available; any assets licensed under WTFPL can be used in any way and attribution is not required.

Official WTFPL website

Copyright and Licensing: What’s the Big Deal?

License Violations: The Law

To put it simply: violating copyright law can cripple your project and ruin your game development career. Even the smallest copyright violation can have serious consequences. Both accidental and intentional violations are subject to the same penalties, so ignorance of the law is no excuse inside the courtroom. The legal penalties for copyright infringement are numerous:

  • Up to 5 years imprisonment
  • Criminal fines of up to $250,000 per offense
  • Up to $150,000 fine for each work infringed
  • An injunction to prevent the sale and distribution of your game 
  • Payment of all attorney fees and court costs, and for damages and lost profits

License Violations: The Ethics

By violating copyright law and licensing agreements, you are effectively stealing from other creators. You are benefiting from the hard work of others without providing proper credit, and this can have serious consequences for your reputation as a game developer. The speed and coverage of social networks can bring unwanted negative attention to you, your team, and your entire project.

Conclusion

At first glance, the legal mumbo-jumbo surrounding copyright licensing can seem overwhelming. Keep in mind that these public licenses were created as a way tohelp artists and developers, and the content creators are doing you a huge favor by making this content readily available without complicated contracts. 

Take the time to read and understand your licenses and don’t forget to contact the content creators once your game is complete. In the same way that you want people to play your game, the artists that create publicly licensed content like to know when their creations are featured. Maintaining a safe and friendly relationship between developers, artists, and the law will help to ensure that the game development community can continue to grow and improve in the years to come.

Resources


Tell Us What You Think & Win Prizes: Tuts+ Annual Survey 2014

$
0
0

It’s back! The Tuts+ annual survey for 2014 is here and we want to know what you think. 

In having your say you could also win from a fantastic prize pool worth a total of more than $20,000! Plus there’s a special offer from Shopify and New Relic for everyone that completes the survey.

This is your opportunity to tell us what you like, dislike and love about Tuts+. You can also help us shape the future of your online learning by letting us know the features that would assist you with online learning.

Get started now!

What can you win?

Grand Prize: Total Prize Value $17,516

  • Lifetime membership to MadeFreshly Pro Plan (RRP $420/year)
  • Runscope team account for up to 25 team members and 500,000 requests per month (RRP $2148)
  • UXPin Pro Plan yearly subscription (RRP $300)
  • A yearly license to Macaw (RRP $179)
  • 1 year designer membership to PixelKit (RRP $39)
  • $400 worth of PSD to HTML or PSD to WordPress services with Reliable
  • 6 months of APM product up to 5 hosts with New Relic (RRP $4470)
  • Heart Internet VPS Plus SSDdrive 4 CPUs 50GB Space 5 GB Ram Plesk Control panel, 30 sites (RRP $1772)
  • A professional level shop for a year and a paid theme of your choice from Shopify (RRP $1128)
  • A year for free on Motiv (RRP $180)
  • One year Adobe Creative Cloud licence (RRP $600)

Runner Up: Total Prize Value $1128

  • 1 year membership to MadeFreshly Pro Plan (RRP $420)
  • UXPin Pro Plan yearly subscription (RRP $300)
  • A yearly license to Macaw (RRP $179)
  • 1 year designer membership to PixelKit (RRP $39)
  • 6 Months free on Motiv (RRP $90)
  • $100 credit on Twillio (RRP $100)

Honorable Mention: Total Prize Value $818

  • 6 month membership to MadeFreshly Pro Plan (RRP $210)
  • UXPin Pro Plan yearly subscription (RRP $300)
  • A yearly license to Macaw (RRP $179)
  • 1 year designer membership to PixelKit (RRP $39)
  • 6 Months free on Motiv (RRP $90)

Editor Favourites 

  • 7x 1 year designer membership to PixelKit (RRP $39 each)
  • 7x Tuts+ yearly subscriptions (RRP $180 each)

Everyone that completes the survey will receive an offer from Shopify and New Relic via email after 10 December.

Take the Tuts+ Annual Survey

A big thanks to all our sponsors! 

$3 Tuts+ Courses this Cyber Monday

$
0
0

This Cyber Monday (December 1st) we're reducing the prices of all individual Tuts+ Courses to just $3 (normally $15)! 

Cyber Monday offer

Discover your new skills in everything from web design and code to photography and design & illustration with our step by step video courses. With expert instructors delivering easy to digest practical lessons, our 430+ courses include 1—3 hours of video, organized into chapters and bite-size lessons. You can learn it all at once or view a little each day.

Make sure you don’t miss out and leave your email here to be notified when the sale starts.

This offer is strictly limited to Cyber Monday and will not be extended.

Web Audio and 3D Soundscapes: Implementation

$
0
0

In this tutorial we will wrap Web Audio in a simple API that focuses on playing sounds within a 3D coordinate space, and can be used for immersive interactive applications including, but not limited to, 3D games.

This tutorial is the second in a two-part series. If you have not read the first tutorial in the series you should do that before reading this tutorial because it introduces you to the various Web Audio elements we will be using here.

Demonstration

Before we get started, here's a small demonstration that uses the simplified API that we will be covering in this tutorial. Sounds (represented by the white squares) are randomly positioned and played in a 3D coordinate space using the head-related transfer function (HRTF) that Web Audio provides for us.

The source files for the demonstration are attached to this tutorial.

Overview

Because the simplified API (AudioPlayer) has already been created for this tutorial and is available for download, what we are going to do here is take a broad look at the AudioPlayer API and the code that powers it.

Before continuing this tutorial, please read the previous tutorial in this series if you haven't done so already and are new to the world of Web Audio.

AudioPlayer

The AudioPlayer class contains our simplified API and is exposed on the window object alongside the standard Web Audio classes if, and only if, Web Audio is supported by the web browser. This means we should check for the existence of the class before we attempt to use it.

if (window.AudioPlayer !== undefined) {
    audioPlayer = new AudioPlayer()
}

(We could have tried to create a new AudioPlayer object within a try...catch statement, but a simple conditional check works perfectly well.)

Behind the scenes, the audioPlayer creates a new AudioContext object and a new AudioGainNode object for us, and connects the GainNode object to the destination node exposed by the AudioContext object.

var m_context = new AudioContext()
var m_gain = m_context.createGain()
...
m_gain.connect(m_context.destination)

When sounds are created and played they will be connected to the m_gain node, this allows us to control the volume (amplitude) of all the sounds easily.

The audioPlayer also configures the audio listener, exposed by m_context, so it matches the common 3D coordinate system used with WebGL. The positive z axis points at the viewer (in other words, it points out of the 2D screen), the positive y axis points up, and the positive x axis points to the right.

m_context.listener.setOrientation(0, 0, -1, 0, 1, 0)

The position of the listener is always zero; it sits at the centre of the audio coordinate system.

Loading Sounds

Before we can create or play any sounds, we need to load the sound files; luckily enough audioPlayer takes care of all the hard work for us. It exposes a load(...) function that we can use to load the sounds, and three event handlers that allow us to keep track of the load progress.

audioPlayer.onloadstart = function() { ... }
audioPlayer.onloaderror = function() { ... }
audioPlayer.onloadcomplete = function() { ... }

audioPlayer.load("sound-01.ogg")
audioPlayer.load("sound-02.ogg")
audioPlayer.load("sound-03.ogg")

The set of sound formats that are supported is browser dependant. For example, Chrome and Firefox support OGG Vorbis but Internet Explorer doesn't. All three browsers support MP3, which is handy, but the problem with MP3 is the lack of seamless sound looping—the MP3 format is simply not designed for it. However, OGG Vorbis is, and can loop sounds perfectly.

When calling the load(...) function multiple times, audioPlayer will push the requests into a queue and load them sequentially. When all of the queued sounds have been loaded (and decoded) the onloadcomplete event handler will be called.

Behind the scenes, audioPlayer uses a single XMLHttpRequest object to load the sounds. The responseType of the request is set to "arraybuffer", and when the file has loaded the array buffer is sent to m_context for decoding.

// simplified example

m_loader = new XMLHttpRequest()
m_queue = []

function load() {
    m_loader.open("GET", m_queue[0])
    m_loader.responseType = "arraybuffer"
    m_loader.onload = onLoad
    m_loader.send()
}

function onLoad(event) {
    var data = m_loader.response
    var status = m_loader.status

    m_loader.abort() // resets the loader

    if (status < 400) {
        m_context.decodeAudioData(data, onDecode)
    }
}

If the loading and decoding of a file is successful, audioPlayer will either load the next file in the queue (if the queue is not empty) or let us know that all the files have been loaded.

Creating Sounds

Now that we have loaded some sound files we can create and play our sounds. We first need to tell audioPlayer to create the sounds, and this is done by using the create(...) function exposed by audioPlayer.

var sound1 = audioPlayer.create("sound-01.ogg")
var sound2 = audioPlayer.create("sound-02.ogg")
var sound3 = audioPlayer.create("sound-03.ogg")

We are free to create as many sounds as we need even if we have only loaded a single sound file.

var a = audioPlayer.create("beep.ogg")
var b = audioPlayer.create("beep.ogg")
var c = audioPlayer.create("beep.ogg")

The sound file path passed to the create(...) function simply tells audioPlayer which file the created sound should use. If the specified sound file has not been loaded when the create(...) function is called, a runtime error will be thrown.

Playing Sounds

When we have created one or more sounds, we are free to play those sounds whenever we need to. To play a sound, we use the aptly named play(...) function exposed by audioPlayer.

audioPlayer.play(sound1)

To determine whether to play a looped sound, we can also pass a Boolean to the play(...) function. If the Boolean is true, the sound will loop continuously until it is stopped.

audioPlayer.play(sound1, true)

To stop a sound, we can use the stop(...) function.

audioPlayer.stop(sound1)

The isPlaying(...) function lets us know whether a sound is currently playing.

if (audioPlayer.isPlaying(sound1)) { ... }

Behind the scenes, the audioPlayer has to do a surprising amount of work to get a sound to play, due to the modular nature of Web Audio. Whenever a sound needs to be played,audioPlayer has to create new AudioSourceBufferNode and PannerNode objects, configure and connect them, and then connect the sound to the m_gain node. Thankfully, Web Audio is highly optimized so the creation and configuration of new audio nodes rarely causes any noticeable overhead.

sound.source = m_context.createBufferSource()
sound.panner = m_context.createPanner()

sound.source.buffer = sound.buffer
sound.source.loop = loop
sound.source.onended = onSoundEnded

// This is a bit of a hack but we need to reference the sound
// object in the onSoundEnded event handler, and doing things
// this way is more optimal than binding the handler.
sound.source.sound = sound
			
sound.panner.panningModel = "HRTF"
sound.panner.distanceModel = "linear"
sound.panner.setPosition(sound.x, sound.y, sound.z)

sound.source.connect(sound.panner)
sound.panner.connect(m_gain)

sound.source.start()

Playing sounds is obviously useful, but the purpose of audioPlayer is to play sounds within a 3D coordinate system, so we should probably set the sound positions before playing them. audioPlayer exposes a few functions that allow us to do just that.

Positioning Sounds

  • The setX(...) and getX(...) functions exposed by audioPlayer can be used to set and get the position of a sound along the coordinate system's x axis.
  • The setY(...) and getY(...) functions can be used to set and get the position of a sound along the coordinate system's y axis.
  • The setZ(...) and getZ(...) functions can be used to set and get the position of a sound along the coordinate system's z axis.
  • Finally, the helpful setPosition(...) function can be used to set the position of a sound along the coordinate system's x, y, and z axes respectively.
audioPlayer.setX(sound1, 100)
audioPlayer.setZ(sound1, 200)

console.log(audioPlayer.getX(sound1)) // 100
console.log(audioPlayer.getZ(sound1)) // 200

audioPlayer.setPosition(sound1, 300, 0, 400)

console.log(audioPlayer.getX(sound1)) // 300
console.log(audioPlayer.getZ(sound1)) // 400

The farther a sound is from the center of the coordinate system, the quieter the sound will be. At a distance of 10000 (the Web Audio default) a sound will be completely silent.

Volume

We can control the global (master) volume of the sounds by using the setVolume(...) and getVolume(...) functions exposed by audioPlayer.

audioPlayer.setVolume(0.5) // 50%

console.log(audioPlayer.getVolume()) // 0.5

The setVolume(...) function also has a second parameter that can be used to fade the volume over a period of time. For example, to fade the volume to zero over a two second period, we could do the following:

audioPlayer.setVolume(0.0, 2.0)

The tutorial demo takes advantage of this to fade-in the sounds smoothly.

Behind the scenes, the audioPlayer simply tells the m_gain node to linearly change the gain value whenever the volume needs to be changed.

var currentTime = m_context.currentTime
var currentVolume = m_gain.gain.value

m_gain.gain.cancelScheduledValues(0.0)
m_gain.gain.setValueAtTime(currentVolume, currentTime)
m_gain.gain.linearRampToValueAtTime(volume, currentTime + time)

audioPlayer enforces a minimum fade time of 0.01 seconds, to ensure that steep changes in volume don't cause any audible clicks or pops.

Conclusion

In this tutorial, we took a look at one way to wrap Web Audio in a simple API that focuses on playing sounds within a 3D coordinate space for use in (among other applications) 3D games.

Due to the modular nature of Web Audio, programs that use Web Audio can get complex pretty quickly, so I hope this tutorial has been of some use to you. When you understand how Web Audio works, and how powerful it is, I'm sure you will have a lot of fun with it.

Don't forget the AudioPlayer and demonstration source files are available on GitHub and ready for download. The source code is commented fairly well so it's worth taking the time to have a quick look at it.

If you have any feedback or questions, please feel free to post a comment below.

Resources

The Dynamic Priority List: How I Manage My Gamedev Projects

$
0
0

Planning a game project and staying on schedule is hard. To keep on top of things, I use a simple system—no apps required, just a pen and some paper. This system drastically improves my scheduling and development process, so if you're always falling behind, give it a try! I think it could help you too.

Why I Like the Dynamic Priority List

When I worked in the game industry, I experienced a “death march” first hand. A combination of two large projects and poor scheduling lead to 18-hour days for months on end.

Since then, I've studied many methods for planning projects, ranging from wikis to specialized computer programs, but I've found one tool that surpasses them all: a standard notebook.

Wikis and apps take time to learn and maintain, and most developers I know would rather spend that time making a better game. As the old saying goes, "the best tool is the one you'll use”; this is the amazing strength of an everyday notebook.

Starting Your List

The Dynamic Priority List is simply a list of tasks that will evolve over time. It begins with a broad overview of the tasks you need to accomplish to finish your game:

Dynamic Priority List Broad Overview
A dynamic priority list, showing the broad outline of a project.

Obviously, this isn't a full plan. Your goal here is simply to have a place to visualize the big picture. It's too easy to forget about that options menu or loading interface when you're neck deep in code and behind schedule.

Now, sit down and work normally. The less time you waste with your new planning tool, the more likely you are to use it.

Working naturally, you'll get a clearer image of each element that needs to be completed. Cross out the broad overview items such as “Gameplay” and replace them with specific features that you need or are even just considering.

Dynamic Priority List Revised and Annotated
A dynamic priority list, with annotations.

As each item pops into your head, quickly add it to the list without taking your mind off your current task. Don't worry about how to implement it or whether it even fits the game, just add it to the list so that you can deal with it in the future.

Advanced Shorthand

Did you notice the question mark and arrows on my list?

As you get used to using the list, you can leave notes for yourself. This shorthand can remind you of things at a glance, and you should customize this to fit your own development style.

For example, I use the following:

  • Question mark: For any task that I'll need to think about—either a design question, or how to implement it.
  • Arrows: For any tasks that are linked in logic or in code. These will likely be implemented at the same time.
  • Slash: When I have two options that I'll need to choose between, I'll link them with a slash.
  • Swear words: For anything that I dread and don't want to do. This serves no valid purpose, but it makes me feel good.

Remember, customize your shorthand to what works for you!

Nightly Ritual

Every single night, when I shut down my computer, I perform a series of tasks to prepare for the next morning. I read the list and pick tasks that should be implemented during the next work day.

I place one star beside each of these “Priority” tasks. When I finish, I set the list next to my computer so I'll know exactly what to start on in the morning.

If any starred items aren't completed during the day, I add another star that night. This increases the priority of each task with time, so that I can't put off the hard or boring tasks for more than a day or two.

I end up with this:

  • One star (*): Things I'll consider working on.
  • Two stars (**): Things I should be doing even if I'm not in the mood.
  • Three stars (***): Nothing else will be worked on until this is finished.

Zero star items are implemented when I have free time, motivation, and no major priorities. This is usually reserved for dream goals that aren't vital to completion, or for features that aren't feasible until later in development.

The AFK Ritual

From now on, take this notebook with you whenever you step away from your computer.

While you're eating lunch, read over the list. 

Watching TV? Read over the list.

Going for a walk? Why not take the list and read over it?

Use this time to ponder how to implement tricky tasks, decide which choices best fit your design, and figure out which features need to be cut based on your current deadline. Update the list with potential solutions and changes.

While working, we all get tunnel vision. In the office we have managers who should be watching the big picture, but as indies we have to do that ourselves. Use your AFK time to be your own best manager.

Cleanup

If you find that your list is getting messy after a few days, you're doing things right!

Every four to seven days you'll want to take some AFK time to copy the remaining tasks to a brand new page. But the value here is that it forces you to look at the project as a whole.

Don't simply copy everything that remains; think about each item individually, based on your current schedule. If you're running low on time, reduce the priorities of some features, and cut others from the list.

It's your AFK time, so you're eating, watching movies, and having fun right now. You have all the time in the world to make the hard decisions. Use it!

You now have a clean list that is up-to-date with your current priorities. 

Dynamic Priority List Week 2
A dynamic priority list, revised, with priority star ratings.

By using these basic strategies, you'll get a much better overview of your project. This will improve your planning dramatically, without cutting into your development time. Learning to use that notebook will allow you to accomplish more with a fraction of the effort. 

Conclusion

Even if you miss deadlines or fail a task, by keeping a list, you have a running log of your priorities. You can see what you accomplished each week and rethink the choices you made. This allows you to learn from each project regardless of its success or failure. 

Every project is a chance to learn and improve the process. This simple list can help you to accomplish that.

References

Mastering the GameMaker Studio Particle System

$
0
0

In this article, we’ll begin with the basics of the GameMaker Studio particle system and end with advanced techniques and implementations. Think of this as a crash course to get you familiar and comfortable with one of GameMaker Studio’s most powerful built-in features. The demo and downloadable project file will allow you to follow along with the article to see exactly what the particle system is all about.

Particle System Overview

The GameMaker Studio particle system is a cheap and easy way to create flashy effects for your game project. Through a combination of particles and emitters, you can quickly create impressive explosions, smoke, blood, shrapnel, and countless other effects. Similar effects can be achieved by using individual objects, but the computing cost of the built-in particle effects system is far cheaper.

To use the particle system in your project, you'll need to understand GML (GameMaker Language), but once you become familiar with the particle system, it is simply a matter of filling in the blanks and experimentation.

Check out the demo below to see what we can achieve:

Particle System Basics

Particle Systems in GameMaker Studio consist of three parts: the system itself, the particle, and the emitter. You can create multiple systems, and each system can contain multiple particles and emitters. Think of the system as a container, with the particles and the emitters defined within.

Implementing a particle effect in GameMaker Studio is a four step process. 

  1. First, you must define the particle system itself. 
  2. Then, you define the actual particles that will be used within that system. 
  3. Next, you have to define the emitter that will create your defined particles. 
  4. Finally, you have to determine when and where the particle emitter will appear in your game.

To teach you the basics of the GameMaker Studio particle system, we’ll start out by creating this very simple green particle effect.

Creating the Particle System

Creating the particle system is as simple as defining a variable. We create an object called obj_first_particle and place the following code in the object’s Create event:

FirstParticleSystem = part_system_create();

The particle system will adopt the depth value of the object that the system is defined in, but you can also set the depth separately with GML:

part_system_depth(FirstParticleSystem,0);

Remember that objects and particle systems with a high depth value are drawn to the screen first. With a depth of 0, our green particle system will appear above objects with a depth greater than 0, and will appear below objects with a depth less than 0.

Particle systems are drawn to the screen with a base position relative to (0,0). If for some reason you want to create an offset for all future positions of this particle system, you can use the following code to create a new base position (where ind is the particle system):

part_system_position(ind, x, y);

With a new base position set to(10,10), a particle created at (25,25) will instead be drawn to (35,35). Changing the base position of a particle system is rarely necessary, but you may find it useful in your specific project.

Creating the Particle Type

Now that the system has been set up, it is time to define the actual particle that the emitter will create. Particles can contain a large number of parameters that dictate how the particle will look and behave. The first step is to create a variable for the particle, and we do this in the Create event of the obj_first_particle object:

first_particle = part_type_create();

Next, we begin defining the individual parameters of the particle. Since we do not plan on altering this particle during runtime, we can place all of this code in the Create event of the obj_first_particle object.

part_type_shape determines the base shape of the particle. There are 14 default particle shapes available in GameMaker Studio, and you can define your own shapes as well. We’ll cover this in the advanced section below, but for now let’s just start with a basic square.

part_type_shape(first_particle,pt_shape_square);

For a full list of the available default shapes, check the official GameMaker documentation.

With part_type_scale, we can set the base X and Y scale values of the particle shape. Since we want a perfect square shape, we use the following code:

part_type_scale(first_particle,1,1);

part_type_size allows us to alter the size of the particle at creation and as well as over time. The format for this code is part_type_size(ind, size_min, size_max, size_incr, size_wiggle)

  • ind is the particle variable. 
  • size_min and size_max determine the range of the particle size when it is first created. If you want a uniform size, simply enter the same value for both the min and the max. 
  • size_incr is a value that allows the particle to grow or shrink over time. This value determines the speed of growth, so if you don’t want your sprite to change size, you can use a value of 0
  • size_wiggle is slightly more complicated, so we’ll cover that in the advanced techniques section below.

Here’s the particle size code used in our green particle effect:

part_type_size(first_particle,0.10,0.15,-.001,0);

The particle will be created with a size somewhere between 0.10 and 0.15 to create variety, and the sprite will slowly shrink at a speed of -0.001. This speed value depends greatly on your room speed, so you will likely need to experiment with values to get the desired results. We will not be using any size wiggle, so we set the value to0.

Particles in GameMaker Studio can actually change colors over time. This is achieved with part_type_color2 andpart_type_color3. If you don’t want your sprite to change colors, then you can just use part_type_color1. For our green particle effect, we want it to start out with a bright yellow/green color and then change to a solid green color, so we use part_type_color2:

part_type_color2(first_particle,8454143,65280);

The two colors I selected are specific numerical values that I use regularly, but if you want to use more traditional hex values, you can use the format $RRGGBB.

Particles can also become more or less transparent over time with part_type_alpha2 and part_type_alpha3. If you want a consistent alpha value, use part_type_alpha1. For our green particle effect, we want the particle to start completely opaque and fade by 25% as it stays on the screen, so we need two alpha values:

part_type_alpha2(first_particle,1,0.75);

In GameMaker, alpha is a value from 0 to 1. A completely invisible object will have an alpha value of 0, while a completely opaque object will have an alpha value of 1.

Particle speed is determined just like particle size. Particles are created within a range of speed values, and that value can increase or decrease. The format for this code is part_type_speed(ind, speed_min, speed_max, speed_incr, speed_wiggle), where ind is the particle variable, speed_min and speed_max is the speed range, speed_incr is the rate at which the particle speed changes, and speed_wiggle is a parameter that we’ll cover later on. 

The speed code for our green particle is:

part_type_speed(first_particle,0.1,0.5,0,0);

Our particle will start moving with a speed value somewhere between 0.1 and 0.5. This speed will remain constant, so we use a value of 0, and we will again not be implementing speed wiggle, so we use a value of 0.

While a particle’s speed parameter determines how fast it moves, the direction parameter determines where it moves. The direction code is in the following format: part_type_direction(ind, dir_min, dir_max, dir_incr, dir_wiggle) and again we set the variable, starting range, an incremental value, and a wiggle value. For the green particle effect, we want our particle to start moving in any direction, and we want that direction to remain constant:

part_type_direction(first_particle,0,359,0,0);

The range of 0 to 359 ensures that the particle has a chance to move in any direction (an angle between 0 and 359 degrees). If you wanted a particle to move up and only up, then you would use a range of 90 to 90).

The gravity of our particle effect is what makes it the most interesting. While our speed and direction parameters are set to create a particle that starts by moving in one direction at a constant speed, the gravity parameter kicks in and alters the particle over time. With a format of part_type_gravity(ind, grav_amount, grav_direction), the gravity parameter is very simple:

part_type_gravity(first_particle,0.02,90);

By applying a slight gravitational pull of 0.02 in an upward direction (90 degrees), we can create a particle that appears to float. Combined with our size and alpha parameters, the particle shrinks and becomes more transparent over time, accompanied by the gravitational lift.

The orientation of the particle shape is also important to the appearance of the effect, so we use part_type_orientation(ind, ang_min, ang_max, ang_incr, ang_wiggle, ang_relative) to rotate the square over time. 

  • ind is the particle variable. 
  • ang_min and ang_max determine the starting value of the shape’s rotation value.
  • ang_incr is used to increment or decrement the shape’s orientation over time.
  • ang_relative is a Boolean value to determine if the orientation should be set relative to the motion of the particle (true) or not (false). 

We want our green particle to rotate slightly to the left, so we use the following code:

part_type_orientation(first_particle,0,359,10,0,true);

One of the most important parameters of a particle is the lifespan value. This value determines the minimum and maximum time that a particle will be drawn to the screen. With two identical min and max values, all particles of that type will exist for the same amount of time. We want our green particles to have variety, so we will use a range of 100 to 150 for the lifespan value:

part_type_life(first_particle,100,150);

The final parameter for particles is a simple Boolean to determine whether the particles should blend together with an additive blend effect:

part_type_blend(first_particle,true);

Creating the Particle Emitter

The first step in defining an emitter is to create a variable. We define this emitter in the Create event of the obj_first_particle object.

first_emitter = part_emitter_create(FirstParticleSystem);

Next, we define the emitter region with part_emitter_region(ps, ind, xmin, xmax, ymin, ymax, shape, distribution).

  • ps is the particle system that the emitter belongs to and ind is the emitter variable.
  • The x and y min and max values determine the size of the emitter region. 
  • shape determines the shape of the emitter region (ps_shape_rectangle, ps_shape_ellipse, ps_shape_diamond, ps_shap_line). 
  • distribution is a distribution curve (ps_dist_linear, ps_distr_gaussian, ps_distr_invgaussian).

We’ll cover the shape and distribution parameters in further detail in the advanced techniques section. For now, we’ll use the default ellipse shape and Gaussian distribution values:

part_emitter_region(FirstParticleSystem, first_emitter, x-20, x+20, y-20, y+20, ps_shape_ellipse, ps_distr_gaussian);

This code creates an elliptical emitter region that is 40 pixels tall and 40 pixels wide, and centred on the x and y values of the obj_first_particle object. Particles created by the emitter will appear within this defined region.

Activating the Particle Emitter

The next step is to determine one of two emitter types: Burst or Stream. A Burst emitter creates a specified amount of a certain particle whenever it is triggered. A Stream emitter creates a specified amount of a certain particle once every step. 

We’ll take a look at the more versatile Burst emitters in the advanced techniques section, so for now let’s just use the Stream emitter:

part_emitter_stream(FirstParticleSystem,first_emitter,first_particle,1);

We place this code in the Create event of the obj_first_particle object, resulting in the emitter creating one particle each step as soon as the object is created. With a room speed of 30, our emitter will create 30 particles per second; to create 60 particles per second, you would simply use a value of 2 instead of 1.

And with that, our simple green particle effect is complete! The usefulness of this effect is limited, but it’s important to start small before diving into the more complicated aspects of the GameMaker Studio particle system. Once you understand the basics of the particle system, you can start implementing more advanced particle systems.

Advanced Particle System Techniques

Particle Wiggle

Wiggle is a simple, yet powerful parameter that can drastically change the appearance of your particles. The wiggle parameter causes the particle to oscillate between the min and max values for the lifetime of the particle. The value can be between 0 and 20 and determines the speed of the wiggle. 

The "Fire Bubble" example in the embedded demo uses a wiggle value of 0.40 in the part_type_size parameter:

part_type_size(fire_bubble_part,0.25,0.75,-0.01,0.40);

Burst Emitters and Moving Objects

One of the most common implementations of particle systems involves particles that emanate from behind a moving object, such as a smoke trail on a rocket. Achieving this effect in GameMaker Studio requires a Burst emitter to be placed in an object’s Step event.

The included example uses the same green particle system as before, but with a slightly modified emitter. Instead of triggering a Stream emitter in the object’s Create event, a Burst emitter is placed in the object’s Step event. The current position of the mouse cursor is checked against the previous position of the cursor, and if there is a change in the cursor’s position, the Burst emitter is triggered to release five particles:

x = mouse_x;
y = mouse_y;

part_emitter_region(MouseParticle,green_mouse_emitter,x,x,y,y,0,0);

if x != old_x || old_y != y
{
    part_emitter_burst(MouseParticle,green_mouse_emitter,green_mouse_particle,5);
}

old_x = x;
old_y = y;

Emitter Region Shapes

By using the different emitter shapes and distribution curves, you can create vastly different effects. Linear curve distribution combined with a line-shaped particle emitter can create a convincing rain effect.

part_emitter_region(RainParticle, rain_emitter, -100, room_width, y, y, ps_shape_line, ps_distr_linear);

The emitter shape is defined by a line that begins 100 pixels to the left of the room origin and is extended to the width of the room. A linear distribution curve is used to distribute the rain particles evenly across the emitter region. It is also useful to usepart_system_update to advance the rain particle several steps in the Create event. This code gives the impression that the rain was falling before you loaded the room, even though the particle system didn’t exist in memory yet.

repeat (room_speed * 3)
{
     part_system_update(RainParticle);
}

Particle System Step and Death Effects

Individual particles within a system can also spawn other particles on Step and Death events. The example shows a purple spark particle that spawns smaller dust particles as it travels to the right, and spawns a smoke particle at the end of its lifespan:

part_type_step(spark_particle,1,dust_particle);
part_type_death(spark_particle,5,smoke_particle);

Custom Particle Shapes

By using part_type_sprite(ind, sprite, animate, stretch, random), you can use custom sprites instead of the built-in GameMaker particle types. 

  • ind is the particle variable.
  • sprite is the sprite variable to be used.
  • animate is a Boolean to determine if the sub-images should be animated.
  • stretch is a Boolean that matches the animation length to the lifespan of the particle. 
  • random is a Boolean to determine whether the starting sub-image should be randomly selected.
part_type_sprite(heart_particle,spr_heart,false,false,false);

Memory Management

The most important thing to remember about the GameMaker Studio particle system is that you need to manually remove items from memory when not in use. Unlike standard game objects, particle systems will remain in memory even if you change rooms. The easiest way to handle this is to place memory management code in the room changing events of your game objects.

  • part_system_clear(ind): Clears all emitters and particles belonging to a specific particle system.
  • part_system_clear(ind): Clears all instances of a specific particle type.
  • part_emitter_clear(ps, ind): Clears all particles belonging to a specific emitter.
  • part_emitter_destroy(ps, ind): Destroys a specific emitter in a specific particle system.
  • part_emitter_destroy_all(ps): Destroys all emitters in a specific particle system.
  • part_type_destroy(ind): Destroys a specific particle type.
  • part_system_destroy(ind): Destroys an entire particle system, including all particles and emitters contained within.

Conclusion

Even with such a lengthy tutorial, we've still only scratched the surface of what kinds of effects the GameMaker Studio particle system is capable of. The key to mastering particles is familiarity and experimentation, so jump in and start creating your own effects using the knowledge you've gained. Be sure to check out the official GameMaker documentation to view all the available particle system GML!

15 Things to Consider When Designing Microconsole Games

$
0
0

Ever since the Ouya successfully kicked off the microconsole concept, several other microconsoles have launched too, including the GameStick, the PS Vita TV, and most recently the Amazon Fire. They exist beside the established home consoles, such as the PlayStation 4, Xbox One, and Wii U, and offer smaller games that are more comparable to mobile titles. Many of these are, in fact, ports of former mobile games.

These microconsoles offer developers a new way to get games out there. They are plugged into TVs, and offer similar processing-power to mobile devices. As most are Android based, such as the Ouya, the GameStick, and the Amazon Fire, creating and porting games for them is much simpler than having to re-create them for each system. Many engines (Unity, Flash, Game Maker) that already offer deployment to Android can be easily adapted to port to microconsoles, and also offer documentation on that process.

I have ported one mobile-game to the Ouya (Vertical Void DX), created one exclusively for it (Strike Craft Copperhead), and helped port one while working for a company, where I also handled the majority of a port to the GameStick. While doing that, we had to adapt the (former) mobile games to the environment of being played on a television instead of a phone or tablet, where we encountered some unforeseen issues. 

The lessons below are ones I learned primarily from designing for the Ouya and GameStick, but are valid (and important!) when designing for other microconsoles, too.

Use a High Screen Resolution

The games are going to run on a television, so you should aim for performance on a high resolution: 1080p(1920x1080px) should be the goal. At the very least, a game should run and support 720p (1280x720px) as a basic resolution.

Look for Glitches on Larger Screens

Running a previously mobile-only game on a TV in 1080p means you suddenly have a completely different view of it. What previously might have been invisible could now be a very visible glitch. Edges that are not anti-aliased edges can become more prominent, and what looked good on a small phone may look worse on a TV.

Remember to check for these issues and play through the whole game looking out for them, so that they do not surprise you.

1080p Requires More Processing Power

Having a higher resolution also means having more pixels to crunch, so make sure you test performance. If your game is already straining on mobile devices, outputting more pixels could be an issue.

The Ouya is comparable to a decent Android tablet in terms of performance, so if your game runs on a Nexus 7 there's a good chance it will run well on the Ouya.

Adapt the UI for TV Displays

As there are a wide range of TVs, which include older models, it is not 100% possible to guarantee that the game's image will fit perfectly on every screen.

Older TVs, in particular, tend to overshoot the image at times, cutting off the sides of the screen by a sometimes considerable amount.

To ensure that everything is displayed correctly, every console game needs to have a "safe zone", which is a border on the edge of the screen where nothing critical is shown. On the Ouya, this border is 15% of the screen's width.

This is a screenshot of Strike Craft Copperhead, with the interface as it would appear on a desktop or mobile-game, where UI elements are close to the edge of the screen:

For TVs, however, we need to think of the aforementioned safe zone. Here is the same image with a 15% safe zone marked:

Nothing vital to the game can go into that part, so we must move all interface elements inside it:

In addition, you can add an option for players to adjust the interface position themselves, so that they can make sure it is in a good position, and so make best use of the available screen space.

Add Complete Gamepad Support

Every microconsole game needs to be able to be controlled with a gamepad, which means that some kinds of games (such as strategy games) could be difficult, or even impossible, to control well.

Unless you can come up with a workable control scheme, such genres will remain very difficult to port.

Avoid the Ouya Touchpad

The Ouya controller has a built-in touchpad, so you might assume that mouse-based controls would work well for such games.

However, in practice, the touchpad is really only useful as an emergency input when running mobile apps on the Ouya. It will not work as a mouse substitute, as the speed, sensitivity, and comfort levels barely even approach those of mice.

Make sure that everything works well with just a controller.

Support Xbox, PlayStation, and Other Gamepads

There are ways to recognize what kind of gamepad is connected. Unity, for example, has a function called Input.GetJoystickNames(), which outputs a string that identifies a controller.

An Xbox 360 Controller would show up as "CONTROLLER (XBOX 360 FOR WINDOWS)"; a PlayStation 3 controller as "PLAYSTATION(R)3 CONTROLLER".

This allows us to differentiate different types of controller, and to tailor the game to work well with all gamepads we want to support.

Allow Hot-Plugging

Hot-plugging is the process of plugging in a device while the software runs. Ideally, we want our games to be able to support that; otherwise,  the gamepads we use to control the game would all have to be already plugged in when we load up the game first, and wireless pads would have to be booted up. Then we'd have to make sure the wireless pads don't enter sleep-mode, which would necessitate re-booting the game to prevent such annoyances. This still often props up even in AAA releases.

So, let's not do that. Let's make sure that hot-plugging works.

If you already a have a system to recognize what kind of joystick is plugged in, you can adapt the code right there. What we will need to do is check which gamepads are connected all the time.

This does not have to happen every single frame, in case you're worried about the processing power. Checking between every frame or every second should suffice.

Design Menu Types to Suit Gamepads

There are different ways to set up menu systems for your games. If your game does not support directional menu controls (via a gamepad or keyboard), it needs to be adapted or rebuilt.

One way to do is to label each function with an image of the corresponding button, as seen here in Strike Craft Copperhead:

A better controller-friendly menu system would be one with highlighted buttons. This feels much better, but is also more difficult to create. Here we can see it in Vertical Void DX:

Regardless of the type of menu, it is always useful to have a "legend", explaining what each button does, like in the image above.

Also, remember to use the established way of using certain buttons. The "lower" action button (A on the Xbox, X on the PlayStation, O on the Ouya) is usually the forward button, while the button on the right is to go back. The central system button should be used to bring up menus or to quit the game.

Use "Side-Loading" to Test Ouya Games

The Ouya offers the ability to run any other Android app out there, without it having to be specifically adapted for the microconsole. This process is called side-loading. When any app is on the system (downloaded via the web, or copied via a USB stick), it can be installed and run.

If you already have an Android game programmed and ready, you can just put it on the Ouya and see how it would work!

Side-loading can also make it easier to deploy to the Ouya. If you have AirDroid running, for example, you can copy new builds via Wi-Fi, which makes the process far quicker and simpler.

Know the Difference Between Ouya and Android

Even though many microconsoles are Android-based, the platforms have to be approached separately.

As we have already discussed, every Android game can also be run on the Ouya. But to become an actual Ouya game, it needs to be adapted and to have all the necessary plugins configured. Some Ouya-specific settings can also override settings in an Android build, so you must consider those separately to prevent problems.

Price Your Games Appropriately

Microconsole games are smaller than AAA-titles, but can be more complex than mobile-titles. The pricing of microconsole games leans closer to the mobile side of things, with prices around $10 being acceptable. Towerfall, one of the most successful Ouya games, had a price of $15 for a long time.

Free-to-play games are also allowed, and microtransactions are supported, at least on the Ouya.

Implement Offline DLC Checks

Usually games check whether the player has bought DLC packs on startup, to make sure they are there before the game starts.

This system breaks, however, when no internet-connection is available.

An alternative would be to use an offline variable, like Unity's PlayerPrefs, to check whether packs were bought. This way, the game with all its content can be played offline too, and a (possibly long) check period at startup can be avoided.

Consider App Store Age Restrictions

Pretty much every major app store (including the iOS App Store and Google Play) restricts "adult" content, and apps for an 18+ years audience are not allowed.

Add a Quit Button

Traditionally, iOS games lack an in-game quit button. On Android, however, those appear quite often.

A quick survey of games on the Ouya store shows that around half have a quit button. It seems worthwhile to add one to our microconsole games as a matter of habit.

Conclusion

We have learned about the major (and some minor) design elements that we have to consider when making a microconsole game. While these aren't the only ones that will come up, this list should give you an idea of what to expect when targeting the microconsole platform.












What Destiny's Failures Can Teach Us About Game Design

$
0
0

Released early this fall, Destiny was arguably the biggest game of the year. As a massive undertaking from Bungie, expectations were high upon its release and sales followed accordingly. 

Now that the game has been out for a while, it's become apparent that some marketing snafus and a few key design mistakes held Destiny back from becoming the game everyone was hoping it would be. In this article, we'll take a look at how and why Destiny failed to meet its lofty expectations, as well as what these shortcomings can teach us about game design.

1. Don't Over-Promise and Under-Deliver

Few games have ever had as much hype as Destiny. Excitement for the game started building even before it was announced; as soon as Bungie declared that they were moving away from the Halo franchise, gamers everywhere were giddy with anticipation for their newest project. The Halo games were all top-of-the-line products, with tight multiplayer, and cinematic campaigns that resonated with many people. Destiny was expected to be great even before anybody knew anything about it.

Then the marketing machine rolled in and the hype got out of control. The game's initial reveal was nothing more than some concept art and some story details from a leaked document, and people were already hooked. Eventually the game was officially revealed to the media as the first instalment in a series of games to be released over ten years. Everything about Destiny just screamed epic, and media outlets and trade shows had a never-ending supply of new details, previews, and trailers for the game.

Unfortunately, this massive hype contributes to Destiny's most obvious flaw: it over-promised and under-delivered. All of this press was filled with details about the game, and how it was to be one of the biggest and most expansive experiences the medium had ever seen. Players were told they could explore their entire solar system in a massive multiplayer open world, with a strong narrative to tie it all together and a solid competitive shooter included on the side.

That's not the whole thing, is it? I thought Pluto was the only planet that got kicked out of our solar system.

The game that actually launched is a pale imitation of the game that was promised, containing only a few planets in our solar system, and a narrative that manages to be cliché and boring and yet also border on incoherence. The gameplay feel of the shooting and movement is excellent, but the game has nowhere near the amount of content people expected, and much of what was promised simply never came to fruition. Even worse, trailers for the game even just months before release featured characters, cutscenes, and environments that are nowhere to be seen in the final product. This type of marketing is disingenuous, and does nothing but leave a sour taste in players' mouths.

It's unclear at this point whether this was an intentional deception on the part of Destiny's marketing team, or if there were more complicated issues that led to the discrepancies between what was advertised and what was released. Game development can be complicated, and things don't always go as planned. What we can learn from this is clear however: don't promise more than what you are confident you are capable of delivering.

Destiny is not a terrible game by any means, and actually has a respectable amount of content for a triple-A shooter, but because of the way it was marketed, players were left feeling let down when they otherwise might not have been. Promising more than you realistically expect to deliver does nothing but create disappointment and animosity for the final product, regardless of its quality or the amount of content it actually offers.

2. Progress Gated by Random Outcomes is a Bad Idea

It seems like every game needs to have RPG elements these days; loot systems, skill trees, and character classes are popping up more and more in genres where you typically wouldn't expect them. Like Borderlands before it, Destiny brings the RPG to the shooter, with questing and character progression being just as important to its core design as landing headshots and well-timed melee attacks. However, Destiny's levelling system is unconventional to say the least, and the game suffers tremendously for it.

Just like almost every other RPG, Destiny initially allows players to accrue experience from killing enemies and completing objectives. When players level up they learn new skills and get access to new gear, just as you would expect. The system is common because it works, and working towards the max level of 20 is great fun. Unfortunately, players quickly learn that 20 isn't really the level cap, it's the highest level that can be gained from traditional experience points. Players can continue to level up, but only by finding and equipping pieces of high-level gear. The better your gear, the higher your level.

Light level 26; the grind is underway.

Many MMOs thrive off this type of progression system, with players being required to constantly find more powerful loot to tackle more difficult challenges. The cycle of gaining power to defeat enemies you couldn't before is fun and engaging in many games, but Destiny just doesn't do it right. Higher-level gear isn't really better than lower-level gear as much as it's just higher-level. The only perceivable difference between lower-level gear and higher-level gear is that your character's listed level goes up.

Since it's virtually impossible to kill enemies that are a higher level than you, your ability to access end-game content is arbitrarily gated by the level of the gear that you have found. This poses a problem because gear drops are strictly random, there is a small chance at any point of finding good gear, and so you simply need to repeat enough of the lower level activities and hope you get lucky with your drops. It is possible to buy decent gear, but the amount of currency required for even just one piece is absurd. The grind is immense and unreasonable, even when compared to other games in the genre.

All of this means that progression in Destiny's endgame is entirely tied to outcomes outside of the player's control. Since there are no reliable ways to increase the chance of getting good loot, players no longer have any agency in their own progression as soon as they hit level 20, leaving the game feeling like a glorified slot machine. Having progression tied to blind luck is incredibly frustrating for players, and is simply an example of bad game design. The lesson here is obvious: always make sure that your player's actions have a role in dictating the outcomes of your gameplay systems.

3. Temporary Content Needs to Have a Reason to be Temporary

Timed content is a big part of the design plan for games that hope to harbour active online communities. Having temporary unique activities for players to participate in is fun for active players, and encourages others who may have lost interest to return to your game. Destiny absolutely loves timed content: there is a constant rotation of weekly and daily missions, as well as timed shopkeepers and weekend-only multiplayer modes. Unfortunately, Destiny once again struggles to get this tried-and-true concept right.

The issue lies primarily in the game's inability to convince players that there is a reason for its content to be time-restricted. Timed content in most games is typically restricted to thematic events, like lore-based or seasonal activities, or to content that differs mechanically or stylistically from the game's core experience. They act as fun diversions to regular content streams, and this is usually well communicated to players.

Oh Xur, where do you go on weekdays?

Destiny's timed content fails because much of it doesn't feel like it should be temporary. A shopkeeper integral to character progression shows up only on weekends, and three of the seven PvP modes advertised before release are only available at select times. Rather than excite players with bonus content, these timed offerings only bring about annoyance when they aren't there. It becomes irritating when you can't play your favorite PvP mode on a Thursday, or you can't buy the new helmet you've been saving up for until the weekend rolls around.

It's also worth noting that announcing modes before release and then having them be limited to timed content is really not a good idea. It leaves players feeling disappointed when they first boot up the game and find that they can't participate in something they were expecting to have access to. Even worse, it gives them the impression, accurate or not, that the content has already been created and is being withheld from them in order to extend their interest in the product. Players' impressions are important, and it's never advisable to present things in a way that leaves them feeling deceived.

There isn't an innate problem with games having temporary content, but the content needs to be of an appropriate nature so that players can understandwhy access to it is limited. Not all of Destiny's timed content has failed in this way—there was an interesting lore-based event not long after release—but the majority of what they have done has missed the mark. The key lesson to learn from this is that temporary content is a game design tool just like any other; its value needs to be well articulated to the player in order for it to have a positive effect on their experience.

4. Don't Imitate Systems Without Including the Core Functionality Players Expect

Destiny plays like the video game equivalent of a top 40 compilation CD. It borrows relentlessly from other games for almost every aspect of its design. It feels like the kind of product people have daydreams about, where they imagine the best parts of their favorite games all wrapped into one package. There's nothing wrong with this; as I've said before, game design is an iterative process, and imitating others is a great way to strengthen your own creation.

As much an MMO as it is an open world shooter and a competitive shooter, Destiny has a lot of different facets that make up its core experience. Each of these three gameplay styles is heavily inspired by games that came before it, and the core gameplay loops typical of these genres are well executed and emulated in Destiny. Unfortunately, the game fails to implement most of the conveniences that are common for games in these genres, and this hurts the quality of the final product.

It really does look very pretty, though.

Destiny is an MMO with almost no player interaction: voice chat is disabled by default in most scenarios, and it's very difficult to partner up and communicate with other players. It's also an open world shooter with an almost completely static world: enemies always spawn at the same places in the same numbers, and getting from any one point to another plays out exactly the same way every single time. Finally, it's a competitive shooter without voice chat, skill-based matchmaking or any kind of map voting or veto system.

Without these mechanics in place, each individual element of the experience feels lacking and somehow incomplete. There's no specific rule that a multiplayer shooter needs map voting, but it's something players have come to expect from the genre, and its absence is obvious. The lesson here is that it's important to be careful when imitating others. We must take the time to understand what parts of other games players simply like, and what parts they deem mandatory to their enjoyment of the experience. Missing out on key conveniences and mechanics is often distracting to players, and can even sometimes impair their ability to enjoy a title at all.

Conclusion

Destiny isn't a game that fails in every respect. It has superb shooting mechanics, with great feeling moment-to-moment action. It also looks beautiful and consistently runs smoothly, and the action is complemented by a beautiful score. The game oozes craftsmanship out of nearly every pore, but it fails to impress due to its crippling design weaknesses.

In the end, it goes to show that even a perfectly polished game can fail if its core design philosophies are misplaced.


How, Where, and When to Add Video Ads to Your Mobile Games

$
0
0

One day, you come up with a great game idea that you think could be a hit. You and your spend countless hours bringing your idea to life, and then a few more to squash all the bugs.

Once you've finished, you decide to add a few video ads. You aren't quite sure where to add them, so you place them haphazardly without thinking about the best user experience. When you publish your app, the reviews come piling in: "There are too many ads!".

If this has happened to you, then you probably planned your monetization around your game instead of your game around monetization. Poorly implemented ads can decimate the launch of the next big app and this article will help guide you in the types of videos ads available, how to use the video ads, providers, and best practices for implementation.

Types of Video Advertisements

Video ads come in at varying length, but the industry standard seems to be 15 seconds, occasionally 30 seconds. Essentially, there are two types of videos advertisement (forced and un-forced) with two types of implementation (voluntary and involuntary). 

Figure 1 - An Example Video Ad from Vungle
An Example Video Ad from Vungle

Forced vs Un-Forced

Forced video advertisements do not allow the player to skip the video content. Some video providers calculate your revenue by percentage of the video watched, and forced video ads will always result in higher revenue. However, forced video ads can result in a poor user experience because they can interrupt the natural flow of the game. 

Un-forced video ads allow the player to skip the video content by pressing a Close button. This type of video ads results in lower revenues, but can allow a better user experience because the user can skip the video content.

Voluntary vs Involuntary

Voluntary ads are considered opt-in ads where the user has to initiate the video advertisement. With these, the user is usually rewarded with a virtual item for watching the ad. 

Involuntary ads are videos that appear without the user's consent. These are commonly displayed between levels or at certain milestones within the game.

How to Implement Video Ads: A Quick Overview

Although each platform will offer a different API, you should have access to some basic functionality with implementing video advertisements. Most platforms should at least have the following:

  • isAdAvailable()
  • playAd()

Note: This tutorial focuses on pseudocode and the general concept of displaying video ads. Please refer to your platform's documentation for detailed information on integrating video ads.

First up is the function isAdAvailable(). Most platforms should support this and it will return true or false depending on whether or not a video ad is available. Some platforms go so far as to download and cache the next video for you!

Next, all platforms should support the playAd() function. When you call this function, you’ll display the full screen ad within your app.

When you want to display an app, you’ll want to check to see if an ad is available and if a video is available display it. This can be done by first calling isAdAvailable() and then calling playAd().

Video Advertising Providers

With the popularity of video advertisements growing, there are a lot of video content providers available. If you are just starting out, you may want to see if your platform supports Vungle or AdColony. Both platforms have widespread adoption, and have been implemented very well.

Games Built for Video Ads

In today's market, there are new game genres popping up all the time—clickers (like Bitcoin Billionaire) and animal simulations (like Crazy Goat) being two recent examples. With a better understanding of video advertising, let’s dig into what types of games are a good fit for video ad monetization. 

Is Your Game Paid or Free?

If your game is paid, your monetization strategy is solved in one step. The consumer pays for your app once and most games do not ask for additional purchases.

If your game is free, it’s an unwritten truth that, although most consumers dislike advertisements, they will be willing (and will most likely expect) to see ads in it.

What Type of Game Are You Creating?

Certain genres lend themselves better to video advertisements than other genres. Casual games and action games can be a perfect fit due to the way player progression is set up, while other games, such as RPGs or puzzle games, often are not. 

In general, if your game uses a built-in monetary system using virtual items for player progression or if you reward the player with virtual items, your app is probably a good fit for video advertisements.

Where Can You Integrate Video Ads?

Instead of injecting video ads in-between levels and forcing players to view the ad, your app will have a better player experience if they opt-in to the videos. Although there are plenty of creative ways to integrate ads, your integration points can focus on pre-gameplay, during gameplay, and post-gameplay. However, the structure of your game might not provide natural break points where ads could be inserted.

  • Pre-gameplay: If your game is split into levels or stages, or has typically short "bursts" of gameplay like Flappy Bird, you could offer the player a power-up or boost that will affect their performance in the next level or on their next attempt.
  • During gameplay: If there are points in your game where the player will naturally pause to think about the answer to a puzzle or how to progress, you could offer the player a hint.
  • Post-gameplay: If your game features levels or stages, or has short bursts of gameplay, or if it is possible for the player to fail, lose a life, or die, you can offer the player the chance to increase the score or virtual items they obtained on their last run, or to pick up where they left off.

If you choose to offer a reward system based on video ads, you need to consider what type of bonuses you’ll offer to balance the game play. Otherwise, you could end up with a game that’s very lopsided!

Examples of Integrated Video Ads

Video advertisements that are integrated well can add to the overall experience of your game, and a happy user tends to leave happy reviews. Instead of just placing video ads at the end of a level or in the middle of the gameplay experience, let’s look at some real case examples of ways that you can integrate video ads in your next app.  

Player Progression

One popular way to integrate video advertisements is to allow player progression after watching a video advertisement. Instead of forcing the player to purchase an in-app item, you can give them the option to watch a short ad and reward them with an in-game hint or boost. In the screenshot below, the player can tap the star icon to watch a 15 second video to receive two hints that are instantly applied to the puzzle.

Screenshot of 90s Word Puzzles on Google Play
Screenshot of 90s Word Puzzles on Google Play.

By allowing them to opt-in to a video, the app isn’t forcing advertisements on the player, the player is happy, and the app still generates revenue. 

Word games aren’t the only way to allow player progression with video ads. If the app is a fighting game, the app could allow the player to watch a video for faster punches or stronger bullets.

Rewards

Another way to implement video advertisements is to allow the player to increase their rewards after watching a short ad. This feel-good experience leads to a better user experience, and can lead to recurring revenue for the app. If this option is provided any time the user earns a reward, the potential revenue could be more than an individual in-app purchase.

One example of using video ads for rewards is in doubling the player's experience points. If the player beats a level and earns 25XP, you can give the player a chance to earn 50XP instead, if they watch a short ad.

Another example is the way that the popular game Bitcoin Billionaire incorporated video ads. When a drone appears, the player can tap the box to receive a random positive or negative event. If the event is positive, they can potentially double the reward by watching a short video.

Screenshot of Bitcoin Billionaire
Screenshot of Bitcoin Billionaire.

Note: I spent more time in Bitcoin Billionaire than I care to admit, and I watched a lot of ads to double my rewards.

Restart From Checkpoint

Another way your app can offer an incentive for watching an ad is by allowing the player to pick up where they lost. This is especially useful in games that are level-driven with set beginning and end points. For example, if the player crashes half way through the race in a driving game, you could offer the player a chance to watch a short video to pick up where they crashed.

Closing Notes

After reading this article, I hope that you have learned how to get started monetizing your app using video advertisements, and how to use them effectively.

I've focused heavily on games, but video advertisements can be used within non-game apps as well. However, you are more limited with the usage of video ads; they should be placed at natural breaks in the content.

Thank you for taking the time to read this article. Are there other ways that you are currently using monetization? I'd love to hear them!

References



5 Approaches to Crafting Systems in Games (and Where to Use Them)

$
0
0

Crafting has expanded from an rarely-seen mechanic in role-playing games to a nearly ubiquitous inclusion in all modern titles. It's now not only used in nearly every RPG, but also in first-person-shooters, action games, driving simulators, and even Steam's user profile badge system. 

There's something thematically appealing about transforming base resources into useful goods, and players enjoy the strategy and choice that customization can enable. Though widely used, there is substantial variation in the appearance and implementation of crafting systems. In this article, I categorize these systems into five approaches, and highlight what works best about each and how they are best used.

Type 1: "Money by Another Name"

Crafting in Dungeons of Dredmor
Crafting in Dungeons of Dredmor.

As Seen In: Dragon Age: Origins, The Elder Scrolls V: Skyrim (Blacksmithing), The Legend of Zelda: Skyward Sword, XCOM: Enemy Unknown, Assassin's Creed III, Terraria, Dungeons of Dredmor, Kingdom of Loathing, Bioshock, and many more.

This is the most common way games feature crafting, but it's actually only "crafting" in a conceptual sense. Here, the player pays for a desired item or upgrade, with resources that they've collected, in a fixed and transparent exchange. The so-called crafting here is abstract: you hand over some units of wood to a craftsman or workbench, and they hand you back a wooden shield. 

This is generally indistinguishable from an in-game market system, but instead of using a money themed resource like gold coins, we're using a raw material themed resource, like iron bars, oak logs, or wolf pelts.

Advantages

Due to its simplicity, this type of crafting is immediately familiar and easy to grasp. The inputs and outputs of the system are clear and unambiguous, so the system requires little or no explanation. Because it leverages existing systems, it also makes it easy to implement, and sometimes even employs the exact same interface that shopping with "money" uses. It's an effective way to add flavor and color to a market system, as the metaphor of turning raw goods into products can be richer than that of purchasing products with money.

This system can also provide a balancing function to the game by allowing additional control over what the player has access to. By making the currency exchanged for a given item rarer, or available only at a certain point in the game, the designer can disguise the true cost of the object. For example, if a cool sword costs 1,000 gold, the player might grind to save that up or think that costs too much and ignore it. But if it costs 50 steel, then it may seem easier to attain, even if it takes the same amount of time and effort to gather one unit of steel as it does to gather 20 units of gold.

Disadvantages

This system's simplicity is its own drawback, and it's typically insufficient for games striving for more immersion. It's also difficult to strike the right balance between being a small but flavorful inclusion and feeling extraneous and tacked on. Plus, if too many resource currencies are used, it's very easy to make the system overcomplicated and tedious. 

In Assassin's Creed III, for example, the crafting system uses dozens of ingredients. Crafting one unit of Firearms, which has no use besides being sold for money, requires combining Weapon Handles (themselves crafted from Maple Lumber), Iron Ore, and Flints (itself made from Limestone, Lead Ore and more Iron Ore). So much detail can easily exhaust the player if the rewards for navigating such a dense system are too small.

Type 2: "Find the Recipe"

Finding a Recipe in Fallout 3
Finding a Recipe in Fallout 3.

As seen in: Dragon Age: Inquisition, Diablo III, World of Warcraft, Dead Island, Fall Out 3/New Vegas, The Witcher 2, Arcanum: Of Steamworks and Magick Obscura

In this system, crafting a given item can't be done until a representational recipe, schematic, or blueprint has been found that unlocks it. These recipes may be found in the world, given as rewards for completing challenges, or purchased from stores. 

Once the player acquires the recipe, crafting proceeds just as with "Money by Another Name" by exchanging resources for outputs in a fixed fashion; the difference here is that the player needs not only the underlying resources for the exchange, but also the knowledge that the exchange can be made at all. More rarely, the recipe might unlock a more customizable form of crafting, as it does in Dragon Age: Inquisition.

Advantages

This system can provide some additional flavor to the crafting process, as it represents the idea that merely having resources isn't enough; the player must also know how to combine them. Also, by adding a step to the crafting process, this effectively increases the number of rewards a player can be given without increasing the total number of items. This has some completionist appeal too, as the blueprints must be hunted down and collected. 

It also maintains surprise for the player, since the full extent of what can be acquired isn't known right away, unlike in a shop-like system. Lastly, it provides the designer with an additional gating mechanic by preventing players from accessing certain content too early, even if the requisite resources themselves are available.

Disadvantages

You must take care in balancing the difficulty of acquiring schematics and the underlying resources needed with the value of the items they produce. If the reward is insufficient, then the system will largely be ignored in favor of items acquired through other means (like stores or enemy loot). Conversely, if it's too good, it can displace the other avenues of item acquisition. 

As with "Money by Another Name", it can be prone to over-use due to the simplicity of implementation, and acquiring all the schematics can thus become a chore. Dragon Age: Inquisition, for example, has over 200 weapon and armor schematics, and they must each be found or purchased individually.

Type 3: "Guess and See What Sticks"

Potion Crafting in The Elder Scrolls Online
Potion Crafting in The Elder Scrolls Online.

As seen in: Minecraft, Kingdoms of Amalur: Reckoning (Alchemy), The Secret World, The Elder Scrolls V: Skyrim (Alchemy), The Elder Scrolls Online (Alchemy), and Diablo II (Horadric Cube).

In this method, the recipes for crafting are neither immediately known nor acquired in-game, but must instead be discovered by the player through combinatorial trial and error. The player is generally presented with a crafting interface in which they can combine the resources they have collected, and is tasked with finding valid outputs by trying different arrangements of inputs. 

The complexity of this can range from merely choosing different ingredients (as in Skyrim), to additionally choosing their quantity or physical placement (as in Minecraft). This makes the crafting combination not an abstract unlocking mechanism, but instead a literal recipe that the player must learn. Because of the ingredient-focused nature of this system, it's most commonly seen when the crafting is flavored as alchemy or cooking.

Advantages

Unlike "Money by Another Name" and "Find the Recipe", in which the crafting outputs are known or accessible, here there can be a sense of real discovery for the player. Rather than merely being told what the crafting system can create, the player is able to research and experiment in order to discover possible items they can create. 

Though the crafting here is still abstract on some level, the process of discovering what works more closely approximates actual experimentation. It also makes the process of crafting require more direct player involvement, which can highlight the mechanic or its emphasis in the overall game design. Because what the player can test is limited to the resources they have gathered, it also provides the same balancing/restriction function as the previous types.

Disadvantages

If the system is too punishing for incorrect guesses (consuming rare resources, for example), then it may discourage players from actually exploring the system. If the correct ingredient combinations seem too arbitrary, then it may be feel less like a game of discovery and more like a random guessing game. In either case, if it is too punishing or difficult, the player will invariably look the answers up instead. In such cases, the system is really just "Money by Another Name", but with an additional layer of player frustration. 

Also, such a system can become unduly laborious (and necessitate outside resources) if there are too many recipes to remember, or if the correct answers aren't recorded in-game.

Type 4: "Made-to-Order Customization"

Armor Crafting in Dragon Age Inquisition
Armor Crafting in Dragon Age Inquisition.

As seen in: Assassin's Creed: Revelations, Dragon Age: Inquisition, Dead Space 3, The Elder Scrolls V: Skyrim (Enchanting), The Elder Scrolls IV: Oblivion (Spell Creation), Mass Effect 3, Monster Hunter Tri, Kingdoms of Amalur: Reckoning (Sagecraft), and Mercenary Kings.

This is any system in which the crafting is modular or dynamic instead of following a set formula or fixed exchange. Rather than a shop-like mechanic, here the crafting gives items a build-your-own flavor, where the player is able to pick from a set of choices. 

In Assassin's Creed: Revelations the player can make bombs, and by choosing the casing, fuse and contents, they can make a variety of different explosives with different uses and effects. Unlike in "Guess and See What Sticks", where the player's "choice" affects a binary right or wrong outcome, here the player is actually having to commit to a strategic choice from a range of possible, known outcomes. This can be reflected in-game as choosing different materials for an item, different design elements or add-ons, or even purely aesthetic flourishes.

Advantages

This system permits an in-depth system of customization that can be very rewarding for players, and can give otherwise static items or equipment new versatility. Since the player has an actual impact on the outcome of the crafting process, it gives them more agency than they have in simply purchasing or finding a better item. And if the options available have distinct uses, trade-offs and drawbacks, then it can create an additional layer of strategic choice in the gameplay. 

The increased flexibility and depth here can make the crafting process an integral part of the core mechanics of a game, instead of just as a flavorful method for producing items.

Disadvantages

This type of system has an increased level of complexity compared to the previous types, and while that provides additional depth, it also means that it is harder to implement. Increased complexity also requires more attention from the player, which might distract their focus if crafting is not meant to be a core game element. On the other hand, if the system has too few real choices, then its depth is largely superficial and will be optimized to a few "correct" choices, removing the advantage of using such a system.

Overall, due to the number of permissible outcomes, it can be particularly difficult to balance custom created items against items acquired from other sources.

Type 5: "Anything is Possible"

Alchemy in Atelier Escha  Logy Alchemists of the Dusk Sky
Alchemy in Atelier Escha Logy Alchemists of the Dusk Sky.

As seen in: Vagrant Story, The Legend of Mana, Treasure of the Rudra (Kotodama), Breath of Fire III (Dragon Genes), the Atelier Series, and The Elder Scrolls III: Morrowind (Spellcrafting).

This a broad category covering anything featuring a true crafting system, where the input variables can produce a wide array of possible outcomes. This allows for a truly dynamic process with very deep strategic choice, like the blade-and-grip combination system of Vagrant Story which has thousands of permutations. This can elevate the crafting system to being the primary game mechanic, as it is in the Atelier series of JRPGs. "Anything is Possible" systems are almost always the only source of the items they produce, because it can be very difficult to balance the outcome against something more easily available.

The complexity such systems introduce can take players a great deal of time to fully explore. The tempering system in The Legend of Mana is so complicated (and mostly undocumented in the game) that players are still discovering useful recipes for it nearly 15 years after the game's release.

Advantages

Investigating a system of this depth can be extremely rewarding. Having so much dynamic possibility can give the player meaningful agency in the process. The depth of these systems can greatly enhance gameplay or extend re-playability, and the difficulty of charting all possible outcomes can help the game resist optimization and the resultant stagnant gameplay.

This can be the least abstract form of crafting, since the immense variability of the system accords to the possibilities of actually making real things, and the testing and development the player must engage in is not unlike performing actual scientific inquiry.

Disadvantages

This level of depth makes these systems extremely hard to develop and implement, as they require substantially more design and testing than simpler systems. The resulting complexity is unnecessary for most games, where crafting is a supplemental mechanic rather than a major feature. 

Because these systems sometimes have infinite range of outcomes, the possibility of game-breaking combinations of ingredients (as in, combinations that produce items that are too good) can be hard to plan for.

Conclusion

Crafting can serve a variety of purposes in a game's design. It can provide a thematic and flavorful way for the player to access items, whether it's smithing armor in a fantasy setting or applying futuristic gun mods in a sci-fi game. It can play a vital balancing role in how the player acquires upgrades, and with greater subtlety than more overt restrictions. And, if built with sufficient depth, crafting can even provide strategic gameplay in and of itself. 

Crafting doesn't belong in every game, however. If tacked on without care, it can be extraneous and distracting, and if made complex for no reason, it can be tedious and burdensome. Crafting systems are a structural mechanic, and so they function best when used to support a game's primary design goals.

Tuts+ Survey Prize Winners

$
0
0

An overwhelming response to our 2014 survey created a small backlog in selecting the lucky prize winners. We've spent the past few weeks slowly making our way through each survey response, and we're excited to finally announce the winners!

Firstly we'd like to thank you for taking the time to tell us what you think, both the good and the bad. Your feedback will help us shape the future of Tuts+ and your online learning journey.

We've been inspired by your stories and we're overwhelmed with your desire to create better lives via self-directed online learning.

Over the coming months we'll be featuring interviews with some of the prize winners to give you the opportunity to meet some of your Tuts+ community. We will also be sharing some of the results of the survey. Please note, full names have not been shared below in order to protect the privacy of the winners. Each prize winner will also be contacted directly.

The Prize Winners

Grand Prize Winner: Catherine, South Africa

  • Lifetime membership to MadeFreshly Pro Plan (RRP $420/year)
  • Runscope team account for up to 25 team members and 500,000 requests per month (RRP $2,148)
  • UXPin Pro Plan yearly subscription (RRP $300)
  • A yearly license to Macaw (RRP $179)
  • One year designer membership to PixelKit (RRP $39)
  • $400 worth of PSD to HTML or PSD to WordPress services with Reliable
  • Six months of APM product, up to five hosts with New Relic (RRP $4,470)
  • Heart Internet VPS Plus SSDdrive 4 CPUs 50GB Space 5 GB Ram Plesk Control panel, 30 sites (RRP $1772)
  • A professional level shop for a year and a paid theme of your choice from Shopify (RRP $1,128)
  • A year for free on Motiv (RRP $180)
  • One year Adobe Creative Cloud licence (RRP $600)

Runner Up: Tassia, Holland

  • One year membership to MadeFreshly Pro Plan (RRP $420)
  • UXPin Pro Plan yearly subscription (RRP $300)
  • A yearly license to Macaw (RRP $179)
  • One year designer membership to PixelKit (RRP $39)
  • Six months free on Motiv (RRP $90)
  • $100 credit on Twillio (RRP $100)

Honorable Mention: David, United States

  • Six month membership to MadeFreshly Pro Plan (RRP $210)
  • UXPin Pro Plan yearly subscription (RRP $300)
  • A yearly license to Macaw (RRP $179)
  • One year designer membership to PixelKit (RRP $39)
  • Six months free on Motiv (RRP $90)

Editor Favorites: 

Joe, United KingdomRosario, Philippines
Steve, United StatesCory, United States
Daren, CanadaAttila, Germany
Ramsha, Pakistan
Bhanu, United Kingdom
Andrew, PhilippinesWayne, United States
David, AustraliaCrystal, United States
Philippe, Cananda
Edison, Dominican Republic
Logos of prize sponsors Motiv Runscope New Relic MadeFreshly Macaw Twilio Reliable Shopify Heart Internet PixelKit UXPin

Let Them Play: Don't Lock Your Players Out of Playing

$
0
0

Interactivity is a fundamental element of games design. Without it, a game isn't a game: it's a TV show, or a book, or an instance of some other static medium. Interactivity is what really defines games, and is (arguably) the single most important aspect for keeping players interested.

Sadly, interactivity is also an element that often falls to the wayside. We might get an idea for a cool game element, and become focused on getting that to work without looking at the implications. Or, worse, we ignore interactivity because "that's the way these games are done".

There are other ways to keep players entertained: the incredibly divisive Dear Esther had very poor interactivity, but managed to find a fan base nevertheless. So, if you're planning on making an art game, then the lessons here may not apply directly to you, but these aspects are important to be aware of anyway.

Dear Esther screenshot
Dear Esther is beautiful, but there's debate about whether it qualifies as a game.

The fact is, most players want to play—so locking them out is an incredibly effective way to frustrate them and make them lose interest. There are some obvious ways of doing this, such as including unskippable cutscenes or lengthy loading times, but we'll focus on game mechanics that lock players out, and how to avoid them.

Lockout as Punishment

In Monopoly, when a player loses all their money, they are removed from the game. In Counter-Strike, when a player dies, they are removed from the game until the next round. In both cases, the player has “lost”, and, as a punishment, they are no longer allowed to play. 

In cases like these, you will find these players very quickly losing interest in the game. In Monopoly, the losing player may have to sit around for an hour or more waiting for the game to finish, trying to keep themselves entertained by a game in which they are no longer invested. Counter-Strike players may tab out and browse the internet while waiting for the next round.

Removing a player from a game is extremely punishing, especially if they have to wait around to play again. Board game designers have realised this, and Euro games have rocketed in popularity, partly due to their "no player elimination" design strategy: rather than giving players resources to lose (such as money or hit points), they award victory to whoever has amassed the most points at the end of the game.

Similarly, in most first-person shooters, players respawn independently of round timers. For many Valve games, such as Team Fortress 2, instant-respawn mods are extremely popular. Dying is already punishment enough, and forcing a “time out” for performing badly just compounds that unnecessarily.

Even some single player games punish the player with a “game over” screen, forcing them to reload the game, and possibly even making them replay content. Super Meat Boy solved this issue by making respawning instantaneous—although this was arguably a necessity for SMBs deadly gameplay (where a player can easily die dozens of times trying a new level), it meant that death never really felt like a punishment.

Awesomenauts also tries to mitigate death. Although the player still has a "time out" period for dying, part of this time out is used playing a mini-game in which the player can collect Solar (the in-game currency). Players can even earn an achievement for playing this mini-game perfectly.

That's not to say that every single player game should have instant respawns, but simply having faster respawns, or the ability to get straight back into the action after death can really help a game maintain its flow, rather than breaking momentum with death screens and “Replay/Quit” menus.

Lockout While Gameplay Unfolds

This form of lockout is often (but not always) found in turn-based games. In most JRPG combat systems, players will find themselves able to perform certain moves and use abilities. Using these abilities will often enforce a short lockout while the move is executed, after which the player can continue playing.

While this sort of turn-based combat makes sense, it doesn't have to be done this way. The Paper Mario series have an excellent system where choosing attacks in combat opens up a quick time event mini-game. Striking an enemy might deal 10 damage, but if you press the attack button at the correct time during the animation (at the point where your weapon collides), then you might deal 20 damage instead. 

While mastering these “stylish moves” wasn't absolutely necessary to get through the game, it certainly made fights a lot easier, and players were expected to be able to hit some (if not all) of these moves.

Although this sort of added interaction is small, it can add up to a large effect over the course of the game, and it can be applied to a variety of situations.

Lockout While Waiting for Something to Happen

A lesser form of lockout occurs in some games when waiting for gameplay to progress. Here, although the player is not directly locked out, any action the user takes is normally detrimental to winning, so the player is forced to sit until a favourable position emerges.

Once again, Counter-Strike is guilty of this, although it is by no means the only offender. Due to the lethality of the guns, and the one-life-per-round system, “camping”—that is, hiding for long periods of time in difficult-to-see areas—is an incredibly effective tactic. This  “sit still, don't move” approach, while valid, also discourages active gameplay from participants. Although many Counter-Strike fans will no doubt protest that camping is part of the tactical gameplay, for new players it can be frustrating to be killed repeatedly by players that simply sit near the objective and shoot anyone that moves. 

There is no quick fix for this, but it is possible to mitigate it by changing certain basic gameplay elements. The first person shooter Dystopia gives players a "wallhack" ability, where once every 20 seconds they can send a "pulse" that briefly shows them the location of all enemies. Some Counter-Strike mods damage players who stay in one spot for too long, forcing them to keep moving or die. These help to a degree, but they are mostly just small fixes to the larger problem of one-life gameplay.

Even outside of games like Counter-Strike, players can find themselves in situations where they simply have to wait. If a player is waiting for health to regenerate, or for more resources to be gathered by workers, then they're effectively locked out. This doesn't mean that you should give the player everything instantly, but if they find themselves unable to do anything until the peasants gather enough gold for a castle, then they're not able to spend their time constructively.

But Waiting Can Be Good

There are times, of course, when it is good to make a player wait. Take the famous ladder scene from Metal Gear Solid 3, where the protagonist literally climbs a ladder for two minutes:

This, on the surface, seems like terrible game design. What would possess the designers to insert a mandatory two minute wait? The purpose is one of storytelling and pacing: the ladder comes after a hectic boss fight, and forcing the player to climb the ladder allows them to transition into a place of calm. Two minutes of not being attacked by enemies, two minutes of reflecting on the battle that took place, two minutes of being allowed a break from the chaos. 

As with almost all aspects of games design, you're allowed to break the rules, but its important to understand why you're breaking them.

Lockout During the Opponent's Turn

Locking players out of a game during their opponents' turns is, not surprisingly, a staple of turn-based games. In chess, a player is essentially locked out the game until his opponent moves. Players may be able to consider further options, but waiting for opponents to take their turn may leave them bored.

There are ways to mitigate this: chess has a popular variant known as speed chess, where each player is given a certain total amount of time (say, five minutes each), and must finish the game within that period. If a player runs out of time, they lose. And while speed chess is not popular with all chess players—partly because it does not allow for the same sort of long-term tactical planning as normal chess games—it does have its fans.

The problem with chess is that the game is complex. It takes time for a player to consider all the options available, especially when they start planning ahead. If you look at a game like noughts and crosses, you'll find that the gameplay is much faster. This is partly because noughts and crosses is a simpler game, but more due to the fact that there are limited options. In chess, the first move a player makes can be one of 18 possible options. In noughts and crosses, half that. In chess, as the game progresses, the player will likely find themselves with more and more options; in noughts and crosses, their options rapidly diminish.

Of course, it'd be strange to say that limiting a player's options automatically makes for a better game, as it's the choices we make within games that make things interesting. But if we ensure that the players aren't overwhelmed by choices, we can try to ensure that gameplay is kept relatively fast. Draughts (or checkers) finds itself in a middle-ground between chess and noughts and crosses; it's complex enough to provide an intellectual challenge, but limited enough that players will rarely have to spend ten minutes considering a single move. The player generally has fewer pieces available, and all the pieces do the same thing anyway (except kings).

Taking Turns Without Waiting

It's possible to partially solve the "waiting for my turn" issue by having simultaneous turns. The board game Diplomacy does this: during each turn, every player writes down their unit moves, then all those moves are enacted during the final phase. Civilisation also attempted this, though arguably less successfully: players were able to take their turns at the same time, but due to the more complex nature of the game this created some situations where the winner of a battle would be whoever moved their units first. While there are players who prefer the faster gameplay, it seems to somewhat destroy the turn-based nature of the game.

Civ4 Screenshot
Cities in Civilisation 4 provide a wealth of information to ponder over.

Rather than having a “fastest first” approach to simultaneous turns, it's possible to allow players to still play without having a direct effect on gameplay. Civilisation-style games generally require some heavy micro-management, especially at later levels, and there's no reason player can't deal with this during opponents' turns. 

You may want to build military units for an upcoming battle, so you could inspect each of your cities and change their build chain to something more suitable. Similarly, you may want to perform diplomatic actions, or change your government type. These actions do not have to be resolved instantly, but allowing players to chain up these actions for the start of their turn allows them to do something during other players' turns, and also (hopefully) makes their own turns faster.

Allowing players to take actions, or respond to certain events, during other players' turns is a good way to keep their involvement high. If a player is invested in opponents' actions, then they are more likely to remain interested in the game. However, if a player doesn't care what their opponent does, then they lose interest

Let Them Play

Keep the player playing. Try to keep wait times to a minimum, and if the player isn't in direct control, then at least try and give them actions they can perform to keep themselves entertained. 

When we break these rules, understand why you're breaking them. The most important thing in any game is fun, and sometimes a break from the action—a "ladder scene"—can be a welcome rest, or can help change the atmosphere. But even when trying changing things around, keeping the player involved in a simple way, such as by pressing X at the right time, will give them a feeling of involvement. Remember, we're making games.

References

Apply to Be the Next Tuts+ Code Course Producer!

$
0
0

Tuts+ Is Looking for a New Code Course Producer!

We’re looking for a new Course Producer to drive the content direction of Tuts+ Code Courses, working with a great team of regular instructors to produce videos on a range of web development topics. People love to teach! It's a highly rewarding experience being able to help them do that and as a result allow people to learn new skills and take on new opportunities.

As part of the Tuts+ Editorial Team, you'll have the flexibility of working at home to your own schedule, while working among a team of instructors and editors across timezones. You'll be setting the direction, managing people and projects and keeping an eye on consistency and quality of our video content.

The role will require you to work 40 hours per week. You may also have the opportunity to contribute your own courses as an instructor outside of this.

What We're Looking For

The ideal person to fill this rewarding, flexible role would be involved in the web development industry and enjoy keeping up-to-date with the latest trends. You have a background as a web developer or have been learning web development. You enjoy managing projects and working with others to achieve outcomes.

It's likely that you'll have:

  • Interest and experience in the web development industry.
  • Ability to work independently and effectively within a remote team environment.
  • High level of communication and project management skills.
  • Ability to solve problems and learn new things as needed.

It's a great bonus if you have: 

  • Experience with screencasting and / or video production.
  • Familiar with HTML, Markdown and GitHub.

Interested? Here's How to Apply

We're hoping to get the process moving fairly quickly with this role. So if you're thinking of applying please do so by Friday, January 30th (or sooner if possible!). If you have any further questions about this role, please contact code.courses@envato.com.

See the Application Form & Apply Now

Viewing all 728 articles
Browse latest View live