
持ち物をドラッグして装備欄に入れる、手札のカードをつかんで場に出す・・・。ボタンを押すだけでなく、UIを「つかんで動かす」操作が欲しくなることがありますよね。UnityのUIでは、スクリプトにイベントのインターフェースを書き足すだけで、マウスを乗せた・つかんだ・離したを受け取れます。今回は、それを使ってUIのドラッグ&ドロップを作ってみたいと思います!
この記事で学べること
- インターフェースとは何か(継承との違い)
- マウスを乗せた・外したを受け取る方法(IPointerEnterHandler・IPointerExitHandler)
- UIをドラッグして動かす方法(IBeginDragHandler・IDragHandler・IEndDragHandler)
- 置き場所で受け取る方法(IDropHandler)と、外で離したら元に戻す方法
- ドラッグ中にCanvas Groupの「Blocks Raycasts」を切る理由
前提知識
- Canvasの基本:EventSystemの役割
- 基本のUI要素:Image
- スクリプトからUIを更新しよう
- クラスを受け継ごう:継承とabstract:インターフェースとの違いで使います
プロジェクトの確認
- Unityのバージョンは6.3 LTS
- テンプレートはUniversal 2D(3Dでも同じように出来ます)
- HierarchyにCanvasとEventSystemがある
- EventSystemには、Input System UI Input Moduleが付いている
UIのイベントはどうやって受け取る?
ボタンを押したときは、Buttonコンポーネントの「On Click ()」に処理を登録しました。でも、ImageにはOn Clickのような欄がありません。
UIのマウス操作は、EventSystemが調べて、マウスの下にあるUIに「乗ったよ」「つかまれたよ」と知らせてくれます。その知らせを受け取るための印が、インターフェースです。
public class HoverScale : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
MonoBehaviourの後ろに、,で区切ってインターフェースの名前を並べる- インターフェースを書いたら、そのインターフェースが決めているメソッド(
OnPointerEnterなど)を必ずpublicで書く - EventSystemは、そのメソッドを持っているUIにだけ知らせてくれる
- 継承(6-14):親クラスは1つだけ。親の変数や処理の中身も受け継ぐ
- インターフェース:いくつでも付けられる。「このメソッドを持っています」という約束だけで、中身は自分で書く
よく使うインターフェースは次のとおりです。どれもusing UnityEngine.EventSystems;が必要です。
| インターフェース | 呼ばれるメソッド | いつ呼ばれる? |
|---|---|---|
| IPointerEnterHandler | OnPointerEnter | マウスが乗ったとき |
| IPointerExitHandler | OnPointerExit | マウスが外れたとき |
| IPointerDownHandler | OnPointerDown | ボタンを押したとき |
| IPointerUpHandler | OnPointerUp | ボタンを離したとき |
| IPointerClickHandler | OnPointerClick | クリックしたとき(押して、同じ場所で離した) |
| IBeginDragHandler | OnBeginDrag | ドラッグを始めたとき |
| IDragHandler | OnDrag | ドラッグ中、マウスが動くたび |
| IEndDragHandler | OnEndDrag | ドラッグをやめたとき |
| IDropHandler | OnDrop | ドラッグしてきたものを、自分の上で離されたとき |
画面を用意しよう
- Canvasの中に、UI>Image を1つ作り、名前を「Card」にする
- Width:200、Height:280
- Pos Y:-300(画面の下の方)
- 子にText – TextMeshProを作り、「カード」と入れる(なくてもOK)
- Canvasの中に、UI>Image を2つ作り、名前を「DropZoneLeft」「DropZoneRight」にする
- Width:240、Height:320
- Pos X:-300 と 300、Pos Y:150
- HierarchyでCardを一番下(DropZoneより下)に移動する
- Hierarchyで下にあるものほど、画面では手前に表示される

マウスを乗せたら大きくしよう
まずは一番かんたんな、マウスを乗せたら大きくなるスクリプトです。
using UnityEngine;
using UnityEngine.EventSystems;
// マウスを乗せると少し大きくなる
public class HoverScale : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
{
[SerializeField] private float hoverScale = 1.1f;
public void OnPointerEnter(PointerEventData eventData)
{
transform.localScale = Vector3.one * hoverScale;
}
public void OnPointerExit(PointerEventData eventData)
{
transform.localScale = Vector3.one;
}
}
- using UnityEngine.EventSystems;
- イベントのインターフェースを使うために必要
- PointerEventData eventData
- マウスの位置や、どのボタンが押されたかなど、イベントの情報が入っている
CardにHoverScaleを付けて再生し、マウスを乗せてみてください。少し大きくなって、外すと元に戻れば成功!

何も起きないときは、ImageのRaycast Targetにチェックが入っているか確認しましょう。チェックを外したUIには、EventSystemが知らせてくれません。
ドラッグして動かそう
次は、カードをつかんで動かせるようにします。
- CardにAdd Component>Canvas Group を付ける(理由はあとで説明します)
- 次のスクリプトを作って、Cardに付ける
using UnityEngine;
using UnityEngine.EventSystems;
// ドラッグで動かせる UI。置き場所以外で離したら元の位置に戻る
[RequireComponent(typeof(CanvasGroup))]
public class DragItem : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
private RectTransform rectTransform;
private CanvasGroup canvasGroup;
private Canvas canvas;
private Vector2 startPosition;
private bool isDropped;
private void Awake()
{
rectTransform = GetComponent<RectTransform>();
canvasGroup = GetComponent<CanvasGroup>();
canvas = GetComponentInParent<Canvas>();
}
public void OnBeginDrag(PointerEventData eventData)
{
startPosition = rectTransform.anchoredPosition;
isDropped = false;
canvasGroup.blocksRaycasts = false; // 自分の下にある置き場所に、マウスが届くようにする
transform.SetAsLastSibling(); // ほかの UI より手前に表示する
}
public void OnDrag(PointerEventData eventData)
{
// マウスが動いた分だけ動かす(Canvas の拡大率で割る)
rectTransform.anchoredPosition += eventData.delta / canvas.scaleFactor;
}
public void OnEndDrag(PointerEventData eventData)
{
canvasGroup.blocksRaycasts = true;
if (!isDropped)
{
rectTransform.anchoredPosition = startPosition; // 置き場所以外なら元に戻す
}
}
// 置き場所(DropZone)から呼ばれる
public void PlaceAt(RectTransform zone)
{
isDropped = true;
rectTransform.position = zone.position;
}
}
- [RequireComponent(typeof(CanvasGroup))]
- このスクリプトを付けると、Canvas Groupも自動で付く(付け忘れ防止)
- OnBeginDrag
- つかんだときの位置を覚えておく。
SetAsLastSiblingで、Hierarchyの一番下(=一番手前)に移す
- つかんだときの位置を覚えておく。
- OnDrag
eventData.deltaは、前のフレームからマウスが動いた量(ピクセル)- Canvas ScalerのUI Scale ModeをScale With Screen Sizeにしていると、UIは画面に合わせて拡大・縮小される。
canvas.scaleFactor(拡大率)で割って、マウスとぴったり同じだけ動くようにする - 作ったばかりのCanvas(Constant Pixel Size)では拡大率は1なので、割っても変わらない。あとで設定を変えてもずれないように、最初から割っておく
- OnEndDrag
- 置き場所に置かれなかったら、覚えておいた位置に戻す
- PlaceAt
- このあと作る置き場所から呼ばれる。置かれた印(isDropped)を付けて、置き場所の真ん中に動かす

Blocks Raycastsを切る理由
ドラッグ中のカードは、ちょうどマウスの真下にあります。そのままだと、EventSystemが「マウスの下にあるのはカード」と判断して、カードの下にある置き場所には何も知らせてくれません。
Canvas GroupのBlocks Raycastsをfalseにすると、そのUIは(子も含めて)マウスに反応しなくなり、下にある置き場所に知らせが届くようになります。離したらtrueに戻して、またつかめるようにしています。
スライムこれを知らなくて、「置き場所のOnDropが呼ばれない!」ってずっと悩んでました・・・



