🐱

FileMakerで2次元配列ゲームの作り方(後編)

2021/12/11に公開約4,900字

では、怒涛の後編(完結編!)です

前編、中編はコチラ

https://zenn.dev/ontherocks_plz/articles/c4e1fbd70e263c
https://zenn.dev/ontherocks_plz/articles/4437ebb2796a7d

ここからが正念場

前回までで、スネークが動き続けるところまでは出来たと思います。

問題になるのは、スネークの長さが1マスの時はこれで良いのですが、エサを食べて2マス以上の場合、つまり長さが伸びている時の動きです。

例えば次のように、3マスの長さのスネークを下に方向転換させたとします。

頭の方向転換自体は良いとして、もともと尻だった部分 [2][3](グレー色にした座標) は消す必要があります。

実際は方向転換先が下でなくても上でも、そのまま右でも、とにかく動き続ける以上は尻を消し続ける必要があります。そもそもとして胴体も頭に追従させ続ける必要があります。

どの方向に動き続けるか分からない状態で、一見複雑そうなこの処理をどうするか。


スネークの位置をトレース

ポータルを使った2次元配列管理の簡便さがここで活きてきます。
まずスネークの胴がどんどん伸びる仕組みは、前回作成した____moveスクリプトの、

9行目、

$$snakeLocate
JSONSetElement ( "" ;
	[ $snakeCIE ; 1 ; 2 ]
 )

ここを、

$$snakeLocate
JSONSetElement ( $$snakeLocate ;
	[ $snakeCIE ; 1 ; 2 ]
 )

このように、JSONSetElementの引数が空だった箇所に自分自身$$snakeLocateを設定します。こうすることで、どんどん新たな配列座標$snakeCIEで値「1」が自身$$snakeLocateに追加され胴が伸びていきます。

実際に改変してやってみます。

キモいですね...。
ということは、自動で色々な絵を描画させることも可能なんですが、それはまた別の機会として、後は尻を消していけば胴が頭に追従しているように見えるスネークが完成です。

もう一度、先程の図を詳しく見てみます。

方向転換前後の配列座標を、頭から改行区切りで記載すると...

転換前
[2][5]・・・頭
[2][4]・・・胴
[2][3]・・・尻

転換後
[3][5]・・・頭 ← new!
[2][5]・・・胴
[2][4]・・・尻
[2][3]・・・不要 ← erase!

となっています。
もうお分かりですね、つまりスネークがどう動こうと、

  • スネークの配列座標を改行区切りで常に保持
  • 頭の新規配列座標をリストの最初に追加し、最後の尻配列座標を消す

というロジックで、保持した配列座標リストを元にスネークの尻を消し続ければ良いということになります。

配列座標リストがどこまで積み重なった時に消すかは、スネークの現在の長さに依りますよね。上記例で言えば、現在スネークの長さは3マスですので、改行区切りが4つ積み重なったら尻を消して積み重なりを3つにします。その為に、別途現在のスネークの長さも保持しておく必要があります。


スネークがエサを食べた時

前述の考え方に則って、スネークがエサを食べる前後を見比べると...

つまりエサを食べた時は、

  • 頭の新規配列座標をリストの最初に追加しても、最後の尻配列座標は消さない

ということになるので、保持している現スネークの長さを+1マスして、改行区切りの積み重なりを消す条件を上げるようにします。

エサを食べれば食べるほど、保持している配列座標リストの積み重なりを消す条件が緩くなるということですね。


ゴリっと盛り込みます

では、これらを元にスクリプトを怒涛の如く丸っと改修していきます。

「draw.apple」スクリプト

まず、中編でも述べましたが、スネークの移動先にエサがあるかの判定をかける為に、エサの座標を保持しておかないといけませんので、draw.appleを改修します。

  • 4行目:ここで$$appleCIEに配列座標を保持

ポータルはスネーク描画時に更新されますので、ポータルの更新ステップは外します。

「____move」スクリプト

次に、OnTimerでキックされる____moveスクリプトの改修です。

うわっ長なった!って思うかもしれませんが、大丈夫です。見やすいように敢えてステップを分解しているだけで実は簡単です。纏めると本来は僅か10行程度で終わるスクリプトです。

