UI is not interactable when converted from standalone application to Zspace app

Hi,
I am converting a normal standalone application to Zspace application, so I have to convert all my canvas to world space. If I am converting all my canvas to world space some canvas is not interactable even “order in layer” is high or the canvas is placed at the top in hierarchy window the canvas which is located below is interactable but I need the canvas which is at the top is interactable.

Hi Jeykamalesh,

It wounds like you are doing the right things. Since I don’t know what is different about the problematic canvas, it’s difficult for me to speculate what might be wrong. Would it be simple for you to share an example project scene? Or perhaps just more details about the canvas that doesn’t work?

Alex S.

Thanks for your reply Alex will share the sample project

Hi Alex,
The Issue is due to “ZEventSystem”. if unity’s default “EventSystem” is added instead of “ZEventSystem” then the canvas button interaction is working fine according to the canvas sorting order. The canvas which is on the bottom and top both is interactable on adding “ZEventSystem” but we need only the top canvas(having high sorting order) only to be interactable.

Hi Jeykamalesh,

You raise a good point. Our ZGraphicRaycaster class does not yet support Canvas sortingOrder. I have gone ahead and implemented this property. I recommend replacing your ZGraphicRaycaster.cs with the code snippet below.

////////////////////////////////////////////////////////////////////////////////
//
//  Copyright (C) 2007-2020 zSpace, Inc.  All Rights Reserved.
//
////////////////////////////////////////////////////////////////////////////////

using System.Collections.Generic;

using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

using zSpace.Core.EventSystems;

namespace zSpace.Core.UI
{
    [RequireComponent(typeof(Canvas))]
    public class ZGraphicRaycaster : GraphicRaycaster
    {
        ////////////////////////////////////////////////////////////////////////
        // MonoBehaviour Callbacks
        ////////////////////////////////////////////////////////////////////////

        protected override void OnEnable()
        {
            base.OnEnable();

            if (!s_instances.Contains(this))
            {
                s_instances.Add(this);
            }
        }

        protected override void OnDisable()
        {
            base.OnDisable();

            if (s_instances.Contains(this))
            {
                s_instances.Remove(this);
            }
        }

        protected override void Start()
        {
            base.Start();

            if (this.Canvas.renderMode == RenderMode.WorldSpace &&
                this.Canvas.worldCamera == null)
            {
                Debug.LogWarning(
                    "No Event Camera found attached to associated world " +
                    "space canvas. Please make sure to assign an appropriate " +
                    "camera to your canvas to minimize performance impact " +
                    "and ensure raycasts are performed correctly.",
                    this);
            }
        }

        ////////////////////////////////////////////////////////////////////////
        // Public Properties
        ////////////////////////////////////////////////////////////////////////

        public Canvas Canvas
        {
            get
            {
                if (this._canvas == null)
                {
                    this._canvas = this.GetComponent<Canvas>();
                }

                return this._canvas;
            }
        }

        ////////////////////////////////////////////////////////////////////////
        // Public Methods
        ////////////////////////////////////////////////////////////////////////

        /// <summary>
        /// Gets a list of all enabled ZGraphicRaycaster instances in the scene.
        /// </summary>
        /// 
        /// <returns>
        /// The list of all enabled ZGraphicRaycasters instances in the scene.
        /// </returns>
        public static IList<ZGraphicRaycaster> GetRaycasters()
        {
            return s_instances;
        }

        /// <summary>
        /// Performs a raycast against all enabled ZGraphicRaycaster instances
        /// in the scene and reports the closest hit.
        /// </summary>
        /// 
        /// <param name="ray">
        /// The starting point and direction of the ray.
        /// </param>
        /// <param name="result">
        /// The raycast result corresponding to the closest hit.
        /// </param>
        /// <param name="maxDistance">
        /// The maximum distance that the hit result is allowed to be from
        /// the start of the ray.
        /// </param>
        /// <param name="layerMask">
        /// A layer mask that is used to selectively ignore graphics when 
        /// casting the ray.
        /// </param>
        /// 
        /// <returns>
        /// True if a graphic was hit. False otherwise.
        /// </returns>
        public static bool Raycast(
            Ray ray, out RaycastResult result, float maxDistance, int layerMask)
        {
            s_results.Clear();

            for (int i = 0; i < s_instances.Count; ++i)
            {
                s_instances[i].Raycast(ray, s_results, maxDistance, layerMask);
            }

            if (s_results.Count > 0)
            {
                result = s_results[0];
                return true;
            }

            result = default(RaycastResult);
            return false;
        }

        /// <summary>
        /// Performs a raycast against all enabled ZGraphicRaycaster instances
        /// in the scene and reports all hits.
        /// </summary>
        /// 
        /// <param name="ray">
        /// The starting point and direction of the ray.
        /// </param>
        /// <param name="resultAppendList">
        /// The raycast results corresponding to all hits.
        /// </param>
        /// <param name="maxDistance">
        /// The maximum distance that a hit result is allowed to be from
        /// the start of the ray.
        /// </param>
        /// <param name="layerMask">
        /// A layer mask that is used to selectively ignore graphics when 
        /// casting the ray.
        /// </param>
        /// 
        /// <returns>
        /// True if a graphic was hit. False otherwise.
        /// </returns>
        public static void RaycastAll(
            Ray ray,
            List<RaycastResult> resultAppendList,
            float maxDistance,
            int layerMask)
        {
            for (int i = 0; i < s_instances.Count; ++i)
            {
                s_instances[i].Raycast(
                    ray, resultAppendList, maxDistance, layerMask);
            }
        }

