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模块下面有GvrPointerInputModule和GvrPointerManager:
其中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代码中注释的。
- Google VR Demo光标系统分析
- VR技术和Google的Demo
- Google VR Demo无法使用cocoapods更新的解决方法
- Google VR SDK
- Google VR 开发浅析
- Google VR 介绍
- Google DayDream VR开发
- Google VR 使用详解
- 【GVR】Google-VR
- Google VR for android
- google vr 简介
- GOOGLE VR SDK开发VR游戏
- Google VR Concepts (2)- VR View
- GOOGLE VR SDK开发VR游戏,VR播放器之一
- GOOGLE VR SDK开发VR游戏,VR播放器之一
- 绝对定位与光标demo
- [GVR]Google VR 工程导入
- Google vr sdk的使用
- 【基础算法强化】(1)分治算法与动态规划
- Python练习题1
- Jvm原理和优化
- static的详解 ----转载
- poj 1458 LCS(最长公共子序列)
- Google VR Demo光标系统分析
- 国务院正式发布《新一代人工智能发展规划》
- fastDFS+nginx
- java实现插入排序
- C#高级编程(第7版)pdf
- javaweb部署服务器 详细版
- codeforces 701 c 尺取法
- 从诗人到程序员
- 科研经验3:公众号建立实验室共享知识体系和宣传窗口