LoginSignup
5
6

More than 5 years have passed since last update.

【C++】中点変位法の世界自動生成アルゴリズム

Last updated at Posted at 2018-09-09

世界を創造しませんか?

今回は初代ドラクエやFFなどに出てきそうなワールドマップを簡単に作っていきます。

条件

マップの大きさは縦横ともに16の倍数 (例: 256 * 128)
マップの縦横はループする。(上下、左右に接続する)
今回は標高ごとに"海(青色)""浅瀬(水色)""陸(緑色)""高山(灰色)"に分けて出力します。

ソースコード

WorldMap.hpp
#pragma once
#include <vector>
#include <iostream>
#include <random>

namespace world {

    using vsize_t = std::vector<size_t>;
    using vvsize_t = std::vector<vsize_t>;

    //乱数
    class Rand32 {
    private:
        std::mt19937 mt;
    public:
        void srand(const unsigned int seed_) { mt.seed(seed_); }
        void init() {
            std::random_device rd;
            mt.seed(rd());
        }
        unsigned int operator()(const int max_) {
            std::uniform_int_distribution<> uid(0, max_ - 1);
            return uid(mt);
        }
    };
    static thread_local Rand32 rand32;
    //チャンク生成
    void worldMapMake(const size_t x_, const size_t y_, const size_t size_, const size_t t1_, const size_t t2_, const size_t t3_, const size_t t4_, size_t map_[17][17])
    {
        //再起の終了処理
        if (size_ == 0) return;
        //頂点の高さを決める
        const size_t mapPlus = ((t1_ + t2_ + t3_ + t4_) >> 2) + (const size_t)rand32((const int)size_);
        map_[x_][y_] = (mapPlus >= 255) ? 255 : mapPlus;
        //四角形の2点同士の中点の高さを決定
        const size_t s1 = ((t1_ + t2_) >> 1);
        const size_t s2 = ((t1_ + t3_) >> 1);
        const size_t s3 = ((t2_ + t4_) >> 1);
        const size_t s4 = ((t3_ + t4_) >> 1);
        //4つの地点の座標を決める
        map_[x_ + size_][y_] = s3;
        map_[x_ - size_][y_] = s2;
        map_[x_][y_ + size_] = s4;
        map_[x_][y_ - size_] = s1;
        //分割サイズを半分にする
        const size_t size = size_ >> 1;
        //4つに分割
        worldMapMake(x_ - size, y_ - size, size, t1_, s1, s2, map_[x_][y_], map_);
        worldMapMake(x_ + size, y_ - size, size, s1, t2_, map_[x_][y_], s3, map_);
        worldMapMake(x_ - size, y_ + size, size, s2, map_[x_][y_], t3_, s4, map_);
        worldMapMake(x_ + size, y_ + size, size, map_[x_][y_], s3, s4, t4_, map_);
    }

    //チャンク生成の呼び出し・実行
    void worldMapSimple(size_t map_[17][17])
    {
        worldMapMake(8, 8, 8, map_[0][0], map_[16][0], map_[0][16], map_[16][16], map_);
    }

    constexpr size_t aslib_world_make_seed1 = 0x3220;
    constexpr size_t aslib_world_make_seed2 = 0x292;

    //ワールドマップ生成
    void worldMake(vvsize_t& world_map, size_t seed_ = 0)
    {
        if (seed_ == 0) seed_ = size_t(rand32(65535));
        size_t map_[17][17]{};
        //横画面サイズ
        const size_t map_x = world_map.size();
        //縦画面サイズ
        const size_t map_y = world_map.front().size();

        const size_t chunk_x = (map_x >> 4);
        const size_t chunk_y = (map_y >> 4);

        for (size_t i = 0; i < chunk_x; ++i) {
            for (size_t j = 0; j < chunk_y; ++j) {
                //四角形の4点の高さを決定
                rand32.srand((unsigned int)(seed_ + i + (j * aslib_world_make_seed1) + ((i^j) * aslib_world_make_seed2)));
                map_[0][0] = size_t(rand32(255));
                rand32.srand((unsigned int)(seed_ + ((i + 1) % chunk_x) + (j * aslib_world_make_seed1) + ((((i + 1) % chunk_x) ^ j) * aslib_world_make_seed2)));
                map_[16][0] = size_t(rand32(255));
                rand32.srand((unsigned int)(seed_ + i + (((j + 1) % chunk_y) * aslib_world_make_seed1) + ((i ^ ((j + 1) % chunk_y)) * aslib_world_make_seed2)));
                map_[0][16] = size_t(rand32(255));
                rand32.srand((unsigned int)(seed_ + ((i + 1) % chunk_x) + (((j + 1) % chunk_y) * aslib_world_make_seed1) + ((((i + 1) % chunk_x) ^ ((j + 1) % chunk_y)) * aslib_world_make_seed2)));
                map_[16][16] = size_t(rand32(255));
                //チャンク生成
                worldMapSimple(map_);
                //生成したチャンクをワールドマップにコピペ
                for (size_t i2 = 0; i2 < 16; ++i2)
                    for (size_t j2 = 0; j2 < 16; ++j2)
                        world_map[(i << 4) + i2][(j << 4) + j2] = map_[i2][j2];
            }
        }
    }
}

サンプルコード(文字で表示)

Source.cpp
#include "WorldMap.hpp"

constexpr size_t size_x = 304;
constexpr size_t size_y = 160;

int main() {
    //乱数初期化
    world::rand32.init();
    //世界生成
    world::vvsize_t wmap(size_y, world::vsize_t(size_x, 255));
    world::worldMake(wmap);

    //表示
    for (size_t i = 0; i < size_y; ++i) {
        for (size_t j = 0; j < size_x; ++j) {

            if (wmap[i][j] <= 30) std::cout << "▲";
            else if (wmap[i][j] <= 110) std::cout << "■";
            else if (wmap[i][j] <= 120) std::cout << "□";
            else std::cout << "△";
        }
        std::cout <<  std::endl;
    }
    return 0;
}

出力結果

スクリーンショット 2018-09-06 22.45.21.png

しっかりと生成できていますね!

サンプルコード(色付きで表示)

Source.cpp
#include "WorldMap.hpp"

constexpr size_t size_x = 304;
constexpr size_t size_y = 160;

int main() {
    //乱数初期化
    world::rand32.init();
    //世界生成
    world::vvsize_t wmap(size_y, world::vsize_t(size_x, 255));
    world::worldMake(wmap);

    //表示
    for (size_t i = 0; i < size_y; ++i) {
        for (size_t j = 0; j < size_x; ++j) {

            if (wmap[i][j] <= 30) std::cout << "\x1b[47m" << " ";
            else if (wmap[i][j] <= 110) std::cout << "\x1b[42m" << " ";
            else if (wmap[i][j] <= 120) std::cout << "\x1b[46m" << " ";
            else std::cout << "\x1b[44m" << " ";
        }
        std::cout << "\x1b[0m" << " " << std::endl;
    }
    return 0;
}

出力結果

スクリーンショット 2018-09-06 21.14.36.png

ちゃんと画像はループしています。
スクリーンショット 2018-09-06 21.14.36_2.png

ゲーム制作に使ってみる

スクリーンショット 2018-08-13 06.34.31.png
スクリーンショット 2018-08-13 14.32.00.png

ソースコードのライセンス

These codes are licensed under CC0.
CC0

5
6
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
5
6