Google VR Demo光标系统分析

因为GVRDemo的光标系统就是用的EventSystem,所以在讲GVRDemo的光标的时候,必须要先对Unity3D的EventSystem系统有所了解。
可以参考http://blog.csdn.net/yupu56/article/details/53010868

了解了怎么使用EventSystem后,接下来简单分析下GVRDemo的光标系统怎么实现的。

关键是从哪里开始入手分析,看一个demo或一大块代码的时候,总是有这个疑问。我一般就从现象入手分析。对于GVRDemo光标系统主要体现在两块。
1.当光标对准立方体的时候,会变成蓝色。
2.当光标对准菜单的时候,菜单会变绿色。

我主要分析立方体的那个,菜单的原理是一样的。
设计到的文件有:
在Main Camera孩子中有GvrReticlePointer:负责画出光标,也就是正常是个小圆点,如果落到一个object上面这个圆的半径变大。
GvrReticlePointerImpl:在GvrReticlePointer文件中被用到了。


在Main Camera里面有GvrPointerPhysicsRaycaster:主要实现了Raycast方法,实现了射线投射,会配合EventSystem使用。它继承方法GvrBasePointerRaycaster类,然后GvrBasePointerRaycaster继承于BaseRaycaster,BaseRaycaster是EventSystem中实现射线投射的基类。
在GvrBasePointerRaycaster中会根据不同的模式产生不同的射线

/// Calculates the ray to use for raycasting based on
  /// the selected raycast mode.
  protected Ray GetRay() {
    if (!IsPointerAvailable()) {
      Debug.LogError("Calling GetRay when the pointer isn't available.");
      lastRay = new Ray();
      return lastRay;
    }

    Transform pointerTransform = GvrPointerManager.Pointer.PointerTransform;

    switch (raycastMode) {
      case RaycastMode.Camera:
        Vector3 rayPointerStart = pointerTransform.position;
        Vector3 rayPointerEnd = rayPointerStart + (pointerTransform.forward * MaxPointerDistance);

        Vector3 cameraLocation = Camera.main.transform.position;
        Vector3 finalRayDirection = rayPointerEnd - cameraLocation;
        finalRayDirection.Normalize();

        Vector3 finalRayStart = cameraLocation + (finalRayDirection * Camera.main.nearClipPlane);

        lastRay = new Ray(finalRayStart, finalRayDirection);
        break;
      case RaycastMode.Direct:
        lastRay = new Ray(pointerTransform.position, pointerTransform.forward);
        break;
      default:
        lastRay = new Ray();
        break;
    }

    return lastRay;
  }

其中的GvrPointerManager.Pointer是在哪赋值的呢?
答案就在GvrReticlePointer类中:

  public void SetAsMainPointer() {
    GvrPointerManager.Pointer = reticlePointerImpl;
  }

reticlePointerImpl的类型是如下类,关于它的介绍如下:

/// Draws a circular reticle in front of any object that the user points at.
/// The circle dilates if the object is clickable.
public class GvrReticlePointerImpl : GvrBasePointer {

大概意思就是它就是画一个光标,当物体被点击的时候,光标变大。

google为什么不用默认的射线投射,很明显因为默认的射线投射是需要有光标点的,用光标点来实现物体的选择,unity默认是没有这个的。
因为其他博客中对于射线的一个说明

系统实现的射线投射类组件有PhysicsRaycaster, Physics2DRaycaster, GraphicRaycaster。这个模块也是可以自己继承BaseRaycaster实现个性化定制。
总的来说,EventSystem负责管理,BaseInputModule负责输入,BaseRaycaster负责确定目标对象,目标对象负责接收事件并处理,然后一个完整的事件系统就有了。


在GVREventSystem模块下面有GvrPointerInputModuleGvrPointerManager
其中GvrPointerManager主要是注册光标的,它里面有方法

  /// GvrBasePointer calls this when it is created.
  /// If a pointer hasn't already been assigned, it
  /// will assign the newly created one by default.
  ///
  /// This simplifies the common case of having only one
  /// GvrBasePointer so is can be automatically hooked up
  /// to the manager.  If multiple GvrGazePointers are in
  /// the scene, the app has to take responsibility for
  /// setting which one is active.
  public static void OnPointerCreated(GvrBasePointer createdPointer) {
    if (instance != null && GvrPointerManager.Pointer == null) {
      GvrPointerManager.Pointer = createdPointer;
    }
  }

会被GvrBasePointer里的方法调用

