ゆくすぃごきげんよう、Budding Lab.編集部のゆくすぃです!
「街 ~ 運命の交差点 ~」というサウンドノベルをご存知でしょうか?シナリオ同士が影響し合う「ザッピングシステム」が衝撃でした!
ゲームが大好きな皆さんなら、一度は「自分でゲームを作ってみたい!」と、思ったことがあるのではないでしょうか?
本連載では、ゲーム開発初心者のゆくすぃが、基礎的なスクリプトだけを使って、超入門・パズルゲームの作り方を解説します!
本記事は、第5回「ドラッグ&ドロップの実装編」です。
プログラミングの専門知識がなくたって、画像や音楽作成アプリが使えなくたって、案外、ゲームって作れるものです。興味が湧いたなら、ぜひ挑戦してみてください!
2D脱出ゲームの作り方も解説しています。ご興味があれば、ぜひご覧ください!


ドラッグ&ドロップの実装
Scriptsフォルダの中に新規スクリプトを作成し、名前を付けて確定します。今回は「UIDragSnap.cs」としました。
ピースのドラッグ&ドロップを制御するスクリプト:UIDragSnap.cs
1 using UnityEngine;
2 using UnityEngine.EventSystems; // ドラッグ系のイベントを扱うために必要な名前空間
3
4 public class UIDragSnap : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
5 {
6 // IBeginDragHandler, IDragHandler, IEndDragHandlerで、ドラッグの開始・中・終了を検知する。
7
8 public RectTransform[] snapTargets; // スナップ先のUI要素:配列
9 public float snapThreshold = 50f; // スナップする距離のしきい値
10 private Vector3 originalPosition; // ドラッグし始めた時の位置を記録する変数
11 public RectTransform correctTarget; // このピースの正解のスナップ先
12 public bool
{ get; private set; } = false; // スナップ先の正誤を記録する変数:初期値は"false"
13
14 public void OnBeginDrag(PointerEventData eventData)
15 {
16 originalPosition = transform.position; // ドラッグ開始時に元の位置を記録
17 }
18
19 public void OnDrag(PointerEventData eventData)
20 {
21 // CanvasのRenderModeがOverlayならマウス位置をそのまま使える!
22 transform.position = Input.mousePosition;
23 }
24
25 public void OnEndDrag(PointerEventData eventData)
26 {
27 RectTransform closestTarget = null; // 一番近いスナップ先の位置:初期は"null"
28 float closestDistance = Mathf.Infinity; // 一番近いスナップ先との距離
29
30 foreach (RectTransform target in snapTargets) // 配列のスナップ先を順番にチェックしていくループ
31 {
32 // ドラッグ開始時の位置とスナップ先の距離を計算
33 float distance = Vector3.Distance(transform.position, target.position);
34
35 if (distance < closestDistance)
36 {
37 closestDistance = distance; // スナップ先との最短距離を更新
38 closestTarget = target; // 最短のスナップ先を更新
39 }
40 }
41
42 if (closestTarget != null && closestDistance < snapThreshold)
43 {
44 // 最短のスナップ先が更新されていて、かつ50fよりも近ければスナップ
45 transform.position = closestTarget.position;
46 IsSnappedCorrectly = (closestTarget == correctTarget);
47 }
48 else
49 {
50 // 条件を満たさなければ元の場所に戻る
51 transform.position = originalPosition;
52 }
53 }
54 }
このスクリプトの大まかな流れは以下の通りです。
- STEP2以降で利用する変数を定義する。




- ドラッグ開始時のピースの元の位置を記録する。
- ドラッグ終了時のピースの位置がスナップポイントと離れ過ぎていた時に、ピースを元の位置へ戻すために記録しておく。
- ドラッグするマウスの動きに合わせてピースを動かす。
- マウスの位置をピースの位置に代入し、マウスの動きに合わせてピースを移動させる。
- ドラッグ終了時にピースの位置を決める。
- ドラッグ終了時のピースの位置と、一番近いスナップポイントとの距離を計算する。
- その距離がしきい値よりも小さければ、スナップポイントに吸着させる。
- その距離がしきい値よりも大きければ、ドラッグ開始時の元の位置に戻す。
EventSystems 名前空間を追記する
2行目に「using UnityEngine.EventSystems;」を追記することで「EventSystems 名前空間」に属するドラッグ系のイベント(IBeginDragHandler, IDragHandler, IEndDragHandler)を使えるようになります。これらを「public class UIDragSnap : MonoBehaviour」の後に付け加えてください。
- IBeginDragHandler:ドラッグ開始時の処理
自動的に OnBeginDrag(PointerEventData eventData) を呼び出す。 - IDragHandler:ドラッグ中の処理
自動的に OnDrag(PointerEventData eventData) を呼び出す。 - IEndDragHandler:ドラッグ終了時の処理
自動的に OnEndDrag(PointerEventData eventData) を呼び出す。
※「eventData」の部分は任意の変数名で良い。「OnDrag(PointerEventData e)」とかでもOK。
※「(PointerEventData eventData)」という引数までがセット。
※「(PointerEventData eventData)」には、マウス位置(eventData.position)、前フレームからの移動量(eventData.delta)などの情報が詰まっている。
変数を定義する
青文字の部分で、変数を定義していきます。
- public RectTransform[] snapTargets; ・・・ スナップポイントのUI要素
RectTransform型の配列、Inspectorウインドウに複数の「変数:Snap Targets」を指定できる。
(第3回:ゲーム画面の作成編でスナップポイントにした「GameObject」を参照させる) - public float snapThreshold = 50f; ・・・ スナップする距離のしきい値
float型の変数、初期値に50fを設定している。Inspectorウインドウで変更も可能。
(スナップポイントとの距離がほぼ50px以内なら吸着する、という設定用) - private Vector3 originalPosition; ・・・ ドラッグ開始時の位置
Vector3型の変数、「private」なのでInspectorウインドウに表示されない。
(ドラッグ開始時の位置を記録して、スナップ失敗時に元の位置へ戻すのに使う) - public RectTransform correctTarget; ・・・ このピースの正解のスナップ先
RectTransform型の変数、Inspectorウインドウに「変数:Correct Target」を指定できる。
(このスクリプトをアタッチされたピースの正解となるスナップ先を参照させる) - public bool IsSnappedCorrectly { get; private set; } = false; ・・・ スナップ先の正誤を記録
bool型の変数、他のスクリプトから取得はできるが、更新は同一クラス内でのみ可能。
(このスクリプトをアタッチされたピースが、正解のスナップ先に吸着したかどうかを記録する)
ドラッグ開始時の処理を書き込む
14行目~17行目の「OnBeginDrag(PointerEventData eventData) 」に、ドラッグ開始と同時に実行したい処理を書き込みます。
public void OnBeginDrag(PointerEventData eventData) // IBeginDragHandler実装で使える
{
// 変数:originalPositionにドラッグ開始時の位置(ワールド座標)を記録
originalPosition = transform.position;
}
これで、スナップが失敗した時にピースを元の位置(originalPosition)に戻せるようになる。
ドラッグ中の処理を書き込む
19行目~23行目の「OnDrag(PointerEventData eventData)」に、ドラッグ中に実行したい処理を書き込みます。


public void OnDrag(PointerEventData eventData) // IDragHandler実装で使える
{
// CanvasのRenderModeがOverlayならマウス位置をそのまま使える
transform.position = Input.mousePosition;
}
マウスを動かすと、その座標をピースの位置に代入するので、ピースがマウスに追従して動く。
ドラッグ終了時の処理を書き込む
25行目~53行目の「OnEndDrag(PointerEventData eventData)」に、ドラッグ終了と同時に実行したい処理を書き込みます。


