Beginning Game Development: Part III - DirectX II

Published 03 November 06 01:36 AM | Coding4Fun 
  This is Part 3 of an introductory series on game programming using the Microsoft .NET Framework and managed DirectX 9.0. This article covers more advanced DirectX principles such as transforms, matrices, culling and clipping.
3Leaf Development

Difficulty: Intermediate
Time Required: 1-3 hours
Cost: Free
Software: Visual Basic or Visual C# Express Editions, DirectX SDK
Hardware: None
Download:
Beginning Game Development Series
  1. Beginning Game Development Part 1 - Introduction
  2. Beginning Game Development Part II - Introduction to DirectX
  3. Beginning Game Development: Part III - DirectX II
  4. Beginning Game Development: Part IV - DirectInput
  5. Beginning Game Development: Part V - Adding Units
  6. Beginning Game Development: Part VI - Lights, Materials and Terrain
  7. Beginning Game Development: Part VII –Terrain and Collision Detection
  8. Beginning Game Development: Part VIII - DirectSound
  9. Beginning Game Development: Part VIII - DirectSound II
  10. Beginning Game Development: Part VIII - DirectSound III

Introduction

Welcome to the third article on beginning game development. In this article we are going to cover some of the more advanced DirectX principles such as transforms, matrices, culling and clipping.

Before we start, I need to cover a couple of items that were brought to my attention via feedback from the readers (thank you everyone for taking the time to do this) and changes not directly related to the items covered in this article.

Code Cleanup

These changes have already been integrated into the code for this article.

  1. I updated to the June 2005 version DirectX SDK. I did have an issue with the DirectX assemblies being added to the Global Assembly Cache (GAC), so if you have any build errors, update the references to: C:\Program Files\Microsoft DirectX 9.0 SDK (June 2005)\Developer Runtime\x86\DirectX for Managed Code.
  2. In the last article you surrounded the IsWindowed setting for the PresentParameters with a #if DEBUG statement. To enable this option you must go to the Project settings and enable it:
    • Right-click the BattleTank2005 project and select Properties.
    • Select the Build tab.
    • In the general section, check the Define DEBUG constant option.
  3. Change the color in the device.Clear method call from DarkBlue to Black.
  4. Add the following code at the very end of the GameEngine constructor to make the form a more standard size.

Visual C#

// force the window to a standard size
// the provides the correct aspect ratio of 1.33
this.Size = new Size ( 800, 600 );

Visual Basic

'force the window to a standard size
' the provides the correct aspect ratio of 1.33
Me.Size = New Size(800, 600)
  1. Change the Startup position of the form to CenterScreen.
    • Right-click the GameEngine form and select View Designer.
    • In Properties, change the StartPosition property to CenterScreen

Now we can actually draw something on the screen and start creating the game.

Drawing the targeting crosshairs.

In BattleTank 2005 we are sitting inside of a tank and looking through the targeting scope at the world around us. We use the targeting crosshairs to make it easier to hit and destroy the enemy. In real life the crosshairs are fixed in the optics of the cannon, so they are always visible and located in the same spot when looking through the targeting scope as a Heads-up Display (HUD).

In our game we have two choices when drawing the crosshairs.

  1. Draw them directly onto the center of the screen using screen coordinates.
  2. Draw them using world coordinates and ensure that the view point is located such that they are visible and centered on the screen.

The tradeoff here is between speed and extensibility. If we choose the first option we do not have to transform the coordinates, since they are already in screen coordinates, which is faster. In effect, we are removing the targeting crosshairs from the model space we are creating. The downside is that if we chose to change the game later on and allow the player to leave the tank, the crosshairs would also be visible when they walk around. The second option requires the coordinates to be adjusted and transformed, but provides us with the ultimate flexibility to change the game later on.

I am going to use option 2 because it is the most flexible and I am not going to waste time trying to optimize the game for speed before knowing that it is slow. When you write a game you will be faced with design choices like this and you have to understand the tradeoffs you are making.

Before we move on, let's define a model that will make it easier to describe and (we hope) understand the terms we are going to cover in this article.

A Model

Imagine an empty room. I determine where in the room the X, Y, Z origin is located. In this example let’s say that the front left corner of the room is the origin in this world. This room becomes my World Space. (A real world space is infinite, but for now we can make do with the finite space of the room.)

World Space: An infinite three-dimensional Cartesian space. You place your objects anywhere you want in this world to create the environment you want.

Now I place a chair in the room at a predefined location. Each point on the chair can be accurately described using a set of Cartesian coordinates (X, Y, Z) and optional information about each point such as color (Color) and texture (Tu, Tv). If you think back to the last article you will notice that this makes each point a vertex. Since we have lots of separate vertices, we store all of them in a vertex array. When this array is loaded into memory it becomes a vertex buffer.

Vertex buffer

Vertex buffers are ideally suited for the complex transformations that DirectX needs to perform. DirectX provides a number of predefined vertex types representing the most common vertex formats. These types are defined as structures in the CustomVertex class.

We are going to use the PositionColored vertex for the targeting crosshairs. This vertex provides X, Y, Z properties and a Color property, which is exactly what we need. This vertex also defines the coordinates' world space rather than screen space, which is what we decided to do.

Add the following methods to the GameEngine class after the OnPaint method.

Visual C#

