您的位置:首页 > 其它

Xna支持中文显示方法归纳

2010-11-04 21:49 483 查看
Xna不同于DirectX,因其内部并未提供类似于D3DFont的机制(据说之所以会这样做,也是考虑到与Xbox360兼容的缘故),使得显示中文变得极为不便。

虽然如此,要实现Xna下的中文显示依然存在多种方法:

1.添加SpriteFont,修该他的CharacterRegion的Start和End,范围就是中文的Unicode范围。

因为通常大家不太好定位自己所需汉字的Unicode范围,而该范围又不允许无限制扩大,因此不可取~

2.GDI+

使用GDI+虽然能够满足中文显示的要求,但众所周知,GDI+本身的效率是个问题,因此,也不建议大家使用这种方法。

相关内容,请参考:http://www.cnblogs.com/XnaZero/archive/2009/03/29/1412148.html ,为XnaZero兄原创~

XNA3.0内带例子 SpriteFontX_1_0_2_1.rar

XNA3.1内带例子 SpriteFontX_1_0_2_1_For_Xna3_1.rar

XNA4.0内带例子 SpriteFontX_1_0_2_2_For_Xna4.rar

XNA支持中文输入示例 http://xna.omgsoft.com.cn/education/XNA_IME.aspx (Xna游戏世界)

作者没有开源,因此不太清楚内部机制,但个人猜想核心部分或许是这样的:

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.IO;
using AppColor = System.Drawing.Color;
using XnaColor = Microsoft.Xna.Framework.Graphics.Color;

namespace FontDemo
{
    /// <summary>          
    /// by kenkao(http://blog.csdn.net/kenkao)          
    /// </summary>          
    ///       
    public class ChFont
    {
        static Graphics graphics;
        static GraphicsDevice graphicsDevice;
        static MemoryStream stream = new MemoryStream();

        public ChFont(Game game)
        {
            graphics = Graphics.FromHwnd(game.Window.Handle);
            graphicsDevice = game.GraphicsDevice;
        }

        /// <summary>              
        /// 生成中文纹理(by gdi+)              
        /// </summary>              
        /// <param name="graphicsDevice">图形设备</param>              
        /// <param name="Text">文字</param>              
        /// <param name="font">字体</param>              
        /// <param name="FontColor">字体前景色</param>              
        /// <returns>所得纹理</returns>              
        public Texture2D CreateChFont(string Text, Font font, AppColor FontColor)
        {
            Texture2D ChTexture;
            try
            {
                //---生成字体   
                SizeF ef = graphics.MeasureString(Text, font);
                Bitmap bitmap = new Bitmap((int)ef.Width, (int)ef.Height);
                graphics = Graphics.FromImage(bitmap);
                graphics.DrawString(Text, font, new SolidBrush(FontColor), new PointF());
                bitmap.Save(stream, ImageFormat.Png); //---注意:这里使用MemoryStream作为gid+与Xna的平行接口                      
                stream.Seek(0L, SeekOrigin.Begin);
                ChTexture = Texture2D.FromFile(graphicsDevice, stream); //---不要妄图使用file,否则效率会非常令人纠结的^^                      
                return ChTexture;
            }
            catch
            {
                return null;
            }
        }
    }
}

调用方法生成中文纹理,而后使用SpriteBatch绘制即可~

3.字体纹理

相比以上两种方法而言,这种方法算是较为正统的Xna中文显示方法,借用Xna的ContentPipeline,这种方法的效率算是比较不错的。此种方法较为可取。

相关内容,请参考:http://kb.cnblogs.com/a/1459016/,为clayman兄原创~

其实要实现所谓的字体纹理,关键的核心方法有两个:

public class Texture2D : Texture
{
    public void GetData<T>(T[] data);
    public void GetData<T>(T[] data, int startIndex, int elementCount);
    public void GetData<T>(int level, Rectangle? rect, T[] data, int startIndex, int elementCount);
    public void SetData<T>(T[] data); public void SetData<T>(T[] data, int startIndex, int elementCount, SetDataOptions options);
    public void SetData<T>(int level, Rectangle? rect, T[] data, int startIndex, int elementCount, SetDataOptions options);
}


而追根究底,这两个方法实现的其实就是纹理间内存数据的拷贝而已。

