TOP > プログラミング関連 > SFML非公式翻訳トップ > 【非公式翻訳】SFML2.2チュートリアル > グラフィックスモジュール > 2Dカメラで視点を操作

[原文(公式)]

◆◆ 2Dカメラで視点を操作 ◆◆
非公式日本語翻訳
視界(ビュー : view)
ゲームの世界は大きい。ウィンドウよりも大きい。
プレイヤーの目に映っているのは世界のほんの一部です。 RPG でもアクションゲームでも、なんでもそうですね。

プログラマの感覚としては、オブジェクトをウィンドウに直接乗せてる気分になるかもですが、 実際はそうではなくて、オブジェクトを「ゲーム内の2D世界」に配置してます。

ウィンドウは一種の「視界(ビュー : view)」です。
ゲーム世界全体の特定の一部分を切り取って表示してます。
ということは……同じ世界を複数の「視界」で同時に描画することもできる、ということです。 さらに、ウィンドウではなくテクスチャに描画することもできたりします。
そして、そういうふうにしたとしても、ゲーム内の世界が変化するわけではないのですね。変化するのは「見え方」です。

ウィンドウが一度に表示できるのは世界全体の小さな一部分だけなので、 どのエリアを表示するのかはプログラマが指定してあげる必要があります。
さらに、制作者としては、 そのエリアがウィンドウの中の「どこに」「どんなふうに」表示されるのかを操作したくなるかもしれませんね? ね?
まさにそれが、SFML のビュー機能が誇る2つのメイン機能なのです!

さて、ビューとは何か?
簡単に言うと、画面を スクロールさせたり 回転させたり ズームしたりするときに使うもの、です。 応用すると、画面を分割したり、ミニマップを作ったりもできます。
設定:ビューが何を表示するか
ビューを扱う SFML のクラスは sf::View です。
コンストラクタに、2D世界から切り取ってビューに表示するエリアの設定を直接指定できます。
// 四角形を設定。2D世界の中の、このエリアが表示される。
sf::View view1(sf::FloatRect(200, 200, 300, 200));

// 中心座標とサイズで作成
sf::View view2(sf::Vector2f(350, 300), sf::Vector2f(300, 200));
上の2つの結果は同じです。 どちらも2D世界の中の座標(350, 300)を中心に、サイズ(300x200)のエリアが表示されます。

※訳註
ちなみに、画像全体はこうなってます
作成には SPIRAL WIND さまの素材を使わせていただきました。
コンストラクタに設定を書き込みたくないとか、後で設定を変更したくなるかもしれませんね。 そのためのセッターもあります。
sf::View view1;
view1.reset(sf::FloatRect(200, 200, 300, 200));

sf::View view2;
view2.setCenter(sf::Vector2f(350, 300));
view2.setSize(sf::Vector2f(200, 200));
ビューを設定したら、トランスフォームが可能になります。 つまり移動・回転・拡大です。
ビューを移動(スクロール)
スプライトなどのSFML の他の2Dオブジェクトでは位置などの基準はオブジェクトの左上の角です。変更することもできます。 が、ビューはその点が、常に真ん中になっています(この方が便利なのです)。
なので、位置を変更する関数名は setCenter() です。setPosition() ではなく。
// 視点を 2D世界内の (200, 200) にセット
view.setCenter(200, 200);

// 視点を 動かす。移動量は(100, 100)。つまり、座標(300, 300) になる。
view.move(100, 100);

視界を回転
回転には setRotation()関数を使います。
// 視界の角度を 20度 に設定
view.setRotation(20);

// 5度回転させる。つまり、向きは 25度になる。
view.rotate(5);

ズーム
ビューをズームイン/アウトするということは、つまりサイズを変更するということです。 なので関数名は setSize() 。
// 2D世界内の サイズ(2400x1600)のエリアを表示
//(特大です。つまりズームアウトになります)。
view.setSize(2400x1600);

// 倍率を指定してズーム(0.5 をかけてるので、結果は サイズ(1200x800) )。
view.zoom(0.5f);

設定:ビューがどう表示「される」か
ここまで、2D世界内のどの部分をウィンドウに表示するのかを設定してきました。
では次に、この視界そのもの、が、ウィンドウの中のどこに表示されるのか、を設定しましょう。

デフォルトでは、ビューはウィンドウと同じサイズで、ど真ん中に居座っています。
この状態でビューのサイズ(2D世界から切り取ってくる四角形の大きさ)がウィンドウと同じだとしたら、つまり、全ての物体が 1:1 のサイズで表示されるということです。 ビューが小さかったり大きかったりする場合は、ウィンドウのサイズに合うように拡大or縮小されます。

大体のケースではデフォルトの挙動でちょうどいいと思います。が、変更したいときもありますよね。 たとえば、マルチプレイヤー型のゲームで画面を分割したいときには、ビューを2つ用意して、ウィンドウを半分ずつにしたい。
ミニマップを実装することもできます。 2D世界全体をビューに切り取って、小さめのサイズでウィンドウの隅に描画するとミニマップの出来上がりです。

つまり、ウィンドウと、ビュー(の中身)が表示されるエリアは別物ということなのですね。 ビュー(の中身)が表示されるエリアのことを「ビューポート」といいます。

ビューポートを設定するには、setViewPort()関数を呼びます。
// ウィンドウの半分のサイズで真ん中に表示
view.setViewport(sf::FloatRect(0.25f, 0.25, 0.5f, 0.5f));

