【Unity】ドラッグ時の接触判定

2021/9/11
スクリプト一部修正


ドロップ時の処理で書き始めた内容ですが、
少し問題がありまして…

修正でスクリプトを書き直していたら訳が分からなくなって…
頭の中がこんがらがっています。

当然、何から書いていいのやら…
頭の中でまとまってないと書くのも難しいと言うか(~ヘ~;)ウーン

伸び伸びになってるテーマを小出しで書いていくので、
暫くお付き合い下さいませ(゜゜;)ゴメンネ・・・

さて、大砲の複製まで出来るようになったので、
後は、フィールドに出して行けばいいだけなのですが、
やはり合成が必要になってきます。

合成に関しては、Collisionを使えば簡単に判断できるのですが、
問題は、ドラッグ中なら合成しない!

重なっただけで合成してしまうと、元に戻せなくなるので、
”ドロップ時に合成する”が必要になるかと思います。

しかし、OnCollisionEnterは、衝突時にしか反応しないメソッドなので、
一度、呼び出されると再衝突しないかぎり働かないのが難点です。

OnCollisionStayでも良いのですが、少し重ねておくと
反応して処理が実行されます。

少し工夫が必要になります。

そこで、大砲同士が衝突と離脱を判断できるようにしてみようと思います。
DragScript

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

public class DragScript : MonoBehaviour
{
private bool Syn; //合成フラグ

//何かのRigidbodyに接触したら
private void OnCollisionEnter2D(Collision2D collision)
{
Syn = true;
}

//接触中のRigidbodyから離れたらフラグを消す
private void OnCollisionExit2D(Collision2D collision)
{
if(Syn) Syn = false;
}
}

フラグを用意して、接触時と離脱時にON・OFFすれば判断ができます。

このフラグを元に、ドロップ時に次レベルの大砲を生成します。
DragScript

public GameObject CanPrefab; //次レベルのプレハブ

//ドロップ時の処理
private void OnMouseUp()
{
//設置フィールド内のマス目座標に修正する
pos.x = Mathf.Clamp(Rounding(this.transform.localPosition.x), Xmin, Xmax);
pos.y = Mathf.Clamp(Rounding(this.transform.localPosition.y), Ymin, Ymax);

//同じ大砲が重なっていたら合成する
if (Syn)
{
GameObject obj = (GameObject)Instantiate(CanPrefab);
obj.transform.SetParent(SetField.transform, false);
obj.transform.localPosition = pos;
obj.transform.SetSiblingIndex(0);
Destroy(this.gameObject);
}


//座標代入
this.transform.localPosition = pos;
this.transform.SetSiblingIndex(0);
}

//何かのRigidbodyに接触したら
private void OnCollisionEnter2D(Collision2D collision)
{
//接触したRigidbodyが同じタグなら
if (collision.gameObject.tag == this.tag) Syn = true;
}

//接触中のRigidbodyから離れたらフラグを消す
private void OnCollisionExit2D(Collision2D collision)
{
if(Syn) Syn = false;
}

同じ大砲なら合成フラグを出すので、OnCollisionEnterも修正します。

これで、ドロップした際に大砲の合成ができるのですが、
実は問題が…

EndDragメソッドで処理をしているので、フィールドに置かれてる大砲は
削除されずに残ってしまいます。

なので、フィールドに置かれてる大砲もDestroyできるように
しないといけません。

OnCollisionEnter2DやOnTriggerEnter2Dは、接触や重なったオブジェクトの
全ての情報を(Collision2D collision)内のcollision変数に取り込んでいるので、
これを利用します。
DragScript

private GameObject coll; //接触中の大砲

//ドロップ時の処理
private void OnMouseUp()
{
//設置フィールド内のマス目座標に修正する
pos.x = Mathf.Clamp(Rounding(this.transform.localPosition.x), Xmin, Xmax);
pos.y = Mathf.Clamp(Rounding(this.transform.localPosition.y), Ymin, Ymax);

//同じ大砲が重なっていたら合成する
if (Syn)
{
Destroy(coll.gameObject); ←ここを追加してます
GameObject obj = (GameObject)Instantiate(CanPrefab);
obj.transform.SetParent(SetField.transform, false);
obj.transform.localPosition = pos;
obj.transform.SetSiblingIndex(0);
Destroy(this.gameObject);
}


//座標代入
this.transform.localPosition = pos;
this.transform.SetSiblingIndex(0);
}

//何かのRigidbodyに接触したら
private void OnCollisionEnter2D(Collision2D collision)
{
//接触したRigidbodyが同じタグならDestroyする
if (collision.gameObject.tag == this.tag)
{
Syn = true;
coll = collision.gameObject;
}
}

//接触中のRigidbodyから離れたらフラグを消す
private void OnCollisionExit2D(Collision2D collision)
{
if (Syn)
{
Syn = false;
coll = null;
}
}