private CustomVertex.PositionColored[] CreateCrossHairVertexArrayTop ( )
{
CustomVertex.PositionColored[] crossHairs =
new CustomVertex.PositionColored[7];
float zval = 0f;

// top of targeting crosshairs crossHairs[0].Position = new Vector3 ( -1f, 1f, zval );
crossHairs[1].Position = new Vector3 ( -1f, 2f, zval );
crossHairs[2].Position = new Vector3 ( 0f, 2f, zval );
crossHairs[3].Position = new Vector3 ( 0f, 3f, zval );
crossHairs[4].Position = new Vector3 ( 0f, 2f, zval );
crossHairs[5].Position = new Vector3 ( 1f, 2f, zval );
crossHairs[6].Position = new Vector3 ( 1f, 1f, zval );

crossHairs[0].Color = Color.Green.ToArgb ( );
crossHairs[1].Color = Color.Green.ToArgb ( );
crossHairs[2].Color = Color.Green.ToArgb ( );
crossHairs[3].Color = Color.Green.ToArgb ( );
crossHairs[4].Color = Color.Green.ToArgb ( );
crossHairs[5].Color = Color.Green.ToArgb ( );
crossHairs[6].Color = Color.Green.ToArgb ( );

return crossHairs;
}

private CustomVertex.PositionColored[] CreateCrossHairVertexArrayBottom ()
{
CustomVertex.PositionColored[] crossHairs =
new CustomVertex.PositionColored[7];

// bottom of targeting crosshairs float zval = 0f;

// bottom of targeting crosshairs crossHairs[0].Position = new Vector3 ( 1f, -1f, zval );
crossHairs[1].Position = new Vector3 ( 1f, -2f, zval );
crossHairs[2].Position = new Vector3 ( 0f, -2f, zval );
crossHairs[3].Position = new Vector3 ( 0f, -3f, zval );
crossHairs[4].Position = new Vector3 ( 0f, -2f, zval );
crossHairs[5].Position = new Vector3 ( -1f, -2f, zval );
crossHairs[6].Position = new Vector3 ( -1f, -1f, zval );

crossHairs[0].Color = Color.Green.ToArgb ( );
crossHairs[1].Color = Color.Green.ToArgb ( );
crossHairs[2].Color = Color.Green.ToArgb ( );
crossHairs[3].Color = Color.Green.ToArgb ( );
crossHairs[4].Color = Color.Green.ToArgb ( );
crossHairs[5].Color = Color.Green.ToArgb ( );
crossHairs[6].Color = Color.Green.ToArgb ( );

return crossHairs;
}

Visual Basic

Private Function CreateCrossHairVertexArrayTop() As _
CustomVertex.PositionColored()
Dim crossHairs(7) As CustomVertex.PositionColored
Dim zval As Single = 0.0F
' top of targeting crosshairs crossHairs(0).Position = New Vector3(-1.0F, 1.0F, zval)
crossHairs(1).Position = New Vector3(-1.0F, 2.0F, zval)
crossHairs(2).Position = New Vector3(0.0F, 2.0F, zval)
crossHairs(3).Position = New Vector3(0.0F, 3.0F, zval)
crossHairs(4).Position = New Vector3(0.0F, 2.0F, zval)
crossHairs(5).Position = New Vector3(1.0F, 2.0F, zval)
crossHairs(6).Position = New Vector3(1.0F, 1.0F, zval)

crossHairs(0).Color = Color.Green.ToArgb()
crossHairs(1).Color = Color.Green.ToArgb()
crossHairs(2).Color = Color.Green.ToArgb()
crossHairs(3).Color = Color.Green.ToArgb()
crossHairs(4).Color = Color.Green.ToArgb()
crossHairs(5).Color = Color.Green.ToArgb()
crossHairs(6).Color = Color.Green.ToArgb()

Return crossHairs
End Function Private Function CreateCrossHairVertexArrayBottom() As _
CustomVertex.PositionColored()
Dim crossHairs(7) As CustomVertex.PositionColored

' bottom of targeting crosshairs Dim zval As Single = 0.0F

' bottom of targeting crosshairs crossHairs(0).Position = New Vector3(1.0F, -1.0F, zval)
crossHairs(1).Position = New Vector3(1.0F, -2.0F, zval)
crossHairs(2).Position = New Vector3(0.0F, -2.0F, zval)
crossHairs(3).Position = New Vector3(0.0F, -3.0F, zval)
crossHairs(4).Position = New Vector3(0.0F, -2.0F, zval)
crossHairs(5).Position = New Vector3(-1.0F, -2.0F, zval)
crossHairs(6).Position = New Vector3(-1.0F, -1.0F, zval)

crossHairs(0).Color = Color.Green.ToArgb()
crossHairs(1).Color = Color.Green.ToArgb()
crossHairs(2).Color = Color.Green.ToArgb()
crossHairs(3).Color = Color.Green.ToArgb()
crossHairs(4).Color = Color.Green.ToArgb()
crossHairs(5).Color = Color.Green.ToArgb()
crossHairs(6).Color = Color.Green.ToArgb()

Return crossHairs
End Function

Remember that the coordinates for the Position property of each vertex are defined in world space coordinates. We will transform them to screen coordinates in a while. Also note that you must call the ToArgb method to convert Color to the 32-bit integer format required by DirectX.

Before we continue we need to let the device know which type of vertex we chose. We accomplish this by setting the VertexFormat property of the Device class to the Format property of the vertex we used. This property determines the fixed function pipeline the device will use. Don’t worry what exactly that is for right now; you just need to know that we are using the position and colored pipeline.

In the OnPaint method, immediately following the device.Clear method add the following code.

Visual C#

device.VertexFormat = CustomVertex.PositionColored.Format;

Visual Basic

device.VertexFormat = CustomVertex.PositionColored.Format

With the crosshairs defined, we must now tell the device to actually render the object described in the vertex buffer to the screen. This is accomplished using the DrawUserPrimitives method of the device class. So what are Primitives?

Drawing Primitives