  public virtual void OnStart() {
    GvrPointerManager.OnPointerCreated(this);
  }

OnStart方法会被GvrReticlePointer里的方法调用如下:

void Start() {
    reticlePointerImpl.OnStart();
    reticlePointerImpl.MaterialComp = gameObject.GetComponent<Renderer>().material;
    UpdateReticleProperties(); 
    CreateReticleVertices();
  }

GvrPointerInputModule
里面重要的方法是

  public override void Process() {
    UpdateImplProperties();
    Impl.Process();
  }

这个方法什么时候被执行?

当我们在场景中创建任一UI对象后,Hierarchy面板中都可以看到系统自动创建了对象EventSystem,可以看到该对象下有三个组件:EventSystem、StandaloneInputModule、TouchInputModule,后面两个组件都继承自BaseInputModule。
EventSystem组件主要负责处理输入、射线投射以及发送事件。一个场景中只能有一个EventSystem组件,并且需要BaseInputModule类型组件的协助才能工作。EventSystem在一开始的时候会把自己所属对象下的BaseInputModule类型组件加到一个内部列表,并且在每个Update周期通过接口UpdateModules接口调用这些基本输入模块的UpdateModule接口,然后BaseInputModule会在UpdateModule接口中将自己的状态修改成’Updated’,之后BaseInputModule的Process接口才会被调用。
BaseInputModule是一个基类模块,负责发送输入事件(点击、拖拽、选中等)到具体对象。EventSystem下的所有输入模块都必须继承自BaseInputModule组件。StandaloneInputModule和TouchInputModule组件是系统提供的标准输入模块和触摸输入模块,我们可以通过继承BaseInputModule实现自己的输入模块。

然后调用GvrPointerInputModuleImpl中的方法:

public void Process() {
    if (Pointer == null) {
      return;
    }

    // Save the previous Game Object
    GameObject previousObject = GetCurrentGameObject();

    CastRay();
    UpdateCurrentObject(previousObject);
    UpdatePointer(previousObject);

   ...
    }

里面剩下了三个重要的方法:
CastRay

