ゲープロ講座セッション3:戦術型SLGの移動アルゴリズム(2)

- 移動アルゴリズムのソースを解析する
Article Written: 99/11/8




 2週間ちょっとのご無沙汰でした、鷹月ぐみなです。前回のアルゴリズム紹介はいかがだったでしょうか。なんとなく移動ルーチンが組めそうだと思いつつも、具体的な実装の仕方がさっぱりな人が多いのではないかと思います。そこで今回は、移動アルゴリズム部の簡単なDelphiサンプルソースを紹介しながら、実際にシステムとして稼動するサンプルアプリケーションを提供しています。今回の内容をマスターしてしまえば、結構SLG制作の道は近づくと思います。技術部分は鷹月のオリジナルなので、フリーで使うことができます。



    [ 図1 ]
 前回のおさらいも兼ねて、このグラフィックを見てもらいましょう。今日の主題はこちらです。戦術SLGではしばしお目にかかれる眺めです。あなたがプレイヤーキャラ(PC)のユニットをクリックするとこいういう画面になり、明るい部分を選びまたクリックすると、その場所に移動してくれるはずです。ここで不思議に思うべきは、「どうやって移動できる範囲を検索しているの?」ということです。この絵で言うならPCの右上にも平野があります。私たちが見れば「岩山があるから駄目じゃん」と分かっても、さてコンピュータはどう見ぬくのでしょうか?
 この、人間のもやもやした考えをはっきり文で表現することが「アルゴリズム」、それをプログラムソースに落とし動かす状態にすることが「実装」です。今日はこのルーチンを攻略していきましょう。
 まずは、実際に動く部分までは考えずに、単に「ある地点から歩数mvで進める地点を明示する」部分だけのアルゴリズムだけ思い浮かべましょう。なお、マップには「進める箇所」と「進めない箇所」があり、進める部分に関しても平地のように進みやすい場所、荒地のように進みにくい場所が存在します。これは処理を書く前にしっかり規定および仮定しておくべきでしょう。

《規定》
・PCの移動歩数を暫定的に6とする。
・平地マスは歩数1、荒地マスは歩数2、岩マスは歩数10を必要とする。

 いきなりちょっとした「テクニック」を使っています。岩マスは「進めない」ではなくて、ウェイト10にしておきます。PCは6歩しか歩けないのですから結局進めないマスです。「結局同じじゃない」と思うかもしれませんが、アルゴリズム上ではまったく変わってきます。「問題は単純化すること」はこの世界の原則です。ちなみに上の絵ではPCの歩数は3になっていることを確認しておいてくださいね。真下は荒地のせいで3つ進めない状態なのです。
 移動歩数をmvではなく、6としている所は「問題の具体化」を意味します。本来は変数にしておくべきなのですが、変数は便利な反面、可変なのでイメージが掴みにくくなるのです。とりあえず仮の数値を用意してあげれば、「ふむふむ、本来はPCは平地は6歩進めるんだな」と感覚的に理解が容易になるのです。

 一つ規定をした上で、以下の移動アルゴリズムを与えます。前回のそれよりさらに単純に、そして分かりやすく具体的になっていますんで、「多少は」安心してくださいね。

《試行》
1、初期化として、一時的に全マスは移動できない範囲であるということにします。上の図でいくなら、白っぽくなった箇所がゼロの状態だと思ってください。
2、PCのマスから、隣接する上下左右それぞれのマスに対して、以下の事を調べます。4方向について調べ終わったら処理完了です。
3、移動歩数値からそのマスの地形ウェイト数を減らします。マイナスになればその地点へは移動できないと分かり、一つの思考は終了します。
4、そうでないなら、少なくとも到達可能である事はわかったので、マークを付けてあげます。マークが付いた所は、実装時には白く光って表示されるというわけです。
5、その地点からさらに3方向に対して、3.以下の思考を繰り返します。なお3方向というのは、4方向のうち戻る方角を除いたものです。

 この結果、マークされた所を光らせてみればめでたく「移動可能地点」がしっかり表示されるはずです。あなたがSLG構築に興味があって、このアルゴリズムに完全に納得していないなら、必ず手で書いて試してみてください。これをトレースと言います。書き方は完全に任せますので。なおその場合、移動歩数が6だと長くなってしまうので、2や3あたりから試してみると良いでしょう。完成予想図が思い浮かばない人も、やはり紙にトレースしましょう。アルゴリズムというものは、頭が明確に理解していない限り、コンピュータに実装させることなんてできるわけがないのです。

 この2-5の流れは再帰処理と呼ばれています。ぱっと見では「繰り返し」のように感じますが、単なる繰り返しではありません。一つの流れからいくつもの流れができあがり、またその流れの一つからいくつもの流れを実行しています。具体的に何回3〜5の流れを繰り返しているのかを計算すると……移動歩数がmvで、マップが仮にすべて平地(ウェイト1)とした場合、4×3の(mv−1)乗回となります。mvは暫定的に6としたわけですから、4×3^5 =972回です。最近のコンピュータの性能のことですから、ほんの一瞬で計算してくれますが(MSXのようなマシンを使って、BASICで強引に処理させてみると時間かかりますよ〜)、人間はまじめにやる気はすでにありません。でも人間はこの完成図は予測できます。全部平地とした場合、長さ6のダイヤ(正方菱形)となるはずです。
 ちなみに、ある処理から回数限定で別な処理を掘って行くことを「アンカーを辿る(伸ばす)」とも言われています。ウェブに詳しい人は知っているかも知れませんね。この言葉によって処理を「歩数6でアンカーを辿る」と短く説明することができます。
 それはともかくとして、972回はいくらなんでも計算のさせすぎですね。正直効率悪いです。どの辺の効率が悪いかは明白で、同じ地点にマーキングをしたり、そこからまた同じ場所へ移動しているのです。「上→左」と、「左→上」で到達できる地点は同じで、その後の思考もほぼ同じなのです。ここで、先程のアルゴリズムには追加をしてあげます。「既にマークをした地点に来たら、そこからどこにも伸ばさないで流れを終える」これだけで回数は十分の一以下に激減します。アルゴリズムというのは考える人がちょっと仕組みを変えるだけで、サッパリと綺麗になったり、絶望的に劣悪なものにもなるのです。



