So I’m working on porting Falling Balls over to Windows Phone 7. I have the animated stick figure, the motion code, the blood spatter, and sound effects all working. I’ll eventually need a real device to get the accelerometer stuff working, but in the meantime, I moved onto drawing the balls. In the game, I need to be able to make circles of arbitrary, random sizes. I looked into doing this with textures, starting with a circle bitmap and scaling it up or down, but we all know what happens when you start scaling bitmaps. Although it was a worthy experiment, it came out about as crappy as I expected it would. What I really needed was a way to dynamically draw a primitive circle.
In Flash, we all know how easy it is to draw a circle:
[as3]graphics.drawCircle(c, y, radius)[/as3]
In Objective-C, I had to use some Core Graphics stuff, which, while a bit verbose (like all Objective-C), was eventually pretty straightforward. XNA, on the other hand, was really designed more for drawing textures, i.e. bitmaps. Once you get into drawing primitives like lines and circles, you need to code a lot closer to the metal. It took digging through a bunch of samples and forum and blog posts, but I think I finally distilled the core actions necessary to draw primitives.
On a side note, I think one of the reasons that people (at least seem to) like my tutorials, books, etc. is that I do distil things down to that base level needed to understand it. I personally find it difficult to find a lot of tutorials on the web that do that. For example, the primitive drawing sample code I learned most of this from gives you a class with hundreds of lines of code wrapping all the primitive drawing functions, and tells you to use this class. It’s great, I guess, if you just want some code that you can drag into your project and use, but it doesn’t explain much at all about what’s happening and why, other than a few one line comments. And there’s a whole lot of extra stuff going on there to make it flexible and reusable – good code, but it adds to the complexity if all you’re trying to do is learn what’s going on.
Drawing a Line
First let’s just draw a line. The first and most important concept you need to grasp is that all the primitive drawing stuff is really working with DirectX 3D drawing code. So even if you just want to draw a 2D line, you’re actually drawing a 3D line where all the points are at 0 in the z dimension. This also means we have to set up a viewport into our 3D scene, but we’ll want to do this so that it winds up just being a 2D scene and everything rendered at a z position of 0 will have a 1 to 1 ratio with the pixels of the screen when it is rendered. In other words, if I draw a line from 0, 0, 0, to 100, 100, 0 in the 3D space, it will draw that line from 0, 0 to 100, 100 on my screen.
We’ll also need to define vertex and pixel shaders on the graphics device. Scared yet? Yeah, so was I, until I realized that there are shortcuts for all this stuff. You still have to do it all, but it just takes a few lines of code that you don’t really need to fully understand. Of course, if you want to understand it, by all means, dig in and learn more. But most of that is really only important when you start doing more complex 3D rendering. For drawing primitives in 2D, you really just need to know what to tell the system to make it work.
OK, first of all, create a new project in VS 2010 Express for Windows Phone. You can make it a WP7 project, Windows desktop, or XBox project, it doesn’t really matter, but I’ll be going with WP7.
This should give you your standard base class with Initialize, LoadContent, UnloadContent, Update, and Draw methods. We’ll need two class properties here, so add these in right away:
[csharp]BasicEffect basicEffect;
VertexPositionColor[] vertices;[/csharp]
The basicEffect is where we set up our drawing properties – the viewport, how things are rendered, the shaders, etc. The vertices is an array of points. Actually, we’ll use the VertexPositionColor data type, which enapsulates a 3D vertex with a color.
Now, jump down to the Initialize method. Here’s where we set up the basicEffect and create a couple of points to draw a line.
[csharp]protected override void Initialize()
{
basicEffect = new BasicEffect(graphics.GraphicsDevice);
basicEffect.VertexColorEnabled = true;
basicEffect.Projection = Matrix.CreateOrthographicOffCenter
(0, graphics.GraphicsDevice.Viewport.Width, // left, right
graphics.GraphicsDevice.Viewport.Height, 0, // bottom, top
0, 1); // near, far plane
vertices = new VertexPositionColor[2];
vertices[0].Position = new Vector3(100, 100, 0);
vertices[0].Color = Color.Black;
vertices[1].Position = new Vector3(200, 200, 0);
vertices[1].Color = Color.Black;
base.Initialize();
}
[/csharp]
First we create a new BasicEffect, passing in the graphics device. We tell it that we want to use colored vertices, so each point can have its own color assigned. Then we create our projection. This is what I was talking about making the 3D world look like a simple 2D plane. The Matrix.CreateOrthographicOffCenter function takes care of this for us. We pass it in the left, right, top and bottom coordinates of the area we want to look at, and a near and far plane. For this we use the coords of our physical screen, and 0 and 1 for the planes. Whew! That part is done. See, not so scary.
Then we create two colored vertices. One is at 100, 100 on x, y and the other at 200, 200. Both have a z index of 0 and a color of black. And that’s all we have to do to initialize. Now onto the Draw method.
Despite how scary I may have made it sound, you only need to write two lines of code to draw a line between these two points.
[csharp]protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
basicEffect.CurrentTechnique.Passes[0].Apply();
graphics.GraphicsDevice.DrawUserPrimitives
base.Draw(gameTime);
}[/csharp]
The first line (after clearing the screen) tells the basicEffect to apply whatever techniques it already has set up for drawing. This sets up the vertex and pixel shaders, etc. Do I have any idea what’s really going on there? Only vaguely. But if you leave that line out, You’ll get a run time error telling you that you need to define vertex and pixel shaders. So you better do it.
The next line is where the drawing happens. We call the DrawUserPrimitives method, telling it first what type of vertices we are using: VertexPositionColor. We pass this in the type of primitive we are are drawing. This can be a line list, line strip, triangle strip, or triangle fan. These should be familiar to you if you’ve dabbled at all in OpenGL or DirectX or even AS3’s advanced drawing API stuff in Flash Player 10. A line list is just that, a list of lines. The data in the vertices array is interpreted as pairs of points. It will draw a line between each pair. Then we pass the array of vertices itself, where to start drawing (0 means the first element in the array), and how many primitives to draw. Here we say 1, since we are drawing 1 line. A common mistake might be to pass in 2, thinking there are two poitns, but it’s the number of primitives, not the number of vertices.
Anyway, if you have that all right, you should be able to run this in the simulator and see a line on the screen. Yippee! An interesting point is that because each vertex has a color, you can get an automatic line gradient just by changing the color of one of the vertices. Try it:
[csharp]vertices = new VertexPositionColor[2];
vertices[0].Position = new Vector3(100, 100, 0);
vertices[0].Color = Color.Black;
vertices[1].Position = new Vector3(200, 200, 0);
vertices[1].Color = Color.Red;
[/csharp]
Neat, eh?
Drawing Multiple Lines
Now what about drawing more than one line? First, lets add a couple more vertices…
[csharp]vertices = new VertexPositionColor[4];
vertices[0].Position = new Vector3(100, 100, 0);
vertices[0].Color = Color.Black;
vertices[1].Position = new Vector3(200, 100, 0);
vertices[1].Color = Color.Red;
vertices[2].Position = new Vector3(200, 200, 0);
vertices[2].Color = Color.Black;
vertices[3].Position = new Vector3(100, 200, 0);
vertices[3].Color = Color.Red;[/csharp]
Note that in the first line we specify that the array will have four elements.
Now in the Draw method, lets tell it we are drawing 2 primitives this time:
[csharp]graphics.GraphicsDevice.DrawUserPrimitives
Again, this is the tricky part. Remember that a line list takes the vertex array as a list of pairs of elements. So it looks at the first two vertices and draws a line between them. Then it takes the next two and draws a line between them. We have four vertices, so that’s two lines.
If we want to draw a continuous line between all the points, we should use a LineStrip. This takes the first vertex as the starting point, much like a moveTo in AS3, and each additional vertex will function as a lineTo. In this case, your number of primitives will be one less than the total number of points. We have four points, so that’s three lines.
[csharp]graphics.GraphicsDevice.DrawUserPrimitives
If you want to draw a line back to the starting point, you’d have add another point to the array, with the same coords as the first one, and add one more primitive.
Drawing a Circle
There is no built in circle drawing function, so we have to roll our own. Luckily circles roll pretty well. (oh…..)
I’m not going to explain the trig here. If you don’t get it, go out and buy a few copies of my book. 😉 First we make the points:
[csharp]vertices = new VertexPositionColor[100];
for (int i = 0; i < 99; i++) { float angle = (float)(i / 100.0 * Math.PI * 2); vertices[i].Position = new Vector3(200 + (float)Math.Cos(angle) * 100, 200 + (float)Math.Sin(angle) * 100, 0); vertices[i].Color = Color.Black; } vertices[99] = vertices[0];[/csharp] Our circle will have 100 points. Its center will be 200, 200, and its radius will be 100. We use some trig to go around from 0 to just under PI * 2 and create 99 vertices around (0 to 98). Vertex 99 will be the same as the starting point so that the circle is closed. In Draw, we just need to up the number of primitives to 99. Remember, with LineStrip, the number of primitives is one less than the number of vertices. [csharp]graphics.GraphicsDevice.DrawUserPrimitives
There you go, a perfect circle. I’m sure it’s very obvious to you how to turn this into a reusable function, so I’ll leave that up to you, now that you know the basics. You probably know enough to make a DrawLine, DrawCircle, DrawRect, and maybe even a DrawCurve function.
Also note that the circle looks a bit rough. There doesn’t appear to be any antialiasing on it. I’m guessing that if you dig around in BasicEffect you might find something that makes it look prettier. Also, as far as I know, there is no built in way to change the line width. If you need something a bit more visually robust, check out this post: