【Unity】ドロップしたオブジェクトを元に戻す

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


初めての方は、関連記事
【Unity】Objectをマス目に合わせてDropする
この記事から続く内容となっています。


大砲オブジェクトの合成と言う事で書いてきましたが、
いよいよ完結となります。

一応、曲がりなりにも合成ができるようになりました。
(バグが残ってそうですが… )

しかし、これで終わりとは行かないですね~

現状、違う種類や違うLvの大砲をドロップすると
削除もされず重なったままになります。

合成操作としては問題ありでっす。(..*) オハズカシイ・・

違う大砲が重なった場合、動かす前の位置に戻す必要があります。

動作は
・ドラッグ
・ドロップ
・違えば戻す

単純な動作です。
しかし…

実は、かなり試行錯誤しながら処理を考えました。

出来上がった処理が、
なぜ?となるので説明したいのですが、
上手く説明できないと言うか…

なので、検証していった事をそのまま記事にしようと思います。

最後に完成版のスクリプトを載せるので、
検証内容を読み飛ばしてもらってもOKでっす。

まず、大砲の戻り位置を記憶させる必要があります。
単純な話、ドラッグ時に大砲の位置を記録すればいいのですが…

private Vector3 backpos; //大砲の戻り座標

//ドラッグ時の処理
public void OnMouseDrag()
{
//ドラッグ時に戻り位置を取得
backpos = this.transform.localPosition;
}

OnMouseDragメソッドで戻り位置を取得させるようにすれば良いだけ!
と最初は思っていたのですが、OnMouseDragは毎フレーム働きます。

簡単に言うとドラッグ中は、常に呼び出され続けます。
なので、backposの中身はドラッグしてる座標が常に更新される事になります。

これでは、戻り位置にならないので、違う方法が必要になります。

そこで考えたのが、ドロップ座標posの内容を記録する。
ドロップ時にposにドロップ座標が残っているので、
そのまま記憶させれば良いのではと考えました。

幸いドロップするまでは、posの内容も変更されないので使えそうです。

private Vector3 pos; //大砲のドロップ座標
private Vector3 backpos; //大砲の戻り座標

//ドラッグ時の処理
public void OnMouseUp()
{
//ドラッグ時に戻り位置を取得
backpos = pos;
}

これでドロップした大砲は、backposを取得する事ができます。

戻り位置の取得ができるので、
大砲が接触した時の処理を追加します。

//マス目に合わせて移動させる
private void PositionMatch()
{
this.transform.localPosition = pos;
this.transform.SetSiblingIndex(0);
}

//何かのRigidbodyに接触したら
private void OnCollisionEnter2D(Collision2D collision)
{
//接触したRigidbodyが同じタグなら
if (collision.gameObject.tag == this.tag)
{
rep.RepCannonn(pos, level, color);
Destroy(this.gameObject);
}
//タグが違えば戻り位置に移動する
else
{
pos = backpos;
PositionMatch();
}
}

大砲の移動はPositionMatchメソッドを使えばできるので、
posに戻り位置を代入して、移動処理を実行します。
これで、posの情報も更新できるので一石二鳥でっす。

この状態で早速テストします。
ドロップ処理㉝.jpg
赤〇の大砲をLv2にドロップしてみます。

結果は…
ドロップ処理㉞.jpg
なんじゃこりゃ(*0*;) ギョッ

Lv1の大砲は戻りましたが、Lv2まで動くとは…
しかも設定してない位置へ…

合成でプレハブ生成された大砲は、位置情報を持ってないので、
pos、backpos共に(0,0,0)の初期値になっています。

