Friday, September 21, 2007

Window System for XNA - New Release

Just to let everybody know that there should be a new release within the next week, basically when my internet connection is back up. The reason for the sudden release is because I've finally managed to fix the GUI rendering, with correct alpha values. The most important impact is that anti-aliased SpriteFont will be used from now on, and the old XNAExtras stuff is to be removed.

Project Site

If anyone is interested in what the problem was, and how I fixed it, then read on.

In the old system, every control was rendered to its own texture using render targets. Each child control would have its texture combined with those of the other children, to make up the texture of the parent control (after drawing itself). It was quite efficient because only the top level controls (generally windows) actually get their textures drawn to the screen, with the textures only redrawn when necessary, such as when the mouse hovers over a button, and a different image must be displayed. Render targets were also an easy way to perform clipping of child controls within their parent, and allowed the alpha value of a control to affect all children as well.

The problem with render targets was that control textures had to be drawn to a transparent texture, causing incorrect colour and alpha values to be produced during the alpha blending operation. In practical terms, it means that the edges of sprites with partial transparency and anti-aliased text would be mixed with white (or any other colour depending on the clear colour of the render target). This made either anti-aliased text unreadable, or made the edges of some controls the wrong colour. The reason I couldn't use SpriteFont, was because it created anti-aliased fonts. With XNAExtras I could make a simple font bitmap with colour keyed transparency, which made the text problem go away.

After playing with render states and pre multiplied alpha for a long time, I finally got fed up and tried to find another way to get it working. I was advised to draw directly to the screen, and use scissor rectangles to handle clipping. This worked great, except performance was abysmal. With just 4 complex windows on the screen, the framerate would start to drop. That was without any game running in the background! The reason for the poor performance was because scissor rectangles must be the same until SpriteBatch.End() is called. This means that I had a SpriteBatch.Begin() and End() call for every single control!

Next I decided to implement my own texture clipping, which all worked great, but there wasn't an obvious way to clip text. When using scissor rectangles for just text, the performance was still really bad. The final method was to use a hybrid approach. I would clip sprites on the CPU, and render text to textures which would be cached to allow fast draw calls. The text texture can then be clipped in the same way as the rest of the GUI sprites. Of course there was still the problem of partial alpha pixels being blended with the transparent texture colour. Basically I set the render states to draw with pre multiplied alpha, and set the background clear colour of the texture to the text colour, with the alpha channel set to 0. This means that the blending still takes place, but it produces the correct results. The performance is good once again!

So yeah, the project isn't dead, I've just had some big problems to deal with. Now that this is working, I will go back to adding more features like Xbox 360 support, and more widgets.

Monday, September 3, 2007

CodePlex Hosting and the Future

Hi guys. I've finally got full project hosting set up, but I decided to go with CodePlex in the end. It seemed well suited to my needs, and is already home to other XNA projects. I will not be using the current temporary forum anymore, but the discussions feature on CodePlex instead.

http://www.codeplex.com/wsx

I have updated the first new release by changing the name of the project, adding a license, and cleaning up the directory structure. The new name is Window System for XNA, instead of XNA Window System. It may seem like an unnecessary change, but it means that XNA is no longer in the official title, as Microsoft doesn't approve of that. I decided on the New BSD license, which is more permissive than the other open source licenses. Finally, the projects now reference binary DLLs, instead of requiring all projects to be included in the solution.

I have been working on fixing the current bugs, and have decided to make some rather large changes along the way. The GUI will no longer be drawn with render targets, because I couldn't get premultiplied alpha values to work correctly, which screwed up font rendering. The font support provided in XNA Refresh will now be used instead of XNAExtras. I will also be adding joypad support for the Xbox 360 controller as well as regular Windows controllers. Support for the Xbox 360 will be added in the longer term (when I find somebody with an Xbox 360 to implement it). These and many other changes will be added over the coming months. You can keep up to date with the project by using the source control feature of the CodePlex project.

Please consider helping with the project, either by contributing code, working on documentation, artwork, or by submiting bugs and helping out in discussions.

Tuesday, July 17, 2007

Project Hosting

It's been quite a while since my last post. I haven't received much feedback about the XNA WindowSystem lately, so either people aren't having problems with the library, or they assume the project is dead.

