Edited at

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情報なので