【Scratch】ドラッグアンドドロップのUIを実装する

Scratcherのyuki384です。

今回はドラッグアンドドロップでマス目に要素を配置する一つの方法について書きます。Scratchでとあるパズルゲームを作る際に「ドラッグアンドドロップをさせたい！」と思ったので作っていきます。

# 作るもの
- 並んでいるマス目にドラッグアンドドロップすると、マス目上にピタッと配置される。
- マス目は四角
- マス目はクローン以外で生成したい(ペンかスタンプ)
- 配置した要素を再配置することができる

完成図は次のようになります。
![draganddrop.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264538/e3a19988-9b88-7476-ad70-6ee1214a9908.gif)
https://scratch.mit.edu/projects/355914705/ で公開しています。

# マス目
デフォルトの猫は削除し、新しいスプライトを作ります。コスチュームは46*46pxの正方形です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264538/20effd27-d912-682a-06d2-cfc5509bd091.png)

生成部分は本題ではないので説明を省略します。

マス目生成と同時に座標をリストに保存します。今回はx座標とy座標を別々のリストに保存していますが、好きな方法で構いません。

マス目のプログラムは次のコードで完成です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264538/c4cf61e3-69c4-249e-84e7-8ee47609657c.png)

# 要素
## 要素を作る
新しいスプライトでドラッグする要素を作ります。コスチュームは30*30の正方形です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264538/ece05a01-522d-e5d6-48f2-da27e75a6379.png)

まずはクリックされたときに、「ドラッグできる要素」をクローンで作ります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264538/161930f4-80f7-c423-853b-a3da341864f0.png)
「このスプライトがクリックされたとき」を使わないのは、クローンされた要素がさらに要素を生成することを防ぐためです。
## ドラッグできるようにする

まず、ドラッグ中はマウスのポインターの場所に要素が行くようにします。マウスが離される(=「マウスが押された」ではない)まで「マウスのポインターに行」きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264538/03ada388-2565-c053-db65-1ada2c6502af.png)
ここで「配置」という定義ブロックを作っているのは、後で再配置のコードを組むときに使い回すためです。

※注意: 実行時は、プレゼンテーションモードもしくはプロジェクトページでドラッグしないと、編集画面での位置変更となってしまい正常に動きません。

## 配置する
それでは肝心の配置部分を作ります。
マス目の座標が保存されているリストを調べて、ドロップした要素の座標との距離を調べて結果に応じて配置します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264538/7bbdf752-4676-cde3-186f-0c69fd493e89.png)


最後までリストのすべての要素を調べて、要素が配置できる場所にあるマス目が一つもなければ削除しています。

### 見切れている部分の説明
マス目と要素の距離を調べています。内容は以下のようになります。マス目は正方形を想定しているのでこのコードでは正方形のみ対応しています。

```
〈マス目の左端x座標 < 要素のx座標(マウスのx座標)〉かつ〈要素のx座標(マウスのx座標) < マス目の右端x座標〉
〈マス目の下端y座標 < 要素のy座標(マウスのy座標)〉かつ〈要素のy座標(マウスのy座標) < マス目の上端y座標〉

```
これらを「〜〜かつ〜〜」でつなぎます。

すべて表示するとこのようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264538/7000d4af-7265-be77-4104-e022aaf3f61d.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264538/441d1a0f-924f-ba49-e7ad-d6fa2704b5b0.png)


変数 `配置する場所の大きさ(四角)` には44を設定しています。マス目の大きさにあわせて変えてください。

### ループについて
このコードは無駄に処理が動く事になってしまい非効率ですが、一旦このままでいきます。シンプルでわかりやすいコードなので。本当は `i` も使いたくないくらいわかりやすいコードを書きたいですが、さすがに仕方がありません。

## 再配置する
さて、この段階で実行するとどうなるでしょう。配置ができました。しかし、一度配置した要素を別のマス目に移動させることはできません。では再配置できるようにコードを組んでいきます。
「クローンされたとき」の下に「クリックされたら配置のプログラムを実行する」というコードを組みます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264538/88b516e2-0b06-9e4e-9f70-526d335f8687.png)

これで、配置後でも再配置できるようになりました。マウスが押されてすぐ離された普通のクリックの場合に別の処理をさせたい場合は少し工夫が必要ですが、ここでは実装しません。

## ドラッグ中の別要素ドラッグを防ぐ
まだこの段階だと、ドラッグ中に他の配置された要素上にマウスを持っていくと、その要素もマウスにくっついてしまいます。これでは困るので一つの要素のみドラッグできるようにします。要素同士を入れ替えるという案もありましたが、一旦なしでいきます。
`掴んでいる` というフラグを立てて、何も要素を掴んでいないときだけ配置プログラムが動くようにします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264538/5a91cfdb-d911-2e97-f1d6-1e60b1810cce.png)

# 再描画しないで実行
現在、繰り返し処理にとても時間がかかるコードなので、マス目が増えたときに正常に動かなくなります。そこで、「画面を再描画せずに実行する」という定義ブロックオプションを活用します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264538/88544b4d-c707-39e8-dd2a-13267782cb82.png)
このオプションをオンにすることで、「N回繰り返す」等のブロックで1ループごとの画面描画がなくなるので処理が早くなります。次のようなコードに変更し、「座標を調べる・配置」という定義ブロックに「画面を再描画せずに実行する」という定義ブロックオプションを設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264538/86ebc880-f335-710b-2bd1-b3df1e3cd629.png)

ここで「配置」ブロックで「画面を再描画せずに実行する」をオンにしてしまうと、ドラッグ操作に使うループでも画面再描画がされなくなってしまい、見た目的なリアルタイムにはマウスのポインターへついていきません。なのでブロックをわけています。

# 要素の配置情報をリストで管理
実際、パズルゲームなどでこのUIを使用する際は、マス目に要素が置かれているのかどうかを管理して別のスプライトに影響させたりしたい場面が多いでしょう。
そこで、リスト管理について書きます。

## 要素の位置を保存する変数
まず、それぞれの要素がどこに置かれてるかを知りたいため、クローンそれぞれに自分の位置をもたせます。クローンそれぞれで違う数値を持たせるためには、「このスプライトのみ」の変数を使います。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264538/7bb2e9da-3332-b5a5-b0f6-23da2a06664d.png" alt="image.png" width="60%" />
「要素の場所ID」変数を作り、次のように配置プログラムに追記します。
「配置の場所IDをiにする」ことで、自分がどのマス目に位置しているのかがわかります。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264538/51c8b1e0-b402-49ad-074d-5eca8253c6e1.png" alt="image.png" width="60%" />

## 配置情報リストの初期化
次に、配置情報を管理するリストを作ります。リストの初期化を「マス目」スプライトでやっておきます。今回はマス目になにもない状態を「0」とします。生成と同時に「0」をリストに追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264538/e3f4a9c3-dc16-e7c7-9a65-4f97f3d594cf.png)

## 配置情報リストの更新
「要素」スプライトで、リスト要素の置き換えをします。配置されていた場所から移動したときに、その場所の配置情報を「0」にします。そして移動後のマス目の配置情報を「1」にします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264538/caf810a4-ae9e-d46b-b331-49ef7dbe5940.png)

これで、要素が配置されているか簡単に管理できるようになりました。

# 完成
一旦完成とします。

マス目を増やす事もできるので、いろいろな展開をしてみてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264538/c33fffdd-243e-d8af-4ebd-869029c16597.png)

今回の作品はScratchでも公開しています。実際に動かしてみてください。
https://scratch.mit.edu/projects/355914705/

ではでは。