LoginSignup
53
68

More than 5 years have passed since last update.

SFMLを使った2Dゲーム開発/DXライブラリとの比較

Posted at

SFML

ごく最近OpenGLのラッパーライブラリである、
SFML(Simple and Fast Multimedia Library) というやつに手を出し始めました。
クロスプラットフォームなマルチメディアライブラリで、まあ要するにゲームとか作れるらしいです。
私は DXライブラリ でゲームは作ったことがあるものの他ライブラリはあまり使ったことがなかったので、
とりあえず勉強を兼ねて使ってみることにしました。
(ちなみにSFMLではまだゲームは作ってませんので、間違ってる所など多々あると思います…
 詳しい方いらっしゃいましたらご指摘願います)

今回は C++とVC2012 を使って進めていきます。

なおSFMLのサイトはここ
ResourcesからAPIドキュメントチュートリアル読めます。英語ですが。

本エントリの対象読者

まあ、対象とか言っちゃうほど大げさなものではないですが…。

  • ゲーム制作してる人/興味がある人
  • DXライブラリ使っていた(いる)人
  • 他のゲーム制作ライブラリに興味がある人
  • 中でもSFMLというものに興味があり、使ってみたい人
  • C++はわからんがクラスぐらいなら多少は分かるぜ!ぐらいのレベル以上の人

導入~ウインドウ表示するまで

ここここを読みましょう(丸投げ)

一応気をつける部分として、プロジェクトを「Win32アプリケーション」で作った場合は
main関数ではなくWinMainとして書かなきゃいけません。
コンソールアプリケーションとして実行するならmainでOKです。

それと、リンカー->入力->追加の依存ファイルの部分ですが、
sfml-system.libに他のライブラリが依存しているため、
sfml-audio.lib;sfml-graphics.lib;sfml-network.lib;sfml-window.lib;sfml-system.lib; (リリース)
sfml-audio-d.lib;sfml-graphics-d.lib;sfml-network-d.lib;sfml-window-d.lib;sfml-system-d.lib; (デバッグ)

の順で追加するといいらしいです(実際のところ、逆順で書くとエラーになるのかどうかは知らない)
あとダイナミックリンク使うときはプロジェクトのフォルダにもdllファイルをコピーしましょう。
binフォルダの中にあります。

あとは基本的にsf::Event関連の部分などはコピペでOKです。
DXライブラリ使いの人は ProcessMessage()互換 のものと思ってもらえれば。
ただし、sf::Windowの部分は色々都合がいいので sf::RenderWindow に変えといた方がいいです。

  • ウインドウ初期化関連ですが、DXライブラリだと下のように書きます:
dxlib.cpp
if( DxLib_Init() == -1 )return -1;
ChangeWindowMode( TRUE );       //ウインドウモード化
SetMainWindowText("TitleBar");      //タイトルを設定
SetWindowSizeChangeEnableFlag(FALSE);   //マウスでウインドウサイズ変更不可
SetGraphMode( 640 , 480 , 32 );     //ウインドウの大きさ、色深度変更
  • SFMLだとこんな感じで書きます:
sfml.cpp
sf::RenderWindow window;    //ただのWindowよりsf::RenderWindowの方がいいです
window.create(sf::VideoMode(800, 600,32), "TitleBar", sf::Style::Default);  //ウインドウの大きさと色深度、タイトルを設定
                        //3つ目の引数にStyle::Defaultを設定すると
                        //マウスでウインドウサイズ変更不可になります。詳しくは上のリンク参照
//window.setTitle("TitleBar");          //こういう書き方でもいいです
window.setVerticalSyncEnabled(true);        //垂直同期を取る(モニター依存、大体60Hz)。一回だけ呼ぶ
window.setPosition(sf::Vector2i(10, 50));   // ウインドウの位置を変更する(デスクトップに対して相対的である)
//window.setSize(sf::Vector2u(640, 480));       //ウインドウの大きさを変更する

上のコードは初期化関連なので、どちらも 最初に一回だけ実行する関数 です。

