「なに切る?」ツールを作る時に設計した UI のお話。完結編です。
他の記事
この記事は4番目の記事です。他の記事については以下のリンクをご覧ください。
- はじめに(概要)
- クラスの構造、実装ルール
- マウスで牌を移動、カーソル位置に戻す
- ドラッグした牌に合わせて、他の牌を動かす← この記事
サンプル環境
- サンプル環境の牌は、記事とは別の画像になっています。
牌を入れ替える条件
牌を入れ替えるのに必要な条件は、牌がどの枠に近づいたのか(触れたのか)? がポイントです。
普通に考えれば「牌の持っているコライダーと、枠のもっているコライダーが接触すれば…」となりますが、なんとなくコライダーを使わずに実装してしまいました。
2点間の距離がn以下だったら…という計算式で凌いでいます。
2点間の距離の求め方を書いた記事はこちら。
コード
(PaiPosGroup.cs) Result getNearestPosNo(Vector3 loc) { float distance = INIT_DISTANCE; int nearPosNo = -1; for (int i = 0; i < poss.Count; i++) { PaiPos pos = poss[i]; float x = pos.transform.localPosition.x - loc.x; float y = pos.transform.localPosition.y - loc.y; float d = x*x + y*y; if (d < distance) { distance = d; nearPosNo = i; } } return new Result() { PosNo = nearPosNo, Distance = distance }; }
ドラッグ中の牌と、PaiPosGroup に登録された枠全て(この例だと 12 個)の距離を算出し、「一番近い枠番号」「その距離」を返しています。
距離を返しているのは、例えばこういう場合。
一番近い枠番号は8ですが、枠に接触していないので入れ替える必要がありません。
枠に接触するぐらいの距離に達している場合のみ、牌を入れ替える必要があります。
牌の入れ替えは1つずつではない
牌が横移動のみであれば、あるいは1つずつでよかったかもしれませんが、マウスドラッグで自由自在に動くので…。
この場合、4から8の間にある牌は全て左に1つずつ動かし、④を8に挿入します。
8から4の場合は全て右に1つずつ動かして…と逆方向になります。
コード
void replaceDragPai() { if (resDrag.PosNo > resBegin.PosNo) { // 牌を入れ替える(左..ドラッグ元、右..ドラッグ先) for (int i = resBegin.PosNo; i < resDrag.PosNo; i++) { PaiPos w0 = GetEntity(i); PaiPos w1 = GetEntity(i+1); var model = w0.Model; w0.LinkModel(w1.Model); w0.MoveToPos(); w1.LinkModel(model); } } else if (resDrag.PosNo < resBegin.PosNo) { // 牌を入れ替える(右..ドラッグ元、左..ドラッグ先) for (int i = resBegin.PosNo; i > resDrag.PosNo; i--) { PaiPos w0 = GetEntity(i); PaiPos w1 = GetEntity(i-1); var model = w0.Model; w0.LinkModel(w1.Model); w0.MoveToPos(); w1.LinkModel(model); } } }
resBegin.PosNo から resDrag.PosNo に向けて牌を入れ替えています。
左からか、右からかによって入れ替える方向が違うので、場合分けしています。
2022年のプログラム作法では、こういう for 文の使い方、忌み嫌われそうです。ユルシテ。
LinkModel によって「枠に所属する牌を入れ替え」、MoveToPos によって「所属した枠まで自動的に牌を動かす(アニメーション開始)」となっています。
w1 は基本的に空欄想定なので、MoveToPos を実行していません。
MoveToPos は その3 の OnEndDrag で使ったものと同じです。
元は別処理でしたが、使いまわしが利く方法はないか、クラスの設計を見直しました。
OnDrag のみ判定する(それ以外では判定しない)
細かい仕様ですが、この牌入れ替えが必要なのは OnDrag している時のみです。
なにも場合分けせず「枠に近づいたら無条件で入れ替え」という単純化したロジックにすると、OnEndDrag で牌が戻っている時に入れ替えが起こる…など予測しづらい問題が起こるかもしれません。
完成
以上で牌を任意に入れ替える処理、完成です!
こんな風に無茶苦茶な速度で入れ替えても大丈夫。
設計が甘いと、こういう操作で大抵不具合が起こり、プログラマは想定外の操作だと仕様説を唱え、逆切れします。暖かく見守ってあげてください。