
Languages used:
With the main scene open, we need to import the Lean Touch Asset. Lean Touch provides a quick and easy way to add multiple gestures to an AR project without writing (almost) any code.
Installation is a two-step process, firstly you need to download the Unity Assets (there are two versions of LeanTouch, the Free version is enough for our needs) from the Unity Store, to add it to your asset collection.
Head to The Unity Store

Secondly, install it in Unity by going to Window -> Package Manager
Search under Packages: My Assests for Lean Touch, download and import.

Add the LeanTouch GameObject by right-clicking on the Hierarchy panelLean -> Touch.
Select it and in the Inspector click on Add Simulator

We now need to add the touch controls to the Prefab GameObject (e.g. ARObject), there are numerous options and Lean Touch can be used for any application with a touch screen.
Double-click your ARObject Prefab to open it in Edit mode and click on Add Component. If you type in Lean you will see a long list of options. The first one is Lean Selectable and we want to tick the Self Selected option, this simple makes sure our object is automatically selected and ready to touch.
Lean Pinch Scale with Required Finger Count3;Lean Twist Rotate Axis and we are moving the y axis - so set y to 1, with Required Finger Count2;Lean Drag Transalte with Required Finger Count1;As the Lean Drag Translate will be in conflict with the tapToPlace script, we can change the latter and use Lean touch for the input tap.
Exit from the Prefab editing mode.
Open the tapToPlace script in VSCode (tapToPlace should be attached to XROrigin GameObject).
The updated script needs to have:
using Lean.Touch; at the top of the fileonEnable and onDisableUpdate function commented out, with the new OnFingerTap(LeanFinger finger) functiontimeThreshold variable is not used anymore and it can be removedThe complete script will look like this:
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;
using UnityEngine.InputSystem;
using Lean.Touch;
[RequireComponent(typeof(ARRaycastManager))]
public class tapToPlace : MonoBehaviour
{
public GameObject gameObjectToInstantiate; //the Prefab GameObject to instantiate in the AR environment. To be added in the inspector window
private GameObject spawnedObject; //the Prefab Instantiate in the scene. Used internally by the script
private ARRaycastManager _arRaycastManager; //part of the XROrigin
static List<ARRaycastHit> hits = new List<ARRaycastHit>();
public float timeThreshold = 0.5f; //User need to tap and hold the finger for at least 0.5 sec to create the content
public bool isTouching = false;
//Event design to fire when content is created
public delegate void ContentVisibleDelegate();
public event ContentVisibleDelegate _contentVisibleEvent;
private void Awake()
{
_arRaycastManager = GetComponent<ARRaycastManager>();
}
private void OnEnable()
{
LeanTouch.OnFingerTap += OnFingerTap;
}
private void OnDisable()
{
LeanTouch.OnFingerTap -= OnFingerTap;
}
public bool TryGetTouchPosition(out Vector2 touchPosition)
{
if (Touchscreen.current.primaryTouch.press.isPressed)
{
isTouching = true;
touchPosition = Touchscreen.current.primaryTouch.position.ReadValue();
return true;
}
touchPosition = default;
isTouching = false;
timeThreshold = 0;
return false;
}
/*
void Update()
{
[...]
}
*/
private void OnFingerTap(LeanFinger finger)
{
if (finger.TapCount == 2) // Check for double tap
{
Vector2 touchPosition = finger.ScreenPosition;
if (_arRaycastManager.Raycast(touchPosition, hits, TrackableType.PlaneWithinPolygon))
{
var hitPose = hits[0].pose;
if (spawnedObject == null)
{
spawnedObject = Instantiate(gameObjectToInstantiate, hitPose.position, hitPose.rotation);
_contentVisibleEvent?.Invoke();
}
else
{
spawnedObject.transform.position = hitPose.position;
}
}
}
}
}
Select the LeanTouch GameObject in the Hierarchy, and in the Inspector Panel:
Lean Select By Finger and set the Camera to the Main Camera in the SceneWith the double tap we will create the object, and a single tap used to select the object (this is provided by the new tapToPlace script).
We also need to change the text in the C# script uiAR to Double Tap to Place
You should now be able to:
We are now going to play and control the animation of the gauge. It this example the animation needs to be part of the FBX object imported.

