藍と淡々

物作り冒険譚


【Unity】「Trocco」で使った、タイルマップの自動生成アルゴリズム【基礎編】

こんにちは!(ؓؒؒؑؑؖؔؓؒؐؐ⁼̴̀ωؘؙؖؕؔؓؒؑؐؕ⁼̴̀ )

かなりお久しぶりの投稿になりましたが、私は元気です!

このながーーーく空いた期間で、やっとスマホ向けの自作ゲーム「Trocco」を完成させることができました!

リリースは4月末でしたが、その後のアプデ作業が大変でしたのでこんな時期に・・・(言い訳)



さてさて、それでは無事完成したことですので、この長いゲーム制作期間中に得た知識を順々にまとめていこうと思います。

今回のテーマは、
タイルベースの自動マップ生成アルゴリズム」です!


0、完成後のイメージ

説明の前に、完成後がどんな感じなのか、プレイ動画を見ていただけると理解しやすいです。(Sorry for graphic quality...)

via GIPHY



1、考え方

今回は「タイルベース」なので、1平方メートルを1つのマスとしてマップを作っていきます。

そのマスを埋める1平方メートルのマップチップを用意します。

とりあえずUnityのキューブが1立方メートルなので、これを使います。(タイルベースというよりキューブベース・・・)

f:id:MegumiSoft:20160619125852p:plain:w400


①直線のマップ

そしたらまずは原点からZ方向に直線のマップを作ってみます。

適当なスクリプトを用意してカメラにでもくっつけておきましょう。

void Start () {
    int zPos = 0;
    // Z位置を+1しながらマップを生成していく
    for(; zPos<10; zPos++) {
        GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
        cube.transform.position = new Vector3(0, 0, zPos);
    }
}

Playボタンを押せばこんな感じになります。

f:id:MegumiSoft:20160619131614p:plain:w400


②曲がったマップ

お次はうねうねした道を作ってみます。

void Start () {
    int zPos = 0;
    int xPos = 0;
    // Z位置を+1しながらマップを生成していく
    for(; zPos<10; zPos++) {
        // Z位置が更新されたら、まず1つマップチップを置く
        GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
        cube.transform.position = new Vector3(xPos, 0, zPos);
        // マップ生成のZ位置が2になったら右に1つ分、Cubeを置く
        if(zPos == 2) {
            xPos++;
            cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
            cube.transform.position = new Vector3(xPos, 0, zPos);
        } 
        // マップ生成のZ位置が6になったら左に3つ分、Cubeを置く
        if(zPos == 6) {
            for(int i=0; i<3; i++) {
                xPos--;
                cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
                cube.transform.position = new Vector3(xPos, 0, zPos);
            }
        }
    }
}

こちらはこんな感じ。

f:id:MegumiSoft:20160619135254p:plain:w400


ここまでをまとめると、

⑴ Z方向に+1しながらマップチップを置く場所を考える
⑵ 曲げたいところでX位置を調整してマップチップを置く

が基本です。常にマップ生成の先端位置を意識しながら(超重要)マップチップを置いていくイメージですね。

しかしこのままでは最初から決められた形のマップしか作れないので、ランダム要素を加えていきます。



2、ランダムな一本道

一本道でランダム要素を入れるのは、

⑴ どこで曲がるか
⑵ 曲がったらどのくらいX方向に進むか

です。

コードにするとこんな感じ。

void Start () {
    int zPos = 0;
    int xPos = 0;

    // Z位置を+1しながらマップを生成していく
    for(; zPos<10; zPos++) {
        // Z位置が更新されたら、まず1つマップチップを置く
        GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
        cube.transform.position = new Vector3(xPos, 0, zPos);

        // 乱数を用意して、その値分X方向に移動する。0が出たら曲がらず直進する
        int randomAddTips = Random.Range(-3, 4); // -3~3の範囲の乱数を得る
        // 乱数が0以上なら右に曲がり、乱数分進む
        if(randomAddTips > 0) {
            for(; randomAddTips>0; randomAddTips--) {
                xPos++;
                cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
                cube.transform.position = new Vector3(xPos, 0, zPos);
            }
        } 
        // 乱数が0以下なら左に曲がり、乱数分進む
        else if(randomAddTips < 0) {
            for(; randomAddTips<0; randomAddTips++) {
                xPos--;
                cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
                cube.transform.position = new Vector3(xPos, 0, zPos);
            }
        }
    }
}

*Random.Range(Min, Max)は、Min, Maxがint型の場合、乱数はMaxを含まず、float型の場合にはMaxを含むので注意。
Unity - スクリプトリファレンス: Random.Range


実際にいくつか生成してみると・・・

f:id:MegumiSoft:20160619152104g:plain:w400

マップチップがくっついていてわかりにくいですが、Z方向に10マス分の、ランダムな一本道が作れました。


しかしこのままでは、X方向進むマスの数に制限がないので、上のGIFにも出てきましたが、乱数によっては右もしくは左方向にとんがったマップができてしまいます。

なので、Xの位置を-3〜+3の範囲に制限し、その範囲内で自由にマップ生成ができるようにします。

たとえば、もし今現在マップ生成の先端のX位置が「-3」なら、左には曲がれず、右に最大6マス曲がれるので、

randomAddTips = Random.Range(0, 7);

になり、もしX位置が「1」なら、

randomAddTips = Random.Range(-4, 3);

になります。

イメージはこんな感じ。

f:id:MegumiSoft:20160619154350p:plain:w400

コードは上のやつにちょこっと手を加えるだけです。

void Start () {
    int zPos = 0;
    int xPos = 0;

    // Z位置を+1しながらマップを生成していく
    for(; zPos<15; zPos++) {
        // Z位置が更新されたら、まず1つマップチップを置く
        GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
        cube.transform.position = new Vector3(xPos, 0, zPos);

        // 現在のマップ生成のX位置から移動できるX方向の範囲を決定
        int randomAddTips_Min = -3 - xPos;
        int randomAddTips_Max = 3 - xPos;

        // 乱数を用意して、その値分X方向に移動する。0が出たら曲がらず直進する
        int randomAddTips = Random.Range(randomAddTips_Min, randomAddTips_Max + 1);
        // 乱数が0以上なら右に曲がり、乱数分進む
        if(randomAddTips > 0) {
            for(; randomAddTips>0; randomAddTips--) {
                xPos++;
                cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
                cube.transform.position = new Vector3(xPos, 0, zPos);
            }
        } 
        // 乱数が0以下なら左に曲がり、乱数分進む
        if(randomAddTips < 0) {
            for(; randomAddTips<0; randomAddTips++) {
                xPos--;
                cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
                cube.transform.position = new Vector3(xPos, 0, zPos);
            }
        }
    }
}

できたのがこちら。(Z方向の生成数を15マスに変えてます)

f:id:MegumiSoft:20160619155530g:plain:w400

マップがX方向に偏らずに生成できているのが確認できました。


とりあえず、Troccoで使用したマップの自動生成アルゴリズムの基礎はこんな感じです。

この後は障害物やアイテムといったオブジェクトをランダムで生成して、ゲーム要素を増やしていく感じになります。

Troccoでは、さらにX方向にマップチップを置いて肉付けしてますが、このまま一本道として、マップチップ同士がくっつかないように少し手を加えて使っても面白いですね。



さて、今回の【基礎編】は以上で終わりです。

次回【実践編】では、このアルゴリズムを使用した、簡単なゲームの作り方を記事にしようと思います。

それではばいちゃ〜 シュッ =͟͟͞͞ (¦3[▓▓]


今回紹介した、AndroidiOS向けのゲーム「Trocco」です。
launchkit.io