Delphi VCLで３Ｄダンジョンを作ってみよう【マップエディタ編】

# お題　ＶＣＬで３Ｄダンジョンを作ります
右へ左へと折れ曲がり続く地下迷宮を、DelphiのVCL (Visual Component Library) でつくります。と、言っても、ごく簡単に。

![説明0000.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273793/e8a25b88-c481-2119-0116-3606b32d0194.png)


つくるアプリは２つです。
 12/14　マップエディター編:　地面を掘って、地下迷宮を作ります。
 12/15　迷路編　　　　　　:　迷宮地図データを、壁が立ち並ぶ３Ｄ迷宮に描きます。

今回は、マップエディターを作成します。と、いってもすごく簡単なものです。

# 環境と準備
言語　Delphi 12 Community Edition
ターゲットプラットフォーム Windows 32ビット
※64ビットでもできますが、32ビットの方がデバッグしやすいため、32ビットがお勧め。完成したら、64ビットに切り替えてコンパイルしても良いです。 

# 画面を準備します
まず、エクスプローラーでマイドキュメントを開いて、
 C:\Users\【中略】\ドキュメント\Embarcadero\Studio\Projects
 の下に、「MapEditer」 みたいな、適当な名前で作業用のフォルダーを作ってください。

つぎに、Delphi 12 Community Editionを起動します。
_ファイル > 新規作成 > Windows VCLアプリケーション_
まず、マップ作製アプリ（簡易版）のプロジェクトを作ります。

![MapEdter-001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273793/cc91b2a4-baff-d598-5393-e0bae76e522a.png)

画面は、こんな感じで準備してください。

## 操作方法

StringGrid1を、マウスでクリックすると1と0が反転します。これで迷路を描きます。
［計算ボタン］を押すと、StringGrid2に計算結果を出力して、保存ダイアログが開きます。
マップデータをCSVファイルで出力します。

![MapEdter-002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273793/235eb183-690c-1130-87e2-8b8a0de3066e.png)

マップエディターは、左側のStringGrid1をクリックして、通路を描きます。
StringGridの　onDrawCell　イベントで、セルの値が　1　ならclGray　0　ならclSkyBlue　で　FillRectしてマップを描いています。


## 3D迷路のデータ構造について

この項でつくる３Ｄ迷路は、こんな感じです。白が壁で、黒が通路になります。
自機がいるセルと前方２セルまでを表示します。
4番の通路の部分ですが、曲がり角の向こうも表示されるように作りますが、今回のマップエディター編では説明を割愛します。

![迷路説明図003.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273793/a4a795fa-9fd2-cc00-daae-94eb56cbe8e8.png)

丸数字が、上図の迷路の描画と、下図の説明で対応しています。
この例だと、現在X:2　Y:7　のセルで　Arrow:0　上を向いています。
３　４　６　９　の壁がありません。通過可能です。

![通路と壁の関係１.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273793/09552a65-b855-5656-2c68-8891942cbb94.png)


## 計算ボタンを押したら、セルの値はこうなります

計算ボタンをクリックすると、StringGrid1のセルを　forループでスキャンして、結果をStringGrid2　へ書き込みます。
マップエディターは、各セルに対して、周囲４セルを調べて、壁か　通路かを判定します。判定結果を、そのセルのデータとします。

![迷路説明図023.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273793/9876d4e6-0bb8-956f-21ac-c739e8acdfb5.png)

できあがったデータは、 TStringList に ADD で書き足して、ファイル出力します。
データ形式は、「,」を足して、CSV形式にしてあります。

もうひとつ。明日のメニューになりますけど、迷路描画プログラムは、自機がいるセルから、周囲プラスマイナス３セルを調べて、マップを表示します。なので、計算を単純にするために、マップは、外周３セル分を固定壁扱いにしています。


ここで作ったマップデータは、明日、３Ｄ迷路作成で使用します。
以上が、マップエディターになります。

# プログラム全文を載せます。
▼▼▼　クリックして　開いてください　▼▼▼

<details><summary>TfmMapEditer (maps.pas) のプログラム全文　[クリックで開いてください]</summary><div>

```pascal
unit Maps;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.Grids, Vcl.ExtCtrls, Vcl.StdCtrls;

type
  TfmMapEditer = class(TForm)
    Panel1: TPanel;
    StringGrid1: TStringGrid;
    StringGrid2: TStringGrid;
    Button1: TButton;
    Button2: TButton;
    SaveDialog1: TSaveDialog;
    procedure FormCreate(Sender: TObject);
    procedure StringGrid1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure StringGrid1DrawCell(Sender: TObject; ACol, ARow: LongInt;
      Rect: TRect; State: TGridDrawState);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  fmMapEditer: TfmMapEditer;

implementation

{$R *.dfm}

procedure TfmMapEditer.Button2Click(Sender: TObject);
var
  x,y:longint;
  M,Wall,path:String;
  StList:TStringList;

begin
  StList:=TStringList.Create;  //文字列リスト作成

  try
  with StringGrid1 do   //外周２セル分は壁なので、2-18を読みます
    for y:=3 to 18 do
    begin
      M:='';

      for x:=3 to 18 do
      begin
        Wall:='';

        if cells[x,y-1]='1' then  //前のセル
          Wall:=Wall+'1'
        else
          Wall:=Wall+'0';

        if cells[x+1,y]='1' then  //右のセル
          Wall:=Wall+'1'
        else
          Wall:=Wall+'0';

        if cells[x,y+1]='1' then  //後のセル
          Wall:=Wall+'1'
        else
          Wall:=Wall+'0';

        if cells[x-1,y]='1' then  //左のセル
          Wall:=Wall+'1'
        else
          Wall:=Wall+'0';

        if Cells[x,y]='1' then    //自セルが１は全面壁
          Wall:='1111';

        StringGrid2.cells[x,y]:=Wall;  //表示用。StringGrid2に書き込み
        M:=M+Wall+',';                 //出力用　カンマ切り付加
      end;

      Stlist.add(M);   //文字列リストに追加
    end;

    path:=ExtractFilePath(Application.Exename);  //このアプリのパス
    SaveDialog1.InitialDir:=path;
    SaveDialog1.filename:='WallMap.csv';
    if SaveDialog1.Execute=true then
      StList.SaveToFile(SaveDialog1.filename);        //文字列リストを保存

  finally
    StList.free;                                 //文字列り糸を解放
  end;
end;

procedure TfmMapEditer.Button1Click(Sender: TObject);
var
  x,y:longint;
begin
  for y:=0 to 20 do
    for X:=0 to 20 do
      StringGrid1.cells[x,y]:='1';    //すべてのセルを壁する

end;

procedure TfmMapEditer.FormCreate(Sender: TObject);
var
  x,y:longint;
begin

  with StringGrid1 do   //StringGrid1 マップを描く方を用意
  begin
    Align:=alLeft;
    RowCount:=21;       //縦横21セル　
    ColCount:=21;       //ただし外周２セル分は壁で固定

    for y:=0 to 20 do
      ColWidths[y] := 20;   //セルの大きさ　20✕20pix

    for x:=0 to 20 do
      RowHeights[x] := 20;

    width:=(ColCount+1)*ColWidths[0]+10;
  end;

  with StringGrid2 do   //StringGrid2 計算後のデータを表示
  begin
    Width:=StringGrid1.width;
    Align:=alLeft;
    RowCount:=21;       //縦横21セル
    ColCount:=21;       //ただし外周２セル分は壁で固定

    for y:=0 to 20 do
      ColWidths[y] := 40;  //セルの大きさ　40✕40pix
                           //文字を表示するため大きめ
    for x:=0 to 20 do
      RowHeights[x] := 40;
  end;

  Button1Click(Sender);    //StringGrid1をクリアしてマップ作成準備
end;

procedure TfmMapEditer.StringGrid1Click(Sender: TObject);
begin
  with StringGrid1 do     //外周３セル分は壁固定なのでexit
  begin
    if (col<3)or(col>17) or (row<3)or(row>17) then
      exit;

    if Cells[col,row]='0' then  //1と空白をクリックで切り替え
      Cells[col,row]:='1'
    else
      Cells[col,row]:='0';
  end;
end;

procedure TfmMapEditer.StringGrid1DrawCell(Sender: TObject; ACol, ARow: LongInt;
  Rect: TRect; State: TGridDrawState);
begin
  With StringGrid1.Canvas do  //StringGrid1を塗ります
  begin
    Brush.Color:=clSkyBlue;   //通路は青空色

    if StringGrid1.Cells[Acol,Arow]='1' then
      if (Acol<3)or(Acol>17)or(Arow<3)or(Arow>17) then
        Brush.Color:=clGray         //外周２セルは固定壁でグレー色
      else
        Brush.Color:=clSilver;      //書き換え壁は銀色

    FillRect(Rect);                 //セルを塗りつぶし

    textout(Rect.left+8,Rect.top+2,StringGrid1.cells[Acol,Arow]);
  end;

end;

end.

```

</div></details>