Ask the ZMan: A Holiday Screen Saver with DirectX, Part 3

  The project is a holiday-themed screen saver called DirectXmas.
ZMan's Diary

Difficulty: Intermediate
Time Required: 1-3 hours
Cost: Free
Software: C# 2005 Express Edition, October 2005 DirectX SDK
Hardware:
Download:

Last time you used a basic scene graph data structure to draw multiple shapes to create the fir tree with the decorations. Hopefully you had some fun creating new objects out of the basic mesh shapes provided by DirectX. Since the final download from last time, I have added another holiday object—a snowman. The body is three spheres, the hat is two cylinders, the nose is a cone just like the tree segments and the eyes are a couple more small spheres. In addition, I have added a very large thin box to represent the ground and put the tree creation inside a loop. Because the drawing process is now relative, all you have to do is change the position of the tree trunk and the whole tree will draw at a different position. All of the code is identical in style to the tree creation from last time and is almost all inside the OnCreateDevice function. I've also moved the camera slightly to give a better point of view and slowed the camera rotation. You can download the start point for this column from the link at the top of the article and compare it to where you left off last time. The start point looks something like this:

This week you will add some animation to the scene and tidy up the code.

Flashing Lights

Animation in a computer game is anything that changes in your scene with time—it's not limited to changing positions or shapes, but any change in the way something is drawn, such as color or a special effect.

The first animation I am going to cover is changing the colors of the tree decorations. The way the scene is currently described, the only reference to those particular shapes is inside the tree structure. It's possible to walk into the tree structure every time you need to change something and search for it, but it's better to be able to go straight to the items you want to change. The first thing to do, then, is add the decorations to a list so they are easy to access. Add the following declaration to store them:

private List<GameObject> decorations=new List<GameObject>();

Then store a reference as they are added to the scene graph. Modify the contents of the loop in addOrnaments to this:

GameObject decoration=new GameObject(bauble, Color.Red, 
new Vector3((float)(baseWidth* Math.Sin(angle)), (float)(baseWidth * Math.Cos(angle)), -0.55f),
Vector3.Empty);
decorations.Add(decoration);
treepart.Children.Add(decoration);

The sample framework provides a function called OnFrameMove which is always called before any rendering happens. This is the correct place to make updates to the scene, such as animation. Animation is almost always correctly done based on the current game time, rather than a fixed increment each time OnFrameMove is called. The combination of different processor speeds and different graphics hardware will mean that you can never guarantee how often OnFrameMove is called. So, while an animation may play at a perfect speed on your computer, you can be sure somewhere else there is a computer that will be faster and another that will be slower. If you base all of your animations on the current game time you can be sure that no matter what the frame rate, everything will move at the speed you want.

For example, imagine if you want to move an object 5.0 units in the x direction in one second. You could add 0.1 to the x value on each frame. This would move the object 6.0 per second if the frame rate is 60fps, but it would move it 12.0 in the same time if the frame rate was 120fps on a faster computer. The correct way is to use a calculation based on the current elapsed time of the application, such as x=startPoint + 5.0 * appTime. Then, no matter what the frame rate, x will have changed by exactly 5.0 in 1 second.

You can see how I already do this to change the position of the camera:

new Vector3(5f * (float)(Math.Sin(appTime/10.0)), .5f, 5f * (float)(Math.Cos(appTime/10.0))) 

appTime is maintained by the framework and passed in to the OnFrameMove function.

In the case of this animation, you want to change the color of the decorations every N seconds, where N is a value that the user should be able to select in the Options dialog. There is no Options dialog yet, so for now this will just be a variable. You should also allow the user to disable the animation so there will be a boolean variable for that decision.

Add the following declarations:

//decoration animation control variables
private bool animateDecorations = false;
private double animateDecorationsTime = 0.2;
private double lastAnimationTickTime = 0.0;
private Color[] decorationColors = new Color[] {Color.Red, Color.Blue,
Color.BlanchedAlmond, Color.DarkOrange, Color.HotPink};

The animation is very trivial. If enough time has passed since you last animated, then loop through the decorations and give each a random new color in the OnFrameMove function:

if (animateDecorations)
{
if (appTime - lastAnimationTickTime >; animateDecorationsTime)
{
lastAnimationTickTime = appTime;
Random randomColor = new Random();
foreach (GameObject decoration in decorations)
{
decoration.Color = decorationColors[randomColor.Next(5)];
}
}
}

If everything is going to plan you should see the lights change when you run:

Goodbye Frosty

