🐡

【Unity】複数のGameObjectをドラッグする

2023/10/08に公開

はじめに

例えば、カードゲームで複数のカードを同時に操作したいという要件はよくあります。そんなとき、自分はこうしているという話をします。

処理の方針

  • 複数のカードを同時に動かします
  • マウスダウン/ホールド/アップはUniRxで検知します
  • マウスダウン時にマウスポインタ上にカードがあるか判定し、ある場合動かすカードを処理中のListに格納します。今回はカードを縦に3枚並べ、マウスポインタ上のカードより下にあるカードをすべて動かすようにしています
  • マウスホールド(ドラッグ)時は、処理中のListに存在するカードに対して位置を更新します
  • マウスアップ時、処理中のリストに存在するカードを削除します

初期設定

他のファイルで発火したイベントを受け取りたい場合は、IObservableを使うといいと思います。今回は同一ファイル内で処理しているため、直接メソッドを呼んでいます。
マウスダウン時にカードを浮かび上がらせているため、浮かばせる量を定義していますが、これはあってもなくても大丈夫です。

/// <summary>
/// カードのList
/// </summary>
[SerializeField] List<GameObject> cards = new List<GameObject>();
/// <summary>
/// ドラッグしているカードのList
/// </summary>
[SerializeField] List<GameObject> grabedCards = new List<GameObject>();
/// <summary>
/// ドラッグしているカードがマウスポインタとどれだけ離れているかを保持するList
/// </summary>
[SerializeField] List<Vector3> cardOffsets = new List<Vector3>();

//private Subject<Vector3> mouseButtonDownSubject = new Subject<Vector3>();
//public IObservable<Vector3> mouseButtonDownObservable => mouseButtonDownSubject;

//private Subject<Vector3> mouseButtonHoldSubject = new Subject<Vector3>();
//public IObservable<Vector3> mouseButtonHoldObservable => mouseButtonHoldSubject;

//private Subject<Vector3> mouseButtonUpSubject = new Subject<Vector3>();
//public IObservable<Vector3> mouseButtonUpObservable => mouseButtonUpSubject;

/// <summary>
/// カードの浮かび上がる量
/// </summary>
const float CARD_FLOAT_DISTANCE = 1.1f;

Start

各種マウスイベントから該当する処理を呼び出すようにします。
OnNextは今回は使用せず、直接処理を呼び出しています。

void Start()
{
    Vector3 previousMousePos = new Vector3 (0, 0, 0);

    this.UpdateAsObservable()
        .Where(_ => Input.GetMouseButtonDown(0))
        .Select(_ => Input.mousePosition)
        .Subscribe(pos => {
            MouseButtonDown(pos);
            //mouseButtonDownSubject.OnNext(pos);
        })
        .AddTo(this);

    this.UpdateAsObservable()
        .Where(_ => Input.GetMouseButton(0))
        .Select(_ => Input.mousePosition)
        .Where(pos => pos != previousMousePos)
        .Subscribe(pos =>
        {
            MouseButtonHold(pos);
            previousMousePos = pos;        
            //mouseButtonUpSubject.OnNext(pos);
        })
        .AddTo(this);

    this.UpdateAsObservable()
        .Where(_ => Input.GetMouseButtonUp(0))
        .Select(_ => Input.mousePosition)
        .Subscribe(pos =>
        {
            MouseButtonUp(pos);
            //mouseButtonUpSubject.OnNext(pos);
        });
}

マウスダウン時の処理

Rayを飛ばして、ヒットしたGameObjectがCardというタグを付与されていたら、それはカードであるということにしています。ヒットしたカードのListの位置から、縦に並べたカードのどこにいるかを判定し、カードを浮かせる処理と、同時に動かすカードがマウスポインタからどれくらい離れているかを保持します。XZ平面上で動かしたかったので、Y軸の差分は取得しない(0f)ことにしています。このタイミングで、処理中のリストに入れたカードを元のリストから削除する等の処理を行うケースもありますが、今回は省略しています。

private void MouseButtonDown(Vector3 pos)
{
    Ray ray = Camera.main.ScreenPointToRay(pos);
    if (Physics.Raycast(ray, out RaycastHit hit) && hit.collider.gameObject.tag == "Card")
    {
        GameObject hitobj = hit.collider.gameObject;
        Vector3 hitCardPosition = hit.collider.gameObject.transform.position;
        for (int i = cards.IndexOf(hitobj); i < cards.Count; i++)
        {
            // カードを浮かせる
            Vector3 dist = new Vector3(
                cards[i].transform.position.x,
                cards[i].transform.position.y + CARD_FLOAT_DISTANCE,
                cards[i].transform.position.z);
            cards[i].transform.position = dist;
            // 現在掴んでいるカードのリストに追加
            grabedCards.Add(cards[i]);
            // マウスポイントからのオフセットを取得
            Vector3 offset = new Vector3(
                cards[i].transform.position.x - hitCardPosition.x,
                0f,
                cards[i].transform.position.z - hitCardPosition.z
                );
            cardOffsets.Add(offset);
        }
    }
}

ドラッグ時の処理

ドラッグ時の処理は、マウスの座標を取得して、それにマウスダウン時に取得したマウスポインタからの距離を加算して、移動する先の座標を求めています。

private void MouseButtonHold(Vector3 pos)
{
    // マウスのワールド座標を取得
    pos.z = 10f;
    Vector3 screenToWorldPosition = Camera.main.ScreenToWorldPoint(pos);

    for (int i = 0; i < grabedCards.Count; i++)
    {
        Vector3 newPos = new Vector3(
            screenToWorldPosition.x + cardOffsets[i].x,
            grabedCards[i].transform.position.y,
            screenToWorldPosition.z + cardOffsets[i].z
            );
        grabedCards[i].transform.position = newPos;
    }
}

マウスボタンを離したときの処理

マウスボタンを離した際は、浮かばせていたカードを元に戻し、処理中のListにいれていたGameObjectと保持していたマウスポインタからの距離が入っているListを空にします。GameObjectは必要に応じて移動先のListに入れる等の処理が必要かと思われますが、今回は省略しています。

private void MouseButtonUp(Vector3 pos)
{
    for (int i = 0;i < grabedCards.Count;i++)
    {
        // 浮かんでるカードを元に戻す
        Vector3 dist = new Vector3(
            grabedCards[i].transform.position.x,
            grabedCards[i].transform.position.y - CARD_FLOAT_DISTANCE,
            grabedCards[i].transform.position.z);
        grabedCards[i].transform.position = dist;
    }
    grabedCards.Clear();
    cardOffsets.Clear();
}

まとめ

GameObjectを複数移動される処理は、起点となるGameObjectの取得と、同時に移動するGameObjectの取得方法を変更するといろいろ応用が利くのではないかと思っています。しかし、今回の処理ではドラッグしているカードが画面端に到達したらどうするか?等の処理を実装していないので、要件に応じて実装する必要があると思います。
他にサクッと実現する方法がありましたら、コメント等で教えていただけるとありがたいです。

Discussion