更にOnCollisionは、当てた方、当たった方共に呼び出されるので、
Lv2の大砲も移動する状態になっていました。(;^_^A

まず、合成された大砲にも位置情報を取得させる必要があります。

void Start()
{
if (SetField == null) SetField = transform.parent.gameObject;
rep = SetField.GetComponent<Reproduction>();
boxcoll = this.GetComponent<BoxCollider2D<>();
if (boxcoll.enabled == false) boxcoll.enabled = true;

//生成時に初期位置を取得
StartPosition();
}

//初期座標を変数に代入
private void StartPosition()
{
backpos = this.transform.localPosition;
pos = this.transform.localPosition;
}

Start内もごちゃごちゃしてきたので、
StartPositionメソッドを作って分離しました。

pos、backpos共に生成座標を取得しておきます。
なぜposまで取得しておくかと言うと、ドラッグ時にbackposを更新するので、
posが初期値だと最初のドラッグ時にbackposに初期値が代入されるからです。

これで、無事にLv2の大砲は移動しなくなったのですが、
見えないだけでLv2大砲も移動処理が行われています。

ドラッグした大砲だけ戻す処理にできないかと
こんな処理を考えてみました。

private bool inst = true; //フィールド設置フラグ

//ドラッグ時の処理
private void OnMouseDrag()
{
//フィールド設置フラグOFF
inst = false;
}

//ドロップ時の処理
private void OnMouseUp()
{
//フィールド設置フラグON
inst = true;
}

//何かのRigidbodyに接触したら
private void OnCollisionEnter2D(Collision2D collision)
{
//接触したRigidbodyが同じタグなら
if (collision.gameObject.tag == this.tag)
{
rep.RepCannonn(pos, level, color);
Destroy(this.gameObject);
}
//タグが違えば戻り位置に移動する
else
{
//設置されてる大砲なら処理しない
if (inst) return;

pos = backpos;
PositionMatch();
}
}

設置フラグを用意して置かれてる大砲を判断しようと言う試みです。
この処理でいけるだろうと考えて、テストしてみる事に…

ドロップ処理㉝.jpg
結果恐ろしい事に…

Lv1の大砲が消えてしまいました。(-"-;) ??

恐る恐るLv2をどけてみると
ドロップ処理㉟.jpg
下からLv1が…  (- .-)ヾ ポリポリ

最初は分からなかったのですが、よくよく考えると、
ドロップした大砲は、instのフラグがONになります。

ドロップする前にOnCollisionが働けば処理できるのですが、
ドロップ後にしか衝突が起こらないので、
既にフラグがONになった大砲がいくら接触しても
if (inst) return;で終了してしまうわけなんです。(T^T) ヒック

正直まいりました…

何かいい方法がないかと考えて思いついたのが、
instのONをドロップ時からドロップ後にしては?
となりまして、余り使いたくなかったのですが、
コルーチンで処理する事に…

//ドロップ時の処理
private void OnMouseUp()
{
Positioning();
PositionMatch();

//ドロップしたらコラインダーをアクティブにする
boxcoll.enabled = true;

//0.05秒後にドロップ後の処理を実行
StartCoroutine("Installation");
}

//ドロップから0.05秒後に設置フラグON
IEnumerator Installation()
{
yield return new WaitForSeconds(0.05f);
inst = true;
}

これでinstフラグを遅らせる事ができるので、
ドラッグした大砲だけを反応させる事ができます。

しかし!
コルーチンには落とし穴も…

0.05秒のタイムラグにバグが潜んでいます。
この辺りが悪さをしなければ問題ないかと思います。
たぶん… ( ̄~ ̄;)

折角コルーチンを導入したので、backposの取得もコルーチンに移します。
ドロップ後に自動的にbackposを更新してくれます。

//ドロップから0.05秒後に設置フラグONと戻り位置更新
IEnumerator Installation()
{
yield return new WaitForSeconds(0.05f);
backpos = pos;
inst = true;
}

Dragメソッド内のbackpos = pos;が要らなくなるので削除します。

フィールド内の大砲については、戻る処理が完成しました。
残るは、大砲ペアレントから持ってきた大砲の処理です。

正直、ここまで作って疲れていたのもあって適当です。
ドロップ処理㊱.jpg
大砲ペアレントには、複製が作られているので戻せません。
なので、ゴミ箱を用意して削除します。

大砲ペアレントに戻す処理も実装した方がいいんでしょうが、
処理が面倒なんでこれでいいでしょうww
(;^o^) \(ToT )あんたほんとにそれでいいの

注意点は、
・ゴミ箱は設置フィールドの子にしておく
・スクリーン外に配置して見えなくしておく

設置フィールドの子にするのは、座標が把握しやすいからです。

大砲ペアレントのbackposにゴミ箱座標を設定して、
ゴミ箱と接触したら削除する処理を追加します。

//初期座標を変数に代入
private void StartPosition()
{
if (level >= 2)
{
backpos = this.transform.localPosition;
pos = this.transform.localPosition;
}
else
backpos = new Vector3(-800, 0, 0);
}

//大砲の削除処理
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.tag == "DustBox") Destroy(this.gameObject);
}

大砲のLvで判断すれば初期座標の設定もできます。
Lv1のみゴミ箱座標を登録しておけばOKでっす。

ゴミ箱の処理については、前回記事を参考にして下さい。

処理は完成したので、各種大砲のセットアップをしてテストします。

動画用にゴミ箱を見える位置に配置しています。
削除されると分かりにくいので、削除処理をOFFにしています。

一応、問題無く処理が行えています。
バグについては、問題なさそうですがゲームに実装した時に
どういう動きになるか検証は必要かと思います。

何回かに分けて書いてきましたが、
ドラッグ&ドロップの合成については完結とします。
最後にスクリプトを
DragScript

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

public class DragScript : MonoBehaviour
{
public GameObject cannonParent; //大砲Parent
public GameObject SetField; //設置フィールド
private Reproduction rep; //プレハブ複製スクリプト
private BoxCollider2D boxcoll; //コライダー取得変数

public int level; //大砲のレベルを設定
public string color; //大砲の色を設定

private float constantZ; //ドラッグ時のZ座標
private bool inst = true; //フィールド設置フラグ

private Vector3 pos; //大砲のドロップ座標
private Vector3 backpos; //大砲の戻り座標

//設置フィールドデータ
private const int pitch = 100; //フィールドのマス目のピッチ
private const int Xmax = 300; //フィールドのX座標上限
private const int Xmin = -300; //フィールドのX座標下限
private const int Ymax = 200; //フィールドのY座標上限
private const int Ymin = -200; //フィールドのY座標下限

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

constantZ = transform.position.z;
rep = SetField.GetComponent<Reproduction>();
boxcoll = GetComponent<BoxCollider2D>();

if (boxcoll.enabled == false)
boxcoll.enabled = true;

//生成時に初期位置を取得
StartPosition();
}


