Help us understand the problem. What is going on with this article?

SFML.netを使ってみた

More than 1 year has passed since last update.

最初に

 僕はここ最近まで、SDL2を使っていたのですが、
諸事情によりSFMLを使い始めました

 理由は、
SDL2のVersion 2.0.5までは問題なかったのですが、
Version 2.0.7ではSDL_mixerのMix_Init関数で正しく初期化ができない、という
バグ?仕様変更?があったため、
SDL2からの移行を考えて、せっかくなので、もっと使いやすいライブラリを探していたら、
SFMLというライブラリが使いやすそうなので、これに移行しようと考えました

** 追記
 SDL2だけでなく、
SDL_imageと、
SDL_mixerと、
SDL_ttfも新バージョンにしないと、
正しく動作しないようでした

ずっとこれに気が付かず、バグだと思ってました
**

** さらに追記
 SDL2 Ver 2.0.5と Ver2.0.7では内部使用が違うためか、
Ver 2.0.7では、Mix_Init関数にMIX_INIT_FLACを指定したときに、
エラーが出るということが分かりました

**

 SFMLのことはまだわからないことだらけですので、
日本語の情報がかなり少ないようなので、
情報を少し書いてみようと思いました

 説明下手ですが、どうか

SFML関連のURL

↓SFML公式サイト
https://www.sfml-dev.org/
↓SFMLのダウンロードページ(C++用)
https://www.sfml-dev.org/download/sfml/2.4.2/
↓SFMLのバインディング集
https://www.sfml-dev.org/download/bindings.php
↓SFML.netのダウンロードページ
https://www.sfml-dev.org/download/sfml.net/

↓数少ないと思われるSFMLの日本語情報
http://www.site-a.info/programming/sfml/SFML_unofficialTranslation.html

準備

 C#ユーザーなので、SFML.netというライブラリを選びました
なので、SFML.netを使う前提でこの記事を書きます

1・SFML.netのダウンロードページにある、
All compilers - 32-bitか、
All compilers - 64-bitのどちらかをダウンロードします
(必要ならば、両方ダウンロードしても構いません)

2・ダウンロードしたZip内の
extlibsフォルダとlibフォルダ内のDLLを全て
プロジェクトの出力先フォルダに入れます

