1. Overview
ARCore is Google's framework for building augmented reality experiences on smartphones. You can use Unity's AR Foundation to build cross-platform AR applications.
What you'll build
In this codelab, you'll build a simple game using AR Foundation. The goal of the game is to collect packages using a car that you control using your handheld device.
However, this won't happen in an entirely virtual world! You'll mix physical atoms and digital bits to create a new type of player experience by creating a game that understands the environment around the player.
By the end of this codelab, your game will be able to:
- Detect real-world planes and draw a playing field over it.
- Cast rays from the camera's view and detect intersections with planes.
- React to real-world lighting conditions to give your game extra realism.
What you'll learn
- How to set up a project that uses Unity's AR Foundation.
- How to use
ARPlaneManager
to subscribe to new planes. - How to use
Raycast
to find intersections with virtual geometry - How to use
ARLightEstimationData
to light your scene.
What you'll need
- A supported ARCore device, connected using a USB cable to your development machine.
- Google Play Services for AR 1.23 or later.
- An installation of Unity Hub or Unity 2020 LTS.
2. Set up your development environment
In this step, you will get your environment ready for development with Unity's AR Foundation.
Ensure your device is compatible with AR
AR experiences on Android devices are driven by ARCore, which is available on ARCore supported devices. Ensure that your development device is compatible with AR. Alternatively, you can use a correctly configured AR-compatible Android Emulator instance.
Setup USB debugging on your device
You will need to enable Developer options on your device to run debug apps. If you haven't done so yet, refer to Android documentation on Enable developer options and USB debugging.
Install Unity (2020.3 LTS)
On your workstation, install Unity 2020 LTS. In this codelab, screenshots are shown of Unity's UI in the 2020.3 (LTS) version. Other versions of Unity may work, but may require additional steps. It may look different than the screenshots shown here.
Create a new project
Create a new project using the Universal Render Pipeline template. Give it a descriptive name and an appropriate location, and press CREATE.
Install the required frameworks
Unity's AR Foundation can be found in the Unity Package Manager.
- Open it by clicking on Window > Package Manager.
- In this window, install the packages you'll be using in this codelab. View the latest versions of these frameworks by expanding its entry using the
►
icon. Install the latest versions for each of these frameworks:- AR Foundation
- ARCore XR Plugin
When you're done, your Package Manager should look something like this:
Install the starter package
For this codelab, we've provided a starter package that contains prefabs and scripts that will expedite some parts of the codelab so you can focus on how to use AR Foundation.
- Install the starter package by opening Assets > Import Package > Custom Package... and opening
starter-package.unitypackage
. - In the window that pops up, ensure everything is selected.
- Click Import.
Change Build Settings
Since the application will run on Android, change the build platform to Android:
- Open File > Build Settings.
- In the Platform pane, select Android.
- Optionally, enable Development Build and Script Debugging in order to retain debugging information while your app runs.
- Click Switch Platform.
Change Project Settings
AR Foundation needs to be configured to initialize XR systems on startup.
- Open Edit > Project Settings... and click on the XR Plug-in Management section.
- In the Android tab, enable ARCore.
- In the left-hand pane, click the Player section.
- In the Android tab, under Other Settings, remove Vulkan from Graphics APIs.
- AR Required apps using ARCore require a minimum API level of 24. Scroll down and find Minimum API Level. Set the minimum API level to 24.
Add the required scene elements
The Universal Render Pipeline template comes with some game objects you won't be using in this tutorial.
- Delete all game objects in the
SampleScene
.
- Add AR Foundation objects. Right-click in the Hierarchy pane. Use this menu to add:
- XR > AR Session: This object controls the lifecycle of an AR experience.
- XR > AR Session Origin: This object transforms AR coordinates into Unity world coordinates.
- Light > Directional Light: This provides a light source to illuminate game objects.
Your hierarchy should look like this:
- Expand the AR Session Origin you created in the hierarchy, and select the AR Camera object. In the inspector, change its tag to MainCamera.
Set up rendering
Unity's Universal Render Pipeline needs one change to be compatible with AR Foundation.
- In the Project pane, navigate through Assets > Settings to find the ForwardRenderer asset.
- Select the ForwardRenderer.
- In the Inspector pane, use Add Renderer Feature to add an AR Background Renderer Feature. This component will render the camera feed in your scene.
Verify the setup
- Ensure that your device is plugged in, and that ADB debugging is on.
- Click File > Build And Run... This will upload the application to your device and start it when it has been installed.
- You should see the camera feed on your device's screen.
In the next step, you'll start adding functionality to your app.
3. Detect planes in the real world
Now that a basic scene has been set up, you can start on developing the game. In this step, you will detect planes and draw them to the scene.
Add an ARPlaneManager
component
An ARPlaneManager
detects ARPlane
s and creates, updates, and removes game objects when the device's understanding of the environment changes.
- Using the Hierarchy pane, create an empty
GameObject
. - Rename it to
Driving Surface Manager
. This component will display planes until one is selected by the player. - Select the new game object. Within the Inspector pane, click Add Component to add a AR Plane Manager.
- Configure the
ARPlaneManager
by setting thePlane Prefab
field:- Click the button next to
None
to bring up the Select GameObject window. - Select the Assets tab and search for Driving Surface Plane.
- Click the button next to
This prefab from the starter package provides a gritty floor texture that will be used as the plane decoration.
- Change the
Detection Mode
toHorizontal
. This configures theARPlaneManager
to only provide horizontal planes, ideal for driving on.
Add an ARRaycastManager
component
An ARRaycastManager
exposes raycast functionality. In the next step, we'll use this object to provide the controls for the user.
- Ensure the object called
Driving Surface Manager
is selected in the Hierarchy pane. - In the Inspector, click Add Component to add an
ARRaycastManager
component to your game object.
No further configuration is needed for this component.
Add a DrivingSurfaceManager
component
A DrivingSurfaceManager
is a helper script from the Starter Package that allows for an ARPlane
to be selected. Once a ARPlane
is selected, all other planes will be hidden, and new planes will be disabled.
- Ensure the object called
Driving Surface Manager
is selected in the Hierarchy pane. - In the Inspector, click Add Component to add a
DrivingSurfaceManager
component to your game object.
No further configuration is needed for this component.
Run the app
- Click File > Build And Run... to test your changes.
- Point your device at a horizontal real-world surface and move your device around to improve ARCore's understanding of the world.
- When ARCore has detected a plane, you should see a dirt texture cover real-world surfaces. The
ARPlaneManager
instantiates the givenPlane Prefab
for each detected plane. TheDriving Surface Plane
prefab has aARPlaneMeshVisualizer
component which creates a mesh for a givenARPlane
.
In the next step, you will use a detected plane as a playing field.
4. Perform a hit test against detected planes
In the previous step, you programmed an application that can detect planes. These planes are reflected in your game's scene. Now, you'll add interactivity with these planes by creating an aiming reticle and a car that will drive on the detected plane's surface.
Create an aiming reticle
The control scheme for this app involves the player pointing their phone at a surface. In order to give clear visual feedback for the designated location, you'll use an aiming reticle.
In order to "stick" this reticle to an AR plane, use a hit test. A hit test is a technique that calculates intersections when casting a ray in a given direction. You will use a hit test to detect an intersection in the direction of the camera's view.
Add the reticle
- In the Project pane near the bottom of the screen, navigate to Assets > Starter Package.
- Place the Reticle Prefab into the scene by dragging it into the project's Hierarchy pane.
- Select the reticle in the hierarchy.
- In the inspector, click Add Component. Add the
ReticleBehaviour
script from the Starter Package. This script contains some boilerplate for controlling the reticle. - The
ReticleBehaviour
script is dependent on theDriving Surface Manager
you created before, so add the dependency by clicking on theDriving Surface Manager
chooser. Select the Scene tab and pick theDriving Surface Manager
.
Edit the ReticleBehaviour
The ReticleBehavior
script will position the reticle on the plane that's in the center of the device's viewport.
- Open the
ReticleBehaviour.cs
script by double-clicking theScript
field. - Determine the center of the screen using Camera's
ViewToScreenPoint
. Edit theUpdate()
method to add the following:
var screenCenter = Camera.main.ViewportToScreenPoint(new Vector3(0.5f, 0.5f));
- Use this point to conduct a raycast. Add the following:
var hits = new List<ARRaycastHit>();
DrivingSurfaceManager.RaycastManager.Raycast(screenCenter, hits, TrackableType.PlaneWithinBounds);
The variable hits
will contain ARRaycastHit
s which describe points on trackables which are intersected by ray
.
- Determine the intersection point of interest by querying the
hits
list. Prioritize the locked plane contained inDrivingSurfaceManager
, and if it does not exist, use the first plane hit. Add the following to the end ofUpdate()
:
CurrentPlane = null;
ARRaycastHit? hit = null;
if (hits.Length > 0)
{
// If you don't have a locked plane already...
var lockedPlane = DrivingSurfaceManager.LockedPlane;
hit = lockedPlane == null
// ... use the first hit in `hits`.
? hits[0]
// Otherwise use the locked plane, if it's there.
: hits.SingleOrDefault(x => x.trackableId == lockedPlane.trackableId);
}
- If
hit
contains a result, move thisGameObject
's transform to the hit position.
if (hit.HasValue)
{
CurrentPlane = DrivingSurfaceManager.PlaneManager.GetPlane(hit.Value.trackableId);
// Move this reticle to the location of the hit.
transform.position = hit.Value.pose.position;
}
Child.SetActive(CurrentPlane != null);
Test the reticle
- Click File > Build And Run... to test your changes.
- When you point your device at a plane, you should see the reticle follow your camera's movements.
Create a car
The player will control a toy car that will drive towards the location of the reticle. A model and behavior for this car is provided in the Starter Package.
Add a CarManager
to your scene
- In the Hierarchy, create a new empty
GameObject
. - Rename it to
Car Spawner
. - Select the object you created. In the Hierarchy pane, click Add Component to add the
CarManager
component. - Set up
CarManager
's dependencies by clicking on the chooser for each field:- Car Prefab: In Assets, select Car Prefab.
- Reticle: In Scene, select Reticle Prefab.
- Driving Surface Manager: In Scene, select Driving Surface Manager.
This CarManager
behavior spawns a toy car on the plane that the reticle is on. If you'd like, check out the CarBehaviour
script to learn how the car is programmed.
Test driving
- Click File > Build And Run to test your changes.
- When you tap on a plane, you should see a small car appear at that location. This car will follow the reticle.
Add the game element
Now that the player can control an entity in the scene, give the player a destination to drive towards.
- Create a new empty
GameObject
in the Hierarchy. - Rename it to
Package Spawner
. - Select the object you created. In the Hierarchy pane, click Add Component to add the
PackageSpawner
component to it. - Set up
PackageSpawner
's dependencies by clicking on the chooser for each field:- Package Prefab: In Assets, select Package Prefab.
- Driving Surface Manager In Scene, select Driving Surface Manager.
This PackageSpawner
behavior spawns a new package at a random location on a locked ARPlane
if there isn't a package already.
Test the game
- Click File > Build And Run to test your changes. 2, After you create a car, a package should spawn.
- Drive your car to the package.
- A new one will appear at a random location.
5. Set up Lighting Estimation
Now that the basic game has been completed, add a touch of realism to your AR scene. In this step, you will use ARCore's Lighting Estimation API to detect the lighting present in the real world based on incoming camera frames. This information will be used to adapt your scene's lighting to match the real-world lighting.
Enable Lighting Estimation
- In Hierarchy, expand the AR Session Origin and select the AR Camera object.
- In the Inspector, expand the AR Camera Manager script.
- Change the Lighting Estimation field to Everything.
Modify the directional light
- In Hierarchy, select the Directional Light object.
- Add the
LightEstimation
component to it. This component from the Starter Package provides some boilerplate for subscribing to lighting changes. - In the
FrameReceived()
function, add:
ARLightEstimationData lightEstimation = args.lightEstimation;
if (lightEstimation.averageBrightness.HasValue)
Light.intensity = lightEstimation.averageBrightness.Value;
if (lightEstimation.averageColorTemperature.HasValue)
Light.colorTemperature = lightEstimation.averageColorTemperature.Value;
if (lightEstimation.colorCorrection.HasValue)
Light.color = lightEstimation.colorCorrection.Value;
if (lightEstimation.mainLightDirection.HasValue)
Light.transform.rotation = Quaternion.LookRotation(lightEstimation.mainLightDirection.Value);
if (lightEstimation.mainLightColor.HasValue)
Light.color = lightEstimation.mainLightColor.Value;
if (lightEstimation.mainLightIntensityLumens.HasValue)
Light.intensity = lightEstimation.averageMainLightBrightness.Value;
if (lightEstimation.ambientSphericalHarmonics.HasValue)
{
RenderSettings.ambientMode = AmbientMode.Skybox;
RenderSettings.ambientProbe = lightEstimation.ambientSphericalHarmonics.Value;
}
Test your changes
- Click File > Build And Run to test your changes.
- When looking at the objects in the scene, you may notice that they are colored depending on the environment's lighting.
- If possible, try modifying your lighting. For example, try turning off the lights in the room you're in. You should see the lighting on the objects adapt to the change in the real-world lighting.
6. Wrap up
Congratulations! You've made it to the end of this codelab on Unity AR Foundation.
What you've covered
- How to set up a basic project using Unity's AR Foundation and the Universal Rendering Pipeline.
- How to use
ARPlaneManager
to subscribe to new planes. - How to use
Raycast
to find intersections with virtual geometry. - How to use
ARLightEstimationData
to light your scene.
Next steps
- Check out Unity's AR Foundation samples.
- Browse AR Foundation documentation.
- Check out Google's documentation on ARCore Extensions for Unity's AR Foundation.
Bonus assignments
If you want to expand on the game you've created here, here are some ideas you could pursue:
- Add a score counter to the your game by modifying a
TextMeshPro
when aPackageManager
spawns a new package. - Check out performance information when your game is running by enabling the Performance Overlay.
- Use Persistent Raycasts to place new objects in your scene first. When a plane is detected in that area, that object will update to snap to that plane.