Search code examples
c#unity-game-enginegoogle-vr

Extending GvrLaserPointerImpl to get Raycast target info for Google Daydream


so I'm using the following script which uses Google's GvrLaserPointerImpl (Google Daydream) to teleport around my scene:

public class PlayerTeleport : MonoBehaviour {

    void Update() {
        if (GvrController.AppButtonUp) {
            GvrLaserPointerImpl laserPointerImpl = (GvrLaserPointerImpl)GvrPointerManager.Pointer;
            if (laserPointerImpl.IsPointerIntersecting) {
                transform.position = new Vector3(laserPointerImpl.PointerIntersection.x, transform.position.y, laserPointerImpl.PointerIntersection.z);
            }
        }
    }
}

For my game, I need to know what the GvrLaserPointerImpl raycast is hitting. If you look at the GvrLaserPointerImpl script OnPointerEnter arguments you can see that there is a targetObject but it not being publicly exposed or handled in any way:

/// Implementation of GvrBasePointer for a laser pointer visual.
/// This script should be attached to the controller object.
/// The laser visual is important to help users locate their cursor
/// when its not directly in their field of view.
public class GvrLaserPointerImpl : GvrBasePointer {
  /// Small offset to prevent z-fighting of the reticle (meters).
  private const float Z_OFFSET_EPSILON = 0.1f;

  /// Size of the reticle in meters as seen from 1 meter.
  private const float RETICLE_SIZE = 0.01f;

  public Camera MainCamera { private get; set; }

  public Color LaserColor { private get; set; }

  public LineRenderer LaserLineRenderer { get; set; }

  public GameObject Reticle { get; set; }

  public float MaxLaserDistance { private get; set; }

  public float MaxReticleDistance { private get; set; }

  // Properties exposed for testing purposes.
  public Vector3 PointerIntersection { get; private set; }

  public bool IsPointerIntersecting { get; private set; }

  public Ray PointerIntersectionRay { get; private set; }

  public override float MaxPointerDistance {
    get {
#if UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR)
      return MaxReticleDistance;
#else
      return 0;
#endif  // UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR)
    }
  }

  public GvrLaserPointerImpl() {
    MaxLaserDistance = 0.75f;
    MaxReticleDistance = 2.5f;
  }

#if !(UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR))
  public override void OnStart() {
    // Don't call base.Start() so that this pointer isn't activated when
    // the editor doesn't have UNITY_HAS_GOOGLE_VR.
  }
#endif  // UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR)

  public override void OnInputModuleEnabled() {
#if UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR)
    if (LaserLineRenderer != null) {
      LaserLineRenderer.enabled = true;
    }
#endif  // UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR)
  }

  public override void OnInputModuleDisabled() {
#if UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR)
    if (LaserLineRenderer != null) {
      LaserLineRenderer.enabled = false;
    }
#endif  // UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR)
  }

  public override void OnPointerEnter(GameObject targetObject, Vector3 intersectionPosition,
      Ray intersectionRay, bool isInteractive) {
#if UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR)
    PointerIntersection = intersectionPosition;
    PointerIntersectionRay = intersectionRay;
    IsPointerIntersecting = true;
#endif  // UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR)
  }

  public override void OnPointerHover(GameObject targetObject, Vector3 intersectionPosition,
      Ray intersectionRay, bool isInteractive) {
#if UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR)
    PointerIntersection = intersectionPosition;
    PointerIntersectionRay = intersectionRay;
#endif  // UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR)
  }

  public override void OnPointerExit(GameObject targetObject) {
#if UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR)
    PointerIntersection = Vector3.zero;
    PointerIntersectionRay = new Ray();
    IsPointerIntersecting = false;
#endif  // UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR)
  }

  public override void OnPointerClickDown() {
    // User has performed a click on the target.  In a derived class, you could
    // handle visual feedback such as laser or cursor color changes here.
  }

  public override void OnPointerClickUp() {
    // User has released a click from the target.  In a derived class, you could
    // handle visual feedback such as laser or cursor color changes here.
  }

