ボタンをドラッグで移動させ特定の場所で固定させる処理を実装する[Unity UI]

Unity

ボタンをドラッグ時に移動させる!

タイトルのことを実装してみたいと思います。
正直この文字だけ見ても訳わかめだと思います。

もはや私自身が訳わかめなのですが、私のつたない言葉だけでやりたいことを伝えようとすると
この表現で精いっぱいでした、ごめんなさい、言葉って難しいですね 笑

ということでまずは映像で見たほうがイメージ沸くかと
思いますので以下を見てみてください。

分かりましたでしょうか?

こんな処理って結構どこでも使いそうだと思いましたので
作ってみたいと思います。
ではいきましょー。

今回は以下のような処理を作っていこうかと思っています。

1.ボタン押下でボタン①を生成
2.ボタン①をドラッグして移動させる
(ボタン①が特定の領域に移動した場合、そこに移動(定着)する)

1.ボタン押下でボタン①を生成

単純なドラッグ処理だけを実装するのでは面白くないと思ったので
そもそもドラッグ処理を行うボタン自体を生成するところから
作っていこうと思います。

ということでまずはボタン①を生成するボタンを作成します。
これは単純にボタンを作るだけですね。

一応説明すると
Hierarchyウィンドウで右クリック→UI→Button
でScene上にボタンが生成されます。

でこのボタンを押したときに
更にボタンが生成される処理を作っていきたいので
スクリプトを作りましょう。

Projectウィンドウ上で右クリック→Create→C#Scriptで作成できます。
今回作成するスクリプトの名前はCreateButtonとしました!

作成できたスクリプトを開いて以下のように記述します。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CreateButton : MonoBehaviour
{
    [SerializeField]
    private GameObject button1;
    [SerializeField]
    private GameObject canvas;

    public void createButton()
    {
        GameObject button = Instantiate(button1, canvas.transform);
        button.AddComponent<DragDropScript>();
    }
}

createButtonメソッドが重要ですね。
その中身は2行だけですがひとつひとつ解説していきます。

まずはボタンを生成しますので
Instantiate関数を使用します。
第一引数:生成するオブジェクト
第二引数:オブジェクトを生成する位置
こんな感じです。

次の行は生成したボタンに対しドラッグ時に移動したりする
スクリプトをアタッチしています。
ただDragDropScriptはまだ作っていないのでエラーになるかと思いますが
一旦そのまま置いておきましょう。

次に作成したスクリプトがボタンを押したときに実行されるようにしましょう。
これにはボタンのOnClick(押したときに動作する)を使います

まずはゲーム上でスクリプトを認識できるようにします。
Create Emptyで空のゲームオブジェクトを作成します。

で、ここに上記で作成したスクリプトをアタッチします。


次にこのゲームオブジェクトをOnClickで呼び出すようにします。
やり方は以下の動画を参考にしてください。

これで第一弾は完了です。
次にこのドラッグ時に呼び出されるスクリプトを作成しましょう

2.ボタン①をドラッグして移動させる

まずこちらの処理は以下のサイトを参考にさせていただきました。

[UI] ドラッグアンドドロップでUIのイメージを動かす : ねぎたまらぼ
ドラッグアンドドロップでUIのイメージを動かしてみます。決められた範囲内にドロップすると固定されます。

実装する処理は以下になります。
1.ドラッグ開始時の処理→初期位置の保存
2.ドラッグ最中の処理→ボタンをドラッグ場所に移動させる
3.ドラッグ終了時の処理→元の初期位置に移動させる
4.ドロップ時の処理→特定の位置にボタンが移動していたらその位置にボタンを移動させる

まずは未作成だったDragDropScriptという名前のスクリプトを作成しましょう。

1.ドラッグ開始時の処理→初期位置の保存

こちらを実装するには以下メソッドを作成する必要があります。
このまま以下をコピペでよいです。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;

public class DragDropScript : MonoBehaviour, IBeginDragHandler
{
    private Vector2 prevPosition;

    /// <summary>
    /// ドラッグ開始時に呼び出される
    /// </summary>
    /// <param name="eventData"></param>
    public void OnBeginDrag(PointerEventData eventData)
    {
        prevPosition = transform.position;
    }
}

