Microsoft XNA Game Studio Creator’s Guide- P13

Chia sẻ: Cong Thanh | Ngày: | Loại File: PDF | Số trang:30

0
58
lượt xem
8

Microsoft XNA Game Studio Creator’s Guide- P13

Mô tả tài liệu

Microsoft XNA Game Studio Creator’s Guide- P13: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ủ đề:

Bình luận(0)

Lưu

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

1. 338 MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE pointSpriteEffect.Techniques[0].Passes[0].End(); pointSpriteEffect.End(); } The code required to draw the particle using point sprites is very similar to code that draws any textured primitive surface. Scaling for the entire group of particles is triggered once—based on the distance between the camera and the group of particles. Each fire particle is rendered individually. The group of particles is moved into posi- tion and then each individual particle is translated from the fire base to its own posi- tion in the roaring fire. The particle’s life level, which ranges between 0 for dead and 1 for full life, is passed to the shader so it can be used to fade the color of the flame and shrink the size as each particle rises away from the core of the fire: private void DrawParticles(){ // 1: declare matrices Matrix world, translateParticle, translateGroup; // scale the point sprite by cam distance to the group of particles Vector3 particlesPosition = new Vector3(0.0f, 0.42f, -5.0f); // 2: initialize matrices translateGroup = Matrix.CreateTranslation(particlesPosition); for (int i = 0; i < NUM_PARTICLES; i++){ // translate each individual particle translateParticle = Matrix.CreateTranslation(particle[i].position); // 3: build cumulative world matrix using I.S.R.O.T. sequence // identity, scale, rotate, orbit(translate & rotate), translate world = translateGroup * translateParticle; // 4: set shader variables pointSpriteEffectWVP.SetValue( world*cam.viewMatrix*cam.projectionMatrix); pointSpriteEffectTexture.SetValue(particleTexture); pointSpriteEffectFade.SetValue(particle[i].life); pointSpriteEffectProjection.SetValue(cam.projectionMatrix); pointSpriteEffectViewport.SetValue( GraphicsDevice.Viewport.Height); // 5: draw object-select vertex type, primitive type, # primitives ParticleShader(vertexBuffer); } }
2. C H A P T E R 2 0 339 Particle Effects Inside the Draw() method, a call can be made to draw the fire: DrawParticles(); Finally, as one last touch to make the example a little more interesting, we’ll add a model torch. For this to work, the torch.fbx file must be referenced from a Models folder under the Content node in the Solution Explorer. The torch.bmp texture will also need to be placed in the Models folder in your project but doesn’t need to be refer- enced. If the torch.bmp texture is referenced from the Solution Explorer, it will be con- fused with the torch.fbx model because they both use the same name. The torch.fbx and torch.bmp files can be found in the Models folder on this book’s website. The logic and methods used to load and draw the models are the same as explained in Chapter 14, so the details behind these next steps will be minimal. First, declara- tions in the game class are required to store the torch model object and the array for the torch’s bone transformations: Model torchModel; Matrix[] matTorch; This InitializeTorch() method includes the code to load the torch and set the transformation matrix for the meshes in it. Placing this in the game class allows you to load the model: void InitializeTorch(){ torchModel = Content.Load("Models\\torchModel"); matTorch = new Matrix[torchModel.Bones.Count]; torchModel.CopyAbsoluteBoneTransformsTo(matTorch); } InitializeTorch() can be called from the Initialize() method to read in the torch.fbx file when the program begins: InitializeTorch(); You can add this next method to your game class to draw the torch: private void DrawTorch(Model model){ // 1: declare matrices Matrix world, translation, scale; // 2: initialize matrices scale = Matrix.CreateScale(0.50f, 0.50f, 0.50f); translation = Matrix.CreateTranslation(0.0f, 0.35f, -5.0f); foreach (ModelMesh mesh in model.Meshes){ // 3: build cumulative matrix using I.S.R.O.T. sequence
3. 340 MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE // identity,scale,rotate,orbit(translate & rotate),translate world = scale * translation; foreach (BasicEffect effect in mesh.Effects){ // 4a. pass wvp effect.World = matTorch[mesh.ParentBone.Index] * world; effect.View = cam.viewMatrix; effect.Projection = cam.projectionMatrix; // 4b. set lighting effect.EnableDefaultLighting(); effect.SpecularPower = 0.01f; } // 5: draw object mesh.Draw(); } } The method to draw the torch model is triggered from Draw() along with the other draw routines that are called. DrawTorch() must be called before the point sprites are rendered to ensure that the point sprites are layered properly over the 3D model: DrawTorch(torchModel); To observe deviant layering when ZWriteEnable is false, try calling DrawTorch() after drawing the point sprites. You will notice that the flame no lon- ger appears to come from the torch, as shown in Figure 20-3. FIGURE 20-3 Draw order issues for point sprites when ZWriteEnable is false
4. C H A P T E R 2 0 341 Particle Effects Setting ZWriteEnable in the shader to false ensures that the point sprites will be blended together. However, sometimes setting ZWriteEnable to true looks good when the background is colored the same as the pixels that are supposed to be transparent, or when the particles are small or disperse. You can always experiment to see what looks good, but remember that a PC game may be played in several differ- ent environments—on different-sized windows. You should consider this in your de- cision as to whether or not to use ZWriteEnable. With the DestBlend state set to 1 in the shader, shiny blending is applied. As a re- sult, the point sprite can only be seen against darker backgrounds. To ensure that you can see the fire against the background, replace the instruction that clears the back- ground and resets the color inside the Draw() method with this new instruction: graphics.GraphicsDevice.Clear(Color.Black); When you run your program, it will show a steady, ever-changing body of fire. As you back away from the fire, the size of the particles will scale properly to match the size of the primitive ground surface and model torch. At any angle the fire particles will face the camera, so you don’t need to have any billboarding code. This is a cool effect, but it’s really only the beginning of what you can do with point sprites and particle effects. This particle effect would be ideal for creating ex- plosions, exhaust trails from missiles, stardust, and more. You could even increase the number of textures used or the particle types to make the fire more interesting. C HAPTER 20 REVIEW EXERCISES To get the most from this chapter, try out these chapter review exercises. 1. Try the step-by-step examples provided in this chapter, if you have not already done so. 2. Starting with the existing algorithm, create an additional particle stream to simulate smoke from your fire. 3. Modify your fire algorithm to create an explosion.
6. CHAPTER 21 Keyframe Animations
7. animations combine a timer and inter- KEYFRAME polation to determine the location of game objects. The term keyframe comes from the world of hand-drawn animation. The senior artists would draw the “key frames” and then other artists would create the “in-betweens.” In computer games, the keyframes still define the most important stages of the animation, but interpolation is used to fill in the frames in between. This can mean interpolating the position or orientation of an object. For example, in a rac- ing game, you might want to include a pace car when the cars are under a caution flag. Using keyframes, you can control the course that the pace car follows as it leads the pack and then eventually drives off into the pit. By the end of this chapter, you will be able to use keyframes to map out a route and regulate the speed of this sort of animation. The proper technique is to use a timeline to control the speed of animations; this allows the animation to be rendered at the same speed regardless of the system that runs it. Until now, the examples in this book have generated translational animations by incrementing X, Y, and Z coordinates by a product of the increment unit and the difference in time between the current and previous frame. Interpolation is a similar process, but it offers other possibilities for moving objects on linear and curved paths. For translations or rotations, a path may be defined for the object and a spe- cific duration of time may be assigned for completing the path. I NTERPOLATION Interpolation can be used to project the location of a game object based on the ex- pected time of arrival at the destination. For example, if the time between the starting frame and ending frame of an object is 10 seconds, and the object is expected to travel 5 units on the X plane and 10 units on the Z plane, then interpolation can be used to estimate the object’s location at any time between 0 and 10 seconds. At 4 seconds, in- terpolation would project the object to be at X = 2 and Z = 4. C URVES When mapping out keyframes on your timeline, you probably won’t always want your vehicles traveling in a straight line. You might want to use a curve to map out a path for a keyframe animation. This chapter uses Bézier curves to fulfill this role, but you could use other types of curves for the same task. Most splines are calculated by similar methods as the Bézier curve, so the Bézier curve provides a good example of how this family of curves can be implemented in your game algorithms. The Bézier curves in this chapter use four points: a start point, an end point, and two control points (see Figure 21-1). The control points provide the user with a way to stretch or compress the curve. Stretching the control points will “push” or “pull” 344 the curve into different shapes.
8. C H A P T E R 2 1 345 Keyframe Animations FIGURE 21-1 Jet travels along Bezier curve that has four control points The formula for finding a point on a Bézier curve is based on the relative position between the start of the curve (0 percent) and the end of the curve (100 percent): Point on Bezier Curve = V start * (1 – fraction)3 + V control 1 * 3 * fraction * (1 – fraction)2 + V control 2 * 3 * fraction2 * (1 – fraction) + V end * fraction3 The following example puts this formula to use. K EYFRAME ANIMATION EXAMPLE This example demonstrates a timed animation that moves a model CF-18 Hornet fighter jet on a fixed route. Two parts of the route are defined by straight lines and two parts of the route are defined by Bézier curves. The CF-18 fighter jet and route are shown in Figure 21-2. FIGURE 21-2 CF-18 fighter jet, animated using a series of straight lines and Bézier curves
9. 346 MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE The code for this example starts with either the MGHWinBaseCode or the MGH360BaseCode project available on this book’s website. A fixed period is specified for completing the combined sections. The total anima- tion time needed to complete all combined routes is 11,200 milliseconds (11.2 sec- onds). At each pass through Update(), the algorithm checks to determine how far along the path the object should be at that specific time. The position of the CF-18 is projected using the keyframes, which store the fixed end points of the lines and points on the Bézier curves. The first step is to store each route. Two Bézier curves are being used, and two lines are being used. The Bézier curve stores four control points: private Vector3[] bezierA = new Vector3[4]; // route 1 private Vector3[] lineA = new Vector3[2]; // route 2 private Vector3[] bezierB = new Vector3[4]; // route 3 private Vector3[] lineB = new Vector3[2]; // route 4 This first routine will initialize the jet's route: private void InitializeRoutes(){ // length of world quadrant const float END = -BOUNDARY; // 1st Bezier curve control points (1st route) bezierA[0] = new Vector3( END+5.0f, 0.4f, 5.0f); // start bezierA[1] = new Vector3( END+5.0f, 2.4f, 3.0f*END); // ctrl 1 bezierA[2] = new Vector3(-END-5.0f, 4.4f, 3.0f*END); // ctrl 2 bezierA[3] = new Vector3(-END-5.0f, 5.4f, 5.0f); // end // 1st line between Bezier curves (2nd route) lineA[0] = new Vector3(-END-5.0f, 5.4f, 5.0f); // start lineA[1] = new Vector3(-END-5.0f, 5.4f, -5.0f); // end // 2nd Bezier curve control points (3rd route) bezierB[0] = new Vector3(-END-5.0f, 5.4f, -5.0f); // start bezierB[1] = new Vector3(-END-5.0f, 4.4f, -3.0f*END); // ctrl 1 bezierB[2] = new Vector3( END+5.0f, 2.4f, -3.0f*END); // ctrl 2 bezierB[3] = new Vector3( END+5.0f, 0.4f, -5.0f); // end // 2nd line between Bezier curves (4th route) lineB[0] = new Vector3( END+5.0f, 0.4f, -5.0f); // start lineB[1] = new Vector3( END+5.0f, 0.4f, 5.0f); // end } You call the jet initialization routine from Initialize(): InitializeRoutes();
10. C H A P T E R 2 1 347 Keyframe Animations Next, you must add module declarations to initialize the time for the whole trip and each individual section of the trip: private float[] keyFrameTime = new float[4]; private float tripTime = 0.0f; private const float TOTAL_TRIP_TIME = 11.2f; private const int NUM_KEYFRAMES = 4; To initialize the timeline, you will provide five values. Each of the total times be- tween keyframes is stored. Also, the total trip time is stored. private void InitializeTimeLine(){ keyFrameTime[0] = 4.8f; // time to complete route 1 keyFrameTime[1] = 0.8f; // time to complete route 2 keyFrameTime[2] = 4.8f; // time to complete route 3 keyFrameTime[3] = 0.8f; // time to complete route 4 } Call the time-initialization routine from Initialize(): InitializeTimeLine(); The next step is to add module declarations for storing the Y rotation of the jet model. This will correct the jet so that it is always pointing in the correct direction: Vector3 currentPosition, previousPosition; float Yrotation; After the jet is pointing in the proper direction, your next hurdle is keeping track of which route the jet is currently flying. Because we know how long each route will take, it’s easy to check the time, and then figure out which route the jet is currently following. The KeyFrameNumber() function performs this check: private int KeyFrameNumber(){ float timeLapsed = 0.0f; // retrieve current leg of trip for (int i = 0; i < NUM_KEYFRAMES; i++) { if (timeLapsed > tripTime) return i - 1; else timeLapsed += keyFrameTime[i]; } return 3; // special case for last route }
11. 348 MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE The next function uses the Bézier curve to figure out what part of the curve your object is on. Unlike the last function, which checked the time, this one is checking the physical location of the jet. For this example, we need two different ways of deter- mining position; the first one checks the position on the Bézier curve: private Vector3 GetPositionOnCurve(Vector3[] bezier, float fraction){ // returns absolute position on curve based on relative return // position on curve (relative position ranges from 0% to 100%) bezier[0] * (1.0f - fraction) * (1.0f - fraction) * (1.0f - fraction) + bezier[1] * 3.0f * fraction * (1.0f - fraction) * (1.0f - fraction) + bezier[2] * 3.0f * fraction * fraction * (1.0f - fraction) + bezier[3] * fraction * fraction * fraction; } The second position-checking function uses linear interpolation to figure out which part of a line the model jet is on: private Vector3 GetPositionOnLine(Vector3[] line, float fraction){ // returns absolute position on line based on relative position // on curve (relative position ranges from 0% to 100%) Vector3 lineAtOrigin = line[1] - line[0]; return line[0] + fraction*lineAtOrigin; } The next function to add, UpdateKeyframeAnimation(), is the workhorse of this example. It uses all of the logic that you have added to update the animation. The function determines which part of the route the fighter jet is on and then uses the ap- propriate check to find out where it should be on that route: private void UpdateKeyframeAnimation(GameTime gameTime){ // update total trip time, use modulus to prevent variable overflow tripTime += (gameTime.ElapsedGameTime.Milliseconds/1000.0f); tripTime = tripTime%TOTAL_TRIP_TIME; // get the current route number from a total of four routes int routeNum = KeyFrameNumber(); // sum times for preceding keyframes float keyFrameStartTime = 0.0f; for (int i = 0; i < routeNum; i++) keyFrameStartTime += keyFrameTime[i]; // calculate time spent during current route