XNA for C#
DirectX 9 for C#
DirectX 9 for C++
DirectX 9 for VB
Forum
   September 24: 2D-19: Particle engine
My Book: Out Now!
      
       Go to section on this site

Additional info


Latest Forum posts

 Next Chapter
  Posted by: jasonbarry
  When: 15/10/2008 at 15:55:47

 HLSL: compilation fails
  Posted by: aguacate
  When: 15/10/2008 at 12:03:19

 my collision sucks!
  Posted by: orgad
  When: 15/10/2008 at 11:25:49

 HLSL: compilation fails
  Posted by: aguacate
  When: 15/10/2008 at 11:16:55

 Flickering/glitches
  Posted by: Danzence
  When: 15/10/2008 at 09:53:00

 Next Chapter
  Posted by: samer
  When: 15/10/2008 at 06:40:32

 Flickering/glitches
  Posted by: samer
  When: 15/10/2008 at 06:39:17

 Texture terrian c++
  Posted by: ruudriem
  When: 15/10/2008 at 05:55:16

 1.7 and 2.8 problem.
  Posted by: riemer
  When: 15/10/2008 at 03:36:34

 Next Chapter
  Posted by: riemer
  When: 15/10/2008 at 03:34:29


Ads



Point sprites – Billboarding

This chapter we’ll be adding bullets to our game. For these bullets, we could use real 3D spheres, but this would ask a lot from our graphics card. Instead, we’ll be using a very simple 2D image of a fireball, which you can download here (link). We will only specify the central point of the image in 3D space, and XNA will display the image, always facing the viewer and scaled to reflect the distance between the viewer and the point in 3D space. This technique is called billboarding.

Billboarding is used a lot in 3D games, for example to add trees in a forest. For a lot of detailed information and samples on billboarding, see Recipes 3-11 and 3-12.

A 2D image is also called a sprite, and since XNA needs only the center point of the image as 3D location, these 2D sprites used in 3D are called point sprites. Since only 1 point is needed, this method consumes very little bandwidth to our graphics card in our PCI express slot.

When being fired, we want the bullets to move forward continuously. Thus, for every bullet, we’ll need to keep track of the current position and the rotation to calculate the direction of the bullet, just as with our plane. So we’re going to define a new struct, which you should put at the very top of our code, above our variables:

 struct Bullet
 {
     public Vector3 position;
     public Quaternion rotation;
 }

With this struct defined, we can create objects of the Bullet type. For each object of the Bullet type, we can store a Vector3 and a Quaternion to store the position and rotation, respectively.

We will keep track of a List containing these Bullet objects. Furthermore, to define the firing speed, we need to keep track of the last time a bullet was fired. Finelly, we will need a Texture2D variable to hold the image of the bullet. So put these lines among our other variable declarations:

 Texture2D bulletTexture;

List<Bullet> bulletList = new List<Bullet> ();double lastBulletTime = 0;

You can download my sample bullet image here (link).

Go ahead and import the image into your solution as you’ve done before. Next, we’ll load the 2D image into our texture variable. This is done by adding this line to the LoadContent method:


bulletTexture = Content.Load<Texture2D> ("bullet");
Now, every time the user presses the spacebar, we want a new bullet to be created and be added to our bulletList, so add this code at the bottom of our ProcessKeyboardmethod:

 if (keys.IsKeyDown(Keys.Space))
 {    
     double currentTime = gameTime.TotalGameTime.TotalMilliseconds;
     if (currentTime - lastBulletTime > 100)
     {
         Bullet newBullet = new Bullet();
         newBullet.position = xwingPosition;
         newBullet.rotation = xwingRotation;
         bulletList.Add(newBullet);
 
         lastBulletTime = currentTime;
     }
 }

When the spacebar is pressed, we compare the current time with the last time our xwing fired a bullet. If our last shot was more than 100 milliseconds ago, we want to fire a new bullet. This comes down to 10 bullets per second.

If it’s OK to fire a new bullet, we create a new Bullet object, and the current position and rotation of the xwing is stored to it. This is of course because we want the bullet to travel in the same direction as our xwing was flying the moment the bullet was fired. The last line effectively adds the newly created Bullet object to the bulletList.

