使用Unity3D的IPointerDownHandler方法,但使用“整个屏幕”
在Unity中,你需要检测场景中某些东西的手指触摸(手指绘图)。
在现代Unity中执行此操作的唯一方法非常简单 :
第1步 。 在该物体上放置一个对撞机。 (“地面”或其他任何东西。) 1
步骤2.在相机的Inspector面板上,单击以添加物理Raycaster(相关的2D或3D)。
步骤3.只需使用下面示例A中的代码。
(提示 – 不要忘记确保有一个EventSystem ……有时Unity会自动添加一个,有时不会!)
太棒了,不容易。 Unity最终通过UI层正确处理un /传播。 在台式机,设备,编辑器等上均匀而完美地工作。万岁Unity。
都好。 但是如果你想在屏幕上画画呢?
因此,您非常希望在“屏幕上”从用户滑动/触摸/绘图。 (例如,简单地说就是操作轨道摄像机。)就像在任何普通的3D游戏中,相机四处奔跑并移动。
你不希望手指在世界空间中的某个物体上的位置,你只需要抽象的“手指运动”(即在玻璃上的位置)。
你用什么对撞机? 没有对撞机你能做到吗? 由于这个原因,添加对撞机似乎很愚蠢。
我们做的是这样的 :
我只是做了某种平面对撞机,实际上是将它安装在相机下面 。 所以它只是坐在相机平截头体上,完全覆盖屏幕。
(对于代码,则不需要使用ScreenToWorldPoint,因此只需使用示例B中的代码 – 非常简单,效果很好。)
我的问题是,我不得不使用我所描述的“镜头下相机”似乎有点奇怪,只是为了触摸玻璃。
这是什么交易?
(注意 – 请不要回答涉及Unity古老的“Touches”系统,这个系统今天对于真实项目无法使用,你不能忽视.UI使用传统方法。)
代码示例A – 在场景对象上绘图。 使用ScreenToWorldPoint。
using UnityEngine; using System.Collections; using UnityEngine.EventSystems; public class FingerMove:MonoBehaviour, IPointerDownHandler, IDragHandler, IPointerUpHandler { public void OnPointerDown (PointerEventData data) { Debug.Log("FINGER DOWN"); prevPointWorldSpace = theCam.ScreenToWorldPoint( data.position ); } public void OnDrag (PointerEventData data) { thisPointWorldSpace = theCam.ScreenToWorldPoint( data.position ); realWorldTravel = thisPointWorldSpace - prevPointWorldSpace; _processRealWorldtravel(); prevPointWorldSpace = thisPointWorldSpace; } public void OnPointerUp (PointerEventData data) { Debug.Log("clear finger..."); }
代码示例B …您只关心用户在设备的玻璃屏幕上执行的操作。 这里更容易:
using UnityEngine; using System.Collections; using UnityEngine.EventSystems; public class FingerMove:MonoBehaviour, IPointerDownHandler, IDragHandler, IPointerUpHandler { private Vector2 prevPoint; private Vector2 newPoint; private Vector2 screenTravel; public void OnPointerDown (PointerEventData data) { Debug.Log("FINGER DOWN"); prevPoint = data.position; } public void OnDrag (PointerEventData data) { newPoint = data.position; screenTravel = newPoint - prevPoint; _processSwipe(); } public void OnPointerUp (PointerEventData data) { Debug.Log("FINEGR UP..."); } private void _processSwipe() { // your code here Debug.Log("screenTravel left-right.. " + screenTravel.x.ToString("f2")); } }
1如果你刚刚接触Unity:在那个步骤非常可能,让它成为一个名为“Draw”的层; 在物理设置中,“绘制”与任何东西互动; 在第二步中,Raycaster只将图层设置为“Draw”。
我要发布的问题和答案似乎基于意见。 不过,我会尽我所能回答。
如果您尝试在屏幕上检测指针事件,则使用对象表示屏幕没有任何问题。 在您的情况下,您使用3D对撞机覆盖相机的整个平截头体。 但是,在Unity中有一种本地方式可以使用覆盖整个屏幕的2D UI对象。 屏幕最好用2D对象表示。 对我来说,这似乎是一种自然的方式。
我为此目的使用通用代码:
public class Screen : MonoSingleton, IPointerClickHandler, IDragHandler, IBeginDragHandler, IEndDragHandler, IPointerDownHandler, IPointerUpHandler, IScrollHandler { private bool holding = false; private PointerEventData lastPointerEventData; #region Events public delegate void PointerEventHandler(PointerEventData data); static public event PointerEventHandler OnPointerClick = delegate { }; static public event PointerEventHandler OnPointerDown = delegate { }; /// Dont use delta data as it will be wrong. If you are going to use delta, use OnDrag instead. static public event PointerEventHandler OnPointerHold = delegate { }; static public event PointerEventHandler OnPointerUp = delegate { }; static public event PointerEventHandler OnBeginDrag = delegate { }; static public event PointerEventHandler OnDrag = delegate { }; static public event PointerEventHandler OnEndDrag = delegate { }; static public event PointerEventHandler OnScroll = delegate { }; #endregion #region Interface Implementations void IPointerClickHandler.OnPointerClick(PointerEventData e) { lastPointerEventData = e; OnPointerClick(e); } // And other interface implementations, you get the point #endregion void Update() { if (holding) { OnPointerHold(lastPointerEventData); } } }
Screen
是单身,因为在游戏的上下文中只有一个屏幕。 对象(如相机)订阅其指针事件,并相应地安排自己。 这也保持了单一责任的完整性。
您可以使用它将其附加到代表所谓玻璃(屏幕表面)的对象。 如果你认为用户界面上的按钮弹出屏幕,玻璃就会在它们下面。 为此,玻璃必须是Canvas
的第一个孩子。 当然, Canvas
必须在屏幕空间中呈现才有意义。
这里的一个黑客,没有意义的是在玻璃上添加一个不可见的Image
组件,因此它将接收事件。 这就像玻璃的射线目标。
您还可以使用Input
( Input.touches
等)来实现此glass对象。 它将用于检查每个Update
调用中的输入是否已更改。 这对我来说似乎是一种基于投票的方法,而上述方法是基于事件的方法。
您的问题似乎正在寻找一种方法来certificate使用Input
类。 恕我直言,不要让自己变得更难。 用什么有效。 并接受Unity不完美的事实。
首先,您需要了解只有3
种方法可以使用OnPointerDown
函数检测对象的单击:
1.为了使用OnPointerDown
函数检测点击,您需要一个UI组件。 这适用于其他类似的UI事件。
2.在2D / Sprite OnPointerDown
上使用OnPointerDown
函数检测点击的另一种方法是将Physics2DRaycaster
附加到Camera,然后在单击时调用OnPointerDown
。 请注意,必须将2D碰撞器连接到它上面。
3.如果这是一个带有碰撞器而不是2D碰撞器的3D物体,则必须将PhysicsRaycaster
连接到摄像机才能调用OnPointerDown
函数。
使用第一种方法执行此操作似乎更合理,而不是覆盖屏幕的大型对撞机或2D对撞机。 您所要做的就是创建一个Canvas
,Panel GameObject,并附加在整个屏幕上延伸到它的Image
组件。
老兄我只是不认为使用.UI作为一个严肃的解决方案:想象一下我们正在做一个大型游戏而你正在领导一个正在做所有UI的团队(我的意思是按钮,菜单和所有)。 我带领一个团队做行走机器人。 我突然对你说“哦,顺便说一下,我无法处理触摸(”!“),你能不能放入UI.Panel,不要忘记把它放在你正在做的一切之下,哦,放在你之间交换的任何/所有canvas或相机上的一个 – 并将该信息传回给我OK!“ :)我的意思是它只是愚蠢。 人们基本上不能说:“哦,Unity不处理触摸”
不像你描述它的方式那么难。 您可以编写一个能够创建Canvas
, Panel
和Image
的长代码。 将图像alpha更改为0
。 您所要做的就是将该代码附加到相机或空GameObject,它将在播放模式下自动执行所有这些操作。
使每个想要在屏幕上接收事件的GameObject订阅它,然后使用ExecuteEvents.Execute
将事件发送到附加到该GameObject的脚本中的所有接口。
例如,下面的示例代码将OnPointerDown
事件发送到名为target的OnPointerDown
。
ExecuteEvents.Execute(target, eventData, ExecuteEvents.pointerDownHandler);
你会遇到的问题 :
隐藏的Image
组件将阻止其他UI或GameObject接收光线投射。 这是这里最大的问题。
方案 :
因为它会导致一些阻塞问题,所以最好让Image的Canvas成为最重要的东西。 这将确保它现在100阻止所有其他UI / GameObject。 Canvas.sortingOrder = 12;
应该帮助我们做到这一点。
每次我们从Image中检测到OnPointerDown
等事件时,我们都会手动将OnPointerDown
事件重新发送到Image
下面的所有其他UI / OnPointerDown
。
首先,我们使用GraphicRaycaster
(UI), Physics2DRaycaster
(2D对撞机), PhysicsRaycaster
(3D Collider)投射光线GraphicRaycaster
,并将结果存储在List
。
现在,我们循环遍历List中的结果,并通过将结果发送到结果来重新发送我们收到的事件:
ExecuteEvents.Execute(currentListLoop, eventData, ExecuteEvents.pointerDownHandler);
您将遇到的其他问题 :
您将无法使用GraphicRaycaster
在Toggle
组件上发送模拟事件。 这是Unity中的一个错误 。 我花了2天才意识到这一点。
也无法将假滑块移动事件发送到Slider
组件。 我不知道这是不是一个错误。
除了上面提到的这些问题,我能够实现这一点。 它分为3部分。 只需创建一个文件夹并将所有脚本放入其中。
脚本 :
1 。 WholeScreenPointer.cs
– 创建Canvas
,GameObject和隐藏Image
的脚本的主要部分。 它完成了所有复杂的工作,以确保Image
始终覆盖屏幕。 它还将事件发送到所有订阅GameObject。
public class WholeScreenPointer : MonoBehaviour { //////////////////////////////// SINGLETON BEGIN //////////////////////////////// private static WholeScreenPointer localInstance; public static WholeScreenPointer Instance { get { return localInstance; } } public EventUnBlocker eventRouter; private void Awake() { if (localInstance != null && localInstance != this) { Destroy(this.gameObject); } else { localInstance = this; } } //////////////////////////////// SINGLETON END //////////////////////////////// //////////////////////////////// SETTINGS BEGIN //////////////////////////////// public bool simulateUIEvent = true; public bool simulateColliderEvent = true; public bool simulateCollider2DEvent = true; public bool hideWholeScreenInTheEditor = false; //////////////////////////////// SETTINGS END //////////////////////////////// private GameObject hiddenCanvas; private List registeredGameobjects = new List (); //////////////////////////////// USEFUL FUNCTIONS BEGIN //////////////////////////////// public void registerGameObject(GameObject objToRegister) { if (!isRegistered(objToRegister)) { registeredGameobjects.Add(objToRegister); } } public void unRegisterGameObject(GameObject objToRegister) { if (isRegistered(objToRegister)) { registeredGameobjects.Remove(objToRegister); } } public bool isRegistered(GameObject objToRegister) { return registeredGameobjects.Contains(objToRegister); } public void enablewholeScreenPointer(bool enable) { hiddenCanvas.SetActive(enable); } //////////////////////////////// USEFUL FUNCTIONS END //////////////////////////////// // Use this for initialization private void Start() { makeAndConfigWholeScreenPinter(hideWholeScreenInTheEditor); } private void makeAndConfigWholeScreenPinter(bool hide = true) { //Create and Add Canvas Component createCanvas(hide); //Add Rect Transform Component //addRectTransform(); //Add Canvas Scaler Component addCanvasScaler(); //Add Graphics Raycaster Component addGraphicsRaycaster(); //Create Hidden Panel GameObject GameObject panel = createHiddenPanel(hide); //Make the Image to be positioned in the middle of the screen then fix its anchor stretchImageAndConfigAnchor(panel); //Add EventForwarder script addEventForwarder(panel); //Add EventUnBlocker addEventRouter(panel); //Add EventSystem and Input Module addEventSystemAndInputModule(); } //Creates Hidden GameObject and attaches Canvas component to it private void createCanvas(bool hide) { //Create Canvas GameObject hiddenCanvas = new GameObject("___HiddenCanvas"); if (hide) { hiddenCanvas.hideFlags = HideFlags.HideAndDontSave; } //Create and Add Canvas Component Canvas cnvs = hiddenCanvas.AddComponent
2 。 EventForwarder.cs
– 它只是从隐藏的Image
接收任何事件,并将其传递给WholeScreenPointer.cs
脚本进行处理。
public class EventForwarder : MonoBehaviour, IDragHandler, IPointerDownHandler, IPointerUpHandler { WholeScreenPointer wcp = null; void Start() { wcp = WholeScreenPointer.Instance; } public void OnDrag(PointerEventData eventData) { wcp.forwardDragEvent(eventData); } public void OnPointerDown(PointerEventData eventData) { wcp.forwardPointerDownEvent(eventData); } public void OnPointerUp(PointerEventData eventData) { wcp.forwardPointerUpEvent(eventData); } }
3 。 EventUnBlocker.cs
– 它通过向上面的任何Object发送假事件来EventUnBlocker.cs
隐藏Image
阻挡的光线。 无论是UI,2D还是3D对撞机。
public class EventUnBlocker : MonoBehaviour { List grRayCast = new List (); //UI List phy2dRayCast = new List (); //Collider 2D (Sprite Renderer) List phyRayCast = new List (); //Normal Collider(3D/Mesh Renderer) List resultList = new List (); //For Detecting button click and sending fake Button Click to UI Buttons Dictionary pointerIdToGameObject = new Dictionary(); // Use this for initialization void Start() { } public void sendArtificialUIEvent(Component grRayCast, PointerEventData eventData, PointerEventType evType) { //Route to all Object in the RaycastResult for (int i = 0; i < resultList.Count; i++) { /*Do something if it is NOT this GameObject. We don't want any other detection on this GameObject */ if (resultList[i].gameObject != this.gameObject) { //Check if this is UI if (grRayCast is GraphicRaycaster) { //Debug.Log("UI"); routeEvent(resultList[i], eventData, evType, true); } //Check if this is Collider 2D/SpriteRenderer if (grRayCast is Physics2DRaycaster) { //Debug.Log("Collider 2D/SpriteRenderer"); routeEvent(resultList[i], eventData, evType, false); } //Check if this is Collider/MeshRender if (grRayCast is PhysicsRaycaster) { //Debug.Log("Collider 3D/Mesh"); routeEvent(resultList[i], eventData, evType, false); } } } } //Creates fake PointerEventData that will be used to make PointerEventData for the callback functions PointerEventData createEventData(RaycastResult rayResult) { PointerEventData fakeEventData = new PointerEventData(EventSystem.current); fakeEventData.pointerCurrentRaycast = rayResult; return fakeEventData; } private void routeEvent(RaycastResult rayResult, PointerEventData eventData, PointerEventType evType, bool isUI = false) { bool foundKeyAndValue = false; GameObject target = rayResult.gameObject; //Make fake GameObject target PointerEventData fakeEventData = createEventData(rayResult); switch (evType) { case PointerEventType.Drag: //Send/Simulate Fake OnDrag event ExecuteEvents.Execute(target, fakeEventData, ExecuteEvents.dragHandler); break; case PointerEventType.Down: //Send/Simulate Fake OnPointerDown event ExecuteEvents.Execute(target, fakeEventData, ExecuteEvents.pointerDownHandler); //Code Below is for UI. break out of case if this is not UI if (!isUI) { break; } //Prepare Button Click. Should be sent in the if PointerEventType.Up statement Button buttonFound = target.GetComponent
用法 :
1.将WholeScreenPointer
脚本WholeScreenPointer
到空的WholeScreenPointer
或Camera。
2.要在场景中接收任何事件,只需在任何脚本中实现IDragHandler
, IPointerDownHandler
, IPointerUpHandler
,然后调用WholeScreenPointer.Instance.registerGameObject(this.gameObject);
一旦。 屏幕上的任何事件现在都将发送到该脚本。 不要忘记在OnDisable()
函数中取消注册。
例如,将Test
附加到您想要接收触摸事件的任何GameObject:
public class Test : MonoBehaviour, IDragHandler, IPointerDownHandler, IPointerUpHandler { void Start() { //Register this GameObject so that it will receive events from WholeScreenPointer script WholeScreenPointer.Instance.registerGameObject(this.gameObject); } public void OnDrag(PointerEventData eventData) { Debug.Log("Dragging: "); } public void OnPointerDown(PointerEventData eventData) { Debug.Log("Pointer Down: "); } public void OnPointerUp(PointerEventData eventData) { Debug.Log("Pointer Up: "); } void OnDisable() { WholeScreenPointer.Instance.unRegisterGameObject(this.gameObject); } }
注意 :
您只需要调用WholeScreenPointer.Instance.registerGameObject(this.gameObject);
如果你想在屏幕上的任何地方接收活动。 如果您只想从当前的Object接收事件,那么您不必调用它。 如果这样做,您将收到多个活动。
其他重要function:
启用WholeScreen事件 - WholeScreenPointer.Instance.enablewholeScreenPointer(true);
禁用WholeScreen事件 - WholeScreenPointer.Instance.enablewholeScreenPointer(false);
最后,这可以进一步改进。