⚓最短航路の作成⚓
今回は自動生成したワールドデータに航路を自動生成していきます。
💻A*による経路探索💻
探索アルゴリズムである**"A*アルゴリズム"**を使用して最短経路を求めます。
下図のようにコストが低い場所から優先して探索していきます。
⚙Vec2クラスとNodeクラスの作成⚙
まずは基本クラスである**"Vec2クラス"と"Nodeクラス"**の作成です。
struct AStarVec2 {
//X座標値を格納する
std::int_fast32_t x{};
//Y座標値を格納する
std::int_fast32_t y{};
constexpr AStarVec2() noexcept = default;
constexpr AStarVec2(const std::int_fast32_t x_, const std::int_fast32_t y_) noexcept :x(x_), y(y_) {}
constexpr bool operator==(const AStarVec2& vec2_) const noexcept {
return vec2_.x == x && vec2_.y == y;
}
};
struct AStarNode {
//位置
AStarVec2 position{};
//親ノード
AStarVec2 parent_node{};
//距離
std::int_fast32_t distance{};
//コスト
std::int_fast32_t cost{};
constexpr AStarNode() noexcept = default;
constexpr AStarNode(const AStarVec2& pos_, const AStarVec2& parent_node_, const std::int_fast32_t distance_, const std::int_fast32_t cost_) noexcept
:position(pos_), parent_node(parent_node_), distance(distance_), cost(cost_) {}
constexpr bool operator==(const AStarNode& node_) const noexcept {
return position == node_.position;
}
constexpr bool operator==(const AStarVec2& vec2_) const noexcept {
return position == vec2_;
}
};
⭐️A*クラスの実装⭐️
次は肝心のA*クラスの作成です。
class AStar {
private:
//始点
AStarVec2 start_vec2{};
//終点
AStarVec2 end_vec2{};
std::array <AStarVec2, 8> adjacent_cell{ {AStarVec2(-1, -1),AStarVec2(1, -1),AStarVec2(-1, 1),AStarVec2(1, 1),AStarVec2(0, -1),AStarVec2(-1, 0),AStarVec2(0, 1),AStarVec2(1, 0)} };
std::list<AStarNode> open{};
std::list<AStarNode> closed{};
public:
constexpr AStar() noexcept = default;
constexpr AStar(const AStarVec2& start_vec2_, const AStarVec2& end_vec2_) noexcept
:start_vec2(start_vec2_), end_vec2(end_vec2_) {}
};
⚙ヒューリスティック関数⚙
コストを算出する関数を作成します。
constexpr std::int_fast32_t calculateDistance(const AStarVec2 & vec2_) const noexcept {
const std::int_fast32_t x{ end_vec2.x - vec2_.x };
const std::int_fast32_t y{ end_vec2.y - vec2_.y };
return (x >= y) ? x : y;
}
⚙範囲指定⚙
constexpr bool isRange(const AStarVec2 & vec2_, const std::int_fast32_t x_, const std::int_fast32_t y_) const noexcept {
return vec2_.x >= 0 && vec2_.y >= 0 && vec2_.x < x_ && vec2_.y < y_;
}
⚙座標判定⚙
bool existPoint(const AStarVec2 & vec2_, const std::int_fast32_t cost_) noexcept {
auto i{ std::find(closed.begin(), closed.end(), vec2_) };
if (i != closed.end()) {
if ((*i).cost + (*i).distance < cost_) return true;
else {
closed.erase(i);
return false;
}
}
i = std::find(open.begin(), open.end(), vec2_);
if (i != open.end()) {
if ((*i).cost + (*i).distance < cost_) return true;
else {
open.erase(i);
return false;
}
}
return false;
}
⚙周囲のセルを開く⚙
template<typename Matrix_>
bool cellOpen(const AStarNode & node_, const Matrix_ & matrix_, const std::int_fast32_t x_, const std::int_fast32_t y_) noexcept {
std::int_fast32_t node_cost;
std::int_fast32_t distance;
AStarVec2 neighbour;
for (std::int_fast32_t x{}; x < 8; ++x) {
neighbour.x = node_.position.x + adjacent_cell[x].x;
neighbour.y = node_.position.y + adjacent_cell[x].y;
if (neighbour == end_vec2) return true;
if (!isRange(neighbour, x_, y_) || matrix_[neighbour.y][neighbour.x]) continue;
//コスト計算
node_cost = 1 + node_.cost;
distance = calculateDistance(neighbour);
if (existPoint(neighbour, node_cost + distance)) continue;
open.emplace_back(AStarNode(neighbour, node_.position, distance, node_cost));
}
return false;
}
⚙探索⚙
template<typename Matrix_>
bool search(const Matrix_ & matrix_, const std::int_fast32_t x_, const std::int_fast32_t y_) noexcept {
open.emplace_back(AStarNode(start_vec2, {}, calculateDistance(start_vec2), 0));
while (!open.empty()) {
const AStarNode nn{ open.front() };
open.pop_front();
closed.emplace_back(nn);
if (cellOpen(nn, matrix_, x_, y_)) return true;
}
return false;
}
⚙探索結果の保存⚙
template<typename Path_>
std::int_fast32_t setPath(Path_ & path_) noexcept {
path_.emplace_front(end_vec2);
std::int_fast32_t cost{ 1 + closed.back().cost };
path_.emplace_front(closed.back().position);
AStarVec2 parent_node{ closed.back().parent_node };
for (auto&& i{ closed.rbegin() }; i != closed.rend(); ++i) {
if (!((*i).position == parent_node) || ((*i).position == start_vec2)) continue;
path_.emplace_front((*i).position);
parent_node = (*i).parent_node;
}
path_.emplace_front(start_vec2);
return cost;
}
⚓完成⚓
航路の自動生成が出来ました。
今回使用したワールドマップの自動生成アルゴリズムは、"セルオートマトン法"です。
(>> セルオートマトン法について解説した記事はこちら)
🏯航路の始点と終点の生成🏯
最初に設置する航路の始点と終点の生成は"イベント生成"を使用しました。
(>> イベント生成について解説した記事はこちら)
実装ライブラリ(ソースコード)
今回解説した自動生成は**"Dungeon Template Library"**を使用して実装されています。
GitHubに全ソースコードを載せています。ご参考になれば幸いです。
ぜひ、活用してみてください!
ソースコードのライセンス
These codes are licensed under CC0.
この記事のソースコードはCC0ライセンスとします。
ぜひ、自由に改変して遊んでみてください。
"いいね"をつけてくださると大変励みになります。
最後までお読みいただきありがとうございました!