Edited at

Monogameの文字描画でSharpFontを使う

More than 3 years have passed since last update.

Monogame(XNA Game Studioの有志による後継版)でSharpFont(FreeTypeのC#ラッパー)を使う方法についての記事です。SharpFontで生成した画像をMonogame用に変換して使うだけなので、単にC#でFreeTypeが使いたいというときにも参考になるかなと思います。


環境


  • Visual Studio Community 2015

  • Monogame 3.2


動機

Monogameでフォントを使って描画するための標準的な方法はスプライトフォントを生成してそれを用いることです。生成するにはスプライトフォント定義ファイル(中身はXML)に必要な文字のUnicode符号位置を並べたものを変換ソフト(私はXNA Formatterを使っています)に噛ませてやれば良いだけなので簡単といえば簡単です。日本語も使うことはできます。

が、漢字を使おうと思うと非常に面倒なことになります。というのもUnicode上に点在する必要な漢字の一覧を選び出すのも大変ですし、UnicodeコンソーシアムのサイトからJIS X 0208に対応する文字の一覧を持ってくるなどの方法でその点をクリアしたとしても、変換には異常な時間がかかります(私の環境では数時間単位でかかります)。もし必要な文字が入っていなかった場合に再度生成することなど考えたくもありません。

Monogameの最近のバージョンではコンテントパイプラインなるものでもう少しやりやすくなっているらしいのですが、Monogame 3.3以降は32bit Windowsの開発環境では動かない(!)という事情のためにそれに移行することができませんでした。そこで外部のフォントライブラリであるところのFreeType(のC#移植であるところのSharpFont)を使ってみたところ意外と簡単だったのでここに書いておきます。


SharpFontのインストール

SharpFontはNugetパッケージを提供してくれているのでインストールは簡単です。VisualStudioのNugetパッケージマネージャでインストールします。

ただ、ここでは


  • SharpFont 3.1.0

  • SharpFont Dependencies 2.6.0

を用います(SharpFont 4.0.1は挙動が良くわかりませんでした)。

普通にインストールしようとすると私の環境ではなぜかSharpFont Dependencies 2.5.5を入れようとしてエラーで落ちていましたが、先にSharpFont Dependencies 2.6.0をインストールすることで問題なくインストールすることができました。


FreeTypeが返すビットマップについて

Freetypeはフォントを扱うライブラリであり、それ以上の層のことはやってくれません。具体的に言うと、FreeTypeがやってくれるのはフォントを読み込んで、そこの指定されたグリフのビットマップを返すことだけです(もちろん他にも機能はたくさんありますが、文字を単に描画するというだけの用途ならばこれくらいです)。

しかもFreeTypeが返してくれるビットマップはDIBではなく8ビットでモノクロの256階調が記されただけの純粋なビットマップなので、使うためにはこれにヘッダを付けるなどの処理を自分でする必要があります。

……なのですが、ありがたいことにSharpFontはFTBitmap.ToGdipBitmap()という函数を用意してくれていて、System.Drawing.Bitmap(GDI+のビットマップ形式らしい)に変換することができます。

したがって、あとはこれをMonogameで読む込める形式にすれば使えることになります(再変換していて明らかに無駄なのでパフォーマンスを求めるならまともな方法を考えるべきでしょうが、これは「とりあえず描画する」程度のときに便利です)。

なお、System.Drawing以下は標準では使えないようなので参照を追加してやる必要があります。


組版

あとは組版です。自力で一から実装するのは大変ですが、これもありがたいことに、SharpFontの公式サンプルに良さそうなクラスがあるので使わせてもらうことにします。

FontFormatCollection関連でコンパイルエラーがでますが(おそらくv4で追加された機能でしょう)、不要なのでごっそり削除してしまえば使えるようになります。

ただし、このままでは文字の高さによって文字の位置が変わってしまう(上の空白が詰められる)という仕様になっていて使いづらいため一か所、RenderString(Library, Face, string, Color, Color)の中を変更します。

if (glyphTop > top)

top = glyphTop;

の部分を

top = (float)face.Size.Metrics.Ascender;

に書き換えます。

これでいい感じに使えるようになるはずです。


結論

以上で技術的なポイントは書いたはずなので、あとはゲームの設計にあわせていい感じのクラスを作ってくださいということになりますが、参考までに私が使っているクラスを置いておきます。

https://gist.github.com/suzusime/b644eedffba87001427291d79f36a955

"Game1"などのGameクラスのなかで最初に

FontTool.parent = this;

としておけば、あとは

FontTool.DrawString("持っている力を使わないのは公平か?", new Vector2(10, 10), "default", spriteBatch);

とするだけ、と標準のDrawString()と似たような感覚で文字が描画できます。