使用 XNA 创建游戏/编程/框架
框架的数量和失败的游戏开发者一样多。每次有人无法完成他们的游戏,或者游戏结果证明是失败的时候,开发者就会将剩余的源代码变成一个“框架”。幸运的是,有一些实际上有用的框架,在这章中,我们想向您展示一些可以轻松快速地用于创建出色游戏的框架。您应该担心的一件事是框架发布的许可证。
LTrees 允许您创建随机生成的树木,包括树干、树枝和树叶。它还具有树木的迎风动画。有几种不同的树木可用,例如桦树、松树和柳树。您可以在右侧看到一个示例。
将 LTrees 添加到您的项目需要一些工作,但是添加一些具有预定义迎风动画的简单树木的代码非常简短。
首先,您需要 LTrees 源代码。您可以从 这里 下载它们。解压缩文件,并将项目 “LTreesLibrary” 和 “LTreesPipeline” 添加到您的解决方案中(见下文)。接下来的步骤都在 Visual Studio 中的 *解决方案资源管理器* 中进行。右键单击您的解决方案,然后选择 *添加 -> 现有项目*。现在,导航到解压缩的项目,并选择相应的 *.csproj 文件以将其添加到您的项目中。您可能需要重新生成您的解决方案(CTRL+Shift+B)。然后,您必须将引用添加到您的主游戏项目中。右键单击 *引用 -> 添加引用*。选择 *项目*,然后添加 LTreesLibrary。现在,展开 *内容* 项目,右键单击 *引用* 子菜单,并添加 LTreesPipeline 引用(与上面相同的步骤)。LTreesLibrary 的引用现在应该位于项目主文件夹中的 *引用* 部分,而 LTreesPipeline 的引用应该位于项目 *内容* 子文件夹的 *引用* 部分。现在,您需要将树模型和纹理添加到您的项目中。只需在资源管理器中打开从下载的源代码包中解压缩的 LTreeDemo 项目(此时是普通的 Windows 资源管理器),导航到 *内容* 子文件夹,然后将 *字体*、*纹理* 和 *树* 文件夹拖放到 Visual Studio 中解决方案资源管理器中游戏项目的 *内容* 文件夹中。
现在我们可以继续进行相关代码。以下示例部分取自源代码包中提供的 LTrees 演示应用程序 [1]。首先要添加的是 LTrees 库
using LTreesLibrary.Pipeline;
using LTreesLibrary.Trees;
using LTreesLibrary.Trees.Wind;
我们需要一些全局变量来加载和创建树木和动画。配置文件变量包含有关不同树木的信息。我们还需要一个 TreeLineMesh、一些 SimpleTree 对象、一个 WindStrengthSin(这定义了迎风动画的模式)和一个 TreeWindAnimator 对象。
public class MyGame : Microsoft.Xna.Framework.Game
{
//...
String profileAssetFormat = "Trees/{0}";
String[] profileNames = new String[]
{
"Birch",
"Pine",
"Gardenwood",
"Graywood",
"Rug",
"Willow",
};
TreeProfile[] profiles;
TreeLineMesh linemesh;
int currentTree = 0;
SimpleTree tree, tree2, tree3;
WindStrengthSin wind;
TreeWindAnimator animator;
//...
}
需要两个新方法。LoadTreeGenerators() 将有关树木的信息加载到内容管理器中,NewTree() 生成一棵简单的树木,包括树干、树枝和树叶。
void LoadTreeGenerators()
{
profiles = new TreeProfile[profileNames.Length];
for (int i = 0; i < profiles.Length; i++)
{
profiles[i] = Content.Load<TreeProfile>(String.Format(profileAssetFormat, profileNames[i]));
}
}
void NewTree()
{
// Generates a new tree using the currently selected tree profile
// We call TreeProfile.GenerateSimpleTree() which does three things for us:
// 1. Generates a tree skeleton
// 2. Creates a mesh for the branches
// 3. Creates a particle cloud (TreeLeafCloud) for the leaves
// The line mesh is just for testing and debugging
//Each tree was loaded into the profiles[]-filed and can be accessed with the numbers 0 to 5. They are chosen randomly here.
Random num = new Random();
tree = profiles[num.Next(0, 5)].GenerateSimpleTree();
tree2 = profiles[num.Next(0, 5)].GenerateSimpleTree();
tree3 = profiles[num.Next(0, 5)].GenerateSimpleTree();
linemesh = new TreeLineMesh(GraphicsDevice, tree.Skeleton);
}
上面的方法在 LoadContent() 方法中调用。此外,还会加载迎风动画。
protected override void LoadContent()
{
// ...
wind = new WindStrengthSin();
animator = new TreeWindAnimator(wind);
LoadTreeGenerators();
NewTree();
// ...
}
最后,需要绘制树木。这发生在 Draw(GameTime) 方法中。树木需要适当缩放和平移。此外,我们需要一个 StateBlock 来捕获并重新应用渲染状态,因为 LTrees 不会为我们这样做。如果您省略了它,您很可能会遇到图形故障。
protected override void Draw(GameTime gameTime)
{
//..
Matrix world = Matrix.Identity;
Matrix scale = Matrix.CreateScale(0.0015f);
Matrix translation = Matrix.CreateTranslation(3.0f, 0.0f, 0.0f);
Matrix translation2 = Matrix.CreateTranslation(-3.0f, 0.0f, 0.0f);
StateBlock sb = new StateBlock(GraphicsDevice);
sb.Capture();
tree.DrawTrunk(world * scale, cam.viewMatrix, cam.projectionMatrix);
tree.DrawLeaves(world * scale, cam.viewMatrix, cam.projectionMatrix);
animator.Animate(tree.Skeleton, tree.AnimationState, gameTime);
sb.Apply();
sb.Capture();
tree2.DrawTrunk(world * scale * translation, cam.viewMatrix, cam.projectionMatrix);
tree2.DrawLeaves(world * scale * translation, cam.viewMatrix, cam.projectionMatrix);
animator.Animate(tree2.Skeleton, tree2.AnimationState, gameTime);
sb.Apply();
sb.Capture();
tree3.DrawTrunk(world * scale * translation2, cam.viewMatrix, cam.projectionMatrix);
tree3.DrawLeaves(world * scale * translation2, cam.viewMatrix, cam.projectionMatrix);
animator.Animate(tree3.Skeleton, tree3.AnimationState, gameTime);
sb.Apply();
//..
}
现在编译并启动您的项目,享受在风中摇曳的树木吧!
Nuclex 是一个框架,它实际上包含多个功能。它专门为 XNA 和其他用 .NET 编写的平台构建。Nuclex 的优势在于不同可用模块的独立性。模块只是指组件,例如 3D 文本渲染或游戏状态管理器。它们可以互换,也可以调整。程序员可以混合它们,只取其中一些元素。事实上,大多数模块对于游戏来说都是必不可少的,因此,只使用一个组件可能已经可以帮助缩短完成时间,或者专注于游戏的其他部分。这些组件是程序员 “不重复造轮子” 的有效帮助,并提供了一种可以稍后自定义的解决方案。如果一个游戏应该包含 GUI、GamePad 输入、矢量字体或其他与游戏相关的功能 - Nuclex 框架是寻找解决方案的正确地方。
有趣的是,Nuclex 框架是微软创建的开源社区 www.codeplex.com 的一部分。即使代码和组件不是微软的财产。
所有类和库都使用完整的测试覆盖率进行编码,其中包括对垃圾回收器或内存管理的测试。正如该项目所说,所有类都具有 Nuclex 是开源的,因此它可以用于任何类型的项目。使用条款明确说明,这些库可以在任何游戏中实现,只要它对其他用户保持开放。此外,每个游戏创建者都欢迎加入平台,与其他 Nuclex 编码者合作。注册一个帐户并成为社区的一部分非常简单。根据 Nuclex 社区的意见,使用框架组件的唯一要求是对编程语言有充分的理解。除此之外,以下所有组件都可以使程序员的生活更愉快。 [2]
Nuclex 框架的功能
- 3D 文本渲染
- 任意原始批处理
- 自动顶点声明
- 特殊集合
- 文本输入和标准 PC 游戏手柄支持
- 核心仿射线程池
- 调试叠加
- 游戏状态管理
- LZMA 内容压缩
- 多线程粒子系统
- 矩形填充
- 带蒙皮的图形用户界面
有关每个模块的更多信息可以在 http://nuclexframework.codeplex.com/ 上找到。由于框架中有很多有用的类,它们的操作可以在网页上轻松跟踪,因此本文只介绍一些解决方案。在接下来的部分中,将解释 Nuclex 的三个主要组件。
框架的组装看起来很复杂,但它实际上只是不同库的集合,这些库可以单独使用。框架的核心包含用于数学、网络和 Windows 窗体的基本类。
Nuclex 框架中最棒的组件之一是矢量字体创建。它实际上从 .ttf 文件中获取字符,并对每个字符的边缘进行插值。插值完成后,所有信息都存储在一个 .xnb 文件中,然后可以由 Nuclex.Fonts 库使用。即使在尺寸很小的情况下,文本看起来也不如大型花哨的标题好,但它是一个很棒的功能。
这些字体可以在 PC 或 Xbox 上无缝使用,甚至比 XNA 中的 SpriteBatch 类更快。
有三种显示字体的方式。一种是带轮廓的文本。它基本上从字体中获取字母,并计算每个字符的边缘以进行描边查看。显示矢量字体的另一种方式是填充方式。技术与之前相同,但它填充了字符。最后,可以使用字符的挤出版本。
首先,重要的是导入 Nuclex.Fonts 和 Nuclex.Graphics,并提供一个加载 ttf 的 VectorFont 对象。在 LoadContent() 方法中,您现在可以加载字体
using Nuclex.Fonts;
using Nuclex.Graphics;
private VectorFont arialVectorFont;
private Text helloWorldText;
protected override void LoadContent() {
this.arialVectorFont = this.content.Load<VectorFont>("Content/Fonts/Arial");
this.helloWorldText = this.arial24vector.Extrude("Hello IMIs!");
//.....
除了 VectorFont 之外,我们还需要一个类似于 SpriteBatch 的类,名为 TextBatch。使用它的实例,我们实际上可以绘制文本。我们仍然在 LoadContent() 方法中。
///....
this.spriteBatch = new SpriteBatch(this.graphics.GraphicsDevice);
this.textBatch = new TextBatch(this.graphics.GraphicsDevice);
}
private TextBatch textBatch;
最后,我们需要将所有部分连接在一起并绘制文本。当然,选择不同的填充类型会产生不同的结果。
///....
this.textBatch.ViewProjection = this.camera.View * this.camera.Projection;
this.textBatch.Begin();
this.textBatch.DrawText(
this.helloWorldText, // text mesh to render
textTransform, // transformation matrix (scale + position)
Color.White // text color
);
this.textBatch.End();
Nuclex 的这一部分是一个库,它提供了所有用于游戏或应用程序的交互式视觉界面的工具。无论如何,图形对象都可以通过缩放或定位进行调整,它们易于控制(例如状态更改),并且渲染系统没有连接。因此,不会对游戏造成干扰。
- 直观且简单的设计
- 跨平台工作(Xbox 360 和 Windows)
- 特殊的控制台 UI 控件
- 支持不同的键盘布局
- 统一缩放
- 与渲染器无关的设计
- 在默认渲染器中的蒙皮(使用 XML 文件蒙皮元素)
- 完全测试覆盖率
用于在游戏中快速轻松地创建 GUI 的组件。它不是用于复杂设置的 GUI 管理器,而是涵盖了典型游戏 GUI 的所有方面。它会根据屏幕自动更改大小,并提供默认视图/蒙皮,除非选择自定义规范。
就像在任何其他 GUI 框架中一样,您可以创建按钮、窗口以及几乎所有其他现代界面功能。
在真正开始之前,我们需要一个基本的界面。这个界面需要非常直观,可以通过 Screen 类来创建。创建一个实例并将其添加到 GuiManager 对象中。GuiManager 负责窗口,因此您需要在前端创建它,可能是在类的构造函数中。
然后,正如之前所述,您可以添加 Screen 对象,并为真正的界面工作做好准备。注意:Viewport 用于将窗口调整到合适的大小。
最后几行确定了窗口的边界。如果您省略它们,窗口仍然会显示,但外观不会很好。
this.graphics = new GraphicsDeviceManager(this);
this.input = new InputManager(Services, Window.Handle);
this.gui = new GuiManager(Services);
Viewport viewport = GraphicsDevice.Viewport;
Screen mainScreen = new Screen(viewport.Width, viewport.Height);
this.gui.Screen = mainScreen;
mainScreen.Desktop.Bounds = new UniRectangle(
new UniScalar(0.1f, 0.0f), new UniScalar(0.1f, 0.0f), // x and y = 10%
new UniScalar(0.8f, 0.0f), new UniScalar(0.8f, 0.0f) // width and height = 80%
);
现在让我们从一个普通的按钮开始。首先,您需要一个 ButtonControl 实例,然后添加文本,最后设置边界。
ButtonControl newGameButton = new ButtonControl();
newGameButton.Text = "Neu...";
newGameButton.Bounds = new UniRectangle(
new UniScalar(1.0f, -190.0f), new UniScalar(1.0f, -32.0f), 100, 32
}
放置按钮后,我们可以为其添加一个委托,使其可点击。之后,我们需要将按钮添加到我们的 mainScreen。这与其他 GUI 管理器非常类似,因为您只需将所有对象添加到不同的组件中。在本例中,我们希望将按钮放在桌面上(基本上是屏幕的最底层)。
注意:在以下代码中,按钮的委托会打开一个新窗口。DialogWin 扩展了 WindowControl 类,稍后会详细介绍。
newGameButton.Pressed += delegate(object sender, EventArgs arguments) {
this.gui.Screen.Desktop.Children.Insert(0, new DialogWin());
};
现在我们有了按钮,并使其可点击,我们可能想要一个新窗口。我们可以通过扩展 WindowControl 类并向其添加自己的组件来轻松实现这一点。添加意味着我们将它附加到当前窗口,更确切地说是附加到它的“子级”。“子级”是一个由 WindowControl 基类默认实例化的对象。
public partial class DialogWin : WindowControl {
//Initializes a new GUI demonstration dialog
public DialogWin() {
this.nameEntryLabel.Text = "Deine Martikelnummer bitte:";
this.nameEntryLabel.Bounds = new UniRectangle(10.0f, 30.0f, 110.0f, 24.0f);
Children.Add(this.nameEntryLabel);
}
最后,我们想将 GUI 添加到我们的框架中,并使鼠标可见。使用 Nuclex 框架创建界面就是这么简单。
Components.Add(this.gui);
this.gui.DrawOrder = 1000;
IsMouseVisible = true;
顾名思义,它是一个协调不同状态的管理器。一次只能激活一个状态,但可以将一个状态放在另一个状态之上。例如,主菜单可以将正在进行的游戏放在旁边,并在退出主菜单后返回。
管理器的接口
// Manages the game states and updates the active game state</summary>
public class GameStateManager {
// Snapshot of the game's timing values
void Update(GameTime gameTime) { /* ... */ }
// Draws the active game state
void Draw(GameTime gameTime) { /* ... */ }
// Pushes the specified state onto the state stack
void Push(GameState state) { /* ... */ }
// Takes the currently active game state from the stack</summary>
void Pop() { /* ... */ }
// This replaces the running game state in the stack with the specified state.
void Switch(GameState state) { /* ... */ }
// The currently active game state. Can be null.</summary>
GameState ActiveState { get { /* ... */ } }
}
Lennart Brüggemann, mglaeser
- ↑ LTrees Demo Application, Change Set 22316. ltrees.codeplex.com,于 2011 年 5 月 28 日获取
- ↑ http://nuclexframework.codeplex.com/
- ↑ http://nuclexframework.codeplex.com/wikipage?title=Nuclex.UserInterface&referringTitle=Home
- ↑ http://nuclexframework.codeplex.com/wikipage?title=Game%20State%20Management&referringTitle=Home