Managed DirectX 应用程序的 GUI 库






4.50/5 (17投票s)
2006 年 8 月 14 日
4分钟阅读

177107

4939
本文介绍 Odyssey User Interface,这是一个可以在任何托管 DirectX 应用程序中使用的控件库。
引言
我正在开发一款名为《星际奥德赛》的 4X 太空歌剧游戏,继承了《大师的奥德赛》系列的传统。每个游戏都需要一个用户界面,我的也不例外。DirectX SDK 提供了一个看起来非常酷的示例 UI,但很难从中提取项目,而且它依赖于纹理 GUI 元素。由于我不擅长绘制 GUI 元素,所以走这条路会花费很长时间。相反,我采取了“程序员艺术”的方式:以动态方式创建控件和其他 GUI 元素,同时尽力使其看起来很酷。结果在此呈现。读者应该对 DirectX 术语有一定的经验。由于游戏不需要绝对的 FPS 响应能力,因此使用简单的 Windows 消息来处理输入。请注意,要运行示例,您需要安装 DirectX SDK。如果您遇到问题,请尝试使用DirectX Web 安装程序。
使用代码
此项目中提供的控件设计得就像 Windows Forms 控件一样。因此,那些已经有开发 Forms 应用程序经验的人会发现这些可渲染控件非常容易使用。在演示存档中,您会找到 Odyssey UI.dll。它可以被任何 C# MDX 项目引用,并且是使用此库的最简单方法。否则,您可以编译源代码。要在您的代码中使用该库,您必须遵循以下简单步骤
- 引用
AvengersUTD.Odyssey.UserInterface
命名空间。 - 您必须让 Odyssey UI 管理 Windows Forms 事件。为了方便您,您可以调用提供的 `UI.SetupHooks(form);` 方法。
- 将您的 DirectX 设备对象分配给
UI.Device
。在整个代码中,有几个“设备”调用,因此这避免了您每次调用都传递设备引用。 - 创建一个
HUD
对象。HUD 是用户界面的屏幕覆盖。 - 告诉 HUD 您正在开始设计 UI,`hud.BeginDesign();`。
- 像编写 Windows Forms 应用程序时一样创建控件。
- 将控件添加到 HUD 容器中,`hud.Add(control);`。请注意,HUD 的顶层子项应被理解为位于桌面上的内容。因此,避免它们重叠是您的责任。如果您想要多个窗口,请将控件添加到一个窗口内,然后将该窗口添加到 HUD 中。
- 如有必要,分配事件。
- 完成 UI 创建后,使用 `
hud.EndDesign();
`。 - 将这样创建的
HUD
对象分配为用户界面的CurrentHUD
,`UI.CurrentHUD = hud;`。这允许您指定不同的HUD
并轻松切换HUD
。 - 最后,在您的渲染循环中,您只需调用 `hud.Render();`。
以下是渲染图像中显示的界面的示例代码
// Add these lines where appropriate in your code:
// (provided that you did all the above steps also)
UI.Device = yourDirectXDevice;
UI.SetupHooks(form);
// Create a new hud object. The string parameter
// is its ID: it will be used in the future
// when the library will allow users
// to "skin" via xml the user interface.
// ScreenSize refers to a size variable that represents
// your current resolution. The HUD should always be set
// to be as big as it can be.
HUD hud = new HUD("TestHud", Settings.ScreenSize);
private void TestUI()
{
// Start designing the UI
hud.BeginDesign();
// Create a panel control;
Panel panelTest = new Panel("UI Test Panel",
new Vector2(25, 150), new Size(520, 400));
// Create two labels. The Vector2 parameter
// refers to its position in the parent control.
// Since the labels are going to be
// added to the previous panel, the
// absolute position of the first
// one will then be: (X: 25 + 20, Y: 250 + 20).
// The alignment parameters are used
// to specify how you want to draw the text.
// The first label can be highlighted, hence the
// extra color value in the constructor.
Label lTest = new Label("LabelTestPanel",
"The blue window is a Panel" +
" Control and this is a Label",
Alignment.Left, Alignment.Top,
new Vector2(10, 10), Color.White, Color.Red);
Label lTb = new Label("LabelTrackBar", "This TrackBar control " +
"goes from 0 to 10 with a\n'TickFrequency' value of 2",
Alignment.Left, Alignment.Top,
new Vector2(20, 40), Color.LightGreen);
// This is a button. We have attached a delegate
// to its "MouseClick" event: it will be fired
// when the user clicks on the button.
Button example = new Button("ButtonExample", "This is a button",
new Vector2(20, 320), new Size(200, 50));
example.MouseClick += delegate(BaseControl sender,
System.Windows.Forms.MouseEventArgs e)
{
example.Label = "Yep you clicked me";
};
// This button opens a modal dialog box.
// In windows forms the MessageBox.Show method is "blocking"
// it means that the program does not continue to the
// "next line" until the user presses one of the dialog's buttons.
// Since simulating that behavior would have taken too much time
// for the time being you have pass a delegate method as a parameter
// that tells what you want to do. Just pass null if you simply
// want the window to close.
Button dialogTest = new Button("DialogTest", "Show me a dialog",
new Vector2(300,320), new Size(200,50) );
dialogTest.MouseClick +=
delegate(BaseControl sender,
System.Windows.Forms.MouseEventArgs e)
{
DialogBox.Show("Test",
"Do you like this User Interface?\n\nBy the way, " +
"this dialog is modal!",
DialogBoxButtons.YesNo,
delegate(BaseControl ctl, DialogResult dialogResult)
{
if (dialogResult == DialogResult.No)
DialogBox.Show("Really?",
"Sigh.... :(", DialogBoxButtons.Ok, null);
else
DialogBox.Show("Thanks",
"I'm glad that you liked it!\n\nIf you have any " +
"feedback drop me a line at avengerdragon at gmail.com " +
"or visit my forum at " +
"[hover=\"Aquamarine\"]http://starodyssey.avengersutd.com[/]",
DialogBoxButtons.Ok, null);
});
};
// This is a trackbar control. You have to set
// the trackbar minim value, tick Frequency
// and maximum values with the SetValues method.
// You can also attach a delegate method for
// its "ValueChanged" event.
TrackBar slider = new TrackBar("TrackBar",
new Vector2(20, 100), new Size(200, 30));
slider.SetValues(0, 2, 10);
slider.ValueChanged += delegate(BaseControl ctl) {
lTb.Text = "The trackbar value is now: " + slider.Value;
};
// This is a textbox. When you click on it, you can start typing.
TextBox tb = new TextBox("TextBox",
new Vector2(20, 140), new Size(200, 30), 24);
tb.Text = "This is a textbox";
// This is a groupbox control: a simple panel
// with a flat border and a caption.
GroupBox gb = new GroupBox("GroupBox", "This is a groupbox",
new Vector2(20, 200), new Size(200, 100),
Color.White, BorderStyle.Flat);
// This is an OptionGroup control: a collection of radio buttons.
// The string array passed as a parameter
// will be used to dynamically create an option button
// for each of the strings. You can also attach
// a delegate too its "SelectedIndexChanged" event
OptionGroup og = new OptionGroup("OptionGroup", new string[] {
"This is", "the OptionGroup", "control"},
new Vector2(5, 10), new Size(100, 30));
og.SelectedIndexChanged += delegate(BaseControl ctl)
{
gb.Caption = "You clicked the OptionButton number: " +
og.SelectedIndex;
};
// This is the CheckBox Control. You can access its
// selected value through its .IsSelected property.
CheckBox cb = new CheckBox("CB1", "Checkbox",
new Vector2(300, 300), new Size(100,30));
// This is the DropDownList control also known as
// a combobox.
DropDownList ddl = new DropDownList("DDL",
new string[] { "This", "is the", "DropDownList", "control" },
new Vector2(300, 100), new Size(150, 30));
// Next, we'll create three windows.
// We'll then create three controls to go inside those
// windows: a RichTextArea, a Table and a TabPanel
Window win1 = new Window("win1",
"Test Window #1", new Vector2(500, 100), new Size(640, 480));
Window win2 = new Window("win2",
"Test Window #2", new Vector2(550, 110), new Size(640, 480));
Window win3 = new Window("win3",
"Test Window #3", new Vector2(600, 120), new Size(640, 480));
// This is the RichTextArea control. It is a panel that
// automatically formats input depending on BBCode like
// string. We simply pass the string to be formatted and the
// style to use as default (if the default one is Arial 20pt
// for example, the bold command will apply the bold effect
// on the Arial 20pt font)
// Accepted commands are b,i,s for bold, italic and shadowd
// respectively and c or color for the standard color and
// h or hover for the color to use when the mouse pointer
// is over the label. Nested markup is not supported at
// the moment.
// We pass an empty vector because we want it to cover
// the whole internal area of the window. We manually
// compute its size because there's no "autosize" feature yet.
RichTextArea rta = new RichTextArea("RTA",
Vector2.Empty, new Size(632, 300), rtext, TextStyle.Default);
// This is the TabPanel control. A Panel that has some
// buttons on the top that allow the user to access different
// pages in it. Each page in this example has a label control.
// You can switch page by clicking the top buttons.
TabPanel tabPanel = new TabPanel("Tab",
new Vector2(30, 50), new Size(310, 200));
tabPanel.AddTab("Page 1");
tabPanel.AddControlInTab(new Label("pag1",
"Page 1", Alignment.Left, Alignment.Top,
new Vector2(100, 20), Color.LightGreen), 0);
tabPanel.AddTab("Page 2");
tabPanel.AddControlInTab(new Label("pag2",
"Page 2", Alignment.Left, Alignment.Top,
new Vector2(100, 20), Color.LightGreen), 1);
tabPanel.AddTab("Page 3");
tabPanel.AddControlInTab(new Label("pag3",
"Page 3", Alignment.Left, Alignment.Top,
new Vector2(100, 20), Color.LightGreen), 2);
// This is the table control. You can esaily format by choosing
// different TableStyle parameters
Table table = new Table("Table", 3, 2, new Vector2(15, 15));
table[0, 0].Text = "This";
table[0, 1].Text = "is the";
table[1, 0].Text = "Table";
table[1, 1].Text = "control!";
table.Format(new TableStyle(150, 30, 1, 1, Border.All));
// Finally we add each control to its parent container.
gb.Add(og);
panelTest.Add(lTest);
panelTest.Add(lTb);
panelTest.Add(slider);
panelTest.Add(tb);
panelTest.Add(gb);
panelTest.Add(ddl)
panelTest.Add(example);
panelTest.Add(dialogTest);
win1.Add(rta);
win2.Add(tabPanel)
win3.Add(table);
hud.Add(panelTest);
hud.Add(win1);
hud.Add(win2);
hud.Add(win3);
// Signal the hud object that we're doing creating controls
hud.EndDesign();
}
// After having called the TestUI method
// somewhere in yor app before rendering,
// in your render loop you have to place
// the following line:
public void Render()
{
hud.Render();
}
关注点
我试图模仿 Windows Forms 事件样式,结果非常相似。在我开发游戏的过程中,它非常有帮助,因为一旦所需的控件实现并完全正常工作,您就可以专注于游戏本身,从而加快进程。这些是 UI 的主要功能
- 完整的**窗口**支持:UI 正确渲染多个窗口。
- 模态和**可拖动**窗口。
- 形状支持:内置圆形、矩形和梯形默认形状,但您可以通过 `Graphics` 静态类定义新的形状。
- 边框样式:平面、凸起、凹陷;还可以实现更多样式。
- 控件状态更改:启用、高亮、聚焦、选中等。
- 所有您能想到的**鼠标**和**键盘**事件!
以下控件包含在内
Window
DialogBox
Label
Panel
Button
文本框
TrackBar
GroupBox
OptionGroup
CheckBox
TabPanel
表格
DropDownList
RichTextArea
PictureBox
值得注意的是,通过派生或改进 PictureBox 控件,您理论上可以开发一套全新的纹理控件,以及可渲染的控件。您甚至可以组合它们来实现新的效果。有多种可能性。更多控件正在开发中。要及时了解此库的开发进展,请参阅星际奥德赛网站上的项目页面。请在论坛上发布您发现的任何错误和请求。
Odyssey UI 在知识共享署名-非商业性许可下发布。您可以随意修改此源代码,如果您决定在您的项目中使用它,请告诉我,以便我们可以共享链接(如果您愿意)。感谢您的阅读!
历史
- 2007 年 7 月 22 日:版本 0.3 - 添加了 Window、DialogBox、RichTextArea 和 PictureBox 控件。
- 2007 年 6 月 14 日:版本 0.2 - 更新版本。添加了 Table、Tabpanel 和 Checkbox 控件以及初步的图层功能。
- 2006 年 8 月 14 日:版本 0.1 - 首次提交 CodeProject。