使用 Unity3D 的 IPointerDownHandler 方法,但使用 "the whole screen"
Using Unity3D's IPointerDownHandler approach, but with "the whole screen"
在 Unity 中,您需要检测场景中某物上的手指触摸(手指绘图)。
在现代 Unity 中做到这一点的唯一方法非常简单:
步骤 1。在该对象上放置一个碰撞器。 ("The ground" 或其他任何形式。)1
第 2 步。 在相机的检查器面板上,单击以添加物理光线投射器(相关的 2D 或 3D)。
步骤 3. 只需使用下面示例 A 中的代码即可。
(提示 - 不要忘记确保有一个 EventSystem ...有时 Unity 会自动添加一个,有时不会!)
太棒了,再简单不过了。 Unity最终通过UI层正确处理了un/propagation。在桌面、设备、编辑器等上统一且完美地工作。Hooray Unity。
一切顺利。但是如果你只想画 "on the screen" 怎么办?
所以你想要的,很简单,来自用户"on the screen"的swipes/touches/drawing。 (例如,仅用于操作轨道摄像机。)就像在摄像机四处移动的任何普通 3D 游戏中一样。
您不需要手指在世界 space 中的 某个对象 上的位置,您只需要抽象 "finger motions" (即在玻璃)。
那你用什么对撞机?你能在没有对撞机的情况下做到吗?仅仅因为这个原因添加碰撞器似乎很愚蠢。
我们做的是这样:
我只是制作了一个平面对撞机,并将其安装在相机下方。所以它只是位于相机视锥体中并完全覆盖屏幕。
(对于代码,则无需使用 ScreenToWorldPoint,因此只需使用示例 B 中的代码即可 - 极其简单,完美运行。)
我的问题,使用我描述的 "under-camera colldier" 似乎有点奇怪,只是为了接触玻璃。
这是怎么回事?
(注意 - 请不要回答涉及 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;
prevPoint = newPoint;
_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 的新手:很可能在那一步,将其设为一个名为 say "Draw" 的层;在物理设置中使 "Draw" 不与任何事物交互;在 Raycaster 的第二步中,只需将图层设置为 "Draw".
我要 post 提出的问题和答案似乎非常基于个人意见。不过,我会尽我所能回答。
如果您试图检测屏幕上的指针事件,用 object 表示屏幕没有任何问题。在您的情况下,您使用 3D 对撞机覆盖相机的整个视锥体。但是,在 Unity 中有一种本地方法可以执行此操作,即使用覆盖整个屏幕的 2D UI object。屏幕可以最好地表示为 2D object。对我来说,这似乎是一种自然的方式。
我为此使用通用代码:
public class Screen : MonoSingleton<Screen>, 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 { };
/// <summary> Dont use delta data as it will be wrong. If you are going to use delta, use OnDrag instead. </summary>
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
是单例,因为游戏上下文只有一个画面。 Objects(如相机)订阅它的指针事件,并相应地安排自己。这也使 single-responsibility 保持完整。
您可以将其附加到代表所谓玻璃(屏幕表面)的 object 上。如果您认为 UI 上的按钮从屏幕上弹出,那么它们下面就是玻璃。为此,玻璃必须是 Canvas
中的第一个 child。当然,Canvas
必须在屏幕 space 中呈现才有意义。
这里有一个没有意义的 hack 是向玻璃添加一个不可见的 Image
组件,因此它会接收事件。这就像玻璃的光线投射目标。
您也可以使用 Input
(Input.touches
等)来实现这种玻璃 object。它可以作为检查输入是否在每个 Update
调用中更改。这对我来说似乎是一种 polling-based 方法,而上面的方法是 event-based 方法。
您的问题似乎是在寻找一种方法来证明使用 Input
class 的合理性。恕我直言,不要让自己更难。使用有效的方法。并接受 Unity 并不完美的事实。
首先,您需要了解只有 3
种方法可以检测使用 OnPointerDown
函数点击对象:
1。您需要一个 UI 组件来检测使用 OnPointerDown
函数的点击。这适用于其他类似的 UI 事件。
2。在 2D/Sprite 游戏对象上使用 OnPointerDown
函数检测点击的另一种方法是将 Physics2DRaycaster
附加到相机,然后OnPointerDown
点击时会被调用。请注意,必须附加一个 2D Collider。
3。如果这是一个带有 Collider 而不是 2D Collider 的 3D 对象,您必须将 PhysicsRaycaster
附加到相机以便调用 OnPointerDown
函数。
使用第一种方法似乎更合理,而不是让大型碰撞器或 2D 碰撞器覆盖屏幕。您所要做的就是创建一个 Canvas
、Panel GameObject,并将横跨整个屏幕的 Image
组件附加到它。
Dude I just don't see using .UI as a serious solution: imagine we're
doing a big game and you're leading a team that is doing all the UI (I
mean buttons, menus and all). I'm leading a team doing the walking
robots. I suddenly say to you "oh, by the way, I can't handle touch
("!"), could you drop in a UI.Panel, don't forget to keep it under
everything you're doing, oh and put one on any/all canvasses or
cameras you swap between - and pass that info back to me OK!" :) I
mean it's just silly. One can't essentially say: "oh, Unity doesn't
handle touch"
并不像你描述的那样难。您可以编写一个长代码来创建 Canvas
、Panel
和 Image
。将图像 alpha 更改为 0
。您所要做的就是将该代码附加到相机或一个空的游戏对象,它会在播放模式下自动为您执行所有这些操作。
让每个想要在屏幕上接收事件的 GameObject 订阅它,然后使用 ExecuteEvents.Execute
将事件发送到附加到该 GameObject 的脚本中的所有接口。
例如,下面的示例代码将向名为 target 的游戏对象发送 OnPointerDown
事件。
ExecuteEvents.Execute<IPointerDownHandler>(target,
eventData,
ExecuteEvents.pointerDownHandler);
问题你会运行变成:
隐藏的 Image
组件将阻止其他 UI 或 GameObject 接收光线投射。这是这里最大的问题。
解决方案:
由于会造成一些遮挡问题,所以最好让Image的Canvas在所有内容之上。这将确保它现在是 100 阻塞所有其他 UI/GameObject。 Canvas.sortingOrder = 12;
应该帮助我们做到这一点。
每次我们从图像中检测到 OnPointerDown
等事件时,我们将 手动 发送重新发送 OnPointerDown
事件给所有其他 UI/GameObjects 在 Image
.
下方
首先,我们用 GraphicRaycaster
(UI), Physics2DRaycaster
(2D collider), PhysicsRaycaster
(3D Collider) 投射光线并存储结果在 List
.
现在,我们遍历列表中的结果,并通过向结果发送人工事件来重新发送我们收到的事件:
ExecuteEvents.Execute<IPointerDownHandler>(currentListLoop,
eventData,
ExecuteEvents.pointerDownHandler);
其他问题你会运行进入:
您将无法使用 GraphicRaycaster
在 Toggle
组件上发送模拟事件。这是 Unity 中的 bug。我花了 2 天时间才意识到这一点。
也无法将假滑块移动事件发送到 Slider
组件。我不知道这是不是一个错误。
除了上面提到的这些问题,我还能够实现这个。它分为 3 个部分。只需创建一个文件夹并将所有脚本放入其中即可。
脚本:
1.WholeScreenPointer.cs
- 创建 Canvas
、GameObject 和隐藏 Image
。它执行所有复杂的操作以确保 Image
始终覆盖屏幕。它还向所有订阅的游戏对象发送事件。
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<GameObject> registeredGameobjects = new List<GameObject>();
//////////////////////////////// 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<Canvas>();
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<RectTransform>();
}
//Adds CanvasScaler component to the Canvas GameObject
private void addCanvasScaler()
{
CanvasScaler cvsl = hiddenCanvas.AddComponent<CanvasScaler>();
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<GraphicRaycaster>();
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<Image>();
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<Image>();
//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<EventForwarder>();
}
//Adds EventUnBlocker script to the Hidden Panel GameObject
private void addEventRouter(GameObject panel)
{
EventUnBlocker evtrtr = panel.AddComponent<EventUnBlocker>();
eventRouter = evtrtr;
}
//Add EventSystem
private void addEventSystemAndInputModule()
{
//Check if EventSystem exist. If it does not create and add it
EventSystem eventSys = FindObjectOfType<EventSystem>();
if (eventSys == null)
{
GameObject evObj = new GameObject("EventSystem");
EventSystem evs = evObj.AddComponent<EventSystem>();
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<StandaloneInputModule>();
if (sdlIpModl == null)
{
sdlIpModl = eventSys.gameObject.AddComponent<StandaloneInputModule>();
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<IDragHandler>(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<IPointerDownHandler>(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<IPointerUpHandler>(registeredGameobjects[i],
eventData,
ExecuteEvents.pointerUpHandler);
}
//Route and send the event to UI and Colliders
eventRouter.routePointerUpEvent(eventData);
}
}
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
- 它通过发送伪造来解锁隐藏的 Image
被阻挡的射线事件到它上面的任何对象。无论是 UI、2D 还是 3D 对撞机。
public class EventUnBlocker : MonoBehaviour
{
List<GraphicRaycaster> grRayCast = new List<GraphicRaycaster>(); //UI
List<Physics2DRaycaster> phy2dRayCast = new List<Physics2DRaycaster>(); //Collider 2D (Sprite Renderer)
List<PhysicsRaycaster> phyRayCast = new List<PhysicsRaycaster>(); //Normal Collider(3D/Mesh Renderer)
List<RaycastResult> resultList = new List<RaycastResult>();
//For Detecting button click and sending fake Button Click to UI Buttons
Dictionary<int, GameObject> pointerIdToGameObject = new Dictionary<int, GameObject>();
// 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<IDragHandler>(target, fakeEventData,
ExecuteEvents.dragHandler);
break;
case PointerEventType.Down:
//Send/Simulate Fake OnPointerDown event
ExecuteEvents.Execute<IPointerDownHandler>(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<Button>();
//If pointerId is not in the dictionary add it
if (buttonFound != null)
{
if (!dictContains(eventData.pointerId))
{
dictAdd(eventData.pointerId, target);
}
}
//Bug in Unity with GraphicRaycaster and Toggle. Have to use a hack below
//Toggle Toggle component
Toggle toggle = null;
if ((target.name == "Checkmark" || target.name == "Label") && toggle == null)
{
toggle = target.GetComponentInParent<Toggle>();
}
if (toggle != null)
{
//Debug.LogWarning("Toggled!: " + target.name);
toggle.isOn = !toggle.isOn;
//Destroy(toggle.gameObject);
}
break;
case PointerEventType.Up:
//Send/Simulate Fake OnPointerUp event
ExecuteEvents.Execute<IPointerUpHandler>(target,
fakeEventData,
ExecuteEvents.pointerUpHandler);
//Code Below is for UI. break out of case if this is not UI
if (!isUI)
{
break;
}
//Send Fake Button Click if requirement is met
Button buttonPress = target.GetComponent<Button>();
/*If pointerId is in the dictionary, check
*/
if (buttonPress != null)
{
if (dictContains(eventData.pointerId))
{
//Check if GameObject matches too. If so then this is a valid Click
for (int i = 0; i < resultList.Count; i++)
{
GameObject tempButton = resultList[i].gameObject;
if (tempButton != this.gameObject && dictContains(eventData.pointerId, tempButton))
{
foundKeyAndValue = true;
//Debug.Log("Button ID and GameObject Match! Sending Click Event");
//Send/Simulate Fake Click event to the Button
ExecuteEvents.Execute<IPointerClickHandler>(tempButton,
new PointerEventData(EventSystem.current),
ExecuteEvents.pointerClickHandler);
}
}
}
}
break;
}
//Remove pointerId since it exist
if (foundKeyAndValue)
{
dictRemove(eventData.pointerId);
}
}
void routeOption(PointerEventData eventData, PointerEventType evType)
{
UpdateRaycaster();
if (WholeScreenPointer.Instance.simulateUIEvent)
{
//Loop Through All GraphicRaycaster(UI) and throw Raycast to each one
for (int i = 0; i < grRayCast.Count; i++)
{
//Throw Raycast to all UI elements in the position(eventData)
grRayCast[i].Raycast(eventData, resultList);
sendArtificialUIEvent(grRayCast[i], eventData, evType);
}
//Reset Result
resultList.Clear();
}
if (WholeScreenPointer.Instance.simulateCollider2DEvent)
{
//Loop Through All Collider 2D (Sprite Renderer) and throw Raycast to each one
for (int i = 0; i < phy2dRayCast.Count; i++)
{
//Throw Raycast to all UI elements in the position(eventData)
phy2dRayCast[i].Raycast(eventData, resultList);
sendArtificialUIEvent(phy2dRayCast[i], eventData, evType);
}
//Reset Result
resultList.Clear();
}
if (WholeScreenPointer.Instance.simulateColliderEvent)
{
//Loop Through All Normal Collider(3D/Mesh Renderer) and throw Raycast to each one
for (int i = 0; i < phyRayCast.Count; i++)
{
//Throw Raycast to all UI elements in the position(eventData)
phyRayCast[i].Raycast(eventData, resultList);
sendArtificialUIEvent(phyRayCast[i], eventData, evType);
}
//Reset Result
resultList.Clear();
}
}
public void routeDragEvent(PointerEventData eventData)
{
routeOption(eventData, PointerEventType.Drag);
}
public void routePointerDownEvent(PointerEventData eventData)
{
routeOption(eventData, PointerEventType.Down);
}
public void routePointerUpEvent(PointerEventData eventData)
{
routeOption(eventData, PointerEventType.Up);
}
public void UpdateRaycaster()
{
convertToList(FindObjectsOfType<GraphicRaycaster>(), grRayCast);
convertToList(FindObjectsOfType<Physics2DRaycaster>(), phy2dRayCast);
convertToList(FindObjectsOfType<PhysicsRaycaster>(), phyRayCast);
}
//To avoid ToList() function
void convertToList(GraphicRaycaster[] fromComponent, List<GraphicRaycaster> toComponent)
{
//Clear and copy new Data
toComponent.Clear();
for (int i = 0; i < fromComponent.Length; i++)
{
toComponent.Add(fromComponent[i]);
}
}
//To avoid ToList() function
void convertToList(Physics2DRaycaster[] fromComponent, List<Physics2DRaycaster> toComponent)
{
//Clear and copy new Data
toComponent.Clear();
for (int i = 0; i < fromComponent.Length; i++)
{
toComponent.Add(fromComponent[i]);
}
}
//To avoid ToList() function
void convertToList(PhysicsRaycaster[] fromComponent, List<PhysicsRaycaster> toComponent)
{
//Clear and copy new Data
toComponent.Clear();
for (int i = 0; i < fromComponent.Length; i++)
{
toComponent.Add(fromComponent[i]);
}
}
//Checks if object is in the dictionary
private bool dictContains(GameObject obj)
{
return pointerIdToGameObject.ContainsValue(obj);
}
//Checks if int is in the dictionary
private bool dictContains(int pointerId)
{
return pointerIdToGameObject.ContainsKey(pointerId);
}
//Checks if int and object is in the dictionary
private bool dictContains(int pointerId, GameObject obj)
{
return (pointerIdToGameObject.ContainsKey(pointerId) && pointerIdToGameObject.ContainsValue(obj));
}
//Adds pointerId and its value to dictionary
private void dictAdd(int pointerId, GameObject obj)
{
pointerIdToGameObject.Add(pointerId, obj);
}
//Removes pointerId and its value from dictionary
private void dictRemove(int pointerId)
{
pointerIdToGameObject.Remove(pointerId);
}
public enum PointerEventType
{
Drag, Down, Up
}
}
用法:
1。将 WholeScreenPointer
脚本附加到一个空的游戏对象或相机。
2。要接收场景中的任何事件,只需在任何脚本中实现 IDragHandler
、IPointerDownHandler
、IPointerUpHandler
,然后调用 WholeScreenPointer.Instance.registerGameObject(this.gameObject);
一次。来自屏幕的任何事件现在都将发送到该脚本。不要忘记在 OnDisable()
函数中取消注册。
例如,将 Test
附加到您想要接收触摸事件的任何游戏对象:
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);
。如果您只想从当前对象接收事件,则不必调用它。如果这样做,您将收到多个事件。
其他重要功能:
启用全屏事件 - WholeScreenPointer.Instance.enablewholeScreenPointer(true);
禁用全屏事件 - WholeScreenPointer.Instance.enablewholeScreenPointer(false);
最后,这个可以再改进一下。
在 Unity 中,您需要检测场景中某物上的手指触摸(手指绘图)。
在现代 Unity 中做到这一点的唯一方法非常简单:
步骤 1。在该对象上放置一个碰撞器。 ("The ground" 或其他任何形式。)1
第 2 步。 在相机的检查器面板上,单击以添加物理光线投射器(相关的 2D 或 3D)。
步骤 3. 只需使用下面示例 A 中的代码即可。
(提示 - 不要忘记确保有一个 EventSystem ...有时 Unity 会自动添加一个,有时不会!)
太棒了,再简单不过了。 Unity最终通过UI层正确处理了un/propagation。在桌面、设备、编辑器等上统一且完美地工作。Hooray Unity。
一切顺利。但是如果你只想画 "on the screen" 怎么办?
所以你想要的,很简单,来自用户"on the screen"的swipes/touches/drawing。 (例如,仅用于操作轨道摄像机。)就像在摄像机四处移动的任何普通 3D 游戏中一样。
您不需要手指在世界 space 中的 某个对象 上的位置,您只需要抽象 "finger motions" (即在玻璃)。
那你用什么对撞机?你能在没有对撞机的情况下做到吗?仅仅因为这个原因添加碰撞器似乎很愚蠢。
我们做的是这样:
我只是制作了一个平面对撞机,并将其安装在相机下方。所以它只是位于相机视锥体中并完全覆盖屏幕。
(对于代码,则无需使用 ScreenToWorldPoint,因此只需使用示例 B 中的代码即可 - 极其简单,完美运行。)
我的问题,使用我描述的 "under-camera colldier" 似乎有点奇怪,只是为了接触玻璃。
这是怎么回事?
(注意 - 请不要回答涉及 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;
prevPoint = newPoint;
_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 的新手:很可能在那一步,将其设为一个名为 say "Draw" 的层;在物理设置中使 "Draw" 不与任何事物交互;在 Raycaster 的第二步中,只需将图层设置为 "Draw".
我要 post 提出的问题和答案似乎非常基于个人意见。不过,我会尽我所能回答。
如果您试图检测屏幕上的指针事件,用 object 表示屏幕没有任何问题。在您的情况下,您使用 3D 对撞机覆盖相机的整个视锥体。但是,在 Unity 中有一种本地方法可以执行此操作,即使用覆盖整个屏幕的 2D UI object。屏幕可以最好地表示为 2D object。对我来说,这似乎是一种自然的方式。
我为此使用通用代码:
public class Screen : MonoSingleton<Screen>, 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 { };
/// <summary> Dont use delta data as it will be wrong. If you are going to use delta, use OnDrag instead. </summary>
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
是单例,因为游戏上下文只有一个画面。 Objects(如相机)订阅它的指针事件,并相应地安排自己。这也使 single-responsibility 保持完整。
您可以将其附加到代表所谓玻璃(屏幕表面)的 object 上。如果您认为 UI 上的按钮从屏幕上弹出,那么它们下面就是玻璃。为此,玻璃必须是 Canvas
中的第一个 child。当然,Canvas
必须在屏幕 space 中呈现才有意义。
这里有一个没有意义的 hack 是向玻璃添加一个不可见的 Image
组件,因此它会接收事件。这就像玻璃的光线投射目标。
您也可以使用 Input
(Input.touches
等)来实现这种玻璃 object。它可以作为检查输入是否在每个 Update
调用中更改。这对我来说似乎是一种 polling-based 方法,而上面的方法是 event-based 方法。
您的问题似乎是在寻找一种方法来证明使用 Input
class 的合理性。恕我直言,不要让自己更难。使用有效的方法。并接受 Unity 并不完美的事实。
首先,您需要了解只有 3
种方法可以检测使用 OnPointerDown
函数点击对象:
1。您需要一个 UI 组件来检测使用 OnPointerDown
函数的点击。这适用于其他类似的 UI 事件。
2。在 2D/Sprite 游戏对象上使用 OnPointerDown
函数检测点击的另一种方法是将 Physics2DRaycaster
附加到相机,然后OnPointerDown
点击时会被调用。请注意,必须附加一个 2D Collider。
3。如果这是一个带有 Collider 而不是 2D Collider 的 3D 对象,您必须将 PhysicsRaycaster
附加到相机以便调用 OnPointerDown
函数。
使用第一种方法似乎更合理,而不是让大型碰撞器或 2D 碰撞器覆盖屏幕。您所要做的就是创建一个 Canvas
、Panel GameObject,并将横跨整个屏幕的 Image
组件附加到它。
Dude I just don't see using .UI as a serious solution: imagine we're doing a big game and you're leading a team that is doing all the UI (I mean buttons, menus and all). I'm leading a team doing the walking robots. I suddenly say to you "oh, by the way, I can't handle touch ("!"), could you drop in a UI.Panel, don't forget to keep it under everything you're doing, oh and put one on any/all canvasses or cameras you swap between - and pass that info back to me OK!" :) I mean it's just silly. One can't essentially say: "oh, Unity doesn't handle touch"
并不像你描述的那样难。您可以编写一个长代码来创建 Canvas
、Panel
和 Image
。将图像 alpha 更改为 0
。您所要做的就是将该代码附加到相机或一个空的游戏对象,它会在播放模式下自动为您执行所有这些操作。
让每个想要在屏幕上接收事件的 GameObject 订阅它,然后使用 ExecuteEvents.Execute
将事件发送到附加到该 GameObject 的脚本中的所有接口。
例如,下面的示例代码将向名为 target 的游戏对象发送 OnPointerDown
事件。
ExecuteEvents.Execute<IPointerDownHandler>(target,
eventData,
ExecuteEvents.pointerDownHandler);
问题你会运行变成:
隐藏的 Image
组件将阻止其他 UI 或 GameObject 接收光线投射。这是这里最大的问题。
解决方案:
由于会造成一些遮挡问题,所以最好让Image的Canvas在所有内容之上。这将确保它现在是 100 阻塞所有其他 UI/GameObject。 Canvas.sortingOrder = 12;
应该帮助我们做到这一点。
每次我们从图像中检测到 OnPointerDown
等事件时,我们将 手动 发送重新发送 OnPointerDown
事件给所有其他 UI/GameObjects 在 Image
.
首先,我们用 GraphicRaycaster
(UI), Physics2DRaycaster
(2D collider), PhysicsRaycaster
(3D Collider) 投射光线并存储结果在 List
.
现在,我们遍历列表中的结果,并通过向结果发送人工事件来重新发送我们收到的事件:
ExecuteEvents.Execute<IPointerDownHandler>(currentListLoop,
eventData,
ExecuteEvents.pointerDownHandler);
其他问题你会运行进入:
您将无法使用 GraphicRaycaster
在 Toggle
组件上发送模拟事件。这是 Unity 中的 bug。我花了 2 天时间才意识到这一点。
也无法将假滑块移动事件发送到 Slider
组件。我不知道这是不是一个错误。
除了上面提到的这些问题,我还能够实现这个。它分为 3 个部分。只需创建一个文件夹并将所有脚本放入其中即可。
脚本:
1.WholeScreenPointer.cs
- 创建 Canvas
、GameObject 和隐藏 Image
。它执行所有复杂的操作以确保 Image
始终覆盖屏幕。它还向所有订阅的游戏对象发送事件。
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<GameObject> registeredGameobjects = new List<GameObject>();
//////////////////////////////// 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<Canvas>();
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<RectTransform>();
}
//Adds CanvasScaler component to the Canvas GameObject
private void addCanvasScaler()
{
CanvasScaler cvsl = hiddenCanvas.AddComponent<CanvasScaler>();
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<GraphicRaycaster>();
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<Image>();
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<Image>();
//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<EventForwarder>();
}
//Adds EventUnBlocker script to the Hidden Panel GameObject
private void addEventRouter(GameObject panel)
{
EventUnBlocker evtrtr = panel.AddComponent<EventUnBlocker>();
eventRouter = evtrtr;
}
//Add EventSystem
private void addEventSystemAndInputModule()
{
//Check if EventSystem exist. If it does not create and add it
EventSystem eventSys = FindObjectOfType<EventSystem>();
if (eventSys == null)
{
GameObject evObj = new GameObject("EventSystem");
EventSystem evs = evObj.AddComponent<EventSystem>();
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<StandaloneInputModule>();
if (sdlIpModl == null)
{
sdlIpModl = eventSys.gameObject.AddComponent<StandaloneInputModule>();
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<IDragHandler>(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<IPointerDownHandler>(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<IPointerUpHandler>(registeredGameobjects[i],
eventData,
ExecuteEvents.pointerUpHandler);
}
//Route and send the event to UI and Colliders
eventRouter.routePointerUpEvent(eventData);
}
}
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
- 它通过发送伪造来解锁隐藏的 Image
被阻挡的射线事件到它上面的任何对象。无论是 UI、2D 还是 3D 对撞机。
public class EventUnBlocker : MonoBehaviour
{
List<GraphicRaycaster> grRayCast = new List<GraphicRaycaster>(); //UI
List<Physics2DRaycaster> phy2dRayCast = new List<Physics2DRaycaster>(); //Collider 2D (Sprite Renderer)
List<PhysicsRaycaster> phyRayCast = new List<PhysicsRaycaster>(); //Normal Collider(3D/Mesh Renderer)
List<RaycastResult> resultList = new List<RaycastResult>();
//For Detecting button click and sending fake Button Click to UI Buttons
Dictionary<int, GameObject> pointerIdToGameObject = new Dictionary<int, GameObject>();
// 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<IDragHandler>(target, fakeEventData,
ExecuteEvents.dragHandler);
break;
case PointerEventType.Down:
//Send/Simulate Fake OnPointerDown event
ExecuteEvents.Execute<IPointerDownHandler>(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<Button>();
//If pointerId is not in the dictionary add it
if (buttonFound != null)
{
if (!dictContains(eventData.pointerId))
{
dictAdd(eventData.pointerId, target);
}
}
//Bug in Unity with GraphicRaycaster and Toggle. Have to use a hack below
//Toggle Toggle component
Toggle toggle = null;
if ((target.name == "Checkmark" || target.name == "Label") && toggle == null)
{
toggle = target.GetComponentInParent<Toggle>();
}
if (toggle != null)
{
//Debug.LogWarning("Toggled!: " + target.name);
toggle.isOn = !toggle.isOn;
//Destroy(toggle.gameObject);
}
break;
case PointerEventType.Up:
//Send/Simulate Fake OnPointerUp event
ExecuteEvents.Execute<IPointerUpHandler>(target,
fakeEventData,
ExecuteEvents.pointerUpHandler);
//Code Below is for UI. break out of case if this is not UI
if (!isUI)
{
break;
}
//Send Fake Button Click if requirement is met
Button buttonPress = target.GetComponent<Button>();
/*If pointerId is in the dictionary, check
*/
if (buttonPress != null)
{
if (dictContains(eventData.pointerId))
{
//Check if GameObject matches too. If so then this is a valid Click
for (int i = 0; i < resultList.Count; i++)
{
GameObject tempButton = resultList[i].gameObject;
if (tempButton != this.gameObject && dictContains(eventData.pointerId, tempButton))
{
foundKeyAndValue = true;
//Debug.Log("Button ID and GameObject Match! Sending Click Event");
//Send/Simulate Fake Click event to the Button
ExecuteEvents.Execute<IPointerClickHandler>(tempButton,
new PointerEventData(EventSystem.current),
ExecuteEvents.pointerClickHandler);
}
}
}
}
break;
}
//Remove pointerId since it exist
if (foundKeyAndValue)
{
dictRemove(eventData.pointerId);
}
}
void routeOption(PointerEventData eventData, PointerEventType evType)
{
UpdateRaycaster();
if (WholeScreenPointer.Instance.simulateUIEvent)
{
//Loop Through All GraphicRaycaster(UI) and throw Raycast to each one
for (int i = 0; i < grRayCast.Count; i++)
{
//Throw Raycast to all UI elements in the position(eventData)
grRayCast[i].Raycast(eventData, resultList);
sendArtificialUIEvent(grRayCast[i], eventData, evType);
}
//Reset Result
resultList.Clear();
}
if (WholeScreenPointer.Instance.simulateCollider2DEvent)
{
//Loop Through All Collider 2D (Sprite Renderer) and throw Raycast to each one
for (int i = 0; i < phy2dRayCast.Count; i++)
{
//Throw Raycast to all UI elements in the position(eventData)
phy2dRayCast[i].Raycast(eventData, resultList);
sendArtificialUIEvent(phy2dRayCast[i], eventData, evType);
}
//Reset Result
resultList.Clear();
}
if (WholeScreenPointer.Instance.simulateColliderEvent)
{
//Loop Through All Normal Collider(3D/Mesh Renderer) and throw Raycast to each one
for (int i = 0; i < phyRayCast.Count; i++)
{
//Throw Raycast to all UI elements in the position(eventData)
phyRayCast[i].Raycast(eventData, resultList);
sendArtificialUIEvent(phyRayCast[i], eventData, evType);
}
//Reset Result
resultList.Clear();
}
}
public void routeDragEvent(PointerEventData eventData)
{
routeOption(eventData, PointerEventType.Drag);
}
public void routePointerDownEvent(PointerEventData eventData)
{
routeOption(eventData, PointerEventType.Down);
}
public void routePointerUpEvent(PointerEventData eventData)
{
routeOption(eventData, PointerEventType.Up);
}
public void UpdateRaycaster()
{
convertToList(FindObjectsOfType<GraphicRaycaster>(), grRayCast);
convertToList(FindObjectsOfType<Physics2DRaycaster>(), phy2dRayCast);
convertToList(FindObjectsOfType<PhysicsRaycaster>(), phyRayCast);
}
//To avoid ToList() function
void convertToList(GraphicRaycaster[] fromComponent, List<GraphicRaycaster> toComponent)
{
//Clear and copy new Data
toComponent.Clear();
for (int i = 0; i < fromComponent.Length; i++)
{
toComponent.Add(fromComponent[i]);
}
}
//To avoid ToList() function
void convertToList(Physics2DRaycaster[] fromComponent, List<Physics2DRaycaster> toComponent)
{
//Clear and copy new Data
toComponent.Clear();
for (int i = 0; i < fromComponent.Length; i++)
{
toComponent.Add(fromComponent[i]);
}
}
//To avoid ToList() function
void convertToList(PhysicsRaycaster[] fromComponent, List<PhysicsRaycaster> toComponent)
{
//Clear and copy new Data
toComponent.Clear();
for (int i = 0; i < fromComponent.Length; i++)
{
toComponent.Add(fromComponent[i]);
}
}
//Checks if object is in the dictionary
private bool dictContains(GameObject obj)
{
return pointerIdToGameObject.ContainsValue(obj);
}
//Checks if int is in the dictionary
private bool dictContains(int pointerId)
{
return pointerIdToGameObject.ContainsKey(pointerId);
}
//Checks if int and object is in the dictionary
private bool dictContains(int pointerId, GameObject obj)
{
return (pointerIdToGameObject.ContainsKey(pointerId) && pointerIdToGameObject.ContainsValue(obj));
}
//Adds pointerId and its value to dictionary
private void dictAdd(int pointerId, GameObject obj)
{
pointerIdToGameObject.Add(pointerId, obj);
}
//Removes pointerId and its value from dictionary
private void dictRemove(int pointerId)
{
pointerIdToGameObject.Remove(pointerId);
}
public enum PointerEventType
{
Drag, Down, Up
}
}
用法:
1。将 WholeScreenPointer
脚本附加到一个空的游戏对象或相机。
2。要接收场景中的任何事件,只需在任何脚本中实现 IDragHandler
、IPointerDownHandler
、IPointerUpHandler
,然后调用 WholeScreenPointer.Instance.registerGameObject(this.gameObject);
一次。来自屏幕的任何事件现在都将发送到该脚本。不要忘记在 OnDisable()
函数中取消注册。
例如,将 Test
附加到您想要接收触摸事件的任何游戏对象:
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);
。如果您只想从当前对象接收事件,则不必调用它。如果这样做,您将收到多个事件。
其他重要功能:
启用全屏事件 - WholeScreenPointer.Instance.enablewholeScreenPointer(true);
禁用全屏事件 - WholeScreenPointer.Instance.enablewholeScreenPointer(false);
最后,这个可以再改进一下。