  private void CastRay() {
    if (Pointer == null || Pointer.PointerTransform == null) {
      return;
    }
    Vector2 currentPose = GvrMathHelpers.NormalizedCartesianToSpherical(Pointer.PointerTransform.forward);

    if (CurrentEventData == null) {
      CurrentEventData = new PointerEventData(ModuleController.eventSystem);
      lastPose = currentPose;
    }

    // Store the previous raycast result.
    RaycastResult previousRaycastResult = CurrentEventData.pointerCurrentRaycast;

    // The initial cast must use the enter radius.
    if (Pointer != null) {
      Pointer.ShouldUseExitRadiusForRaycast = false;
    }

    // Cast a ray into the scene
    CurrentEventData.Reset();
    // Set the position to the center of the camera.
    // This is only necessary if using the built-in Unity raycasters.
    RaycastResult raycastResult;
    CurrentEventData.position = GvrMathHelpers.GetViewportCenter();
    bool isPointerActiveAndAvailable = IsPointerActiveAndAvailable();
    if (isPointerActiveAndAvailable) {
      ModuleController.eventSystem.RaycastAll(CurrentEventData, ModuleController.RaycastResultCache);
      raycastResult = ModuleController.FindFirstRaycast(ModuleController.RaycastResultCache);
    } else {
      raycastResult = new RaycastResult();
      raycastResult.Clear();
    }

    // If we were already pointing at an object we must check that object against the exit radius
    // to make sure we are no longer pointing at it to prevent flicker.
    if (previousRaycastResult.gameObject != null
        && raycastResult.gameObject != previousRaycastResult.gameObject
        && isPointerActiveAndAvailable) {
      if (Pointer != null) {
        Pointer.ShouldUseExitRadiusForRaycast = true;
      }
      ModuleController.RaycastResultCache.Clear();
      ModuleController.eventSystem.RaycastAll(CurrentEventData, ModuleController.RaycastResultCache);
      RaycastResult firstResult = ModuleController.FindFirstRaycast(ModuleController.RaycastResultCache);
      if (firstResult.gameObject == previousRaycastResult.gameObject) {
        raycastResult = firstResult;
      }
    }

    if (raycastResult.gameObject != null && raycastResult.worldPosition == Vector3.zero) {
      raycastResult.worldPosition =
        GvrMathHelpers.GetIntersectionPosition(CurrentEventData.enterEventCamera, raycastResult);
    }

    CurrentEventData.pointerCurrentRaycast = raycastResult;

    // Find the real screen position associated with the raycast
    // Based on the results of the hit and the state of the pointerData.
    if (raycastResult.gameObject != null) {
      CurrentEventData.position = raycastResult.screenPosition;
    } else {
      Transform pointerTransform = Pointer.PointerTransform;
      float maxPointerDistance = Pointer.MaxPointerDistance;
      Vector3 pointerPos = pointerTransform.position + (pointerTransform.forward * maxPointerDistance);
      if (CurrentEventData.pressEventCamera != null) {
        CurrentEventData.position = CurrentEventData.pressEventCamera.WorldToScreenPoint(pointerPos);
      } else if (Camera.main != null) {
        CurrentEventData.position = Camera.main.WorldToScreenPoint(pointerPos);
      }
    }

    ModuleController.RaycastResultCache.Clear();
    CurrentEventData.delta = currentPose - lastPose;
    lastPose = currentPose;

    // Check to make sure the Raycaster being used is a GvrRaycaster.
    if (raycastResult.module != null
        && !(raycastResult.module is GvrPointerGraphicRaycaster)
        && !(raycastResult.module is GvrPointerPhysicsRaycaster)) {
      Debug.LogWarning("Using Raycaster (Raycaster: " + raycastResult.module.GetType() +
        ", Object: " + raycastResult.module.name + "). It is recommended to use " +
        "GvrPointerPhysicsRaycaster or GvrPointerGrahpicRaycaster with GvrPointerInputModule.");
    }
  }

其中ModuleController.eventSystem.RaycastAll(CurrentEventData, ModuleController.RaycastResultCache);

官方解释:Raycast into the scene using all configured BaseRaycasters.

意思应该是它会调用已经注册的所有BaseRaycaster的Raycast方法。
GVRDemo中,有两个一个GvrPointerPhysicsRaycaster(是给3d物体用的),另外一个是Floor Canves下面的GvrPointerGraphicRaycaster(是给UI系统用的)

UpdateCurrentObject

private void UpdateCurrentObject(GameObject previousObject) {
if (Pointer == null || CurrentEventData == null) {
      return;
    }
    // Send enter events and update the highlight.
    GameObject currentObject = GetCurrentGameObject(); // Get the pointer target
    HandlePointerExitAndEnter(CurrentEventData, currentObject);

    // Update the current selection, or clear if it is no longer the current object.
    var selected = EventExecutor.GetEventHandler<ISelectHandler>(currentObject);
    if (selected == ModuleController.eventSystem.currentSelectedGameObject) {
      EventExecutor.Execute(ModuleController.eventSystem.currentSelectedGameObject, ModuleController.GetBaseEventData(),
        ExecuteEvents.updateSelectedHandler);
    } else {
      ModuleController.eventSystem.SetSelectedGameObject(null, CurrentEventData);
    }

    // Execute hover event.
    if (currentObject != null && currentObject == previousObject) {
      EventExecutor.ExecuteHierarchy(currentObject, CurrentEventData, GvrExecuteEventsExtension.pointerHoverHandler);
    }
  }

只保留了关键代码,这段代码会触发击中object的:
onPointerEnter
onPointExit
onPointerHover
onSelect

UpdatePointer

private void UpdatePointer(GameObject previousObject) {
    if (Pointer == null || CurrentEventData == null) {
      return;
    }

    GameObject currentObject = GetCurrentGameObject(); // Get the pointer target

    bool isInteractive = CurrentEventData.pointerPress != null ||
                         EventExecutor.GetEventHandler<IPointerClickHandler>(currentObject) != null ||
                         EventExecutor.GetEventHandler<IDragHandler>(currentObject) != null;

    if (isPointerHovering && currentObject != null && currentObject == previousObject) {
      Pointer.OnPointerHover(CurrentEventData.pointerCurrentRaycast, GetLastRay(), isInteractive);
    } else {
      // If the object's don't match or the hovering object has been destroyed
      // then the pointer has exited.
      if (previousObject != null || (currentObject == null && isPointerHovering)) {
        Pointer.OnPointerExit(previousObject);
        isPointerHovering = false;
      }

      if (currentObject != null) {
        Pointer.OnPointerEnter(CurrentEventData.pointerCurrentRaycast, GetLastRay(), isInteractive);
        isPointerHovering = true;
      }
    }
  }

它会触发GvrReticlePointerImpl

