XNA Game Editor Tutorial #2 – Tool Box pt. 1 / 2
In this chapter, we are going to focus on the Tool Box part of the editor. This toolbox will be a tree of categories and the components available in them, which will be constructed by scanning for components in the assemblies linked to by the game’s .csproj that we are working with.
To start, add a TreeView to your form, and call it toolboxTree. Second, add a MenuStrip to the form. It should stick itself to the top of the form. Now click where it says “Type Here”, and type “Tools”. This will add a new button to the menu strip. Click on it, and repeat the process in the dropdown by adding a “Link XNA Game” button. Double click on the “Link XNA Button” now, and you will be taken to its “Click” event. Before we start writing code here, we need a place to keep track of what game we are working with. Create a new class in another file called “EditorGlobals.cs”.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | // Keeps track of global variables used to keep track of editor state public static class EditorGlobals { // Path to the .csproj project of the linked game static string gameFilePath; static string currentSceneFileName = null; // Class that watches a directory and notifies us of changes. For // example, new content being built or the game project being updated static FileSystemWatcher watcher; // Events for when the game project is about to change // and has changed public static event EventHandler LinkedGameChanging; public static event EventHandler LinkedGameChanged; public static event FileSystemEventHandler GameDirectoryModified; // Path to the .csproj project of the linked game public static string GameFilePath { get { return gameFilePath; } set { // Fire LinkedGameChanging to notify the editor // that the path is about to change if (LinkedGameChanging != null) LinkedGameChanging(null, new EventArgs()); // Set the new path gameFilePath = value; currentSceneFileName = null; // Fire LinkedGameChanging to notify the editor // that the path has changed if (LinkedGameChanged != null) LinkedGameChanged(null, new EventArgs()); // Rebuild the FileSystemWatcher with the new directory watcher = new FileSystemWatcher(GameDirectory); watcher.IncludeSubdirectories = true; // We must make the watcher use our thread to avoid illegal // cross thread operation watcher.SynchronizingObject = Form1.ActiveForm; watcher.Changed += new FileSystemEventHandler(watcher_Changed); watcher.Deleted += new FileSystemEventHandler(watcher_Changed); // Make sure the watcher doesn't fire until we are done with the // project's directories watcher.EnableRaisingEvents = false; // Make sure the scene directory exists if (!Directory.Exists(GameSceneDirectory)) Directory.CreateDirectory(GameSceneDirectory); // Make sure the content directory exists if (!Directory.Exists(GameContentDirectory)) Directory.CreateDirectory(GameContentDirectory); Engine.Content.RootDirectory = GameDirectory + "bin\\x86\\Debug\\"; // Re-enable the watcher watcher.EnableRaisingEvents = true; // Fire the GameDirectoryModified event through watcher_Changed watcher_Changed(null, new FileSystemEventArgs( WatcherChangeTypes.Changed, GameDirectory, Path.GetDirectoryName(value))); } } // Called by the FileSystemWatcher when the game directory is modified static void watcher_Changed(object sender, FileSystemEventArgs e) { // Simply forward the event to GameDirectoryModified if (GameDirectoryModified != null) GameDirectoryModified(sender, e); } // Whether or not a game has been linked to the editor public static bool EditorLinkedToGame { get { return gameFilePath != null; } } // The directory of the game's project public static string GameDirectory { get { return Path.GetDirectoryName(gameFilePath) + "\\"; } } // The directory of the game's built content public static string GameContentDirectory { get { return GameDirectory + "bin\\x86\\Debug\\Content"; } } public static string GameSceneDirectory { get { return GameDirectory + "bin\\x86\\Debug\\Scenes"; } } public static string CurrentSceneName { get { return currentSceneFileName; } set { currentSceneFileName = value; } } public static string CurrentSceneFilePath { get { return GameSceneDirectory + "\\" + currentSceneFileName + ".iescn"; } } } |
There is a lot of stuff in there we won’t be using yet, but it’s easiest just to write it all now. This class is basically a container for us to store a bunch of values used all over the editor, so we can get to them through EditorGlobals.XXXXX, instead of passing stuff around. These include settings like the current directory of the game’s scenes, content, the game projects filename and directory, etc. It also takes care of monitoring these directores and firing an event when they change. One important property here the the file path to the game’s .csproj file. When this is changed, we do a lot of work resetting our FileSystemWatcher, checking if all the necessary directories exists, etc.
Now we can get back to “Link XNA Game” button’s event handler. We are simply going to ask for a filename for an XNA game .csproj, then set the GameFilePath property above and trigger all the code mentioned above.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | private void linkXNAGameToolStripMenuItem_Click(object sender, EventArgs e) { // Reset the editor ResetScene(true); // Create a file selection window OpenFileDialog ofd = new OpenFileDialog(); ofd.Filter = "XNA Game Project|*.csproj"; // Set the new projected to the selection if (ofd.ShowDialog() == DialogResult.OK) EditorGlobals.GameFilePath = ofd.FileName; } // Resets the editor back to its initial state void ResetScene(bool CheckForSave) { if (EditorGlobals.EditorLinkedToGame && CheckForSave) { DialogResult result = MessageBox.Show("The current scene is about to be unloaded. Would you like to save it first?", "Warning", MessageBoxButtons.YesNo, MessageBoxIcon.Warning); if (result == DialogResult.Cancel) return; else if (result == DialogResult.Yes) { string sceneName = Path.GetFileNameWithoutExtension( TextDialog.Show("Scene Name", "Choose a name for the scene", !string.IsNullOrEmpty(EditorGlobals.CurrentSceneName) ? EditorGlobals.CurrentSceneName : "")); if (sceneName != null) SaveScene(sceneName); } } Engine.Reset(); RenderControl.AddDefaultComponents(); } |
For this to work, the initialization code for the camera, keyboard, and mouse must be moved in the RenderControl class to its own method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public static void AddDefaultComponents() { // Setup the camera and move it to a good position, and make it a service Camera = new FPSCamera(); Camera.RotateTranslate(new Vector3(0, 0, 0), new Vector3(128, 15, 300)); Engine.Services.AddService(typeof(Camera), Camera); // Setup the keyboard and mouse, and allow the cursor to move Keyboard = new KeyboardDevice(); Mouse = new MouseDevice(); Mouse.ResetMouseAfterUpdate = false; Camera.Serialize = false; Keyboard.Serialize = false; Mouse.Serialize = false; } |
We also need a method to save the current scene in the main form:
1 2 3 4 5 | void SaveScene(string Name) { Engine.SerializeState(EditorGlobals.GameSceneDirectory + "\\" + Name + ".iescn"); EditorGlobals.CurrentSceneName = Name; } |
The ResetScene method also makes use of another class, which simply displays a TextBox on a popup. Here are the code files for that windows form. (I don’t want to get into it, because it’s not exactly relevent, but we still need it.)
TextDialog.cs
TextDialog.Designer.cs
TextDialog.resx
That’s all we are going to do this time, but in the next tutorial we will add the code that populates the toolbox TreeView when a new project is linked.
Tags:
Sean
7 Responses to “XNA Game Editor Tutorial #2 – Tool Box pt. 1 / 2”
May 4, 2009 at 1:13 pm
Hey i noticed that you removed the ctor for the component class to accept a parent screen as a parameter. I was wondering why this is? Great job on this tutorial i cant wait till the next i have been anxiously waiting sense you left for your vacation =P
May 4, 2009 at 1:23 pm
Also i was testing your seirliezer out and it seems to keep the filestream open so you should close it after your done with it.
August 3, 2009 at 11:04 pm
Hi, Sean!
Your tutorials are AWESOME. I am enjoying each line you have written. Waiting for future posts.
Keep up the good work
August 21, 2009 at 6:58 am
has anyone expanded on this tutorial yet, or is sean around to finsih it up? great work so far.
August 30, 2009 at 8:04 pm
How did it cost to start up this blog…I want to start my own.
August 30, 2009 at 8:09 pm
A hosting account runs about $60/year. The domain name is about $20/year. Wordpress (the publishing platform) is free. I host a number of websites on my account so it’s not that bad. In most cases, though, it’s cheaper to use a free blogging site like wordpress.com. I believe you can even buy a domain name to point to a hosted blog, so you could get the domain name and point it at a wordpress blog and be done with it.
September 2, 2009 at 3:44 am
Hi Sean was wondering if you was working on part 2/2 of this tutorial would love to see some more of this work.