Drawing Primitives are collections of vertices that define a single three-dimensional object. There are six primitives in DirectX listed in the PrimitiveType enumeration.

  1. Line List: Mainly used for adding Heads-up Display (HUD) information to a screen. The primitive count is equal to the number of points divided by two. The number of points must be even for this type to work.
  2. Line Strip: This has the same uses as the Line List but renders a single continuous line. The primitive count is equal the number of vertices minus 1.
  3. Point List: Mainly used for rendering individual points in particle images such as explosions or stars in the night sky. The primitive count is equal to the number of points in the vertex buffer.
  4. Triangle Fan: This is most useful when drawing an oval object.
  5. Triangle List: This is the most commonly used primitive. The primitive count is the number of vertices divided by three,
  6. Triangle Strip: These are most useful when rendering rectangular objects.

In our case we choose the LineStrip type to draw the crosshairs to the screen since it is an HUD. In the OnPaint method, immediately following the device.Clear method add the following code.

Visual C#

device.DrawUserPrimitives ( PrimitiveType.LineStrip, 6, 
CreateCrossHairVertexArrayTop ( ) );
device.DrawUserPrimitives ( PrimitiveType.LineStrip, 6,
CreateCrossHairVertexArrayBottom ( ) );

Visual Basic

device.DrawUserPrimitives(PrimitiveType.LineStrip, 6, _
CreateCrossHairVertexArrayTop())
device.DrawUserPrimitives(PrimitiveType.LineStrip, 6, _
CreateCrossHairVertexArrayBottom())

The DrawUserPrimitives method requires us to pass in the PrimitiveType, the count of primitives to render, and the source of the vertex data for the object. Since we are using the LineStrip primitive, the seven points in each vertex buffer create six lines.

Up to this point we have not actually drawn anything to the screen, just cleared the device; so we need to tell the device that we are planning to do so. This is accomplished with the BeginScene method of the device class.

Begin Scene/End Scene

As I mentioned in the first article, there are a lot of correlations between movies and a DirectX game. The first we encountered was frame. Now we are going to add Scenes and little later a camera.

We use the BeginScene and EndScene methods of the device to define the starting and ending points of a scene. The BeginScene method prepares the device for the actions that follow by locking the back buffer. The EndScene method tells the device that we are finished drawing and unlocks the back buffer. You must always call EndScene after calling BeginScene otherwise the back buffer will remain locked. The BeginScene and EndScene methods work closely together with the Present method to mange the back buffer; if one of these method calls fails the others will fail also.

In the OnPaint method of the GameEngine class, add calls to BeginScene and EndScene. The call to BeginScene needs to occur immediately after the Clear method call.

Visual C#

device.Clear ( ClearFlags.Target, Color.Black, 1.0f, 0 );
// Tell DirectX we are about to draw something device.BeginScene ( );

Visual Basic

device.Clear(ClearFlags.Target, Color.Black, 1.0F, 0)
' Tell DirectX we are about to draw something device.BeginScene()

And the EndScene method needs to be called immediately before the Present method call.

Visual C#

// Tell DirectX that we are done drawing
device.EndScene ( );
// Flip the back buffer to the front
device.Present ( );

Visual Basic

' Tell DirectX that we are done drawing
device.EndScene()
' Flip the back buffer to the front
device.Present()

Now we are almost done. The last step remaining is to convert the world coordinates of each three-dimensional object into screen coordinates.

In the last article we covered a lot of the DirectX terminology you need to understand. We are almost done with new terms, but before we can successfully render a 3-D world to the screen we need to cover one last set of definitions.

Lights, Camera, Action

In our model we have described each point on the chair using Cartesian coordinates and a color value and stored these points in a vertex buffer. But DirectX still can’t render the chair for me. Why? The missing pieces of information are our location in the room and the direction we are looking. We also need to determine how to handle the projection. Only with this information can the 3-D world be converted into the 2-D picture displayed on the screen.

