TOP > プログラミング関連 > SFML非公式翻訳トップ > 【非公式翻訳】SFML2.5チュートリアル > ウィンドウモジュール > ウィンドウを表示したり、いじったり

[原文(公式)]

◆◆ ウィンドウを表示したり、いじったり ◆◆
SFML2.5 非公式日本語翻訳
ウィンドウを表示したり、いじったりする方法だよ。
画像とかを表示するのは、また今度。それについては「グラフィックモジュール」のところでおはなしするよ。 今回は あくまでも「ウィンドウモジュール」の お話です。 でもグラフィック関連の操作も、ウィンドウをいじるやり方と、要領はほとんど同じだよ。だからこのページをしっかり読んでおくと絶対役に立つよ!
ウィンドウ、オープン!
SFMLのウィンドウは sf::Windowクラスで定義されてます。
コンストラクタを呼ぶだけでウィンドウを作れるよ!
#include <SFML/Window.hpp>

int main()
{
    sf::Window window(sf::VideoMode(800, 600), "My window");

    /* 中略 */

    return 0;
}
最初の引数はウィンドウのサイズです。 タイトルバーとか境界線とかを除いたクライアントエリアのサイズだよ。
上のサンプルでは、800×600ピクセルのウィンドウを作ってるってわけだね。

sf::VideoMode クラスには便利な関数が用意されてて、デスクトップの解像度を取得したり、 フルスクリーンモードに使えるビデオモードのリストを取得したりできるんだ。 詳しくは、遠慮せずにドキュメントを読むべし。

2番目の引数は、もちろんウィンドウのタイトルです。

さて、実は3番目の引数も指定することができます。ウィンドウのスタイルです。
下記の表にあるスタイルを組み合わせて、お好みの機能やデコレーションを指定しよう。
sf::Style::None お飾りなし。タイトルバーも何もない完全な すっぴん です。こんなの使い道あるのかって? あるのですよ! スプラッシュウィンドウとか。 ちなみにこのスタイルは他のスタイルと組み合わせ不可です。
sf::Style::Titlebar タイトルバーつきのウィンドウ。
sf::Style::Resize サイズ変更可能なウィンドウ。最大化ボタンも漏れなくついてきます。
sf::Style::Close 閉じるボタンつきのウィンドウ。
sf::Style::Fullscreen フルスクリーンで表示されるよ。 これも他のスタイルと組み合わせ不可。 これを使うときには、もちろん、フルスクリーン可能なビデオモードが必要です。
sf::Style::Default デフォルトスタイル。「Titlebar」と「Resize」と「Close」を指定したのと同じになります。 というか、ヘッダーファイル内で「Default = Titlebar | Resize | Close」と定義されてます。
もう1つ! 4番目の引数もあるのです。
OpenGLを使うための引数です。それについては「OpenGL を使う」のところでおはなしするね。


さて、コンストラクタで直接ウィンドウを作る方法を書いてきたわけですが、 実は Create という関数もあったりします。 コンストラクタを呼んだ後とか、別のビデオモードやタイトルでウィンドウを作り直したいときに使うとよいですよ。
使い方は全く同じです。
#include <SFML/Window.hpp>

int main()
{
    sf::Window window;
    window.create(sf::VideoMode(800, 600), "My window");

    /* 中略 */

    return 0;
}
ウィンドウ、始動!
上のサンプルコードをコピーして実行すると、ウィンドウが表示される……と、見せかけて、実は表示されません。
いや、表示はされるんですけど、サンプルの「/* 中略 */」のところに何も書かないままだと、見た目、何にも見えないのです。
えーっと、理由は2つあって、まず1つめは、プログラムがすぐに終了してしまう。 2つ目は、イベントを1つもお世話してない。 なので、今のままでも無限ループにすればプログラムは終了しなくなるからウィンドウは見えるようになるのだけれど、 固まったまま、というわけです。動かせない、サイズ変えれない。閉じれない。

そんなわけで、ちょっと手を加えて、かわいくしてあげましょう。
#include <SFML/Window.hpp>

int main()
{
    sf::Window window(sf::VideoMode(800, 600), "My window");

    // メインループ:ウィンドウが開いている限り、実行しつづける
    while (window.isOpen())
    {
        // イベントループ:たまってるイベントを全部取り出す。
        sf::Event event;
        while (window.pollEvent(event))
        {
            // イベントの種類が「閉じるボタンが押された」だったら?
            if (event.type == sf::Event::Closed) {
                
                // ウィンドウを閉じる。
                window.close();
            }
            /*
                if文を列挙して、他のイベントを捕まえることもできます。
            */
        }
    }

    return 0;
}
上のサンプルは、ウィンドウを開いて、ユーザーが閉じるボタンを押すと閉じる、というものです。 では、詳しく見ていきましょう。

