Wednesday, March 2, 2011

Building Your Own Game Engine - Tools II

This is the second article on tools that we developed and used during the making of The Jelly Reef. It covers our profiler, the level editor and few external tools.

Profiler
Performance is essential for any game and knowing how big of a chunk out of the update time goes to different tasks can be very helpful. For this purpose, we built a profiler early on and always kept a watch over the performance. The profiler has few roles. Firstly, to have a bunch of variables that can be used as counters, like counting the number of collision checks, drawing calls, triangles and such. Secondly, to count framerate. And most importantly, to measure execution time of different sections of the code. The profiler is implemented as a singleton manager, as discussed in the previous post. All the counters are accessible via a public dictionary. The profiler itself only draws the counters as name and value pair. It is up to the user of the counter to take care of the value, like resetting the counter zero or incrementing it.
The game uses a fixed framerate of 60 FPS, but we still use a framerate counter to detect if drop in performance occurs and eliminate the cause. For measuring section execution time we use the Stopwatch, which is a high-resolution timer. At the beginning of update the stopwatch is reset. At the beginning of the section in the update, like collision detection or water simulation, a the BeginSection method is called with the name if the section. When done, the EndSection method is called with the same name of the section. The profiler simply samples the time of the stopwatch so section can be nested or overlap. The profiler draws a bar from the beginning of the section to the end, using the width of the screen as a reference for the 0.0166 seconds (60-th of a second) available CPU time per update. So if we reach the right side of the screen, we know we need to optimize something, and we might as well start with the longest bar. The difference between the end and the beginning of a section gives its duration and this is shown just below the bars with the same color as the corresponding bar. The profiler is only compiled when the PROFILE symbol is defined, so just like the debug draw it is not part of the final build. Unlike the debug drawing, it is also compiled with the release builds of the game to track their performance as the C# compiler can optimize the release version quite a bit.

Editor
What started as a runtime inspector for the entities in the gameworld, quickly turned into a editor that we used to create the levels. This means that we used an in-game editor to author and edit the levels, rather than a separate tool. In fact at any time you can press E button on the keyboard and bring up the editor. This means very short turnaround times for our designer, as changing a value like mass or a position of a wall has an immediate effect in game. On the other hand, the user needs to remember that the game is running at all times. Physics and collision can easily be broken by large changes while the game is running. That is why the editor got a pause button, but somehow it was almost never clicked. And the programers need to remember that the same code runs during designing and playing. The editor is not included in the release build and the Surface unit doesn’t need a keyboard in user mode, so tampering with the game is not a problem.
The editor is very much based on features available in C# the .NET Framework. The editor is basically a window with a tree control a property grid, both of which a regular windows forms. The tree control is depicting the world hierarchy and allows the user to select one or more entities in the world. For the window to be able to create this hierarchy all the object that we would like to display in the editor must implement the ISceneNode interface.
/// 
/// Alows browsing the scene graph in a simple way
/// 
public interface ISceneNode
{
   IEnumerable<ISceneNode> GetChildNodes();
}

// GameWorld.cs
public class GameWorld : ISceneNode
{
   // ...
   public virtual IEnumerable<ISceneNode> GetChildNodes()
   {
            foreach (BaseGameEntity o in Entities) { yield return o; }
            //
            foreach (Wall w in Walls) { yield return w; }            
            //
            foreach (ParallaxLayer l in  Layers) { yield return l; }            
#if DEBUG
            yield return DebugDraw.Instance;
#endif
   }
   // ...
}

Using the interface window can build the tree recursively. This is a simple approach also lets a put things in the tree that are not really part of the world, but we would like to be able to tweak them too, like the options for the debug drawing.
The property grid on the other hand required a bit more attention. The property grid in .NET is uses reflection to give the user an interface to tweak any public property of any object, but some care must be taken. Firstly non-primitive types like our vector implementation would need to implement a conversion to a type that can be displayed properly. This actually took few days to get right, so that is doesn’t delete data when some has typed something wrong. Without some kind of description the user interface would be very difficult to use as it might be hard for the designer to know what linear dampening might mean, there might be too many properties that are public and they are not sorted in any way. So the entities have to have attributes attached to all their public properties, as the code below shows.
class MovingEntity : BaseGameEntity // Which implements ISceneNode
{
   // ...

   #if WINDOWS
   [Category("Physical Parameters")]
   [Description("Max turn rate in deg/s")]
   #endif
   [XmlIgnore]
   public float MaxTurnRate
   {
      get { return maxTurnRate; }
      set { maxTurnRate = value; }
   }
   // Field
   protected float maxTurnRate;

   // ...
}
As it turns out these attributes, especially the description, also help keep code well documented for programmers too. We always knew if a rotation property is in angles or radians. The component model API is only available on Windows, so some preprocessor guards come in handy if you would like to compile the project for other platforms like Xbox and Windows Phone. Our classes already had the XML attributes, so the code for a single property can be a bit long, but maintenance and ease of implementation made all the scrolling totally worth.
A very important part of the editor also were the different design modes. With a press of a button on the keyboard the game would switch to wall editing mode or entity manipulation mode. We added those model in a somewhat ad hoc manner and were more or less hacked into the game. Eliminating turnaround  time for the designer was a nice plus and it worked great for this game, but this approach doesn't scale well. In retrospective I think I might have been better to build an editor that still uses the engine with all of its classes and functionality but built a separate editor.

External Tools
Aside from the usual content creation applications like Photoshop and After Effect, we used few plugins for Photoshop some other tools. As we learned Photoshop doesn’t work with explicit alpha when saving in the PNG format, so we needed something that can roll back the time to Photoshop 4.0 when it was the last time it did work. Luckily there was a plugin for that. Another plugin, called Solodify, was used to fade the edges in the color channels so that no white pixels appear when alpha testing is used for rendering. The water caustics were precalculated using Caustics Generator, which available for free. I like to work with FX Composer for developing shaders regardless of the fact that is was a bit of overkill for the few shaders we needed. FX Composer is also available as a free download on nVidia’s developer website. As last line of defense when I need to debug my rendering code, I use PIX. This extremely powerful tool comes with the DirectX SDK and can capture every single call to the GPU, including its internal state and even show the intermediate rendering results.

No comments:

Post a Comment