【Unity パズル】構造設計にハマったので備忘録

【Unity パズル】構造設計にハマったので備忘録

こんにちは。体調が悪いと記事を書きがちなサカモトトマトです。

Unityで2Dのパズルゲーム(スマホ向け)を作っていますが、構造について詰まったところがあったので、忘れないようにメモしておく感じです。
不親切な部分もあるかもしれませんが、よろしくお願いします。

やりたいこと

・グリッド状の盤面に赤いオブジェクトと黒いオブジェクトがあります。
・プレイヤーは画面をフリック入力(スマホ向け)することで、上下左右に赤いオブジェクトを動かせます。
・黒いオブジェクトは動きません。赤いオブジェクトは、盤面の縁または黒いオブジェクトの手前にぶつかるまで進みます。

まぁ、早い話がRPGでおなじみの「氷の床」迷路ですね。これをUnityでパズルにしてみた感じです。

移動先の計算について

本編ではないので、ここはとばしてもいいです。

フリック方向の判定方法は、多くのゲーム開発で必要といえるのではないでしょうか。
タップ開始地点とタップ終了地点の位置差分、つまりベクトルを計算し、そのベクトルが垂直方向か水平方向かを判定します。上の図の座標の振り方の場合だと、Xが水平方向、Yが垂直方向ですね。
例えば、タップ開始地点の座標が(2,1), タップ終了地点の座標が(3,-3), だった場合、X方向に+1, Y方向に-4だけ動いています。Y方向のほうが、X方向よりも「移動量」が大きいので、垂直方向にフリックしたのだなと判定できます。
ここで、移動量は、各方向の「絶対値」を指します。UnityではMath.Abs()だったっけ?符号を含めた大小関係では +1 > -4 ですが、移動量は |1| < |-4| で、Y方向に大きく動いています。
さらに垂直方向といっても、移動先のY座標が減少している:ベクトルのY方向がマイナスのほうに動いているので、下にフリックしたのだな、とわかります。逆に大きい方向に行ったら上にフリックしたなということ。
X方向のほうがY方向よりも移動量が大きい場合は水平方向と判定できますし、左右の判定は同じようにベクトルのX方向の符号で分かります。

方向が分かったら、赤いオブジェクトの移動距離の計算ですが、ここはさほど難しくなく。
選んだ方向の1マス先に別のオブジェクトがないか、座標の範囲を超えていないか(例えば、xは0以上9以下であるべきところが、1マス先はマイナス座標になっていないか)を確認し、NGになるまで繰り返し1マス先、さらにその1マス先をと確認していけばよろしい。NGになったら、その手前まで動かせばいいので。

というところで、ここまでで
・フリックした方向に応じて、赤いオブジェクトの移動先を決める
方法が分かった。

構造設計

じゃあどうやってオブジェクトの座標とかを管理しようかなというお話ですが

ダメだったパターンから

X座標とY座標のマス目の上にオブジェクトを置くのだから、
二次元配列 board というのを作って、各要素にオブジェクトまたはNULL:空状態を直接入れてみました。
・二次元配列
 例えば、board の大きさが 10×10 なら、0以上9以下の好きな整数m, nを使って、board[m, n]を変数として使える。
 サカトマさんは、board[m, n]に変数としてオブジェクトを置けばいいんじゃない?と思ったわけだ。
・board の大きさが 10×10 なら、m, n は0から始まり、最大値は10-1=9:m, n が取りうる値は10通り。
上の図のように
board[1,2] には赤いオブジェクトが
board[4,4] には黒いオブジェクトが入る~て感じですね。まぁ見た目通りかな??。

この時点でダメだと勘づいたプログラマさんには、もう申し上げることございません。ありがとうございました。
今度サカトマさんにUnityのノウハウを教えてください。

じゃあ、もうちょっと、やろうとしたことを書きますね。

ダメだったパターンの計算例

(1,2)にある赤いオブジェクトの移動先を計算し、(3,2)に持っていきたいと思います。board[1,2] にあるオブジェクトを、board[3,2] に移し替えればいいんだから、
1.board[1,2] のオブジェクトをコピーして
2.board[3,2] にそのオブジェクトをペーストして
3.もともとあった board[1,2] のオブジェクトを削除し、空にする。