単純にOnBeginDragというメソッドを書くだけでは
ゲーム開始してもドラッグ時にOnBeginDragメソッドは呼び出されません。

この状態では今作成したスクリプトのメソッドとしてOnBeginDragメソッド
があるだけになっちゃいます。

このOnBeginDragはIBeginDragHandlerというクラスのメソッドなので
IBeginDragHandlerを継承する必要があります

OnBeginDragを使いたいならIBeginDragHandlerもスクリプト上に追加
しないといけないよってことです。
ちなみにIBeginDragHandlerを追加するためには
using UnityEngine.EventSystems;
も必要です。

初心者の人は訳わからんですよね…ということで理解したい方のために無理やり別で例えてみます。

IBeginDragHandlerを追加してない状態でOnBeginDragメソッドを実装した場合は
あなたが有名人と同姓同名の一般人と友達みたいなものです。

その状態で別の友達に対し「俺、有名人〇〇〇と友達だよ」って言った場合
友達は「え、マジで!あの人と友達なの!?今度会わせて!!」
ってなりますよね。

でも実体はただの一般人なので実際に会った際に別の友達は
「全然違うじゃん、嘘つき!」とあなたに言うはずです。
これは悲しいですね。

IBeginDragHandlerを追加すると本当にあなたは有名人と友達であるという
権利を受け取れるんです。これで安心して友達にもちゃんと自慢できますね!

というわけで…こんな感じです。ごめんなさい、そんなにいい例えでないことは十分
承知いたしております…。しかしこれが今の私の限界のようです。

何言ってんだ!訳分からんぞ!って方は単純に上記を真似てスクリプトを作ってみてください。

ちなみに引数で出てくるPointEventDataは何かというと
ドラッグしているマウスの情報を取得できるようです。

さらにちなみにスマホのタッチ操作もマウスの操作と一緒と考えることが
出来るようなっているので、タッチ操作の場合も同様の記載でタッチの情報を取得できます。

EventSystems.PointerEventData - Unity スクリプトリファレンス
ポインタ(マウス/タッチ)イベントに関連するイベントの情報データ

2.ドラッグ最中の処理→ボタンをドラッグ場所に移動させる

こちらは上記1.と理屈は同じです。
IDragHandlerとOnDragを追加しましょう。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;

public class DragDropScript : MonoBehaviour, IBeginDragHandler, IDragHandler
{
    private Vector2 prevPosition;

    /// <summary>
    /// ドラッグ開始時に呼び出される
    /// </summary>
    /// <param name="eventData"></param>
    public void OnBeginDrag(PointerEventData eventData)
    {
        prevPosition = transform.position;
    }

    /// <summary>
    /// ドラッグ中に呼び出される
    /// </summary>
    /// <param name="eventData"></param>
    public void OnDrag(PointerEventData eventData)
    {
        transform.position = eventData.position;
    }
}


OnDragはドラッグ中の処理を実行することが出来ます。
transform.position = eventData.position;
とすることでボタンの位置をドラッグしているマウスの位置に
変更することができます。

3.ドラッグ終了時の処理→元の初期位置に移動させる

こちらも上記1.と2.と理屈は同じです。
IEndDragHandlerとOnEndDragを実装してあげます。

で、ドラッグが終わった場合は1.で保存しておいた
移動前の位置にボタンを戻してあげる処理を追加しましょう。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;

public class DragDropScript : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
    private Vector2 prePosition;

    /// <summary>
    /// ドラッグ開始時に呼び出される
    /// </summary>
    /// <param name="eventData"></param>
    public void OnBeginDrag(PointerEventData eventData)
    {
        prePosition = transform.position;
    }

    /// <summary>
    /// ドラッグ中に呼び出される
    /// </summary>
    /// <param name="eventData"></param>
    public void OnDrag(PointerEventData eventData)
    {
        transform.position = eventData.position;
    }

    /// <summary>
    /// ドラッグ終わりに呼び出される
    /// </summary>
    /// <param name="eventData"></param>
    public void OnEndDrag(PointerEventData eventData)
    {
        transform.position = prePosition;
    }

}

4.ドロップ時の処理→特定の位置にボタンが移動していたらその位置にボタンを移動させる

何度もくどいですがこちらも上記と理屈は同じです。

IDropHandlerとOnDropを実装してあげます。
OnDropはドラッグ&ドロップのドロップ時の処理を記載できます。

