Edited at
Siv3DDay 3

Siv3D での 2D カメラ操作をサポートする Camera2D

More than 1 year has passed since last update.

Siv3D AdventCalendar 2016 の 3 日目の記事です.


概要

Siv3D に付属されている HamFramework は,2D カメラ操作を簡単に実装できるクラス ham::Camera2D を提供しています.以下のコードでマウス操作や W/A/S/D/上下キー入力による 2D カメラ移動が可能になります.


Main.cpp

# include <Siv3D.hpp>

# include <HamFramework.hpp>

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

Camera2D camera;

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

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

Circle(Mouse::PosF(), 50).draw({ 255, 0, 0, 127 });
}
camera.draw(Palette::Orange);
}
}



このクラスを作った理由

マウス操作でのカメラ移動が可能な状況で四角形を描画すると以下のようになります.


Main.cpp

# include <Siv3D.hpp>

# include <HamFramework.hpp>

void Main()
{
Vec2 cameraPos(0.0, 0.0);

const Vec2 rectPos = Window::Size() / 2.0;

const Vec2 rectSize(100.0, 100.0);

Vec2 mouseClickStartPos;

while (System::Update())
{
if (Input::MouseR.clicked)
{
mouseClickStartPos = Mouse::Pos();
}

if (Input::MouseR.pressed)
{
cameraPos += (Mouse::Pos() - mouseClickStartPos) * 0.1;

Circle(mouseClickStartPos, 100.0).drawFrame(2.0, 2.0);
Circle(Mouse::Pos(), 30.0).draw();
}

RectF(rectSize).setCenter(rectPos - cameraPos).draw();
}
}


このプログラムでは


  1. カメラの位置 cameraPos を用意しその座標をマウスの操作により動かす

  2. 四角形の描画時に位置 rectPos からカメラの位置を引く

ことにより,マウス操作によるカメラ移動を実現しています.ここで,カメラ移動だけでなく拡大縮小やキーボード入力への対応などの機能を追加したい場合にはどうすればよいでしょうか.描画対象すべての位置や拡大率を個別に操作することが必要になりそうでが,可能であれば描画とカメラ操作は分離して実装したいと思いました.他にも同じ思いをしている人がいるのではないかと考え,Siv3D 上での2Dカメラ移動を簡単に実装できる Camera2D を作成しました.


2Dカメラ操作をサポートする Camera2D

Siv3D には描画とマウスの座標に任意の行列を使って移動やスケーリングを一律に適用できる Graphics2D::PushTransform()Graphics2D::PopTransform() があります.Graphics2D::PushTransform() には移動やスケーリングを行列形式で表現する Mat3x2 型の値を渡します.Graphics2D::PopTransform() が呼ばれると,それまで呼ばれた行列を最新の順から解除していきます.さらに,スコープから外れた際に Push した行列が自動的に Pop してくれる,Transformer2D クラスが Siv3D に存在します.HamFramework の Camera2D はこの Transformer2D を作成する関数 createTransformer() を使うことにより,特定のスコープ内の描画オブジェクトの移動・スケーリングを行います.


基本的な使い方


Main.cpp

# include <Siv3D.hpp>

# include <HamFramework.hpp>

void Main()
{
Camera2D camera;

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

Circle(100, 100, 100).draw(Palette::Red);
Rect(150, 100, 200, 200).draw(Palette::Orange);
Line(50, 400, 600, 50).draw(3, Color(128, 255, 255));
Circle(400, 400, 200).draw(Color(0, 0, 255, 127));
}
camera.draw(Palette::Orange);
}
}




  1. HamFramework.hpp をインクルード


  2. Camera2D クラスのインスタンスを作成

  3. メインループの中で Camera2D::update() を呼ぶ


  4. { } でスコープを作り、その中で Camera2D::createTransformer() を使って Transformer2D を作成

  5. カメラ操作する対象であるオブジェクトを描画

  6. スコープ外で Camera2D オブジェクトの draw() を呼ぶ(任意)

これにより


  • マウスの右クリック・ドラッグまたは W/A/S/D キーでカメラの移動

  • マウスのホイールまたは上下キーでカメラのズーム操作

ができるようになります.


マウス座標

マウス座標の取扱には注意が必要です.


Main.cpp

# include <Siv3D.hpp>

# include <HamFramework.hpp>

void Main()
{
Camera2D camera;

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

Circle(Mouse::Pos(), 2.0).drawFrame(0.1, 0.1);
Circle(Mouse::PosF(), 1.0).draw();
}
camera.draw(Palette::Orange);
}
}


マウス座標の取得方法には二種類あります.Point 型でマウス座標を取得する Mouse::Pos()Vec2 型でマウス座標を取得する Mouse::PosF() です.Mouse::Pos() ではズーム時に実際のマウス位置からは離れた場所を指し示すことがあるため,Camera2D とうまく連携させるには Mouse::PosF() を利用してください.


カメラ操作の調整

キーボードまたはマウスのどちらかのみでカメラを操作したい場合や,カメラの移動速度などをカスタマイズしたいときには,Camera2D::update() の引数を変更します.引数は以下の通りです.



  1. speed: カメラの速度


  2. scaleRatio: ズーム変更率 [0.0~1.0]


  3. mouseSpeedRatio: マウス操作の影響率 [0.0~1.0]


  4. lerpRatio: 補間率 [0.0~1.0](ぬるっと感)


  5. useKeyControl: キーボード入力を受け付けるかどうか


  6. useMouseControl: マウス入力を受け付けるかどうか