I assure you that the project is not dead, I've just been working on some other stuff while I decide what to do next with the GUI. For starters, the project now has a home. It will be hosted by the guys at http://www.legendro.net/, who I believe are using the library in their upcoming game. At the moment, a forum and a bug tracker have been set up. It would be cool for some of you to sign up to the forums to ask questions, make feature requests, and generally support the project. I am hoping to get the version control software and a wiki for documentation set up soon. The aim of all this is to recruit some developers to help keep the project going. I don't want it to get to the stage where a ton of competing libraries are released, effectively killing off my GUI.

Check out the new home at http://xnaws.legendro.net/

Many thanks to Anthony for the free hosting, and all the effort in setting it all up.

Oh yeah, and I'm starting a new job in the games industry soon. Wish me luck!

Monday, April 30, 2007

Tutorial 2

I haven't had much feedback regarding the first tutorial, so I'm assuming nobody has had any problems with it. I hope it was useful to some of you.

This next tutorial will cover how to create a class that inherits from Dialog, and retrieve some useful information from it. This method of using windows was developed while putting together my tile map editor application. It is the best way of using the GUI that I have discovered so far, and is pretty closely based on other window systems I've used in the past. I'm sure some other methods will expose themselves in the future.

Setting Up The Project

I have already covered setting up a new project, and setting up the GUIManager in the previous tutorial, so you can go back and look it up there if necessary. Simply follow Tutorial 1 up until the Adding Controls heading, and you'll be ready to go.

Creating The Dialog

We will create a Dialog box, where the user can enter their name into a textbox. It will have OK and Cancel buttons, so that the dialog can return a result.

The first thing to do is to create a new class called UserDetailsDialog, which is inherited from Dialog. Dialog is inherited from Window, and the only addition is that it returns a result, which can be queried after the Dialog is closed.


using System;
using Microsoft.Xna.Framework;
using XNAWindowSystem;

namespace GUI_Testbed
{
public class UserDetailsDialog : Dialog
{
}
}


Next we can add a few fields to our new class.


private const int LargeSeparation = 10;
private const int SmallSeparation = 5;
private TextButton OKButton;
private TextButton cancelButton;
private TextBox nameTextBox;


The LargeSeparation and SmallSeparation constants are something I add to all my dialogs. They allow me to control the spacing between the window edge and between dialog controls. I use LargeSeparation for the window edge, and between unrelated controls. SmallSeparation is used for the space between related controls, like between a textbox and it's label.

The rest of the fields are controls that we will need to keep track of after they are set up. We will need to get the text from nameTextBox for example. Other controls like labels can just be added without keeping a reference if they don't need to change, or we don't need to query them after events are triggered.

Next we should add a property, so that the user's name can be queried from outside the class.


public string Name
{
get { return nameTextBox.Text; }
}


The constructor sets the control properties and adds them as child controls, as well as settings it's own properties.


public UserDetailsDialog(Game game, GUIManager guiManager)
: base(game, guiManager)
{
// Name label
Label nameLabel = new Label(game, guiManager);
Add(nameLabel);
nameLabel.Text = "Name:";
nameLabel.X = LargeSeperation;
nameLabel.Y = LargeSeperation;
nameLabel.Width = 75;
nameLabel.Height = nameLabel.TextHeight;

// Name textbox
this.nameTextBox = new TextBox(game, guiManager);
Add(this.nameTextBox);
this.nameTextBox.Initialize();
this.nameTextBox.X = nameLabel.X;
this.nameTextBox.Y = nameLabel.Y
+ nameLabel.Height
+ SmallSeperation;

// Set the window width to the default textbox width
ClientWidth = nameTextBox.Width +
(2 * LargeSeperation);

// Cancel button
this.cancelButton = new TextButton(game, guiManager);
Add(this.cancelButton);
this.cancelButton.Text = "Cancel";
this.cancelButton.X = ClientWidth
- this.cancelButton.Width
- LargeSeperation;
this.cancelButton.Y = this.nameTextBox.Y
+ this.nameTextBox.Height
+ LargeSeperation;
this.cancelButton.Click
+= new ClickHandler(OnButtonClicked);

// OK button
this.OKButton = new TextButton(game, guiManager);
Add(this.OKButton);
this.OKButton.Text = "OK";
this.OKButton.X = this.cancelButton.X
- SmallSeperation
- this.OKButton.Width;
this.OKButton.Y = this.cancelButton.Y;
this.OKButton.Click
+= new ClickHandler(OnButtonClicked);

// Set the window height to the amount needed to show
// all controls.
ClientHeight = this.OKButton.Y
+ this.OKButton.Height
+ LargeSeperation;

// Set the window title
TitleText = "User Details";

// This dialog does not need to be resized by the user
Resizable = false;

CenterWindow();
}


