C#
SFML

SFML.netを使ってみた

最初に

 僕はここ最近まで、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情報なので