# How to Make Interactable Tectonic Plates in AR – Part 2

If you haven’t read Part 1 of the tutorial yet, it’s highly recommended you do so.

In the first part of our tutorial, we began setting up our tectonic plates with animations and so forth.  We’re ready to continue with our educational AR app and set it up so that the correct animation can run at the right time.

BUILD GAMES

FINAL DAYS: Unlock 250+ coding courses, guided learning paths, help from expert mentors, and more.

### Continuing with Scripting the Touch Manager

Now it’s time to create the “CalculateDragDirectionOnPlate” function. It takes in a touch end position in pixel coordinates. This is a pretty big function, so let’s take it in steps.

```// called after the dragged touch is released
void CalculateDragDirectionOnPlate (Vector3 touchEnd)
{

}```

First, we make a variable which will hold the world position of the touch. Then we create a new ray which shoots from the screen to the touch position.

```// create world pos for touch end
Vector3 touchEndWorldPos = Vector3.zero;

// create ray from camera to touch
Ray ray = Camera.main.ScreenPointToRay(touchEnd);```

This is unlike the normal raycast though. Instead of shooting a ray and checking if it hit a collider, we’re doing a plane raycast. This creates an abstract, flat plane which we then get the position that the ray intersected it. That’s our world position.

```// we are raycasting to a plane so it's Y axis remains 0
Plane rayPlane = new Plane(Vector3.up, Vector3.zero);

float enter = 0.0f;

// send the raycast
if(rayPlane.Raycast(ray, out enter))
{
touchEndWorldPos = ray.GetPoint(enter);
}```

We now need to check the distance of the drag. If it’s too short, then we’ll un-assign the plate (we’ll make this later) and return.

```// was this not a drag but a tap? if so, unassign the plate
if(Vector3.Distance(touchStartWorldPos, touchEndWorldPos) < 3.0f && touchingPlate)
{
// un-assign the plate movement
return;
}```

Then, we need to calculate a normalized direction between the two positions. Next, we round the X and Z values to the nearest integer. This means that each axis will be either: -1, 0 or 1. With this, we can then calculate the direction of the drag in 4 ways.

```// get direction between 2 points and round it
Vector3 dir = Vector3.Normalize(touchEndWorldPos - touchStartWorldPos);
dir = new Vector3(Mathf.Round(dir.x), 0, Mathf.Round(dir.z));```

Now we check for a right, left, forwards or back drag by looking at the axis direction.

```Vector3 plateDir = Vector3.zero;

// dragged RIGHT
if (dir.x == 1.0f)
plateDir = Vector3.right;
// dragged LEFT
else if (dir.x == -1.0f)
plateDir = Vector3.left;
// dragged UP
else if (dir.z == 1.0f)
plateDir = Vector3.forward;
// dragged DOWN
else if (dir.z == -1.0f)
plateDir = Vector3.back;```

Then we’ll assign that direction to the plate.

`// assign the plate movement`

Let’s make that script now.

### Scripting the Plate Manager

Create a new C# script called “PlateManager” and attach it to the “Manager” object.

We need to add a few libraries, for using the timelines.

```using UnityEngine.Timeline;
using UnityEngine.Playables;```

Now let’s start on our variables.

First, we got our left and right plates which hold all the data for their corresponding plates. “currentlyAnimating” is a bool which is true when we’re animating the plates.

```// plates
public Plate leftPlate;             // left tectonic plate
public Plate rightPlate;            // right tectonic plate

// states
public bool currentlyAnimating;     // are the plates currently animating?```

Then we need to list all the playable assets (timelines) for each of our animations.

```// timeline playable assets
public PlayableAsset transformForwardsBackAnim;
public PlayableAsset transformBackForwardsAnim;
public PlayableAsset divergentAnim;
public PlayableAsset convergentOverUnderAnim;
public PlayableAsset convergentUnderOverAnim;```

We need to also access the playable director, so let’s link it.

```// components
public PlayableDirector director;   // component used to play the PlayableAssets above```

Finally, we create an instance (singleton) of the script so that we can access it easier later on.

```// instance
public static PlateManager instance;

void Awake ()
{
// set instance to this script
instance = this;
}```

Our first function is our largest and most important. “AssignPlateMovement” is a function which is called from the TouchManager. It sends over the plate to assign and a global movement direction.