That title probably only makes sense to those who have lived in the USA during the holidays. Frosty the Snowman is a classic US holiday cartoon during which Frosty goes out in the sun and suffers the consequences. I can't do an animation worthy of Pixar, but melting the snowman in this scene is as easy as changing the coordinates of the body parts so that they slide below the "ground." Leaving the hat on a pile of snow is a nice end point for the animation. The GameObjects to animate are currently local variables in OnCreateDevice, so promote their scope to the class and leave just their instantiation in OnCreateDevice:

private GameObject lowerBodyNode;
private GameObject upperBodyNode;
private GameObject headNode;
//melting animation control variables
private bool animateSnowman = true;
private double animateSnowmanSpeed = 120.0f; //2 minutes change to a lower number if you are not patient!
//replace the similar lines in OnCreateDevice with these
lowerBodyNode = new GameObject(body, Color.White, new Vector3(0f, .15f, 0f), Vector3.Empty);
upperBodyNode = new GameObject(body, Color.White, new Vector3(0f, .3f, 0f), Vector3.Empty);
headNode = new GameObject(head, Color.White, new Vector3(0f, .25f, 0f), Vector3.Empty);

To animate these three shapes toward the ground, you just have to decrease the y component of the position over a suitable period of time in the OnFrameMove function.

if (animateSnowman)
{
if (appTime <; animateSnowmanSpeed)
{
Vector3 position = lowerBodyNode.Position;
position.Y = (float)(0.15 - 0.35 * (appTime / animateSnowmanSpeed));
lowerBodyNode.Position = position;

position = upperBodyNode.Position;
position.Y = (float)(0.3 - 0.2 * (appTime / animateSnowmanSpeed));
upperBodyNode.Position = position;

position = headNode.Position;
position.Y = (float)(0.25 - 0.15 * (appTime / animateSnowmanSpeed));
headNode.Position = position;
}
}

Notice that because everything is relative, you don't have to worry about animating the hat, eyes, or nose. Since they are attached to the head, they just move wherever the head moves. The snowman has to stop melting at some point, so this animation stops once the application time passes the melting time. Each part of the snowman has its position changed based on the current application time and a speed factor that can be changed.

Tidying up the screen saver

So the graphics work of the screen saver is now complete. If that's all you were interested in, then I will see you next time. The small remaining part of this article is adding in the screen saver controls and removing the remnants of the RSS screen saver that you started with.

Since you don't need the RSS media, or the sample UI media, you can remove the media folder. You can also get rid of the documentation folder and the RSS folder as they are no longer needed either. Delete the ScreenSaverForm.cs and then remove the couple of lines that still refer to the RSS object. The easiest way is to compile and remove the lines with errors.

Finally, you need to update the OptionsForm with controls that map to the variables used in the animation. Since this is not a Windows Forms column I will not walk you through this step-by-step but you can download the example code to see how it works. The values are saved in the config file just like the screen saver starter kit and are read back when the screen saver starts up. The final code can be downloaded from the link at the top of the article.

Installing the screen saver is simpler than the first article because there is no media to worry about. Simply rename the .EXE to .SCR and copy it and the config file into the system32 directory. See the first part of this series if you need more detailed instructions.

You have now completed a Managed DirectX screen saver using the sample framework, created and used a simple scene graph and learned how to animate object in the scene. Remember that, as I explained in the first part, nobody really creates graphics using lots of spheres and cylinders. It's just a convenient way to introduce the scene graph and the animation effects without having to worry about complex models. Hopefully its also been fun in a "Lego" kind of way to think about building shapes out of other shapes.

Homework

I'm sure the first thing most of you did to the code was change the tree density so there were a lot more trees in the scene and I'm sure you were shocked about how quickly the performance of the screen saver dropped. After all you probably paid several hundred dollars for a graphics card that could draw millions of triangles per second and yet for some reason a few hundred trees with relatively few triangles is bringing it to its knees. The solutions to these will be in some future Ask The Zman columns, but for now I would love to hear your ideas on why you think things are going so slowly on such powerful hardware.

The code in this article uses C# 2005 Express Edition and the October 2005 DirectX SDK

The ZMan is here to solve your Managed DirectX programming problems. If you have a question for the ZMan then send it to zman@thezbuffer.com.

Credits:

Thanks to

Follow the Discussion

Comments Closed

Comments have been closed since this content was published more than 30 days ago, but if you'd like to continue the conversation, please create a new thread in our Forums,
or Contact Us and let us know.