今回は、アクツクMVで迷路の自動生成を試みました。
(ツイッターで動画:https://twitter.com/havin_nothin/status/1625674204107636736 )
とりあえず、こうすればできますという手順を説明します。
1.プロジェクトはトップビュー、画面サイズは680x360、タイルサイズは24x24で作成しています。
プロジェクト設定から、オブジェクトグループに「迷路」を追加しています。
2.タイルを登録。タイル設定から、ギミックタイルをチェック。
(画像はトップビューのチュートリアルプロジェクトのimgファルダにある041.pngを使用)
3.迷路の「壁」にしたいタイルをクリックします。(画面では左上の赤い四角)
迷路グループがタイルに重なったら、別のタイルに変化して「道」になる設定をします。(画像の右側)
壁判定が必要なら基本設定で設定します。
4.シーンを作成し、タイルを設定。画面全体を「壁」で埋める。
5.アニメーションを作る。
「透明」モーションを作成します。アニメーションと言っても、画像は何でもよいですが、ないとダメなのです。スケール0%にして表示されないようにする。
画面では、タイルと同じ画像をつかっています。(23x18に分割)
(画面の「四角」モーションはデバッグ用に表示するためのものです)
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とありますが、たくさんあってもいいので多めに作ってます。
7.アクションプログラム
アクションプログラムは、以下の画面のようになります。
とりあえず4つのアクションを作って、画面のように→でつないでください。
「開始」アクションに、モーション「透明」に設定。それ以外のアクションは「遷移前のモーションを引き継ぐ」にチェックしてください。
「何もしない」ボックスはなにもしていません。
↓は最後の「移動→終了」以外は、すべて「無条件で切り替え」です。
「移動→終了」の↓の設定は画面の通りです。「判定優先度」を別の矢印より大きくしてください(画面では2)。
変数MazaNowはカウンタです。カウンタが変数MazeSizeより大きくなったら終了ということです。
「終了」アクションはなにもしていません。
8.スクリプト
スクリプトは2つあります。
「迷路データ作成」と「迷路を掘る」です。
まず、「開始」アクションに「迷路データ作成」スクリプトを設定します。
スクリプトはこちら
(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);
}
})()
「移動」アクションにも同様に、「迷路を掘る」スクリプトを設定します。
(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オブジェクトは画面外に配置してください。
「迷路を掘る」スクリプトは、変数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から。↓こんな感じ。
(「終了」アクションでモーションを引き継がないで「設定しない」にすると、迷路を掘らなくなるのでいいかも)
長くなったので、どのように実現したかは
次回 https://qiita.com/havin_nothin/items/fa2bf322b366e8ed8473
にします。
では。
迷路作成にあたって、以下のサイトを参考にさせていただきました(っていうか、改変して使用させていただきました。ありがとうございます。)
https://little-strange.hatenablog.com/entry/2021/05/02/224300