//ドラッグ時の処理
private void OnMouseDrag()
{
//大砲Parentの子なら設置フィールドに移行する
if (transform.parent.gameObject == cannonParent)
transform.SetParent(SetField.transform, false);

//ドラッグしたらコライダーを非アクティブにする
boxcoll.enabled = false;

//フィールド設置フラグOFF
inst = false;

//マウスポイントの取得と座標代入
Vector3 mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
mousePos.z = constantZ;
transform.position = mousePos;
transform.SetSiblingIndex(99);
}


//ドロップ時の処理
private void OnMouseUp()
{
Positioning();
PositionMatch();

//ドロップしたらコライダーをアクティブにする
boxcoll.enabled = true;

//0.05秒後にドロップ後の処理を実行
StartCoroutine("Installation");
}


//ドロップから0.05秒後に設置フラグONと戻り位置更新
IEnumerator Installation()
{
yield return new WaitForSeconds(0.05f);
backpos = pos;
inst = true;
}


//初期座標を変数に代入
private void StartPosition()
{
if (level >= 2)
{
backpos = transform.localPosition;
pos = transform.localPosition;
}
else
backpos = new Vector3(-800, 0, 0);
}


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


//マス目に合わせて移動させる
private void PositionMatch()
{
transform.localPosition = pos;
transform.SetSiblingIndex(0);
}


//ドロップ時の座標を四捨五入(六捨五入)する
private int Rounding(float axis)
{
float dec = axis / pitch;
float centi = 0;
int conf = 0;

//座標が正の数値なら四捨五入
if (axis >= 0)
{
centi = (dec - Mathf.FloorToInt(dec));
if (centi >= 0.5) conf = Mathf.CeilToInt(dec) * pitch;
if (centi < 0.5) conf = Mathf.FloorToInt(dec) * pitch;
}
//座標が負の数値なら六捨五入
else
{
centi = (dec - Mathf.CeilToInt(dec));
if (centi >= -0.5) conf = Mathf.CeilToInt(dec) * pitch;
if (centi < -0.5) conf = Mathf.FloorToInt(dec) * pitch;
}
return conf;
}


//何かのRigidbodyに接触したら
private void OnCollisionEnter2D(Collision2D collision)
{
//接触したRigidbodyが同じタグなら
if (collision.gameObject.tag == this.tag)
{
rep.RepCannonn(pos, level, color);
Destroy(this.gameObject);
}
//タグが違えば戻り位置に移動する
else
{
//設置されてる大砲なら処理しない
if (inst) return;
pos = backpos;
PositionMatch();
}
}


//大砲の削除処理
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.tag == "DustBox") Destroy(this.gameObject);
}
}

CellScript

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

public class CellScript : MonoBehaviour
{
public GameObject cannonParent; //大砲Parent
public string tagname; //大砲のタグ名

//CellのColliderから大砲オブジェクトが出たら
private void OnTriggerExit2D(Collider2D collision)
{
//大砲とCellのタグが同じなら
if (collision.gameObject.tag == this.gameObject.tag)
{
//Cellの座標に大砲を複製する
GameObject Cannon = (GameObject)Instantiate(collision.gameObject);
Cannon.transform.SetParent(cannonParent.transform, false);
Cannon.transform.localPosition = this.transform.localPosition;

//外れた大砲のタグを書き換える
collision.gameObject.tag = tagname;
}
}
}

Reproduction

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

public class Reproduction : MonoBehaviour
{
//各種プレハブ
public GameObject[] WCannon;
public GameObject[] Bcannon;
public GameObject[] RCannon;
public GameObject[] YCannon;
public GameObject[] GCannon;

//呼び出しカウント変数
private int i = 0;

//各大砲の生成メソッド
public void RepCannonn(Vector3 pos,int Lv,string color)
{
i++;
GameObject cannon=null;

//呼び出しカウントが2回目なら生成する
if (i == 2)
{
//色別の大砲判定
if (color == "White") cannon = (GameObject)Instantiate(WCannon[Lv]);
if (color == "Blue") cannon = (GameObject)Instantiate(Bcannon[Lv]);
if (color == "Red") cannon = (GameObject)Instantiate(RCannon[Lv]);
if (color == "Yellow") cannon = (GameObject)Instantiate(YCannon[Lv]);
if (color == "Green") cannon = (GameObject)Instantiate(GCannon[Lv]);

//ペアレント・生成座標・表示レイヤーの指定
cannon.transform.SetParent(this.gameObject.transform, false);
cannon.transform.localPosition = pos;
cannon.transform.SetSiblingIndex(0);

//呼び出しカウントリセット
i = 0;
}
}
}

もっと良い処理ができそうなのですが、これが限界でした(^.^; オホホホ
こんな処理の方が良いとかあればコメント頂けると幸いでっす。

それではこの辺で(^ _ ^)/~~サヨナラ

この記事へのコメント