Intro

Check out the other parts for our How to Create an AR Shoot’em Up Gallery Game here:

In Part 1 of this series, I showed you how to:

setup a Unity Project to create iOS AR games

import a 3D Bottle Model

create/design materials for it and then assign them

add physics to said bottle.

With the initial setup done, let’s now develop the game mechanics.

The complete Unity project for this tutorial is available here . All images and models used in this tutorial have been created by yours truly.

This tutorial was implemented in:

Xcode 10.1

Unity 2018.3.8f1

Tutorial Outline

Create Placement Indicator Add Script to Placement Indicator

*Building to Android with ARCore is outside the scope of this tutorial, we encourage you to research how to do so if you would like to.

Create Placement Indicator

When a user first opens your game, they’ll need indications on where to place their targets. You’ll need to create a Placement Indicator to implement this functionality. You’ll start by creating an empty game object, right-clicking in the Scene Hierarchy and selecting Create Empty. Name it Placement Indicator. Within it, create a Quad, then right-click Placement Indicator 3D Object > Quad. The quad primitive resembles the plane, but its edges are only one unit long and the surface is oriented in the XY plane of the local coordinate space. Also, a quad is divided into just two triangles, whereas the plane contains two hundred. A quad is useful in cases where a scene object must be used simply as a display screen for an image or movie.

Rotate the quad on its x-axis 90 degrees, and resize it on all axes to the size of the plane you created originally 0.1.

Now you’ll need to create a material for it. Navigate into your new Materials folder by double-clicking it. Inside said folder, you once again right click and rollover to Create from the menu that appears and select Material. Name the new material Indicator. With it selected, move over to the Inspector pane from the Shader drop-down select Unlit > Transparent, click Select inside of the Texture box below, and choose the image titled AR Placement Indicator in the dialogue box that appears. With that done, drag the material on to the quad then your Placement Indicator is complete.

Add Script to Placement Indicator

Now you’ll need to create a script to control the logic in your scene. In your Assets folder, create a new folder and label it Scripts. You’ll house all the scripts need for your game here. Once you’ve done so, right-click > Create > C# Script and name it ARTapToPlaceObject.

Double click on your new C# Script to open it. The first thing you’ll need to do is to import some libraries you’ll need to pull from to implement your AR functions.

using System.Collections; using System.Collections.Generic; using UnityEngine; //import the libraries below using UnityEngine.XR.ARFoundation; using UnityEngine.Experimental.XR; using System; 1 2 3 4 5 6 7 8 using System . Collections ; using System . Collections . Generic ; using UnityEngine ; //import the libraries below using UnityEngine . XR . ARFoundation ; using UnityEngine . Experimental . XR ; using System ;

You’ll need to interact with the AR Session you created in your scene to be able to connect with your real-world environment. But before you can do so, you’ll need to create a public variable to store your AR Session attribute. Then with your AR Session variable, you can save a reference to it right as the game starts.

