Endless Elevator – Early Access Beta

Endless Elevator went into Beta Testing on May 27, 2020, 8:10 PM.

Finally we made it to public testing! After a few months of keeping myself quiet and a seeming eternity of bug fixes I think I got a minimum viable product fit for your eyes, ears, and phone-ready fingers.

Please head over to our Beta Test Page and try it out. I’d love to get some feedback from other enthusiasts and developers.

Get it Here!

Chat With Us!

We have an anonymous Chat Channel right there on the page that you can trash talk into so go for broke! (You are also allowed to be supportive and kind as well if that is your nature).

🙂

The Beta Page has more information about the game as well links to articles written during development that explore the mechanics of the game.

Unity: Circular Movement with Triangles

My son asked to do some game programming with me last week. I was super excited. We did an eyeball rolling around in a circular motion (see image below). He did everything from working in Unity to making the assets and materials and I was very proud. I helped with the function to make it circle. It got me thinking how to explain circular movement and the use of Pythagorean triangles and cos and sin functions. They are simple maths but kind of hard to explain without a picture so hence this project about making circles with triangles.

Rolling Eyeballs!

The Basic Idea

To simplify I’ll work in 2D so we only got two Axis. X and Y. The Center of our circle will be on the 0 (zero, zero). So we could think of this as a co-ordinate system with two planes. If we were mathematicians we would call this a Cartesian Plane after Rene Descartes but being a Game Developer with Unity I’m going to call it the Scene view in 2D.

Our circle is defined by it’s Radius (ie. the distance from the Center).

On each Update() event our GameObject gets a new X,Y position on the Radius of the circle.

The Angle of Change

To start with we work out what the angle of difference is between this Update and the last one. The time passed during that period is known as the DeltaTime. Our “speed” is how many Radians we are travelling around the circle in that time. A radian is the length of the radius laid around the circumference of the circle. We multiply the Speed (how fast we are ticking through the radians) by the DeltaTime (time passed since last Update) to tell us that angle size.

# angleInRadians += RotateSpeed * Time.deltaTime;

Convert Polar to Cartesian

After working out the angle we have what is called a Polar Coordinate. That is we define our location by how far away it is (the distance – in this case as it’s a circle it’s always the same i.e. the radius), and what the angle (θ) is. Now we need to convert between that definition of a location in the Scene View to another one we can use in the Unity move function.

This is where Pythagoras and right angled triangles comes in – to convert from Polar Coordinates (r,θ) to Cartesian Coordinates (x,y) we use cos and sin :
x = r × cos( θ )
y = r × sin( θ )

In Unity there is already a function that does this:

newPosition = new Vector2(Mathf.Cos(angleInRadians), Mathf.Sin(angleInRadians)) * Radius;

But what does this really mean? Cos and Sin are just numbers. They represent the relationship between two sides of the triangle.

For example cos is the value of the relationship between the side of the triangle that is adjacent to the angle (the side next to the angle) and the hypotenuse (the long side on the other side of the angle). We are going to use cos to find the x value (how far along the horizon it is) in our Vector2 position. The same way we use Sin to find the y position (how far up or down).

In the image below see how there are squares built off each side of the triangle. Pythagorean theory for right angled triangles states that the area/volume of the two squares built of the two smaller sides will be equal to the area/volume of the big square built off the longest (hypotenuse) side of the triangle.

The big blue square has a volume of 1 because the radius of the circle is 1 unit on the Cartesian plane and One Squared is One. The volume of the other two squares (Magenta and Yellow) will always add up to one. Their volumes added together will always equal the volume of the blue square. This is the Pythagorean theory of right angled triangles in action. The length of a side of the Yellow box is the x value and the length of a side on the Magenta box is the y value. That’s our current position in (x, y) format which we can pass to the transform.position function.

The two images below show two Updates() that are reasonably close together so you can see in detail and in “freeze frame” what’s going on between the Updates() with all the variables. You can see the angle go from about 8 degrees to 20 degrees and the changing values for sin and cos which result in changing (x,y) values and volumes of the squares.

One Position on Update()
The next position on Update()

That’s basically it apart from some modifications to the values for being on the negative sides of the circle.

The script attached to the moving object is below but I’ve also put it on github here: https://github.com/zuluonezero/MoveInACircleWithTriangles

I’m finding it deeply satisfying to watch the triangles and squares get used to define a circle going round over and over again.

If this sort of thing floats your boat I’ve done some other posts on making curves using intersecting lines:

http://www.zuluonezero.net/2019/06/04/unity-2d-curves-using-triangles/