  public override void GetPointerRadius(out float enterRadius, out float exitRadius) {
#if UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR)
    if (Reticle != null) {
      float reticleScale = Reticle.transform.localScale.x;

      // Fixed size for enter radius to avoid flickering.
      // This will cause some slight variability based on the distance of the object
      // from the camera, and is optimized for the average case.
      enterRadius = RETICLE_SIZE * 0.5f;

      // Dynamic size for exit radius.
      // Always correct because we know the intersection point of the object and can
      // therefore use the correct radius based on the object's distance from the camera.
      exitRadius = reticleScale;
    } else {
      enterRadius = 0.0f;
      exitRadius = 0.0f;
    }
#else
    enterRadius = 0.0f;
    exitRadius = 0.0f;
#endif  // UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR)
  }

#if UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR)
  public void OnUpdate() {
    // Set the reticle's position and scale
    if (Reticle != null) {
      if (IsPointerIntersecting) {
        Vector3 difference = PointerIntersection - PointerIntersectionRay.origin;
        Vector3 clampedDifference = Vector3.ClampMagnitude(difference, MaxReticleDistance);
        Vector3 clampedPosition = PointerIntersectionRay.origin + clampedDifference;
        Reticle.transform.position = clampedPosition;
      } else {
        Reticle.transform.localPosition = new Vector3(0, 0, MaxReticleDistance);
      }

      float reticleDistanceFromCamera =
        (Reticle.transform.position - MainCamera.transform.position).magnitude;
      float scale = RETICLE_SIZE * reticleDistanceFromCamera;
      Reticle.transform.localScale = new Vector3(scale, scale, scale);
    }

    if (LaserLineRenderer == null) {
      Debug.LogWarning("Line renderer is null, returning");
      return;
    }

    // Set the line renderer positions.
    Vector3 lineEndPoint;
    if (IsPointerIntersecting) {
      Vector3 laserDiff = PointerIntersection - base.PointerTransform.position;
      float intersectionDistance = laserDiff.magnitude;
      Vector3 direction = laserDiff.normalized;
      float laserDistance = intersectionDistance > MaxLaserDistance ? MaxLaserDistance : intersectionDistance;
      lineEndPoint = base.PointerTransform.position + (direction * laserDistance);
    } else {
      lineEndPoint = base.PointerTransform.position + (base.PointerTransform.forward * MaxLaserDistance);
    }
    LaserLineRenderer.SetPositions(new Vector3[] {base.PointerTransform.position, lineEndPoint});

    // Adjust transparency
    float alpha = GvrControllerVisual.AlphaValue;
    LaserLineRenderer.SetColors(Color.Lerp(Color.clear, LaserColor, alpha), Color.clear);
  }


#endif  // UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR)
}

I want to extend the GvrLaserPointerImpl class and override the OnPointerEnter method to publicly expose the targetObject so I can do a check in my PlayerTeleport script to do a check to see if the raycast is hitting a targetObject with a tag. I'm not sure what to put in the overrided OnPointerEnter method to access to targetObject class though. GvrLaserPointerImpl inherits from the abstract GvrBasePointer class so there is nothing in the abstract class which tells me how I could do this.

Any help would be appreciated!


Solution

  • The typical design pattern when using Unity's Event System for interaction would be to place a script on the target object that implements an IEventSystemHandler. Example:

    public class ClickResponder : MonoBehaviour, IPointerClickHandler {
        public void OnPointerClick(PointerEventData eventData) {
            Debug.Log("Clicked!");
        }
    }
    

    If that doesn't work for your use case, you may also be able to access the object currently being pointed at via this API: https://docs.unity3d.com/ScriptReference/EventSystems.EventSystem-currentSelectedGameObject.html

    If neither of those approaches work for your needs, you can expose the target Object in GvrLaserPointerImpl like this:

    public GameObject TargetObject { get; private set; }
    
    public override void OnPointerEnter(GameObject targetObject, Vector3 intersectionPosition,
      Ray intersectionRay, bool isInteractive) {
    #if UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR)
        TargetObject = targetObject;
        PointerIntersection = intersectionPosition;
        PointerIntersectionRay = intersectionRay;
    IsPointerIntersecting = true;
    #endif  // UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR)
    }
    
    public override void OnPointerExit(GameObject targetObject) {
    #if UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR)
        TargetObject = null;
        PointerIntersection = Vector3.zero;
        PointerIntersectionRay = new Ray();
        IsPointerIntersecting = false;
    #endif  // UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR)
    }
    

    I hope that helps!