注意:

使ってる最中で気づいたのですが、 setSizeでウインドウサイズを変更しても
座標の原点はウインドウサイズを変更する前の位置で計算される
らしく、
そのままなにかを描画すると描画される位置がずれたりして大変なことになります。
座標原点を現在のウインドウ原点に合わせる関数ないかどうか探してますのでご協力ください…
なのでとりあえずは無闇矢鱈に ウインドウサイズを変えたりしないほうがいい です。

画像を表示して動かす

ただ表示された座標を動かすだけというのは解説的にあまり役に立たないと思うので、
回転・拡大縮小・半透明表示・ブレンドモード変更 を一挙に使ってみます。

SFMLとDXライブラリの大きな違いとして、スプライト(sf::Sprite)シェイプ(sf::Shape派生クラス)などの
表示可能なオブジェクトによる描画という点が挙げられます。
( sf::Drawableを継承したクラス による描画、ってことです)
sf::Spriteは座標や回転角度や拡大縮小の情報などを保持しているクラスで、
こいつにロードしたテクスチャ(sf::Texture)をセットしてやることで初めて画面に描画可能になります。

DXライブラリだとテクスチャ番号と位置を指定すれば画像が表示できたので、若干手間ではあります。
ちなみにテクスチャセットしないと 真っ白い四角 が表示されるみたいです。

  • まずDXライブラリだとこんな感じで画像表示します:
dxlib.cpp
//...
//色々初期化後
int GrHandle = LoadGraph("test.png");   //testというテクスチャをロード

//ゲームループ
while( ProcessMessage() == 0 ){
    SetDrawScreen(DX_SCREEN_BACK);  //裏画面へ表示
    ClearDrawScreen();  //画面をクリア

    //ここから画像とかを表示 

    SetDrawBlendMode(DX_BLENDOMODE_ADD,128);    //α値128で加算合成モードに設定
    SetDrawBright( 255, 0, 64);         //RGB=(255,0,64)で表示
    int XSize = 0;
    int YSize = 0;
    GetGraphSize(GrHandle ,&XSize, &YSize) ;    //画像サイズを読み込み
    DrawRotaGraph3(320, 240 ,XSize/2.0f, YSize/2.0f, 2.0f, 0.5f, 3.141592/2.0f, GrHandle, TRUE, TRUE);
    //画面上の(320,240)に、GrHandleというテクスチャを
    //画像中央を中心として画像を90度回転させ、x方向に2倍、y方向に1/2倍に引き伸ばしたうえで左右反転させ表示

    SetDrawBlendMode(DX_BLENDOMODE_NOBLEND,255);    //設定した値を元に戻す
    SetDrawBright( 255, 255, 255);          //同上

    //ここまで表示部分
    ScreenFlip();   //画面上に転送
}

//...
//色々後始末
  • 対して、SFMLだとこんな感じになります:
sfml.cpp
//...
//色々初期化後
sf::Texture Gr;
Gr.loadFromFile("test.png");    //testというテクスチャをロード。
Gr.setSmooth(true); //こうすると拡大縮小した時にバイリニア補正がかかる

//if(!Gr.loadFromFile("test.png")){
//  return -1; //ちなみにロード失敗することもあるらしいのでこういう風にやった方がいいかも?
//}

//ウインドウが開いている(ゲームループ)
while (window.isOpen()){

    sf::Event event;
    while (window.pollEvent(event)){
        //「クローズが要求された」イベント:ウインドウを閉じる
        if(event.type == sf::Event::Closed)
        window.close();
        }

    window.clear(); //画面をクリアする(初期値は黒)

    //ここから画像とかを表示
    sf::Sprite sprite(Gr);  //画面上に表示するスプライトを作り、テクスチャを登録
    sprite.setColor(sf::Color(255,0,64,128));   //RGBA=(255,0,64,128)で表示
    sprite.setOrigin(sprite.getLocalBounds().width/2.0f,sprite.getLocalBounds().height/2.0f);   //画像真ん中を中心として
    sprite.setPosition(320,240);    //画面上の(320,240)に
    sprite.setScale(-2.0f,0.5f);    //X方向に2倍、Y方向に1/2倍に引き伸ばしたうえでX方向に反転させ
    sprite.setRotation(90);     //スプライトを90度回転させ(度数表記であることに注意)

    window.draw(sprite,sf::RenderStates(sf::BlendAdd)); //加算合成で表示

    //ここまで表示部分
    window.display();   //画面上に転送
}