Also on using sin to use curves in movement: http://www.zuluonezero.net/2018/06/20/fly-birdy-fly-2d-curved-movement-in-unity/.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEditor;
public class CircleMe : MonoBehaviour
{
public float RotateSpeed = 5f;      // How fast we move through the radians
public float Radius = 0.1f;         // How "deep" the circle is
public Vector2 centreOfCircle;      // The Centre of our circle
public float angleInRadians;        // The angle (in radians) between our position between one update and the next
// A radian is the angle created if the length of the radius 
// is laid along the circumference of the circle (about 57.2958 degrees)
public Vector2 newPosition;         // The new position for every new Update event
public Text displayText;
public float angleInDegrees;
private LineRenderer triLine;
private Vector3 centre;
private Vector3 yLoc;
public float angle4Display;
private Vector3 sq1;
private Vector3 sq2;
private Vector3 sq3;
private Vector3 sq4cyan1;
private Vector3 sq4cyan2;
public Vector2 slopecyan;
public Vector2 p1cyan;
public Vector2 p2cyan;
private void Start()
{
centreOfCircle = transform.position;
// (0, 0) but could be anywhere
centre = transform.position;
}
private void Update()
{
angleInRadians += RotateSpeed * Time.deltaTime;
if (angleInRadians > 6.28319f)
{
angleInRadians = (angleInRadians % 6.28319f);
}
// eg.       0 += 5 * 0.25  (answer is 1.25)    // if deltaTime was 0.25 of a second
// and our initial angle was 0 radians. 
// Remember += is short for x = the current value of x plus itself (x = x + x)
// we need to convert the angle and radius into an x, y position
newPosition = new Vector2(Mathf.Cos(angleInRadians), Mathf.Sin(angleInRadians)) * Radius;
//          = new Vector2(opposite/hypotenuse(1.25), adjacent/hypotenuse(1.25)) * Radius;
// (x, y)   =            (0.9997, 0.0218) * 0.1
// (x, y)   =            (0.09997, 0.00218)
transform.position = centreOfCircle + newPosition;   // Adding Vectors
// (0.09997, 0.00218) = (0, 0) + (0.09997, 0.00218)
// this is our starting (x, y) position
// Now do it again for the next Update - the code below has been duplicated for this example
/*
angleInRadians += RotateSpeed * Time.deltaTime;
// eg.       1.25 += 5 * 0.25  (answer is now 1.25 + 1.25 = 2.5)    // if deltaTime was 0.25 of a second 
newPosition = new Vector2(Mathf.Cos(angleInRadians), Mathf.Sin(angleInRadians)) * Radius;
//          = new Vector2(opposite/hypotenuse(2.5), adjacent/hypotenuse(2.5)) * Radius;
// (x, y)   =            (0.99904, 0.0436) * 0.1
// (x, y)   =            (0.09990, 0.00436)
transform.position = centreOfCircle + newPosition;   // Adding Vectors
// (0.09990, 0.00436) = (0, 0) + (0.09990, 0.00436)
*/
if (transform.position.x > 0)
{
yLoc = new Vector3(centre.x + Radius, centre.y, centre.z);
}
else
{
yLoc = new Vector3(centre.x - Radius, centre.y, centre.z);
}
angleInDegrees = angleInRadians * 57.2958f;
}
public void OnDrawGizmos()
{
// Radius
Gizmos.color = Color.blue;
Gizmos.DrawWireSphere(centre, Radius);
// Yellow square
Gizmos.color = Color.yellow;
sq1 = new Vector3((transform.position.x / 2), (transform.position.x / 2f), 0f);
if (transform.position.x > 0)
{
if (transform.position.y > 0)
{
Gizmos.DrawWireCube(new Vector3((transform.position.x / 2), -(transform.position.x / 2f), 0f), new Vector3((transform.position.x), (transform.position.x), (transform.position.x)));
Handles.Label(new Vector3((transform.position.x / 2), -(transform.position.x / 2f), 0f), "Vol: " + (transform.position.x * transform.position.x));
}
else
{
Gizmos.DrawWireCube(sq1, new Vector3((transform.position.x), (transform.position.x), (transform.position.x)));
Handles.Label(sq1, "Vol: " + (transform.position.x * transform.position.x));
}
}
else
{
if (transform.position.y > 0)
{
Gizmos.DrawWireCube(sq1, new Vector3((transform.position.x), (transform.position.x), (transform.position.x)));
Handles.Label(sq1, "Vol: " + (transform.position.x * transform.position.x));
}
else
{
Gizmos.DrawWireCube(new Vector3((transform.position.x / 2), -(transform.position.x / 2f), 0f), new Vector3((transform.position.x), (transform.position.x), (transform.position.x)));
Handles.Label(new Vector3((transform.position.x / 2), -(transform.position.x / 2f), 0f), "Vol: " + (transform.position.x * transform.position.x));
}
}
// Magenta square
Gizmos.color = Color.magenta;
sq2 = new Vector3((transform.position.y / 2), (transform.position.y / 2f), 0f);
if (transform.position.x > 0)
{
if (transform.position.y > 0)
{
Gizmos.DrawWireCube(new Vector3((transform.position.x + transform.position.y / 2), (transform.position.y / 2f), 0f), new Vector3((transform.position.y), (transform.position.y), (transform.position.y)));
Handles.Label(new Vector3((transform.position.x + transform.position.y / 2), (transform.position.y / 2f), 0f), "Vol: " + (transform.position.y * transform.position.y));
}
else
{
Gizmos.DrawWireCube(new Vector3((transform.position.x + Mathf.Abs(transform.position.y) / 2), (transform.position.y / 2f), 0f), new Vector3((transform.position.y), (transform.position.y), (transform.position.y)));
Handles.Label(new Vector3((transform.position.x + Mathf.Abs(transform.position.y) / 2), (transform.position.y / 2f), 0f), "Vol: " + (transform.position.y * transform.position.y));
}
}
else
{
if (transform.position.y > 0)
{
Gizmos.DrawWireCube(new Vector3((transform.position.x - transform.position.y / 2), (transform.position.y / 2f), 0f), new Vector3((transform.position.y), (transform.position.y), (transform.position.y)));
Handles.Label(new Vector3((transform.position.x - transform.position.y / 2), (transform.position.y / 2f), 0f), "Vol: " + (transform.position.y * transform.position.y));
}
else
{
Gizmos.DrawWireCube(new Vector3((transform.position.x + transform.position.y / 2), (transform.position.y / 2f), 0f), new Vector3((transform.position.y), (transform.position.y), (transform.position.y)));
Handles.Label(new Vector3((transform.position.x + transform.position.y / 2), (transform.position.y / 2f), 0f), "Vol: " + (transform.position.y * transform.position.y));
}
}   
// Red Triangle
Gizmos.color = Color.red;
Gizmos.DrawLine(centre, new Vector3(transform.position.x, centre.y, centre.z));
Gizmos.DrawLine(new Vector3(transform.position.x, centre.y, centre.z), transform.position);
Gizmos.DrawLine(transform.position, centre);
if (transform.position.x > 0)
{
if (transform.position.y > 0)
{
Gizmos.DrawLine(new Vector3(transform.position.x - 0.1f, 0f, 0f), new Vector3(transform.position.x - 0.1f, 0.1f, 0f));
Gizmos.DrawLine(new Vector3(transform.position.x - 0.1f, 0.1f, 0f), new Vector3(transform.position.x, 0.1f, 0f));
}
else
{
Gizmos.DrawLine(new Vector3(transform.position.x - 0.1f, 0f, 0f), new Vector3(transform.position.x - 0.1f, -0.1f, 0f));
Gizmos.DrawLine(new Vector3(transform.position.x - 0.1f, -0.1f, 0f), new Vector3(transform.position.x, -0.1f, 0f));
}
}
else
{
if (transform.position.y > 0)
{
Gizmos.DrawLine(new Vector3(transform.position.x + 0.1f, 0f, 0f), new Vector3(transform.position.x + 0.1f, 0.1f, 0f));
Gizmos.DrawLine(new Vector3(transform.position.x + 0.1f, 0.1f, 0f), new Vector3(transform.position.x, 0.1f, 0f));
}
else
{
Gizmos.DrawLine(new Vector3(transform.position.x + 0.1f, 0f, 0f), new Vector3(transform.position.x + 0.1f, -0.1f, 0f));
Gizmos.DrawLine(new Vector3(transform.position.x + 0.1f, -0.1f, 0f), new Vector3(transform.position.x, -0.1f, 0f));
}
}
// Cyan Square
Gizmos.color = Color.cyan;
if ((transform.position.x > 0 && transform.position.y > 0) || (transform.position.x < 0 && transform.position.y < 0))
{
slopecyan = new Vector2(transform.position.x, transform.position.y);
p1cyan = new Vector2((transform.position.x - slopecyan.y), (transform.position.y + slopecyan.x));
p2cyan = new Vector2((centre.x - slopecyan.y), (centre.y + slopecyan.x));
Gizmos.DrawLine(transform.position, p1cyan);
Gizmos.DrawLine(centre, p2cyan);
Gizmos.DrawLine(p1cyan, p2cyan);
}
else
{
slopecyan = new Vector2(transform.position.x, transform.position.y);
p1cyan = new Vector2((transform.position.x + slopecyan.y), (transform.position.y - slopecyan.x));
p2cyan = new Vector2((centre.x + slopecyan.y), (centre.y - slopecyan.x));
Gizmos.DrawLine(transform.position, p1cyan);
Gizmos.DrawLine(centre, p2cyan);
Gizmos.DrawLine(p1cyan, p2cyan);
}
Vector3 lbl = new Vector3((p1cyan.x / 2), (p1cyan.y / 2), 0f);
Handles.Label((lbl), "Vol: " + (Radius * Radius));
// Angle Marker
if (transform.position.y > 0)
{
if (transform.position.x > 0)
{
angle4Display = angleInDegrees;
Handles.DrawSolidArc(centre, Vector3.forward, yLoc, angle4Display, 0.25f);
}
else
{
angle4Display = -(angleInDegrees - 180f);
Handles.DrawSolidArc(centre, Vector3.forward, transform.position, angle4Display, 0.25f);
}
}
else
{
if (transform.position.x < 0)
{
angle4Display = (angleInDegrees - 180f);
Handles.DrawSolidArc(centre, Vector3.forward, yLoc, angle4Display, 0.25f);
}
else
{
angle4Display = -(angleInDegrees - 360f);
Handles.DrawSolidArc(centre, Vector3.forward, transform.position, angle4Display, 0.25f);
}
}
// Labels
Handles.color = Color.blue;
Handles.Label(centreOfCircle, angle4Display.ToString());
Handles.color = Color.white;
Handles.Label(transform.position, "X: " + System.Math.Round(transform.position.x, 2) + " Y: " + System.Math.Round(transform.position.y, 2));
// sin opposite/hypotenuse
Handles.Label(new Vector3(1.2f, 0.8f, 0f), "sin opposite/hypotenuse");
Handles.Label(new Vector3(1.3f, 0.7f, 0f), "sin: " + Vector3.Distance(centre, (new Vector3(transform.position.y, 0f, 0f))) / Vector3.Distance(centre, (transform.position)) );
// cos adjacient/hypotenuse
Handles.Label(new Vector3(1.2f, 0.6f, 0f), "cos adjacient/hypotenuse");
Handles.Label(new Vector3(1.3f, 0.5f, 0f), "cos: " + Vector3.Distance(centre, (new Vector3(transform.position.x, 0f, 0f))) / Vector3.Distance(centre, (transform.position)));
// tan opposite/adjacient
Handles.Label(new Vector3(1.2f, 0.4f, 0f), "tan opposite/adjacient");
Handles.Label(new Vector3(1.3f, 0.3f, 0f), "tan: " + Vector3.Distance(centre, (new Vector3(transform.position.y, 0f, 0f))) / Vector3.Distance(centre, (new Vector3(transform.position.x, 0f, 0f))));
Handles.Label(new Vector3(1f, -0.3f, 0f), "Next Position on Update()");
Handles.Label(new Vector3(1f, -0.4f, 0f), "newPosition = new Vector2(Mathf.Cos(angleInRadians), Mathf.Sin(angleInRadians)) * Radius");
Handles.Label(new Vector3(1f, -0.5f, 0f), "= new Vector2(opposite/hypotenuse(angleInRadians), adjacent/hypotenuse(angleInRadians)) * Radius");
Handles.Label(new Vector3(1f, -0.6f, 0f), "" + Mathf.Cos(angleInRadians) + ", " + Mathf.Sin(angleInRadians) + " * "  + Radius + " = " + newPosition);
}
}
/*
Using Cartesian Coordinates we mark a point by how far along (x) and how far up (y) it is:
Using Polar Coordinates we mark a point by how far away (magnitude or in this case as it's a circle always the radius is the same), and what angle (θ) it is:
To convert from Polar Coordinates (r,θ) to Cartesian Coordinates (x,y) :
x = r × cos( θ )
y = r × sin( θ )
Example: add the vectors a = (8,13) and b = (26,7)
c=a+b
c= (8,13) + (26,7) = (8+26,13+7) = (34,20)
*/

Unity Script: Find all Assets and Prefabs with a given Component by Type

Hi Xander here…

The current game in development is Endless Elevator. During the development cycle we have used a bunch of placeholder audio files and Audio.Play() functions. This is to get some quick auditory feedback from the game during testing phases. It’s much more fun to hear a gun go off and have the bullet zing past to impact on the wall and make a satisfying “Crack-Bang! Zoom! Whhhumppp!” noise than hearing nothing at all.

But now we are ramping up development of the noises and soundtracks in the game and while it’s not my favourite part (and not my skill set) I have enjoyed watching The Baron get all Foley artist. I recently witnessed the birth of a gun noise by repeatedly slamming a dryer door with a bunch of copper wire and a microphone taped to the inside. Game development is really fun and you get to do a shocking amount of creative weirdness. I digress.

Since we now have to start replacing all the temporary raw audio calls with some proper artistry, and serious sound work I had to find all those existing audio components out there in the project and add them to Unity Mixer channels. At the moment Endless Elevator is not huge but it’s not small either. There are are maybe a hundred prefabs sorted into four sub-folders in the Project explorer and I didn’t really want to go searching through them all and open up each one looking for Audio Sources. (Does anyone else find the new Prefab workflow of having to open up a prefab for editing a few clicks too many?)

So this script is my solution for finding all the Audio Sources in the project.

(I do like to make a Zulu heading in the Editor Window to run scripts from)

Finding Audio Sources in the Current Scene

Firstly I wanted to know how many Assets in the current scene had Audio Components. This wasn’t too hard as the game is mostly procedural (and endless…) so there isn’t too many objects to a scene in the Edior.

I’ll put the full script at the end but this is the code snippet to do that:

Debug.Log("Finding all Assets in the Current Scene that have an Audio Component...");
AudioSource[] myAudioSources = FindObjectsOfType(typeof(AudioSource)) as AudioSource[];
Debug.Log("Found " + myAudioSources.Length + " objects with an AudioSource attached");
foreach (AudioSource item in myAudioSources)
{
Debug.Log(item.gameObject.name);
}

The main function here is the FindObjectsOfType() which I rarely have cause to use but is perfect for this sort of thing. The output in the Console is shown below…

I also found a really useful tool on the Asset Store that looked interesting for those who need a bit more power for doing this: Asset Usage Detector. It looks pretty full featured and is free! Shout out and respect to the dev Süleyman Yasir Kula who has a whole heap of good looking tools up there.

Finding Audio Sources in Prefabs

The next bit took a longer to write. I had never really delved into the Asset Database API and I found it really interesting and a bit of an eye-opener. Which is why I decided to write it up in this post.

This is how I found all the Prefabs in my project with an Audio Component:

Debug.Log("Finding all Prefabs that have an Audio Component...");
string[] guids = AssetDatabase.FindAssets("t:Object", new[] { "Assets/Prefabs" });
foreach (string guid in guids)
{
//Debug.Log(AssetDatabase.GUIDToAssetPath(guid));
string myObjectPath = AssetDatabase.GUIDToAssetPath(guid);
Object[] myObjs = AssetDatabase.LoadAllAssetsAtPath(myObjectPath);
//Debug.Log("printing myObs now...");
foreach (Object thisObject in myObjs)
{
//Debug.Log(thisObject.name);
//Debug.Log(thisObject.GetType().Name); 
string myType = thisObject.GetType().Name;
if (myType == "AudioSource")
{
Debug.Log("Audio Source Found in...  " + thisObject.name + " at " + myObjectPath);
}
}
}

The first step was this line:

string[] guids = AssetDatabase.FindAssets(“t:Object”, new[] { “Assets/Prefabs” });

This makes an array of all the Objects under my Prefabs folder in the Project Hierarchy. You can replace t:Objects with any other Type but I was playing around with what I could find using this method so left it in there in case I wanted to look at other things. It’s also important to note that as the Audio Component is attached to a Game Object as a Component, and is not an Object in it’s own right, we have to reference the Object first so we can start to access the added components.

Next we get a reference to the path of the Object and use LoadAllAssetsAtPath to make an array of the “Assets” that we can start to interrogate. These are the active lines:

string myObjectPath = AssetDatabase.GUIDToAssetPath(guid);
Object[] myObjs = AssetDatabase.LoadAllAssetsAtPath(myObjectPath);

Next we run a foreach loop through those Objects and test for the AudioSource name in the component. The output in the console looks like this:

The Whole Script…

I’ll put the whole script below:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
public class FindAssets : MonoBehaviour
{
// Find all object that have an Audio component
[MenuItem("Zulu/FindAssets Audio")]
static void FindAudio()
{
Debug.Log("Find Assets Script started...");
// Find all Prefabs that have an Audio component under my Prefabs folder
Debug.Log("Finding all Prefabs that have an Audio Component...");
string[] guids = AssetDatabase.FindAssets("t:Object", new[] { "Assets/Prefabs" });
foreach (string guid in guids)
{
//Debug.Log(AssetDatabase.GUIDToAssetPath(guid));
string myObjectPath = AssetDatabase.GUIDToAssetPath(guid);
Object[] myObjs = AssetDatabase.LoadAllAssetsAtPath(myObjectPath);
//Debug.Log("printing myObs now...");
foreach (Object thisObject in myObjs)
{
//Debug.Log(thisObject.name);
//Debug.Log(thisObject.GetType().Name); 
string myType = thisObject.GetType().Name;
if (myType == "AudioSource")
{
Debug.Log("Audio Source Found in...  " + thisObject.name + " at " + myObjectPath);
}
}
}
// Find all object that have an Audio component in the current Scene
Debug.Log("Finding all Assets in the Current Scene that have an Audio Component...");
AudioSource[] myAudioSources = FindObjectsOfType(typeof(AudioSource)) as AudioSource[];
Debug.Log("Found " + myAudioSources.Length + " objects with an AudioSource attached");
foreach (AudioSource item in myAudioSources)
{
Debug.Log(item.gameObject.name);
}
}
}

I should finish off that this is an Editor only tool and will not impact the performance of your game or blow out your memory by loading in all those assets (if you got heaps). Also make sure you put this script in a folder called Editor otherwise you build will fail 🙂

Enjoy and happy coding… Xander.

Endless Elevator Weapons

The last few weeks I’ve been working up a couple of new weapons for the main protagonist in our upcoming game Endless Elevator.

This is a bit of a “Screen Shot Saturday” demo.

The first bad guy is dispatched with the standard issue firearm (with the big red bullets).

The next two are fried by the new Electricity Gun.

The last huddle are amiably frozen with the non-lethal Freeze Ray or Ice Gun.

I’m going to release a beta/demo for general testing and fun pretty soon so I’ll post again when it’s ready.

Unity Code Review and Style Guide

Hi Trixie here. It’s Code Review time! Yay. Time to clean up that code and start really looking at all the crap work you’ve done over the last months and clean that sh!t up!

I’ll post our in-house Style Guide (which of course goes out the window when you are coding in anger) at the bottom of the post. Hopefully someone else will follow it or find it useful.

This is how the code review works:

  • Make a list of all your scripts and the game objects they are attached to.
  • Go through all the variables/functions/iterators and make them follow the standard in the Style Guide (which includes making logical names sensible to humans).
  • Check all your Public references and make Private if you are really not using them.
  • Trawl the console output and clean up the extraneous debugging guff.
  • Start grouping your code into functions that do a similar thing and try and make them standardised.
  • If you are lucky you will find some optimisations in there as well to make your game run faster, better, more efficient, lighter.

Then at least it will be another few months before you muck it up again.

I know there are automated tools to do some of this work but I prefer to work though my own methods. I like to have complete control over the process. My favourite tool for analysis is the Unix Command line (I know weird right?). My workstation is Windows 10 but I have a Cygwin like utility called MobaXterm installed which allows me to interrogate the file system like a Unix machine and use grep and other commands on all the files that make up my scripts.

Basically I want to build a big spreadsheet of information that lists all my scripts and the Game Objects they attach to and the Public interfaces and variables etc.

This is one that I started for Endless Elevator:

Then I start extracting Game Objects and Code loops using the Unix Command Line so I can start to build a picture of what’s going on.

One of the outcomes of this process is that I want to be left with a map of how stuff works that I can connect the dots on in the future. It’s kind of like a Design Document in reverse.

Here are a few examples of the commands I’m using and how the information is extracted:

# grep bool *.cs # Getting out all my bools – I tend to use true or false tests a lot to explicitly define functions and events.

As you can see above the bool names are mostly descriptive and the variable name is mostly in camelCase. Those few like swingme will get updated to swingMe and the overly generic “yes” and “yesNow” (what was I thinking!) will be made more descriptive of what we are really agreeing to in that code.

This command looks at all the sort of functions I am using and where. It’s nice to know which one’s don’t have an Update() function and where all my Collisions and Triggers are.

# egrep “{|}” AI.cs # I use something like this to collect an idea of the complexity of my loops and functions.

# egrep “{|}(|)|if|for|else” AI.cs # This is my favourite type of command. Like the command above I use it for working out the structure of a script. This is way easier than scrolling through lines and lines of code and comment and makes it really easy to spot areas where you have gone loopy crazy pants.

# grep GameObject *.cs # This one makes a good list of all the scripts that I reference another Game Object in. It helps build a visual map of what dependencies there are between objects.

# grep script *.cs # This one does a similar thing in that it grabs all the times that I am calling something from within another script attached to another Game Object..

The command line is also a nice quick way to check how many lines of code you have written.

There are some good Style Guides out there.

I like this one for it’s organised folder structure and naming convention for files: https://github.com/stillwwater/UnityStyleGuide

I like the Microsoft C# one for layout and block style: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/inside-a-program/coding-conventions

I like this for it’s use of camelCase and Capitals and it’s preferred block style: https://github.com/raywenderlich/c-sharp-style-guide

As a basic guide though I always try and mimic the Unity API manual. For example this page for OnCollisionEnter:

// A grenade
// - instantiates an explosion Prefab when hitting a surface
// - then destroys itself
using UnityEngine;
using System.Collections;
public class ExampleClass : MonoBehaviour
{
public Transform explosionPrefab;
void OnCollisionEnter(Collision collision)
{
ContactPoint contact = collision.contacts[0];
Quaternion rotation = Quaternion.FromToRotation(Vector3.up, contact.normal);
Vector3 position = contact.point;
Instantiate(explosionPrefab, position, rotation);
Destroy(gameObject);
}
}

The comments are appropriate. The Class name uses a Capital for each word. The curly brackets are on a new line and indented block space. Varables are in camelCase.

Here is our minimal Style Guide. This is the basics of how we roll things:

Thanks for reading. See you all in the New Year… Trixie.

Unity: How to Create a Cut-Scene with Cinemachine and Unity Recorder

Hi Xander here,

For Endless Elevator we wanted to do an Introduction Scene for the game. The gameplay, as the name suggests, consists of climbing endless elevators and escalators. The player navigates floor after floor in the bad guys luxury hotel and tries to climb as high as possible while defeating the bad guys. It’s a 2.5D top down scroll mechanism and clipped into the limits of the building. Just dumping the player into the start of the game in the levels of the building felt a little weird as there was no context to where you were in the story. Hence the need for an opening shot to set the scene and to literally drop the player into the game.

Our hero flies into the enemies’ oasis headquarters in a helicopter and storms into the foyer of their luxury hotel. We mocked up a scene and a helicopter in Blender and imported the assets into the main foyer scene of the game. I hadn’t used Unity’s Cinemachine before but wanted to try it out. Previously, in other projects, we had captured gameplay for cut-scenes using external software and video editing suites which was OK but the experience with Cinemachine and Unity Recorder was way smoother. It was much easier to work with and much better quality avi files. Plus we didn’t have to do custom scripts for switching cameras and panning. It was so easy it kind of made me excited about movie making with Unity but you know I don’t need another distraction.

To start working with Cinemachine and Unity Recorder you can download them using the Package Manager. Unity Recorder has only recently been added (it’s still also on the Asset Store) so you need to enable the “Preview Packages” selection from the Advanced menu in the Package Manager.

Cinemachine in the Package Manager

Have a look at the Unity Manual and tutorials for more info about Cinemachine and Unity Recorder.

Below is a screen shot of my scene in Unity. You can see the main building in green and the surrounding buildings and water in the bad guys oasis HQ. The helicopter is just visible down where the camera sight lines join and on the left in the Hierarchy you can see my Timeline component and my two vcams (Virtual Cameras).

The Timeline is where all the magic happens and was very easy to set up.

First we did a few animations on the helicopter to fly it in to the building and make the rotor spin. Then we added an animation to move the character from the helicopter into the building (which looks terrible but remember this is a quick mock up)

The Helicopter Animation

We dragged this animation into a new Animation track on the Timeline object (right click and Add Animation Track). Then we created two Virtual Cameras in the scene. One Camera (vCam1) was set using the properties in the Inspector to automatically Loot At and Follow the helicopter. This means that where ever we flew the Helicopter the camera would follow it round from behind at a set distance and keep it in the frame automatically. This was really fun when we had it under manual control for testing and worked well when under the control of the Animator. We used a preset for a bit of camera jitter and shake to mimic a real camera man in a second helicopter.

The second Camera (vCam2) was stationary at the building site but set to Follow (ie. Look At) the Main Character. We timed the cut from one camera to the other so that once the helicopter landed it would pass control to the second camera and seamlessly start focusing on the Player. This was so easy it was ridiculous. The Camera objects were added to the Timeline and the split where we cut from one camera to the next is clearly visible in the screenshot below (two triangles). The first time I ran it and that view cut automatically from one vcam to the other gave me an enormous sense of satisfaction like I’d just been named a modern day Hitchcock.

The Timeline Editor Window

To record what we had done as an AVI we opened the Recorder Window:

Opening the Recorder Window.

We used the default settings and triggered the Start of the Recording with the start of the animation by having it in the Timeline. The Capture target was the Game View (you can also get the other elements of the Editor if you need it). The Output Resolution is interesting as you can use the Size of the Editor Game window on your screen or set it to standard default movie formats.

The Recorder Window

That’s about it. We hit Play in the Editor and the Timeline starts the Recording of the AVI and synchronises the Animations and the Camera movement automatically. Once we are done we are left with a good quality moving image of our game screen that we will use as the cut-scene to drop the player into the start of our game. Obviously what we got here is just a “screen test” but I was really happy with what we could achieve in just a few hours and with so little complexity.

Xander out…

Unity SVG Screen Test

I been toying with an idea for a new text adventure game. I wanted to finally start using the framework we built earlier in the year (See this post and check out the code for yourself). I also wanted to get into the new-ish support for SVG graphics in Unity. I love Vector Graphics and I think this project is going to be a great way to use their simple clean aesthetic.

I did a “screen test” of a very quick vector image using InkScape. (I have a deep respect for the Linux and Open Source community and want to thank them now for such awesome software).

To get started with SVG files in Unity 2018.x you need to import the package using the Package Manager (it’s called Vector Graphics). This allows you to pick up SVG files when you import a new asset. You can also drag and drop from your explorer into your project hierarchy.

The SVG image shown below is the three receding black squares. The SVG sprite is on a separate canvas to my text (and sorted behind it). I’ve added a couple of raw images in the same color as my background and an animator component to move them around to give the “drawing” effect.

I have to say I’m pretty happy with the resulting mock up and think this will be the way forward in developing this project. Resizing the screen view into different formats works as expected with the Vector image staying crisp (which is more than you can say for the poor four color buttons below it that use a scaled sprite – very fuzzy).

Zulu out.

Unity Audio vs Wwise

To start with I wanted to do a general investigation into Wwise the integrated audio package for Unity by AudioKinetic. When I started working through it I figured it would be more interesting to look at Wwise in comparison to Unity’s own audio API and mixer components which have been around since Unity 5.

To do that I’m going to compare a game in three different builds. Build one is it’s original state with simple scripts that run an AudioSource.Play() method. The Second build I will add another layer of complexity by using the Unity built in Mixer and see if there are any differences or advantages. Lastly I’ll redo the project with the Wwise API and investigate how that impacts build size and project complexity and weigh it up against the previous two builds. Mostly I’m looking for difference in performance between the three builds, build size and complexity, and weighing that up against ease of implementation and flexibility.

I refreshed an old project called “MusicVisualiser”that I started for my Five Games in Ten Weeks Challenge. The game is like a singing solar system. There is a bunch of “planets” in the night sky that play a set piece of music when clicked. It’s a really simple concept and project but I think it will work for this comparison as the parameters can be limited to just a few audio tracks but we can play with spacing and roll-off and other advanced audio features.

Let’s have a look at the game first.

These “planets” are simple native Unity sphere meshes with an Audio Source component and a particle system that’s triggered when it’s clicked. You can see in the Audio Source that we are not using a Mixer for Output and all the Audio sources compete for resources and play at their default volume and priority.

The PlayMe script just takes in the AudioSource and plays it:

   public AudioSource my_sound;
if (Input.GetMouseButtonDown(0))
{
RaycastHit hitInfo;
target = GetClickedObject(out hitInfo);
if (target != null && target.name == my_name)
{
_mouseState = true;
screenSpace = Camera.main.WorldToScreenPoint(target.transform.position);
offset = target.transform.position - Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, screenSpace.z));
my_sound.Play();   // This is the Audio Component!
var expl1 = GetComponent<ParticleSystem>();
expl1.Play();
}
}

Pretty simple right. This is what the project looks like in the Profiler when it’s running and being actively engaged with. At that point we are looking at two Audio Sources are playing:

This is the build size from the Editor Log with our Audio Files broken out:

Build Report (Audio.Play)
Uncompressed usage by category:
Textures 0.0 kb 0.0%
Meshes 0.0 kb 0.0%
Animations 0.0 kb 0.0%
Sounds 547.5 kb 1.4%
Shaders 188.0 kb 0.5%
Other Assets 1.4 kb 0.0%
Levels 38.3 kb 0.1%
Scripts 941.9 kb 2.4%
Included DLLs 3.9 mb 10.2%
File headers 9.3 kb 0.0%
Complete size 38.6 mb 100.0%

Used Assets and files from the Resources folder, sorted by uncompressed size:
204.3 kb 0.5% Assets/SomethingLurks_AAS.wav
164.5 kb 0.4% Assets/Step2Down_AAS.wav
136.9 kb 0.3% Assets/Underwater_AAS.wav
41.8 kb 0.1% Assets/M1_M12_37_ThumPiano_Aflat1.wav

Unity Audio with Mixer

Now we add in the Mixer component to the project:

Then add a couple of Channels to the Mixer to split the audio between left and right. Then the Audio Sources are dropped into one or another of the Mixer Channels:

Adding the Mixer as the Output source

Next for bit more interest I added some effects in the Mixer. Here is where we see the advantages of using the Unity Mixer. Sounds can be manipulated in complex ways and the Audio Output chain be defined with presets and levels etc.

If we have a look at our Profiler while running with the new component we cannot really see any great differences. The ‘Others’ section of the CPU Usage is a bit higher and the Garbage Collector in the Memory is pumping regularly but the Audio Stats look pretty much unchanged:

Profiler Mixer

Mind you this is a fairly low utilising game so we might get wildly different stats if we were really putting the system under the pump but I’m not performance testing here just comparing run states between the two builds.

Next if we build the game and have a look at the Editor Log the only thing that’s changed here is that the “Other Assets” size is a KB higher (Complete size has not been changed):

Build Report (Mixer)
Uncompressed usage by category:
Textures 0.0 kb 0.0%
Meshes 0.0 kb 0.0%
Animations 0.0 kb 0.0%
Sounds 547.5 kb 1.4%
Shaders 188.0 kb 0.5%
Other Assets 2.3 kb 0.0%
Levels 38.3 kb 0.1%
Scripts 941.9 kb 2.4%
Included DLLs 3.9 mb 10.2%
File headers 9.3 kb 0.0%
Complete size 38.6 mb 100.0%

Unity with Wwise

Next we are going to add Wwise to the Project. This is the basic workflow. In the Wwise Launcher we register our project and on the first tab we are presented with three Hierarchies.

Project Audio Explorer in Wwise

The Master-Mixer Hierarchy – does what it says.
The Actor-Mixor Hierarchy – where most of your game audio develops (use the SoundSFX defaults).
Interactive Music Hierarchy – other stuff we won’t get into.

Events Tab

The next tab along is the events tab where you link your audio to game events. You can define your event here (use the default work unit).
Once you got the event there you can associate the event with the audio in the Action List.

SoundBank Tab – this is the bit that get’s imported into your project.

Next you generate a SoundBank with Wwise that includes your audio and the code for the API calls to trigger sounds. You export that SoundBank into your game engine and link up the calls in your code.

To Get Started with Wwise

To get started make an account with Audiokinetic and download the Wwise Launcher. The Integration package for Unity can be downloaded and installed directly from the WWise Launcher.

In the Wwise Launcher there is a WWISE tab that you can install and start the application from. Once you open it up you need to register your project within the launcher so Wwise can track you 🙂 ( click on the key icon next to your Wwise project and select ‘Register your Project to obtain a License’). Wise will run in Trial mode which restricts the SoundBank content to 200 media assets and cannot be used for Commercial purposes. Pricing for licensing is on their site but this is not a sales piece so if you want it you can look it up.

There are a bunch of plugins by Audiokinetic and their partners available and also Community offerings like AudioRain a dedicated rain synth with 60 procedurally generated presets for rain. What’s not to love about that!

There is a Wwise SDK for authoring your own plugins and a Wwise API which allows you to integrate into any engine, tool or application.

Audiokinetic do certifications that covers audio integration workflows,
mixing virtual soundscapes, working with sound triggering systems, and performance optimisation :
https://www.audiokinetic.com/learn/certifications/

Basically in Wwise you let the Launcher do all the setting up for you. You will install the Wwise binaries from here and manage your platform versions. Projects can be integrated here and if you don’t have the necessary plugins installed the Wwise Launcher will install them for you.

Integrating the MusicVisualiser project with Wwise.
This is how big the Wwise Integration packages and binaries are.
Applying…
Done!

That’s basically it for the set up of Wwise and Integration with your Project. Next up we will have a look at what this has done to the Unity Console.

Wwise in Unity

First thing we see is a bunch of errors that can be safely ignored. As we did not perform any configuration of our project in Wwise with audio files and events there was no SoundBank to generate yet.

Unity – Initial Errors can be ignored if you have not generated your SoundBank yet.

In the Unity Console we have a new tab in our editor. The Wwise Picker Tab contains all the elements of the Wwise project that have been imported with the project integration. There is also a Wwise Global Game Object in the Unity Hierarchy and all the Wwise folders in the Assets folder.

Unity Editor
The WwiseGlobal Game Object

Under the Component pull down there is a whole slew of Ak (AudioKinetic) options.

Wwise Components.
Wwise Configuration Settings.

I know there has been a lot of “show and tell” in this post but I’m going to keep going and show the process of importing the audio into the Wwise Project, creating Events, and Generating the SoundBank.

Working in Wwise

In the Wwise Project Explorer I right click on the Default Work Unit and import the audio files that were part of my project. (I’ve stripped the raw files out of my project for now and removed all the Mixer components and etc.).

Importing Audio Files into the Wwise Project.
This is what the files look like.
Right click on the file to create a new Event (which can be called in the Unity code).
Here is the event created for “Play”.
And all my “Play” events.

Finally a SoundBank is generated from which the Unity project can access the sound files through the AudioKinetic API.

Generating a SoundBank

Wwise Audio in Unity

When we go back to our Unity Editor and Refresh the Project and Generate SoudBanks we are presented with the following in the Wwise Picker. We can now access these files and and drag them on to our game objects directly. It’s that simple. Drag a sound from the Picker onto a Game Object and it automagically creates a component that is immediately accessible from within the editor.