ドラッグ&ドロップで一番つまずくところだよね。「つかんでいる間は、マウスを下に通す」って覚えておこう!
置き場所で受け取ろう
最後に、置き場所のスクリプトです。ドラッグ中に上に来たら光って、離されたらカードを受け取ります。
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
// ドラッグしてきた UI を受け取る置き場所
public class DropZone : MonoBehaviour, IDropHandler, IPointerEnterHandler, IPointerExitHandler
{
[SerializeField] private Color normalColor = new Color(1f, 1f, 1f, 0.3f);
[SerializeField] private Color highlightColor = new Color(1f, 0.9f, 0.3f, 0.6f);
private Image image;
private void Awake()
{
image = GetComponent<Image>();
image.color = normalColor;
}
public void OnPointerEnter(PointerEventData eventData)
{
// 何かをドラッグしているときだけ光らせる
if (eventData.dragging)
{
image.color = highlightColor;
}
}
public void OnPointerExit(PointerEventData eventData)
{
image.color = normalColor;
}
public void OnDrop(PointerEventData eventData)
{
image.color = normalColor;
// ドラッグしてきたものが DragItem なら、ここに置く
if (eventData.pointerDrag == null)
{
return;
}
DragItem item = eventData.pointerDrag.GetComponent<DragItem>();
if (item == null)
{
return;
}
item.PlaceAt((RectTransform)transform);
Debug.Log($"{item.name}を{name}に置いた!");
}
}
- eventData.dragging
- 何かをドラッグしている最中ならtrue。ただマウスを乗せただけでは光らない
- eventData.pointerDrag
- 今ドラッグされているGameObject。ここからDragItemを取り出す
- OnDropとOnEndDragの順番
- 離したときは、置き場所のOnDropが先、カードのOnEndDragがあとに呼ばれる
- なので、OnDropでPlaceAt(isDroppedをtrue)→ OnEndDragで「置かれていないときだけ戻す」がうまくいく
DropZoneLeftとDropZoneRightの両方に、DropZoneを付けましょう。
置き場所を増やしたいときは、ImageにDropZoneを付けるだけでOK。置き場所とカードは、おたがいの数を知らなくても動くので、手札やインベントリのようにたくさん並べても、スクリプトはこのままで使えます。


動かして確認してみよう!
再生して、次のことを試してみてください。


- マウスを乗せると、カードが少し大きくなる
- ドラッグすると、カードがマウスにぴったりついてくる
- 置き場所の上に来ると、置き場所が黄色く光る
- 置き場所の上で離すと、置き場所の真ん中に置かれる。Consoleに「CardをDropZoneLeftに置いた!」
- 置き場所の外で離すと、つかむ前の位置に戻る
置いたカードをもう一度つかんで、別の置き場所に移すことも出来ます。外で離したときは、最後に置いてあった場所に戻ります。
よくあるエラーと対処法
マウスを乗せても、ドラッグしても何も起きない
- 原因:シーンにEventSystemがない。またはImageのRaycast Targetのチェックが外れている
- 対処法:Hierarchyに EventSystem があるか確認する(9-1)。ImageのRaycast Targetにチェックを入れる
置き場所のOnDropが呼ばれない
- 原因:ドラッグ中もカードのBlocks Raycastsがtrueのままで、カードがマウスをさえぎっている
- 対処法:OnBeginDragで
canvasGroup.blocksRaycasts = falseにする
カードがマウスより速く(遅く)動く
- 原因:
eventData.deltaをそのまま足している。Canvas ScalerがScale With Screen Sizeのときは、画面の大きさによってずれる - 対処法:
canvas.scaleFactorで割る
カードが置き場所の後ろに隠れる
- 原因:Hierarchyで、カードが置き場所より上にある(上にあるものほど奥に表示される)
- 対処法:OnBeginDragで
SetAsLastSibling()を呼ぶ
エラー:’DragItem’ does not implement interface member ‘IDragHandler.OnDrag(PointerEventData)’
- 原因:インターフェースを書いたのに、そのメソッドを書いていない。または
publicが付いていない - 対処法:
public void OnDrag(PointerEventData eventData)を書く
まとめ
- UIのマウス操作は、EventSystemが知らせてくれる。受け取るにはイベントのインターフェースを付ける
- インターフェースはいくつでも付けられる。決められたメソッドを
publicで書く - ドラッグは、OnBeginDrag・OnDrag・OnEndDragの3つ。動かす量は
eventData.delta / canvas.scaleFactor - ドラッグ中は、Canvas GroupのBlocks Raycastsを切って、下の置き場所にマウスを通す
- 置き場所はIDropHandlerで受け取る。OnDropはOnEndDragより先に呼ばれる
インターフェースをもっと詳しく知りたい人は、UIを操作するためのイベントインターフェースも参考にしてください。
次のステップ
- 手札のカードをドラッグして敵に使う回:デッキ構築カードゲームの作り方#6(準備中)
- Unityスタートガイド 目次
コメント