In part 2 of our series on Bubble Byte’s QR Invaders app, Jon Cebula talks about how he used Unity to read and process QR codes into a compelling and dynamic experience for gamers. Check out part 1 here if you missed it!
Obtaining the QR Code
The first unique challenge for QR Invaders was to actually scan a QR code. I wanted the scanning screen to be slick and “game-like” in its own sense. Unity provides WebCamTexture and GUITexture classes that make using the device’s camera very simple.
I wanted 3D graphics to appear over the camera preview, but the GUITexture is drawn after all 3D geometry, so I had to rethink how to present the preview.
In the end I used a simple 3D plane and overlaid the camera preview into its texture. This is fairly easy to do using the MeshFilter and MeshRenderer objects. The code provided below shows how these are added to a MonoBehaviour, which is Unity’s base object for anything that is used within its scene environment.
void Awake() { gameObject.AddComponent("MeshFilter"); gameObject.AddComponent("MeshRenderer"); meshFilter = (MeshFilter)GetComponent(typeof(MeshFilter)); meshRenderer = (MeshRenderer)GetComponent(typeof(MeshRenderer)); meshRenderer.renderer.material = material; mesh = meshFilter.mesh; }
This code creates a MeshFilter, which contains all the 3D objects mesh data ─ basically the things that describe the shape of the object in 3D space ─ and a MeshRenderer, which contains the information about how the object looks such as surface color and textures. We then store and reference the Mesh object from the MeshFilter. So now we just need to create the actual plane and set up the Texture:
this.CameraPoints = new Vector3[] { camera.ScreenToWorldPoint( new Vector3( meshX, meshY, camera.farClipPlane * 0.99f ) ), camera.ScreenToWorldPoint( new Vector3( meshX + meshWidth ,meshY, camera.farClipPlane * 0.99f ) ), camera.ScreenToWorldPoint( new Vector3( meshX + meshWidth, meshY + meshHeight, camera.farClipPlane * 0.99f ) ), camera.ScreenToWorldPoint( new Vector3( meshX, meshY + meshHeight, camera.farClipPlane * 0.99f ) ) }; mesh.vertices = this.CameraPoints; mesh.uv = new Vector2[] { new Vector2 (0, 0), new Vector2 (1, 0), new Vector2(1, 1), new Vector2 (0, 1) }; mesh.triangles = new int []{0, 2, 1, 0, 3, 2}; mesh.normals = new Vector3[] { new Vector3(0f, 0f, 1f), new Vector3(0f, 0f, 1f), new Vector3(0f, 0f, 1f), new Vector3(0f, 0f, 1f) }; material.mainTexture = this.WebCamManager.CamTexture;
Here we create an array of Vector3 objects and assign them to this.CameraPoints. This is then set to the vertices array of the mesh, which is all of the points in 3D space that define the plane (think of it like a dot to dot picture). Next, we set the triangles array with indices to each of the points defining triangles ─ this is like joining the dots. Although you can use quads in Unity, it’s best to use triangles, allowing you to define exactly when the separating line will appear, as quads are converted to triangles anyway.
Using this technique I can now create GameObjects and place them in front of the plane to make them appear in front of the camera preview.
I then used some shading effects to alter the camera preview in real-time, creating a tint so the user gets a more compelling visual response when the QR code is scanned. The overlaid text here is actually made up from multiple meshes, helping to reduce the performance hit while we’re in camera preview.
Parsing the QR Code
Once the camera was working, it was a case of capturing the data and parsing it into the Space Invaders. I used the dot patterns on the QR codes to determine the enemy types that would be created.
- The 3 big squares you see on each QR code became the bosses for Levels 2, 3 and 4.
- The smaller block that appears on most QR codes became the Level 1 Boss.
On bigger QR codes the smaller blocks appear more frequently throughout so I parsed each to make extra mini-bosses for Level 2, 3 and 4.
Groups of four white or black tiles in a square became Quad Invaders who help the Bbosses at the end of each stage. Finally, each single dot became either a black or a white Invader.
In the final blog post, I’ll discuss some of the level optimizations I used to ensure a consistent 60 FPS experience for the player. Thanks for reading!
Jonathon Cebula
Bubble Byte