LoginSignup
0
0

FreeTypeSharpを使った文字列描画(MonoGame用ラッパー)

Last updated at Posted at 2024-05-20

1.前回の内容

2.目標

 日本語文字列をMonoGameのTexture2Dへ変換してDrawメソッドで描画する。

3.ソース

4.説明

4.1.前回部分の変更点

4.1.1.static変数をメンバ変数へ変更

 ImageFontの一部のstatic変数をメンバ変数に変更しました。
 これにより、1種類のフォント、サイズしか使えなかった問題を解決しました。

ImageFont (before)
        private static FreeTypeLibrary _Library;
        private static FreeTypeFaceFacade _FaceFacade;
        private static float _Size;
ImageFont (after)
        private static FreeTypeLibrary FTL_Library;
        private const int INT_FontSize = 12;

        private FreeTypeFaceFacade _FaceFacade;
        private float _Size;

4.1.2.その他細かな修正

 無駄な処理とかを消したり書き直しました。

4.2. 追加したクラス

4,2,1. TextTextureクラス

 Texture2Dを管理するクラス。Texture2Dは継承してないので名付けは失敗かも。
 GetTextureメソッドでTexture2Dを取得して描画する。
 Texture2Dオブジェクトは不要になったらDisposeが必要だが、不要になるタイミングがちょっと掴みにくい&描画毎に生成するのって違うよねっていうところからTextTextureは管理クラスという扱いになりました。
 その代わり、文字列の変更も受け付けられるようになったし良いよね…?

TextTexture (GetTexture()メソッド周り)
        public Texture2D GetTexture()
        {
            if (string.Equals(_ImageData.Value, Text) == false || Color.Equals(_PrevColor, Color) == false || _PrevViewLength != _ViewLength)
            {
                if (string.Equals(_ImageData.Value, Text) == false)
                {
                    _ImageData = _Font.Render(Text);
                }

                _Texture?.Dispose();
                _Texture = CreateTexture2D(_Device, _ImageData.ToImageData(0, _ViewLength), Color);
                _PrevColor = Color;
                _PrevViewLength = _ViewLength;
            }

            return _Texture;
        }
        public static Texture2D CreateTexture2D(GraphicsDevice device, ImageData image, Color color)
        {
            var texture = new Texture2D(device, image.Width, image.Height);
            var array = new Color[image.Width * image.Height];

            var clrTable = new Dictionary<int, Color>(256);
            for (int i = 0; i < array.Length; i++)
            {
                var alpha = image.Datas[i];
                if (alpha == 0) continue;

                Color clr;

                if (clrTable.TryGetValue(alpha, out clr) == false)
                {
                    //clr = new Color(color, color.A * alpha / 255);
                    clr = Color.Multiply(color, alpha / 255f);
                    clrTable[alpha] = clr;
                }

                array[i] = clr;
            }

            texture.SetData(array);
            return texture;
        }

 ちょっと納得いかないのがColorの扱い
 前回の呼出メソッドでは"System.Drawing.Color"構造体、今回は"Microsoft.Xna.Framework.Color"構造体なので違うのが当然と言えばそうなのだが、なにか納得いかない。

 以下が2つの比較である。

System.Drawing.Color構造体の場合
            var baseColor = Color.DarkGreen;
            var image = new Bitmap(data.Width, data.Height);

            for (int i = 0; i < data.Width; i++)
            {
                for (int j = 0; j < data.Height; j++)
                {
                    var tmp = data.GetData(i, j);
                    if (tmp == 0) continue;
                    image.SetPixel(i, j, Color.FromArgb(tmp, baseColor));
                }
            }

 テストコードなので実行速度とかは気にしないで欲しい。
 それよりも、System.Drawing.ColorではColor.FromArgb(int, color)を呼んでいるところに注目して欲しい。
 ここではベースカラーに対してAlpha値を変更する方法で色を決定している。

Microsoft.Xna.Framework.Color構造体の場合
            var clrTable = new Dictionary<int, Color>(256);
            for (int i = 0; i < array.Length; i++)
            {
                var alpha = image.Datas[i];
                if (alpha == 0) continue;

                Color clr;

                if (clrTable.TryGetValue(alpha, out clr) == false)
                {
                    //clr = new Color(color, color.A * alpha / 255);
                    clr = Color.Multiply(color, alpha / 255f);
                    clrTable[alpha] = clr;
                }

                array[i] = clr;
            }

 そしてこちらはMicrosoft.Xna.Framework.Colorの場合だ。
 Color.Multiply(Color, int)を呼んでいる。そう乗算しているのだ!!
 これには理由があって、比較的System.Drawing.Colorに近いnew Color(Color, int)との実行結果の比較画像が以下のとおりです。
 ■new Color(Color, int)の場合
TestApp2_0.png
 ■Color.Multiply(Color, int)の場合
TestApp2_1.png

………なんでこうなるんでしょうね?
MonoGameさんは何か余計なことしてるのかな?調べてないのでわからないよ。

まぁとにかく挙動が違うので注意が必要。

4.2.2. TextTextureFactoryクラス

 TextTextureを作成するクラス
 Textureを生成するためにGraphicsDeviceが必要だったり、ImageFontの生成を一元化するために作成。

5.呼び出し側ソース

Game1
        TextTexture textTexture; // メンバ変数

        // (………省略)

        protected override void LoadContent()
        {
            _spriteBatch = new SpriteBatch(GraphicsDevice);

            // TODO: use this.Content to load your game content here

            TextTextureFactory.Parent = this;
            TextTextureFactory.LoadFont("Default", @"JF-Dot-MPlusH12.ttf", 24);
            textTexture = TextTextureFactory.Create("Default", "日本語でおk", Color.Violet);
        }
        
        // (………省略)

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            // TODO: Add your drawing code here
            _spriteBatch.Begin();

            _spriteBatch.Draw(textTexture.GetTexture(), new Vector2(10, 40), Color.White);
            _spriteBatch.End();

            base.Draw(gameTime);
        }

6. 終わりに

 とりあえず日本語表示できるようになったのでよかったですね。
 これでやっとゲーム作成の第一歩…

7. 続き

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0