まず、ウィンドウがリフレッシュ・更新されるようにループしてます。 このループはウィンドウが閉じられるまで続けます。 ほとんどの SFMLプログラムでは、こういう感じのループを書くことになると思うよ。 いわゆる「メインループ」とか「ゲームループ」ってやつだね。

で、そのゲームループ内で何をしてるかと言うと、 どんなイベントが発生したのかをループ内で見張ってます。
「while (window.pollEvent(event))」を使っているところに注目。 つまり、処理されずにたまってるイベントをここで全部処理してます。
たまってるイベントがあれば pollEvent関数の戻り値が true、なければ falseです。

イベントを取得したら、まず、その種類を確認です (ウィンドウ閉じる? キー押下? マウス? パッド接続? などなど…)。 そんで、イベントごとに、適切に処理してあげます(いらないイベントは無視すればいいよ)。
上のサンプルでは、Event::Closedイベントだけ、お相手してあげてますね。 閉じるボタンを押したときに飛んでくるイベントです。 この時点では、まだウィンドウは開いたままです。 close関数を呼んであげることで、実際に閉じます。
え、何? 閉じるボタンを押したんだから、その場で自動的に閉じてくれればいいのにって? なんでわざわざこんな回りくどい仕様になってるのかって?
よくぞ訊いてくれました。それはですね、それはですね、 ウィンドウを閉じる前にデータをセーブしたり、確認メッセージを出したりもできるよ! ってことです。
イベントループを書き忘れる、というのは ありがちなミスです。 入力の受付にイベントではなくリアルタイム入出力を使っていると、イベントのお世話を忘れてしまうかもしれませんね。 ですが、たとえリアルタイム入出力を使うとしても、イベントループがないと、ウィンドウが反応してくれません。

イベントループには役割が2つあります。

・ユーザー(プログラマー)が、イベントをハンドリングできるようにする
・ウィンドウ自身に、内部で必要なイベントを処理するタイミングを与える

つまりユーザーのためだけでなく、システムのためでもある。 ウィンドウを動かしたりサイズを変更したり、といった、ウィンドウ自身の基本的な動作のためにはイベントループが必要、というわけですね。 以上、ためになるお話でした。
ウィンドウが閉じられた後は、メインループが終了して、プログラムも終了します。

ここで気づいたと思うけど、まだウィンドウに何も描画してませんね? 最初に言ったように、何かを描画するのは、ウィンドウモジュールじゃなくてグラフィックスモジュールの仕事です。 なので、スプライトとか文字とか図形とかを描画したいときは、グラフィックスモジュールのチュートリアルを読んでね。

描画のときには SFMLのグラフィックスモジュールを無視して、OpenGLを使うこともできます。 実は、SFMLのウィンドウモジュールは内部では OpenGL を使ってます。なので、OpenGL とは仲良しです。 詳しくは 「OpenGL を使う」を見てね。

そんなわけで、ここで扱ってるウィンドウそのものには、過度な期待はしないでね。
ウィンドウで遊ぶ
もちろん、ちょっとぐらいは SFMLで遊べます。 サイズを変えたり、位置を変えたり、アイコンを変えたり、猫耳をつけたり、みたいな必要最低限の基本的な機能はサポートされてます。 でも、いわゆる、GUIアプリケーションを作るための専用のライブラリ(Qt とか? wxWidgets とか?)と違って、 SFMLのウィンドウそのものは、ハイテクな機能を持ってません。 SFMLのウィンドウは、あくまでも、何かを描画するための下地、です。 (で、その描画には SFMLのグラフィックスモジュールや、OpenGLを使う、と)
// ウィンドウの位置を変更(デスクトップ上の座標)
window.setPosition(sf::Vector2i(10, 50));

// ウィンドウのサイズを変更
window.setSize(sf::Vector2u(640, 480));

// ウィンドウのタイトルを変更
window.setTitle("SFML window");

// ウィンドウのサイズを取得
sf::Vector2u size = window.getSize();
unsigned int width = size.x;
unsigned int height = size.y;

...
sf::Windowクラスのメソッドについては、APIドキュメントに全部載ってるよ。