Note that I add child controls before setting their properties. This is because some controls don't become fully set up until they are initialised, which happens automatically when they are added to the GUI or another control. You could also just call Initialize() manually, but who needs that kind of extra typing?

Also notice that control positions are set in relation to each other. I find this keeps the layout quite dynamic, especially for resizeable windows. I will consider providing layout managers in a future release, perhaps similar to those found in the Java Swing library.

The TextBox and TextButton controls don't have their Width or Height properties set. That's because some controls have useful default sizes, which allows some flexibility when modifying the default GUI settings.

Next we will add an event handler to the class, which will be called whenever a button is clicked. The buttons had this event handler added for their Click events in the constructor.


protected void OnButtonClicked(UIComponent sender)
{
if (sender == this.OKButton)
SetDialogResult(DialogResult.OK);

CloseWindow();
}


This method simply checks which button was clicked, and sets the result accordingly before closing the dialog. We don't bother checking for the Cancel button because cancel is the default result. This is because that is also the result when the user clicks on the window close button. If necessary the close button can be removed by setting the HasCloseButton property to false.

Using The Dialog

Back to the main game class which I have called Tutorial2, and we're going to attempt to use our new dialog.

The application will show an information message box, explaining how to use our simple program, then show our User Details dialog box when the user presses enter. If the user clicks on the OK button, a message box will display the name entered into the dialog textbox.

Firstly, add a dialog reference to our Game class.


private UserDetailsDialog dialog;


Next add an event handler that will be called when the user presses a key. In the constructor add the following code.


// Add a key down event handler
this.input.KeyDown += new KeyDownHandler(OnKeyDown);


Then create the event handler method.


private void OnKeyDown(KeyEventArgs args)
{
if (args.Key == Keys.Enter)
{
// Only create a new dialog if one isn't already
// shown.
if (this.gui.GetModal() == null)
{
// Create and show dialog
this.dialog =
new UserDetailsDialog(this, this.gui);
this.dialog.Close +=
new CloseHandler(OnDialogClosed);
this.dialog.Show(true);
}
}
}


Basically this function just checks that no other modal window is already opened, if not then it pops up a new User Details dialog. Modal just means any window that has exclusive focus, such as a message box, or our dialog. To show a window as modal, pass true to the Window.Show() method, otherwise pass false.

When your program calls a modal dialog in Windows, it takes full control of you're processing until the dialog is closed. Unfortunately, our system is a little different. We have to add an event handler to the window's Close event, that checks which window was closed, and acts accordingly.

The following code is the method called when our dialog is closed.


private void OnDialogClosed(UIComponent sender)
{
if (sender == this.dialog)
{
if (this.dialog.DialogResult == DialogResult.OK)
{
MessageBox message = new MessageBox(
this,
this.gui,
"Name: " + this.dialog.Name,
"User Name",
MessageBoxButtons.OK,
MessageBoxType.Info
);
message.Show(true);
}

// Remove event handler so garbage collector will
// pick it up.
this.dialog.Close -= OnDialogClosed;
this.dialog = null;
}
}


A message box is shown repeating the name entered by the user, only if the result was OK. Then the event handler is removed from the dialog and the dialog is set to null, to prevent it from lingering about in memory. This is very important to remember, especially in a real application. I've caught out by event handlers and the garbage collector on few occasions.

The final things to add is a message box explaining how to use the application. This should be placed in the Game class Initialize() method, to ensure it's shown at the start of the program.


// Show a message box informing the user how to use the
// program.
MessageBox info = new MessageBox(
this,
this.gui,
"Press enter to bring up the dialog.",
"Info",
MessageBoxButtons.OK,
MessageBoxType.Info
);
info.Show(true);


Conclusion

I hope this tutorial was helpful, it was definitely more difficult to write than the last one. I would really appreciate any feedback, because I'm quite new to writing documents like this.

I'm sure some of you are wondering where to find the source code for my tutorials, well they're not available at the moment. I will add it to the GUI_Testbed project for the next release of the WindowSystem.