```// called after the user drags a direction for the plate to move in
public void AssignPlateMovement (Plate plate, Vector3 moveDirection)
{

}```

First, we check if we’re currently animating. If so, we can’t assign anything so return. Then we check if this is the left plate we’re setting. Since the left plate is a rotated version of the right one, we need to flip the X axis of the move direction.

```// if we're currently animating, don't allow for assigning plate movement
if (currentlyAnimating)
return;

// invert moveDirection X axis if the plate is the left one
if (plate == leftPlate)
moveDirection.x = -moveDirection.x;```

Now we check the direction and assign the corresponding movement to the plate.

```// did the user swipe FORWARDS?
if(moveDirection == Vector3.forward)
{
plate.assignedMovement = PlateMovement.TransformForward;
}
// did the user swipe BACKWARDS?
else if(moveDirection == Vector3.back)
{
plate.assignedMovement = PlateMovement.TransformBack;
}
// did the user swipe AWAY from the center?
else if (moveDirection == Vector3.right)
{
plate.assignedMovement = PlateMovement.Divergent;
}```

If you’re converging the plates, the one on top depends on the last plate you drag. So we need to check if the other plate’s assigned movement is convergent under. If so, then the current one is over. Otherwise, we just set it to converge under.

```// did the user swipe TOWARDS the center?
else if(moveDirection == Vector3.left)
{
// get the other plate
Plate otherPlate = plate == leftPlate ? rightPlate : leftPlate;

// if the other plate converges under, converge over
if (otherPlate.assignedMovement == PlateMovement.ConvergentUnder)
plate.assignedMovement = PlateMovement.ConvergentOver;
// otherwise, just converge under
else
plate.assignedMovement = PlateMovement.ConvergentUnder;
}```

Now we need to activate the arrow and rotate it to face the direction we dragged.

```// set arrow visual
plate.arrowVisual.SetActive(true);

// rotate arrow depending on assigned movement
switch(plate.assignedMovement)
{
case PlateMovement.TransformForward: plate.arrowVisual.transform.localEulerAngles = new Vector3(0, plate == leftPlate? 90 : -90, 0); break;
case PlateMovement.TransformBack: plate.arrowVisual.transform.localEulerAngles = new Vector3(0, plate == leftPlate ? -90 : 90, 0); break;
case PlateMovement.Divergent: plate.arrowVisual.transform.localEulerAngles = new Vector3(0, 0, 0); break;
case PlateMovement.ConvergentOver: plate.arrowVisual.transform.localEulerAngles = new Vector3(0, 180, 0); break;
case PlateMovement.ConvergentUnder: plate.arrowVisual.transform.localEulerAngles = new Vector3(0, 180, 0); break;
}```

If we have an assigned movement on both plates, we check to see if the plates are compatible. If so, we call the “PlayAnimation” function, otherwise we call the “OnAnimationEnd” function.

```// do both plates have an assigned movement?
if (leftPlate.assignedMovement != PlateMovement.Unassigned && rightPlate.assignedMovement != PlateMovement.Unassigned)
{
// are the 2 assigned movements compatable with eachother?
if (PlatesAreCompatable())
Invoke("PlayAnimation", 0.5f);
else
Invoke("OnAnimationEnd", 0.35f);
}```

This returns a bool. True being that the plates are compatible and false being that they aren’t.

It’s basically checking for all the possible plate combinations.

```// returns true is both plates assigned movement are compatible
bool PlatesAreCompatible ()
{
// plates are both transforming forwards
if (leftPlate.assignedMovement == PlateMovement.TransformForward && rightPlate.assignedMovement == PlateMovement.TransformForward)
return false;
// plates are both transforming backwards
else if (leftPlate.assignedMovement == PlateMovement.TransformBack && rightPlate.assignedMovement == PlateMovement.TransformBack)
return false;
// left plate is diverging but right plate is not
else if (leftPlate.assignedMovement == PlateMovement.Divergent && rightPlate.assignedMovement != PlateMovement.Divergent)
return false;
// left plate is not diverging but right plate is
else if (leftPlate.assignedMovement != PlateMovement.Divergent && rightPlate.assignedMovement == PlateMovement.Divergent)
return false;
// left plate is converging over but right plate is not converging under
else if (leftPlate.assignedMovement == PlateMovement.ConvergentOver && rightPlate.assignedMovement != PlateMovement.ConvergentUnder)
return false;
// left plate is converging under but right plate is not converging over
else if (leftPlate.assignedMovement == PlateMovement.ConvergentUnder && rightPlate.assignedMovement != PlateMovement.ConvergentOver)
return false;
else
return true;
}```

Now let’s create a function called “UnassignPlate”. This basically resets a plate’s assigned movement and disables the arrow visual.

```// unassigns a plate's assigned movement and disables arrow
public void UnassignPlate (Plate plate)
{
plate.assignedMovement = PlateMovement.Unassigned;
StartCoroutine(DeactivateArrowVisual(plate));
}

// deactivates desired plate's arrow visual
IEnumerator DeactivateArrowVisual (Plate plate)
{
plate.arrowAnimator.SetTrigger("Exit");
yield return new WaitForSeconds(0.3f);
plate.arrowVisual.SetActive(false);
}```

“OnAnimationEnd” is a function that will be called after the animation has finished. This basically resets the plates. When the animation begins, this function gets invoked to be called after [timeline duration] seconds.

```// called after the plate animation has ended
void OnAnimationEnd ()
{
currentlyAnimating = false;
UnassignPlate(leftPlate);
UnassignPlate(rightPlate);
}```

Now let’s create the “PlayAnimation” function.

```// plays the assigned plate animation
void PlayAnimation ()
{

}
```

First, we deactivate the arrow visuals.

```// disable arrows
StartCoroutine(DeactivateArrowVisual(leftPlate));
StartCoroutine(DeactivateArrowVisual(rightPlate));
```

Then we set the corresponding timeline to the director.

```// assign the corresponding timeline to the director
switch (leftPlate.assignedMovement)
{
case PlateMovement.TransformForward:
{
director.playableAsset = transformForwardsBackAnim;
break;
}
case PlateMovement.TransformBack:
{
director.playableAsset = transformBackForwardsAnim;
break;
}
case PlateMovement.Divergent:
{
director.playableAsset = divergentAnim;
break;
}
case PlateMovement.ConvergentOver:
{
director.playableAsset = convergentOverUnderAnim;
break;
}
case PlateMovement.ConvergentUnder:
{
director.playableAsset = convergentUnderOverAnim;
break;
}
}```

Lastly, we play the animation and invoke “OnAnimationEnd” to be called in [timeline duration] seconds.

```// play animation
currentlyAnimating = true;
director.Play();
Invoke("OnAnimationEnd", (float)director.playableAsset.duration);```

The last function we call is “DoubleTapResetPlates”. This just resets everything when you double tap.

```// called when the player double taps the screen, resets plates
public void DoubleTapResetPlates ()
{
director.Stop();
director.playableAsset = null;
// --disable UI text
currentlyAnimating = false;
UnassignPlate(leftPlate);
UnassignPlate(rightPlate);
}```

### Lava Visual

Something that adds to the visual element is a lava element.

Create a new Plane object (right click Hierarchy > 3D Object > Plane) and call it “Lava”. Set the material to “Lava”. Then, scale and position it so it leaks around the edges and just rises above the base. Finally, set it as a child of the “TectonicPlates” object.

We’re also going to add a slight animation which will “pulse” the intensity of the lava.

First, un-parent the lava object so it’s got no parent. We need to do this due to the original parent already having an animator component.

So, with the lava selected, go to the Animation window and create a new animation.

Here, I ping-ponged the scale and color.

Once you’ve finished animating, you can make the “Lava” object a child of the “TectonicPlates” object.

### Creating the UI Element

Since this app is aimed at education, it would be good to show the user what type of plate movement they just triggered.

Create a new Canvas (right click Hierarchy > UI > Canvas). Set the “Render Mode” to World Space. Set the scale to 0.015, and position it behind the plates.

Add a new Text element to the canvas (right click Canvas > UI > Text). Resize it and edit the Text component to your liking. I added an Outline component to contrast any possible background.

Now create an animation called “InfoText_Entrance” that makes the text pop up and after a few seconds it disappears.

With the animation completed, let’s disable the text object and begin scripting.

Create a new C# script called “UI” and attach it to the “Manager” object.

Since we’re going to be using UI, we’ll need to reference Unity’s UI library.

`using UnityEngine.UI;`

Our 2 variables are the “infoText”, which is the actual text element we’re going to edit, and “infoTextAnim” is the Animator component attached to the text.

```public Text infoText;
public Animator infoTextAnim;```

We’re also going to include an instance (singleton) so that we can access this script easier later on.

```// instance
public static UI instance;

void Awake ()
{
// set instance to this script
instance = this;
}```

Our one and only function is “SetText”. This sets the text to display a certain string and plays the animation.

```// sets the info text
public void SetText (string textToDisplay)
{
infoText.gameObject.SetActive(true);
infoText.text = textToDisplay;
infoTextAnim.Play("InfoText_Entrance");
}```

### Connecting the Scripts

Now that we have all of our scripts, it’s time to connect them together.

Create a new function called “DoubleTapCheck”. This checks to see if the user has double tapped, and calls the corresponding function in the PlateManager script if so.

```// checks for a double tap to reset the plates
void DoubleTapCheck ()
{
if(Time.time - lastTapTime <= doubleTapMaxTime)
{
PlateManager.instance.DoubleTapResetPlates();
}

lastTapTime = Time.time;
}```

We also need to call this function up in Update, where we check…

```// did the touch START this frame?
if(Input.touches[0].phase == TouchPhase.Began)
{
DoubleTapCheck();
SetStartTouch(Input.touches[0].position);
}```

Then, in the “CalculateDragDirectionOnPlate” function, we need to do 2 things.

First, where we check if the player tapped and didn’t drag, call the “UnassignPlate” function in the PlateManager script.

```// was this not a drag but a tap? if so, unassign the plate
if(Vector3.Distance(touchStartWorldPos, touchEndWorldPos) < 3.0f && touchingPlate)
{
PlateManager.instance.UnassignPlate(touchingPlate);
return;
}```

And right at the end of the function, assign the plate movement.

```// assign the plate movement
PlateManager.instance.AssignPlateMovement(touchingPlate, plateDir);```

Now for the PlateManager script.

In the “PlayAnimation” function, we need to call the “SetText” function in UI to display the current animation playing.

```// assign the corresponding timeline to the director
// also set the UI text to display the movement type
switch (leftPlate.assignedMovement)
{
case PlateMovement.TransformForward:
{
director.playableAsset = transformForwardsBackAnim;
UI.instance.SetText("Transform Boundry");
break;
}
case PlateMovement.TransformBack:
{
director.playableAsset = transformBackForwardsAnim;
UI.instance.SetText("Transform Boundry");
break;
}
case PlateMovement.Divergent:
{
director.playableAsset = divergentAnim;
UI.instance.SetText("Divergent Boundry");
break;
}
case PlateMovement.ConvergentOver:
{
director.playableAsset = convergentOverUnderAnim;
UI.instance.SetText("Convergent Boundry");
break;
}
case PlateMovement.ConvergentUnder:
{
director.playableAsset = convergentUnderOverAnim;
UI.instance.SetText("Convergent Boundry");
break;
}
}```

Finally, in the “DoubleTapResetPlates” function, we need to deactivate the text object.

`FindObjectOfType<UI>().infoText.gameObject.SetActive(false);`

### Connecting Inspector Properties

Make sure that each plate’s “Plate” script has the correct properties assigned to it.

Do the same for the “Manager” object and its various scripts.

### Testing in the Editor

You might want to test out the app in the editor before we build it to a device. There are three things we need to do:

• Disable EasyAR integration
• Add a camera to the scene

To add mouse input, go to the “TouchManager” script. In the “Update” function, add:

```if (Input.GetMouseButtonDown(0))
{
DoubleTapCheck();
SetStartTouch(Input.mousePosition);
}
else if(Input.GetMouseButtonUp(0))
{
if(touchingPlate != null)
CalculateDragDirectionOnPlate(Input.mousePosition);
}```

Back in the editor we need to disable the EasyAR components, so that they don’t try and run. Deactivate the “EasyAR_Startup” GameObject, and disable the Image Target Behaviour script on the “ImageTarget_TectonicPlate” GameObject.

Now we just need to add a new camera to the scene (right click Hierarchy > Camera). Set its tag as “MainCamera” and position it where you like.

You should now be able to press play and test the app out in the editor!

### Building

You can follow the last section of this tutorial on how to build to an Android device.

### Conclusion

So we’ve made a working model of tectonic plates, with interactions to trigger certain plate interactions. If you want to use this in AR, you’ll need to print out the included image marker (located inside the StreamingAssets) folder.  You can locate the project files here.