In DirectX our (the viewer's) location is called the camera location and it is defined by the View Matrix. From now on consider the terms view and camera to be synonymous. The projection is the way distance is applied to the objects and can be compared to the lens setting of the camera.

Cameras are very powerful tools in creating cool 3-D games. You can attach the camera to a moving object to get the feeling of actually being inside, or offset it slightly to the rear to get a chase view. You can also set up multiple static cameras in your world and view the action by switching from camera to camera.

All of the computations involved in converting the world coordinates to screen coordinates are performed used a Matrix. These computations are called Transforms. Almost all of the heavy lifting performed in DirectX is the transformation of coordinates using Matrices.

Transforms (http://en.wikipedia.org/wiki/Transformation_%28mathematics%29): Transformations change the coordinates of three-dimensional objects based on the view, projection type and world transform specified. These transformations are accomplished via a set of 4x4 matrices.

Matrix (http://en.wikipedia.org/wiki/Matrix_%28mathematics%29): A rectangular table of numbers that is very efficient in transformations.

You could spend a lot of time understanding the exact details of transformations with matrices. Understanding how they work is important, but you will not have to perform any manual calculations on your own, so we can skip the details for now. The Matrix class contains a number of the most common methods for manipulating matrices.

The first step is placing and orienting the camera in our three-dimensional world.

View Transform

The view matrix defines the location of the camera and the orientation of the camera (by specifying a target). The view matrix also includes a value to determine which direction is considered to be up in our world. This is almost always the Y axis. You can either define your own matrix or use the built-in Matrix.LookAtLH and Matrix.LookAtRH methods. Since we are using the left handed coordinate system we naturally use the LookAtLH method (LH stands for Left Handed). Even if you do not explicitly define a view, then DirectX uses a default view.

In the OnPaint method of the GameEngine class, add the following code immediately after the device.Clear method call.

Visual C#

device.Transform.View = Matrix.LookAtLH(new Vector3 (0, 0, 5f), 
new Vector3(0, 0, 0), new Vector3(0, 1, 0));

Visual Basic

device.Transform.View = Matrix.LookAtLH(New Vector3(0, 0, 5.0F), _
New Vector3(0, 0, 0), New Vector3(0, 1, 0))

In Battle Tank 2005 we are placing the camera at the origin (0,0) and slightly forward on the Z axis. We then point the camera at the origin, and identify the Y axis as up by providing a value for Y.

After placing and orienting the camera we need to define the projection we are going to use.

Projection Transform

In DirectX there are two types of projections to choose from:

The Perspective projection is the most commonly used projection and is how humans see the world. In this projection objects appear smaller the further away from us they are and become deformed at some distance (like a road appears to converge toward the horizon).

Orthogonal projection, on the other hand, ignores distance (the Z value) so items retain their size regardless of their distance from the camera.

We are going to use the Perspective Projection in BattleTank 2005. The projection determines how the vertices of the objects in the viewing frustum (http://en.wikipedia.org/wiki/Viewing_frustum) are transformed. The viewing frustum defines the three-dimensional space in which objects are visible to the camera. You can visualize this as a pyramid with the top cut off. The base of the pyramid is the far plane, the top the near plane, and the field of view is the angle at the apex. To perform this transformation we need to know four pieces of information.

  1. Field of View (FoV): This is typically 45 degrees or ¼ of p (Math.PI / 4). Reducing the FoV value is like zooming in on the scene, while a larger FoV value is like zooming out. A value greater then 45 degrees is like looking at the scene through a fisheye lens.
  2. Aspect Ratio: This is identical to the aspect ratio of your TV or Monitor and is always calculated as the viewport Width/Height. The viewport is nothing other than the form onto which we are rendering the game. Standard aspect ratios for computer screens are 1.33 (640x480 or 1280x1024).
  3. Near Clipping Plane: Objects closer to the camera than this plane are not rendered.
  4. Far Clipping Plane: Objects beyond this plane are not rendered.

    Radians (http://en.wikipedia.org/wiki/Radian): In DirectX most angles are expressed as radians rather than degrees. To convert from degrees to radians, simple multiply the degree value by p/180, where n is the measure of the angle in degrees. The DirectXDX library contains helper functions to perform the conversion for you in the Geometry class.

Using these values and some fancy math, DirectX transforms the vertices of each object from world coordinates to screen coordinates. To a certain extent, this transformation is what we do in our heads when we draw a picture of a three-dimensional space. We draw distant objects smaller, even though the actual object has not really changed its size, and we distort objects further away.

In the OnPaint method of the GameEngine class, add the following code immediately after the device.Transform.View method call added in the previous step.

Visual C#

device.Transform.Projection = Matrix.PerspectiveFovLH ( (float)Math.PI / 
4, this.Width / this.Height, 1.0f, 100.0f);

Visual Basic

device.Transform.Projection = Matrix.PerspectiveFovLH ((single)Math.PI / _
4, Me.Width / Me.Height, 1.0f, 100.0f)

For BattleTank 2005 we used the traditional setting for the FoV of 45 degrees. Next we set the aspect ratio base and then define the viewing frustum as between 1 and 100 in our world coordinate space. The values for the clipping planes define the size of our visible world and can represent anything we want. One unit equals 10 meters in BattleTank 2005 so we have a 1 kilometer playing field.

At this point you have to use some common sense when defining the viewing frustum. We could easily declare that each unit equals one kilometer, making the viewable area 100 kilometers deep. However, if we make the tanks regular size, you will never be able to see them beyond a couple of kilometers, so why waste resources rendering them to the screen if the player cannot see them?

While the View and Projection Matrices describe the camera and camera lens, the world transformation matrix converts the model space coordinates into world space coordinates. These world space coordinates are then converted to screen coordinates in the View and Projection transforms.

World Transform

The last transform is a world transform. This transforms the object we are rendering from model space into world space.

In a world transform you can move, rotate and scale each object. This transform applies to all objects drawn after setting the transform, until a new transform is specified.

Model Space: We define each object by defining each vertex with respect to the model.

In BattleTank 2005 we are not going to use the World transform at this time, but I have included a transform in the code for this article that uses a test cube for you to experiment with. I suggest you play with changing the values for all three transforms and see how the results look on the screen. That is the easiest way to really understand what the various settings do. See the Experimenting section for details on how to do this.

Lights

Whenever we draw a primitive using world coordinates, DirectX uses lighting to determine the color of each pixel. But since we have not yet defined a light source, we can simply turn the lights off at this time. If we do not turn lighting off, then DirectX assumes no lights are shining on the scene and renders each pixel as black.

In the OnPaint method of the GameEngine class, add the following code immediately after the device.Transform.Projection method call added in the previous step.

Visual C#

// turn off the light source
device.RenderState.Lighting = false;

Visual Basic

' turn off the light source
device.RenderState.Lighting = False

In addition to controlling the lighting, the RenderState also allows us to control the culling behavior.

Culling

Culling is an operation that eliminates entire objects (unlike clipping, which removes only portions) from the scene that fall outside of the viewing frustum to reduce the total set of objects to render. The overall goal is of course speed; by eliminating the non essential objects the scene can be rendered faster.

Clipping: Clipping discards portions of any single object that fall outside of the viewing frustum. Clipping is automatically managed by DirectX and requires no further intervention.

When drawing a three-dimensional object, DirectX does not render the primitives (triangles) that comprise the faces of those objects that do not face the camera. This is called back face culling.

DirectX determines which side of an object is facing the camera by using the order (winding) of the vertices. If you choose either clockwise or counterclockwise, then the vertices that are wound the opposite are on the back of the object and are culled. The default mode is counterclockwise culling, so you need to make sure to define your vertices in a clockwise order.

The culling options are set in the RenderState by assigning one of the Cull enumeration settings to the device.RenderState.CullMode property.

In the OnPaint method of the GameEngine class, add the following code immediately after device.RenderState.Lighting code added in the previous step.

Visual C#

// Turn off backface culling
device.RenderState.CullMode = Cull.None;

Visual Basic

 ' Turn off backface culling
device.RenderState.CullMode = Cull.None

In the code that accompanies this article I have added a couple of extra items to make it easier for you to experiment with the various settings we just covered.

Experimenting

To remove the HUD display, comment out the following lines: 122 and 123 (in VB.NET the lines are 92 and 93).

You can also display a rotating cube by un-commenting lines 98, 101, 120 and commenting line 95 (VB.NET: 68, 71, 87, 90 and 65). You can change any of the values in the models or adjust the View, Projection and World transforms to see the effect of your changes.

Line Numbers: To turn on line numbers, go to Tools | Options | Text Editor | All Languages and check the Line Numbers box in the Display group. If you don't see the full options tree, just check the Show all Settings option in the lower left of the Options dialog.

Summary

In these first three articles we have covered a lot of ground. The steps in the upcoming articles build upon the foundation laid in these first three articles. At this point you should know how to create a device and hook it to a Windows form and create a game loop using the OnPaint and Invalidate methods. You should be able to create and draw your own three-dimensional objects using vertex buffers and the DrawUserPrimitives method, set up a camera, and transform the models into world space.

If something is unclear at this time, you should review the links provided in the articles, the DirectX SDK, or go to one of the resources listed on the web. Once you have fully understood these principles, the rest of the game development process will be much easier.

With this knowledge you are ready to perform all but the most advanced operations in DirectX. The only steps required to add more objects to our world in BattleTank 2005 are to define them in model space, store them in a vertex buffer, and render them to the screen. As you imagine, complex objects consist of thousands of separate vertices, and defining them all in code is almost impossible, very error prone and extremely boring. So in the next article I will cover how to accomplish this task more efficiently. In the next article we are also going to finish of the last graphic pieces and then focus on controlling the tank using the classes in the DirectInput namespace.

Until then: Happy coding!

]]>
Filed under: ,

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

# Justin said on April 21, 2007 11:25 PM:

Everything works fine until I add the following line of code:

device.DrawUserPrimitives ( PrimitiveType.LineStrip, 6,

   CreateCrossHairVertexArrayTop ( ) );

device.DrawUserPrimitives ( PrimitiveType.LineStrip, 6,

   CreateCrossHairVertexArrayBottom ( ) );

After that every time I try to run the program it hi-lights the first two lines of code and says "InvalidCallException was unhandled" or "InvalidCallException occured". I don't know if I set up something wrong or what but I can't get it to work after that.

# kyle said on April 28, 2007 12:24 AM:

going great so far...I think I have the code in all the right places.....cant wait to get a finished product

# M said on May 4, 2007 11:53 PM:

I´m new to C# and could not find a solution...

the lines:

           device.DrawUserPrimitives(PrimitiveType.LineStrip, 6, CreateCrossHairVertexArrayTop());

           device.DrawUserPrimitives(PrimitiveType.LineStrip, 6, CreateCrossHairVertexArrayBottom());

retuns an InvalidCallException

what can I do?

# Mike said on May 6, 2007 7:13 PM:

When I run the program it crashes on:

device.DrawUserPrimitives(PrimitiveType.LineStrip, 6, CreateCrossHairVertexArrayTop());

All it says is in the debugger is "InvalidCallException was unhandled"  

I'm using C# and dx SDK April 2007

# juliano said on May 23, 2007 9:23 AM:

Hi everyone. I'm completely new to directX, but I'm an experienced programmer (c#, java, php), and also worked with 3D modeling and video post-production. I'm having problems understanding the units and coordinates system, and some else.

The questions are:

1 - We always work with gerenic units? (If so,we must always know the scale we are dealing with)

2 - I tried to modify the crosshair object. Ok I did it, but I'm not comfortable with this yet. I think I need a clue on where is the world origin.

3 - Where did we define DirectX stage would occupy all the form size? I ask this because I want to know how to create a form (like 800x600) but filled with form controls (Like combos and buttons ), and the directX stage would be a 320x240 box next to the controls, so I can control drawing into the directx through the buttons outside the 3d space.

Thank you a lot, and congratulations on this tutorial series, it is really good.

# Carl said on May 23, 2007 10:58 AM:

I had to compare the sample code(download top of page) to my own work to get my code working.

# waco stimson said on May 26, 2007 1:53 PM:

haveing some troubles witht the CustomVertex.PositionColored[] crossHairs = new CustomVertex.Postion colored[7];

line. the first CustomVertex is the problem and i cannot figure it out. it says a get or set accessor expected. but im not sure how to fix it any help would be hot. thanks

# Dast said on May 27, 2007 1:07 PM:

Can anyone clue me in here as to why it is a good idea to constantly 'new' arrays of verticies rather than just creating them once and saving them?  Is there something about the pipeline in DirectX that makes this a requirement?  Obviously, they will be cleaned up by gc at some point, but constantly creating new copies during the render loop seems... odd.

# Leo Sieben said on June 6, 2007 2:39 AM:

Juliano -

1) Yes, somewhere someone "defines" what units are. This definition doesn't happen anywhere in code, however; it is implicit. Essentially a 3D artist models some object, say a house. He will create it in arbitrary units in the modeling package, so he follows guidelines set by his company. The company I work for, Gearbox Software, uses centimeters. A 30-ft wide house would be 914 cm wide, so it should be 914 units wide in the modeling app.. Typically the lead artist and the lead programmer agree on a convention (or it's been handed down by the company).

2) The world origin, as this code is written in this article when I read it, appears in the center of the screen. This is because the Matrix.LookAtLH() function's second parameter is (0,0,0), the origin.

You might find it a fun exercise to make a debugging function called DrawCoordinateAxes().  This function might draw three lines:

(0,0,0) --> (1,0,0)   //An x-axis

(0,0,0) --> (0,1,0)   //A y-axis

(0,0,0) --> (0,0,1)   //A z-axis

Then since all three of these lines start from (0,0,0), you could visually "see" the origin, and get a feel for which way is up.

3) DirectX is told to hog the whole Form when, in the CreateDevice() function, 'this' was specified for the third parameter.  If you wanted to have normal widgets on the side of the rendering viewport, try to make a typical user interface, and have one of the components be a Panel, then pass that Panel to the CreateDevice function...