Create -> Animation -> Animator Controller, provide a name for the controller (i.e. GaugeAnimatorController) and double click on it to open the Animation Panel.
Animation Panel, not on the three existing block (Any State, Entry, Exit), and Create State -> Empty. A New State block, link with the Entry block, will be created. If not automatically linked, select the Entry block, right click on it and Set StateMachine Default State and create the link with the New State block.
Animation Panel (not the Prefab, but the FBX imported in Unity). A new grey block with the name of the animation will appear (e.g. Scene).Any State block and Make Transition with the new grey block Scene.+ button and add a Trigger named explodeTrigger (set to false, unchecked) and a Float named AnimationSpeed to be set to 1.
1 (any value or name can be used), we will need this later.AnimationSpeed from the drop-down menu
Animator Panel click on the arrow that connect Any State with SceneInspector Panel add a new Conditions and set explodeTrigger as conditions to start the animation.
The Animator controller is now ready to be connected with a user interface. In this example we are controlling the animation using the Lean Touch asset. Specifically, we want to use a single tap on the object to start the animation (explodeTrigger), and another single tap on the object to play the animation backwards (AnimationSpeed from 1 to -1).
ARObject prefab to open the Prefab Edit viewAnimator to the gauge model inside the Prefab (not the parent Prefab) and add to the Controller the GaugeAnimatorControlleranimationExplode and add it to the gauge model inside the Prefab (not the parent Prefab). Open the script and add the following code blocks:using UnityEngine;
[RequireComponent(typeof(Animator))]
public class animationExplode : MonoBehaviour
{
Animator animator;
float speed;
bool isExploding = false;
void Start()
{
//Get Animator component
animator = GetComponent<Animator>();
speed = animator.GetFloat("AnimationSpeed");
}
The [RequireComponent(typeof(Animator))] ensure that the component Animator is added to the GameObject. The variable speed is used in this case to control the direction of the animation.
public void Explosion()
{
if (isExploding == false)
{
//Add a Tag value to the animation block, select the animation block from the Animator Controlelr and set its Tag in the Inspector
if (animator.GetCurrentAnimatorStateInfo(0).IsTag("1"))
{
speed = 1;
animator.SetFloat("AnimationSpeed", speed);
isExploding = true;
}
else
{
animator.SetTrigger("explodeTrigger");
isExploding = true;
}
}
else
{
if (animator.GetCurrentAnimatorStateInfo(0).IsTag("1"))
{
speed = -1;
animator.SetFloat("AnimationSpeed", speed);
isExploding = false;
}
else
{
speed = -1;
animator.SetFloat("AnimationSpeed", speed);
animator.SetTrigger("explodeTrigger");
isExploding = false;
}
}
}
The Explosion function is used to control the animation and it will be trigger when the ARObject is selected using a Lean Finger Tap.
void Update()
{
if (animator.GetCurrentAnimatorStateInfo(0).IsTag("1") && animator.GetCurrentAnimatorStateInfo(0).normalizedTime > 1)
{
animator.Play("Scene", -1, 1);
}
else if (animator.GetCurrentAnimatorStateInfo(0).IsTag("1") && animator.GetCurrentAnimatorStateInfo(0).normalizedTime < 0)
{
animator.Play("Scene", -1, 0);
}
}
}
The Update function contains a conditional function to control when the animation is finished (to reset its Time to 0 or 1).
Lean Selectable component of the parent ARObjectLean Selectable component, expand Show Unused Events, add a new one in OnSelected, and add drag the Gauge model inside the prefab on it and select the component animationExplode.Explosion()Lean Pinch Scale, Lean Twist Rotate Axis, Lean Drag Translate have on the Required Selectable the Prefab of the Gauge itself. You should see in the field ARObject (Lean Selectable) (or the name you gave to the Gauge)Box Collider to the parent Prefab of the Gauge. Set Size (e.g. 0.15 0.1 0.15) and Centre to fit the Gauge (e.g. y 0.1)Exit the Prefab Edit Mode and select the LeanTouch GameObject in the Hierarchy window and in the Inspector window
Lean Finger TapShow Unused Events and add a new one to the On FingerLeanTouch GameObject itself to the field and, on the No Function dropdown menu, select LeanSelectByFinger -> SelectScreenPositionYou can now Build and Run your project, or run in the simulator, to place the digital gauge in your environment.
In the same way we can control a digital object using MQTT, we can use the same library to publish MQTT messages and therefore control physical devices or other services from the AR mobile app. In this example, we will use the AR app to control a custom website.
Open the Prefab ARObject in Prefab Edit Mode
UI -> Canvas to the root, select it, and in the Inspector windowCanvas -> Render Mode to World SpacePos X = 0; Pos Y= 0.1; Pos Z = 0;width = 0.2; height = 0.2Horizontal Layout Group with Child Alignment set to Lower CenterUI -> Button - TextMeshPro to the Canvas and rename it ButtonRow, select it, and in the Inspector windowPos Z = -0.1width = 0.05 ; height = 0.05rotation X = 45Source Imagefrom Image and change colourVertical Layout Group with Child Alignment set to Middle Center and only Control Child Size selected (both Width and Height), so uncheck Child Force ExpandButtonRow and duplicate the Text (TMP)Text (TMP) to Row and in the Inspector window change the Text to Row and the FontSize to 0.01Text (TMP) to RowNumber and in the Inspector window change the Text to 1 and the FontSize to 0.02columnNumber and rename the GameObject Column to Grid and change the Text accordinglyThe structure in the Hierarchy should look like this

