In the last post of this series, we learned how to set up Vuforia and start developing an AR game from scratch, adopting a logic similar to the one used on Pokémon GO!
Create a Pokémon GO Style Augmented Reality Game With Vuforia
Pokémon GO Style Augmented Reality With Vuforia
Pokémon GO Style Augmented Reality: Markers
We've started development of an Augmented Reality game called Shoot the Cubes. Now it's time to improve the game by adding interaction and making the experience more engaging. We'll concentrate mostly on the possibilities Unity gives us, leaving aside Vuforia's specifics. Experience with the Unity engine is not mandatory.
1. Making the Cubes Look Alive
Let's start working on our game. So far we've managed to create an Augmented Reality scene that moves with the user's device. We'll improve this app by making the cubes spawn and fly around, and by letting the player search and destroy them with a laser shot.
1.1. Spawning the Elements
We've already set an initial position of the _SpawnController according to the device camera rotation. Now we'll establish an area around this point where our cubes will spawn. Now we'll update the SpawnScript
to make the _SpawnController instantiate the cube elements with different sizes and random positions, relative to the _SpawnController.
Let's edit the SpawnScript
class, adding some variables to control the spawning process.
public class SpawnScript : MonoBehaviour { // Cube element to spawn public GameObject mCubeObj; // Qtd of Cubes to be Spawned public int mTotalCubes = 10; // Time to spawn the Cubes public float mTimeToSpawn = 1f; // hold all cubes on stage private GameObject[] mCubes; // define if position was set private bool mPositionSet; }
We'll create a coroutine called SpawnLoop
to manage the spawn process. It will also be responsible to set the initial position of the _SpawnController when the game begins. Notice that the Random.insideUnitSphere
method causes the cubes to be instantiated at random locations within a spherical area around the _SpawnManager.
public class SpawnScript : MonoBehaviour { // Loop Spawning cube elements private IEnumerator SpawnLoop() { // Defining the Spawning Position StartCoroutine( ChangePosition() ); yield return new WaitForSeconds(0.2f); // Spawning the elements int i = 0; while ( i <= (mTotalCubes-1) ) { mCubes[i] = SpawnElement(); i++; yield return new WaitForSeconds(Random.Range(mTimeToSpawn, mTimeToSpawn*3)); } } // Spawn a cube private GameObject SpawnElement() { // spawn the element on a random position, inside a imaginary sphere GameObject cube = Instantiate(mCubeObj, (Random.insideUnitSphere*4) + transform.position, transform.rotation ) as GameObject; // define a random scale for the cube float scale = Random.Range(0.5f, 2f); // change the cube scale cube.transform.localScale = new Vector3( scale, scale, scale ); return cube; } }
Finally, edit the Start()
function. Make sure to remove the StartCoroutine( ChangePosition() );
line and replace it with a call to start the SpawnLoop
coroutine.
public class SpawnScript : MonoBehaviour { void Start () { // Initializing spawning loop StartCoroutine( SpawnLoop() ); // Initialize Cubes array according to // the desired quantity mCubes = new GameObject[ mTotalCubes ]; } }
Now back in Unity you'll have to create a cube prefab to be instantiated by the script.
- Create a folder called Prefabs in the Project window.
- Change the Cube element scale on all axes to 1 and change the rotation to 0 on all axes.
- Drag the Cube to the Prefabs folder.
- Delete the Cube from the Hierarchy window.
- Select the _SpawnController and drag the Cube prefab from the Prefabs folder to the M Cube Obj field, located in the
SpawnScript
area of the inspector.
Now, if you press play in Unity and and run the project on the device, you should see the cubes spawning.
1.2. Rotating Cubes
We need to add movement to those cubes to make things more interesting. Let's rotate the cubes around their axes and over the ARCamera. It would be also cool to add some random factor over the cube movement to create a more organic feel.
Drag the Cube Prefab from the Prefabs folder to the hierarchy.
- On the Scripts folder create a new C# Script called CubeBehaviorScript.
- Drag the Script to the Cube prefab and open it to edit.
Each cube will have random characteristics. The size, rotation speed and rotation direction will be defined randomly, using some references previously defined. Let's create some controller variables and initialize the state of the cube.
using UnityEngine; using System.Collections; public class CubeBehaviorScript : MonoBehaviour { // Cube's Max/Min scale public float mScaleMax = 2f; public float mScaleMin = 0.5f; // Orbit max Speed public float mOrbitMaxSpeed = 30f; // Orbit speed private float mOrbitSpeed; // Anchor point for the Cube to rotate around private Transform mOrbitAnchor; // Orbit direction private Vector3 mOrbitDirection; // Max Cube Scale private Vector3 mCubeMaxScale; // Growing Speed public float mGrowingSpeed = 10f; private bool mIsCubeScaled = false; void Start () { CubeSettings(); } // Set initial cube settings private void CubeSettings(){ // defining the anchor point as the main camera mOrbitAnchor = Camera.main.transform; // defining the orbit direction float x = Random.Range(-1f,1f); float y = Random.Range(-1f,1f); float z = Random.Range(-1f,1f); mOrbitDirection = new Vector3( x, y , z ); // defining speed mOrbitSpeed = Random.Range( 5f, mOrbitMaxSpeed ); // defining scale float scale = Random.Range(mScaleMin, mScaleMax); mCubeMaxScale = new Vector3( scale, scale, scale ); // set cube scale to 0, to grow it lates transform.localScale = Vector3.zero; } }
Now it's time to add some movement to our cube. Let's make it rotate around itself and around the ARCamera, using the random speed and direction defined earlier.
// Update is called once per frame void Update () { // makes the cube orbit and rotate RotateCube(); } // Makes the cube rotate around a anchor point // and rotate around its own axis private void RotateCube(){ // rotate cube around camera transform.RotateAround( mOrbitAnchor.position, mOrbitDirection, mOrbitSpeed * Time.deltaTime); // rotating around its axis transform.Rotate( mOrbitDirection * 30 * Time.deltaTime); }
To make it more organic, the cube will scale up from size zero after it is spawned.
// Update is called once per frame void Update () { // makes the cube orbit and rotate RotateCube(); // scale cube if needed if ( !mIsCubeScaled ) ScaleObj(); } // Scale object from 0 to 1 private void ScaleObj(){ // growing obj if ( transform.localScale != mCubeMaxScale ) transform.localScale = Vector3.Lerp( transform.localScale, mCubeMaxScale, Time.deltaTime * mGrowingSpeed ); else mIsCubeScaled = true; }
2. Searching and Destroying
Those cubes are too happy flying around like that. We must destroy them with lasers! Let's create a gun in our game and start shooting them.
2.1. Shooting a Laser
The laser shot must be connected to the ARCamera and its rotation. Every time the player 'taps' the device's screen, a laser will be shot. We'll use the Physics.Raycast
class to check if the laser hit the target and if so we'll remove some health from it.
- Create a new empty object named _PlayerController and another empty object called _LaserController inside of it.
- Add a C# script called LaserScript inside the Scripts folder and drag it to _LaserController.
Inside LaserScript, we'll use a LineRenderer
to show the laser ray, using an origin point connected to the bottom of the ARCamera. To get the origin point of the laser ray—the barrel of the virtual gun—we'll get the camera Transform
at the moment when a laser is shot and move it 10 units down.
First, let's create some variables to control the laser settings and get mLaserLine
.
using UnityEngine; using System.Collections; public class LaserScript : MonoBehaviour { public float mFireRate = .5f; public float mFireRange = 50f; public float mHitForce = 100f; public int mLaserDamage = 100; // Line render that will represent the Laser private LineRenderer mLaserLine; // Define if laser line is showing private bool mLaserLineEnabled; // Time that the Laser lines shows on screen private WaitForSeconds mLaserDuration = new WaitForSeconds(0.05f); // time of the until the next fire private float mNextFire; // Use this for initialization void Start () { // getting the Line Renderer mLaserLine = GetComponent<LineRenderer>(); } }
The function responsible for shooting is Fire()
. That will be called every time the player presses the fire button. Notice that Camera.main.transform
is being used to get the ARCamera position and rotation and that the laser is positioned 10 units below that. This positions the laser at the bottom of the camera.
// Shot the Laser private void Fire(){ // Get ARCamera Transform Transform cam = Camera.main.transform; // Define the time of the next fire mNextFire = Time.time + mFireRate; // Set the origin of the RayCast Vector3 rayOrigin = cam.position; // Set the origin position of the Laser Line // It will always 10 units down from the ARCamera // We adopted this logic for simplicity mLaserLine.SetPosition(0, transform.up * -10f ); }
To check if the target was hit, we'll use a Raycast
.
// Shot the Laser private void Fire(){ // Hold the Hit information RaycastHit hit; // Checks if the RayCast hit something if ( Physics.Raycast( rayOrigin, cam.forward, out hit, mFireRange )){ // Set the end of the Laser Line to the object hit mLaserLine.SetPosition(1, hit.point ); } else { // Set the enfo of the laser line to be forward the camera // using the Laser range mLaserLine.SetPosition(1, cam.forward * mFireRange ); } }
At last, it's time to check if the fire button was pressed and call the laser effects when the shot is fired.
// Update is called once per frame void Update () { if ( Input.GetButton("Fire1") && Time.time > mNextFire ){ Fire(); } } private void Fire() { // anterior code supressed for simplicity // Show the Laser using a Coroutine StartCoroutine(LaserFx()); } // Show the Laser Effects private IEnumerator LaserFx(){ mLaserLine.enabled = true; // Way for a specific time to remove the LineRenderer yield return mLaserDuration; mLaserLine.enabled = false; }
Back in Unity we'll need to add a LineRenderer
component to the _LaserController object.
- Select _LaserController and in the Inspector window, click on Add Component. Name it "Line Renderer" and select the new component.
- Create a new folder called Materials, and create a new material inside of it called Laser.
- Select the Laser material and change it to any color that you like.
- Select the _LaserController and drag the Laser material to the Materials field of the LineRenderer component.
- Still in LineRenderer, under Parameters set Start With to 1 and End With to 0.
If you test the game now you should see a laser being shot from the bottom of the screen. Feel free to add an AudioSource component with a laser sound effect to _LaserController to spice it up.
2.1. Hitting the Cubes
Our lasers need to hit their targets, apply damage and eventually destroy the cubes. We'll need to add a RigidBody to the cubes, applying force and damage to them.
- Drag the Cube prefab from the prefabs folder to any place on the Stage.
- Select the Cube and select Add Component in the Inspector window. Name the new component "RigidBody" and select it.
- On the RigidBody component set Gravity and Is Kinematic to Off.
- Apply those changes to the prefab.
Now let's edit the CubeBehavior
script to create a function responsible for applying damage to the cube and another one for destroying it when the health falls below 0.
public class CubeBehaviorScript : MonoBehaviour { // Cube Health public int mCubeHealth = 100; // Define if the Cube is Alive private bool mIsAlive = true; // Cube got Hit // return 'false' when cube was destroyed public bool Hit( int hitDamage ){ mCubeHealth -= hitDamage; if ( mCubeHealth >= 0 && mIsAlive ) { StartCoroutine( DestroyCube()); return true; } return false; } // Destroy Cube private IEnumerator DestroyCube(){ mIsAlive = false; // Make the cube desappear GetComponent<Renderer>().enabled = false; // we'll wait some time before destroying the element // this is usefull when using some kind of effect // like a explosion sound effect. // in that case we could use the sound lenght as waiting time yield return new WaitForSeconds(0.5f); Destroy(gameObject); } }
Okay, the cube can take now damage and be destroyed. Let's edit the LaserScript
to apply damage to the cube. All we have to do is change the Fire()
function to call the Hit
method of the CubeBehavior
script.
public class LaserScript : MonoBehaviour { // Shot the Laser private void Fire(){ // code supressed for simplicity ... // Checks if the RayCast hit something if ( Physics.Raycast( rayOrigin, cam.forward, out hit, mFireRange )){ // Set the end of the Laser Line to the object hitted mLaserLine.SetPosition(1, hit.point ); // Get the CubeBehavior script to apply damage to target CubeBehaviorScript cubeCtr = hit.collider.GetComponent<CubeBehaviorScript>(); if ( cubeCtr != null ) { if ( hit.rigidbody != null ) { // apply force to the target hit.rigidbody.AddForce(-hit.normal*mHitForce); // apply damage the target cubeCtr.Hit( mLaserDamage ); } } } }
3. Conclusion
Congratulations! Our Augmented Reality game is finished! Yes, the game could be more polished, but the basics are there and the overall experience is pretty engaging. To make it more interesting you could add a particle explosion, like I did in the video, and on top of that, you could add a score system or even a wave system with a timer to make it more of a challenge. The next steps are up to you!
3.1. What's next?
We created an interesting AR experiment using Vuforia on Unity, however we still have a lot of cool features to cover. We didn't see any of the more sophisticated Vuforia resources: Targets, Smart Terrain, Cloud, and so on. Stay tuned for the next tutorials, where we'll cover more of those features, always using the same step-by-step approach.
See you soon!