摘要:
游戏中开发不同于一般应用程序的开发,它更注重于界面美观,我们需要在游戏界面设计中花费大量的时间以便使它看起来更炫、更酷,当然这其中就少不了游戏中的字符文本,那么如何制作出漂亮的游戏文本呢?今天我们就一起来看一下。
内容:
在XNA中2D文本的绘制方式种类比较多,这有助于我们制作出更美观的文本效果,下面我就逐一来看一下。
一、SpriteFont
这种方式在XNA游戏开发中应该算是最基本的一种形式,使用方法就是在游戏对应的Content项目中添加SpriteFont文件(右键Add—New Item—Sprite Font)。之后你会看到生成了一个XML格式的文件:
View Code 在这个文件中定义了你使用的字体类型、字体大小、字符间距等信息。值得一提的是上面的字体区间,它的意思是指你在游戏中使用的的字符范围,从上面的值可以看出是ASCII的32-126,具体对应字符如下:
ASCII码 | 字符 | ASCII码 | 字符 | ASCII码 | 字符 | ASCII码 | 字符 | ASCII码 | 字符 | ASCII码 | 字符 | ASCII码 | 字符 | ASCII码 | 字符 |
32 | [空格] | 33 | ! | 34 | " | 35 | # | 36 | $ | 37 | % | 38 | & | 39 | ' |
40 | ( | 41 | ) | 42 | * | 43 | + | 44 | , | 45 | - | 46 | . | 47 | / |
48 | 0 | 49 | 1 | 50 | 2 | 51 | 3 | 52 | 4 | 53 | 5 | 54 | 6 | 55 | 7 |
56 | 8 | 57 | 9 | 58 | : | 59 | ; | 60 | < | 61 | = | 62 | > | 63 | ? |
64 | @ | 65 | A | 66 | B | 67 | C | 68 | D | 69 | E | 70 | F | 71 | G |
72 | H | 73 | I | 74 | J | 75 | K | 76 | L | 77 | M | 78 | N | 79 | O |
80 | P | 81 | Q | 82 | R | 83 | S | 84 | T | 85 | U | 86 | V | 87 | W |
88 | X | 89 | Y | 90 | Z | 91 | [ | 92 | \ | 93 | ] | 94 | ^ | 95 | _ |
96 | ` | 97 | a | 98 | b | 99 | c | 100 | d | 101 | e | 102 | f | 103 | g |
104 | h | 105 | i | 106 | j | 107 | k | 108 | l | 109 | m | 110 | n | 111 | o |
112 | p | 113 | q | 114 | r | 115 | s | 116 | t | 117 | u | 118 | v | 119 | w |
120 | x | 121 | y | 122 | z | 123 | { | 124 | | | 125 | } | 126 | ~ | | |
当然它几乎涵盖了所有常用英文字符。之所以要定义这个区间主要是为了减少游戏资源,毕竟在一个游戏中并不是所有的字符我们都要用到。到这里可能会有朋友问,既然如此我要是使用中文怎么办,这个区间肯定不够啊,中文有两万多个字符,定义这个区间的意义也不大啊?
事实上我们如果需要用到中文字符的话一般并不是修改这个区间(当然修改它是可以做到的,但是占用资源十分大,毕竟字符太过了),而是通过Font Description Processor来处理。具体做法就是:准备一个txt文件,其中存放我们游戏中要用到的中文字符,例如我们建立一个FontDescription.txt文件,里面写上"中文字体"四个字存放到游戏的Content项目中;接着在解决方案中添加一个Content Pipeline Extension Library(4.0)类型的项目,然后编写一个类继承于FontDescriptionProcesor,重写Process方法。在这个方法中我们读取外部的一个txt文件,当然你也可以直接写到代码中或存储到其他位置,然后将txt文件中的字符(当然我们这里是中文字符了)读取到FontDescription中,具体代码如下:
using System; using System.Collections.Generic; using System.Linq; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Content.Pipeline; using Microsoft.Xna.Framework.Content.Pipeline.Graphics; using Microsoft.Xna.Framework.Content.Pipeline.Processors; using System.IO; using System.Text; using System.ComponentModel; namespace ContentPipelineExtensionDemo { [ContentProcessor(DisplayName = " ContentPipelineExtensionDemo.MyContentProcessor " )] // 这个名字将用于SpriteFont的Content Processor属性 public class MyContentProcessor : FontDescriptionProcessor { private string fDescription = @" ../XNAGameFontContent/Fonts/FontDescription.txt " ; // 注意这里的路径,因为FontDescription.txt文件在XNAGameFontContent项目的Fonts文件夹中 public override SpriteFontContent Process(FontDescription input, ContentProcessorContext context) { string path = Path.GetFullPath(fDescription); context.AddDependency(path); string content = File.ReadAllText(path,Encoding.UTF8); // FontDescription.txt文件必须保存成utf-8格式,此处也需使用utf-8读取 foreach ( char c in content) // 读取文件中字符,存放到FontDescription中 { input.Characters.Add(c); } return base .Process(input, context); } } } 做完上面两步之后,此时文件目录结构如图:
这里需要注意两点:FontDescription.txt文件必须保存成utf-8;由于文件在Content项目中默认XNAGameFontContent中的文件都需要进行编译,编译时会自动检测里面的文件类型,而txt文件不属于这其中任何类型,因此我们需要修改它的BuildAction属性为None。接下来我们在XNAGameFontContent下面中添加对ContentPipelineExtensionDemo生成的dll文件的引用,然后在XNAGameFontContent项目上右键选择Project Dependencies,弹出如下图窗口:
在Project项中选择XNAGameFont,Depends on中勾选ContentPipelineExtensionDemo,确定,以此添加游戏项目对内容管道扩展的依赖(这样一来就可以修改SpriteFont文件的Processor为我们自定义的扩展内容)。最后我们在XNAGameFontContent项目中添加SpriteFontForChinese.spritefont文件(上面txt中只是定义了中文字符的范围,在这里可以指定字体类型、字体大小等信息),文件内容如下:
<? xml version="1.0" encoding="utf-8" ?> < XnaContent xmlns:Graphics ="Microsoft.Xna.Framework.Content.Pipeline.Graphics" > < Asset Type ="Graphics:FontDescription" > < FontName > 华文行楷 </ FontName > < Size > 30 </ Size > < Spacing > 0 </ Spacing > < UseKerning > true </ UseKerning > < Style > Regular </ Style > < CharacterRegions > < CharacterRegion > < Start > </ Start > < End > ~ </ End > </ CharacterRegion > </ CharacterRegions > </ Asset > </ XnaContent > 修改文件的Content Processor属性为我们自定义的ContentPipelineExtensionDemo.MyContentProcessor(这就是上面添加依赖关系的原因),然后我们就可以在游戏中使用我们文本中的中文汉字了。具体使用时的代码如下:
using System; using System.Collections.Generic; using System.Linq; 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.Input.Touch; using Microsoft.Xna.Framework.Media; namespace XNAGameFont { public class MyGame : Microsoft.Xna.Framework.Game { GraphicsDeviceManager graphics; SpriteBatch spriteBatch; SpriteFont sf1; // SpriteFont文本 Vector2 sfPosition; // SpritFont文本所在位置 SpriteFont sfChinese; // SpriteFont中文文本 Vector2 sfChinesePosition; // SpriteFont中文文本的显示位置 public MyGame() { graphics = new GraphicsDeviceManager( this ); Content.RootDirectory = " Content " ; graphics.PreferredBackBufferWidth = 480 ; graphics.PreferredBackBufferHeight = 800 ; TargetElapsedTime = TimeSpan.FromTicks( 333333 ); } protected override void Initialize() { sfPosition = new Vector2( 130 , 200 ); sfChinesePosition = new Vector2( 160 , 400 ); base .Initialize(); } protected override void LoadContent() { spriteBatch = new SpriteBatch(GraphicsDevice); sf1 = Content.Load < SpriteFont > ( @" Fonts/SpriteFont " ); sfChinese = Content.Load < SpriteFont > ( @" Fonts/SpriteFontForChinese " ); } protected override void UnloadContent() { } protected override void Update(GameTime gameTime) { if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this .Exit(); base .Update(gameTime); } protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.CornflowerBlue); spriteBatch.Begin(); spriteBatch.DrawString(sf1, " SpriteFont " , sfPosition, Color.White); spriteBatch.DrawString(sfChinese, " 中文字体 " , sfChinesePosition, Color.Red); spriteBatch.End(); base .Draw(gameTime); } } } 运行效果如图:
二、SpriteFontTexture
由于SpriteFont文本对于显示效果的调整很有限,因此XNA又对其进行了扩充。SpriteFontTexture事实上是以图片来作为XNA的字符集,我们实现只要制作好相关字符的图片,然后像使用SpriteFont一样使用就可以了。当然使用起来很简单,主要问题就是如何来制作图片字库了。这个不用担心,很多牛人早已经想过这类问题了,下面我们看几种这类工具:
2.1 ttf2bmp
是一个制作字符库的简单工具,并且它是开源的,有了它我们就可以轻松制作图片字符库了。工具如下图:
我们将需要的字符编码范围确定下来,在Min char中输入最小字符编码,在Max char中输入最大字符编码,然后点击Export就可以生成类似于下面的图片:
将上图添加到XNAGameFontContent下面的Fonts文件夹中,接下来就可以写代码使用了,当然这时我们的字符图片在XNAGameFontContent下面中默认的是Textrue图片类型,还需要修改图片的Processor属性为Sprite Font Texture - XNA Framework。具体使用代码如下:
using System; using System.Collections.Generic; using System.Linq; 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.Input.Touch; using Microsoft.Xna.Framework.Media; namespace XNAGameFont { public class MyGame : Microsoft.Xna.Framework.Game { GraphicsDeviceManager graphics; SpriteBatch spriteBatch; SpriteFont sfTexture; // Sprite Font Texture文本(ttf2bmp制作) Vector2 sfTexturePosition; // Sprite Font Texture文本位置 public MyGame() { graphics = new GraphicsDeviceManager( this ); Content.RootDirectory = " Content " ; graphics.PreferredBackBufferWidth = 480 ; graphics.PreferredBackBufferHeight = 800 ; TargetElapsedTime = TimeSpan.FromTicks( 333333 ); } protected override void Initialize() { sfTexturePosition = new Vector2( 60 , 350 ); base .Initialize(); } protected override void LoadContent() { spriteBatch = new SpriteBatch(GraphicsDevice); sfTexture = Content.Load < SpriteFont > ( @" Fonts/SpriteFontTexture " ); } protected override void UnloadContent() { } protected override void Update(GameTime gameTime) { if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this .Exit(); base .Update(gameTime); } protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.CornflowerBlue); spriteBatch.Begin(); spriteBatch.DrawString(sfTexture, " Sprite Font Texture " , sfTexturePosition, Color.Yellow); spriteBatch.End(); base .Draw(gameTime); } } } 运行效果如图:
2.2 SpriteFont2
准确的来说应该是对ttf2bmp的扩展,默认的ttf2bmp生成的字体效果比较单一(当然也可以利用一些图形处理工具(如:Photoshop)来对生成的图片进行处理),SpriteFont2则可以制作出更炫的效果,例如字体填充色、边框、投影、发光等。下面是工具的截图:
使用这个工具我们制作下面一张Sprite Font Texture图片:
然后添加到XNAGameFontContent项目的Fonts文件夹,当然别忘了修改片的Processor属性为Sprite Font Texture - XNA Framework。然后再就可以在程序中使用我们制作的字体:
using System; using System.Collections.Generic; using System.Linq; 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.Input.Touch; using Microsoft.Xna.Framework.Media; namespace XNAGameFont { public class MyGame : Microsoft.Xna.Framework.Game { GraphicsDeviceManager graphics; SpriteBatch spriteBatch; SpriteFont sfTextureExtend; // Sprite Font Texture文本效果扩展(spritefont2制作) Vector2 sfTextureExtendPosition; // Sprite Font Texture Extend文本位置 public MyGame() { graphics = new GraphicsDeviceManager( this ); Content.RootDirectory = " Content " ; graphics.PreferredBackBufferWidth = 480 ; graphics.PreferredBackBufferHeight = 800 ; TargetElapsedTime = TimeSpan.FromTicks( 333333 ); } protected override void Initialize() { sfTextureExtendPosition = new Vector2( 100 , 330 ); base .Initialize(); } protected override void LoadContent() { spriteBatch = new SpriteBatch(GraphicsDevice); sfTextureExtend = Content.Load < SpriteFont > ( @" Fonts/SpriteFontTexture2 " ); } protected override void UnloadContent() { } protected override void Update(GameTime gameTime) { if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this .Exit(); base .Update(gameTime); } protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.CornflowerBlue); spriteBatch.Begin(); spriteBatch.DrawString(sfTextureExtend, " SFT 2 " , sfTextureExtendPosition, Color.White); spriteBatch.End(); base .Draw(gameTime); } } } 运行效果:
2.3 XNA Bitmap Font Plus
不是一个直接按照字符编码范围生成相关字体图片的工具,它的字体来源于图片或者剪贴板,这里我们主要说如何从剪贴板加载字体,首先我们在PowerPoint中按顺序编辑好我们常用的字符,就是ASCII从32-126之间的字符:
! " #$%&'()*+,-./ 0123456789 :; <=>? @ABCDEFGHIJKLMNO PQRSTUVWXYZ[\] ^ _ `abcdefghIjklmno pqrstuvwxyz{ | } ~ 然后调整字符的样式,修改字符间距:
接着 ctrl+c复制,此时字符信息复制到了剪贴板,打开XNA Bitmap Font Plus,它会自动加载剪贴板的内容,如下图:
当然此时我们需要调整字符行数、偏移量和间隔等信息,点击Generate Bitmap Font按钮查看效果是否符合我们的要求:
当调整好之后,我们就可以点击Save Image As…按钮来保存生成的图片:
最后将图片添加到XNAGameFontContent项目的Fonts文件夹,修改图片的Processor属性为Sprite Font Texture - XNA Framework。就可以在程序中使用我们制作的字体:
using System; using System.Collections.Generic; using System.Linq; 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.Input.Touch; using Microsoft.Xna.Framework.Media; namespace XNAGameFont { public class MyGame : Microsoft.Xna.Framework.Game { GraphicsDeviceManager graphics; SpriteBatch spriteBatch; SpriteFont sfTexturePlus; // Sprite Font Texture文本(xna bitmap font plus制作) Vector2 sfTexturePlusPosition; // Sprite Font Texture 文本位置 public MyGame() { graphics = new GraphicsDeviceManager( this ); Content.RootDirectory = " Content " ; graphics.PreferredBackBufferWidth = 480 ; graphics.PreferredBackBufferHeight = 800 ; TargetElapsedTime = TimeSpan.FromTicks( 333333 ); } protected override void Initialize() { sfTexturePlusPosition = new Vector2( 40 , 350 ); base .Initialize(); } protected override void LoadContent() { spriteBatch = new SpriteBatch(GraphicsDevice); sfTexturePlus = Content.Load < SpriteFont > ( @" Fonts/SpriteFontTexture3 " ); } protected override void UnloadContent() { } protected override void Update(GameTime gameTime) { if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this .Exit(); base .Update(gameTime); } protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.CornflowerBlue); spriteBatch.Begin(); spriteBatch.DrawString(sfTexturePlus, " Sprite Font Texture 3 " , sfTexturePlusPosition, Color.White); spriteBatch.End(); base .Draw(gameTime); } } } 运行效果:
下面给出所有以上这几种字体综合到一起的代码:
using System; using System.Collections.Generic; using System.Linq; 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.Input.Touch; using Microsoft.Xna.Framework.Media; namespace XNAGameFont { public class MyGame : Microsoft.Xna.Framework.Game { GraphicsDeviceManager graphics; SpriteBatch spriteBatch; SpriteFont sf1; // SpriteFont文本 Vector2 sfPosition; // SpritFont文本所在位置 SpriteFont sfChinese; // SpriteFont中文文本 Vector2 sfChinesePosition; // SpriteFont中文文本的显示位置 SpriteFont sfTexture; // Sprite Font Texture文本(ttf2bmp制作) Vector2 sfTexturePosition; // Sprite Font Texture文本位置 SpriteFont sfTextureExtend; // Sprite Font Texture文本效果扩展(spritefont2制作) Vector2 sfTextureExtendPosition; // Sprite Font Texture Extend文本位置 SpriteFont sfTexturePlus; // Sprite Font Texture文本(xna bitmap font plus制作) Vector2 sfTexturePlusPosition; // Sprite Font Texture 文本位置 public MyGame() { graphics = new GraphicsDeviceManager( this ); Content.RootDirectory = " Content " ; graphics.PreferredBackBufferWidth = 480 ; graphics.PreferredBackBufferHeight = 800 ; TargetElapsedTime = TimeSpan.FromTicks( 333333 ); } protected override void Initialize() { sfPosition = Vector2.Zero; sfChinesePosition = new Vector2( 0 , 100 ); sfTexturePosition = new Vector2( 0 , 200 ); sfTextureExtendPosition = new Vector2( 0 , 300 ); sfTexturePlusPosition = new Vector2( 0 , 400 ); base .Initialize(); } protected override void LoadContent() { spriteBatch = new SpriteBatch(GraphicsDevice); sf1 = Content.Load < SpriteFont > ( @" Fonts/SpriteFont " ); sfChinese = Content.Load < SpriteFont > ( @" Fonts/SpriteFontForChinese " ); sfTexture = Content.Load < SpriteFont > ( @" Fonts/SpriteFontTexture " ); sfTextureExtend = Content.Load < SpriteFont > ( @" Fonts/SpriteFontTexture2 " ); sfTexturePlus = Content.Load < SpriteFont > ( @" Fonts/SpriteFontTexture3 " ); } protected override void UnloadContent() { } protected override void Update(GameTime gameTime) { if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this .Exit(); base .Update(gameTime); } protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.CornflowerBlue); spriteBatch.Begin(); spriteBatch.DrawString(sf1, " SpriteFont " , sfPosition, Color.White); spriteBatch.DrawString(sfChinese, " 中文字体 " , sfChinesePosition, Color.Red); spriteBatch.DrawString(sfTexture, " Sprite Font Texture " , sfTexturePosition, Color.Yellow); spriteBatch.DrawString(sfTextureExtend, " SFT 2 " , sfTextureExtendPosition, Color.White); spriteBatch.DrawString(sfTexturePlus, " Sprite Font Texture 3 " , sfTexturePlusPosition, Color.White); spriteBatch.End(); base .Draw(gameTime); } } } 最终效果:
OK,就到这里吧!
最后附上源代码(上面提到的几个工具,都可以点击相关链接下载):