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を使う前提でこの記事を書きます

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

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

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

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

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

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

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

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

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

``` csharp
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();
			}
		}
	}
}
```

#ビットマップ描画
``` csharp
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();
			}
		}
	}
}

``` 

#テキスト描画

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

``` csharp
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();
			}
		}
	}
}
```

#テキスト描画（もう一つの方法）

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

``` csharp
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の２つが使えることを確認

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

``` csharp
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にするサンプルです

``` csharp
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を使って描画するのと
自前で描画するのとでは処理速度にあまり差がありませんでした

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