整个方法原理如下:

1.准备好事先生成的“字库”(可以用Gdi+,好像还有一个开源的工具叫做ttf2bmp),其实就是带有通用中文字符的纹理。

2.程序启动时,通过内容管线,事先将其加载到内存,也就是一个Texture2D对象里。这里我们称作原始纹理。

3.需要绘制中文字符时,事先new一个空白的Texture2D。这里我们称作目标纹理。

4.根据字符的位置对应关系,从原始纹理中截取所要绘制的字符数据,赋值给目标纹理。

5.绘制目标纹理即可。

如上方法中,用到的其实也仅仅就是GetData与SetData。

其中需要注意的问题有两点:

1.生成的目标纹理,规格需要与原始纹理相同。

这里的规格,其实也就是指Texture2D构造函数中的【int numberLevels, TextureUsage usage, SurfaceFormat format】三个参数。那么如何来解决这个问题呢?其实很简单,从原始纹理中获取这三个属性,传入目标纹理的构造函数中即可。这样一来,目标纹理与原始纹理规格绝对相同。

不知是否有人要问,原始纹理的规格你如何获得?呵~ 这个你自然不用管,因为有Xna的内容管线替你完成~

2.使用GetData从原始纹理获取数据时,各个参数的确定问题。

我们获取数据,一般要用到此方法的第三个重载方法,即:

public void GetData<T>(int level, Rectangle? rect, T[] data, int startIndex, int elementCount);

T模板,不用问,当然是通用性最强的byte。

第一个参数,一般置0即可。

第二个参数,当然就是你要截取的矩形区域,你自己生成的纹理,位置的对应当然得自己掌握。

第三个参数,根据T的设定,自然是byte[]类型的数据缓冲区。需要注意,GetData对这个缓冲区的长度有着严格的要求——不能大也不能小。那我们如何来获得这个缓冲区长度呢?这里我推荐一条定律:原始纹理使用Xna中通用性最强的.png,则其被内容管道编译为.xnb且加载至Texture2D之后,刚刚好每个像素占4个字节(r,g,b,α)。于是,假定你截取的区域为50*10,则数组长度必然为50*10*4=2000.

第四个参数,数组的起始索引,为充分利用数组空间,当然置0.

第五个参数,元素个数,当然等于byte[]缓冲区的长度了。

SetData参数用法类似。

原理代码如下:

/// <summary>       
/// by kenkao(http://blog.csdn.net/kenkao)       
/// </summary>             
/// 
int StringWidth;          //字符串宽(目标纹理宽)            
int StringHeight;         //字符串高(目标纹理高)            
Texture2D SurTexture;     //原始纹理            
Texture2D DesTexture;     //目标纹理            
SurTexture = Content.Load<Texture2D>("Font");            
DesTexture = new Texture2D(graphics.GraphicsDevice, StringWidth, StringHeight, SurTexture.LevelCount, SurTexture.TextureUsage, SurTexture.Format);//生成与原始纹理相同规格的目标纹理            
byte[] data = new byte[StringWidth * StringHeight * 4];  //生成数据缓冲区            
//SurTexture.GetData获取原始数据            
//………………            
//DesTexture.SetData赋值            
//………………            
//绘制DesTexture即可


因为所有数据事先已经加载到内存之中,因此即使是即时生成新的目标纹理即时绘制,针对于人眼相对信息显示的感官要求而言,亦是绰绰有余的。

注意:提一个很外行的话题,千万不要试图从原始纹理中一个字一个字的截取出来,然后再一个字一个字的绘制上去。道理我不说,大家也应该懂。否则用上述方法就没什么意义了~

另,为进一步提高效率,我们应该避免反复的new、dispose(.net基本常识),大家可以提供一个缓存机制,即构建一个string到Texture2D的缓存映射即可。

如果你想知道有关Xna纹理字体的更多相关细节,以及如何自行生成一套Xna字体纹理,这里推荐几篇经典文章供大家参考:

>>> http://blogs.msdn.com/garykac/archive/2006/08/30/728521.aspx
>>> http://blogs.msdn.com/garykac/articles/732007.aspx
>>> http://www.angelcode.com/products/bmfont/

4.扩展content processor

