Link to game page:
http://goo.gl/8P6jsB
The Wall is a stealth game based on the political climate of Cold War Germany and its effect on the citizens of Berlin. The game’s four levels reflect the structural evolution of the Berlin Wall and its “Death Strip”, with the addition of fortifications and improvements from a simple wire fence to the intimidating concrete structure that was razed in 1989. Players attempt to move as many East German defectors across the wall as possible without being detected by the guards, using historically accurate methods that were used to cross to the West. These methods include running, jumping from windows, ziplining, swimming, ballooning and tunneling.
I worked as the programmer on a team of four to create this project using C# and Unity 3D. We decided early on that we needed a system so our designer could work on gameplay while I developed features. As a result I created the Vision Cone creation system that uses mesh generation and lots of custom options for tweaking. Our designer Julian said that it helped a lot, so I thought I’d show you what it looked like:
It gives you control over the rotation path of the vision cone, its position, speed, wait times, etc. The elements of the ‘angle values’ are pairs corresponding to target angle and direction- C for clockwise and A for anticlockwise. ‘Pivot offset’ moves the rotation pivot from the center of the sprite.
I was able to make this tool easy to use with a bit of editor scripting. I wanted the ‘angle values’ array to automatically populate when the Size variable is adjusted. Here’s the code that made it work:
[CustomEditor(typeof(CreateVisionCone))]
public class VisionConeEditor : Editor {
public override void OnInspectorGUI() {
CreateVisionCone myTarget = (CreateVisionCone)target;
myTarget.speed = EditorGUILayout.FloatField(“Speed”, myTarget.speed);
myTarget.coneSize = EditorGUILayout.FloatField(“Cone Size”, myTarget.coneSize);
myTarget.length = EditorGUILayout.FloatField(“Length”, myTarget.length);
myTarget.waitTime = EditorGUILayout.FloatField(“Wait Time”, myTarget.waitTime);
myTarget.initialDelay = EditorGUILayout.FloatField(“Initial Delay”, myTarget.initialDelay);
myTarget.startAngle = EditorGUILayout.FloatField(“Start Angle”, myTarget.startAngle);
//for displaying the angle array
serializedObject.Update();
EditorGUIUtility.LookLikeInspector();
SerializedProperty tps = serializedObject.FindProperty (“angleValues”);
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(tps, true);
if(EditorGUI.EndChangeCheck())
serializedObject.ApplyModifiedProperties();
EditorGUIUtility.LookLikeControls();
GUILayout.Label (“eg format 45-C : where 45 is the target angle, direction clockwise”);
}
}
The vision cones in the game are all meshes created and textured at runtime. I did this to have precise control over the size and shape of each cone. This is what a vision cone gameobject looks like at runtime. As you can see it uses a ‘ScriptedMesh’ instance:
This is the code that generates it:
Mesh CreateMesh(float width, float height)
{
Mesh m = new Mesh();
m.name = “ScriptedMesh”;
m.vertices = new Vector3[] {
new Vector3(-width, 0, 0.01f),
new Vector3(width, – height, 0.01f),
new Vector3(width, height, 0.01f),
};
m.uv = new Vector2[] {
new Vector2 (0, 1),
new Vector2(1, 1),
new Vector2 (1, 0),
};
m.triangles = new int[] { 0, 1, 2 , 0, 1, 2};
m.RecalculateNormals();
return m;
}
private void DrawConeArea()
{
GameObject visionCone = new GameObject(“VisionCone”);
MeshFilter meshFilter = (MeshFilter)visionCone.AddComponent(typeof(MeshFilter));
meshFilter.mesh = CreateMesh(length/2, coneSize/4);
MeshRenderer renderer = visionCone.AddComponent(typeof(MeshRenderer)) as MeshRenderer;
renderer.material.shader = Shader.Find (“Particles/Additive”);
renderer.sortingLayerName = “Player”;
renderer.sortingOrder = 2;
Vector3[] vertices = meshFilter.mesh.vertices;
Vector2[] uvs = new Vector2[vertices.Length];
for (int i = 0 ; i < uvs.Length; i++)
uvs[i] = new Vector2 (vertices[i].x, vertices[i].y);
meshFilter.mesh.uv = uvs;
renderer.material = (Material)Resources.Load(“YellowTex”, typeof(Material));
visionCone.transform.position = transform.position;
//pivot is an empty gameobject for rotation- parented to soldier
if(!StandingGuard) //pivot is at the center of the sprite
{
visionCone.transform.position += new Vector3 ((visionCone.GetComponent<MeshRenderer> ().bounds.size.x)/2, 0, -0.5f);
pivot.transform.position = transform.position;
}
else //pivot is near the top of the sprite
{
pivot.transform.position = transform.position;
float offset = (visionCone.GetComponent<MeshRenderer> ().bounds.size.y)/2 * transform.localScale.y * 0.85f;
pivot.transform.position += new Vector3(0, offset, 0);
visionCone.transform.position += new Vector3
((visionCone.GetComponent<MeshRenderer> ().bounds.size.x)/2,
(visionCone.GetComponent<MeshRenderer> ().bounds.size.y)/2 *
transform.localScale.y * 0.85f, -0.5f);
}
visionCone.AddComponent(typeof(MeshCollider));
visionCone.GetComponent<MeshCollider>().isTrigger = true;
visionCone.AddComponent(typeof(VisionCone));
visionCone.transform.parent = pivot.transform;
pivot.transform.rotation = Quaternion.Euler (0, 0, angleShift);
}
Summary:
This tool helped our designer play around with the core mechanic and come up with some fun, satisfying levels. We’ll be following a similar approach towards development in the future. I hope you enjoyed reading about this project!