使用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组件,因此它将接收事件。 这就像玻璃的射线目标。

等级制度 检查员

您还可以使用InputInput.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不处理触摸”

不像你描述它的方式那么难。 您可以编写一个能够创建CanvasPanelImage的长代码。 将图像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); 

您将遇到的其他问题

您将无法使用GraphicRaycasterToggle组件上发送模拟事件。 这是Unity中的一个错误 。 我花了2天才意识到这一点。

也无法将假滑块移动事件发送到Slider组件。 我不知道这是不是一个错误。

除了上面提到的这些问题,我能够实现这一点。 它分为3部分。 只需创建一个文件夹并将所有脚本放入其中。

脚本

1WholeScreenPointer.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(); cnvs.renderMode = RenderMode.ScreenSpaceOverlay; cnvs.pixelPerfect = false; //Set Cavas sorting order to be above other Canvas sorting order cnvs.sortingOrder = 12; cnvs.targetDisplay = 0; //Make it child of the GameObject this script is attached to hiddenCanvas.transform.SetParent(gameObject.transform); } private void addRectTransform() { RectTransform rctrfm = hiddenCanvas.AddComponent(); } //Adds CanvasScaler component to the Canvas GameObject private void addCanvasScaler() { CanvasScaler cvsl = hiddenCanvas.AddComponent(); cvsl.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize; cvsl.referenceResolution = new Vector2(800f, 600f); cvsl.matchWidthOrHeight = 0.5f; cvsl.screenMatchMode = CanvasScaler.ScreenMatchMode.MatchWidthOrHeight; cvsl.referencePixelsPerUnit = 100f; } //Adds GraphicRaycaster component to the Canvas GameObject private void addGraphicsRaycaster() { GraphicRaycaster grcter = hiddenCanvas.AddComponent(); grcter.ignoreReversedGraphics = true; grcter.blockingObjects = GraphicRaycaster.BlockingObjects.None; } //Creates Hidden Panel and attaches Image component to it private GameObject createHiddenPanel(bool hide) { //Create Hidden Panel GameObject GameObject hiddenPanel = new GameObject("___HiddenPanel"); if (hide) { hiddenPanel.hideFlags = HideFlags.HideAndDontSave; } //Add Image Component to the hidden panel Image pnlImg = hiddenPanel.AddComponent(); pnlImg.sprite = null; pnlImg.color = new Color(1, 1, 1, 0); //Invisible pnlImg.material = null; pnlImg.raycastTarget = true; //Make it child of HiddenCanvas GameObject hiddenPanel.transform.SetParent(hiddenCanvas.transform); return hiddenPanel; } //Set Canvas width and height,to matach screen width and height then set anchor points to the corner of canvas. private void stretchImageAndConfigAnchor(GameObject panel) { Image pnlImg = panel.GetComponent(); //Reset postion to middle of the screen pnlImg.rectTransform.anchoredPosition3D = new Vector3(0, 0, 0); //Stretch the Image so that the whole screen is totally covered pnlImg.rectTransform.anchorMin = new Vector2(0, 0); pnlImg.rectTransform.anchorMax = new Vector2(1, 1); pnlImg.rectTransform.pivot = new Vector2(0.5f, 0.5f); } //Adds EventForwarder script to the Hidden Panel GameObject private void addEventForwarder(GameObject panel) { EventForwarder evnfwdr = panel.AddComponent(); } //Adds EventUnBlocker script to the Hidden Panel GameObject private void addEventRouter(GameObject panel) { EventUnBlocker evtrtr = panel.AddComponent(); eventRouter = evtrtr; } //Add EventSystem private void addEventSystemAndInputModule() { //Check if EventSystem exist. If it does not create and add it EventSystem eventSys = FindObjectOfType(); if (eventSys == null) { GameObject evObj = new GameObject("EventSystem"); EventSystem evs = evObj.AddComponent(); evs.firstSelectedGameObject = null; evs.sendNavigationEvents = true; evs.pixelDragThreshold = 5; eventSys = evs; } //Check if StandaloneInputModule exist. If it does not create and add it StandaloneInputModule sdlIpModl = FindObjectOfType(); if (sdlIpModl == null) { sdlIpModl = eventSys.gameObject.AddComponent(); sdlIpModl.horizontalAxis = "Horizontal"; sdlIpModl.verticalAxis = "Vertical"; sdlIpModl.submitButton = "Submit"; sdlIpModl.cancelButton = "Cancel"; sdlIpModl.inputActionsPerSecond = 10f; sdlIpModl.repeatDelay = 0.5f; sdlIpModl.forceModuleActive = false; } } /* Forwards Handler Event to every GameObject that implements IDragHandler, IPointerDownHandler, IPointerUpHandler interface */ public void forwardDragEvent(PointerEventData eventData) { //Route and send the event to UI and Colliders for (int i = 0; i < registeredGameobjects.Count; i++) { ExecuteEvents.Execute(registeredGameobjects[i], eventData, ExecuteEvents.dragHandler); } //Route and send the event to UI and Colliders eventRouter.routeDragEvent(eventData); } public void forwardPointerDownEvent(PointerEventData eventData) { //Send the event to all subscribed scripts for (int i = 0; i < registeredGameobjects.Count; i++) { ExecuteEvents.Execute(registeredGameobjects[i], eventData, ExecuteEvents.pointerDownHandler); } //Route and send the event to UI and Colliders eventRouter.routePointerDownEvent(eventData); } public void forwardPointerUpEvent(PointerEventData eventData) { //Send the event to all subscribed scripts for (int i = 0; i < registeredGameobjects.Count; i++) { ExecuteEvents.Execute(registeredGameobjects[i], eventData, ExecuteEvents.pointerUpHandler); } //Route and send the event to UI and Colliders eventRouter.routePointerUpEvent(eventData); } } 

2EventForwarder.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); } } 

3EventUnBlocker.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.要在场景中接收任何事件,只需在任何脚本中实现IDragHandlerIPointerDownHandlerIPointerUpHandler ,然后调用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); 最后,这可以进一步改进。