You'll get to have a lot of "fun" getting the app's render loop to play nice with the other components, tho.

===================================

Dast -

You're right, it's an exquisitely inefficient approach. But the author is not trying to make a production-quality clone, he is just taking small steps for the benefit of the readers who are new to all of this.  Some of my personal thoughts on ways to make this a lot better, if you were curious:

A) It would be much better to cache the vertices and reuse them.

B) It would be nice if the crosshair's design wasn't hardcoded. Make the app data-driven so that it reads a file.

C) Prolly the best idea is to make the crosshair design part of a texture and then texture a simple quad in screen-space. Then a designer or artist could tweak the crosshair in an art program, and the code never needs to change.

The author is trying to keep the example simple. But good eye. =)

# Marky said on June 12, 2007 6:35 AM:

Great Tutorial!

But I have one big problem when I try to compile the program by the end of this chapter. I always get an error saying the following:

Microsoft.DirectX.Direct3D.InvalidCallException wurde nicht behandelt.

 Message="Fehler in der Anwendung."

 Source="Microsoft.DirectX.Direct3D"

 ErrorCode=-2005530516

 ErrorString="D3DERR_INVALIDCALL"

 StackTrace:

      bei Microsoft.DirectX.Direct3D.Device.DrawUserPrimitives(PrimitiveType primitiveType, Int32 primitiveCount, Object vertexStreamZeroData)

      bei BattleTank_2007.GameEngine.OnPaint(PaintEventArgs e) in C:\Dokumente und Einstellungen\NB_Geraet\Eigene Dateien\Visual Studio 2005\Projects\BattleTank 2007\BattleTank 2007\GameEngine.cs:Zeile 83.

      bei System.Windows.Forms.Control.PaintWithErrorHandling(PaintEventArgs e, Int16 layer, Boolean disposeEventArgs)

      bei System.Windows.Forms.Control.WmPaint(Message& m)

      bei System.Windows.Forms.Control.WndProc(Message& m)

      bei System.Windows.Forms.ScrollableControl.WndProc(Message& m)

      bei System.Windows.Forms.ContainerControl.WndProc(Message& m)

      bei System.Windows.Forms.Form.WndProc(Message& m)

      bei System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)

      bei System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)

      bei System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)

      bei System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)

      bei System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(Int32 dwComponentID, Int32 reason, Int32 pvLoopData)

      bei System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)

      bei System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)

      bei System.Windows.Forms.Application.Run(Form mainForm)

      bei BattleTank_2007.Program.Main() in C:\Dokumente und Einstellungen\NB_Geraet\Eigene Dateien\Visual Studio 2005\Projects\BattleTank 2007\BattleTank 2007\Program.cs:Zeile 17.

      bei System.AppDomain.nExecuteAssembly(Assembly assembly, String[] args)

      bei System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)

      bei Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()

      bei System.Threading.ThreadHelper.ThreadStart_Context(Object state)

      bei System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)

      bei System.Threading.ThreadHelper.ThreadStart()

