【Unity】ドラッグでオブジェクトを動かしてみる

2019/7/15 更新
2021/9/10 ドラッグ処理について刷新・理由についての追記
2021/9/14 Camera.mainの注意点追加とスクリプト修正


回転やスライドのパズルを作ってみましたが、
パズルと言えばジグソー!

王道のパズルゲームを考えてみます。

今まではピースを押せば移動していたのですが、
ジグソーはピースを持って動かす事が条件になります。

また、正しい位置にきたらピースがハマる事も必要になるので、
初心者には、かなり敷居の高い操作になるかと…

とりあえず、完成するかは別としてチャレンジあるのみでっす。


ジグソーの動きを考えると
・ピースを持って動かす
・ピースが位置に来たらハマる
・絵が完成したらクリア

が条件になるので、

まずは、ピースを掴む・動かすを実装しないといけません。


ピースはImageオブジェクトを使うので、
オブジェクトをドラッグできるようにすればいいかと思います。

ここからは、記事を刷新しました。
2021/9月現在、Unityバージョン2020.2.3f1の情報になります。

以前は、EventTriggerを使うように書いてましたが、
なくてもDragできるようになっています。


オブジェクトのドラッグを簡単に設定する方法は、
OnMouseDragメソッドを利用すると便利です。

ただ、スクリプトリファレンスで、
この関数はレイヤーが「 Ignore Raycast 」のゲームオブジェクトでは呼び出されません。
とあるので、注意は必要になります。

OnMouseDragは、Colliderに反応するようにできているので、
オブジェクトには、必ずColliderを設定する必要があります。

また、動きを伴うColliderにはRigidbodyも併せてアタッチしておきます。

Rigidbodyが必要な理由は、
スクリプトリファレンス コライダー
静的コライダーの部分を読んでもらうと理解できると思います。

前置きが長くなったので、実際に手作業をしていきます。
まず、Imageオブジェクトを追加して、ImagePieceにリネームしておきます。

続いて、必要なコンポーネントを追加します。
DragTest改①.jpg
2D物理からBoxCollider2DとRigidbody2Dを追加します。

BoxCollider2Dは、オブジェクトの大きさに合わせておけばOKでっす。
Rigidbody2Dの設定を変更します。これが重要でっす。
DragTest改②.jpg
ボディタイプをキネマティック
完全なるキネマティックにチェックを入れます。

この設定は、Rigidbodyに働く物理演算を除外する設定になります。
Drag操作は、物理演算が必要ないので除外しておきます。

この設定をしておかないと、
移動させる度に物理演算が再計算される事になり、リソースを取ってしまいます。

オブジェクトの用意ができたので、スクリプトを組んで行きます。
DragTest

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

public class DragTest : MonoBehaviour
{
private Camera mainCamera; //メインカメラ取得
private float piecePosZ; //ピースのZ座標

// Start is called before the first frame update
void Start()
{
mainCamera = Camera.main;
piecePosZ = transform.position.z;
}

//ドラッグ処理
private void OnMouseDrag()
{
//マウスのポジションを取得してピースZ座標を代入しておく
Vector3 mousePos = mainCamera.ScreenToWorldPoint(Input.mousePosition);
mousePos.z = piecePosZ;

//ドラッグ中のピースにマウス座標を代入する
transform.position = mousePos;
}
}

説明すると、
マウスがどの座標にあるかを取得したいので、カメラの描画範囲から座標を取得します。

変数mainCameraを用意して、
Start内で、この変数にCamera.mainを使ってメインカメラを取得しておきます。

これでメインカメラの情報を使う事ができるので、
ScreenToWorldPointを使って座標を割出します。

例えば、1280×720横のCanvasを用意してカメラを合わせた場合、
描画範囲の左下を起点に
DragTest改⑧.jpg
こんな座標が形成されます。
この座標を読み取るのがCamera.main.ScreenToWorldPointです。

WorldPointの引数にInput.mousePositionを設定する事で、
マウスがカメラ座標のどのPointにあるか取得する事ができます。

マウスのポジションが確定したらピース座標に代入するだけで、
マウスに追従してくれます。

