Microsoft XNA Game Studio Creator’s Guide- P11


lượt xem

Microsoft XNA Game Studio Creator’s Guide- P11

Mô tả tài liệu
  Download Vui lòng tải xuống để xem tài liệu đầy đủ

Microsoft XNA Game Studio Creator’s Guide- P11:The release of the XNA platform and specifically the ability for anyone to write Xbox 360 console games was truly a major progression in the game-programming world. Before XNA, it was simply too complicated and costly for a student, software hobbyist, or independent game developer to gain access to a decent development kit for a major console platform.

Chủ đề:

Nội dung Text: Microsoft XNA Game Studio Creator’s Guide- P11

  1. 278 MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE vector that stores a rotation around an axis. Quaternion math is used to calculate an increment to update the camera’s Look vector. We can express the value of the Look vector like this: Look = View - Position By rearranging this equation we can say the following: View = Look + Position If the quaternion represents the updated Look vector, then Updated View = Updated Look Vector + Position Updated Look Vector The formula for calculating the updated Look vector is: qRotation * qLook * qRotation' (qRotation' is the conjugate of qRotation) Each of the three operands will be discussed next. Local Rotation Quaternion The first quaternion that is used to calculate the updated Look vector, qRotation, is a local rotation. Quaternion theory provides a formula for computing the local ro- tation. In this case, the local rotation is generated using a direction vector for X, Y, and Z. Rotations about the X axis are applied using the Look vector. Rotations about the Y axis are applied using the Right direction vector. The rotation angle stored in the W component is obtained from the deviation of the mouse (or thumbstick) from the center of the window. With this information, we can generate the local rotation by writing the following: qRotation.W = cos(MouseDeviationFromCenter/2) qRotation.X = UnitDirection.X * sin(MouseDeviationFromCenter/2) qRotation.Y = UnitDirection.Y * sin(MouseDeviationFromCenter/2) qRotation.Z = UnitDirection.Z * sin(MouseDeviationFromCenter/2) Using the Look Vector as a Quaternion The next quaternion used in the formula for the updated Look vector is based on the Look direction: qLook.X = Look.X qLook.Y = Look.Y qLook.Z = Look.Z qLook.W = 0
  2. C H A P T E R 1 7 279 Building a Graphics Engine Camera Conjugate Quaternion A conjugate quaternion is used to calculate the updated Look vector. The conjugate is created by negating a quaternion vector’s X, Y, and Z components: Quaternion conjugate = (-Quaternion.X, -Quaternion.Y, -Quaternion.Z, Quaternion.W) Quaternion Product The equation for multiplying two quaternion is as follows: (Quaternion1*Quaternion2).W = W 1 x2 - x 1 w2 + y1y2 - z1z2 (Quaternion1*Quaternion2).X = w 1 x2 + x 1 w2 + y1z2 - z1y2 (Quaternion1*Quaternion2).Y = w 1 y2 - x 1 z2 + y1w2 + z1x2 (Quaternion1*Quaternion2).Z = w 1 z2 + x 1 y2 - y1x2 + z1w2 Updating the View The updated Look vector is obtained using the product of local rotation, look, and conjugate quaternions. Updated Look Vector = qRotation * qLook * qRotation' With the result from this product, the View can be updated: Updated View = Updated Look Vector + Position Now you will apply this logic to the graphics engine to update your view. Updating the View in the Camera Class RotationQuaternion() can be added to the camera class to generate the local rotation quaternion based on the direction vector. The first parameter of this method represents the shift of the mouse or thumbstick from the resting position. The second parameter is a direction vector that can be either the Look or Right vector: private Vector4 RotationQuaternion(float degrees, Vector3 direction){ Vector4 unitAxis = Vector4.Zero; Vector4 axis = new Vector4(direction, 0.0f); // only normalize if necessary if ((axis.X != 0 && axis.X != 1) || (axis.Y != 0 && axis.Y != 1) || (axis.Z != 0 && axis.Z != 1)){
  3. 280 MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE unitAxis = Vector4.Normalize(axis); } float angle = degrees * MathHelper.Pi/180.0f; float sin = (float)Math.Sin(angle/2.0f); // create the quaternion. Vector4 quaternion = new Vector4(0.0f, 0.0f, 0.0f, 0.0f); quaternion.X = axis.X * sin; quaternion.Y = axis.Y * sin; quaternion.Z = axis.Z * sin; quaternion.W = (float)Math.Cos(angle/2.0f); return Vector4.Normalize(quaternion); } Next, you’ll add the UpdateView() method. UpdateView() computes the product of these three quaternions and uses it to update the player’s view: private void UpdateView(float rotationAmount, Vector3 direction) { // local rotation quaternion Vector4 Q = RotationQuaternion(rotationAmount, direction); Vector4 look = Vector4.Zero; look.X = view.X - position.X; look.Y = view.Y - position.Y; look.Z = view.Z - position.Z; // rotation quaternion * look Vector4 Qp; Qp.X = Q.W*look.X + Q.X*look.W + Q.Y*look.Z - Q.Z*look.Y; Qp.Y = Q.W*look.Y - Q.X*look.Z + Q.Y*look.W + Q.Z*look.X; Qp.Z = Q.W*look.Z + Q.X*look.Y - Q.Y*look.X + Q.Z*look.W; Qp.W = Q.W*look.W - Q.X*look.X - Q.Y*look.Y - Q.Z*look.Z; // conjugate is made by negating quaternion x, y, and z Vector4 conj = new Vector4(-Q.X, -Q.Y, -Q.Z, Q.W); // updated look vector Vector4 Qlook;
  4. C H A P T E R 1 7 281 Building a Graphics Engine Camera Qlook.X = Qp.W*conj.X + Qp.X*conj.W + Qp.Y*conj.Z - Qp.Z*conj.Y; Qlook.Y = Qp.W*conj.Y - Qp.X*conj.Z + Qp.Y*conj.W + Qp.Z*conj.X; Qlook.Z = Qp.W*conj.Z + Qp.X*conj.Y - Qp.Y*conj.X + Qp.Z*conj.W; Qlook.W = Qp.W*conj.W - Qp.X*conj.X - Qp.Y*conj.Y - Qp.Z*conj.Z; // cap view at ground and sky if (Qlook.Y > -0.49f && Qlook.Y < 0.49f){ // updated view equals position plus the quaternion view.X = position.X + Qlook.X; view.Y = position.Y + Qlook.Y; view.Z = position.Z + Qlook.Z; } } The camera class uses the ChangeView() method to receive changes in View di- rection from the game class and apply them to the camera orientation. ChangeView() checks whether the mouse or right stick has been shifted. If no movement is detected, the method exits and no changes to the view are performed. Otherwise, a relative measure for the X and Y rotations is generated based on the de- viation of the mouse from the center of the window. Rotations about the X axis are applied using the Right vector. Rotations about the Y axis are applied using the Up vector: public void ChangeView(float X, float Y) { // exit if no change to view if (X == 0 && Y == 0) return; float rotationX, rotationY; const float SCALEX = 50.0f; const float SCALEY = 2000.0f; Vector3 look = view - position; // tilt camera up and down Vector3 right = Vector3.Cross(look, up); rotationX = Y / SCALEX; UpdateView(rotationX, Vector3.Normalize(right)); // swivel camera left and right rotationY = X * timeLapse / SCALEY; UpdateView(-rotationY, up); }
  5. 282 MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE With ChangeView() in the game class, it will adjust the camera view according to shifts of the mouse or left thumbstick. A revised SetView() method in the cam- era class replaces the existing one to process changes to the camera’s Look direction for each frame: public void SetView(Vector2 viewChange){ ChangeView(viewChange.X, viewChange.Y); viewMatrix = Matrix.CreateLookAt(position, view, up); } Triggering Changes to the View from the Game Class Back inside the game class, the camera needs to be enabled for manipulation by the game controller, keyboard, and mouse. The camera will function on the PC like a first-person shooter, where a typical configuration uses the mouse to change the view. XNA provides the Mouse and MouseState classes to handle the mouse on a PC. When run on the PC, the mouse has to be enabled in the game class. The mouse will adjust the view by checking the distance from the center of the window to the mouse. At the top of the game class, a MouseState object is declared: #if !XBOX MouseState mouse; #endif The game class’s ChangeView() method receives changes in view on X and Y that are triggered from the game class by mouse movements, or by shifts to the right thumbstick. After the relative changes in view have been captured and processed on the PC, the Mouse class SetPosition() method moves the cursor back to the cen- ter of the window so the mouse’s relative change from the center of the window can be calculated in the next frame. Otherwise, the camera will use the right stick’s devia- tion from the center to calculate the change in view: Vector2 ChangeView(GameTime gameTime){ const float SENSITIVITY = 250.0f; const float VERTICAL_INVERSION =-1.0f; // vertical view control // negate to reverse // handle change in view using right and left keys KeyboardState kbState = Keyboard.GetState(); int widthMiddle = Window.ClientBounds.Width/2; int heightMiddle = Window.ClientBounds.Height/2; Vector2 change = Vector2.Zero;
  6. C H A P T E R 1 7 283 Building a Graphics Engine Camera GamePadState gp = GamePad.GetState(PlayerIndex.One); if (gp.IsConnected == true) // gamepad on PC / Xbox { float scaleY = VERTICAL_INVERSION * (float) gameTime.ElapsedGameTime.Milliseconds/50.0f; change.Y = scaleY * gp.ThumbSticks.Right.Y * SENSITIVITY; change.X = gp.ThumbSticks.Right.X * SENSITIVITY; } else{ // mouse only (on PC) #if !XBOX float scaleY = VERTICAL_INVERSION * (float) gameTime.ElapsedGameTime.Milliseconds/100.0f; float scaleX = (float) gameTime.ElapsedGameTime.Milliseconds/400.0f; // get cursor position mouse = Mouse.GetState(); // cursor not at center on X if (mouse.X != widthMiddle){ change.X = mouse.X - widthMiddle; change.X /= scaleX; } // cursor not at center on Y if (mouse.Y != heightMiddle){ change.Y = mouse.Y - heightMiddle; change.Y /= scaleY; } // reset cursor back to center Mouse.SetPosition(widthMiddle, heightMiddle); #endif } return change; } To update your camera’s view from the game class (for each frame), replace the existing call to SetView() from the Update() method with this revision that changes the view based on how the mouse or right thumbstick is shifted: cam.SetView(ChangeView(gameTime)); If you run your code now, your project will have a fully functional camera en- abled. To actually see it moving, you need to draw some kind of reference, such as
  7. 284 MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE ground, a triangle, or a 3D model. The camera moves and strafes with the left thumbstick or ARROW keys. It changes view with the right thumbstick or the mouse. With this camera, your game players now have full access to journey into the world hosted by your graphics engine. B UILDING THE BASE CODE FROM SCRATCH EXAMPLE Not only is the camera viewer the heart of the base code, but this camera viewer dis- cussion completes our explanation of how the base code works. You can actually build the base code starting with the solution from this last example. When you add the code from the “Texture Example, Part A: Adding the Grass Texture” demonstra- tion in Chapter 9, you will see the grassy ground when you run the project. Then, you can follow the steps listed in Chapter 6 in “Position Color Shader Example: Ref- erencing the Shader” to add the simple shader for drawing surfaces with colored ver- tices. Finally, inside InitializeBaseCode(), add the following instruction to write the title of this book on the status bar and complete the base code: Window.Title = "Microsoft® XNA Game Studio Creator's Guide 2nd Edition"; You have just created the base code for this book. If you have followed the discus- sions behind this code, you should now be able to say you understand how the base code works and, using this book as your reference, you can create it from scratch. C HAPTER 17 REVIEW EXERCISES To get the most from this chapter, try out these chapter review exercises. Follow the step-by-step examples presented in this chapter, but make the follow- ing changes. 1. Add an option to “invert” the camera. This is a common first-person shooter game feature that allows players to reverse the direction of the Up and Down view control. 2. Add an option to move the camera forward on the PC when the left mouse button is pressed.
  8. CHAPTER 18 Collision Detection
  9. detection determines whether two objects COLLISION overlap and therefore have collided in your virtual world. Accurate collision detection is fundamental to a solid game en- gine. Without collision detection, your cars would drive off the road, your people would walk through buildings, and your camera would travel through cement walls. Collision detection is also fundamental when dealing with any sort of missiles. For example, if you had faulty detection applied to a rocket, you might successfully hit your target and not receive credit—or possibly even worse, your enemies might miss a shot at you and be credited with a hit. These sorts of problems are occasionally evi- dent in commercial games, but to avoid player frustration, you should strive to have excellent collision detection. This chapter shows you how to use collision detection to add boundaries around your game objects. XNA has two main types for implementing collision detection. They are: 1. BoundingBox 2. BoundingSphere Implementing collision detection with these types involves encasing your physi- cal game objects either with invisible boxes or spheres. Obviously, a box will better suit a rectangular high-rise building, while a bounding sphere offers a better fit for rounded objects such as a domed arena. Together, these two collision types deliver a powerful range of options that allow you to implement efficient collision check- ing in any situation. F INE-TUNING YOUR COLLISION DETECTION SYSTEMS For irregular shapes—such as an alien ship—you can use a group of smaller boxes or spheres for each section. Compared to boxes, spheres are easier to transform and they use less storage space, so you’ll want to use spheres when you can. Also, XNA’s model loader exposes mesh objects in the .X models and .FBX mod- els. Each mesh within a model is assigned a bounding sphere with a properly sized ra- dius. With this system already in place, you can load your model in a modeling tool such as Blender or MilkShape, and add spheres using the designer to cover the surface of your model. You then need to delete your original model group(s) from this model project. After exporting your cluster of spheres as a separate model, you can load them in your XNA project and extract bounding-sphere information at run time. This means you can rapidly and accurately build your collision detection systems us- ing a model design tool and the XNA Framework. Figure 18-1 shows models with spheres added before the original meshes were deleted. 286
  10. C H A P T E R 1 8 287 Collision Detection FIGURE 18-1 Additional spheres surrounding the original mesh are used for collision detection. E ARLY WARNING SYSTEMS As you continue to add bounding spheres or bounding boxes to improve the accuracy of your collision detection, you may encounter a performance decrease if too much collision checking is required each frame. For this reason, you need broad colli- sion-checking routines to first detect if two objects are within close proximity of each other before doing anything else. These early warning systems only need to compare a few large blunt bounding spheres or boxes to check for collisions. Whenever a close proximity between large bounding objects is established, more intensive and accu- rate collision-checking routines can be used. A decent early warning system avoids the need to run exhaustive sets of routines every frame. C ONTAINMENTTYPE Both BoundingSphere and BoundingBox types use a ContainmentType to de- scribe the extent of surface overlap. These containment types include Contains, Disjoint, and Intersect. This allows you to determine if your spheres or boxes contain or intersect each other. A Disjoint condition occurs when the collision ob- jects are completely separate.
  11. 288 MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE B OUNDINGSPHERE The use of bounding spheres is popular because spheres can easily be transformed and compared and they do not require much processing power. The bound- ing-sphere method involves creating an invisible sphere around each object that needs collision detection. If the distance between the centers of the two spheres is less than the sum of their radii, a collision is detected. You don’t have to actually perform these calculations, though, because XNA’s accompanying methods for the BoundingSphere type already do it for you. Initializing the Bounding Sphere When you initialize the bounding sphere, you will specify the location of the center of the sphere using a three-dimensional vector and the radius of the sphere. public BoundingSphere(Vector3 center, float radius); Intersects() The BoundingSphere Intersects() method has several overloads. Two com- mon overloads allow comparisons between the current bounding sphere and either a sphere or bounding box that can be passed in as parameters. public bool Intersects(BoundingBox box); public bool Intersects(BoundingSphere sphere); Contains The bounding-sphere object offers the Contains() method to compare the bound- ing sphere with another bounding sphere, a bounding box, or a three-dimensional position. public void Contains(ref BoundingSphere sphere, out ContainmentType result); public void Contains(ref BoundingBox box, out ContainmentType result); public void Contains(ref Vector3 point, out ContainmentType result); B OUNDINGBOX The BoundingBox type offers collision checking for objects contained within rect- angular 3D space. When you initialize this type, you pass in the minimum and maxi- mum positions for the corners of your bounding box:
  12. C H A P T E R 1 8 289 Collision Detection BoundingBox boundingBox = new BoundingBox(Vector3 min, Vector3 max); The BoundingBox type in XNA is not axis aligned, so if you rotate or animate your bounding box you will have to reinitialize it each frame to reset the new mini- mum and maximum corners. This is not a practical solution because the bounding box shape will change significantly at different angles of a rotation. In spite of limitations you might face with the bounding box, it really is a valuable structure—especially for rectangular objects that stay in one place. In the code dem- onstration later, you will see how over one hundred spheres that line the walls of the world are replaced with one large box for a giant leap in providing efficient collision detection. Intersects() Similar to the BoundingSphere Intersects() method, the BoundingBox Intersects() method has several overloads. Two common overloads allow com- parisons between the current bounding sphere and either a sphere or bounding box that can be passed in as parameters. public bool Intersects(BoundingBox box); public bool Intersects(BoundingSphere sphere); Contains() The BoundingBox Contains() method has several variations. Here are three common ways to use the Contains() method: 1. Comparing one bounding box with another: public void Contains(ref BoundingBox box, out ContainmentType result); 2. Comparing a bounding box with a bounding sphere: public void Contains(ref BoundingSphere sphere, out ContainmentType result); 3. Comparing a bounding box with a single three-dimensional position: public void Contains(ref Vector3 point, out ContainmentType result); The second Contains() overload mentioned is very exciting because it allows you to compare your boxes with spheres, giving you many possibilities to generate the best fit for your objects. We will show an example of this overload later in this chapter to generate a very efficient and well-fitted collision-detection routine be- tween the car and the box that surrounds the world.
  13. 290 MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE C OLLISION DETECTION EXAMPLE: INITIALIZING AND DRAWING BOUNDING SPHERES This example shows how to build a collision detection framework to implement XNA’s BoundingSphere and BoundingBox objects. Usage of bounding spheres is emphasized because spheres are easier to update and transform than bounding boxes. The framework provided here also allows you to draw your bounding spheres while you debug your code so you can ensure that your spheres are sized and posi- tioned properly. This example begins with the solution from the “Adding a Car as a Third-Person Object” example from Chapter 14. You will first modify the code by manually add- ing bounding spheres around the edge of the world. Then, in a modeling tool, you will add a group of spheres for the car and wheel. Once you add your cluster of spheres, you will delete the original model and export it to .fbx format. Once you have exported your sphere group, you will load them in your project to obtain the bounding-sphere center and radius information. When you are finished working with the BoundingSphere portion of this exam- ple, you will have a world that is divided into four quarters. Four large spheres each circle one quarter of the smaller spheres that line the wall of the world. A large sphere also surrounds the entire car, which moves with the camera. If a collision is detected between the large sphere around the car and one of the small spheres on the wall in the current quarter of the world, a more exhaustive routine checks for collisions be- tween all car spheres and wall spheres within that world quadrant. See Figure 18-2. At the end of this demonstration—since our world is a rectangle—you will replace the spheres that surround our world with just one bounding box for even larger per- FIGURE 18-2 Different sphere groups for varying levels of efficiency and accuracy
  14. C H A P T E R 1 8 291 Collision Detection formance gains. The final solution provides fast and accurate collision detection be- tween the car and the world walls. Once you have the solution from Chapter 14 open, add a code file named Vertices.cs to create a series of PositionColor vertices for drawing a sphere using line strips. The vertices are arranged in a grid of slices or columns and stacks, which are rows. You need to add this code to the Vertices.cs file to generate your sphere vertices: using System; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; namespace MGHGame{ public class SphereVertices{ private Color color; private int numPrimitives; private Vector3 offset = Vector3.Zero; public SphereVertices(Color vertexColor, int totalPrimitives, Vector3 position){ offset = position; color = vertexColor; numPrimitives = totalPrimitives; } public VertexPositionColor[] InitializeSphere(int numSlices, int numStacks, float radius){ Vector3[] position = new Vector3[(numSlices + 1) * (numStacks + 1)]; float angleX, angleY; float rowHeight = MathHelper.Pi / numStacks; float colWidth = MathHelper.TwoPi / numSlices; float X, Y, Z, W; // generate horizontal rows (stacks in sphere) for (int stacks = 0; stacks
  15. 292 MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE X = W * (float)Math.Sin(angleY); Z = W * (float)Math.Cos(angleY); // position sphere vertices at offest from origin position[stacks * numSlices + slices] = new Vector3(X+offset.X, Y+offset.Y, Z+offset.Z); } } int i = -1; VertexPositionColor[] vertices = new VertexPositionColor[2 * numSlices * numStacks]; // index vertices to draw sphere for (int stacks = 0; stacks < numStacks; stacks++){ for (int slices = 0; slices < numSlices; slices++){ vertices[++i] = new VertexPositionColor( position[stacks * numSlices + slices], color); vertices[++i] = new VertexPositionColor( position[(stacks + 1) * numSlices + slices], color); } } return vertices; } } } Now that the vertex building code is in place, you need to add another code file called Sphere.cs to generate a list of bounding spheres. When the SHOW constant is set to true in the game class, vertex data is generated for drawing the spheres. If SHOW is set to false, the vertex data is not generated. In either case, though, the AddSphere() routine generates a dynamic list of spheres for each group that is rep- resented by an instance of the Sphere class: using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; namespace MGHGame{
  16. C H A P T E R 1 8 293 Collision Detection public struct SphereData{ public VertexPositionColor[] vertices; public BoundingSphere boundingSphere; } class Sphere{ const int SLICES = 10; const int STACKS = 10; public List sphere = new List(); private int numPrimitives = 0; public int PrimitiveCount { get { return numPrimitives; } } public PrimitiveType primitiveType = PrimitiveType.LineStrip; private Color vertexColor = Color.White; private bool show; public bool Show { get { return show; } } public Sphere(bool showSphere){ show = showSphere; } public void AddSphere(float radius, Vector3 position, Color color){ SphereData sphereData = new SphereData(); sphereData.boundingSphere.Center = position; sphereData.boundingSphere.Radius = radius; if (show){ numPrimitives = SLICES * STACKS * 2 - 1; SphereVertices sphereVertices = new SphereVertices(color, numPrimitives, position); sphereData.vertices = sphereVertices.InitializeSphere(SLICES,STACKS, radius); } sphere.Add(sphereData); } } } Our sphere group objects and corresponding lists are initialized from the game class. Each list of spheres is associated with one separate collision object. For example,
  17. 294 MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE we have a separate group of spheres for each wheel, the car, and sections of the wall within our world. For blunt but fast initial collision detection, we have one group named bigCar that stores only one sphere surrounding the entire car. This large sphere is checked with spheres along the wall. The smaller car spheres are only checked if the large sphere registers a collision. Also, to cut down on the number of wall spheres that are checked each frame, the world is divided into four sets of spheres. The quad- rant that contains the car is also where the player is currently located. This means that only one of the four quadrants needs to be checked for collisions. To help track the groups of spheres, named enumerations are used. A separate sphere list is created for each named group. Each group of spheres implements colli- sion detection for one specific object. public enum Group { wheelFL, wheelFR, wheelBL, wheelBR, // wheels // bigCar stores one large sphere for initial collision checks // car holds many small spheres for accuracy – but they are only checked // when bigCar registers a collision car, bigCar, // wall spheres divided into 4 groups wallBackRight, wallBackLeft, wallFrontLeft, wallFrontRight, // wall spheres grouped according to location within 4 world quadrants worldBackRight, worldBackLeft, worldFrontLeft, worldFrontRight } const int TOTAL_SPHERE_GROUPS = 14; Sphere[] sphereGroup = new Sphere[TOTAL_SPHERE_GROUPS]; const bool SHOW = true; // set to false during release The following helper method, WallSphere(), is added in the game class to add spheres to the list: void WallSphere(Group group, float X, float Z, float radius){ Color color = Color.Yellow; Vector3 position = new Vector3(X, 0.0f, Z); sphereGroup[(int)group].AddSphere(radius, position, color); } When the program begins in the game class, InitializeWallSpheres() manually adds spheres around the edges of our world. This method also adds four larger spheres to determine the quadrant where the car is located: void InitializeWallSpheres(){ // initialize vertices around border of world
  18. C H A P T E R 1 8 295 Collision Detection sphereGroup[(int)Group.wallBackRight] = new Sphere(SHOW); sphereGroup[(int)Group.wallBackLeft] = new Sphere(SHOW); sphereGroup[(int)Group.wallFrontRight] = new Sphere(SHOW); sphereGroup[(int)Group.wallFrontLeft] = new Sphere(SHOW); Vector3 wall; const float RADIUS = 0.3f; const float SPHERE_INCREMENT = 2.0f; // 1st qtr wall = new Vector3(BOUNDARY, 0.0f, -BOUNDARY); // small spheres right and left - back half of world while (wall.Z < 0.0f){ WallSphere(Group.wallBackRight, wall.X, wall.Z, RADIUS); WallSphere(Group.wallBackLeft, -wall.X, wall.Z, RADIUS); wall.Z += SPHERE_INCREMENT * RADIUS; } // small spheres right and left - front half of world while (wall.Z < BOUNDARY){ WallSphere(Group.wallFrontRight, wall.X, wall.Z, RADIUS); WallSphere(Group.wallFrontLeft, -wall.X, wall.Z, RADIUS); wall.Z += SPHERE_INCREMENT * RADIUS; } // small spheres right side of world - back and front wall = new Vector3(BOUNDARY, 0.0f, -BOUNDARY); while (wall.X > 0.0f){ WallSphere(Group.wallBackRight, wall.X, wall.Z, RADIUS); WallSphere(Group.wallFrontRight, wall.X, -wall.Z, RADIUS); wall.X -= SPHERE_INCREMENT * RADIUS; } // small spheres left side of world - top and bottom while (wall.X > -BOUNDARY){ WallSphere(Group.wallBackLeft, wall.X, wall.Z, RADIUS); WallSphere(Group.wallFrontLeft, wall.X, -wall.Z, RADIUS); wall.X -= SPHERE_INCREMENT * RADIUS; } // separate world into 4 quadrants sphereGroup[(int)Group.worldBackRight] = new Sphere(SHOW); sphereGroup[(int)Group.worldBackLeft] = new Sphere(SHOW); sphereGroup[(int)Group.worldFrontLeft] = new Sphere(SHOW);
  19. 296 MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE sphereGroup[(int)Group.worldFrontRight] = new Sphere(SHOW); // large spheres – each one surrounds 1 quarter of world float radius = BOUNDARY; WallSphere(Group.worldBackRight, BOUNDARY, -BOUNDARY, radius); WallSphere(Group.worldBackLeft, -BOUNDARY, -BOUNDARY, radius); WallSphere(Group.worldFrontLeft, -BOUNDARY, BOUNDARY, radius); WallSphere(Group.worldFrontRight, BOUNDARY, BOUNDARY, radius); } You can now add ExtractBoundingSphere() to retrieve and store bound- ing-sphere radius and position data from your sphere models: void ExtractBoundingSphere(Model tempModel, Color color, int groupNum){ // set up model temporarily Matrix[] tempMatrix = new Matrix[tempModel.Bones.Count]; tempModel.CopyAbsoluteBoneTransformsTo(tempMatrix); // generate new sphere group BoundingSphere sphere = new BoundingSphere(); sphereGroup[groupNum] = new Sphere(SHOW); // store radius, position, and color information for each sphere foreach (ModelMesh mesh in tempModel.Meshes){ sphere = mesh.BoundingSphere; Vector3 newCenter = sphere.Center; Matrix transformMatrix = ScaleModel(); sphereGroup[groupNum].AddSphere(sphere.Radius, sphere.Center, color); } } If you compare the process of manually adding bounding spheres on your own to adding spheres in a designer tool, you will find that it is a lot easier and more accurate to use a modeling tool. To generate the necessary groups of bounding spheres, you need to import the hotrod.fbx model and wheel.fbx models into either MilkShape or some other modeling tool such as Blender. Next, you have to add spheres around the edges of each model using the designer so they can be used for collision checking. When you are done, delete the original meshes so you are left only with the surround- ing spheres. At this point, you can export these sphere groups as .fbx models. We have already created these sphere groups in MilkShape and they are contained in the hotrodSpheres.fbx, bigCarSpheres.fbx, and wheelSpheres.fbx files, which you can find in the Models folder on the book’s website. These model files must be referenced
  20. C H A P T E R 1 8 297 Collision Detection under the content node so you can load their bounding-sphere information at run time. The InitializeModelSpheres() routine loads these models and passes their references to the ExtractBoundingSphere() method to retrieve and store the radius and center data contained inside each file. Since this routine only stores the model data on the stack, the unused model data is released from memory when the method finishes. In other words, you do not have to worry about storing a bunch of unused models in memory during the course of your game. Add InitializeModelSpheres() to your game class to temporarily load these new models: void InitializeModelSpheres(){ // smaller fine spheres around car Model sphereModel = Content.Load("Models\\hotrodSpheres"); ExtractBoundingSphere(sphereModel, Color.Red, (int); // load big sphere surrounding the car for broad collision checking sphereModel = Content.Load("Models\\bigCarSphere"); ExtractBoundingSphere(sphereModel, Color.Blue, (int)Group.bigCar); // same wheel drawn four times each with different transformation // for convenience same wheel is loaded four times to create a // separate collision object for each wheel sphereModel = Content.Load("Models\\wheelSpheres"); ExtractBoundingSphere(sphereModel, Color.White, (int)Group.wheelFL); ExtractBoundingSphere(sphereModel, Color.White, (int)Group.wheelFR); ExtractBoundingSphere(sphereModel, Color.White, (int)Group.wheelBL); ExtractBoundingSphere(sphereModel, Color.White, (int)Group.wheelBR); } You can now call your sphere-loading routines from Initialize(): InitializeWallSpheres(); InitializeModelSpheres(); Since the sphere group models used in this example were created using a different modeling tool than the original model designer, they need to be scaled differently. The SphereScalar() and ScaleSpheres() methods are needed in the game class at this stage to apply scaling when you transform and draw your bounding spheres: float SphereScalar(){ return 0.2f; }
Đồng bộ tài khoản