Then the program crashes.

Can anyone help me?

Thanks a lot

# Hoazl said on June 27, 2007 4:41 PM:

Hi there,

you have to add the line

device.VertexFormat = CustomVertex.PositionColored.Format;

between

device.BeginScene();

and

device.DrawUserPrimitives ( .... );

this should help!

Greetings,

Hoazl

# Nick said on July 5, 2007 3:50 PM:

Can anyone tell me why its saying it doesn't recognize Vector3 and Matrix? Is there a using statement I forgot to put at the top?

# Sylas said on July 9, 2007 3:56 PM:

An unhandled exception of type 'Microsoft.DirectX.Direct3D.InvalidCallException' occurred in Microsoft.DirectX.Direct3D.dll

Additional information: Error in the application.

no errors accept after I try to run the app. Then I get the above exception?

It occurs on this:

device.DrawUserPrimitives(PrimitiveType.LineStrip, 6,

               CreateCrossHairVertexArrayTop());

# Steve said on July 19, 2007 7:58 PM:

Anyone else getting a runtime error after you run this one?  I get  "An unhandled exception of type 'Microsoft.DirectX.Direct3D.InvalidCallException' occurred in Microsoft.DirectX.Direct3D.dll

Additional information: Error in the application"

this only occurs during runtime as it compiles fine... I am running DirectX SDK June 2007 if that matters

# Steve said on July 19, 2007 8:00 PM:

the code it points out is at

"device.DrawUserPrimitives(PrimitiveType.LineStrip, 6, CreateCrossHairVertexArrayTop());"

# frizey said on July 23, 2007 5:31 AM:

hi it is very hard to read the intructions straight off the net it would be a good idea if they were avalible to download.

# Perrin said on August 6, 2007 8:21 PM:

Why does changing the field of view angle zoom in or out?

# Goki said on August 10, 2007 2:49 AM:

Ya, a bit of confusion with Greek letters here, I see.  To convert degrees to radians, multiply the degrees with Pi/180, not with Ro (as indicated in the text).

# tierro said on August 12, 2007 11:00 AM:

To make a long story very, very short.

IT doesn't make sense. I can get nothing to work. Not in C#, neither in VB. Thousands of errors while i can compile anything else whithout a problem. If you want to drive people nuts, go work at a callcenter.

