【GameMaker】超簡単グリッドベースの経路探索を作ってみよう
はじめに
こんにちは!
GameMaker大好きクリエイターのはちのすです。
この記事は『GameMaker Advent Calendar 2024』の参加記事です。
なんかすごい人達がなんかすごい記事をいっぱい書いているので、見てみてね。
この記事では、「mp_grid」という関数を使ったグリッドベースの経路探索について紹介します!
現在開発中のストラテジーゲーム『絶対ロケット防衛主義』で使ってみたら、なんかすごい簡単にできたので、メモがてら書いてみることにしました。
mp_grid関数って?
mp_grid関数では、roomをグリッド状に分割し、ある地点からある地点までの最短経路を計算してくれる関数です。
主に見下ろし視点のゲームの敵AIの挙動などで使用できます。
A*アルゴリズムというなんかすごい仕組みを使って、障害物がある複雑な地形でもなんかいい感じに経路を計算してくれます。
敵を大量に出して2015年に買ったオンボロノートPCで動かしても処理落ちしないので、たぶん処理的にもそんなに重くないんだと思います(そこらへんはよく知らない)。
今回は「クリックした地点にプレイヤーを移動させる」という処理を作ってみます。
準備
今回は640x360の大きさのroomを作って、o_playerとo_wallというオブジェクトを作成しておきます。
今回はroomを32x32の大きさのセルに分割するので、o_wallには32x32の正方形のスプライトを設定しておきます。
エディターのグリッド表示を32x32にして、適当にo_wallとo_playerを配置します。
mp_grid関数の使い方
では、具体的なコードを解説していきます。
1.グリッドを作成する
まず、「グリッド」を作成する必要があります。
グリッドとは、roomをグリッド状に分け、どの地点に障害物があるかのデータを入れた地図のようなものです。
グリッドの作成には、mp_grid_create()関数を使います。
mp_grid_create( グリッドの開始点の左端のx座標, グリッドの開始点の上端のy座標, 横方向に並べるセルの数, 縦方向に並べるセルの数, セルの横幅, セルの縦幅);
今回は、セルの大きさを32x32にして、roomの左端から敷き詰める形にします。
oPlayerのCreate(作成)イベントに以下を書きます。
//Global関数でグリッドを作成
global.grid = mp_grid_create( 0, 0, room_width / 32, room_height / 32, 32, 32);
2.グリッドに障害物を登録する
次に、障害物(今回はo_wall)の情報をグリッドに登録します。
特定のオブジェクトの情報を登録する場合、mp_grid_add_instances()関数を1行書くだけでOKです(便利!)。
先程書いたmp_grid_createの下に以下を追加します。
//グリッドに障害物を登録する
mp_grid_add_instances( global.grid, o_wall, true);
3.プレイヤーのパスを作成する
次に、プレイヤーの経路である「パス」を作成します。
今回は「クリックした地点にプレイヤーを移動させる」という処理なので、o_playerのStep(ステップ)イベントに以下を書きます。
if(mouse_check_button_pressed(mb_left))
{
//クリックしたときにパスを作成する
path = path_add();
mp_grid_path( global.grid, path, x, y, mouse_x, mouse_y, 1);
}
4.プレイヤーのパスを再生する
パスを作成したわけですが、これだけでは動きません。
パスはいわば経路のデータでしかないので、それを再生する必要があります。
path_start()関数を使ってパスを再生すると、o_playerが動きます。
先程の書いたmp_grid_pathの下に、path_startを追加します。
if(mouse_check_button_pressed(mb_left))
{
//クリックしたときにパスを作成する
path = path_add();
mp_grid_path( global.grid, path, x, y, mouse_x, mouse_y, 1);
//パスを再生する
speed = 5;
path_start( path, speed, path_action_stop, true);
}
ではゲームを実行してみましょう。
o_playerがちゃんと障害物を回避して動いたら成功です!
おまけ①:経路を可視化する
デバッグする時に、経路を可視化して確認したくなることがあると思います。
そういう時は、draw_path()でパスを描画することができます。
oPlayerのCreate(作成)イベントに以下を追加して、
path = path_add();
oPlayerのDraw(描画)イベントを以下に書き換えます。
draw_self();
draw_path( path, 0, 0, true);
移動経路が描画されていたら成功です!
他にも例えば、path_get_point_x() / path_get_point_y() という関数で、パスの通過点の座標が取得できるので、それをなんやかんやして、こんな感じで敵の進撃経路を描画したりしています。
おまけ②:経路を少しランダムにする
今回やったことをそのまま実用すると少し問題なのが、例えば『絶対ロケット防衛主義』のようなタワーディフェンスで敵を大量に出した場合、開始地点と目的地点が同じだと、全ての敵が同じ経路を辿ってしまい、敵同士が重なってしまったり、不自然に見えてしまうことがあります。
これを解消するために、経路を多少ランダムにしてみます。
o_playerのStep(ステップ)イベントを以下のように書き換えます。
if(mouse_check_button_pressed(mb_left))
{
//クリックしたときにパスを作成する
path = path_add();
mp_grid_path( global.grid, path, x, y, mouse_x, mouse_y, 1);
//パスの通過点をランダムにする
var _x, _y, _random_size = 32 / 2;
for (var _i = 0; _i < path_get_number(path); _i++)
{
_x = path_get_point_x( path, _i) + irandom_range( -_random_size, _random_size);
_y = path_get_point_y( path, _i) + irandom_range( -_random_size, _random_size);
path_change_point( path, _i, _x, _y, 100);
}
//パスを再生する
speed = 5;
path_start( path, speed, path_action_stop, true);
}
上でも少し紹介した、path_get_point_x() / path_get_point_y() 関数で通過点の座標を取得し、それにランダムの値を足した後、path_change_point()関数で通過点の座標を書き換えています。
これで重ならずに自然な見た目になったと思います!
『絶対ロケット防衛主義』でも、敵やプレイヤーに追従する仲間の動きにランダムを加えて、重ならないようにしています。
そんな感じです!
お役に立てたら嬉しいです。
良きGameMakerライフを!
*宣伝*