// ユニット上のPCをクリックした時の処理
begin
  // マップ情報の初期化
  for i:=0 to 255 do for j:=0 to 255 do begin
    GW1.BGf[1,i,j]:=1;
    mvrec[i,j]:='';
  end;

  // 四方向にアンカーを伸ばす
  Search(px,py-1,mv,1);
  Search(px+1,py,mv,2);
  Search(px,py+1,mv,3);
  Search(px-1,py,mv,4);

  // この結果、画面表示ルーチンへ
end;

 ではプログラムソースを紹介しましょう。この言語環境はDelphi3.1J+TGWですが、アルゴリズムというのは基本的に言語非依存ですから、多少なりプログラム経験のある方は自分の使っている言語での再現が可能なはずです。では解説を始めましょう。上のソースは、先の試行1.と2.とそのまんま一致しています。ただ、プログラムスタイル的に細かくなっています。まず GW1.BGf[1,i,j] ですが、これが最終的に移動可能範囲を表示するためのマップフィルタです。iとjはマップ上のx,y座標を指します。この配列には1(移動不能っぽいですね)か0(移動可能っぽいですね)の情報のどちらかを書きこみます。また、mvrec[i,j] は、マークフラグを指します。ヌル状態はまだ到達していない地点、1は既に到達が確認された地点であることを示します。
 こうした初期化処理の後、アンカーを伸ばしています。3.から5.の再帰的試行は一つの手続きにまかせることにします。引数は4つあり、順番に検索対象x座標、同y座標、残り移動歩数、移動方向通知引数を示します。この関数の具体的なルーチンは次の通りです。

// 探索手続きSearch
Procedure Search(ax,ay,mv,di);
var down,num:integer;
begin

  // すでにチェック済みだったらこれ以上の探索は無意味(Par2-1)
  if mvrec[ax,ay]<>'' then exit;

  // 現在の地点にあるマップ情報を取り出して、ウェイトを計算(Par2-2)
  down:=0; num:=GW1.BGf[0,ax,ay];
  if (num=1) then down:=1;
  if (num=2) then down:=2;
  if (num=3) then down:=10; 

  // 歩数がマイナスになった地点へは進めないので思考中断(Par2-3)
  if mv-down<0 then begin
    exit;
  end;

  // マークをします(Par2-4)
  GW1.BGf[1,ax,ay]:=0;
  mvrec[ax,ay]:='1';

  // さらに現在の位置からアンカーを伸ばす(Par2-5)
  if di<>3 then Search(ax,ay-1,mv-down,1);
  if di<>4 then Search(ax+1,ay,mv-down,2);
  if di<>1 then Search(ax,ay+1,mv-down,3);
  if di<>2 then Search(ax-1,ay,mv-down,4);