Before we move on to the drawing code, let’s finish this part by making the bullets move forward. We’ve already created a method that does all the calculations: MoveForward. We’re going to create a new method, UpdateSpritePositions, which will scroll through our bulletList and update the position of every bullet. This is the code:

 private void UpdateBulletPositions(float moveSpeed)
 {
     for (int i = 0; i < bulletList.Count; i++)
     {
         Bullet currentBullet = bulletList[i];
         MoveForward(ref currentBullet.position, currentBullet.rotation, moveSpeed * 2.0f);
         bulletList[i] = currentBullet;
     }
 }

Pretty straightforward. The position of every bullet in our bulletList is updated. This method will also receive the same moveSpeed as our xwing, but since we multiply it with 2.0f the bullets will go twice as fast as our xwing.

Call this method from within the Update method:

 UpdateBulletPositions(moveSpeed);

Next, we want to draw our sprites. For each bullet. For each bullet drawn, XNA needs to be passed a vertex containing 2 things: the position and the size of the sprite. A new technique in our effect file, called “PointSprites”, will automatically scale the images down according to their distance to the camera.

As in Series 1, we find ourselves at a point where we cannot use one of the predefined vertexformats XNA offers. So we need to define our very own vertexformat, a structure that is able to hold the necessary data. This is explained in detail in Series 3. So put this struct at the top of your code:, for example after your definition of the Bullet struct:

 private struct VertexPointSprite
 {
     private Vector3 position;
     private float pointSize;
 
     public VertexPointSprite(Vector3 position, float pointSize)
     {
         this.position = position;
         this.pointSize = pointSize;
     }
 
     public static readonly VertexElement[] VertexElements =
     {
         new VertexElement(0, 0, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Position, 0),
         new VertexElement(0, sizeof(float)*3, VertexElementFormat.Single, VertexElementMethod.Default, VertexElementUsage.PointSize, 0),
     };
     public static int SizeInBytes = sizeof(float) * (3 + 1);
 }

This struct can hold the data XNA needs to draw point sprites: the position and the size. We also define the array of VertexElements, which we will need to create the VertexDefinition suitable for this kind of vertices. Remember that this VertexDeclaration is needed when it comes to rendering, as the VertexDeclaration explains to the graphics card exactly which kind of data is contained in the vertices.

Add the VertexDeclaratio to your variables at the top of your code:

 VertexDeclaration pointSpriteVertexDeclaration;

And fill it by putting the line below in the LoadContent method. This is a nice spot, as a VertexDeclaration needs to be defined only once since the kind of data contained in a type of vertex doesn’t change.

 pointSpriteVertexDeclaration = new VertexDeclaration(device, VertexPointSprite.VertexElements);

Now we will define a new method, DrawSprites, which draws the bullets stored in our bulletList. It looks quite difficult, but contains nothing we haven’t seen before:

 private void DrawBullets()
 {
     if (bulletList.Count > 0)
     {
         VertexPointSprite[] spriteArray = new VertexPointSprite[bulletList.Count];
         for (int i = 0; i < bulletList.Count; i++)
             spriteArray[i] = new VertexPointSprite(bulletList[i].position, 50);
 
         effect.CurrentTechnique = effect.Techniques["PointSprites"];
         Matrix worldMatrix = Matrix.Identity;
         effect.Parameters["xWorld"].SetValue(worldMatrix);
         effect.Parameters["xView"].SetValue(viewMatrix);
         effect.Parameters["xProjection"].SetValue(projectionMatrix);
         effect.Parameters["xTexture"].SetValue(bulletTexture);
 
         device.RenderState.PointSpriteEnable = true;
 
         effect.Begin();
         foreach (EffectPass pass in effect.CurrentTechnique.Passes)
         {
             pass.Begin();
             device.VertexDeclaration = pointSpriteVertexDeclaration;
             device.DrawUserPrimitives(PrimitiveType.PointList, spriteArray, 0, spriteArray.Length);
             pass.End();
         }
         effect.End();
 
         device.RenderState.PointSpriteEnable = false;
     }
 }

First we check whether the bulletList is not empty. If there are some bullets to be drawn, we move on. In order to render things, we need to define their vertices. In the case of a point sprite, we need to define only one vertex. So we scroll though our bulletList, and for each bullet we add a new VertexPointSprite to an array. We specify its 3D position and size.

Next, we need to instruct our graphics card to render from these vertices using the PointsSprites technique. As we’re still dealing with a 3D-to-2D transformation, we need to set a World, View and Projection matrix. Because the position of all bullets are defined relative to the absolute axis (the same as our 3D city), we’re using the Identity matrix as World matrix. We also need to set the image of the bullet as active texture, as this is where our graphics card needs to sample the color for each pixel from.