ちなみに7行目までは変更なしです。以下、追加点になります。

  • 9行目:スネークの配列座標を保持する為、そのリストを$$snakeCIE.Listとしてスネークの新規頭の配列座標$snakeCIEを改行区切りで先頭にどんどん追加
  • 11行目:もし、スネークの頭座標$snakeCIEがエサ座標$$appleCIEと重なったら、
  • 13行目:スネークの長さ$$snakeLengthに1追加($$snakeLengthの初期値はstartスクリプトで設定します)
  • 14行目:エサを食べられたのでdraw.appleを呼び出してエサ再配置
  • 18行目:現在、$$snakeCIE.Listにいくつ配列座標が積み重なっているか$listLengthにカウント
  • 20行目:もしその重なり数$listLengthが、スネークの長さ$$snakeLengthを越えたら、
  • 22行目:消す配列座標、つまり尻配列座標を$erase.snakeCIEに取得
  • 23行目:その配列座標値をNullに(JSONSetElementのタイプをJSONNull
  • 24行目:スネーク配列座標リスト$$snakeCIE.Listから尻座標を除外

という流れになります。
注意する点としては、23行目で対象配列を消すからと言ってJSONDeleteElementでやってはいけません。これをすると配列が詰まることになり、スネークがぶった斬られて大参事になります。

また24行目でLeftValuesを使う為、最後に改行が付いて返却されますがケアは必要ありません。配列座標は常に頭に追加していくのと、18行目のカウント時のValueCount関数では最後の改行は無視される為です。

「start」スクリプト

最後に、startスクリプトの改修です。

4行目までは一緒で、

  • 5行目:初期スネーク配列リスト$$snakeCIE.Listを設定、当然最初は$snakeCIEのみ
  • 7行目:初期スネークの長さ(ここを変えると、その長い状態で開始)
  • 9行目:初期エサ配置

くらいですかね。


そして伝説へ・・・

以上の僅か3つのスクリプトだけで出来る、スネークゲームの実際の動きです。

どうですかね、上手く再現できましたでしょうか?

OnTimerの引数(実行秒間隔)を色々触って遊んでみて下さい。引数を0.2秒とかにすると、スネークの動きがビックリするほど速すぎて難易度はなかなかに上がると思います。また初期スネークの長さ$$snakeLengthを「5」とかにすると、いきなりハードモードスタートです。

ただ、「ポータルを使用している」また「OnTimerの仕様」といった点から、リアルタイム処理が必要なこういったゲームは色々と限界はあります。解決策として、サーバーサイド(PSOS)を利用する手法を検証中ですが、それはまたいずれ...


怒涛のまとめ

一先ず、エサの配置・スネークの動き・スネークがエサを食べた時など、これら一通りの大まかな考え方と処理は以上のようになります。ただ実際のゲームとして仕上げるには、もう少し考慮する点があります。

  • スネークが行列範囲外(ポータル外)へ移動した時の処理
  • スネークの頭がぐるっと回って自身の体に当たった時の処理
  • エサのランダム配置時、既存スネークに重ならないようにする処理

などなど。
今回はその辺りの処理までは解説しませんでしたが、ただこれらも結局は配列座標同士での判定になりますので、エサとスネークの$$appleCIE $snakeCIE $$snakeCIE.List、この辺り同士を比較すれば可能になりますね。


おわりに

前中後編、いかがだったでしょうか。
2次元配列で値を管理できるゲームは、ネイティブでもポータルを使えば比較的再現し易いことが分かったかと思います。しかも用意するテーブルやフィールドは僅か、スクリプトも長くない。今回は細かくスクリプトステップをバラしていますが、最適化すればもっとコンパクトになると思います。

配列を使う利点は何と言っても、例えば将棋やオセロなどは、その時その時の駒配置の配列がある為、それを保存しておけば後でそれが忠実に再現出来る点です。何なら多言語でも使用できます。ただの配列ですから。

この機会にゼヒ、FileMakerのゲームとしての可能性を追及してみてはいかがでしょうか。実務でも役立つ意外な手法が、ひょっとしたら編み出せるかもしれません。

それでは
Let's enjoy FileMaker!

鬼頑張ればポータルでコラムスも...

Discussion

ログインするとコメントできます