sprite.setOrigin(sprite.getLocalBounds().width/2.0f,sprite.getLocalBounds().height/2.0f); //画像真ん中を中心として
この部分についてですが、画像のどこを中心として回転・拡大・縮小といった処理を行うかを指定してます。
getLocalBound()でスプライトの四角形の大きさ情報が返ってくるので、/2すると中心部分が指定できます。
Originの初期値は(0,0) です。

ちなみに今回はループ内で毎回sf::Spriteを作っていますが、
ブロック外に作って使いまわしたりクラス内でメンバとして持って使ったりも当然可能です。
その場合、setPosition()などで設定した値も次フレームへ持ち越されます。(当たり前のようですが)
rotate()やscale()を使うと、スプライト内に保存してある値に足された値 が使われます。

また 単純図形(円や線)の描画も可能 です。
sf::Shapeの派生クラスであるsf::CircleShapesf::RectangleShapeを使います。

sfml.cpp
    sf::CircleShape circle(90); //半径90の円シェイプ
    circle.setTexture(&Gr);     //色情報の代わりにテクスチャを設定することも可能
    circle.setOrigin(circle.getLocalBounds().width/2,circle.getLocalBounds().height/2);
    circle.setScale(2.0f,0.5f); //拡大縮小描画
    circle.setPosition(600,500);
    window.draw(circle,sf::RenderStates(sf::BlendAlpha));   //アルファ合成で円を表示

    sf::CircleShape triangle(80, 3);    //円シェイプの頂点数を減らすことで三角形が描画可能
    triangle.setOrigin(triangle.getLocalBounds().width/2,triangle.getLocalBounds().height/2);
    triangle.setFillColor(sf::Color(200,200,100,255));  //内部のカラー
    triangle.setOutlineColor(sf::Color(250,250,100,255));   //枠線カラー
    triangle.setOutlineThickness(5.0f); //枠線の太さを設定可能
    triangle.setRotation(30);           
    triangle.setPosition(sf::Vector2f(200,500));//位置はsf::Vector2fにラップして渡すことも可能
    window.draw(triangle);

sf::VertexArrayやsf::ConvexShapeを使えば頂点を指定して図形描画もできます。(試してませんが)

オフスクリーンレンダリング

DXライブラリでいう 裏画面とかダブルバッファレンダリングとか に相当します。
複数画面作ってなんか色々できるやつです。

  • DXライブラリだとこう書きます:
dxlib.cpp
//...
//初期化関連

int ScreenBuf = MakeScreen(640,480,TRUE);   //バッファを作る

while( ProcessMessage() == 0 ){
    SetDrawScreen(ScreenBuf);//バッファ画面に書き込むよう変更
    ClearDrawScreen();  //バッファ画面をクリア
//なにか描画

//...

//描画終わり
    SetDrawScreen(DX_SCREEN_BACK);  //裏画面に書き込むよう変更
    ClearDrawScreen();  //画面をクリア
    DrawGraph(0,0,ScreenBuf,FALSE); //裏画面に描画
    ScreenFlip();   //画面上に転送

}
  • SFMLだとこう書きます:
sfml.cpp
//...
//初期化関連

sf::RenderTexture ScreenBuf;
ScreenBuf.create(640,480);  //バッファを作る
ScreenBuf.setSmooth(true);  //スムース設定ON

//ウインドウが開いている(ゲームループ)
while (window.isOpen()){
    sf::Event event;
    while (window.pollEvent(event)){
        //「クローズが要求された」イベント:ウインドウを閉じる
        if(event.type == sf::Event::Closed)
        window.close();
        }

    ScreenBuf.clear(sf::Color(0,0,0,255));  //バッファ画面を黒でクリア
//なにか描画

    //描画する場合はScreenBuf.draw(sprite);みたいな感じで書けるため、
    //書き込み画面を変更とかは必要ありません。
    //...

//描画終わり
    ScreenBuf.display();    //バッファ画面をアップデート
    sf::Sprite sprite(ScreenBuf.getTexture());  //バッファ画面用のスプライトを作る
    window.clear();     //画面をクリア
    window.draw(sprite);    //バッファ画面テクスチャの入ったスプライトを画面に描画
    //ちなみにsf::SpriteのPositionの初期値は(0,0)です。
    window.display();   //描画アップデート
}

このコードだとあんまり意味ないことやってますが、
書き込み可能なバッファを複数作っておくとゲームエフェクトなどで色々捗ると思います(割愛)
あと、window.draw()の第二引数に何も指定しなければ デフォルト状態で描画される らしいです。
なんか指定すると加算合成とかシェーダ描画とかが使えます。 (sf::RenderStates(sf::BlendAdd)とか)

それぞれのスクリーンに描画したあとにdisplay()を呼ぶのを忘れないでください。
ちなみにdisplay()を1ループ内で2回以上呼んでしまうとちらつきが発生するので、
表示がちらついてる時はdisplay()をたくさん呼んでないかチェックしましょう。

キー入力・ジョイスティック関連