ドラッグの終了とドロップって一緒じゃないの!?という
気もしますが、呼ばれる順としてはOnEndDrag→OnDropだと思います。
気になるのでDebug.Logで確認してみましょう。

まずスクリプトは以下のように記載しました。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;

public class DragDropScript : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler, IDropHandler
{
    private Vector2 prePosition;
    private int count;

    /// <summary>
    /// ドラッグ開始時に呼び出される
    /// </summary>
    /// <param name="eventData"></param>
    public void OnBeginDrag(PointerEventData eventData)
    {
        prePosition = transform.position;
        count++;
        Debug.Log("OnBeginDrag " + count);
    }

    /// <summary>
    /// ドラッグ中に呼び出される
    /// </summary>
    /// <param name="eventData"></param>
    public void OnDrag(PointerEventData eventData)
    {
        transform.position = eventData.position;
    }

    /// <summary>
    /// ドラッグ終わりに呼び出される
    /// </summary>
    /// <param name="eventData"></param>
    public void OnEndDrag(PointerEventData eventData)
    {
        transform.position = prePosition;
        count++;
        Debug.Log("OnEndDrag " + count);
    }

    /// <summary>
    /// ドロップ時に呼び出される
    /// </summary>
    /// <param name="eventData"></param>
    public void OnDrop(PointerEventData eventData)
    {
        count++;
        Debug.Log("OnDrop " + count);
    }

}

countという整数が加算されていくのでどちらが先に呼び出されるか分かりますね。
これで実行してみた結果のコンソールの出力が以下になります。

おぉ!? OnEndDragよりOnDropの方が先に呼ばれていますね…。

完全に予想と反しましたのでちょっと方向転換します。
OnDropなしでもよさそうなので今回はOnDropなしで
作成していこうと思います。

最後の処理!特定の位置にボタンが移動していたらその位置にボタンを移動させる

それでは最終段階です!
OnEndDrag内でレイキャストというのでマウスの位置にあるUIを取得します。
そのUIのタグが想定した値と一緒だったらボタンの位置をUIの位置に移動させてあげます。
レイキャストについては

【Unity】RayCastを使いこなせ!判定や表示に使ってみよう | 侍エンジニアブログ
この記事では「 【Unity】RayCastを使いこなせ!判定や表示に使ってみよう 」といった内容について、誰でも理解できるように解説します。この記事を読めば、あなたの悩みが解決するだけじゃなく、新たな気付きも発見できることでしょう。お悩み...

が分かりやすいので参考にしてください。

まずは移動先のUIを作成しましょう。
今回はImageを作成して、そのタグを「image」としました。

でOnEndDragを以下のように変更しました。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;

public class DragDropScript : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
    private Vector2 prePosition;

    /// <summary>
    /// ドラッグ開始時に呼び出される
    /// </summary>
    /// <param name="eventData"></param>
    public void OnBeginDrag(PointerEventData eventData)
    {
        prePosition = transform.position;
    }

    /// <summary>
    /// ドラッグ中に呼び出される
    /// </summary>
    /// <param name="eventData"></param>
    public void OnDrag(PointerEventData eventData)
    {
        transform.position = eventData.position;
    }

    /// <summary>
    /// ドラッグ終わりに呼び出される
    /// </summary>
    /// <param name="eventData"></param>
    public void OnEndDrag(PointerEventData eventData)
    {
        bool flg = true;

        var raycastResults = new List<RaycastResult>();
        EventSystem.current.RaycastAll(eventData, raycastResults);

        foreach (var hit in raycastResults)
        {
            if (hit.gameObject.CompareTag("image"))
            {
                transform.position = hit.gameObject.transform.position;
                flg = false;
            }
        }

        if (flg)
        {
            transform.position = prePosition;
        }
    }

}

レイキャストでぶつかったオブジェクトのタグが”image”だったらそのオブジェクト上に
ボタンを移動させます。
そうでない場合はボタンの初期位置に移動させます。

さぁこれで出来るはずです。実行してみましょう!

できました!

初心者の方には難しかったかもしれませんが
やり方を掴めば結構簡単に実装できた!って思うようになりますので
それまで一緒に頑張りましょう!

タイトルとURLをコピーしました