We now need to create a new script named mqttPublish to publish the message to the broker
using UnityEngine;
using TMPro;
public class mqttPublish : MonoBehaviour
{
public string tag_mqttManager = ""; //to be set on the Inspector panel. It must match one of the mqttManager.cs GameObject
[Header(" Case Sensitive!!")]
[Tooltip("the topic to publish !!Case Sensitive!! ")]
public string topicPublish = ""; //the topic to subscribe
public mqttManager _eventSender;
private bool _connected;
private int row = 1;
private int column = 1;
private int[] colour;
public TextMeshProUGUI rowLabel;
public TextMeshProUGUI columnLabel;
public string myName;
private string messagePublish = "";
void Awake()
{
if (GameObject.FindGameObjectsWithTag(tag_mqttManager).Length > 0)
{
_eventSender = GameObject.FindGameObjectsWithTag(tag_mqttManager)[0].gameObject.GetComponent<mqttManager>();
_eventSender.OnConnectionSucceeded += OnConnectionSucceededHandler;
}
else
{
Debug.LogError("At least one GameObject with mqttManager component and Tag == tag_mqttManager needs to be provided");
}
}
private void OnConnectionSucceededHandler(bool connected)
{
_connected = true; //control if we are connected to the MQTT Broker
}
public void rowNumber()
{
row = (++row > 8) ? 1 : row;
rowLabel.text = row.ToString();
}
public void columnNumber()
{
column = (++column > 8) ? 1 : column;
columnLabel.text = column.ToString();
}
public void Grid()
{
colour = new int[3] {
UnityEngine.Random.Range(0, 256), // Range: 0-255
UnityEngine.Random.Range(0, 256), // Range: 0-255
UnityEngine.Random.Range(0, 256) // Range: 0-255
};
messagePublish = "{\"myName\":\"" + myName + "\",\"row\":" + row + ",\"column\":" + column + ",\"colour\":\"" + colour[0] + "," + colour[1] + "," + colour[2] + "\"}";
if (!_connected) //publish if connected
return;
//if the messagePublish is null, use the one of the MQTTReceiver
if (messagePublish.Length > 0)
{
_eventSender.topicPublish = topicPublish;
_eventSender.messagePublish = messagePublish;
}
_eventSender.topicPublish = topicPublish;
_eventSender.Publish();
Debug.Log("Publish" + messagePublish);
}
}
Attach the script to the root of the prefab (e.g. ARObject)
mqttManagerTopic Publish (e.g.group/your ID/grid)RowNumber and ColumnNumber)My Name field
Before closing the Prefab Edit Mode set the behaviour of the three buttons
ButtonRow and in the Inspector, under Button, add a new event On Click. Drag in the empty field the Prefab root (e.g. ARObject with the mqttPublish script attached)mqttPublish -> rowNumbermqttPublish -> columnNumber and mqttPublish -> Grid)
Additionally, we need to set the user ID and password in the mqttManager and change the port accordingly for the MQTT service. Build and Run the project or run in the simulator.
On this GridTTQ CodePen can be use to see if the publishing is working correctly