【非公式翻訳】SFML2.1チュートリアル:キーボード、マウス、ゲームパッド
ここを読みま(略

SFMLの入力関連は全部スタティックです。
あと、DXライブラリでいうところのGetJoypadInputState(DX_INPUT_KEY_PAD1);←こういうやつが使えないので
キーボードからの入力とパッドからの入力はsf::Keyboardとsf::Joystickの別系統で取得しないといけません。
もっとも、最近はGetJoypadInputStateは使わないのかなという気がしますが…
(XInputやDirectInputなどを利用した入力取得系統の関数が色々増えてるので)

  • なのでとりあえずSFML側のコードだけ書きます。 :
sfml.cpp
//キー入力とパッドの入力両方受け取る処理

//...
//初期化コード

sf::Joystick::update(); //ジョイスティックの状態を手動更新(ゲームループ中では呼ぶ必要ないです)
bool JoyStick0_Connected = sf::Joystick::isConnected(0);    //0番ジョイスティクがあるかどうか検出

//ウインドウが開いている(ゲームループ)
while (window.isOpen()){
    sf::Event event;
    while (window.pollEvent(event)){
        //「クローズが要求された」イベント:ウインドウを閉じる
        if(event.type == sf::Event::Closed)
        window.close();
        }

    //→キーかジョイスティックの右入力があった時の処理
    if(sf::Keyboard::isKeyPressed(sf::Keyboard::Right)||sf::Joystick::getAxisPosition(0, sf::Joystick::X)>0)
    {
        //sf::Joystick::getAxisPosition(0, sf::Joystick::X)は0番ジョイスティックのX軸の傾きを検出する
        //[-100...100]の数値が返ってくるので、0より上ならば右に傾いている
        //...
    }
    //ほかのキーも同様に処理

    //Zキーか、ジョイスティックの1ボタンが押されたときの処理
    if(sf::Keyboard::isKeyPressed(sf::Keyboard::Z)||sf::Joystick::isButtonPressed (0, 1)){
        //...
    }
}

//後始末
//...

上のコードではキーボードとパッドの判定を両方とってますが、まぁそこはゲームによるので自由に書きましょう。
あと、DXライブラリでもそうですが、
「ボタンが押されてるか否か」の判定しかできないので、ボタンが押された瞬間・放された瞬間の判定や
どのぐらいボタンを押し続けているかなどについては自力で実装する
必要があります。

文字描画関連

【非公式翻訳】SFML2.1チュートリアル:文字とフォント
ここを(ry

DXライブラリと違って フォントファイルは自前で用意し、ディレクトリに置いておく必要がある
(Windowsのフォントフォルダは読んでくれない)ので、注意しましょう。
とりあえず、自前で用意したフォントを利用して文字を書くコードを書いてみます。

  • DXライブラリではこう書きます:
dxlib.cpp
AddFontResourceEx("test.ttf", FR_PRIVATE, NULL);    //メモリにフォントを読み込み
int myfont = CreateFontToHandle( "テストフォント", 20, 1, DX_FONTTYPE_ANTIALIASING) ;    //「テストフォント」はフォントネームとします
//20pxでアンチエイリアスをかけたフォントを描画します

SetDrawBlendMode(DX_BLENDMODE_ALPHA,128);   //アルファ値128でアルファ合成
DrawFormatStringToHandle( 0, 0, 0xff0000, myfont, "テスト"); //赤で(0,0)に描画
RemoveFontResourceEx("test.ttf", FR_PRIVATE, NULL);
  • SFMLではこう書きます:
sfml.cpp
sf::Font myfont;
myfont.loadFromFile("test.ttf");    //フォントのロード

sf::Text Testtext("testtest", myfont, 20);  //20pxでmyfontで描画するtestという文字オブジェクト
Testtext.setString(L"テスト");   //日本語を扱う場合はLをつけてワイド文字列にする
Testtext.setString(std::wstring(L"テスト")); //これでも可能
Testtext.setFont(myfont);   //あとからフォントファイルを差し替え可能
Testtext.setCharacterSize(30);  //もちろんサイズも可能
Testtext.setColor(sf::Color(255,0,0,128));  //色も変更可能。赤色の透明度128で表示
Testtext.setStyle(sf::Text::Bold);  //太字に変更とか
Testtext.setStyle(sf::Text::Italic);    //斜体に変更とかもできる。
Testtext.setStyle(sf::Text::Italic | sf::Text::Underlined); //パイプで区切れば複数適用可能

window.draw(Testtext);  //描画は他のオブジェクトと同じ

その他、 拡大縮小、回転、反転などスプライトやシェイプと同じように扱えます。
ここら辺かなり柔軟でいい感じですね。
もっと柔軟なことがやりたい場合、
sf::Glyphを使うとテクスチャからフォントを作れるみたいです。

音関連

【非公式翻訳】SFML2.1チュートリアル:音を出す。音楽とか効果音とか
こ(
上のサイトでも書いてありますが、sf::Soundは非ストリームsf::Musicはストリーム再生です。
効果音とかのメモリをあまり食わないファイルはsf::Soundで鳴らしましょう。

そして結構重要なポイントなのですが、
sf::MusicにはsetLoop(bool loop)というループするか否かを指定する関数はあるものの、
どこの場所からループするかを指定する関数が存在しません。
常に先頭から再生されます…。
なので曲が終わったら、 自前でチェックをかけてループ位置を設定する 必要があります。
割と曲者です。
曲をループするか否かのフラグは自前で持っておいて、ゲームループ内で

sfml.cpp
if(music.getStatus()==sf::Music::Status::Stopped && LoopFlag){
//上のLoopFlagは自前で管理してるフラグ
//止まっていたら、任意の位置(このサンプルでは150000ミリ秒)から再生
    music.play();
    music.setPlayingOffset(sf::microseconds(150000));
}

とかやるのがいい気がします。
これはsf::Soundについても同様なので、同様に対処しましょう。
勿論、常に先頭から再生される仕様で問題ないならばsetLoop(true);でいいです。

  • さて、DXライブラリでのサウンド再生コードです:
dxlib.cpp
//効果音の再生
int sound = LoadSoundMem("test.ogg");   //サウンドをロード
SetLoopPosSoundMem(150000, sound);  //150000ミリ秒にループ位置を設定
ChangeVolumeSoundMem( 100, sound) ; //音量を落とす(最大値は255)
PlaySoundMem(sound,DX_PLAYTYPE_LOOP,FALSE); //ループ再生

//音楽のストリーム再生
PlayMusic("test.wav" ,DX_PLAYTYPE_LOOP);    //ループ再生
SetVolumeMusic(100);    //ボリュームを落とす(最大値は255)
  • 次にSFMLのコードです:
sfml.cpp
//効果音の再生
sf::SoundBuffer sbuf;
sbuf.loadFromFile("test.ogg");  //サウンドバッファにロード
sf::Sound sound(sbuf);      //サウンドバッファをsf::Soundに入れて再生
sound.setVolume(40);    //音量を落とす(最大値は100)
bool LoopFlag = true;   //ループするか否かのフラグを自前で持つ
const sf::Time Looptime = sf::milliseconds(150000); //150000ミリ秒をループ位置とする
sound.play();

//音楽のストリーム再生
sf::Music music01;
music01.openFromFile("SE2.wav");
music01.play();

//...
//以下、ゲームループ内で

if(sound.getStatus()== sf::SoundSource::Status::Stopped && LoopFlag){
//Soundの現在状態を表すのはsf::SoundSource::Statusです
//止まっていたら、任意の位置(このサンプルでは150000ミリ秒)から再生
    sound.play();
    sound.setPlayingOffset(Looptime);
}

パニング関連についてはSFMLだと 3Dサウンド というものを使って表現するらしいのですが、
私もよくわかっていないので別の機会にまたやります。
OpenALの機能らしいです。
あとSFMLのオーディオAPIを使う場合、openal32.dlllibsndfile-1.dllを同梱する必要があるので注意が必要です。

3D関連

SFMLだとできません。
なんか外部ライブラリと連携させればできるらしいですがよくわかりません(
一応、OpenGLを直で叩けば3Dは使えるので、
自前でDrawableを継承したクラス作れば描画はできます。
ちなみに今後3D機能に対応予定は ない そうです…

ムービー再生関連

SFML単体だとできませんが、
有志の方?が作った補助ライブラリ sfe::Movieを使えば再生可能 になるようです。
→sfeMovieのサイトはこちら
使い方は簡単で、sfe::Movieオブジェクトを作ったあとにopenFromFile();で開き、再生するだけです。
window.draw()にぶち込めば普通に描画できます。
今のところ32bit版しかないので、 64bitで使う場合は自前でビルドが必要 です。

まとめ?

とまあざっと比較するとこんな感じですかねー。
長文でしたがここまで読んでくださった方、お疲れ様でした。

実際に使ってみるとちょっと面倒なところはありますが、
2D機能については結構成熟している感 はありますね。
特にシェイプやフォント周りは非常に柔軟でなかなか使えそうです。
実行速度についてはまだ測ってないですが、割とサクサク動きそうな感じはします。
また、公式ドキュメントもチュートリアルあったりして、そこそこ読みやすくまとまってると思います。

ただどうしても 3D機能が標準では備わってない というのが痛い。
DXライブラリでも高機能な3Dはできない(というかやりたくない)ですが、
カメラぐりんぐりん動かしたりワイヤーフレーム使った物体を生成したり
ポリライン飛ばしたりぐらいのことはしてたのでちょい残念です。
ちなみにOpenGL叩いて3Dやってるサンプルがexamples/opengl/フォルダにあるので、
まあ3DやりたければOpenGL使えるようになろうってことなんでしょう。

しかし描画系がOpenGLなので、 クロスプラットフォームで動く というのはいいですねー。
なんでも SFML2.2からはiOSに対応 したアプリケーションが作れるらしいですよ。
携帯機向けの開発が楽になるのはありがたいです。

あと シェーダ周りもDXライブラリで扱うより楽そう です。
GLSLそのままロードすれば使えるみたいなので…(
この辺とかネットワーク関連、3Dオーディオ関連はまた別の記事にでもしたいと思います。

SFMLは現状日本語での情報があまりないみたいなので、あると私が喜びます。

53
68
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
53
68