例えば,キーボード入力を受け付けずマウス入力のみを受け付けたい場合には,useKeyControlfalse にし,useMouseControltrue にします.(デフォルトではどちらも true です.)


カメラ位置・ズームの変更


Main.cpp

# include <Siv3D.hpp>

# include <HamFramework.hpp>

void Main()
{
Camera2D camera;

Vec2 playerPos = Window::Center();

const Rect rect(20, 20, 200, 100);

const Circle circle(150, 300, 100);

const Polygon star = Geometry2D::CreateStar(200, 0.0, Vec2(400, 250));

while (System::Update())
{
camera.update(10.0, 1.1, 0.01, 0.2, false, true);
{
const auto t1 = camera.createTransformer();

playerPos += Vec2(Input::KeyRight.pressed - Input::KeyLeft.pressed, Input::KeyDown.pressed - Input::KeyUp.pressed) * 10.0;

camera.setTargetPos(playerPos);

const Circle player(playerPos, 30);

// プレイヤーと重なっていたら赤、そうでなかったら黄色で図形を描く
rect.draw(player.intersects(rect) ? Palette::Red : Palette::Yellow);
circle.draw(player.intersects(circle) ? Palette::Red : Palette::Yellow);
star.draw(player.intersects(star) ? Palette::Red : Palette::Yellow);

player.draw();
}
}
}


ここでは,Camera2D のカメラ位置をプレイヤーと同じ位置に設定しています.

Camera2DsetTargetPos() 次回の Siv3D アップデートで追加される関数です.それ以前のバージョンでは使用できないので,以下のコードを HamFramework/CameraManager.hpp の 227 行目に入れてください.


CameraManager2D.hpp

        void setTargetPos(const Vec2& targetPos)

{
m_targetPos = targetPos;
}

void setTargetScale(const double targetScale)
{
m_targetScale = targetScale;
}



キーボード入力時におけるキー割り当て

Camera2D のキーボード入力のキー割り当てはデフォルトでは移動が W/A/S/D, ズーム操作が上下キーとなっています.変更をしたい場合には,setKeyConfig() を利用してください.たとえば,移動に上下左右キー,ズーム操作に Z・X キーを割り当てるには


Main.cpp

# include <Siv3D.hpp>

# include <HamFramework.hpp>

void Main()
{
const std::array<s3d::Key, 6> keyConfig =
{
s3d::Input::KeyUp, s3d::Input::KeyLeft, s3d::Input::KeyDown, s3d::Input::KeyRight,
s3d::Input::KeyZ, s3d::Input::KeyX
};

Camera2D camera;

camera.setKeyConfig(keyConfig);

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

Circle(100, 100, 100).draw(Palette::Red);
Rect(150, 100, 200, 200).draw(Palette::Orange);
Line(50, 400, 600, 50).draw(3, Color(128, 255, 255));
Circle(400, 400, 200).draw(Color(0, 0, 255, 127));
}
camera.draw(Palette::Orange);
}
}


のように,std::array<s3d::Key, 6> で作成されるキー割り当てを Camera2D::setKeyConfig() の引数に入れてください.デフォルトでのキー割り当ては以下のようになっています.

std::array<s3d::Key, 6> m_keyConfig =

{
s3d::Input::KeyW, s3d::Input::KeyA, s3d::Input::KeyS, s3d::Input::KeyD,
s3d::Input::KeyUp, s3d::Input::KeyDown
};


ScalableWindow との併用

Camera2D と同様,Transformer2D を利用することでウィンドウの大きさを自由に変更可能な機能 ham::ScalableWindow を HamFramework で提供しています.Camera2D と ScalableWindow を同時に使いたい場合には以下のように書きます


Main.cpp

# include <Siv3D.hpp>

# include <HamFramework.hpp>

void Main()
{
Camera2D camera;

ScalableWindow::Setup();

while (System::Update())
{
camera.update();
{
const auto t1 = camera.createTransformer();
const auto t2 = ScalableWindow::CreateTransformer();

Circle(100, 100, 100).draw(Palette::Red);
Rect(150, 100, 200, 200).draw(Palette::Orange);
Line(50, 400, 600, 50).draw(3, Color(128, 255, 255));
Circle(400, 400, 200).draw(Color(0, 0, 255, 127));
}
camera.draw(Palette::Orange);
}
}




  1. ScalableWindow::Setup() を呼ぶ


  2. Camera2D::createTransformer() の後 ScalableWindow::CreateTransformer()Transformer2D オブジェクト生成


カメラ操作をもっとカスタマイズしたい

Camera2D の操作をもっとカスタマイズしたい場合は,BasicCamera2D もしくは Camera2D を継承した独自のカメラクラスを作成してください.例えば,Y軸の正の方向を上向きに調整したカメラクラス CameraBox2DCamera2D の一部の関数をオーバーライドすることで作成しました.


まとめ

Siv3D Advent Calendar 2016 3 日目は Camera2D の使い方について紹介しました.

明日は @agehama_ さんの記事です.よろしくお願いいたします.