        public override void Raycast(
            PointerEventData eventData, List<RaycastResult> resultAppendList)
        {
            ZPointerEventData e = eventData as ZPointerEventData;

            if (e != null)
            {
                this.Raycast(
                    e.Pointer.PointerRay, 
                    resultAppendList, 
                    float.PositiveInfinity, 
                    ~0);
            }
            else
            {
                base.Raycast(eventData, resultAppendList);
            }
        }

        ////////////////////////////////////////////////////////////////////////
        // Private Methods
        ////////////////////////////////////////////////////////////////////////

        private void Raycast(
            Ray ray,
            List<RaycastResult> resultAppendList,
            float maxDistance, 
            int layerMask)
        {
            // Potentially reduce the maximum hit distance based on whether
            // any 2D or 3D blocking objects have been intersected.
            float distance =
                this.eventCamera.farClipPlane - this.eventCamera.nearClipPlane;

            if (this.blockingObjects == BlockingObjects.ThreeD ||
                this.blockingObjects == BlockingObjects.All)
            {
                RaycastHit hit;
                if (Physics.Raycast(ray, out hit, distance, this.m_BlockingMask))
                {
                    maxDistance = Mathf.Min(hit.distance, maxDistance);
                }
            }

            if (this.blockingObjects == BlockingObjects.TwoD ||
                this.blockingObjects == BlockingObjects.All)
            {
                RaycastHit2D hit = Physics2D.GetRayIntersection(
                    ray, distance, this.m_BlockingMask);

                if (hit.collider != null)
                {
                    maxDistance = Mathf.Min(
                        hit.fraction * distance, maxDistance);
                }
            }

            // Retrieve the list of graphics associated with the canvas.
            IList<Graphic> graphics = 
                GraphicRegistry.GetGraphicsForCanvas(this.Canvas);

            // Iterate through each of graphics and perform hit tests.
            for (int i = 0; i < graphics.Count; ++i)
            {
                Graphic graphic = graphics[i];

                // Skip the graphic if it's not in the layer mask.
                if (((1 << graphic.gameObject.layer) & layerMask) == 0)
                {
                    continue;
                }

                // Perform a raycast against the graphic.
                RaycastResult result;
                if (this.Raycast(ray, graphic, out result, maxDistance))
                {
                    resultAppendList.Add(result);
                }
            }

            // Sort the results by depth.
            resultAppendList.Sort((x, y) => y.depth.CompareTo(x.depth));
            resultAppendList.Sort((x, y) => y.sortingOrder.CompareTo(x.sortingOrder));
        }

        private bool Raycast(
            Ray ray,
            Graphic graphic,
            out RaycastResult result,
            float maxDistance)
        {
            result = default(RaycastResult);

            // Skip graphics that aren't raycast targets.
            if (!graphic.raycastTarget)
            {
                return false;
            }

            // Skip graphics that aren't being drawn.
            if (graphic.depth == -1)
            {
                return false;
            }

            // Skip graphics that are reversed if the ignore reversed
            // graphics inspector field is enabled.
            if (this.ignoreReversedGraphics &&
                Vector3.Dot(ray.direction, -graphic.transform.forward) > 0)
            {
                return false;
            }

            // Create a plane based on the graphic's transform.
            Plane plane = new Plane(
                -graphic.transform.forward, graphic.transform.position);

            // Skip graphics that failed the plane intersection test.
            float distance = 0.0f;
            if (!plane.Raycast(ray, out distance))
            {
                return false;
            }

            // Skip graphics that are further away than the max distance.
            if (distance > maxDistance)
            {
                return false;
            }

            Vector3 worldPosition = 
                ray.origin + (ray.direction * distance);

            Vector3 screenPosition =
                this.eventCamera.WorldToScreenPoint(worldPosition);            

            // Skip graphics that have failed the bounds test.
            if (!RectTransformUtility.RectangleContainsScreenPoint(
                    graphic.rectTransform, screenPosition, this.eventCamera))
            {
                return false;
            }

            // Skip graphics that fail the raycast test.
            // NOTE: This is necessary to ensure that raycasts against
            //       masked out areas of the graphic are correctly ignored.
            if (!graphic.Raycast(screenPosition, this.eventCamera))
            {
                return false;
            }

            result.depth = graphic.depth;
            result.distance = distance;
            result.worldPosition = worldPosition;
            result.worldNormal = plane.normal;
            result.screenPosition = screenPosition;
            result.gameObject = graphic.gameObject;
            result.sortingLayer = graphic.canvas.sortingLayerID;
            result.sortingOrder = graphic.canvas.sortingOrder;
            result.module = this;

            return true;
        }

        ////////////////////////////////////////////////////////////////////////
        // Private Members
        ////////////////////////////////////////////////////////////////////////

        private static readonly List<ZGraphicRaycaster> s_instances =
            new List<ZGraphicRaycaster>();

        private static readonly List<RaycastResult> s_results =
            new List<RaycastResult>();

        private Canvas _canvas = null;
    }
}

Please let us know if this works as you expect. We still need to vet the changes some more, but I expect that we will release an updated version of zCore in the near future with this fix.

(If you are using sortingLayers, more work is needed to sort them correctly. I will update when this is implemented as well.)

Regards,
Alex S.

1 Like

Hi Alex,
Thanks a lot!, works for me.

Hi all,

I am writing to let everyone interested know that a new zCore version 6.0.2 has been released. This new version now supports canvas sortingOrder as well as sortingLayer.

zCore6.0.2 Download Link

Alex S.

1 Like