【Unity学习笔记】使用鼠标和3D空间的物体进行交互的方法

## 前言
鼠标操作的一些游戏中，鼠标不仅仅会和UI元素交互，类似LOL，Dota，星际争霸等俯视视角的策略游戏中，鼠标也需要和3D场景中的物体进行各种交互。
当然，自己编写脚本进行坐标换算，射线检测等方法即可实现此功能。但，有没有一种简单快速的实现方法呢？
答案是有的，Unity提供了一套非常方便的控制器交互的方案，包括了鼠标，键盘，手柄和触屏，那就是EventSystem和StandaloneInputModule。
本文介绍一下利用鼠标时如何快速实现与3D空间的物体交互的方法。

**本文使用的Unity版本：Unity 2019.3.0f3**

### 1. 在Hierarchy上添加EventSystem
![コメント 2019-12-24 222308.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265897/3635ab88-ccee-271b-26d2-a4fa8f010b34.png)

**新建的EventSystem物体上默认附带一个EventSytem控件和StandaloneInputModule。各项属性保持默认值即可。**
![コメント 2019-12-24 223511.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265897/01822d76-f80f-0f5c-7eef-7c60872d1272.png)

### 2. 添加射线投射器，Raycaster控件
Unity中的鼠标交互，是使用射线检测（RayCast）来实现的。

| 控件名            |       作用 |    使用方法    |接受体|
|:-----------------|:------------------|:--------------|:------------------|
| Graphic Raycaster| 与UI组件进行交互 |附着在Canvas上|带有继承了Graphic类的控件的物体，例如Image，Text等|
| Physics Raycaster| 与3D物体进行交互 |   附着在Camera上          |带有Collider的物体|
| Physics 2D Raycaster|与2D物体进行交互|    附着在Camera上         |带有Collider2D的物体|

本文需要与3D物体进行交互，因此在主相机上加上Physics Raycaster。(图中的黄色叹号是由于该测试版本下Unity的Bug产生的，实际对使用并没有影响)
![コメント 2019-12-24 231724.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265897/e2df3694-2c1f-1837-2493-f8458d8e74f8.png)

### 3. 创建一些带有Collider的3D物体在场景中。(本文为方便查看效果，在3D物体的基础上制作了弹出文本的UI功能)
![コメント 2019-12-24 232518.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265897/31638c89-ece1-a6ad-ebda-b3c6909db71c.png)
![コメント 2019-12-24 232606.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265897/349b2e76-6c8c-630b-e4ab-f429da4374ab.png)

### 4. 创建接收交互事件的脚本
Raycaster投射出射线，射中接受体后会向该物体和其父级物体发送信息。我们只要在该物体或者其父级的物体上创建继承对应消息的接口并实现对应的接收函数的脚本即可。

EventSystem中提供的信息接口如下：

- IPointerEnterHandler - OnPointerEnter - 当鼠标指针进入时触发
- IPointerExitHandler - OnPointerExit - 当鼠标指针离开时触发
- IPointerDownHandler - OnPointerDown - 当鼠标指针按下时触发
- IPointerUpHandler - OnPointerUp - 当鼠标指针松开时触发
- IPointerClickHandler - OnPointerClick - 当鼠标指针按下并且送开时触发
- IInitializePotentialDragHandler - OnInitializePotentialDrag - 当发现拖拽对象时触发，可以用于初始化数值。
- IBeginDragHandler - OnBeginDrag - 当拖拽开始时，在拖拽对象物体上触发
- IDragHandler - OnDrag - 当拖拽进行中，在拖拽对象物体上触发
- IEndDragHandler - OnEndDrag - 当拖拽结束时，在拖拽对象物体上触发
- IDropHandler - OnDrop - 在拖拽结束时，在鼠标指针停留的物体上触发
- IScrollHandler - OnScroll - 当鼠标滚轮滚动时触发
- IUpdateSelectedHandler - OnUpdateSelected - 在被选择的物体上每一帧都会触发
- ISelectHandler - OnSelect - 当被选中时触发
- IDeselectHandler - OnDeselect - 当被取消选中时触发
- IMoveHandler - OnMove - 当方向操作被执行时触发
- ISubmitHandler - OnSubmit - 当决定按键被按下时触发
- ICancelHandler - OnCancel - 当取消案件被按下时触发

创建一个Cube.cs脚本，继承IPointerEnterHandler，IPointerExitHandler，IPointerDownHandler，IPointerUpHandler，并实现对应的函数。

```csharp:Cube.cs
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;

public class Cube : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler, IPointerDownHandler, IPointerUpHandler
{
    [SerializeField]
    private Animator animator;
    [SerializeField]
    private Canvas canvas;
    [SerializeField]
    private Text text;

    private Camera mainCamera;

    private void Start()
    {
        mainCamera = Camera.main;
        canvas.worldCamera = mainCamera;
    }

    private void Update()
    {
        canvas.transform.rotation = mainCamera.transform.rotation;
    }

    private void PopText(string str)
    {
        text.text = str;
        animator.SetTrigger("Pop");
    }

    public void OnPointerEnter(PointerEventData eventData)
    {
        PopText("PointerEnter");
    }

    public void OnPointerExit(PointerEventData eventData)
    {
        PopText("PointerExit");
    }

    public void OnPointerDown(PointerEventData eventData)
    {
        PopText("PointerDown");
    }

    public void OnPointerUp(PointerEventData eventData)
    {
        PopText("PointerUp");
    }
}
```

### 5. 结果预览
当鼠标移入，移除，按下，松开时分别都会执行对应的函数。
![5.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265897/32c420f0-a2bd-e4e6-f064-31e2b6064339.gif)

## 总结
1. 在Hierarchy上创建EventSystem。
2. 在Camera上添加Physics Raycaster
3. 在场景上需要互动的3D物体上添加Collider，并编写继承所需要的交互事件的接口并实现接收函数。