Google VR Demo光标系统分析

来源:互联网 发布:java实现支付宝转账 编辑:程序博客网 时间:2024/06/03 18:51

因为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代码中注释的。

原创粉丝点击