ちなみに、マウスのZ座標にピースのZ座標を代入していますが、
これは保険です。(^.^; オホホホ

オブジェクトを増やしたり、ドラッグしているとたまにZ座標が狂う事があります。
そうするとDragしたオブジェクトが消えるなんて現象が起こります。
設定時のオブジェクトのZ座標を取得しておけば間違いがないかと思います。

注意点として、
OnMouseDragは、ドラッグ中は毎フレーム呼び出されます。
Updateと同じようなものって認識でいいかと。
なので、FindやGetComponentは禁物でっす。

なので重要になってくるCamera.mainに触れておきます。

Camera.mainは、
ヒエラルキーからMainCameraのタグが付いたオブジェクトを探す処理です。
FindGameObjectWithTagと同じようなものだと認識してもらえばいいです。

なので、Updateで呼び出す場合、繰り返すと処理が重くなる可能性が高いです。

OnMouseDragもUpdateと同じようなものなので、
Camera.mainを、そのまま使うと重くなる原因になります。
先にStart内で取得しておいて、使う事をおすすめ致します。


説明も終わったので、
このスクリプトをImagePieceにアタッチすれば準備完了!ヽ(´▽`)/~♪

準備ができたのでPLAYしてみます。


さてドラッグでオブジェクトを動かせるようになりましたが、
ピースが正位置に来たらハメるをどうすればいいか…

位置情報からハメるかコライダーを使うか…
2パターン考えてみます。

まずはコライダーの接触判定を使ってみます。
DragTest1.jpg
ピースのハマる位置をイメージで作り
BoxCollider2DをアタッチしてIsTriggerをonにします。
Colliderの大きさは30×30くらいにしておきます。

接触判定の時に呼び出すtagネームをpos1としておきます。

Dragのスクリプトに接触時の処理を追加してみます。

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

public class DragTest : MonoBehaviour
{
private Camera mainCamera; //メインカメラ取得
private float piecePosZ; //ピースのZ座標

// Start is called before the first frame update
void Start()
{
mainCamera = Camera.main;
piecePosZ = transform.position.z;
}

//ドラッグ処理
private void OnMouseDrag()
{
//マウスのポジションを取得してピースZ座標を代入しておく
Vector3 mousePos = mainCamera.ScreenToWorldPoint(Input.mousePosition);
mousePos.z = piecePosZ;

//ドラッグ中のピースにマウス座標を代入する
transform.position = mousePos;
}

//ポジションに接触したら重ねる
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.tag == "Pos1")
{
transform.position = collision.transform.position;
Debug.Log("当たった");
}
}
}

Debugコマンドは当たり判定が出てるか見たかったので
書いてます。(..*) オハズカシイ・・

実際に動かしてみます。

あら…
Debugは吐き出してるけど、ピースはハマらない…
困ったな~ ( ̄~ ̄;) ウーン

接触判定がonしたらDragを外すようにしてみます。

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

public class DragTest : MonoBehaviour
{
private Camera mainCamera; //メインカメラ取得
private float piecePosZ; //ピースのZ座標
private bool posON; //ポジションに乗ったか

// Start is called before the first frame update
void Start()
{
mainCamera = Camera.main;
piecePosZ = transform.position.z;
}

//ドラッグ処理
private void OnMouseDrag()
{
//ポジションに乗ったらDragを中止する
if (posON)
return;

//マウスのポジションを取得してピースZ座標を代入しておく
Vector3 mousePos = mainCamera.ScreenToWorldPoint(Input.mousePosition);
mousePos.z = piecePosZ;

//ドラッグ中のピースにマウス座標を代入する
transform.position = mousePos;
}

//ポジションに接触したら重ねる
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.tag == "Pos1")
{
transform.position = collision.transform.position;
posON = true;
Debug.Log("当たった");
}
}
}

単純にbool型変数でドラッグできないようにしてみました。

実際に動かしてみます。

一応ピースがハマるようになりました。

接触判定でピースをハメるには、ピース分のColliderを用意しないと…
大量のピースなら大変ですね~(;^_^A

実用的でないか~

やはり位置情報からハメる方が現実的のようです。

位置情報からのスクリプトは次回で
今回のスクリプトをほぼ使えるので簡単とは思うんですが…



追記
2021/9/11
参考にさせて頂いたブログがお休みになっておられるので、
ドラッグの内容が説明できていませんでした。

また、EventTriggerの必要性が無くなった事、Rigidbodyの注意点も書きたかったので、
2021年で理解している内容に刷新しました。

ドラッグの内容が無い状態で長らく放置していた事をお詫び致します。
(._.;) スンマセン


2021/9/14
OnMouseDragでは、FindやGetComponentは禁物と書きながら
Camera.mainを、堂々と使う処理を書いてました。
注意点追加とスクリプト修正を行ってます。

ホント恥ずかしいかぎりです。
(ToT)>゛スンマセン

この記事へのコメント

  • tamachan

    私もswiftではパズルゲームを作ってリリースなどしたのですが、unityでもとりあえずパズルゲームを作ろうと思ったのですが、本もないので探しています。2048のサンプルはあるようですがそれを作ろうとしてもわかりません。本があるといいですね。でもとりあえずこのブログを見て勉強しようと思います。
    2019年01月29日 19:47
  • Kero

    コメントありがとうございます。
    リリースされているんですね。凄い!
    まだまだリリースもできない初心者なんでお恥ずかしい。(/ω\)
    これからも調べた事や作れた物を公開できればいいなと思っているので、
    今後ともよろしくお願いします。m(_ _)m
    2019年01月31日 02:54