public class ARTapToPlaceObject : MonoBehaviour { // to interact with the AR Session object you'll need to create a variable to store the AR Session attribute private ARSessionOrigin arOrigin; void Start() { // right when your game starts you'll save a reference it arOrigin = FindObjectOfType<ARSessionOrigin>(); } } 1 2 3 4 5 6 7 8 9 10 11 public class ARTapToPlaceObject : MonoBehaviour { // to interact with the AR Session object you'll need to create a variable to store the AR Session attribute private ARSessionOrigin arOrigin ; void Start ( ) { // right when your game starts you'll save a reference it arOrigin = FindObjectOfType < ARSessionOrigin > ( ) ; } }

The game logic is that on every frame update you’ll want to check the world around you to find out where the camera is pointing and identify if there is a position where you can place a virtual object. In order to represent that position in space, you’ll need to use a Pose object. Let’s call said pose object placementPose. The Pose object is a simple data structure that describes the position and rotation of a 3D point so its perfect for what we’ll need.

In the Update method, you’ll call a new method that will update the placement pose called UpdatePlacementPose().

public class ARTapToPlaceObject : MonoBehaviour { // to interact with the AR Session object you'll need to create a reference to it below private ARSessionOrigin arOrigin; //a position where you can place a virtual object. Pose object is a simple data structure that describes the position and rotation of a 3D point private Pose placementPose; void Start() { // right when your game starts you'll save a reference it arOrigin = FindObjectOfType<ARSessionOrigin>(); } void Update() { // call placement pose in the update method to calculate pose position and rotation UpdatePlacementPose(); } // method that will update the placement pose private void UpdatePlacementPose() { } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class ARTapToPlaceObject : MonoBehaviour { // to interact with the AR Session object you'll need to create a reference to it below private ARSessionOrigin arOrigin ; //a position where you can place a virtual object. Pose object is a simple data structure that describes the position and rotation of a 3D point private Pose placementPose ; void Start ( ) { // right when your game starts you'll save a reference it arOrigin = FindObjectOfType < ARSessionOrigin > ( ) ; } void Update ( ) { // call placement pose in the update method to calculate pose position and rotation UpdatePlacementPose ( ) ; } // method that will update the placement pose private void UpdatePlacementPose ( ) { } }

Before we go any further we’ll need to get familiar with AR Foundation Physics attribute Raycast. Raycast will cast a ray into the real world and it will tell if it has hit a real-world surface. Raycast needed parameters are Raycast(Vector3, List<ARRaycastHit>, TrackableType). The Vector3, is the point, in the device screen pixels, from which to cast the ray. List<ARRaycastHit> is the hitResults contents of the list are replaced with the raycast, if successful. TrackableType is the types of objects to cast against.

Knowing this let’s use the screen center as your first parameter the ray. To so create a variable called screenCenter and set it to the camera you are currently rendering with and define that position in pixels. For your second parameter create a variable called hits and assign new empty List<ARRaycastHit>(). Using your AR Session attribute, arOrigin, cast your ray. Then for the last parameter, you’ll want to use a plane as your TrackableType.

private void UpdatePlacementPose() { // the ray to be cast var screenCenter = Camera.current.ViewportToScreenPoint(new Vector3(0.5f, 0.5f)); // list of hitResults var hits = new List<ARRaycastHit>(); // Cast the ray arOrigin.Raycast(screenCenter, hits, TrackableType.Planes); } 1 2 3 4 5 6 7 8 9 private void UpdatePlacementPose ( ) { // the ray to be cast var screenCenter = Camera . current . ViewportToScreenPoint ( new Vector3 ( 0.5f , 0.5f ) ) ; // list of hitResults var hits = new List < ARRaycastHit > ( ) ; // Cast the ray arOrigin . Raycast ( screenCenter , hits , TrackableType . Planes ) ; }

After arOrigin.Raycast is called you’ll either end up with empty hit list, meaning there are no flat planes in front of the camera currently, or you’ll have a list with one or more items representing the planes that are in front of the camera. To keep track of this outcome you need to create a class boolean variable called Placement Pose Is Valid and have it initially set false.

public class ARTapToPlaceObject : MonoBehaviour { private ARSessionOrigin arOrigin; private Pose placementPose; // keep track of hits on flat planes private bool placementPoseIsValid = false; … } 1 2 3 4 5 6 7 8 public class ARTapToPlaceObject : MonoBehaviour { private ARSessionOrigin arOrigin ; private Pose placementPose ; // keep track of hits on flat planes private bool placementPoseIsValid = false ; … }

In your Update Placement Pose method, you’ll only consider the placement pose valid when if you actually hit something. So, in other words, if your hits array has at least one item in it. In your, if statement you will set your placement pose looking up the first hit result in your hits array and accessing its pose property. Now your placement pose will be constantly be running as your game updates.

private void UpdatePlacementPose() { ... // if at least one item is in the hit array access its pose properties placementPoseIsValid = hits.Count > 0; if (placementPoseIsValid) { placementPose = hits[0].pose; } } 1 2 3 4 5 6 7 8 9 10 11 12 private void UpdatePlacementPose ( ) { . . . // if at least one item is in the hit array access its pose properties placementPoseIsValid = hits . Count > 0 ; if ( placementPoseIsValid ) { placementPose = hits [ 0 ] . pose ; } }

The complete code is But the placement post is just the descriptor of position and rotation. You haven’t actually updated any of the visuals yet. Back in your update method lets call a new method called UpdatePlacementIndicator().

void Update() { UpdatePlacementPose(); // graphic repersentation of placement indicator to update in every frame of the game UpdatePlacementIndicator(); } 1 2 3 4 5 void Update ( ) { UpdatePlacementPose ( ) ; // graphic repersentation of placement indicator to update in every frame of the game UpdatePlacementIndicator ( ) ; }

If you are going to update that placement indicator object in your scene you’ll need to make a reference to it in your code. To do that create a public variable of type GameObject and call it placementIndicator. Create your UpdatePlacementIndicator method. Inside of it, you’ll again check to make sure that the placement pose is valid with an if statement. If it is you’ll want to make that placement indicator active, in other words, visible. If the pose isn’t valid you’ll want to hide the indicator by setting its active state to false

private void UpdatePlacementIndicator() { // if there is at least on hit in hit list set placement indicator to visable if (placementPoseIsValid) { placementIndicator.SetActive(true); } else { // if not set placement indicator to not visible placementIndicator.SetActive(false); } } 1 2 3 4 5 6 7 8 9 10 private void UpdatePlacementIndicator ( ) { // if there is at least on hit in hit list set placement indicator to visable if ( placementPoseIsValid ) { placementIndicator . SetActive ( true ) ; } else { // if not set placement indicator to not visible placementIndicator . SetActive ( false ) ; } }

Now your indicator visibility is controlled but you’ll need to control its position and rotation if placement pose is valid. You’ll do that by modifying its transform property calling the Set Position and Rotation method, which sets the world space position and rotation of the Transform component. For the parameters for this method, you’ll need a Vector3 position, which is your placement pose position, and Quaternion rotation, which is your placement pose rotation.

if (placementPoseIsValid) { placementIndicator.SetActive(true); // if there is at least one hit in hit list set the position and a roation of the placement indicator to that of the placement pose position and rotation placementIndicator.transform.SetPositionAndRotation(placementPose.position, placementPose.rotation); } 1 2 3 4 5 if ( placementPoseIsValid ) { placementIndicator . SetActive ( true ) ; // if there is at least one hit in hit list set the position and a roation of the placement indicator to that of the placement pose position and rotation placementIndicator . transform . SetPositionAndRotation ( placementPose . position , placementPose . rotation ) ; }

Go ahead and save your code and switch back over to Unity. Currently, the code you created does not exist in your scene. To add it you’ll need to create an empty game object within your scene hierarchy and name it Interaction. Its a game object with no physical presence but it gives you a place to hang your code. Drag and drop your AR Tap To Place Object script on the Interaction game object. Once you have done so you’ll notice in the Inspector panel the public parameter called PlacementIndicator appears. In that field drag and drop your Placement Indicator game object.

Build your Xcode project

With what you have done so far go ahead and build the Xcode project. Open the Build Settings from the main menu, File > Build Settings. Click Add Open Scenes to add the Main scene to the list of scenes that will be built. Click Build to build.

You will be prompted to choose where to build your Xcode project. Best practice mandate that you have a dedicated builds folder within your project folder. To make said new folder, click the down arrow in the top right of the prompt to expand it, and then click New Folder. When prompted to choose a name, enter Builds and click Create. This will create a new folder called Builds in the root directory for your project. In the Sava As filed, enter AR_Shooting_Gallery and click Save.

Unity will now create an Xcode project called AR_Shooting_Gallery in the Builds folder.

Test the project on your iOS device

With your Xcode project now ready, let’s build it to your iOS device. Navigate back to the Project Settings by selecting your project in the Navigator panel. In the top left, select Unity-iPhone to view the project settings. It will open with the General tab selected. Under the topmost section called Identity, you may see a warning and a button.

This warning doesn’t mean that anything is wrong with your project: it just means that Unity-iPhone applications need a development team. In the General page under Signing, select your personal development team from the Team dropdown menu.

When I test this, I am intentionally holding the phone very still so that the camera does not detect any flat planes. But once I start moving the phone camera a little bit it picks up enough information about the world around me to identify those flat planes. You’ll see that the placement indicator moves just as desired. You can even switch to the floor and it sees it as a separate plan.

One thing you’ll notice is that as you turn your phone, the placement indicator doesn’t turn. That’s because the rotation of the pose returned by our ray cast is always going to be oriented to whatever direction the phone was facing when AR Kit started up. It would be better if that placement indicator turned as the phone turned.

Conclusion

In this tutorial, you learned how to build:

a placement indicator from scratch that will instruct players on the best place to place their bottles for target practice

the script to develop your placement indicator logic for gameplay

Next time in How to Create an AR Shoot’em Up Gallery Game Part 3, you will learn how to make the placement indicator turn as your phone turns, place your bottles in the game world, and add a shooting interface to your game.

Go forth into the AR universe and create more!