さて、どうしても、もっといろんな機能が欲しかったら、他の GUIライブラリでウィンドウを別個に作って、 そこに SFMLを埋め込めばいいじゃない。ふん。
sf::Window クラスには、ウィンドウハンドルを指定するものも用意されておりますからね。 ウィンドウハンドルの中身は OS によって異なっているでしょうけれど、そこは優秀な SFML のことですから、お任せして安心です。 元のウィンドウ自身の処理を邪魔することなく、描画用のコンテキストを作りつつ、イベントをキャッチいたしますよ。
sf::WindowHandle handle = /* 別個に作ったウィンドウのウィンドウハンドルを代入 */;
sf::Window window(handle);
SFML にない機能を1つ追加したいだけ、という場合は、逆に、 SFML のウィンドウからウィンドウハンドルを取り出して使うとよいですね。 もちろんそのハンドルも、OS に合わせた仕様になっておりますよ。
取り出したウィンドウハンドルを使って、 そのウィンドウに何でもお好きなように機能を追加すればいいでしょー。
// こんな感じで、SFML のウィンドウがあったとして、
sf::Window window(sf::VideoMode(800, 600), "SFML window");

// ウィンドウハンドルがあれば、あとはもう、やりたい放題
sf::WindowHandle handle = window.getSystemHandle();
SFMLと他のライブラリを混ぜて使うのは、ちょっとした一苦労というかなんというか、 ここでは解説しないので、別の解説サイトとかを探してね。掲示板で質問するとか。
フレームレートを制御
アプリケーションが高速で動作しているときとか、ティアリングが発生することがありますよね。 アプリのリフレッシュレートがモニターの垂直同期と合ってないのが原因です。 つまり、1フレーム前の画面が現在の画面と混じってしまう、ということです。

この問題は、ウィンドウの垂直同期を有効にすることで解決できます。
setVerticalSyncEnabled (true) を実行しておくと、 ビデオカードが自動的に対処してくれるようになります。
// ウィンドウを作成した後、1回だけ実行
window.setVerticalSyncEnabled(true);
これを1回実行しておくと、キミのアプリケーションはモニターと同じフレームレートで動くようになるよ。
setVerticalSyncEnabled(true) を実行しても何も効果がないときがあります。 よくある原因は、グラフィックドライバーの設定で、垂直同期が「off」になっていることです。 この設定を「controlled by application」にしておく必要があります。
フレームレートを好きな値にしたいときもありますよね。モニターと同じにするんじゃなくて。
その夢は setFramerateLimit関数によって実現されるのです。
// ウィンドウを作成した後、1回だけ実行
window.setFramerateLimit(60);
setVerticalSyncEnabled関数と違って、この関数は SFML 内部で独自に実装されてます。 内部では sf::Clock と sf::sleep を組み合わせて動作させてます。
そんなわけで、100% は信頼できません(特に高いフレームレートのとき)。
sf::sleep の解像度は OS に依存してて、大体10~15ミリ秒です。 正確な結果は期待しないでね。
setVerticalSyncEnabled() と setFramerateLimit() を同時に使ってはイケません! 嫌な感じに混ざり合って状況が悪化するぞ!
その他、知っておくとよさそうなこと
SFMLのウィンドウで「できること」「できないこと」を簡単にまとめておきます。
同時に複数のウィンドウを作れる
そんで、メインループで全部まとめて面倒みてもいいし、 ウィンドウごとに別スレッドにするのもアリです(ただし……下記参照)。
ウィンドウをたくさん作ったら、イベントループもたくさん実装するのを忘れないでね!
マルチモニターは、いまいち。
SFMLちゃんは、マルチモニターのことをよく知りません。 なので、ウィンドウがどのモニターに出現するかは、やってみてのお楽しみです。 フルスクリーンを2つ以上つくることもできません。 未来の SFMLちゃんの成長に乞うご期待。
イベントは、そのウィンドウのスレッドで取り出すこと
これはですね、ほとんどの OSでそういう制限がありまして、イベントループ(つまり pollEvent関数 or waitEvent関数) は、そのウィンドウを作ったスレッドで実行してあげる必要があります。 たとえば、イベントハンドリング専用のスレッドを作る場合、 ウィンドウも、そのスレッドで作っておく必要があります。
スレッドを使って処理を分離したいときは、イベントハンドリングはメインスレッドに残しておいて、 他の処理(レンダリング、物理演算、ロジックなどなど)を別のスレッドに移動させるとよいです。
この実装方式は、次に説明するもう1つの制限事項とも相性がよいですよ。
macOSでは、ウィンドウとイベントはメインスレッドで処理せよ
残念ながら、そうなのです。
macOSは、なんか、メインスレッド以外のところで ウィンドウを作ったりイベントをハンドリングしたりするのを、許してくれないのですね。
ウィンドウズではデスクトップより大きいウィンドウは、動作がヘンになる
ウィンドウズさんはデスクトップより大きいサイズのウィンドウがお嫌いのようです。
「デスクトップより大きいサイズ」には、VideoMode::getDesktopMode()で作ったウィンドウも含まれます。 タイトルバーや境界線を含むとデスクトップよりちょっとだけ大きくなってしまう……。