The new audio imported into the Wwise Picker.

Below the Play_Underwater_AAS event and audio file has been added to the Sphere Game Object.

The Trigger, Actions, and Callbacks can all be configured and access through the API. In my case I easily integrated the functionality I wanted with only one line change to my attached PlayMe.cs script that we looked at above. So now instead of my audio coming from the AudioSource component referenced by my_sound the audio is played by the AKSoundEngine.PostEvent.

            //my_sound.Play();
AkSoundEngine.PostEvent("Play_Underwater_AAS", this.gameObject);

Actually getting Wwise installed and set up and integrated with my Project was very very easy but not without bumps. It takes a very long time for packages to download and I had a bit of trouble upgrading my Wwise Launcher from an old version (it got stuck! and I had to remove it by hand and re-install). When I did have issues I got some very excellent help from AudioKinetic and after logging a case was emailed directly by a real person (which honestly was so surprising and wonderful to get that kind of support from a company when I’m on a trial license with no formal support agreement or rights).

So lets have a look at the differences in performance and package size. The first thing you notice with the Profiler below is that there is very little difference in performance but we can no longer see our audio stats as it’s been abstracted away from the Unity Engine. The Graph still shows the resources being used by Audio and the Total Audio CPU seems to be up to a third lower than the native Unity Audio statistics. It looks like it’s being clamped at just over 1.2. MB instead of regular peaks over 3 MB.

Profiler with Wwise Audio running.

The Build Report is only a couple of MB larger for the total project size:

Build Report
Uncompressed usage by category:
Textures 0.0 kb 0.0%
Meshes 0.0 kb 0.0%
Animations 0.0 kb 0.0%
Sounds 0.0 kb 0.0%
Shaders 188.0 kb 0.5%
Other Assets 7.3 kb 0.0%
Levels 38.5 kb 0.1%
Scripts 1.3 mb 3.1%
Included DLLs 3.9 mb 9.7%
File headers 13.2 kb 0.0%
Complete size 40.5 mb 100.0%

Basically a 2 MB difference! The Sounds have been extracted away as a file in the Build Report and we assume they are now part of “Other Assets” above.

I’m kinda blown away by how how little additional file size there is to the build considering the additional libraries code and available complexity that Wwise adds. There is literally a plethora of options and effects that we can play with in the Wwise package. It’s a bit like the excitement I got after the install of my first real Audio DAW. The scope is part boggling and part fantastical wonder at where we can go next. (Audio does get me unusually stimulated but that’s to be expected and tempered accordingly).

The questions I wanted to answer with this whole experiment was 1. Would including an audio middleware like Wwise make my Project more complex and difficult to manage? 2. Would the added Package make my build much larger? and 3. Would the performance of the Audio package be as good as the simple Unity Audio API? The answers are: No. No, and Yes. So I’m pretty happy with that and if the cost point of using the licensed version of Wwise is balanced out against the advantages of using it in the total cost of the Project then I would most definitely one hundred percent go for it.

Preparing to Draw

Hi Gene here…

This post is about preparing for a good drawing. Drawing is the fundamental skill for all (or most) forms of art. I’d like to be better at it but I really only just get by…

But still you have to prepare to do your best otherwise you are setting yourself up to fail. So this is the process or workflow that I find is the best for me. I’ll take you through the process and use a rough example drawing as we go.

A lot of these ideas come from reading Andrew Loomis and Walt Stanchfield. (I cannot recommend Gesture Drawing for Animation enough for reading about drawing rather than actually doing drawing.)

I often think of this quote (well paraphrase…) from Andrew Loomis when I start out to draw something:

“You must have a desire to give an excellent personal demonstration of ‘Ability’ coupled with a capacity for unlimited effort that hurdles the difficulties that would frustrate lukewarm enthusiasm.”

The Idea

To begin with every drawing starts out with a message or purpose or job to do. The Idea or Emotion of the Drawing. First and foremost you have to draw an idea. Every object that you put in your drawing is an elaboration of that idea.
Your idea has to be an action (or verb – a “doing” word) but the vehicles of that action are the things/objects in your drawing. Those things can be a figure, ten figures, a dog, a house, a tree, a swirling galaxy, or whatever.
If it’s a figure then the pose, the anatomical structure, etc. have to portray that idea. In every drawing you have to find that emotion of the idea. It’s a bit of a nebulous concept but I don’t have any other way to describe it.

For example in figure drawing the essence of the idea is all the outward manifestations of that internal emotion. Every moving part and direction portray the motive and mood of the drawing. Your character has to be responding characteristically to some real or imaginary motivation.
To quote Stanchfield:
“These are basic human emotions such as joy, sorrow, anger, tenderness, submission, domination, fear, surprise, distress, disgust, contempt, and shame.”

The second part to this idea is the story. What happens next. There’s no need for a whole story to be crammed into one drawing all you need is you figure doing something or reacting to something in a “characteristic” way for who they are supposed to be.

Preparation to Draw

As I said before you have to prepare to make a good drawing.
It usually doesn’t just happen if you just start drawing.

This is the best process I’ve found that does just that.

It starts with Mental Preparation or Rough Sketching.
You have to answer these sorts of questions about what you want to draw:
What is the idea?
What is your pose?
Is it the extreme of the action?
Is there an action and a re-action?
What is the visual depth?
Is there a primary and secondary action?
What is the “stage” for the action?
What is the anticipation? (What is just about to happen?)
Will you use caricature?
What details will you include?
What objects will you use?
Do the objects have a texture?

Once you have worked your way through those questions try starting your first drawing.

This one simplifies your idea and starts nutting out the technical execution.

This is the first sketch for my example drawing.

What is the idea? Piano Player immersed in playing. The idea is total absorbtion in the music.

What is your pose? Sitting one leg up tapping – hands flying. I want every action to be reinforcing that one-ness with the music.

Is it the extreme of the action? Not in the first sketch I did – that right hand could be up higher with the fingers poise like an eagle about to strike. He is supposed to be immersed in the action so his head could be down further or looking at the sky. The left leg is supposed to be horizontal and then on tip toes. Maybe it should poke out more to the left so you can see that outline instead of being hidden in the foreshortening.

Is there an action and a re-action? Not really – that right arm really needs to look like its at the top of it’s upward trajectory and is about to slam down. The shoulder could be either more hunched or raised up. The other shoulder needs to be stretched out like he’s really reaching for the low note. The tapping foot needs to be up as well and just about to come down. The left foot needs to be jittering about and only just holding his balance on that stool.

What is the visual depth? In the sketch it’s quite shallow. No background and a very close middle ground.

Is there a primary and secondary action? The primary is that hand. The secondary is the repeat of that in the foot and the hunching or lifting of the body.

What is the “stage” for the action? Is this a bar in a western or a jazz club or a luxury penthouse or a garrette? I think I’ll go for a down and out garrette. A total slum of a place that he is escaping with the music. I think I will change the format from landscape to portrait to hem him in and make room for a window.

What is the anticipation? (What is just about to happen?) His right hand is just about to crash down and peal out the most amazing lick while the left hand pumps the bass notes. The jittering and stomping foot are like the rhythm section.

Will you use caricature? I don’t think so.

What details will you include? What objects will you use?
Whisky glass! Shadows! Mood lighting. Other people? I don’t think so it’s all about him. Cigarette ash. Old stool and table lamp. Add a broken window and sliced up blinds behind him with a crappy part of the city and the moon overhead.

Do the objects have a texture? Woody piano, dirty floor,

This is where I got the inspiration the second mannequin looked like he was playing the keyboard.

First Drawing

Aim for Simplification.
Shapes and composition.
What are the most basic shapes (try and limit it down to three or max six) use the square, the rectangle, the circle or ellipse, and triangles.
Define the Scale and point of view.
(Which perspective are you using? How many vanishing points?)
Is there a Direction (or Flow)? (Beat or Rhythm.)
Is there Tension? Is it Extreme? (Use extreme poses and balance action and reaction to create tension.)
Where is the overlap? Which objects are in front or behind?
What are the positive and negative shapes?
What is the extreme pose? This usually means the farthermost extension of some pose just prior to a change of direction.
Your drawing should show, in a flash, what is happening in the pose.
Those extremes are vital to explaining the idea.
I’ll paraphrase Stanchfield again:
If the extreme pose is missing or diluted, the drawing will deteriorate from expressive to bland or confusing or boring.
The Silhouette almost explains “Extreme,” if it is not thought of as a tracing of the outside of the figure.
The extreme pose is generated by the forces at play in a gesture (the force and thrust and tension).