I'm not sure what to cover in the next one. I'll probably wait until I get some responses from this one before I decide. I think I should cover skinning and modifying the GUI at some point. Any ideas?

Thursday, April 26, 2007

Fixed Project Files

Now that I have an Internet connection again, I decided to fix the project files that you guys have had problems with. It seems Visual Studio uses relative paths for everything, except the XNA content assemblies.

Download Source Code

This release is exactly the same as the last one, so don't bother downloading if you've already fixed your project files.

Microsoft have recently released an update for XNA, which I will be checking out tonight. It has font support, although I gather it's fairly basic. I will try to integrate this into the GUI, although I suspect I will run into the same problems I had before with their font sample code. In case you haven't read my comments from a while ago, the problem is that anti-aliased fonts simply won't work with the GUI at the moment. The only way I can see it working is to implement multiple passes when rendering the controls. Personally I think that small font sizes should be used anyway, which look better without anti-aliasing.

Monday, April 23, 2007

Quick Update

I've been pleased to learn that some people have started to use my window system, at least to play around with. If anybody manages to finish a game or tool that uses the GUI, then I'd love to hear about it.

Obviously the system is in it's early stages, and is yet to be used in an actual game. The good news is that I now have an Internet connection on my development computer now, so I'll be able to resume work on it sometime over the next few days. Before I do that however, I'd like to write another couple tutorials to bring everybody up to speed on how to use the system. Also, I'd like to write an article on the design of the system, which may help people to understand it better, especially for those who wish to expand upon it.

I'm hoping to finish the first version of my XNA tile map editor I've been working on in parallel with the window system. The source code for that should shed some light on how best to use the system. The tutorials are actually based on what I learned from building that application.

Regarding the Internet connection, I spent several hours yesterday looking for that perfect spot in my basement room where I could get a signal from the wireless router with more than a one bar signal strength. Turns out it's in the closet, so the computer is currently half inside my room and the closet!

Wednesday, April 18, 2007

Tutorial 1

This first tutorial will cover setting up the GUIManager, adding new controls, and handling their events.

Setting Up The Project

The simplest way to begin is to use the GUI_Testbed project as a starting template. I simply created a new source file containing a class called Tutorial1, and set that as the startup Game object in Program.cs. You could also just use the existing GUI_Testbed.cs if you don't feel like starting from scratch.

Setting Up The GUIManager

The first thing to do is to add a couple using statements for the InputEventSystem and the XNAWindowSystem.


using System;
using Microsoft.Xna.Framework;
...
using InputEventSystem;
using XNAWindowSystem;


Now add some fields.


private GraphicsDeviceManager graphics;
private InputEvents input;
private GUIManager gui;


In the constructor we set up these objects, and add input and gui to the list of game components. It is important that the InputEvents object is created before the GUIManager object because it adds itself to the game services in the constructor, which the GUI will need access to. Not creating an InputEvents object at all will cause the program to crash.


public Tutorial1()
{
this.graphics = new GraphicsDeviceManager(this);

this.input = new InputEvents(this);
Components.Add(this.input);

this.gui = new GUIManager(this);
Components.Add(this.gui);

// GUI requires variable timing to function correctly
IsFixedTimeStep = false;
Window.Title = "XNA Window System Tutorial 1";
}


The GUIManager object should be initialised in the overriden Initialize() method, before any child controls are added. This is so that order of resource loading can be predicted.


protected override void Initialize()
{
// Has to be initialised before child controls can be added
this.gui.Initialize();

base.Initialize();
}


You will also want to override the Draw() method, to clear the screen each frame, although this probably doesn't affect the GUI as it handles drawing itself.

Adding Controls

I'm going to use a menu bar with various menus and menu items as an example. This will show how to create controls, how to add them to the GUI, and how to handle their events.

First we should add some fields, which are the menu bar and the menu items that the user will actually select. This means that we don't actually need to keep a reference to the 'File' menu for example, because it handles itself. We do need to keep references to child menu items that will actually be clicked, so that we can determine which one was selected. An example would be the 'New' or 'Save' menu items.


private MenuBar menuBar;
private MenuItem newMenuItem;
private MenuItem openMenuItem;
private MenuItem saveMenuItem;
private MenuItem saveAsMenuItem;
private MenuItem exitMenuItem;
private MenuItem undoMenuItem;
private MenuItem redoMenuItem;