# Kevin said on August 13, 2007 8:21 PM:

i have come up with 19 errors one is Matrix does not exist and vector3 could not be found any help would be gr8

THANKS

Kevin

# Jesse said on August 15, 2007 3:35 PM:

when i try to run the program i get a runtime error at this line:

device.DrawUserPrimitives(PrimitiveType.LineStrip, 6, CreateCrossHairVertexArrayTop());

the debugger says InvalicCallException was unhandled, and Error in the application.  When i click details i get not much useful information for me, it just displays the properties of the invalidcallexception that i dont understand (probably because im pretty new with this stuff--which is why im here in the first place).

anyone get a similar error and know how to fix it?

# Ryan said on August 31, 2007 2:14 PM:

Anyone have any idea how to print the individual frames on this thing. How do I print this stuff so I don't waste too much paper? "Print frames individually" doesn't seem to work on this; Is anyone using firefox to print this stuff out correctly?

# cocoflop said on September 4, 2007 8:13 AM:

"1 - We always work with gerenic units? (If so,we must always know the scale we are dealing with)"

It's true that DirectX uses generic units as you mentioned. On the other hand, think of the functionality that some 3D modeling software require to display a 3D world, a choice between DirectX or OpenGL as a rendered device is used. There is a compatibility/simplicity issue here, it seems quite logical for: 1 max unit = 1 dx/gl unit.

"2 - I tried to modify the crosshair object. Ok I did it, but I'm not comfortable with this yet. I think I need a clue on where is the world origin."

The origin of all the 3D world is located at [0,0,0]. Each vertex of our crosshair has it's own position so it can form a nice shape (Local position). After we formed a nice crosshair, all that left to do is to find the exact location to place the crosshair, just in-front of the camera (Global position). That means that you construct each 3D piece individually using it's own local dimensions, afterwards simply "throw" this object in the Direct3D world so it can take a global position.

"3 - Where did we define DirectX stage would occupy all the form size? I ask this because I want to know how to create a form (like 800x600) but filled with form controls (Like combos and buttons )".

It's the method this.SetStyle() inside the constructor method. There you can find method's parameter ControlStyles.AllPaintingInWmPaint.

You can give whatever functionality you wish in your Form. That means that you can put a GUI component that deals with drawing and rendering stuff. I am not sure to tell you though, it could be PictureBox but I recommend you to browse the MSDN library that it's fully documented.

I hope that information helped.

I agree with you btw on this great tutorial, I am a DX noob myself, though I have be programming in Blitz3D for many years.

# John said on October 3, 2007 8:53 PM:

I am having the same problem as waco it says that a get or set accessor is expected can some one explain

# Ryan said on October 23, 2007 2:23 PM:

Any idea how we're supposed to read these coordinates?

crossHairs(0).Position = New Vector3(1.0F, -1.0F, zval)

what does the .of stand for? How are these numbers read?

What does the "f' stand for in front of each number? Are these left hand cartesian coordinates?

Drop me a line at tramel_e4196@sbcglobal.net if you know the answers. I've been struggling with this for weeks now.

crossHairs[6].Position = new Vector3 ( -1f, -1f, zval );

# Daniel said on November 4, 2007 4:36 PM:

'F' indicates that the value is of type float.

# djhayman said on November 6, 2007 4:37 AM:

Ryan:

The "f" on the end of a number tells the compiler that it is a floating-point number, not an integer.

The ".0f" is just saying "1.0 as a float".

Likewise, you could say "1.5f" which means "1.5 as a float".

# WydOne said on November 13, 2007 4:30 AM:

At this point you also need a:

using Microsoft.DirectX;

at the beginning of your GameEngine.cs file in order to have the Vertex3 and Matrix classes.

# Jon said on November 28, 2007 5:28 AM:

I was getting the runtime error

An unhandled exception of type 'Microsoft.DirectX.Direct3D.InvalidCallException' occurred in Microsoft.DirectX.Direct3D.dll

So I moved the line

device.VertexFormat=CustomVertecPositionColored.Format

directly after the device.BeginScene() and before device.DrawUserPrimitives

this sorted it out for me

# Peter Keating said on November 28, 2007 8:28 AM:

To fix the exception mentioned by many people you must move this line:

_device.VertexFormat = CustomVertex.PositionColored.Format;

above these lines:

_device.DrawUserPrimitives(PrimitiveType.LineStrip, 6, CreateCrossHairVertexArrayTop());

_device.DrawUserPrimitives(PrimitiveType.LineStrip, 6, CreateCrossHairVertexArrayBottom());

Hope this helps :)

# jtrosp said on December 11, 2007 1:23 AM:

I am new to writing code and this has been a challenging experience so far.  In each part, I have had to use both the tutorial and the help comments to work the bugs out of my code.  Thanks All!

Due to my experience I will not be able to help others with the code.  However, to other newbes you might want to cut and past the tutorial to a word document (and Print).  If you notice some of the longer lines of code on the right side get cut off.  If you cut and past you will be able to read them.

I hope this helps.

# prem said on January 15, 2008 5:41 AM:

Everything works fine until I add the following line of code:

device.DrawUserPrimitives ( PrimitiveType.LineStrip, 6,

  CreateCrossHairVertexArrayTop ( ) );

device.DrawUserPrimitives ( PrimitiveType.LineStrip, 6,

  CreateCrossHairVertexArrayBottom ( ) );

# janaka said on January 17, 2008 2:30 PM:

I have the same problem as prem; the error messages I get is :

The name 'CreateCrossHairVertexArrayTop' does not exist in the current context

The name 'CreateCrossHairVertexArrayBottom' does not exist in the current context

Those lines of code appear right after:

device.BeginScene();

thanks

# akaim said on January 19, 2008 3:24 PM:

To fix the exception mentioned by many people you must move this line:

_device.VertexFormat = CustomVertex.PositionColored.Format;

above these lines:

_device.DrawUserPrimitives(PrimitiveType.LineStrip, 6, CreateCrossHairVertexArrayTop());

_device.DrawUserPrimitives(PrimitiveType.LineStrip, 6, CreateCrossHairVertexArrayBottom());

Hope this helps :)

=================================

it doesn#t help at all :/

# Link_Dead said on August 14, 2008 12:22 AM:

ok to fix the "InvalidCallException" in the OnPaint code block, make SURE these statements are in this EXACT order...

protected override void OnPaint(PaintEventArgs e)

       {

           deltaTime = FrameworkTimer.GetElapsedTime();

           this.Text = string.Format("Framerate: {0}", FrameRate.CalculateFrameRate());

           this.Invalidate();

           FrameworkTimer.Start();

           device.Clear(ClearFlags.Target, Color.Black, 1.0f, 0);

           device.Transform.View = Matrix.LookAtLH(new Vector3(0, 0, 5f), new Vector3(0, 0, 0), new Vector3(0, 1, 0));

           device.Transform.Projection = Matrix.PerspectiveFovLH((float)Math.PI / 4, this.Width / this.Height, 1.0f, 100.0f);

           device.RenderState.Lighting = false;

           device.RenderState.CullMode = Cull.None;

           device.BeginScene();

           device.VertexFormat = CustomVertex.PositionColored.Format;

           device.DrawUserPrimitives(PrimitiveType.LineStrip, 6, CreateCrossHairVertexArrayTop());

           device.DrawUserPrimitives(PrimitiveType.LineStrip, 6, CreateCrossHairVertexArrayBottom());

           device.EndScene();

           device.Present();

           this.Size = new Size(800, 600);

       }

c/p (copy/paste) if you want, mine were in the wrong order giving me an error. If this does NOT help, email the person, he DOES read his email ;)

# Coding4Fun said on September 11, 2008 4:36 PM:

This is Part 2 of an introductory series on game programming using the Microsoft .NET Framework and managed

# Coding4Fun : Beginning Game Development: Part V - Adding Units said on September 11, 2008 4:37 PM:

PingBack from http://blogs.msdn.com/coding4fun/archive/2006/11/03/941679.aspx

# Coding4Fun : Beginning Game Development: Part I ??? Introduction said on September 11, 2008 4:40 PM:

PingBack from http://blogs.msdn.com/coding4fun/archive/2006/11/02/938703.aspx

# Coding4Fun : Beginning Game Development: Part IV - DirectInput said on September 11, 2008 4:41 PM:

PingBack from http://blogs.msdn.com/coding4fun/archive/2006/11/03/940908.aspx

# Coding4Fun : Beginning Game Development: Part VIII - DirectSound said on September 11, 2008 4:55 PM:

PingBack from http://blogs.msdn.com/coding4fun/archive/2006/11/06/999786.aspx

# biswarup ghosh said on November 3, 2008 8:44 PM:

all prob.s related to error in the foll:

device.DrawUserPrimitives ( PrimitiveType.LineStrip, 6, CreateCrossHairVertexArrayTop ( ) );

device.DrawUserPrimitives ( PrimitiveType.LineStrip, 6, CreateCrossHairVertexArrayBottom ( ) );

gets resolved ONCE YOU DOWNLOAD THE SRC-CODE GIVEN ON THIS PAGE.

on analysing, u'll find out that PROBLEM LIES IN THE POSITION OF THE FOLL:

device.EndScene ( );

device.Present ( );

THESE 2 LINES HAVE TO BE PLACED AFTER THE

device.DrawUserPrimitives(..) LINES OF CODE.

ITS MENTIONED TO INCLUDE EndScene IMMEDIATELY BEFORE THE PresentScene

BUT NO-WHERE IS IT STATED THAT EndScene IS TO BE HAD AFTER DrawUserPrimitives.

HAVING SAID THAT, ITS REALLY A TUTORIAL IN WHICH MOST OF THE THINGS HAVE BEEN STATED EXCEPTIONALLY WELL AND CLEARLY.

AND FURTHERMORE, THE SRC-CODE IS ALWYS ACCOMPANYING THE TUTORIAL SO THAT IF ANYTHING GOES WRONG, THEN SRC-CODE MAY COME TO RESCUE!!!

AND AFTER ALL, THE AUTHOR TOO'S A HUMAN-BEING. A TRIVIAL LITTLE SOMETHING LEFT OUT CAN BE EASILY MANAGED ONCE U FOLLOW THE SOURCE-CODE.

I'D GIVE THUMBS UP TO ALL THE AUTHOR'S EFFORTS.

:)

# Edward said on January 20, 2009 2:14 PM:

Thank you to all who contributed in this tutorial. It still is helping people out there.

# Sharad said on March 13, 2009 2:05 AM:

Yes...indeed - extremely helpful and a superb explanation of how DirectX works :-) Many many thanks!!

# Brian said on June 6, 2009 10:11 PM:

if you get a mmds loader lock problem thing, go to  the debug menu item in your ide, click on Exceptions, go to the managed Debugging assistant and Uncheck the Loader Lock flag

# Dave said on September 9, 2009 5:44 PM:

Well, I pretty much worked all the bugs out within 10-20 minutes and I can even see the rotating cube but this is a real challanging tutorial because of all the co-ordinates being used to draw the shapes. Very brain busting. Is there an easy way to plot out how you are going to create a shape or object and mark out the co-ordinates. For the triangle, the first point is the top part of the triangle, the second is the right side, and the third is the left. Had to play with the numbers just to figure that out.

Leave a Comment

(required) 
(optional)
(required) 

  
Enter Code Here: Required

Search

This Blog

Syndication

Page view tracker