 /// Called when the user is pointing at valid GameObject. This can be a 3D
  /// or UI element.
  ///
  /// The targetObject is the object the user is pointing at.
  /// The intersectionPosition is where the ray intersected with the targetObject.
  /// The intersectionRay is the ray that was cast to determine the intersection.
  public override void OnPointerEnter(RaycastResult rayastResult, Ray ray,
    bool isInteractive) {
    SetPointerTarget(rayastResult.worldPosition, isInteractive);
  }

  /// Called every frame the user is still pointing at a valid GameObject. This
  /// can be a 3D or UI element.
  ///
  /// The targetObject is the object the user is pointing at.
  /// The intersectionPosition is where the ray intersected with the targetObject.
  /// The intersectionRay is the ray that was cast to determine the intersection.
  public override void OnPointerHover(RaycastResult rayastResult, Ray ray,
    bool isInteractive) {
    SetPointerTarget(rayastResult.worldPosition, isInteractive);
  }

  /// Called when the user's look no longer intersects an object previously
  /// intersected with a ray projected from the camera.
  /// This is also called just before **OnInputModuleDisabled** and may have have any of
  /// the values set as **null**.
  public override void OnPointerExit(GameObject previousObject) {
    ReticleDistanceInMeters = RETICLE_DISTANCE_MAX;
    ReticleInnerAngle = RETICLE_MIN_INNER_ANGLE;
    ReticleOuterAngle = RETICLE_MIN_OUTER_ANGLE;
  }

这些方法被调用。

总结:

GvrPointerInputModule分发事件,它里面会遍历所有的BaseRaycaster发射射线,然后更新gameObject,调用他们想要的回调比如OnPointEntry,然后更新光标Pointer,选中后变大,未选中变小。

最后一个问题,物体选中后变绿在那实现的?
这里写图片描述
上图,不解释。

菜单选中后的操作在实现呢?
这里写图片描述
需要提一下的是菜单的onClick事件是被GvrPointerGraphicRaycaster射线投射间接调用的。

如果自己写光标系统怎么办呢?

/// This script provides an implemention of Unity’s BaseInputModule class, so
/// that Canvas-based (uGUI) UI elements and 3D scene objects can be
/// interacted with in a Gvr Application.
///
/// This script is intended for use with either a
/// 3D Pointer with the Daydream Controller (Recommended for Daydream),
/// or a Gaze-based-Pointer (Recommended for Cardboard).
///
/// To use, attach to the scene’s EventSystem object. Be sure to move it above the
/// other modules, such as TouchInputModule and StandaloneInputModule, in order
/// for the Pointer to take priority in the event system.
///
/// If you are using a Canvas, set the Render Mode to World Space,
/// and add the GvrPointerGraphicRaycaster script to the object.
///
/// If you’d like pointers to work with 3D scene objects, add a GvrPointerPhysicsRaycaster to the main camera,
/// and add a component that implements one of the Event interfaces (EventTrigger will work nicely) to
/// an object with a collider.
///
/// GvrPointerInputModule emits the following events: Enter, Exit, Down, Up, Click, Select,
/// Deselect, UpdateSelected, and GvrPointerHover. Scroll, move, and submit/cancel events are not emitted.
///
/// To use a 3D Pointer with the Daydream Controller:
/// - Add the prefab GoogleVR/Prefabs/UI/GvrControllerPointer to your scene.
/// - Set the parent of GvrControllerPointer to the same parent as the main camera
/// (With a local position of 0,0,0).
///
/// To use a Gaze-based-pointer:
/// - Add the prefab GoogleVR/Prefabs/UI/GvrReticlePointer to your scene.
/// - Set the parent of GvrReticlePointer to the main camera.
///

这块内容是GvrPointerInputModule代码中注释的。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值