TOP > プログラミング関連 > SFML非公式翻訳トップ > 【非公式翻訳】SFML2.1チュートリアル > グラフィックスモジュール > 移動、回転、拡大

[原文(公式)]

◆◆ 移動、回転、拡大 ◆◆
非公式日本語翻訳
トランスフォーム
SFML には「スプライト(sf::Sprite)」「テキスト(sf::Text)」「図形(sf::Shape)」などの2Dオブジェクトがありますが、 どれも、共通のインターフェイスを持ってます。sf::Transformable というベースクラスです。
このベースクラスに、移動、回転、拡大のためのシンプルなAPIが定義されてます。

この API はわかりやすさと使いやすさを優先してます。
なので、なんでもできるというわけではないのですが、99% のケースでは充分だと思います。 残り 1% の特殊なケースが気になるアナタは……このページの最後の章をお楽しみに!

sf::Transformable クラス(と、これを継承してる全てのクラス。sf::Sprite とか)は、4つのプロパティを持ってます。 「位置(position)」「向き(rotation)」「倍率(scale)」「中心点(origin)」です。 どの子もそれぞれにゲッターとセッターを持ってます。
この子たちはお互いに独立していて他の子の邪魔をしないようになってます。 なので、たとえば、回転させたいときには「向き(rotation)」の値だけを変えれば OK です。 位置や倍率を気にしなくても大丈夫です。

位置
2Dオブジェクトを表示する座標の設定、です。
プログラム内にある2D世界の中で、そのオブジェクトをどこに表示するか? です。
これ以上の説明は不要ですよね?
// 以下の 'entity' は sf::Sprite か、sf::Text か、sf::Shape か、
// ともかく sf::Transformable を継承している2Dオブジェクトです。

// 座標を直接セット
entity.setPosition(10, 50);

// 移動(=現在位置からの相対座標)
entity.move(5, 5);

// 座標を取得
// (10, 50) から (5, 5) 移動したので、(15, 55) が得られます。
sf::Vector2f position = entity.getPosition();

デフォルトでは、2Dオブジェクトの左上の角が座標の基準(中心点)です。
基準点を変更するお話もあとでするので、お楽しみに!
向き
回転です。2Dオブジェクトを、どっち向きに表示するか? の設定です。
単位は「度」です(ラジアンじゃなく)。
そして、時計回りです(理由:SFML では Y軸が下向きだから)。
// 以下の 'entity' は sf::Sprite か、sf::Text か、sf::Shape か、
// ともかく sf::Transformable を継承している2Dオブジェクトです。

// 角度を直接セット
entity.setRotation(45);

// 現在の角度から、回転させる。
entity.rotate(10);

// 現在の角度を取得
// 45 + 10 = 55度 が得られます。
float rotation = entity.getRotation();

getRotation() で得られる値はいつでも 0~359 の間です。

位置と同じように、向きはその2Dオブジェクトの「中心点」を基準に回転します。
中心点のお話はもう少し待ってね。
倍率
2Dオブジェクトの表示サイズの倍率です。
デフォルトでは 1 です。
1 より小さくすると(少数)図形は小さく表示されます。
1 より大きくすると、図形は大きく表示されます。
// 以下の 'entity' は……(以下同文)

// 倍率を直接セット(X軸方向の倍率と、Y軸方向の倍率)
entity.setScale(4.0f, 1.6f);

// 現在の倍率に、倍率をかける
entity.scale(0.5f, 0.5f);

// 倍率を取得
// (4.0, 1.6) x (0.5, 0.5) なので、(2.0, 0.8) が得られます。
sf::Vector2f scale = entity.getScale();

中心点
「中心点」は、これまでお話してきた3つの操作それぞれの基準になる点、です。
表示位置は中心点が基準です。回転も、ここを中心にくるくるしますし、拡大縮小もここが中心です。
デフォルトでは、その2Dオブジェクトの左上の角の点、つまり座標(0, 0) が中心です。

仕様をシンプルにするために、SFML では 1つの2Dオブジェクトは1つの中心点だけを持ってます。 どういうことかというと、たとえば、位置は左上を基準につつ、回転は真ん中を中心に……ということはできません。
そういうことがしたいってお友達は、次の章を楽しみにしててね。
// 'entity' は 毎度おなじみのアレ。

// 中心点を設定
entity.setOrigin(10, 20);

// 中心点を取得。(10, 20)が得られます。
sf::Vector2f origin = entity.getOrigin();
注意点。
表示座標を変更しなくても、中心点を変更すると、2Dオブジェクトの見た目の位置も変化します。
どうしてなのか、わかるかな?
わからないってお友達は、このチュートリアルをもう一回読んでね!

※ 訳註
ここでいう「中心点」の原語は「origin」です。「原点」と訳す方が、用語としては正しい気がします。 でも、「中心点」と言った方が、意味はわかりやすい、ですよね……? そうでもないですか……?
あなたのクラスも移動、回転、拡大
sf::Transformable は SFML のクラス専用じゃなくて、みんなのお友達です。 あなたのクラスのベースクラスにもできるし、メンバーにもできちゃいます。
class MyGraphicalEntity : public sf::Transformable
{
    // ...
};

MyGraphicalEntity entity;
entity.setPosition(10, 30);
entity.setRotation(110);
entity.setScale(0.5f, 0.2f);
位置や回転などの設定を実際の表示に反映させるには、getTransform() 関数を呼びます。 この関数の戻り値として sf::Transform のインスタンスが得られます。 この子の扱い方については、次の章を見てね。

