0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

アクツクMVで迷路を自動生成してみた

Last updated at Posted at 2023-02-22

今回は、アクツクMVで迷路の自動生成を試みました。
(ツイッターで動画:https://twitter.com/havin_nothin/status/1625674204107636736

とりあえず、こうすればできますという手順を説明します。

1.プロジェクトはトップビュー、画面サイズは680x360、タイルサイズは24x24で作成しています。
 プロジェクト設定から、オブジェクトグループに「迷路」を追加しています。

2.タイルを登録。タイル設定から、ギミックタイルをチェック。
(画像はトップビューのチュートリアルプロジェクトのimgファルダにある041.pngを使用)

3.迷路の「壁」にしたいタイルをクリックします。(画面では左上の赤い四角)
迷路グループがタイルに重なったら、別のタイルに変化して「道」になる設定をします。(画像の右側)
壁判定が必要なら基本設定で設定します。
01tile.png

4.シーンを作成し、タイルを設定。画面全体を「壁」で埋める。
02scene.png

5.アニメーションを作る。
「透明」モーションを作成します。アニメーションと言っても、画像は何でもよいですが、ないとダメなのです。スケール0%にして表示されないようにする。
画面では、タイルと同じ画像をつかっています。(23x18に分割)
(画面の「四角」モーションはデバッグ用に表示するためのものです)
03anim.png

6.MazeMakerオブジェクトを設定する。
作成したアニメーションで迷路作成オブジェクトを作る。
名前は「MazeMaker」にしてください。別の名前は不可。
以下変数を登録します。これも別の名前は不可です。

TileSize 初期値:タイルサイズを入力。(ここでは24)
MazeNow 迷路作成の現在値のための変数:初期値:0
MazeSize 迷路の大きさ:初期値:0
MazeSizeX 迷路の横幅 初期値:横幅を入力。(ここでは15)
MazeSizeY 迷路の縦幅 初期値:縦幅を入力。(ここでは13)
OffsetX 初期値:迷路の位置のX座標(ここでは5)
OffsetY 初期値:迷路の位置のX座標(ここでは1)
RoadType 道の種類を示す(後述)初期値:-2
変数001~ 迷路の縦幅(MazeSizeY)分の変数を用意する。名前はかならず、変数001、変数002と連番。(画面下の「+」を押したら自動で名づけられる、そのままを使う)

MazeSizeX、MazeSizeYが迷路の大きさになります。縦横どちらも奇数にしてください。横幅は49以下です。50以上は仕様上できません。縦幅には制限はありません(理論上)。
(画面では変数001~変数025とありますが、たくさんあってもいいので多めに作ってます。
04variable.png

7.アクションプログラム
アクションプログラムは、以下の画面のようになります。
とりあえず4つのアクションを作って、画面のように→でつないでください。

「開始」アクションに、モーション「透明」に設定。それ以外のアクションは「遷移前のモーションを引き継ぐ」にチェックしてください。
「何もしない」ボックスはなにもしていません。

↓は最後の「移動→終了」以外は、すべて「無条件で切り替え」です。
「移動→終了」の↓の設定は画面の通りです。「判定優先度」を別の矢印より大きくしてください(画面では2)。
変数MazaNowはカウンタです。カウンタが変数MazeSizeより大きくなったら終了ということです。
「終了」アクションはなにもしていません。

06DigEnd.png

8.スクリプト
スクリプトは2つあります。
「迷路データ作成」と「迷路を掘る」です。
まず、「開始」アクションに「迷路データ作成」スクリプトを設定します。
04MazeData.png

スクリプトはこちら

(function () {
    //迷路データ作成
 
   const mazeMaker = Agtk.objectInstances.get(instanceId);
    let mazeMaxX = mazeMaker.variables.get(mazeMaker.variables.getIdByName("MazeSizeX")).getValue();
    let mazeMaxY = mazeMaker.variables.get(mazeMaker.variables.getIdByName("MazeSizeY")).getValue();
    mazeMaker.variables.get(mazeMaker.variables.getIdByName("MazeSize")).setValue(mazeMaxX * mazeMaxY); let mztxt = '';
    let dd = 0;
    let mps = [];

    makemaze(); //迷路作成0か1のテキストがmztxtにはいる。0が通路

    //Agtk.log(mztxt);
    for (i = 1; i <= mazeMaxY; i++) {
        let mzd = parseInt(mztxt.substr((i - 1) * mazeMaxX, mazeMaxX), 2);
        let mdname = "変数" + ("000" + i.toString()).slice(-3);
        Agtk.log(mdname);
        Agtk.log(mzd);
        Agtk.log(mzd.toString(2));
        mazeMaker.variables.get(mazeMaker.variables.getIdByName(mdname)).setValue(mzd);
    }

    mazeMaker.variables.get(mazeMaker.variables.getIdByName("MazeSize")).setValue(mazeMaxX * mazeMaxY);

    function makemaze() {
        mztxt = '';
        for (i = 0; i < mazeMaxX * mazeMaxY; i++) {
            mztxt += '1';
        }
        mps = [];
        mps.push([1, 1]);
        mzw(1, 1);
        dd = 1;
        do {
            mpi = Math.floor(Math.random() * mps.length);
            if (mz(mps[mpi][0] - 2, mps[mpi][1]) + mz(mps[mpi][0] + 2, mps[mpi][1]) + mz(mps[mpi][0], mps[mpi][1] - 2) + mz(mps[mpi][0], mps[mpi][1] + 2) == 0) { mps.splice(mpi, 1); } else {
                dig(mps[mpi][0], mps[mpi][1]);
            }
        } while (dd < Math.floor(mazeMaxX / 2) * Math.floor(mazeMaxY / 2));

    }

    function dig(xd, yd) {
        vt = Math.floor(Math.random() * 4);
        vy = Math.floor((vt - 1) / 2);
        vx = (vt * 2 - 3) - vy * 3;
        if (mz(xd + vx * 2, yd + vy * 2) == 1) {
            mzw(xd + vx * 2, yd + vy * 2);
            mzw(xd + vx, yd + vy);
            xd += vx * 2; yd += vy * 2;
            mps.push([xd, yd]);
            dig(xd, yd);
            dd++;
        }
    }

    function mz(ax, ay) {
        if (ax < 0 || ax >= mazeMaxX || ay < 0 || ay > mazeMaxY) { return 0; }
        else { return parseInt(mztxt.substr(ax + ay * mazeMaxX, 1)); }
    }

    function mzw(ax, ay) {
        mztxt = mztxt.substr(0, ax + ay * mazeMaxX) + '0' + mztxt.substr(ax + ay * mazeMaxX + 1);
    }
})()

「移動」アクションにも同様に、「迷路を掘る」スクリプトを設定します。
05Dig.png

(function () {
    //迷路を掘って、道タイプをRoadTypeに入れるスクリプト
    //MazeMaker以外のオブジェクトで使用するときは、変数MazeNowとRoadTypeの設定が必要
    const dropper = Agtk.objectInstances.get(instanceId);
    const mazeMaker = Agtk.objectInstances.get(Agtk.objectInstances.getIdByName(-1, "MazeMaker"));

    const tileSize = mazeMaker.variables.get(mazeMaker.variables.getIdByName("TileSize")).getValue();
    const mazeMaxX = mazeMaker.variables.get(mazeMaker.variables.getIdByName("MazeSizeX")).getValue();
    const mazeMaxY = mazeMaker.variables.get(mazeMaker.variables.getIdByName("MazeSizeY")).getValue();
    const offsetX = mazeMaker.variables.get(mazeMaker.variables.getIdByName("OffsetX")).getValue();
    const offsetY = mazeMaker.variables.get(mazeMaker.variables.getIdByName("OffsetY")).getValue();

    const mazeNow = dropper.variables.get(dropper.variables.getIdByName("MazeNow")).getValue();

    //迷路情報を取得、復元
    let mazeData = '';
    for (i = 1; i <= mazeMaxY; i++) {
        let mdname = "変数" + ("000" + i.toString()).slice(-3);
        let mztxt0 = mazeMaker.variables.get(mazeMaker.variables.getIdByName(mdname)).getValue().toString(2);
        mazeData += mztxt0;
    }
    const mazeBits = mazeData + "1".repeat(mazeMaxX);


    //道タイプ判定(上左右下の2進数:0が道、1が壁)
    const bitCenter = mazeBits.substr(mazeNow, 1);
    const bitUp = mazeBits.substr(mazeNow - mazeMaxX, 1);
    const bitLeft = mazeBits.substr(mazeNow - 1, 1);
    const bitRight = mazeBits.substr(mazeNow + 1, 1);
    const bitDown = mazeBits.substr(mazeNow + mazeMaxX, 1);
    let loadType = -1;

    //現在地が道ならそこに移動して道を掘る
    if (bitCenter == "0") {
        //Agtk.log(bitUp + bitLeft + bitRight + bitDown);
        loadType = parseInt(bitUp + bitLeft + bitRight + bitDown, 2);

        tileX = mazeNow % mazeMaxX + offsetX;
        tileY = Math.floor(mazeNow / mazeMaxX) + offsetY;
        digx = tileX * tileSize + tileSize * 0.5;
        digy = tileY * tileSize + tileSize * 0.5;
        dropper.variables.get(dropper.variables.getIdByName("X座標位置")).setValue(digx);
        dropper.variables.get(dropper.variables.getIdByName("Y座標位置")).setValue(digy);
    }

    //変数を書き出す
    dropper.variables.get(dropper.variables.getIdByName("MazeNow")).setValue(mazeNow + 1);
    dropper.variables.get(dropper.variables.getIdByName("RoadType")).setValue(loadType);

    //Agtk.log(mazeBits);
    //Agtk.log(bit);
})()

9.MazeMakerオブジェクトをシーンに配置して、実行すると出来上がりです。
MazeMakerオブジェクトは画面外に配置してください。
07Maze.png

「迷路を掘る」スクリプトは、変数MazeNowに迷路上の現在値をいれて実行すると、変数RoadTypeに、周囲がどんな形かを入れます。
形とRoadTypeの対応(上下左右は通路の空き方向)

1か所だけ(行き止まり)
上  7
左  11
右  13
下  14
2か所(直線か曲がり角)
上左 3
上右 5
上下 6
左右 9
左下 10
右下 12
3か所(三叉路)
上左右 1
上左下 2
上右下 4
左右下 8
4か所(十字路)
上下左右 0

MazeNowの位置が道ではない -1

MazeNowは左上から横に数えて、何番目かをしめす。開始は1から。↓こんな感じ。
image.png

(「終了」アクションでモーションを引き継がないで「設定しない」にすると、迷路を掘らなくなるのでいいかも)

長くなったので、どのように実現したかは
次回 https://qiita.com/havin_nothin/items/fa2bf322b366e8ed8473
にします。

では。

迷路作成にあたって、以下のサイトを参考にさせていただきました(っていうか、改変して使用させていただきました。ありがとうございます。)
https://little-strange.hatenablog.com/entry/2021/05/02/224300

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?