Next we have to actually set up the GUI objects, and add them to the GUI. This should take place in Initialize(), after the GUIManager has been initialised.


this.menuBar = new MenuBar(this, gui);
MenuItem fileMenu = new MenuItem(this, gui);
fileMenu.Text = "File";
this.newMenuItem = new MenuItem(this, gui);
this.newMenuItem.Text = "New...";
fileMenu.Add(this.newMenuItem);
this.openMenuItem = new MenuItem(this, gui);
this.openMenuItem.Text = "Open...";
fileMenu.Add(this.openMenuItem);
this.saveMenuItem = new MenuItem(this, gui);
this.saveMenuItem.Text = "Save";
this.saveMenuItem.IsEnabled = false;
fileMenu.Add(this.saveMenuItem);
this.saveAsMenuItem = new MenuItem(this, gui);
this.saveAsMenuItem.Text = "Save As...";
this.saveAsMenuItem.IsEnabled = false;
fileMenu.Add(this.saveAsMenuItem);
this.exitMenuItem = new MenuItem(this, gui);
this.exitMenuItem.Text = "Exit";
fileMenu.Add(this.exitMenuItem);
menuBar.Add(fileMenu);
MenuItem editMenu = new MenuItem(this, gui);
editMenu.Text = "Edit";
this.undoMenuItem = new MenuItem(this, gui);
this.undoMenuItem.Text = "Undo";
this.undoMenuItem.IsEnabled = false;
editMenu.Add(this.undoMenuItem);
this.redoMenuItem = new MenuItem(this, gui);
this.redoMenuItem.Text = "Redo";
this.redoMenuItem.IsEnabled = false;
editMenu.Add(this.redoMenuItem);
this.menuBar.Add(editMenu);

// Add menubar to gui
this.gui.Add(this.menuBar);


You may notice that some of the menu items have a property called IsEnabled set to false. This basically makes the item grey and unclickable, just how windows does it. This property only applies to MenuItem objects currently, but at some point in the future, I will probably make it apply to all controls.

Now if you run the application, it should show a menu bar with all the new menus and menu items.

Control Events

Next we will add event handlers that will be called whenever a menu item is clicked. The reason for holding references to menu items is for comparison with the clicked control, determining which one was actually clicked.

So add the following code to the Initialize() method, probably after the menu bar is added to the GUI.


// Add event handlers
this.newMenuItem.Click += new ClickHandler(OnMenuItemClicked);
this.openMenuItem.Click += new ClickHandler(OnMenuItemClicked);
this.exitMenuItem.Click += new ClickHandler(OnMenuItemClicked);
this.undoMenuItem.Click += new ClickHandler(OnMenuItemClicked);
this.redoMenuItem.Click += new ClickHandler(OnMenuItemClicked);


Finally we need to add a new method that is called whenever a menu item is clicked. We simply check which item was clicked, and act accordingly.


private void OnMenuItemClicked(UIComponent sender)
{
if (sender == this.newMenuItem)
{
MessageBox messageBox = new MessageBox(
this,
gui,
"New clicked!",
"Tutorial 1",
MessageBoxButtons.OK,
MessageBoxType.Info
);
messageBox.Show(true);
}
else if (sender == this.openMenuItem)
{
MessageBox messageBox = new MessageBox(
this,
gui,
"Open clicked!",
"Tutorial 1",
MessageBoxButtons.OK,
MessageBoxType.Info
);
messageBox.Show(true);
}
else if (sender == this.undoMenuItem)
{
MessageBox messageBox = new MessageBox(
this,
gui,
"Undo clicked!",
"Tutorial 1",
MessageBoxButtons.OK,
MessageBoxType.Info
);
messageBox.Show(true);
}
else if (sender == this.redoMenuItem)
{
MessageBox messageBox = new MessageBox(
this,
gui,
"Redo clicked!",
"Tutorial 1",
MessageBoxButtons.OK,
MessageBoxType.Info
);
messageBox.Show(true);
}
else if (sender == this.exitMenuItem)
Exit();
}


This first tutorial was really simple, but should show you the basics of using the window system. It should be enough if all you want to do is add a couple buttons to your game. In the next tutorial I will show how to create a class that inherits from Window, to show how to begin to create a real application.