Object interaction with touch

Hello! I’m working on project, which detects some images. I made a quite simple script, which allows me to scan 5 images and then place 5 corresponding 3D prefabs according to every image. Also there is some text which shows name of scanned image, if this process was succesful or limited tracking if we not scan apprpriate images. Basically script looks like this: `

using UnityEngine;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;
using System.Collections.Generic;
using TMPro;

public class MultipleImageTracker : MonoBehaviour
{
    [System.Serializable]
    public struct TrackableImage
    {
        public string imageName;
        public GameObject prefabToInstantiate;
    }

    public List<TrackableImage> trackableImages;
    public GameObject statusTextPrefab;
    public float statusDisplayTime = 5f;
    public Canvas arCanvas;

    private ARTrackedImageManager trackedImageManager;
    private Dictionary<string, GameObject> spawnedPrefabs = new Dictionary<string, GameObject>();
    private Dictionary<string, GameObject> statusTexts = new Dictionary<string, GameObject>();
    private Dictionary<string, TrackingState> lastTrackingStates = new Dictionary<string, TrackingState>();
    private GameObject currentActiveStatusText;

    private void Awake()
    {
        trackedImageManager = FindObjectOfType<ARTrackedImageManager>();
        if (arCanvas == null)
        {
            Debug.LogError("AR Canvas is not assigned!");
        }
    }

    private void OnEnable()
    {
        trackedImageManager.trackedImagesChanged += OnTrackedImagesChanged;
    }

    private void OnDisable()
    {
        trackedImageManager.trackedImagesChanged -= OnTrackedImagesChanged;
    }

    private void OnTrackedImagesChanged(ARTrackedImagesChangedEventArgs eventArgs)
    {
        foreach (var trackedImage in eventArgs.added)
        {
            UpdateImage(trackedImage);
        }

        foreach (var trackedImage in eventArgs.updated)
        {
            UpdateImage(trackedImage);
        }

        foreach (var trackedImage in eventArgs.removed)
        {
            string imageName = trackedImage.referenceImage.name;
            if (spawnedPrefabs.ContainsKey(imageName))
            {
                Destroy(spawnedPrefabs[imageName]);
                spawnedPrefabs.Remove(imageName);
            }
            if (statusTexts.ContainsKey(imageName))
            {
                Destroy(statusTexts[imageName]);
                statusTexts.Remove(imageName);
            }
            lastTrackingStates.Remove(imageName);
        }
    }

    private void UpdateImage(ARTrackedImage trackedImage)
    {
        string imageName = trackedImage.referenceImage.name;
        Vector3 position = trackedImage.transform.position;
        TrackableImage trackableImage = trackableImages.Find(x => x.imageName == imageName);
        if (trackableImage.prefabToInstantiate != null)
        {
            UpdateSpawnedPrefab(trackableImage, imageName, position, trackedImage.trackingState);
            UpdateStatusText(imageName, trackedImage.trackingState);
        }
    }

    private void UpdateSpawnedPrefab(TrackableImage trackableImage, string imageName, Vector3 position, TrackingState trackingState)
    {
        if (spawnedPrefabs.ContainsKey(imageName))
        {
            spawnedPrefabs[imageName].transform.position = position;
            spawnedPrefabs[imageName].SetActive(trackingState == TrackingState.Tracking);
        }
        else
        {
            GameObject prefabInstance = Instantiate(trackableImage.prefabToInstantiate, position, Quaternion.identity);
            prefabInstance.AddComponent<TouchInputHandler>();
            spawnedPrefabs[imageName] = prefabInstance;
        }
    }

    private void UpdateStatusText(string imageName, TrackingState currentTrackingState)
    {
        if (!lastTrackingStates.ContainsKey(imageName) || lastTrackingStates[imageName] != currentTrackingState)
        {
            ShowStatusText(imageName, currentTrackingState);
            lastTrackingStates[imageName] = currentTrackingState;
        }
    }

    private void ShowStatusText(string imageName, TrackingState trackingState)
    {
        if (currentActiveStatusText != null)
        {
            StopAllCoroutines();
            currentActiveStatusText.SetActive(false);
        }

        GameObject statusTextObject;
        if (!statusTexts.ContainsKey(imageName))
        {
            statusTextObject = Instantiate(statusTextPrefab, arCanvas.transform);
            statusTexts[imageName] = statusTextObject;
            RectTransform rectTransform = statusTextObject.GetComponent<RectTransform>();
            rectTransform.anchorMin = new Vector2(1, 1);
            rectTransform.anchorMax = new Vector2(1, 1);
            rectTransform.anchoredPosition = new Vector2(-200, -200);
            rectTransform.sizeDelta = new Vector2(200, 50);
        }
        else
        {
            statusTextObject = statusTexts[imageName];
        }

        TextMeshProUGUI tmp = statusTextObject.GetComponent<TextMeshProUGUI>();
        if (tmp != null)
        {
            switch (trackingState)
            {
                case TrackingState.Tracking:
                    tmp.color = Color.green;
                    tmp.text = $"Recognized: {imageName}";
                    break;
                case TrackingState.Limited:
                    tmp.color = Color.yellow;
                    tmp.text = "Tracking limited...";
                    break;
                case TrackingState.None:
                    tmp.color = Color.red;
                    tmp.text = "Lost tracking";
                    break;
            }
        }

        statusTextObject.SetActive(true);
        currentActiveStatusText = statusTextObject;
        StartCoroutine(HideStatusText(imageName));
    }

    private System.Collections.IEnumerator HideStatusText(string imageName)
    {
        yield return new WaitForSeconds(statusDisplayTime);
        if (statusTexts.ContainsKey(imageName) && statusTexts[imageName] == currentActiveStatusText)
        {
            statusTexts[imageName].SetActive(false);
            currentActiveStatusText = null;
        }
    }
}

Now I want to add some interactions with my prefabs that appears according to images. If I touch prefab, it should scale up two times and go back to normal size, like simple animation. If I touch again that prefab, it stops animating and returns to started scale. I tried something like this:

using UnityEngine;

public class TouchInputHandler : MonoBehaviour
{
    private bool isScaled = false;

    void Update()
    {
        if (Input.touchCount > 0 && Input.touches[0].phase == TouchPhase.Began)
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.touches[0].position);
            RaycastHit[] hits = Physics.RaycastAll(ray);

            Debug.Log("Touch detected");

            foreach (RaycastHit hit in hits)
            {
                Debug.Log($"Raycast hit: {hit.transform.name}");

                if (hit.transform == transform)
                {
                    Debug.Log($"Object {hit.transform.name} was touched");

                    if (!isScaled)
                    {
                        StartCoroutine(ScaleObject(transform, 2f, 0.5f));
                    }
                    else
                    {
                        StartCoroutine(ScaleObject(transform, 1f, 0.5f));
                    }
                    isScaled = !isScaled;
                }
            }
        }
    }

    private System.Collections.IEnumerator ScaleObject(Transform obj, float targetScale, float duration)
    {
        Vector3 originalScale = obj.localScale;
        Vector3 destinationScale = originalScale * targetScale;

        float currentTime = 0.0f;

        while (currentTime <= duration)
        {
            obj.localScale = Vector3.Lerp(originalScale, destinationScale, currentTime / duration);
            currentTime += Time.deltaTime;
            yield return null;
        }
        obj.localScale = destinationScale;
    }
}

But nothing happens. All my prefabs have Box Collider, I added AR Raycast Manager to my scene and check I guess everything, but nothing works.
Can anybody help me to fix this problem, that my images will do animation?
Updated code for TouchInputHandler (renamed to TouchScaler):

using UnityEngine;
using System.Collections;
using UnityEngine.XR.ARFoundation;
using System.Collections.Generic;

public class TouchScaler : MonoBehaviour
{
    private Vector3 originalScale;
    public float scaleFactor = 2.0f;
    public float scaleSpeed = 2.0f;
    private bool isAnimating = false;
    private Coroutine scaleCoroutine;
    private ARRaycastManager arRaycastManager;
    // public float overlapSphereRadius = 0.1f; // Increased default radius

    private void Start()
    {
        originalScale = transform.localScale;
        Debug.Log($"TouchScaler: Started on {gameObject.name}. Original scale: {originalScale}");
        arRaycastManager = FindObjectOfType<ARRaycastManager>();
        if (arRaycastManager == null)
        {
            Debug.LogError("TouchScaler: ARRaycastManager not found in the scene.");
        }

        AdjustColliderSize();
    }

    private void AdjustColliderSize()
    {
        BoxCollider boxCollider = GetComponent<BoxCollider>();
        if (boxCollider != null)
        {
            Renderer renderer = GetComponent<Renderer>();
            if (renderer != null)
            {
                boxCollider.size = renderer.bounds.size;
                boxCollider.center = renderer.bounds.center - transform.position;
                Debug.Log($"TouchScaler: Adjusted collider size to {boxCollider.size} for {gameObject.name}");
            }
        }
    }

  private void Update()
  {
      if (Input.touchCount > 0 && Input.GetTouch(0).phase == TouchPhase.Began)
      {
          Debug.Log($"TouchScaler: Touch detected on {gameObject.name}");
          
          Vector3 touchPosition = Input.GetTouch(0).position;
          HandleInteraction(touchPosition);
      }
      
      // if (Input.GetMouseButtonDown(0))
      // {
      //     Debug.Log($"TouchScaler: Mouse click detected on {gameObject.name}");
      //     Vector3 mousePosition = Input.mousePosition;
      //     HandleInteraction(mousePosition);
      // }
  }
  
  private void HandleInteraction(Vector3 screenPosition)
  {
      Ray ray = Camera.main.ScreenPointToRay(screenPosition);
      RaycastHit hit;
      
      if (Physics.Raycast(ray, out hit))
      {
          Debug.Log($"TouchScaler: Raycast hit {hit.collider.gameObject.name}");
          if (hit.collider.gameObject == this.gameObject)
          {
              Debug.Log($"TouchScaler: Raycast hit this object: {gameObject.name}");
              ToggleScaling();
          }
      }
      else
      {
          Debug.Log($"TouchScaler: No hit detected for {gameObject.name}");
      }
  }
  
  private void ToggleScaling()
  {
      Debug.Log($"TouchScaler: ToggleScaling called for {gameObject.name}");
      if (isAnimating)
      {
          StopScaling();
      }
      else
      {
          StartScaling();
      }
  }
  
  private void StartScaling()
  {
      Debug.Log($"TouchScaler: StartScaling called for {gameObject.name}");
      if (scaleCoroutine != null)
      {
          StopCoroutine(scaleCoroutine);
      }
      scaleCoroutine = StartCoroutine(ScaleAnimation());
      isAnimating = true;
  }
  
  private void StopScaling()
  {
      Debug.Log($"TouchScaler: StopScaling called for {gameObject.name}");
      if (scaleCoroutine != null)
      {
          StopCoroutine(scaleCoroutine);
      }
      StartCoroutine(ResetScale());
      isAnimating = false;
  }
  
  private IEnumerator ScaleAnimation()
  {
      Debug.Log($"TouchScaler: ScaleAnimation started for {gameObject.name}");
      while (true)
      {
          yield return ScaleObject(originalScale * scaleFactor);
          yield return ScaleObject(originalScale);
      }
  }
  
  private IEnumerator ScaleObject(Vector3 targetScale)
  {
      Debug.Log($"TouchScaler: ScaleObject called for {gameObject.name}. Target scale: {targetScale}");
      while (Vector3.Distance(transform.localScale, targetScale) > 0.01f)
      {
          transform.localScale = Vector3.Lerp(transform.localScale, targetScale, scaleSpeed * Time.deltaTime);
          yield return null;
      }
      transform.localScale = targetScale;
      Debug.Log($"TouchScaler: Scaling completed for {gameObject.name}. Current scale: {transform.localScale}");
  }

  private IEnumerator ResetScale()
  {
      Debug.Log($"TouchScaler: ResetScale called for {gameObject.name}");
      yield return ScaleObject(originalScale);
  }

}

So I made some improvements to code, but now the problem is that I can’t stop animation, cause of no hit detected and I even can’t start animation if I go back to the same image (the animation only works for first time for any image and then everything breaks). Here are my logs:

Sounds like you wrote a bug… and that means… time to start debugging!

By debugging you can find out exactly what your program is doing so you can fix it.

Use the above techniques to get the information you need in order to reason about what the problem is.

You can also use Debug.Log(...); statements to find out if any of your code is even running. Don’t assume it is.

Once you understand what the problem is, you may begin to reason about a solution to the problem.

UI stuff like this can be VERY fiddly to get right. You generally need to create one feature at a time and iterate, sometimes taking a step aside and iterating without the previous functionality in place just to keep a clean slate. Work in small steps and use source control to capture good proven work as you move forward.

Here’s a cool video about how to work in small incremental steps:

Imphenzia: How Did I Learn To Make Games:

Yeah, I tried to create features step by step and to this point everything worked fine. I meant “but nothing happens” that object is not scaling. As you see, in my code are some Debug.Log and I can say that debug with Raycast hit is not working and that’s the problem. All necessary features I added, in my opinion. Thank you for providing me video and some tips, I appreciate it! However, unfortunately, I still can’t find solution.

You have to keep debugging. You’re playing detective. Who killed the raycast? :slight_smile:

For instance, when you scale it up, how big is the BoxCollider? Is it so big that your raycast starts inside it? Raycasts starting inside colliders will NOT hit them.

Also what are the values of other controlling variables like isScaled ? Are they correct?

Thanks for your advice! I tried to rework my approach and updated question with new information. Unfortunately, this feature still not working correct. I added appropriate logs.