さて、1つ大事なことに気がつきましたね? ビューポートの設定にはピクセルを使わないのです。
その代わりに何を使うかというと、ウィンドウサイズとの比率を使います。
これはすごく便利。
このおかげで、キミはウィンドウのリサイズイベントを追いかけてビューポートの設定を変更しなくて済む。
しかも直感的。
ビューの使い道から考えて、固定サイズの矩形として設定するよりも、 ウィンドウのレイアウトの一部として設定しますよね?

ビューポートを使うと、マルチプレイヤーゲームでの画面分割の設定も簡単です:
// プレイヤー1(画面の左半分)
player1View.setViewport(sf::FloatRect(0, 0, 0.5f, 1));

// プレイヤー2(画面の右半分)
player2View.setViewport(sf::FloatRect(0.5f, 0, 0.5f, 1));

ミニマップの設定はこんな感じ:
// ゲーム本体のビュー(ウィンドウと同じ大きさ)
gameView.setViewport(sf::FloatRect(0, 0, 1, 1));

// ミニマップ用のビュー(ウィンドウの右上の隅に小さく表示)
minimapView.setViewport(sf::FloatRect(0.75f, 0, 0.25f, 0.25f));

※訳註
上の画像では、見やすくするためにミニマップの周囲に縁取りをつけています。
ミニマップの位置とサイズに合わせて矩形を作って表示しています。

実装例:
sf::Vector2f defaultSize = window.getDefaultView().getSize();
sf::RectangleShape rectShape ( defaultSize * 0.25f);
rectShape.setPosition ( sf::Vector2f ( defaultSize.x * 0.75f, defaultSize.y * 0.0f ) );
rectShape.setOutlineThickness (-2);
rectShape.setFillColor ( sf::Color(200,200,250,50) );
rectShape.setOutlineColor ( sf::Color(0,0,0,255) );
ビューを使う
ビューを使って表示するには、 レンダリングターゲットの setView()関数を事前に呼んでおきます。
レンダリングターゲットというのは sf::RenderWindow や sf::RenderTexture などのことです。
// ビューを作成
sf::View view(sf::FloatRect(0, 0, 1000, 600));

// window に対して、ビューを有効にする
window.setView(view);

// このビューで何かを表示
window.draw(some_sprite);

// 現在のビューの設定を確認したい? getView() で取得できます。
sf::View currentView = window.getView();
...
ビューを1回設定すると、別のビューを設定するまで有効なままです。 つまり、常に何らかのビューが有効になっているということです。
プログラマが特に何も設定しない場合は、レンダリングターゲットはデフォルトのビューを使ってます。 サイズはそのレンダリングターゲットに対して 1:1。

getDefaultView()関数で、デフォルトのビューを取り出せます。
デフォルトの設定を元にして独自のビューを設定したいときに便利です。
または、元の設定を保存して再利用したいときにも。 GUI 部品を表示するときなどがそういう感じですよね。スクロールせずに画面上に固定しておきたい。
// デフォルトビューの半分のサイズのビューを作成
sf::View view = window.getDefaultView();
view.zoom(0.5f);
window.setView(view);

// デフォルトビューをバックアップ
window.setView(window.getDefaultView());
setView() でビューをセットすると、レンダリングターゲットはそのビューのコピーを作って、 自分の中に保存します。元データへのポインタではないのです。 なので、元のビューの設定を変更したときには、setView() をやりなおしてあげないと、 変更した設定が反映されません。 setView() するたびにコピーされるというと、なんだか重そうな処理のように聞こえるかもですが、 sf::View は軽いオブジェクトなので、遠慮せずにコピーしまくっても大丈夫です (float値をいくつか持ってるだけ)。
ウィンドウを広げたら視界も広げたい
ウィンドウを作成してからデフォルトのビューをデフォルトのままにしておくと、 視界に入ってくる内容も同じままです。 ウィンドウをリサイズすると、表示されている内容もウィンドウのサイズに合わせて伸び縮みします。
これがデフォルト。

ウィンドウのサイズに合わせて、視野を広げたり狭めたりしたい?
じゃあ、ウィンドウのサイズに合わせて、ビューの範囲を更新してあげようじゃありませんか。
// イベントループ
sf::Event event;
while (window.pollEvent(event))
{
    ...

    // リサイズイベントをキャッチ
    if (event.type == sf::Event::Resized)
    {
        // ビューのサイズをウィンドウの新しいサイズに合わせる
        sf::FloatRect visibleArea(0, 0, event.size.width, event.size.height);
        window.setView(sf::View(visibleArea));
    }
}
座標変換
デフォルトじゃないビューを使っていたり、 上記のサンプルのような処理を使わずにウィンドウのサイズを変更した場合(伸び縮み後)、 画面上の座標と、2D世界の中での座標が一致しなくなります。
たとえば、クリックした座標は (10, 50) なのだけど、2D世界の中では (26.5, -84) を指している、 なんてこともあり得るわけです。

座標を変換する関数をお呼びしましょう。 mapPixelToCoords() さんの登場です。
画面上のピクセルを、2D世界の中の座標に変換してくれます。
// ウィンドウ上でのマウスの座標を取得
sf::Vector2i pixelPos = sf::Mouse::getPosition(window);

// ビューの中での座標に変換
sf::Vector2f worldPos = window.mapPixelToCoords(pixelPos);
デフォルトでは mapPixelToCoords() さんは、レンダリングターゲットにその時点で設定されているビューを参照します。 アクティブになっていないビューで座標変換したい場合は、関数の引数にその旨をお知らせしてあげてください。

逆の操作も可能です。
mapPixelToCoords() さんには mapCoordsToPixel() さんというお友達がいて、 ビューの中の座標を画面上のピクセルに変換してくれます。