これで、移動元(1,2)にあったオブジェクトはなくなり、移動先(3,2)に同じオブジェクトを出現させたのだから、
やりたいことは実現できたでしょ?

 

罠ですぅ~!

計算は正しいのですが、表示させるときに赤いオブジェクトが瞬間移動してしまいました。
どうやら、移動元から移動先にかけてのアニメーションが、この方法だとできないようです。

2.board[3,2] にそのオブジェクトをペーストして

このとき、一瞬ですが board[1,2] board[3,2] に同じオブジェクトが2つ現れるんですねぇ。
ドッペルゲンガーかな?

違います。この2つは違うオブジェクトです。
board[1,2]のオブジェクトにアニメーションをかけても 3.で削除されているから確認できないし、
board[3,2]にアニメーションをかけても、はて(3,2)で生まれたんだが動く必要あるかなぁ
ということで動かないんですねぇ。

分かりやすくアニメーションを実現させるためには、オブジェクトをコピーしたりせず、動かしたいオブジェクトを一つ指定してやらないといけないようです。

成功した例

まず、itemsというオブジェクトの集合(図の右)を作ってみて、それぞれのオブジェクトの「表示座標」は各々持ってもらおうと思いました。UnityのオブジェクトにはTransform.positionという属性があって、そこで表示座標を管理できるようですが、ここを毎フレーム徐々に変えることでアニメーションさせる感じですね(実際にはDOTween使うけど)。
なので、計算上の座標は常に整数だったのですが、表示座標は小数も取り得ますよっと。

じゃあもともと計算用にあった二次元配列boardは要らなかったのでしょうか?
いや、boardがないと、赤いオブジェクトの右隣にオブジェクトがあるかどうかを知りたいときに、毎度すべてのオブジェクトの座標を調べないといけないので、計算コストのロスですねぇ。

でもboardにオブジェクトを入れる方法は失敗したので、とりあえず33とか34とかいった数字を持ってもらいましょう(図の中央)。数字があったらオブジェクトがあり、数字がないとかマイナスだったらない、的な。


さすがにそのままだと、赤いオブジェクトなのか黒いオブジェクトなのかって、分からないじゃないですか。
なので、オブジェクトにタグ番号をつけて、その番号をboardに紐づけようと思いました(再び図右)。
これで、(3,4) にあるオブジェクトは何?と聞かれたときには、34番を拾って、タグ34を持つオブジェクトを拾ってあれこれ計算に使う感じでございます。

ちょっとわかりにくいかもしれなかったかも。
フードコートでうどんを注文したとき、注文番号のついた端末を持って、整列された座席に座りますよね。
座席=boardの座標、注文番号=タグ番号、うどん=オブジェクト
として見ると、イメージが分かるかも。隣の人は何を食べるのかなと思ったら、注文番号見て、そこから注文の品を覗き見るイメージです(実際、覗き見ないけど)。

結構プログラミング用語を使っちゃった気がします。ごめんなさい。
一応、メモなので許してください(免罪符)。

報告する

コメント

  • コメント ( 3 )

  • トラックバックは利用できません。

  1. Q. なんで33から始めたんですか?
    A. とある開発現場では、32以下はシステム定数、33以上はカスタム定数と識別されているため、その名残でつけました。

    Q. 赤いオブジェクトと黒いオブジェクトだけなのに、タグ番号まで考える必要ある?
    A. 動くオブジェクトは複数あるかもしれませんし、動かないオブジェクトにもいろんなタイプのオブジェクトがあるかもしれないので、その拡張性を踏まえた実装方法になります。実際、私が現在作っているゲームでは動かすオブジェクトが複数あります。また、同じマスに動かないオブジェクトと動くオブジェクトが重なるかもしれないので、1マス当たりにそれぞれのタグ番号を保存する必要性が見えてきます。

  2. こんばんは

    せっかく物理エンジンがあるので、RigidBody2Dのvelocityをいじった方がいいと思います。

    Unity関連の書籍を漁れば、キー入力に応じてプレイヤーを上下左右に動かせるような例があると思うので、それを参考にすればいけるのではないでしょうか。

コメントするためには、 ログイン してください。