【Unity】Objectをマス目に合わせてDropする

2021/9/11
Drag&Dropのスクリプト修正


また、いつものサボり癖が発動してしまいました(;^_^A

記事にできる内容もなかったので、
更新ができないと言うかヤル気が無かったと言うか…
(;^o^) \(ToT )あんたほんとにそれでいいの

このままでは、ダメなので少し実験した事を記事にしてみます。

以前、ドラッグについて記事にしたのですが、
オブジェクトを掴んで動かすまでは、作る事ができました。

単純な移動の処理なら問題ないのですが、
ゲームでは、オブジェクトを指定の範囲に置いたり、
同じオブジェクトを合成したり、
オブジェクトをゴミ箱にポイしたり、

実際には、ドロップ時の処理が必要になります。
この辺りを試してみたので、まとめてみたいと思います。

まずは、マス目に合わせてドロップする事を記事にしてみます。

将棋やオセロなんかでは、必要になる処理です。
ジグソーパズルにも使えるかと思います。


まず、マス目のある盤面とドロップするオブジェクトが必要なので、
こんなものを用意してみました。
ドロップ処理①.jpg
大砲のオブジェクトとマス目のフィールドを用意してみました。

大砲をドラッグしてフィールド上でドロップすると
ドロップの座標からマス目にハマるように処理を行います。


まずは、大砲をドラッグ操作できるようにスクリプトを組みます。

ドラッグ操作については、
【Unity】ドラッグでオブジェクトを動かしてみる
または、
【Unity】追いかけてくる敵を作ってみる

どちらかを参考にして頂ければいいかと思います。

続いて、オブジェクトの配置なんですが、
ドロップ処理④.jpg
フィールドはマス目の背景と大砲を移動させた時の設置フィールドを用意します。

ドロップ処理⑤.jpg
ドラッグする大砲は大砲Parentを用意して保管しておきます。

大砲を設置フィールドに出す際、
大砲Parentから設置フィールドに移行する必要があるので、
ドラッグ処理に修正を加えます。
DragScript

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

public class DragScript : MonoBehaviour
{
public GameObject cannonParent; //大砲Parent
public GameObject SetField; //設置フィールド

private float constantZ; //ドラッグ時のZ座標

// Start is called before the first frame update
void Start()
{
constantZ = transform.z;
}

// Update is called once per frame
void Update()
{

}

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

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


OnMouseDragメソッドの最初にParentの判断を入れて、
大砲Parentの子なら設置フィールドの子に変更します。

これで、設置フィールドの座標を基準にドロップする事ができます。

最後にSetSiblingIndexを入れましたが、
ドラッグ中の大砲が、置いてある大砲の下を通過すると
見た目がおかしくなるので、ドラッグ中は一番上に表示されるようにしています。

ドラッグ処理ができたので、ドロップの処理を考えて行きます。

ドラッグ中の大砲は、座標がマウスポイントに依存しています。
そのままドロップしてもマス目には関係ない位置に止まるので、
マス目の座標に修正する必要があります。

マス目の座標は、設置フィールドの座標そのものなので、
マス目をポジションと捉えます。
ドロップ処理⑥.jpg
ドロップした大砲をポジション化すれば、対応したマス目にハメる事ができます。

ポジション化については、
【Unity】四捨五入を考える
を参考に処理していきます。

ただ、この記事の四捨五入は、正の数のみ対応してるので、
負の数の場合、処理できません。

コードも改良の余地があるので、そのへんを踏まえて
メソッドを考えてみます。
DragScript

//設置フィールドデータ
private const int pitch = 100; //フィールドのマス目のピッチ

//ドロップ時の座標を四捨五入(六捨五入)する
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;
}

少し説明します。

引数axisで渡ってきた座標をマス目のピッチで割ると
ポジションが出せます。

計算したポジション数は、端数が出るので、四捨五入して整数化します。
整数化した数値にピッチを掛けるとマス目の座標になります。
その座標を戻り値にして、大砲の座標に代入すればマス目に合うようになります。

正の数と負の数で処理を変えたのは、
ポジションが1.5の場合、centi = (dec - Mathf.FloorToInt(dec));を使っています。

これは、Mathf.FloorToInt(dec)で1.0が算出されます。
実際は、1.5-1.0=0.5の計算が行われる事になります。

では、ポジションが-1.5だった場合はどうなるかと言うと
-1.5-(-2)=0.5となります。

Mathf.FloorToInt(-1.5)だと切捨ての関数なので、-2になってしまいます。
なので、Mathf.CeilToInt(dec)で切り上げてやります。

centi = (dec - Mathf.CeilToInt(dec));
-1.5-(-1.0)=-0.5となります。

後は、正の数なら四捨五入、負の数なら六捨五入してやれば
マス目のポジションに揃える事ができます。

マス目の座標が計算できたので、ドロップ時に数値を代入していきます。
DragScript

private Vector3 pos; //大砲の座標

//ドロップ時の処理
private void OnMouseUp()
{
//マス目の座標に修正する
pos.x = Rounding(this.transform.localPosition.x);
pos.y = Rounding(this.transform.localPosition.y);

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

ドロップ時にtransformを代入してやれば、マス目の座標に移動してくれます。

早速PLAYしてみます。
マス目に上手くハマってくれます。

??
ドロップ処理⑦.jpg
マス目の外にドロップされました。

設置フィールドの範囲を指定するのを忘れてました。
ロボアニメーションの時もやってしまいましたが、
範囲設定って重要ですね。
(゜゜;)\(--;)オイオイ

設置フィールドの範囲を指定して出来た完成版がこちら
DragScript

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

public class DragScript : MonoBehaviour
{
public GameObject cannonParent; //大砲Parent
public GameObject SetField; //設置フィールド

private float constantZ; //ドラッグ時のZ座標
private Vector3 pos; //大砲の座標


//設置フィールドデータ
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座標下限

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

// Update is called once per frame
void Update()
{

}

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

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

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

//座標代入
this.transform.localPosition = pos;
this.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;
}
}

最後に大砲のインスペクターを

ドロップ処理㊲.jpg
各、親オブジェクトのアタッチですね。

アタッチ忘れでエラーが出て焦る事が多々あります。(;^_^A

次回は、ドロップ時の合成なんかをまとめてみようと思います。
今回はこのへんで(^ _ ^)/~~サヨナラ

この記事へのコメント