public void OnEndDrag(PointerEventData eventData) // IEndDragHandler実装で使える
{
// RectTransform型の変数、一番近いスナップポイントの位置:初期は”null”
RectTransform closestTarget = null;
// float型の変数、一番近いスナップポイントとの距離:初期は”Mathf.Infinity;”(無限大)
float closestDistance = Mathf.Infinity;
// 配列のスナップ先を順番にチェックしていくループ
// foreach (型 変数 in 配列):指定した配列の全ての要素に反復処理を行う
foreach (RectTransform target in snapTargets) // 配列 snapTargets の全ての要素に処理を行う
{
// float型の変数 distance を定義
// Vector3.Distance(a, b); は、aとbの間の距離を計算する
// transform.position = ドラッグしているピースの位置
// target.position = スナップポイントの位置
// つまり、ドラッグ開始時の位置とスナップポイントとの距離を計算している
float distance = Vector3.Distance(transform.position, target.position);
// ドラッグ中のピースと一番近いスナップポイントとの距離がclosestDistanceより小さい時、
if (distance < closestDistance)
{
closestDistance = distance; // スナップポイントとの最短距離を更新
closestTarget = target; // 最短のスナップポイントを更新
}
}
// 最短のスナップ先が初期値から更新されていて、かつ50fよりも近ければスナップ
// “!=” は “closestTarget” が “null” でなければ “true” を返す
if (closestTarget != null && closestDistance < snapThreshold)
{
// 一番近いスナップポイントの位置をピースの現在位置に代入
transform.position = closestTarget.position;
// closestTargetとcorrectTargetが一致していれば、IsSnappedCorrectlyに “true” を代入
// closestTargetとcorrectTargetが一致していなければ、IsSnappedCorrectlyに “false” を代入
IsSnappedCorrectly = (closestTarget == correctTarget);
}
else
{
// 条件を満たさなければ元の場所に戻る
transform.position = originalPosition;
}
}
これで・・・
- ピースの位置と一番近いスナップポイントとの距離を計算
- その距離が 50f 以内ならスナップポイントに吸着させる
- その距離が 50f より大きければ、ドラッグ開始時の元の場所にピースを戻す
- 吸着したスナップポイントの正誤判定を行い、結果を IsSnappedCorrectly に代入する → クリア判定に利用する
・・・という一連の流れが処理される。
スクリプトをアタッチする
満を持して、スクリプト「UIDragSnap.cs」をゲームオブジェクトにアタッチしてみましょう!
このスクリプトは、マウスでドラッグ&ドロップするゲームオブジェクト = インベントリにある5つのピース(ドラッグ操作用ピース)にアタッチします。
「UIDragSnap.cs」を、Scriptsフォルダからドラッグ操作用ピースのInspectorウインドウにドラッグ&ドロップするか、Inspectorウィンドウの「Add Component」から選択することでアタッチできます。
UIDragSnap.csをアタッチしたInspectorウィンドウの様子


- Snap Targets
スナップポイントの数「5」と入力 - Element 0 ~ 4
スナップポイントとなるゲームオブジェクトを参照させる- Element 0 – 3:スナップポイント
- Element 4:ドラッグ操作用ピースが最初にあった場所(インベントリの中)
→ Element 0 – 3 のどこにも置きたくない場合に戻せるホームポイントとして登録
- Snap Threshold
吸着判断用のしきい値、初期値:50f が代入されている - Correct Target
このドラッグ操作用ピースが吸着するべき、正解のスナップポイントを参照させる
すべてのドラッグ操作用ピースにアタッチが済んだら、再生モードで動作を確認してみてください。



インベントリのピースをドラッグして、任意のスナップポイントまで移動させることができましたか?ピタッと吸い付いてくれる感覚は、ちょっとした感動ですね!
まとめ
今回の記事では、ドラッグ&ドロップを実装するスクリプトについて解説しました。ゆくすぃ自身、ゲームオブジェクトがマウスのドラッグによって移動し、スナップポイントに吸着する仕組みを理解するとても貴重な機会となりました。
ここまで来れば、あとはクリア判定だけです!ゲーム完成までもうひと踏ん張りなので、ぜひ一緒に頑張りましょう!
次回は、【第6回】超入門!Unityでパズルゲームを作ろう!「クリア判定とビルド編」です。
以上、最後まで読んでいただき有難うございました!