これでフィールドに置かれた大砲も削除する事ができます。

続いて、次レベルのプレハブを作ります。
ドロップ処理㉒.jpg
大砲(白)を複製して、スプライトをLv2に差し替えます。
できた大砲(白)Lv2をプレハブ化します。

このプレハブには難点があって
ドロップ処理㉓.jpg
ペアレントの変数が解除されてしまいます。

プレハブなら取り込む事ができるのですが、
gameObjectを取り込むとプレハブ化した際、リンクが切れます。
このままでは、Lv3の生成時に設置フィールドがnullになるので、
エラーが発生します。

そこで、クローン後の大砲も設置フィールドを取得できるように
スクリプトに追加します。
DragScript

void Start()
{
if (SetField == null) SetField = transform.parent.gameObject;
}

クローンの生成は、設置フィールドを指定してるので、
生成されたクローンは、すでに設置フィールドの子になっています。
Startメソッドで、ペアレントの設置フィールドを取得すればいいかと思います。

大砲のLv1は、設置フィールドをアタッチしているので、
生成後の物だけを判断する為、if (SetField == null)で変数が空ならとしました。

これで、Lv2が合成されても設置フィールド内にLv3を生成する事ができます。
続いて、タグの編集をします。
ドロップ処理㉔.jpg
分かり易い名前ならなんでもいいので、
今回は、こんな感じにしてます。

タグが追加できたらLvに合わせてタグを変更します。
ドロップ処理㉕.jpg
これで合成ができるようになるので、
Lv3・Lv4のプレハブも作ります。

プレハブの準備ができたら試してみます。

無事に合成させる事ができましたヽ(´▽`)/~♪

と、喜んでみましたが実は問題点がありまして…
実装には向いてないと言うか…

問題点は、後ほど説明するとして、
先にゴミ箱ポイの実装をしてみたいと思います。

Androidのウィジェットなんかにもあった
ゴミ箱マークの上でドロップすると削除される処理でっす。

あくまで、合成と同じ処理なので、ぱぱっと説明します。

まず、ドラッグするオブジェクトとゴミ箱を用意します。
今回は、大砲があるので流用してみます。
ドロップ処理㉗.jpg
まずゴミ箱オブジェクトを作ります。
このオブジェクトには、BoxCollider2Dをアタッチして、
トリガーにチェックを入れます。
これが感知センサーとなります。

OnTriggerで処理できるようにタグを変更します。
今回は”DustBox”にしておきます。

ドラッグするオブジェクトのスクリプトに次の処理を追加します。
DragScript

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

public class DragScript : MonoBehaviour
{
private bool dell; //削除フラグ
private const string dustbox = "DustBox"; //ゴミ箱タグ

//ドラッグ時の処理
private void OnMouseDrag()
{
//マウスポイントの取得と座標代入
Vector3 mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
mousePos.z = constantZ;
transform.position = mousePos;
transform.SetSiblingIndex(99);
}

//ドロップ時の処理
private void OnMouseUp()
{
if (dell) Destroy(this.gameObject);
}

//何かのトリガーに接触した
private void OnTriggerEnter2D(Collider2D collision)
{
//トリガーがゴミ箱なら
if (collision.gameObject.tag == dustbox) dell = true;
}

//何かのトリガーから離れた
private void OnTriggerExit2D(Collider2D collision)
{
//ゴミ箱トリガーから離れた
if (collision.gameObject.tag == dustbox) dell = false;
}

これで、ゴミ箱にドロップしたら削除できると思います。
合成の際は、CollisionEnterを使いましたが、ゴミ箱にはRigidbodyもいらないので、
TriggerEnterで処理できます。


さてさて話を戻して、実装に不向きな合成処理と言う事で、
何が問題か説明します。

まず、大砲のコラインダーが接触してないと合成が出来ないのですが、
接触してるコラインダーが二つ以上になればどうするの?って事なんです。
ドロップ処理㉘.jpg
4枚の大砲の丁度中間に新しい大砲をドロップすると
コラインダーが4枚全部と接触します。

衝突判定は、コラインダーの緑線に少しでも当たっていると
検出されるので、4枚同時に衝突判定が呼び出される事になります。

4枚ともレベルが上がるか、もしくはどれか一枚だけレベルが上がるか
検証してないので分かりませんが変な処理が実行されてしまいます。

コラインダーサイズを少し小さくしても、中間地点なので全てに接触しない状況になります。

絶妙に中間に置く事は無いとは思いますが、
置いてしまうとバグになるので、修正が必要になります。

合成って意外と難しい事に気付きました。(T^T) ヒック
次回、修正できたか分かりませんが記事にしたいと思います。

OnTriggerやOnCollisionの遅延処理としては、応用できそうなので、
参考になれば幸いかと思います。

それでは、この辺で(TωT)/~~~ BYE BYE

この記事へのコメント