This is where I start playing with the basic shapes and setting up the perspective
(I use Carapace for perspective guides)
In this version I try and tighten up the figure a bit more

Second Drawing

The Second Drawing is about mass and the solid and flexible parts of the subject. It’s also about expressing the tension of the idea:
Model the figure/character/object roughly.
Give it weight and mass. (depth and volume.)
Use planes to provide solidity.
What is the weight distribution? (If it’s a figure – how it balances itself due to what it is doing.)
Thrust and Body Language. (It usually requires a limb to be thrust out – a hip thrust, or shoulder shrugged up, or knees apart, or arms out.)
Tension and Counterpart. (Whenever one member of the body moves set up a counter move with its counterpart.
Tension is captured when one elbow is working against the other or one knee against the other.
Feet, hands, hips and shoulders should always be in counter position.
Never draw one part of the body without drawing the counter move of its opposite at the same time …. never.
Use the solid and flexible parts of the body as the basis for the angles that portray the action.

Blocking in the main shapes in the body.
Starting to look OK

Third Drawing

Sometimes I’ll do a third drawing (or incorporate it into the second). This one concentrates on the line:
Define the line and silhouette.
Use arcs to define movement (and follow through).
Split it up by straights and curves.
Straights and curves when used logically can emphasise and clarify the gesture.
Straights and curves can be used for “squash” and “stretch”.
Further define the direction of the drawing – make all the elements come together to define the idea.

Concentrating on the lines

Fourth Drawing

This one is all about Perspective and Anatomy. Use it. Tighten it. Get your straights and curves to follow it.
Use Reference images and get it right.
Draw the bones first (in perspective) or a rough skeleton. Get the perspective right now. Then do surface form.
Model the muscles or flesh.
Focus down on parts.
Also textures – what parts have what texture or shading etc.

These are some references I used
Grey scale painting
Starting to Colour

Fifth Drawing

Draw everything again!
But this time picking the best bits of all drawings.
Concentrate on line quality.
Concentrate on tone.
Concentrate on light.

Drawing

Finally a few notes about Drawing from Life.
Everywhere you go take a sketchbook.

When you draw try to first concentrate on color.
Then switch to dark and light (tone or texture),
then to masses,
then to the three-dimensional qualities of things near and far.
Now, try to see all of those things at once.

Finally an inspirational quote from Stanchfield:
“Carry a sketch book—a cheap one so you won’t worry about wasting a page. Sketch in the underground, while watching television, in pubs, at horse shows. Sports events are especially fun to sketch— boxing matches, football games, etc. Draw constantly. Interest in life will grow. Ability to solve drawing problems will be sharpened. Creative juices will surge. Healing fluids will flow throughout your body. An eagerness for life and
experience and growth will crowd out all feelings of ennui and disinterest.
Where are you going to get all this energy, you ask? Realize that the human body is like a dynamo, it is an energy producing machine. The more you use up its energy, the more it produces. A work-related pastime like sketching is a positive activity. Inactivity, especially in your chosen field, is a negative. Negativity is heavy, cumbersome, debilitating, unproductive and totally to be avoided. Take a positive step today. Buy a sketch book and a pen (more permanent than pencil), make a little rectangle on the page and fill it with a simple composition.”

It starts with Mental Preparation or Rough Sketching.

Carapace

This is Carapace a tool designed by Epic Games and made available for free. I find it very useful. The link on their website is broken but if you search for it it’s still around. One source you can get it is here: https://www.florianhaeckh.com/blog/carapace

Unity 2D Curves using Triangles

Hi Xander here….

I know I shouldn’t be spending time doing this sort of stuff when I got games to make but I got really sidetracked with this little brain boiler. I got the idea while doing some maths research and came across an image of a cat’s cradle spun in a triangle. The way the lines joined made a perfect curve and I really liked the idea of doing something like that for making custom curves in games. I know the idea is probably not original and there has got to be some better implementations out there but once my noodle started working on this I got a little obsessed with seeing it through to the end.

I have written before about making curved movement by using sin functions and still think that’s a pretty cool way to do it. You can read about it here: (Fly Birdy Fly! 2D Curved Movement in Unity). But this is a much more intuitive way to get the perfect curve you want and very easy to plot and track the path of movement without having to guess.

This is how it works…

You take the three points of a triangle. I was thinking of something like a cannon shot, or lobbed object, or a flying arrow to start with so I called them Source, Height and Target. You measure the distance between those points and make lines to form a triangle. Then you cut those lines into equal points and start joining one point on one line to another point on the other line all the way down the length. It’s easier to explain in an image:

Building a Triangle and “Cat’s Cradle” lines to make a curve!

Now for the Mathy part… Once you get those lines drawn you use algebra to find the intersection point of one line and the next to get your curved path! Every additional line crosses the one before and by finding that point where they cross you get a list of points that make a curve.

Simple curves only need a few lines.

Five Lines

The more lines you use the smoother your line is…

Ten Lines
Twenty Lines

Start moving around those points of the triangle and it becomes really easy in the Unity Editor to Map and draw custom curves. This kind of blew my mind.

Different Types of Curves

Here we have a number of different curves all just by making a few tweaks to the position of those three points of the triangle. I’ve used the intersecting points to draw a parabolic line on the game scene below.

Here are few of the same images zoomed in (in case you are reading on your phone).

The Code

I’ll put the full script at the bottom of the post but for now I’ll work through the code a little bit.

If you want to copy the script you need to attach it to a GameObject that you want to move (or if you want to draw lines you need to attach it to a GameObject with a Line Renderer).

The script has a number of check boxes exposed in the editor which lets you control the movement and drawing functions as well as resetting and applying changes after moving the triangle’s points.

The only other variable that you can play with is the timeToHit float. This number controls how many lines you want to use to create the curve. Remember: The more lines the smoother the movement but the higher processing. (That said I’ve yet to do any serious profiling of the script but haven’t found any real performance hits yet).

Much of everything else is public so you can see what’s going on inside all the Lists and Arrays.

… … … (Editor View)

Defining the Triangle

First of all we get the positions of the three triangle points and find the length (Magnitude) of the lines between them using normal Vector maths.

Then we divide those lines by the number of strings we want to have (timeToHit) and work out the relative size of each one:

        Vector3 X_line = source - target;  
X_line_length = Vector3.Magnitude(X_line);
Vector3 Y_line = height - source;
Y_line_length = Vector3.Magnitude(Y_line);
Vector3 Y_Negline = target - height;
Y_Negline_length = Vector3.Magnitude(Y_Negline);
X_line_bit_x = (height.x - source.x ) / timeToHit;
X_line_bit_y = (height.y - source.y) / timeToHit;
Negline_bit_x = (target.x - height.x) / timeToHit;
Negline_bit_y = (height.y - target.y) / timeToHit;

Get the Points Along Each Line

Next we iterate through all the points on the lines and make a pair of Lists (one for the forward or positively sloping line and one for the negatively sloped line):

        for (int i = 0; i < timeToHit + 1; i++)
{
P_lines.Add(new Vector3(Px, Py, 0f));
Px += X_line_bit_x;
Py += X_line_bit_y;
Q_lines.Add(new Vector3(Qx, Qy, 0f));
Qx += Negline_bit_x;
Qy -= Negline_bit_y;
}

Get Intersection Points

Getting the intersection points was much easier to do in 2D but is totally achievable if you wanted to extend it to 3D. We pass in our start and end points on each line (x and y coordinates) and return the intersection point (and convert it back to a Vector3):

            myPoint = findIntersectionPoints(
(new Vector2(P_lines[i].x, P_lines[i].y)), 
(new Vector2(Q_lines[i].x, Q_lines[i].y)),
(new Vector2(P_lines[bc].x, P_lines[bc].y)), 
(new Vector2 (Q_lines[bc].x, Q_lines[bc].y)));
Vector3 myPoint_3 = new Vector3(myPoint.x, myPoint.y, 0f);
IntersectionPoints.Add(myPoint_3);

(If you want to do more than idly read about this stuff have a look at Math Open Ref for more information on the functions for finding the intersection of two lines. I promise it’s actually really interesting.)

The maths bit:

float P1 =(Line2Point2.x - Line2Point1.x) * (Line1Point2.y - Line1Point1.y)
- (Line2Point2.y - Line2Point1.y) * (Line1Point2.x - Line1Point1.x);
float P2 = ((Line1Point1.x - Line2Point1.x) * (Line1Point2.y -Line1Point1.y)
- (Line1Point1.y - Line2Point1.y) * (Line1Point2.x - Line1Point1.x)) / P1;
return new Vector2(
Line2Point1.x + (Line2Point2.x - Line2Point1.x) * P2,
Line2Point1.y + (Line2Point2.y - Line2Point1.y) * P2);

That’s about it for the tricky stuff. There is a function to draw a line along the curved path and a function to move the attached object along the path as well. Add in a few Gui functions for displaying the pretty stuff in the scene view and you are done.

Moving the Green Sphere

This is an example of the script running in the editor that shows the scene view with the OnGui helper lines and then switches to the game view where I use the function to draw a curve and then move the green sphere along that path.

Full Script:

Here is the full script…enjoy!

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CurveFunction : MonoBehaviour {
public bool resetMe;    // Use these to manage the screen display
public bool updateMe;
public bool drawMe;
public bool moveMe;
public GameObject Source;  // The three points of the triangle
public GameObject Target;
public GameObject Height;
public Vector3 source;  // The three points of the triangle
public Vector3 target;
public Vector3 height;
public float timeToHit;     // A variable use to split the lines of the triangle into equal parts
public int targetreached = 0;
public float X_line_length;     // The length of the horizontal line between source and target
public float Y_line_length;     // length from source to height
public float Y_Negline_length;  // length from height to target (Negative slope of the triangle)
public float X_line_bit_x;      // the x and (below) y points of the X_Line.  
public float X_line_bit_y;
public float Negline_bit_x;     // the x and (below) y points of the Negline.
public float Negline_bit_y;
public float[] X_line_bit_xs;
public float[] X_line_bit_ys;
public float[] Negline_bit_ys;
public List<Vector3> P_lines = new List<Vector3>();     // A List of points on the Y_Line 
public List<Vector3> Q_lines = new List<Vector3>();     // Same for the Negline
public List<Vector3> IntersectionPoints = new List<Vector3>();  // Where two lines cross
public float Px;        // Used as shorthand for points on the lines when calculating
public float Py;
public float Qx;
public float Qy;
public bool isFound;
public float speed;         // Used for Draw function
public LineRenderer lineRend;
public int bc;
// Use this for initialization
void Start () {
source = Source.transform.position;
height = Height.transform.position;
target = Target.transform.position;
getPointsOnTriangle();
Px = source.x;
Py = source.y;
Qx = height.x;
Qy = height.y;
makeLineArrays();
}
// Update is called once per frame
void Update () {
if (updateMe)
{
getPointsOnTriangle();
makeLineArrays();
updateMe = false;
}
if (moveMe)
{
MoveMe();
}
if (drawMe)
{
drawLines();
}
if (resetMe)
{
ResetMe();    
}
}
void getPointsOnTriangle ()
{
source = Source.transform.position;
height = Height.transform.position;
target = Target.transform.position;
// Define the lines of the triangle and get their lengths
Vector3 X_line = source - target; 
X_line_length = Vector3.Magnitude(X_line);
Vector3 Y_line = height - source;
Y_line_length = Vector3.Magnitude(Y_line);
Vector3 Y_Negline = target - height;
Y_Negline_length = Vector3.Magnitude(Y_Negline);
// Time to hit is not really a time but an increment of how many times we want to cut the line into 
// chunks to make the lines from. The more lines the better the curve points but more processing.
X_line_bit_x = (height.x - source.x ) / timeToHit;
X_line_bit_y = (height.y - source.y) / timeToHit;
Negline_bit_x = (target.x - height.x) / timeToHit;
Negline_bit_y = (height.y - target.y) / timeToHit;
// Handy handlers of the x and y values of the source and height.
Px = source.x;
Py = source.y;
Qx = height.x;
Qy = height.y;
}
void makeLineArrays()
{
for (int i = 0; i < timeToHit + 1; i++)
{
P_lines.Add(new Vector3(Px, Py, 0f));
Px += X_line_bit_x;
Py += X_line_bit_y;
Q_lines.Add(new Vector3(Qx, Qy, 0f));
Qx += Negline_bit_x;
Qy -= Negline_bit_y;
}
makeIntersectionPoints();
}
public void makeIntersectionPoints()
{
bc = 0;
Vector2 myPoint = Vector2.zero;   // It's a bit easier to do 2D. So convert.
for (int i = 0; i < timeToHit; i++)
{
if (bc < timeToHit)
{
bc++;
}
myPoint = findIntersectionPoints(
(new Vector2(P_lines[i].x, P_lines[i].y)), 
(new Vector2(Q_lines[i].x, Q_lines[i].y)),
(new Vector2(P_lines[bc].x, P_lines[bc].y)), 
(new Vector2 (Q_lines[bc].x, Q_lines[bc].y)));
Vector3 myPoint_3 = new Vector3(myPoint.x, myPoint.y, 0f);
IntersectionPoints.Add(myPoint_3);
}
IntersectionPoints.Add(target);
}
public Vector2 findIntersectionPoints(Vector2 Line1Point1, Vector2 Line1Point2, Vector2 Line2Point1, Vector2 Line2Point2)
{
float P1 = (Line2Point2.x - Line2Point1.x) * (Line1Point2.y - Line1Point1.y)
- (Line2Point2.y - Line2Point1.y) * (Line1Point2.x - Line1Point1.x);
float P2 = ((Line1Point1.x - Line2Point1.x) * (Line1Point2.y - Line1Point1.y)
- (Line1Point1.y - Line2Point1.y) * (Line1Point2.x - Line1Point1.x)) / P1;
return new Vector2(
Line2Point1.x + (Line2Point2.x - Line2Point1.x) * P2,
Line2Point1.y + (Line2Point2.y - Line2Point1.y) * P2
);
/// Code modified from: https://blog.dakwamine.fr/?p=1943  
/// (Thanks for the leg up!)
}
public void drawLines()
{
lineRend.positionCount = 0;
Vector3[] positions = new Vector3[Mathf.RoundToInt(timeToHit) + 1];
for (int i = 0; i < timeToHit + 1; i++)
{
positions[i] = IntersectionPoints[i];  // Draws the path
}
lineRend.positionCount = positions.Length;
lineRend.SetPositions(positions);
drawMe = false;
}
public void MoveMe()
{
if (transform.position != IntersectionPoints[targetreached])
{
float step = speed * Time.deltaTime;
transform.position = Vector3.MoveTowards(transform.position, IntersectionPoints[targetreached], step);
}
else
{
if (targetreached != IntersectionPoints.Count)
{
targetreached++;
}
}
if (transform.position == Target.transform.position)
{
moveMe = false;
}
}
public void ResetMe()
{
transform.position = source;
targetreached = 0;
X_line_length = 0;
Y_line_length = 0;
Y_Negline_length = 0;
X_line_bit_x = 0;
X_line_bit_y = 0;
Negline_bit_x = 0;
Negline_bit_y = 0;
X_line_bit_xs.Initialize();
X_line_bit_ys.Initialize();
Negline_bit_ys.Initialize();
P_lines.Clear();
Q_lines.Clear();
IntersectionPoints.Clear();
Px = 0;
Py = 0;
Qx = 0;
Qy = 0;
moveMe = false;
resetMe = false;
}
void OnGUI()
{
GUI.Label(new Rect(10, 10, 140, 20), "Source: " + source);
GUI.Label(new Rect(10, 30, 140, 20), "Target: " + target);
GUI.Label(new Rect(10, 50, 140, 20), "Height: " + height);
}
void OnDrawGizmos()
{
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(source, 0.2f);
Gizmos.DrawWireSphere(target, 0.2f);
Gizmos.DrawWireSphere(height, 0.2f);
Gizmos.color = Color.green;
Gizmos.DrawLine(source, target);
Gizmos.DrawLine(source, height);
Gizmos.DrawLine(height, target);
UnityEditor.Handles.Label(source, "SOURCE");
UnityEditor.Handles.Label(target, "TARGET");
UnityEditor.Handles.Label(height, "HEIGHT");
Gizmos.color = Color.yellow;
// Uncomment to see lines in editor
for (int i = 0; i < timeToHit + 1; i++)
{
Gizmos.DrawLine(P_lines[i], Q_lines[i]);
}
}
}

Xander out.