end;

 これが手続き部分の全てです。3. から5.の試行通りになっていることを確認してください。これら2つの処理の結果、図1のような画面を表示する準備が整うというわけです。プログラム経験者は、「あら、ほんとこれだけでできるんですか〜」と感じるのではないでしょうか。
 ソースで一応流れをコメントしているので、あとは簡単に説明を加えていきます。Par2-2で、まず本当のマップから現在位置の地形情報を取り出しています。鷹月の使っているTGWコンポーネントでは配列によってマップを保持しています。 GW1.BGF[0,x,y] は地形マップ、GW1.BGf[1,x,y]は移動可能フィルタのマップを示します。この情報を元に、マップ表示のあと、透過処理で黒いフィルタをかけて、移動可能/不能地点をビジュアル的に表現するのです。グラフィック周りは今回のアルゴリズムとは直接関係はありませんので全て省略していますが。
 ともかく、検索対象のマップ情報を取り出します。1が平地、2が荒地、3が岩山であり、それぞれ減少歩数down を設定しています。その結果、残り歩数がマイナスにならないならば、マークをして、アンカーを続けていきます。
 最後のパラグラフ(Par2-5)が、アンカーを広げる部分です。第4引数がここで活躍します。1が上、2が右、3が下、4が左を示し、「自分が来た方角にはアンカーを戻さない」ということを実現しています。

 さて、アルゴリズムは理解できましたでしょうか。ぱっと見程度では分からなくて当然です。それでもアルゴリズムはかなり平易であることは間違いないのです。



    [ 図2 ]
 このアルゴリズムソースを実装し、操作のできるアプリケーションを制作してみました。その画面写真を図2に示しています。図1の頃とは違い、ちょっと本格的な作りになっていて、マップ移動、フィルタ表示、画面のスクロールなども実装しています。これは実際の戦術SLGを想定したプロトタイプバージョンとなっています。これから、このアプリケーションを試してもらい、それからソースを解説していこうと思います。
 では、ここから先を読む前に、このlzhファイルをダウンロード、適当なフォルダ内に解凍して、move.exe を実行して暫く試してみてください。Windows95/98/NT上で動作可能です(Machintoshの方ごめんなさい……)。なお、画像のチップ素材は泣く大臣さんのものを、BGMにはみくしぎさんのフリーサウンド「十字架の誓い」を使用させていただきました。
→ アプリケーションをダウンロードする(162K)
→ ソースファイルだけ見たい人はここから



 ……というわけで、しばらく動かしてみましたでしょうか。そういう前提でいきます。ソースファイルを見ても分かるように、先のアルゴリズムからもう少し拡張しています。指定地点へ最短距離で移動してくれることをしっかり確認しましょう。これはアルゴリズムには用意していなかった部分ですが、多少の改変で実装できるようになっています。各地点において到達可能か不可能かを示すだけのフラグ mvrec[x,y] があったと思います。これを、「その地点に到達したルートを記録しておく文字列」に変更すればいいのです。そうすれば、後で移動目標地点をクリックした際に、その文字列に書かれたとおりにPCを移動させればいいわけです。ここでポイントとなるのは、手続きArch(先のSearchと同じです)の4番目の引数である、方向情報です。この方向情報を順次加えながら渡していくことによりそれなりに実装できます。細かくソースの解説はしませんので、ソースファイルを各自追ってもらいたいと思います。それでも分からなければメールをお願いします。鷹月がこのコンテンツを「講座」としたのは、読み手からの質問や要望によって、それを反映させて行きたいと思ったからです。あなたがSLG制作を目指していて、ルーチンが分からなかったら聴きましょう。100%回答できるとはいいませんが、ソースおよびアルゴリズムをしっかり理解していただけるように努力しますので。
 話を戻しますが、いま加えたルーチンはまだ完全ではありません。最短距離になっていないのです。一歩下に動かそうとすると、なんと「上→右→下→下→左」という奇天烈な動きをしてくれるのです。素直に下に動いてくれないのです。こういう意図しない動きをするのは誰のせいでしょうか。他ならないアルゴリズムの設計者です。コンピューターは、指定した通りにしか動かないのです。そこで、なぜこういった事が起きるのかを考えてみましょう。
 原因は手続きの「再帰の順番」にあります。処理の順番は常に上→右→下→左の順番で行なわれます。いまの設定によると、すでに「到達地点までの方法情報が書かれている」場合は書きこまないようになていますので、上→右→下→下→左ではじめて到達した情報が書きこまれて、それを使ってしまうというわけなのです。上書きすれば良いのでは、と思いますが、こうすると逆に、上に動かしたときに「下→左→上→上→右」と動くようになってしまいます。これを解決するために使う方法が、書きこまれている到達移動情報の文字数と、現在までの移動情報の文字数を比較して、少ない方を最短として採用するという仕掛けです。これによって、すべての移動可能地点において、最短で到達できる道筋のみを記録してくれて、後でその通りに動いてくれると言うわけなのです。



 このサンプルプログラムは、まだ色々と解説しておきたいTipsがありますが、一度に積めこみすぎるのも何ですから、今日はこれくらいにしておきましょう。サンプルを動かして、「お、これが自分でも作れるなら、ひょっとしてこういうゲームの実装も可能なのでは?」と夢膨らませてみるのもいいかも知れません。このSLG講座は最後まではナビゲートはしませんが、十分な「手ほどき」くらいは提供しますので。次回はサンプルの解説の続きかな。それからセッション2で書いていた相手側の索敵・戦闘アルゴリズムへの発展(ケースの洗い出し)へと進めていこうと思います。
 感想などお待ちしています。ただし、サンプルプログラムの移植要望だけは受け付けませんのでご了承下さい。(てか、他の言語持ってないってば)

- 鷹月ぐみな



  Session4:戦術SLGの移動アルゴリズム3 (1999/11/10)



カレッジの入り口に戻る
鷹月ぐみな情報局2号館

Written by. gumina(鷹月 ぐみな)