We’ve already seen the last part a few times: for each pass of the effect, we draw a certain number of elements from an array containing vertices. Because now every image requires only 1 vertex, this number of elements to draw will be the same as the number of elements in our spriteArray. Note that this is where we use our VertexDeclaration.

We need to set the PointSpriteEnable renderstate to true, as some graphics cards (such as the one in the XBox360) needs to know this. Don't forget to turn it off at the end of the method, though.

In this example, the CPU is calculating the new position of each bullet for each frame, creates an array of vertices from these positions and sends them to the graphics card for each frame. It would be much better to have the GPU calculate all the new positions. See Recipe 3-12 on how this can be done using a particle engine.

This concludes the method; All we have to do is call this method from the bottom of our Draw method:

 DrawSprites();

Now try to run this code!

You should see a screen as the one below:




DirectX Tutorial 11 - Point sprites

If you appreciate the amount of time I spend creating and updating
these pages, feel free to donate -- any amount is welcome !



Click here to go to the forum on this chapter!

Or click on one of the topics on this chapter to go there:
  • Invisible bullets
          I have followed the Series 2 tutorials about the f...
  • memory consumption
          Hello riemer great tutorials, first than anythi...
  • Right Point Sprite Rendering
          Hello! I have a problem with way of the renderi...
  • The method call is invalid
          Hi Riemer! I'm following your XNA tutorial from ...


    We will fine-tune our bullets in the next chapter. This will also include detecting collisions between a bullet and an obstacle. In case of a collision, the bullet will be removed from our bulletList, which will free the memory.

    You can try these exercises to practice what you've learned:
  • Change the size of the point sprites.
    Our code thus far:

     using System;
     using System.Collections.Generic;
     using Microsoft.Xna.Framework;
     using Microsoft.Xna.Framework.Audio;
     using Microsoft.Xna.Framework.Content;
     using Microsoft.Xna.Framework.GamerServices;
     using Microsoft.Xna.Framework.Graphics;
     using Microsoft.Xna.Framework.Input;
     using Microsoft.Xna.Framework.Net;
     using Microsoft.Xna.Framework.Storage;
     
     namespace XNAseries2
     {
         public class Game1 : Microsoft.Xna.Framework.Game
         {
             struct Bullet
             {
                 public Vector3 position;
                 public Quaternion rotation;
             }
     
             private struct VertexPointSprite
             {
                 private Vector3 position;
                 private float pointSize;
     
                 public VertexPointSprite(Vector3 position, float pointSize)
                 {
                     this.position = position;
                     this.pointSize = pointSize;
                 }
     
                 public static readonly VertexElement[] VertexElements =
                  {
                      new VertexElement(0, 0, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Position, 0),
                      new VertexElement(0, sizeof(float)*3, VertexElementFormat.Single, VertexElementMethod.Default, VertexElementUsage.PointSize, 0),
                  };
                 public static int SizeInBytes = sizeof(float) * (3 + 1);
             }
     
             enum CollisionType { None, Building, Boundary, Target }
     
             const int maxTargets = 50;
             int[] buildingHeights = new int[] { 0, 2, 2, 6, 5, 4 };
     
             GraphicsDeviceManager graphics;
             GraphicsDevice device;
     
             Effect effect;
             Vector3 lightDirection = new Vector3(3, -2, 5);
             Matrix viewMatrix;
             Matrix projectionMatrix;
     
             Texture2D sceneryTexture;
             Texture2D bulletTexture;
             Model xwingModel;
             Model targetModel;
     
             Vector3 xwingPosition = new Vector3(8, 1, -3);
             Quaternion xwingRotation = Quaternion.Identity;
     
             int[,] floorPlan;
             float gameSpeed = 1.0f;
     
             VertexBuffer cityVertexBuffer;
             VertexDeclaration texturedVertexDeclaration;
             VertexDeclaration pointSpriteVertexDeclaration;
     
             BoundingBox[] buildingBoundingBoxes;
             BoundingBox completeCityBox;
     

            List<BoundingSphere> targetList = new List<BoundingSphere> ();

            List<Bullet> bulletList = new List<Bullet> ();        double lastBulletTime = 0;

     
             public Game1()
             {
                 graphics = new GraphicsDeviceManager(this);
                 Content.RootDirectory = "Content";
             }
     
             protected override void Initialize()
             {
                 graphics.PreferredBackBufferWidth = 500;
                 graphics.PreferredBackBufferHeight = 500;
                 graphics.IsFullScreen = false;
                 graphics.ApplyChanges();
                 Window.Title = "Riemer's XNA Tutorials -- Series 2";
     
                 LoadFloorPlan();
                 lightDirection.Normalize();
                 SetUpBoundingBoxes();
                 AddTargets();
     
                 base.Initialize();
             }
     
             protected override void LoadContent()
             {
                 device = graphics.GraphicsDevice;
     

                effect = Content.Load<Effect> ("effects");
                sceneryTexture = Content.Load<Texture2D> ("texturemap");
                bulletTexture = Content.Load<Texture2D> ("bullet");
                xwingModel = LoadModel("xwing");
                targetModel = LoadModel("target");

                SetUpVertices();
                SetUpCamera();


                 pointSpriteVertexDeclaration = new VertexDeclaration(device, VertexPointSprite.VertexElements);
             }
     
             private Model LoadModel(string assetName)
             {

                Model newModel = Content.Load<Model> (assetName);            foreach (ModelMesh mesh in newModel.Meshes)
                    foreach (ModelMeshPart meshPart in mesh.MeshParts)
                        meshPart.Effect = effect.Clone(device);
                return newModel;
            }

            private void LoadFloorPlan()
            {
                floorPlan = new int[,]
                 {
                     {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
                     {1,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
                     {1,0,0,1,1,0,0,0,1,1,0,0,1,0,1},
                     {1,0,0,1,1,0,0,0,1,0,0,0,1,0,1},
                     {1,0,0,0,1,1,0,1,1,0,0,0,0,0,1},
                     {1,0,0,0,0,0,0,0,0,0,0,1,0,0,1},
                     {1,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
                     {1,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
                     {1,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
                     {1,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
                     {1,0,1,1,0,0,0,1,0,0,0,0,0,0,1},
                     {1,0,1,0,0,0,0,0,0,0,0,0,0,0,1},
                     {1,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
                     {1,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
                     {1,0,0,0,0,1,0,0,0,0,0,0,0,0,1},
                     {1,0,0,0,0,1,0,0,0,1,0,0,0,0,1},
                     {1,0,1,0,0,0,0,0,0,1,0,0,0,0,1},
                     {1,0,1,1,0,0,0,0,1,1,0,0,0,1,1},
                     {1,0,0,0,0,0,0,0,1,1,0,0,0,1,1},
                     {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
                 };

                Random random = new Random();
                int differentBuildings = buildingHeights.Length - 1;
                for (int x = 0; x < floorPlan.GetLength(0); x++)
                    for (int y = 0; y < floorPlan.GetLength(1); y++)
                        if (floorPlan[x, y] == 1)
                            floorPlan[x, y] = random.Next(differentBuildings) + 1;
            }

            private void SetUpBoundingBoxes()
            {
                int cityWidth = floorPlan.GetLength(0);
                int cityLength = floorPlan.GetLength(1);


                List<BoundingBox> bbList = new List<BoundingBox> ();            for (int x = 0; x < cityWidth; x++)
                {
                    for (int z = 0; z < cityLength; z++)
                    {
                        int buildingType = floorPlan[x, z];
                        if (buildingType != 0)
                        {
                            int buildingHeight = buildingHeights[buildingType];
                            Vector3[] buildingPoints = new Vector3[2];
                            buildingPoints[0] = new Vector3(x, 0, -z);
                            buildingPoints[1] = new Vector3(x + 1, buildingHeight, -z - 1);
                            BoundingBox buildingBox = BoundingBox.CreateFromPoints(buildingPoints);
                            bbList.Add(buildingBox);
                        }
                    }
                }
                buildingBoundingBoxes = bbList.ToArray();

                Vector3[] boundaryPoints = new Vector3[2];
                boundaryPoints[0] = new Vector3(0, 0, 0);
                boundaryPoints[1] = new Vector3(cityWidth, 20, -cityLength);
                completeCityBox = BoundingBox.CreateFromPoints(boundaryPoints);
            }

            private void AddTargets()
            {
                int cityWidth = floorPlan.GetLength(0);
                int cityLength = floorPlan.GetLength(1);

                Random random = new Random();

                while (targetList.Count < maxTargets)
                {
                    int x = random.Next(cityWidth);
                    int z = -random.Next(cityLength);
                    float y = (float)random.Next(2000) / 1000f + 1;
                    float radius = (float)random.Next(1000) / 1000f * 0.2f + 0.01f;

                    BoundingSphere newTarget = new BoundingSphere(new Vector3(x, y, z), radius);

                    if (CheckCollision(newTarget) == CollisionType.None)
                        targetList.Add(newTarget);
                }
            }

            private void SetUpCamera()
            {
                viewMatrix = Matrix.CreateLookAt(new Vector3(20, 13, -5), new Vector3(8, 0, -7), new Vector3(0, 1, 0));
                projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, device.Viewport.AspectRatio, 0.2f, 500.0f);
            }

            private void SetUpVertices()
            {
                int differentBuildings = buildingHeights.Length - 1;
                float imagesInTexture = 1 + differentBuildings * 2;

                int cityWidth = floorPlan.GetLength(0);
                int cityLength = floorPlan.GetLength(1);


                List<VertexPositionNormalTexture> verticesList = new List<VertexPositionNormalTexture> ();
                for (int x = 0; x < cityWidth; x++)
                {
                    for (int z = 0; z < cityLength; z++)
                    {
                        int currentbuilding = floorPlan[x, z];

                        //floor or ceiling
                        verticesList.Add(new VertexPositionNormalTexture(new Vector3(x, buildingHeights[currentbuilding], -z), new Vector3(0, 1, 0), new Vector2(currentbuilding * 2 / imagesInTexture, 1)));
                        verticesList.Add(new VertexPositionNormalTexture(new Vector3(x, buildingHeights[currentbuilding], -z - 1), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2) / imagesInTexture, 0)));
                        verticesList.Add(new VertexPositionNormalTexture(new Vector3(x + 1, buildingHeights[currentbuilding], -z), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2 + 1) / imagesInTexture, 1)));

                        verticesList.Add(new VertexPositionNormalTexture(new Vector3(x, buildingHeights[currentbuilding], -z - 1), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2) / imagesInTexture, 0)));
                        verticesList.Add(new VertexPositionNormalTexture(new Vector3(x + 1, buildingHeights[currentbuilding], -z - 1), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2 + 1) / imagesInTexture, 0)));
                        verticesList.Add(new VertexPositionNormalTexture(new Vector3(x + 1, buildingHeights[currentbuilding], -z), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2 + 1) / imagesInTexture, 1)));

                        if (currentbuilding != 0)
                        {
                            //front wall
                            verticesList.Add(new VertexPositionNormalTexture(new Vector3(x + 1, 0, -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2) / imagesInTexture, 1)));
                            verticesList.Add(new VertexPositionNormalTexture(new Vector3(x, buildingHeights[currentbuilding], -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2 - 1) / imagesInTexture, 0)));
                            verticesList.Add(new VertexPositionNormalTexture(new Vector3(x, 0, -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2 - 1) / imagesInTexture, 1)));

                            verticesList.Add(new VertexPositionNormalTexture(new Vector3(x, buildingHeights[currentbuilding], -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2 - 1) / imagesInTexture, 0)));
                            verticesList.Add(new VertexPositionNormalTexture(new Vector3(x + 1, 0, -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2) / imagesInTexture, 1)));
                            verticesList.Add(new VertexPositionNormalTexture(new Vector3(x + 1, buildingHeights[currentbuilding], -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2) / imagesInTexture, 0)));

                            //back wall
                            verticesList.Add(new VertexPositionNormalTexture(new Vector3(x + 1, 0, -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2) / imagesInTexture, 1)));
                            verticesList.Add(new VertexPositionNormalTexture(new Vector3(x, 0, -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2 - 1) / imagesInTexture, 1)));
                            verticesList.Add(new VertexPositionNormalTexture(new Vector3(x, buildingHeights[currentbuilding], -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2 - 1) / imagesInTexture, 0)));

                            verticesList.Add(new VertexPositionNormalTexture(new Vector3(x, buildingHeights[currentbuilding], -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2 - 1) / imagesInTexture, 0)));
                            verticesList.Add(new VertexPositionNormalTexture(new Vector3(x + 1, buildingHeights[currentbuilding], -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2) / imagesInTexture, 0)));
                            verticesList.Add(new VertexPositionNormalTexture(new Vector3(x + 1, 0, -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2) / imagesInTexture, 1)));

                            //left wall
                            verticesList.Add(new VertexPositionNormalTexture(new Vector3(x, 0, -z), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2) / imagesInTexture, 1)));
                            verticesList.Add(new VertexPositionNormalTexture(new Vector3(x, 0, -z - 1), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2 - 1) / imagesInTexture, 1)));
                            verticesList.Add(new VertexPositionNormalTexture(new Vector3(x, buildingHeights[currentbuilding], -z - 1), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2 - 1) / imagesInTexture, 0)));

                            verticesList.Add(new VertexPositionNormalTexture(new Vector3(x, buildingHeights[currentbuilding], -z - 1), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2 - 1) / imagesInTexture, 0)));
                            verticesList.Add(new VertexPositionNormalTexture(new Vector3(x, buildingHeights[currentbuilding], -z), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2) / imagesInTexture, 0)));
                            verticesList.Add(new VertexPositionNormalTexture(new Vector3(x, 0, -z), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2) / imagesInTexture, 1)));

                            //right wall
                            verticesList.Add(new VertexPositionNormalTexture(new Vector3(x + 1, 0, -z), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2) / imagesInTexture, 1)));
                            verticesList.Add(new VertexPositionNormalTexture(new Vector3(x + 1, buildingHeights[currentbuilding], -z - 1), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2 - 1) / imagesInTexture, 0)));
                            verticesList.Add(new VertexPositionNormalTexture(new Vector3(x + 1, 0, -z - 1), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2 - 1) / imagesInTexture, 1)));

                            verticesList.Add(new VertexPositionNormalTexture(new Vector3(x + 1, buildingHeights[currentbuilding], -z - 1), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2 - 1) / imagesInTexture, 0)));
                            verticesList.Add(new VertexPositionNormalTexture(new Vector3(x + 1, 0, -z), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2) / imagesInTexture, 1)));
                            verticesList.Add(new VertexPositionNormalTexture(new Vector3(x + 1, buildingHeights[currentbuilding], -z), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2) / imagesInTexture, 0)));
                        }
                    }
                }

                cityVertexBuffer = new VertexBuffer(device, verticesList.Count * VertexPositionNormalTexture.SizeInBytes, BufferUsage.WriteOnly);

                cityVertexBuffer.SetData<VertexPositionNormalTexture> (verticesList.ToArray());
                texturedVertexDeclaration = new VertexDeclaration(device, VertexPositionNormalTexture.VertexElements);
            }

            protected override void UnloadContent()
            {
            }

            protected override void Update(GameTime gameTime)
            {
                ProcessKeyboard(gameTime);
                float moveSpeed = gameTime.ElapsedGameTime.Milliseconds / 500.0f * gameSpeed;
                MoveForward(ref xwingPosition, xwingRotation, moveSpeed);

                BoundingSphere xwingSpere = new BoundingSphere(xwingPosition, 0.04f);
                if (CheckCollision(xwingSpere) != CollisionType.None)
                {
                    xwingPosition = new Vector3(8, 1, -3);
                    xwingRotation = Quaternion.Identity;
                    gameSpeed /= 1.1f;
                }

                UpdateCamera();

                 UpdateBulletPositions(moveSpeed);
     
                 base.Update(gameTime);
             }
     
             private void UpdateCamera()
             {
                 Vector3 campos = new Vector3(0, 0.1f, 0.6f);
                 campos = Vector3.Transform(campos, Matrix.CreateFromQuaternion(xwingRotation));
                 campos += xwingPosition;
     
                 Vector3 camup = new Vector3(0, 1, 0);
                 camup = Vector3.Transform(camup, Matrix.CreateFromQuaternion(xwingRotation));
     
                 viewMatrix = Matrix.CreateLookAt(campos, xwingPosition, camup);
                 projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, device.Viewport.AspectRatio, 0.2f, 500.0f);
             }
     
             private void ProcessKeyboard(GameTime gameTime)
             {
                 float leftRightRot = 0;
     
                 float turningSpeed = (float)gameTime.ElapsedGameTime.TotalMilliseconds / 1000.0f;
                 turningSpeed *= 1.6f * gameSpeed;
                 KeyboardState keys = Keyboard.GetState();
                 if (keys.IsKeyDown(Keys.Right))
                     leftRightRot += turningSpeed;
                 if (keys.IsKeyDown(Keys.Left))
                     leftRightRot -= turningSpeed;
     
                 float upDownRot = 0;
                 if (keys.IsKeyDown(Keys.Down))
                     upDownRot += turningSpeed;
                 if (keys.IsKeyDown(Keys.Up))
                     upDownRot -= turningSpeed;
     
                 Quaternion additionalRot = Quaternion.CreateFromAxisAngle(new Vector3(0, 0, -1), leftRightRot) * Quaternion.CreateFromAxisAngle(new Vector3(1, 0, 0), upDownRot);
                 xwingRotation *= additionalRot;
     
                 if (keys.IsKeyDown(Keys.Space))
                 {
                     double currentTime = gameTime.TotalGameTime.TotalMilliseconds;
                     if (currentTime - lastBulletTime > 100)
                     {
                         Bullet newBullet = new Bullet();
                         newBullet.position = xwingPosition;
                         newBullet.rotation = xwingRotation;
                         bulletList.Add(newBullet);
     
                         lastBulletTime = currentTime;
                     }
                 }
             }
     
             private void MoveForward(ref Vector3 position, Quaternion rotationQuat, float speed)
             {
                 Vector3 addVector = Vector3.Transform(new Vector3(0, 0, -1), rotationQuat);
                 position += addVector * speed;
             }
     
             private CollisionType CheckCollision(BoundingSphere sphere)
             {
                 for (int i = 0; i < buildingBoundingBoxes.Length; i++)
                     if (buildingBoundingBoxes[i].Contains(sphere) != ContainmentType.Disjoint)
                         return CollisionType.Building;
     
                 if (completeCityBox.Contains(sphere) != ContainmentType.Contains)
                     return CollisionType.Boundary;
     
                 for (int i = 0; i < targetList.Count; i++)
                 {
                     if (targetList[i].Contains(sphere) != ContainmentType.Disjoint)
                     {
                         targetList.RemoveAt(i);
                         i--;
                         AddTargets();
     
                         return CollisionType.Target;
                     }
                 }
     
                 return CollisionType.None;
             }
     
             private void UpdateBulletPositions(float moveSpeed)
             {
                 for (int i = 0; i < bulletList.Count; i++)
                 {
                     Bullet currentBullet = bulletList[i];
                     MoveForward(ref currentBullet.position, currentBullet.rotation, moveSpeed * 2.0f);
                     bulletList[i] = currentBullet;
                 }
             }
     
             protected override void Draw(GameTime gameTime)
             {
                 device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.DarkSlateBlue, 1.0f, 0);
     
                 DrawCity();
                 DrawModel();
                 DrawTargets();
                 DrawBullets();
     
                 base.Draw(gameTime);
             }
     
             private void DrawCity()
             {
                 effect.CurrentTechnique = effect.Techniques["Textured"];
                 effect.Parameters["xWorld"].SetValue(Matrix.Identity);
                 effect.Parameters["xView"].SetValue(viewMatrix);
                 effect.Parameters["xProjection"].SetValue(projectionMatrix);
                 effect.Parameters["xTexture"].SetValue(sceneryTexture);
                 effect.Parameters["xEnableLighting"].SetValue(true);
                 effect.Parameters["xLightDirection"].SetValue(lightDirection);
                 effect.Parameters["xAmbient"].SetValue(0.5f);
                 effect.Begin();
                 foreach (EffectPass pass in effect.CurrentTechnique.Passes)
                 {
                     pass.Begin();
                     device.VertexDeclaration = texturedVertexDeclaration;
                     device.Vertices[0].SetSource(cityVertexBuffer, 0, VertexPositionNormalTexture.SizeInBytes);
                     device.DrawPrimitives(PrimitiveType.TriangleList, 0, cityVertexBuffer.SizeInBytes / VertexPositionNormalTexture.SizeInBytes / 3);
                     pass.End();
                 }
                 effect.End();
             }
     
             private void DrawModel()
             {
                 Matrix worldMatrix = Matrix.CreateScale(0.0005f, 0.0005f, 0.0005f) * Matrix.CreateRotationY(MathHelper.Pi) * Matrix.CreateFromQuaternion(xwingRotation) * Matrix.CreateTranslation(xwingPosition);
     
                 Matrix[] xwingTransforms = new Matrix[xwingModel.Bones.Count];
                 xwingModel.CopyAbsoluteBoneTransformsTo(xwingTransforms);
                 foreach (ModelMesh mesh in xwingModel.Meshes)
                 {
                     foreach (Effect currentEffect in mesh.Effects)
                     {
                         currentEffect.CurrentTechnique = currentEffect.Techniques["Colored"];
                         currentEffect.Parameters["xWorld"].SetValue(xwingTransforms[mesh.ParentBone.Index] * worldMatrix);
                         currentEffect.Parameters["xView"].SetValue(viewMatrix);
                         currentEffect.Parameters["xProjection"].SetValue(projectionMatrix);
                         currentEffect.Parameters["xEnableLighting"].SetValue(true);
                         currentEffect.Parameters["xLightDirection"].SetValue(lightDirection);
                         currentEffect.Parameters["xAmbient"].SetValue(0.5f);
                     }
                     mesh.Draw();
                 }
             }
     
             private void DrawTargets()
             {
                 for (int i = 0; i < targetList.Count; i++)
                 {
                     Matrix worldMatrix = Matrix.CreateScale(targetList[i].Radius) * Matrix.CreateTranslation(targetList[i].Center);
     
                     Matrix[] targetTransforms = new Matrix[targetModel.Bones.Count];
                     targetModel.CopyAbsoluteBoneTransformsTo(targetTransforms);
                     foreach (ModelMesh mesh in targetModel.Meshes)
                     {
                         foreach (Effect currentEffect in mesh.Effects)
                         {
                             currentEffect.CurrentTechnique = currentEffect.Techniques["Colored"];
                             currentEffect.Parameters["xWorld"].SetValue(targetTransforms[mesh.ParentBone.Index] * worldMatrix);
                             currentEffect.Parameters["xView"].SetValue(viewMatrix);
                             currentEffect.Parameters["xProjection"].SetValue(projectionMatrix);
                             currentEffect.Parameters["xEnableLighting"].SetValue(true);
                             currentEffect.Parameters["xLightDirection"].SetValue(lightDirection);
                             currentEffect.Parameters["xAmbient"].SetValue(0.5f);
                         }
                         mesh.Draw();
                     }
                 }
             }
     
             private void DrawBullets()
             {
                 if (bulletList.Count > 0)
                 {
                     VertexPointSprite[] spriteArray = new VertexPointSprite[bulletList.Count];
                     for (int i = 0; i < bulletList.Count; i++)
                         spriteArray[i] = new VertexPointSprite(bulletList[i].position, 50);
     
                     effect.CurrentTechnique = effect.Techniques["PointSprites"];
                     Matrix worldMatrix = Matrix.Identity;
                     effect.Parameters["xWorld"].SetValue(worldMatrix);
                     effect.Parameters["xView"].SetValue(viewMatrix);
                     effect.Parameters["xProjection"].SetValue(projectionMatrix);
                     effect.Parameters["xTexture"].SetValue(bulletTexture);
     
                     device.RenderState.PointSpriteEnable = true;
     
                     effect.Begin();
                     foreach (EffectPass pass in effect.CurrentTechnique.Passes)
                     {
                         pass.Begin();
                         device.VertexDeclaration = pointSpriteVertexDeclaration;
                         device.DrawUserPrimitives(PrimitiveType.PointList, spriteArray, 0, spriteArray.Length);
                         pass.End();
                     }
                     effect.End();
     
                     device.RenderState.PointSpriteEnable = false;
                 }
             }
         }
     }
     


    Google
     
    Webwww.riemers.net


    If you appreciate the amount of time I spend creating and updating
    these pages, feel free to donate -- any amount is welcome !



    - Website design & XNA + DirectX code : Riemer Grootjans -
    ©2003 - 2008 Riemer Grootjans
  • Translations

    This site in English
    This site in Korean
    This site in Czech

    Microsoft MVP Award



    2007 - 2008 MVP Award
    DirectX - XNA

    Contents

    News
    Home
    Forum
    XNA 2.0 Recipes Book (8)
    Downloads
    Extra Reading (3)
    Matrices: geometrical
    Matrix Mathematics
    Homogenous matrices
    Tutorials (160)
    XNA 2.0 using C# (89)
    2D Series: Shooters! (22)
    3D Series 1: Terrain (13)
    3D Series 2: Flightsim (14)
    Starting point
    Textures
    Loading the floorplan
    Creating the 3D city
    Loading a Model
    Ambient and diffuse
    Quaternion camera
    Flight kinematics
    Collision detection
    Adding targets
    Point sprites
    Alpha blending
    Skybox
    Camera delay
    3D Series 3: HLSL (18)
    3D Series 4: Adv. terrain (19)
    Short Tuts (3)
    DirectX using C# (54)
    DirectX using C++ (15)
    DirectX using VB (2)
    -- Expand all --


    Thank you!