uGUIにおけるポインターイベントの伝播 (Unity)

# 前提
### 環境
- Unity 2019.4.6 (LTS)

### 目的
- uGUIにおける主なポインターイベントのバブリングを研究します。
- uGUIで、ボタンの反応する範囲をデザイン上の見た目よりも広く取りたい場合に、簡易に、`Button`の子に大きくて透明な`Image`を配して済ますことがあると思います。
    - ![コメント 2020-08-15 131611.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/365845/a56204dc-7d7d-0976-aefe-d04f1aaf5ad1.png) 
    - このとき、`Image`が受け取ったポインターイベントは、`Button`に伝播されます。
- あるいは、画面上に複数配置されたボタンを一斉にブロックしたい場合に、ボタンと同じか上の階層に全体を覆う透明な`Image`を置いて済ませたりします。
    - すると、`Image`がイベントを横取りして、ボタンには届かなくなります。
- 「子が受け取ったポインターイベントは親に伝わる」という漠然とした理解を、少しだけ深めます。

### 公式ドキュメント
- [Unity UI Event System (マニュアル)](https://docs.unity3d.com/ja/2019.4/Manual/EventSystem.html)
- [UnityEngine.EventSystems.PointerEventData (スクリプトリファレンス)](https://docs.unity3d.com/ja/2018.4/ScriptReference/EventSystems.PointerEventData.html)

# イベントの伝播を確認する
### 準備
- ▼♻Scene
    - ▼🧊Canvas
        - ▼🧊Obj (0)
            - ▼🧊Obj (1)
                -  🧊Obj (2)
    -  🧊EventSystem

※`Obj (0～2)`には、イベントを受け取る`Image`と、受け取ったイベントと`hovered`を報告する`CheckPointerEvent`をアタッチ
<details><summary>`CheckPointerEvent`の内容</summary><div>

```cs:CheckPointerEvent.cs
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.EventSystems;

public class CheckPointerEvent : MonoBehaviour
	, IBeginDragHandler, IDragHandler, IEndDragHandler, IDropHandler  // ドラッグ
	, IPointerEnterHandler, IPointerExitHandler // 領域出入
	, IPointerUpHandler, IPointerDownHandler, IPointerClickHandler // クリック
{

	public void OnBeginDrag (PointerEventData eventData) {
		Debug.Log ($"{name}.OnBeginDrag [{string.Join (", ", eventData.hovered.ConvertAll (o => o.name))}]");
	}

	public void OnEndDrag (PointerEventData eventData) {
		Debug.Log ($"{name}.OnEndDrag [{string.Join (", ", eventData.hovered.ConvertAll (o => o.name))}]");
	}

	public void OnDrop (PointerEventData eventData) {
		Debug.Log ($"{name}.OnDrop [{string.Join (", ", eventData.hovered.ConvertAll (o => o.name))}]");
	}

	private List<GameObject> lastDragHoverd; // 最後のドラッグ・ログ
	public void OnDrag (PointerEventData eventData) {
		if (lastDragHoverd == null || !lastDragHoverd.SequenceEqual (eventData.hovered)) { // 異なるときだけ
			Debug.Log ($"{name}.OnDrag [{string.Join (", ", eventData.hovered.ConvertAll (o => o.name))}]");
			lastDragHoverd = new List<GameObject> (eventData.hovered);
		}
	}

	public void OnPointerEnter (PointerEventData eventData) {
		Debug.Log ($"{name}.OnPointerEnter [{string.Join (", ", eventData.hovered.ConvertAll (o => o.name))}]");
	}

	public void OnPointerExit (PointerEventData eventData) {
		Debug.Log ($"{name}.OnPointerExit [{string.Join (", ", eventData.hovered.ConvertAll (o => o.name))}]");
	}

	public void OnPointerUp (PointerEventData eventData) {
		Debug.Log ($"{name}.OnPointerUp [{string.Join (", ", eventData.hovered.ConvertAll (o => o.name))}]");
	}

	public void OnPointerDown (PointerEventData eventData) {
		Debug.Log ($"{name}.OnPointerDown [{string.Join(", ", eventData.hovered.ConvertAll (o => o.name))}]");
	}

	public void OnPointerClick (PointerEventData eventData) {
		Debug.Log ($"{name}.OnPointerClick [{string.Join (", ", eventData.hovered.ConvertAll (o => o.name))}]");
	}

}
```
</div></details>

### 試行と結果

```:親「エンター、ダウン、ドラッグ、アップ、イグジット」
Obj (0).OnPointerEnter []
Obj (0).OnPointerDown [Obj (0), Canvas]
Obj (0).OnBeginDrag [Obj (0), Canvas]
Obj (0).OnPointerUp [Obj (0), Canvas]
Obj (0).OnPointerClick [Obj (0), Canvas]
Obj (0).OnEndDrag [Obj (0), Canvas]
Obj (0).OnPointerExit [Obj (0), Canvas]
```

```:親「エンター、ダウン、ドラッグ、イグジット、ドラッグ、アップ」
Obj (0).OnPointerEnter []
Obj (0).OnPointerDown [Obj (0), Canvas]
Obj (0).OnBeginDrag [Obj (0), Canvas]
Obj (0).OnDrag [Obj (0), Canvas]
Obj (0).OnPointerExit [Obj (0), Canvas]
Obj (0).OnDrag []
Obj (0).OnPointerUp []
```

```:子「エンター、ダウン、ドラッグ、アップ、イグジット」
Obj (1).OnPointerEnter []
Obj (0).OnPointerEnter [Obj (1)]
Obj (1).OnPointerDown [Obj (1), Obj (0), Canvas]
Obj (1).OnBeginDrag [Obj (1), Obj (0), Canvas]
Obj (1).OnDrag [Obj (1), Obj (0), Canvas]
Obj (1).OnPointerUp [Obj (1), Obj (0), Canvas]
Obj (1).OnPointerClick [Obj (1), Obj (0), Canvas]
Obj (1).OnEndDrag [Obj (1), Obj (0), Canvas]
Obj (1).OnPointerExit [Obj (1), Obj (0), Canvas]
Obj (0).OnPointerExit [Obj (1), Obj (0), Canvas]
```

```:子「エンター、ダウン、ドラッグ、イグジット、ドラッグ、アップ」
Obj (1).OnPointerEnter []
Obj (0).OnPointerEnter [Obj (1)]
Obj (1).OnPointerDown [Obj (1), Obj (0), Canvas]
Obj (1).OnBeginDrag [Obj (1), Obj (0), Canvas]
Obj (1).OnDrag [Obj (1), Obj (0), Canvas]
Obj (1).OnPointerExit [Obj (1), Obj (0), Canvas]
Obj (0).OnPointerExit [Obj (1), Obj (0), Canvas]
Obj (1).OnDrag []
Obj (1).OnPointerUp []
Obj (1).OnEndDrag []
```

```:孫「エンター、ダウン、ドラッグ、アップ、イグジット」
Obj (2).OnPointerEnter []
Obj (1).OnPointerEnter [Obj (2)]
Obj (0).OnPointerEnter [Obj (2), Obj (1)]
Obj (2).OnPointerDown [Obj (2), Obj (1), Obj (0), Canvas]
Obj (2).OnBeginDrag [Obj (2), Obj (1), Obj (0), Canvas]
Obj (2).OnDrag [Obj (2), Obj (1), Obj (0), Canvas]
Obj (2).OnPointerUp [Obj (2), Obj (1), Obj (0), Canvas]
Obj (2).OnPointerClick [Obj (2), Obj (1), Obj (0), Canvas]
Obj (2).OnEndDrag [Obj (2), Obj (1), Obj (0), Canvas]
Obj (2).OnPointerExit [Obj (2), Obj (1), Obj (0), Canvas]
Obj (1).OnPointerExit [Obj (2), Obj (1), Obj (0), Canvas]
Obj (0).OnPointerExit [Obj (2), Obj (1), Obj (0), Canvas]
```

```:孫「エンター、ダウン、ドラッグ、イグジット、ドラッグ、アップ」
Obj (2).OnPointerEnter []
Obj (1).OnPointerEnter [Obj (2)]
Obj (0).OnPointerEnter [Obj (2), Obj (1)]
Obj (2).OnPointerDown [Obj (2), Obj (1), Obj (0), Canvas]
Obj (2).OnBeginDrag [Obj (2), Obj (1), Obj (0), Canvas]
Obj (2).OnPointerExit [Obj (2), Obj (1), Obj (0), Canvas]
Obj (1).OnPointerExit [Obj (2), Obj (1), Obj (0), Canvas]
Obj (0).OnPointerExit [Obj (2), Obj (1), Obj (0), Canvas]
Obj (2).OnDrag []
Obj (2).OnPointerUp []
Obj (2).OnEndDrag []
```

- 他に、ドラッグして他のオブジェクトにドロップすると、落とされたオブジェクトにのみ`OnDrop`が届く様子を確認できました。

# 考察
### 結果の解釈
- `OnPointerEnter `と`OnPointerExit `はターゲット以降もバブルアップ(親へ伝播)されます。
    - その他(クリック、ドラッグ)はターゲットで留められてバブルアップされません。
    - このイベントによる違いは、ボタンを親子に配置することでも確認できます。
        - 子ボタンをポインターで指すと、親ボタンも`Highlighted`状態になりますが、クリックしても親ボタンには伝わりません。
- `hovered`は、[「マウスが上にのっているオブジェクトのスタックリスト」と説明されています](https://docs.unity3d.com/ja/2018.4/ScriptReference/EventSystems.PointerEventData-hovered.html)が、イベント発生直前の親を辿るようです。
    - 特に、`OnPointerEnter`では侵入直前の状態を示すため、ポインターが孫に侵入した場合、孫では侵入前なので何もなし、バブルアップされた子では孫に侵入済み、さらにバブルアップされた親では孫と子に侵入済みとなるようです。
    - 他のイベントの多くは、直前が既にオブジェクトに乗った状態なので、総親の`Canvas`まで辿られています。
    - 対して、オブジェクトから外れてからドラッグを終えると、何にも乗っていないことになります。

### 疑問
- イベントによって、ターゲットがイベントを受け取った後のバブルアップの有無が異なるようですが、どのようなルールなのでしょうか?
    - 固定的なものなのか、設定あるいは制御可能なものなのでしょうか?
- いろいろ調べて見ましたが、関係するような記述は発見できませんでした。
    - 制御できても良さそうなものですが、「そういうもの」として設計されているということでしょうか。