(lib内に含まれているDLLはすべて、C#用のライブラリです)

3・以下の4つのDLLをプロジェクトの参照に追加します

sfmlnet-audio-2.dll
sfmlnet-graphics-2.dll
sfmlnet-system-2.dll
sfmlnet-window-2.dll

(上記DLL全てlib内のフォルダにあるDLLです)

これで実行に必要なライブラリの準備ができました

ウィンドウを表示するまで

SFML.netを動かすための
最低限のコードです

using SFML.Graphics;

namespace Game_Project1 {
    static class Program {
        static void Main() {
            //ウィンドウを作成
            var window = new RenderWindow(
                new SFML.Window.VideoMode {
                    Width = 400,   //横幅
                    Height = 400, //縦幅
                },
                "テストプログラム", //表示する文字列
                SFML.Window.Styles.Close //ウィンドウ右上に×ボタンを設置
            );
            //window.Closedイベントで、
            //×ボタンが押されたときの処理を追加できる
            //
            //window.Close関数を呼ぶとウィンドウを閉じる
            window.Closed += (sender, e) => window.Close();

            //メインループを開始
            while (window.IsOpen) {
                //ウィンドウのイベント処理を実行
                //(必ずこれを実行すること!)
                window.DispatchEvents();
            }
        }
    }
}

ビットマップ描画

using SFML.Graphics;

namespace Game_Project1 {
    static class Program {
        static void Main() {
            var window = new RenderWindow(
                new SFML.Window.VideoMode {
                    Width = 400,
                    Height = 400,
                },
                "テストプログラム",
                SFML.Window.Styles.Close
            );
            window.Closed += (sender, e) => window.Close();

            //画像を読み込み
            var image = new Image("0.png");
            //画像をテクスチャに変換
            var texture = new Texture(image);
            //描画用オブジェクトを作成
            var sprite = new Sprite(texture);

            while (window.IsOpen) {
                window.DispatchEvents();

                //ウィンドウを黒で塗りつぶす
                window.Clear();

                sprite.Position = new SFML.System.Vector2f(20, 20);
                //画像の描画
                window.Draw(sprite);

                //描画したものを表示
                window.Display();
            }
        }
    }
}

テキスト描画

比較的楽なテキスト描画コードです

using SFML.Graphics;

namespace Game_Project1 {
    static class Program {
        const string fontName = "0.ttf";

        const string textStr = "表示テスト";

        static void Main() {
            var window = new RenderWindow(new SFML.Window.VideoMode {
                Width = 400,
                Height = 400,
            }, "テストプログラム", SFML.Window.Styles.Close);
            window.Closed += (sender, e) => window.Close();

            //フォント読み込み
            var font = new Font(fontName);
            //テキスト描画用オブジェクトを作成
            var text = new Text(textStr, font);

            while (window.IsOpen) {
                window.DispatchEvents();

                //ウィンドウを黒で塗りつぶす
                window.Clear();

                text.Position = new SFML.System.Vector2f(20, 20);
                //テキストを描画
                window.Draw(text);

                //描画したものを表示
                window.Display();
            }
        }
    }
}

テキスト描画(もう一つの方法)

やや面倒な方法ですが
こちらのほうが柔軟性が高めです

using SFML.Graphics;

namespace Game_Project1 {
    static class Program {
        const string fontName = "0.ttf";

        const string textStr = "表示テスト";
        const uint textSize = 32;

        static void Main() {
            var window = new RenderWindow(new SFML.Window.VideoMode {
                Width = 400,
                Height = 400,
            }, "テストプログラム", SFML.Window.Styles.Close);
            window.Closed += (sender, e) => window.Close();

            //フォント読み込み
            var font = new Font(fontName);

            foreach (var i in textStr) {
                //フォントテクスチャに、
                //指定した文字を書き込む
                font.GetGlyph(i, textSize, false);
            }

            //フォントからテクスチャを作成
            var texture = font.GetTexture(textSize);
            var sprite = new Sprite(texture);

            while (window.IsOpen) {
                window.DispatchEvents();

                //ウィンドウを黒で塗りつぶす
                window.Clear();

                for (var i = 0; i < textStr.Length; i++) {
                    //表示したい文字を描画するためのデータを取得
                    var glyph = font.GetGlyph(textStr[i], textSize, false);

                    sprite.Position = new SFML.System.Vector2f(20 + textSize * i, 20);
                    sprite.TextureRect = glyph.TextureRect;

                    //文字列を表示
                    window.Draw(sprite);
                }

                //描画したものを表示
                window.Display();
            }
        }
    }
}

音再生

SFMLでの音再生で使えるファイルの拡張子は
WAVとOGGの2つが使えることを確認

MIDファイルとMP3のファイルの再生はSFMLでは不可
SDL2ではできるのに・・・

using SFML.Graphics;
using SFML.Audio;

namespace Game_Project1 {
    static class Program {
        static void Main() {
            var window = new RenderWindow(
                new SFML.Window.VideoMode {
                    Width = 400,
                    Height = 400,
                },
                "テストプログラム",
                SFML.Window.Styles.Close
            );
            window.Closed += (sender, e) => window.Close();

            //音再生用オブジェクトの作成
            //
            //WAVファイルとOGGファイルの再生が
            //できることを確認
            //
            //MIDとMP3の再生は残念ながらできない
            //(読み込み時にエラーが出る)
            var music = new Music("0.wav");
            //Loopプロパティでループ再生できるかどうかを指定できる
            //(ただし、Play関数を呼ぶ前に指定しないと反映されない)
            //初期値はfalse
            //
            //falseならループしない
            //trueならループする
            music.Loop = true;
            //音再生の開始
            music.Play();

            while (window.IsOpen) {
                window.DispatchEvents();
            }
        }
    }
}

フレームレートの実装

上記例では60Fpsになっていませんでした
ゲームループを60Fpsにするサンプルです

using SFML.Graphics;
using SFML.Window;
using SFML.System;

namespace Game_Project {
    static unsafe class Program {
        const uint fps = 60;

        static void Main() {
            var window = new RenderWindow(new VideoMode(320, 320), "", Styles.Close);
            window.Closed += (sender, e) => window.Close();
            //フレームレートの指定
            //引数はuint型
            //
            //(この例では60 Fpsを指定)
            window.SetFramerateLimit(fps);

            var texture = new Texture("0.png");
            var sprite = new Sprite(texture);

            while (window.IsOpen) {
                window.DispatchEvents();

                window.Clear();

                if (Mouse.IsButtonPressed(Mouse.Button.Left)) {
                    sprite.Position += new Vector2f(1, 0);
                }

                window.Draw(sprite);

                //この関数を使うと、
                //window.SetFramerateLimit関数で指定したFpsになる
                //
                //(この例では60 Fpsになる)
                window.Display();
            }
        }
    }
}

Spriteを使わずに描画するサンプルコード

Spriteクラスを使わずにテクスチャ描画をする方法が分かったので

*追記*
Spriteを使って描画するのと
自前で描画するのとでは処理速度にあまり差がありませんでした

using SFML.Graphics;
using SFML.Window;
using SFML.System;

namespace Game_Project {
    static unsafe class Program {
        const uint fps = 60;

        static void Main() {
            var window = new RenderWindow(new VideoMode(320, 320), "", Styles.Close);
            window.Closed += (sender, e) => window.Close();
            window.SetFramerateLimit(fps);

            var texture = new Texture("0.png");

            var sprite = new Sprite(texture);
            sprite.Position = new Vector2f(20, 20);

            var spriteTester = new SpriteTester(texture);
            spriteTester.X = 20;
            spriteTester.Y = 50;

            while (window.IsOpen) {
                window.DispatchEvents();

                window.Clear();

                //既定のスプライトを表示
                window.Draw(sprite);
                //自作スプライトを表示
                window.Draw(spriteTester);
                //スプライトを使わずに画像を表示
                TextureDrawer.Draw(window, texture, 20, 100);

                window.Display();
            }
        }
    }

    //スプライトを使わずに画像を表示するためのクラス
    static class TextureDrawer {
        public static Vertex[] Vertexs { get; } = new Vertex[4];

        public static void Draw(RenderTarget target,Texture texture, float x, float y){
            //描画データの指定
            var transform = Transform.Identity;

            //描画範囲を指定
            var left = x;
            var right = x + texture.Size.X;
            var top = y;
            var bottom = y + texture.Size.Y;

            //左上
            Vertexs[0].Position = new Vector2f(left, top);
            Vertexs[0].TexCoords = new Vector2f(0, 0);
            //左下
            Vertexs[1].Position = new Vector2f(left, bottom);
            Vertexs[1].TexCoords = new Vector2f(0, texture.Size.Y);
            //右上
            Vertexs[2].Position = new Vector2f(right, top);
            Vertexs[2].TexCoords = new Vector2f(texture.Size.X, 0);
            //右下
            Vertexs[3].Position = new Vector2f(right, bottom);
            Vertexs[3].TexCoords = new Vector2f(texture.Size.X, texture.Size.Y);

            for (var i = 0; i < Vertexs.Length; i++) {
                //new Color(255, 255, 255, 255)
                //で画像の色変更なし
                Vertexs[i].Color = new Color(255, 255, 255, 255);
            }

            var states = default(RenderStates);
            //これも指定しないと表示されない
            states.BlendMode = BlendMode.Alpha;

            //描画データをstatesに指定する
            states.Transform = transform;
            //テクスチャをstatesに指定する
            //
            //states.Textureがnullの場合は矩形が表示される
            states.Texture = texture;

            //描画開始
            target.Draw(Vertexs, 0, (uint)Vertexs.Length, PrimitiveType.TrianglesStrip, states);
        }
    }

    //自作スプライト
    sealed class SpriteTester : Drawable {
        public Vertex[] Vertexs { get; } = new Vertex[4];
        public Texture Texture { get; }
        public Transform Transform { get; set; }

        public float X { get; set; }
        public float Y { get; set; }
        public float Width { get; set; }
        public float Height { get; set; }

        public float Left => X;
        public float Right => X + Width;
        public float Top => Y;
        public float Bottom => Y + Height;

        public SpriteTester(Texture texture) {
            //テクスチャの指定
            Texture = texture;
            //描画データの指定
            Transform = Transform.Identity;

            Width = Texture.Size.X;
            Height = Texture.Size.Y;
        }

        public void Draw(RenderTarget target, RenderStates states) {
            //左上
            Vertexs[0].Position = new Vector2f(Left, Top);
            Vertexs[0].TexCoords = new Vector2f(0, 0);
            //左下
            Vertexs[1].Position = new Vector2f(Left, Bottom);
            Vertexs[1].TexCoords = new Vector2f(0, Height);
            //右上
            Vertexs[2].Position = new Vector2f(Right, Top);
            Vertexs[2].TexCoords = new Vector2f(Width, 0);
            //右下
            Vertexs[3].Position = new Vector2f(Right, Bottom);
            Vertexs[3].TexCoords = new Vector2f(Width, Height);

            for (var i = 0; i < Vertexs.Length; i++) {
                //new Color(255, 255, 255, 255)
                //で画像の色変更なし
                Vertexs[i].Color = new Color(255, 255, 255, 255);
            }

            //描画データをstatesに指定する
            states.Transform = Transform;
            //テクスチャをstatesに指定する
            //
            //states.Textureがnullの場合は矩形が表示される
            states.Texture = Texture;

            //描画開始
            target.Draw(Vertexs, 0, (uint)Vertexs.Length, PrimitiveType.TrianglesStrip, states);
        }
    }
}

最後に

SDL2よりも必要なコードがかなり短く
楽そうですね

ここまででSDL2よりかなり使いやすく感じられました
SDL2は初期化から描画までにもっとコードを書く必要がありますし

もう少し使い方を探して
SDL2からの移行を目指します

・・・SDL2のversion 2.0.8では初期化関数が元通りになるかなあ・・・

 一部、SFMLではなくSDL2のこと書いているけど、
これのためにページを作るほどではないと思ったので、
ここに書きました

 SDL2 2.0.7のエラーの原因がわかったため、
SFMLに移行する理由がなくなりました

 でもこの記事は残します
数少ないSFML情報なので

minion_cs_user
1991年10月22日生まれ。男性。
http://mini09memo.blog.fc2.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした