Siv3D
Siv3DDay 7

Siv3Dでゲームでよくあるタイプの2Dカメラを使う

More than 1 year has passed since last update.

Siv3D Advent Calendar 2016 7 日目の記事です。

Siv3Dでゲームでよくあるタイプの2Dカメラを使う

Siv3Dには視点移動機能がついていて、こんな感じのコードで動かすことが出来ます。

# include <Siv3D.hpp>
# include <HamFramework.hpp>

//右クリックしてカーソルを移動させたり、マウスホイールを回すと視点移動できます。
void Main()
{
    const Font font(30);

    Camera2D camera;

    while (System::Update())
    {
        camera.update();
        {
            auto t = camera.createTransformer();

            font(L"ようこそ、Siv3D の世界へ!").draw();

            Circle(200, 160, 20).draw(Palette::Red);
            Circle(50, 120, 50).draw(Palette::Skyblue);
        }
        camera.draw(Palette::Orange);
    }
}

こちらの記事に詳しい解説が載っています。

この機能はもう完成されたゲームにも導入できるので、非常に便利なのですが、

ゲームでよく使うマウス右ボタンが占有されてしまうため、ちょっと使いづらい側面もあります。

そこで、今回はよりゲームに導入しやすいようなカメラを作ってみたのでその紹介をします。

# include <Siv3D.hpp>

struct TinyCamera
{
    RectF   targetDrawingRegion;        //目標の描画範囲
    RectF   smoothDrawingRegion;        //実際の描画範囲
    double  followingSpeed = 0.2;       //遅延。0.0~1.0

    TinyCamera(RectF _drawingRegion);   //指定した範囲
    TinyCamera();                       //WindowSizeに指定。グローバル変数にしてはいけない。

    void    update();                           //毎フレーム呼ぶ。視点移動を止める場合はこれを使う
    Transformer2D   createTransformer() const;  //視点移動
};

TinyCamera::TinyCamera(RectF _drawingRegion)
    : targetDrawingRegion(_drawingRegion)
    , smoothDrawingRegion(targetDrawingRegion)
{}
TinyCamera::TinyCamera()
    : targetDrawingRegion(Window::ClientRect())
    , smoothDrawingRegion(targetDrawingRegion)
{}

void    TinyCamera::update()
{
    //AspectRatioに基づく修正。WindowSizeが変わると対応できなくなるため。
    targetDrawingRegion.w = targetDrawingRegion.h * Window::AspectRatio();
    smoothDrawingRegion.w = smoothDrawingRegion.h * Window::AspectRatio();

    //ズームイン、アウト処理。createTransformer下で動かすと崩壊するので注意。
    {
        auto tc = createTransformer();
        const double delta = 0.1*Mouse::Wheel();    //ズームによる変動
        const auto pos = targetDrawingRegion.pos + delta*(targetDrawingRegion.pos - Mouse::PosF());
        const auto size = (1.0 + delta)*targetDrawingRegion.size;
        targetDrawingRegion.set(pos, size);
    }

    //描画領域の調節
    smoothDrawingRegion.pos = smoothDrawingRegion.pos*(1.0 - followingSpeed) + targetDrawingRegion.pos*followingSpeed;
    smoothDrawingRegion.size = smoothDrawingRegion.size*(1.0 - followingSpeed) + targetDrawingRegion.size*followingSpeed;

    //視点移動処理。フルスクリーンのゲームじゃないならキーボードによる操作にした方がいいかも
    if (Mouse::Pos().x <= 0) targetDrawingRegion.pos.x -= targetDrawingRegion.size.y*0.01;
    if (Mouse::Pos().y <= 0) targetDrawingRegion.pos.y -= targetDrawingRegion.size.y*0.01;
    if (Mouse::Pos().x >= Window::Size().x - 1) targetDrawingRegion.pos.x += targetDrawingRegion.size.y*0.01;
    if (Mouse::Pos().y >= Window::Size().y - 1) targetDrawingRegion.pos.y += targetDrawingRegion.size.y*0.01;
}

Transformer2D   TinyCamera::createTransformer() const
{
    auto mat3x2 = Mat3x2::Translate(-smoothDrawingRegion.center).scale(Window::Size().y / smoothDrawingRegion.size.y).translate(Window::ClientRect().center);
    return Transformer2D(mat3x2, true);
}

//マウスホイールを回すとマウスカーソルを中心に拡大縮小します
void Main()
{
    const Font font(30);

    TinyCamera tinyCamera(Window::ClientRect());

    while (System::Update())
    {
        tinyCamera.update();
        {
            auto t = tinyCamera.createTransformer();

            font(L"ようこそ、Siv3D の世界へ!").draw();

            Circle(200, 160, 20).draw(Palette::Red);
            Circle(50, 120, 50).draw(Palette::Skyblue);
        }
    }
}

これがそのソースコードです。

Camera2Dと同様に使えます。

画面中心を拡大縮小するのではなく、マウスカーソルを中心に拡大縮小するカメラです。

こうすることで詳しく見たいところにカーソルを置いてホイールを回すだけで拡大できます。

あと、このままだとソースコードが長くなるだけなので、TinyCamera.hとTinyCamera.cppファイルに分割しておくと便利です。

ここのCamera.hとCamera.cppみたいな感じで

ファイル分割するとこんな感じで使えます。

# include <Siv3D.hpp>
# include "TinyCamera.h"

void Main()
{
    const Font font(30);

    TinyCamera tinyCamera;

    while (System::Update())
    {
        tinyCamera.update();
        {
            auto t = tinyCamera.createTransformer();

            font(L"ようこそ、Siv3D の世界へ!").draw();

            Circle(200, 160, 20).draw(Palette::Red);
            Circle(50, 120, 50).draw(Palette::Skyblue);
        }
    }
}

これは便利ではないでしょうか。
マウスホイール以外のInputを占有しないので、

既存の機能と競合することはほとんどないでしょう。

あと、なんとなく察している人もいると思いますが、色々と機能を削って見やすくしているので、

描画範囲制限とかしたいならここのCamera.hとCamera.cppをコピーして使った方がよいと思います。

使用例

この視点移動システムを使って開発しているゲームを載せておきます。

生態系シミュレータ

動画

ソースコード

頻繁に視点移動するタイプのシミュレーションゲームに適していることがよくわかると思います。

工場を作るゲーム

動画

ソースコード

細かい作業をするときには直観的な操作と正確な視点移動が役に立ちます。

最後に

是非皆さんも導入してくれると嬉しいです。

明日は @Milk_Spoon さんの記事です。
よろしくお願いします。