Drawing Primitives in XNA Game Studio 4.0

Sep 25 2010 Published by under Uncategorized, Windows Phone 7

Send to Kindle

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:

graphics.drawCircle(c, y, radius)

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:

BasicEffect basicEffect;
VertexPositionColor[] vertices;

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.

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();
}

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.

protected override void Draw(GameTime gameTime)
{
    GraphicsDevice.Clear(Color.CornflowerBlue);

    basicEffect.CurrentTechnique.Passes[0].Apply();
    graphics.GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(PrimitiveType.LineList, vertices, 0, 1);

    base.Draw(gameTime);
}

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:

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;

Neat, eh?

Drawing Multiple Lines

Now what about drawing more than one line? First, lets add a couple more vertices…

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;

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:

graphics.GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(PrimitiveType.LineList, vertices, 0, 2);

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.

graphics.GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(PrimitiveType.LineStrip, vertices, 0, 3);

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:

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];

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.

graphics.GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(PrimitiveType.LineStrip, vertices, 0, 99);

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:

http://blogs.msdn.com/b/manders/archive/2007/01/07/lines-2d-thick-rounded-line-segments-for-xna-programs.aspx

Send to Kindle

21 responses so far. Comments will be closed after post is one year old.

  • viaria says:

    nice information, i thought i was lot harder than actionscript, i am also thinking to learn objective-c for mac platform, but i donT even have a mac yet, it is really expensive with al taxes. it costs $1,799 in usa. in turkey it coasts $2.399. fcking taxes. i guess i will never be able to buy one. anyway, i hope you didnT leave flash platform, i am reading your second book (advanced animation) right now. dont worry i have pdf version but i bought it from amazon. worth it.
    thanks.

  • Raphael Santos says:

    Great article.

    So, do you see future for WP7?

    I would really like to spend some time on XNA, just not sure if it’s worth it, since Windows looks like to be lagging way behind iOS and Android at this moment.

  • keith says:

    well, nobody knows, but I think it’s worth investing some time in. I’ve honestly heard nothing but overwhelmingly positive reviews about WP7. Of course, that doesn’t guarantee success, but I feel like it’s going to be pretty positive.

    Even it if never surpasses iOS and Android, #3 is nothing to ignore.

  • tom says:

    Hi Keith,
    I totally agree with your point about the value of getting to the basic nub of the code when you’re first learning and it’s certainly one of the reasons I value your tutorials. Obviously it helps that you offer lucid and well written explanations around the code snippets too. :)

    I’ve been thinking of taking a look at XNA stuff for a while now (with an eye on producing stuff for my Xbox) and these tutorials are great encouragement.

    Thanks.

    Tom

  • zanlok says:

    More props. Much appreciated.

    Thanks for a) sharing the fruits of your research and b) highlighting the crux of the issues here. Wish I’d found this gem of an article a couple hours earlier in my search for how to do this stuff, especially in the new 4.0 version of XNA Game Studio, Microsoft.Xna.Framework, etc.

  • wtf says:

    Holy shitknockers!

    They write a fucking game library and they don’t include support for LINES? Nice move, Microsoft, real great job there guys.

  • Denis says:

    Firstly: Thanks! This is a very good guide and I appreciate the time you took to write it. Though there is a small problem I’m facing. When drawing multiple lines next to each other (separated by say 10px) they do not look the same.

    Here’s what I mean: http://i35.servimg.com/u/f35/15/25/51/19/untitl10.png
    This is what it looks like in the phone emulator using the code you supplied.
    I built the same thing for windows and the lines look superb: http://i35.servimg.com/u/f35/15/25/51/19/untitl11.png

    You think its cause the phone isn’t HD the results are as they are? I need to build a grid that the game will use and the lines need to look as fine as they can. Tried some other methods too like using a solid color texture2d and stretch it as needed… no luck.

    Another grid: http://i35.servimg.com/u/f35/15/25/51/19/untitl12.png

  • If using this in your own projects, and you employ a perspective matrix in your camera structure, remember to set the View property of the basicEffect object.

  • Thanks for this tutorial! Even though this wasn’t the exact thing I was looking to do, it did clarify some things, like how to set the 3D view matrix to use the same coordinates as 2D. I always make sure to tell everyone how useful their stuff is, since I love it when I get comments myself so I figure everyone else does too! Keep it up.

  • Chris says:

    Great tutorial, very well explained. Just noticed one small quirk in the above code.

    Although you have 100 vertices in that circle, two of them are the same (first = last).
    So replace: float angle = (float)(i / 100.0 * Math.PI * 2);
    with : float angle = (float)(i / 99.0 * Math.PI * 2);

    If not when drawing a circle with a small number of vertices there will be a small ‘defect’.

    • Dave says:

      Yes, I found this too when testing things with ’3/4/5 point circles’ etc.

      Definitely an error in the original post.

      Other than that… very helpful!

      I’m trying to write a little game as a project to learn XNA and keep my hand
      in with C#. This has been useful, as I’d much rather draw my circles than have
      bitmaps :)

  • hannes says:

    ty for an awesum post !!!

    i do happen to agree with wtf that its a bit of a runaround just to draw a line but you explained it very well and i now have a working segment of my game , tyvm !!

    i guess, the fast we get used to using 3d perspective gfx models the better though !!

  • Lukas says:

    Hey there

    Just to let you know your circle maths is okies but when you want it standardised per object for instance its not the best, here is a much better example of a cirlce in XNA (Note this is a whole class so it may be best if you want to use it to stick it in its own one – much easier!)

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using Microsoft.Xna.Framework.Graphics;
    using Microsoft.Xna.Framework;

    namespace KOSC
    {
    public class Circle
    {
    private float xPos;
    private float yPos;
    private float radius;
    private VertexPositionColor[] points;
    private BasicEffect effect;

    public Circle(float _x, float _y, int rad)
    {
    xPos = _x;
    yPos = _y;
    radius = rad;
    points = new VertexPositionColor[360];
    setupCircle();
    }

    public void setupCircleEffect(GraphicsDeviceManager graphics)
    {
    effect = new BasicEffect(graphics.GraphicsDevice);
    effect.VertexColorEnabled = true;
    effect.Projection = Matrix.CreateOrthographicOffCenter
    (0, graphics.GraphicsDevice.Viewport.Width, // left, right
    graphics.GraphicsDevice.Viewport.Height, 0, // bottom, top
    0, 1); // near, far plane

    }

    private void setupCircle()
    {
    float thetaInc = (((float)Math.PI * 2) / points.Length);
    for (int i = 0; i < points.Length; i++)
    {
    double theta = (thetaInc * i);
    double xInc = Math.Sin(theta);
    double yInc = Math.Cos(theta);
    points[i].Position = new Vector3((xPos + (float)(xInc * radius)), (yPos + (float)(yInc * radius)), 0f);
    points[i].Color = Color.Green;
    }
    }

    public void drawCircle(GraphicsDeviceManager graphics)
    {
    effect.CurrentTechnique.Passes[0].Apply();
    graphics.GraphicsDevice.DrawUserPrimitives(PrimitiveType.LineStrip, points, 0, points.Length – 1);
    }
    }
    }

  • This Not Accurate says:

    If you take the class coded above and take a radius over 30+ its not solid any more there is a gap at 6 o’clock. plus say i make a loop of ten and draw the radii 1-10 to make a solid fill it leaves a gap or pattern in the fill.

  • Leon says:

    Please, let me know how to draw the movement of a sprite in XNA. I mean, the initial point is fixed and the end point is variable according with the movement; however the screen maintais the drawing of the motion.

    Thank you

  • dirk says:

    Great post, first web site in which I was able to easily decipher graphics.GraphicsDevice.DrawUserPrimitives(…);
    MS have a way of convoluting things