此种方法是此次需要重点介绍的方法,也是笔者较为推荐的方法。

如下来自:

http://xna.omgsoft.com.cn/education/unicode_font_class.aspx (Xna游戏世界)

在XNA中显示中文字符或其它UNICODE字符(非GDI+)

XNA3.0+VS2008SP1下调试通过

由于XNA内置的DrawString方法并不能输出全角UNICODE字符,只能设定字符的起始和终止的内码,欧洲语系的字元不多,可以一次导入并生成字体,但像亚洲语系这样动辄上千的字元又是全角,好像设计者并没有考虑到这些情况。为了实现字符输出,已经有一些方法,例如利用.net中的GDI+。下面的实现方法是通过生成自定义的文字托管方式来实现的。

步骤如下:

建立字体文件

在当前项目中的“Content”中点击右键

加入新的文件,类型是"Sprite Font",起名为"DefaultFont.spritefont"

打开这个XML格式的文件,将"FontName"节中的字体改成含有目标语言字体的字体文件,这里使用“幼圆”

字典文件:

把游戏中需要的文字放到一个文本文件“message.txt”中,方便随时修改,以换行符结尾,内容如下: message.txt

中文输入测试,日本語テストを入力する



将这个文件放置在项目的Content目录下。

另存为一下这个文件,以“UTF-8”编码格式。

在解决方案浏览器中选择这个文件,在属性窗口的高级中的“生成操作”中选择“无”,“复制到输出目录”里选择“始终复制”。

文字处理类:

在解决方案浏览器中右键点击解决方案

添加新的项目,在项目类型对话框中选择"Windows Game Library(3.0)",起名"FontProcess"

在项目中增加引用,选择“Microsoft.Xna.Content.Pipeline”

将这个项目中的默认类"Class1.cs"改名为"DefaultFontProcessor.cs",修改为如下代码:

重新编译这个项目

DefaultFontProcessor.cs 

using System.IO;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline.Processors;

namespace FontProcessors
{
    [ContentProcessor]
    public class DefaultFontProcessor : FontDescriptionProcessor
    {
        public override SpriteFontContent Process(FontDescription input, ContentProcessorContext context)
        {
            //载入文件
            string fullPath = Path.GetFullPath("message.txt");
            context.AddDependency(fullPath);
            string letters = File.ReadAllText(fullPath, System.Text.Encoding.UTF8);

            //导入字符
            foreach (char c in letters)
            {
                input.Characters.Add(c);
            }
            return base.Process(input, context);
        }
    }
}



添加项目引用

在本项目的Content下的"引用"上点击右键,添加项目引用,在弹出的对话框中选择刚才建立的项目:“FontProcessors”

在解决方案管理器选择本项目中的字体文件"DefaultFont.spritefont"

在属性窗口中的Content Processor中选择我们建立的处理类:"DefaultFontProssor"

在项目用使用

在我们的项目中使用如下代码:

Game1.cs 

using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

namespace HelloWorld
{
    public class GameMain : Microsoft.Xna.Framework.Game
    {
        private GraphicsDeviceManager graphics;
        private SpriteBatch spriteBatch;
        private SpriteFont font;

        public GameMain()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        protected override void Initialize()
        {
            base.Initialize();
        }

        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
            font = Content.Load<SpriteFont>("DefaultFont");
        }

        protected override void UnloadContent()
        {
        }

        protected override void Update(GameTime gameTime)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back ==
                ButtonState.Pressed)
            {
                Exit();
            }

            base.Update(gameTime);
        }

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

            spriteBatch.Begin();
            DrawString("中文输入测试,日本語テストを入力する", 50, 50);
            spriteBatch.End();

            base.Draw(gameTime);
        }

        private void DrawString(String str, int x, int y)
        {
            spriteBatch.DrawString(font, str, new Vector2(x, y), Color.White);
        }
    }
}



或可参考:http://blog.csdn.net/sweetwxh/archive/2008/03/20/2199762.aspx

http://hi.baidu.com/wingde%BF%D5%BC%E4/blog/item/107643541e404dceb745aec9.html
http://www.cnblogs.com/aawolf/archive/2010/09/22/1833167.html
http://www.cnblogs.com/kenshincui/archive/2011/04/10/2011695.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: