Skip to content

ナビメッシュ

2D マップの作成と NavMesh

# include <Siv3D.hpp>

// Vec2 から NavMesh 用の Vec3 へ変換
inline constexpr Vec3 ToVec3(const Vec2& xy)
{
    return Vec3(xy.x, 0.0, xy.y);
}

void Main()
{
    // 画面サイズを変更
    Window::Resize(1280, 720);

    // マップの領域
    constexpr Rect mapArea(0, 0, 1000, 720);

    // ナビメッシュ
    NavMesh navMesh;

    // マップの形状を表現する Polygon
    Polygon polygon;

    // 経路
    LineString path;

    // 現在地
    Vec2 pos(100, 100);

    // 目的地
    Vec2 destination(300, 300);

    // 更新の必要の有無
    bool dirty = true;

    // 入力モード 0: マップ, 1: 現在地, 2: 目的地
    size_t inputMode = 0;

    while (System::Update())
    {
        // マップエリアを表示
        mapArea.draw(ColorF(0.2, 0.4, 0.3));

        // マップの形状を表示(更新が必要な時はグレー)
        polygon.draw(dirty ? Palette::Gray : Palette::Yellow);
        polygon.drawWireframe(1.0, Palette::Gray);

        // マップエリア上での操作
        if (mapArea.mouseOver())
        {
            if (inputMode == 0)
            {
                const Circle shape(Arg::center(Cursor::Pos()), 30);
                shape.drawFrame(2, Palette::Skyblue);

                if (MouseL.pressed())
                {
                    // マップの Polygon に円を追加
                    dirty |= polygon.append(shape.asPolygon());
                }

                // カーソルは非表示
                Cursor::RequestStyle(CursorStyle::Hidden);
            }
            else if (inputMode == 1)
            {
                if (MouseL.pressed())
                {
                    // 現在地を変更
                    pos = Cursor::Pos();
                    path = navMesh.query(ToVec3(pos), ToVec3(destination)).map([](auto v) { return Vec2(v.x, v.z); });
                }

                // カーソルは手のマーク
                Cursor::RequestStyle(CursorStyle::Hand);
            }
            else if (inputMode == 2)
            {
                if (MouseL.pressed())
                {
                    // 目的地を変更
                    destination = Cursor::Pos();
                    path = navMesh.query(ToVec3(pos), ToVec3(destination)).map([](auto v) { return Vec2(v.x, v.z); });
                }

                // カーソルは手のマーク
                Cursor::RequestStyle(CursorStyle::Hand);
            }
        }

        // 入力モードを変更するラジオボタン
        SimpleGUI::RadioButtons(inputMode, { U"map", U"position", U"destination" }, Vec2(1050, 40), 180);

        if (SimpleGUI::Button(U"Build Path", Vec2(1050, 180), 180))
        {
            // 計算コスト削減のため Polygon を単純化
            polygon = polygon.simplified(2.0);

            // NavMesh を更新
            const Array<Float3> vertices = polygon.vertices().map([](auto v) { return Float3(v.x, 0.0, v.y); });
            const Array<uint16> indices = polygon.indices().map([](auto i) { return static_cast<uint16>(i); });
            if (navMesh.build(vertices, indices))
            {
                // 経路を計算
                path = navMesh.query(ToVec3(pos), ToVec3(destination)).map([](auto v) { return Vec2(v.x, v.z); });
                dirty = false;
            }
        }

        // 経路を表示
        path.draw(10, Palette::Red);

        // 現在地を表示
        RectF(Arg::center(pos), 30)
            .rotated(Scene::Time() * 60_deg)
            .draw((inputMode == 1) ? Palette::Red.lerp(Color(255), Periodic::Sine0_1(1s)) : Palette::Red);

        // 目的地を表示
        Circle(destination, 15)
            .draw((inputMode == 2) ? Palette::Red.lerp(Color(255), Periodic::Sine0_1(1s)) : Palette::Red);
    }
}