sf::Transformable の全部の関数が必要とは限らないかもしれません。 そんなときは遠慮せず、sf::Transformable をメンバー変数として持つとよいです。
継承しないなら、sf::Transformable のインターフェイスに無理に合わせなくても済みますね。 sf::Transformable は仮想クラスじゃないので、インスタンス生成もできるのです。
もっと細かく操作したい!(カスタムトランスフォーム)
sf::Transformable は使いやすいけど、機能は少なめです。欲張りなお友達は満足できないかもしれませんね。 単純な移動・回転・拡大だけじゃなく、細かい操作を組み合わせなければ、その欲望を満たせない……。

そんなお友達のために、SFML は細かい操作ができるクラスを用意してます。その名は sf::Transform クラス。
その正体は単なる 3x3 の行列(マトリクス)です。
余分なものがないので、2D空間内でのどんなトランスフォームも実現可能なのです。

sf::Transform のインスタンスを作る方法は色々あります: では、サンプルをどうぞ:
// 原点に初期化
sf::Transform t1 = sf::Transform::Identity;

// 回転
sf::Transform t2;
t2.rotate(45);

// 要素を個別に設定
sf::Transform t3(2, 0, 20,
                 0, 1, 50,
                 0, 0, 1);

// 組み合わせる
sf::Transform t4 = t1 * t2 * t3;
単一の sf::Transform へ 複数の操作を重ねがけすることもできます:
sf::Transform t;
t.translate(10, 100);
t.rotate(90);
t.translate(-10, 50);
t.scale(0.5f, 0.75f);
大事なことを言い忘れてました。
こうして仕込んだトランスフォームを2Dオブジェクトの描画に反映させるにはどうすればいいのか?
カンタンです。ウィンドウの draw() 関数に引数として渡してあげるだけです。
window.draw(entity, transform);
ちなみに、このコードは次のコードの省略形です:
sf::RenderStates states;
states.transform = transform;
window.draw(entity, states);
もし、この2Dオブジェクトが sf::Transformable を継承してる場合(sf::Sprite とか)は、 内部にすでにトランスフォームの情報を持ってます。
そこへ上のやり方で独自のトランスフォームを設定すると、重ねがけ、になります。
バウンディングボックス
これでオブジェクトを動かせるようになりました。
では、次にしたいことは何だろう? 衝突判定だ!

SFML の2Dオブジェクトはバウンディングボックスを持ってます。
え? 「バウンディングボックス」って何?
四角形です。その2Dオブジェクトをぴったりと囲む四角形です。
各辺が X軸と Y軸に平行な四角形です。


バウンディングボックスは衝突判定に便利です。
他の点や四角形との衝突を高速に計算できます。
オブジェクトの見た目と完全に一致しているわけではないですが、大体 OK ですよね?
// バウンディングボックスを取得
sf::FloatRect boundingBox = entity.getGlobalBounds();

// 「点」との衝突をチェック
sf::Vector2f point = ...;
if (boundingBox.contains(point))
{
    // 衝突してます!
}

// 他の四角形との衝突をチェック
// (つまり他のオブジェクトのバウンディングボックスとの衝突チェック)
sf::FloatRect otherBox = ...;
if (boundingBox.intersects(otherBox))
{
    // 衝突してます!
}
getGlobalBounds()というお名前の関数でバウンディングボックスを取得できます。 なんで "global" とついているかというと、グローバルな座標システムの中でのバウンディングボックスだから、です。 移動、回転、拡大などの操作が影響します。

ローカル座標版もあります。getLocalBounds() 関数。
移動などの操作は影響しません。 この関数は、2Dオブジェクトのもともとのサイズを取得したり、 もっと、こう、なんというか、細かい衝突計算をするのに使うとよいです。
複数のオブジェクトを階層的に関連づける
前の章で見たカスタムトランスフォームを使うと、オブジェクトを階層的に関連付けられるようになります。 つまり、オブジェクトに親子関係を作る。
子オブジェクトは親オブジェクトの位置を基準に動いたり回ったりする。
親オブジェクトを動かすと、子オブジェクトも一緒に動いたり回ったりする。

考え方は簡単。
親から子へトランスフォーム情報を受け渡して、描画する前に重ねがけするだけです。
//=========================================================
// ノードとしての抽象クラス
//=========================================================
class Node
{
public:

    // ……このノードのトランスフォーム処理のための関数群

    // ……子オブジェクト(たち)を管理するための関数群


    // 親から呼ばれる描画処理
    void draw ( 
            sf::RenderTarget& target, 
            const sf::Transform& parentTransform 
        ) const
    {
        // 親のトランスフォームに、自分のトランスフォームを重ねがけ(順番に注意)
        sf::Transform combinedTransform = parentTransform * m_transform;

        // 自分を描画(これが抽象関数)
        onDraw ( target, combinedTransform );

        // 子オブジェクトを描画
        for (std::size_t i = 0; i < m_children.size(); ++i) {
        
            // 子の描画処理を呼ぶ(つまり、同じく draw()関数を呼ぶ)
            m_children[i]->draw(target, combinedTransform);
        }
    }

private:
    
    // 自分を描画する関数(これが抽象関数)
    virtual void onDraw ( 
                sf::RenderTarget& target, 
                const sf::Transform& transform 
            ) const = 0;
    
    // トランスフォーム情報(マトリクス)
    sf::Transform m_transform;
    
    // 子リスト
    std::vector<Node*> m_children;
};

//=========================================================
// 上の抽象ノードクラスを継承するサンプル
//=========================================================
class SpriteNode : public Node
{
public:

    // ……スプライトを定義する関数群

private:
    
    // 自分を描画。スプライトを描画する実装になってますね?
    virtual void onDraw ( 
            sf::RenderTarget& target, 
            const sf::Transform& transform 
        ) const
    {
        target.draw ( m_sprite, transform );
    }

    sf::Sprite m_sprite;
};