Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 30 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
30
Dung lượng
291,89 KB
Nội dung
Figure 19-3 shows several rockets at various elevations (on the Y axis) and at dif- ferent stages of flight. Over time, the projectiles lose momentum and gravity pulls them to the ground. The overall effect creates a nice arcing projectile path. Game developers will often use real-world physics to create more realistic graph- ics effects. The physical properties that they consider may include gravity, friction, force, velocity, acceleration, viscosity, and much more. In case you’re wondering, game development companies will often implement pseudo-physics in their algo- rithms. As long as the effect looks correct and is efficient, an approximation of the laws of physics is usually the faster and more effective alternative. After all, as a simu- lation approaches reality, it can become so complex that it loses its value. However, even when the code deviates from the laws of physics, realistic algorithms usually consider some portion of the real physical model. Once the launch velocity and direction have been obtained, the effect of gravity can be computed and the X, Y, and Z positions of the projectile can be calculated over time. The X and Z positions are calculated using the same equations as the Lin- ear Projectile algorithm to obtain the projectile’s position over time: X t = X start + V x * t Z t = Z start + V z * t The Arcing Projectile algorithm treats the calculation of the Y position over time as a special case that also considers gravity. Initially, the projectile’s velocity is pow- erful enough to defy gravity—otherwise, there would be insufficient energy to launch MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE 308 FIGURE 19-3 Considering the effect of gravity over time 309 the projectile into the air. However, over time, the projectile loses its momentum and gravity becomes the strongest force on the object. This gravitational pull isdefined by a constant value of acceleration, g, which represents the Earth’s gravity. The ac- cepted value for g equals 9.8 meters / second 2 (32 ft/s 2 ). After the Earth’s gravity is fac- tored in, the equation used for calculating the Y position over time becomes: Y t = Y start + V y * t - 0.5 * g * t 2 Implementing these projectile algorithms in code is simple. The first example in this chapter implements the Linear Projectile algorithm. Then, in the example that follows, the Linear Projectile algorithm is converted into an Arcing Projectile algo- rithm. L INEAR PROJECTILES EXAMPLE This example demonstrates how to add projectiles that can be launched on a linear path from a rocket launcher, as shown back in Figure 19-1. In this example, you will shoot ten rockets into the air at a time. When a trigger or spacebar event occurs, the first available rocket (that is not already in flight) is launched. At the time of launch, the rocket is given a position and direction to start it on an outward journey from the tip of the rocket launcher. The rocket launcher’s po- sition and direction are based on the camera’s current position and Look direction. Also, during the launch, the activation state for the projectile is set to true, and re- mains set to true until the projectile reaches the end of the path. The activation state prevents the projectile from being reused while it is in flight. The projectile properties are reset every time the projectile is launched. This example begins with either the MGHWinBaseCode or MGH360BaseCode project located in the BaseCode folder on this book’s website. You will create a Projectile class to assist with the implementation of your projectiles. You will use Projectile to keep track of each rocket and to update its position. The Projectile class can be created from scratch in the Solution Ex- plorer. To generate it, right-click the project and choose Add New Item. Then, choose the Code File icon and enter Projectile.cs as the Name in the Add New Item di- alog. When you click Add, GS will generate an empty Projectile.cs file. First, add the following code shell to start your Projectile class: using Microsoft.Xna.Framework; namespace Projectiles{ public class Projectile{ } } CHAPTER 19 Ballistics MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE 310 Class-level declarations are also required for storing the position, direction, and activation state of each projectile. An additional variable, for storing the size of the world, enables a check to determine whether the projectile has flown out of sight. This tells you when to deactivate the projectile. To allow access to these variables throughout the class, we place their declarations at the top of the Projectile class (inside the class declaration): public Vector3 position, previousPosition; // rocket position private Vector3 speed; // relative change in X,Y,Z public Matrix directionMatrix; // direction transformations public bool active; // visibility private float boundary; // edge of world on X and Z private float seconds; // seconds since launch private Vector3 startPosition; // launch position When the program begins, each projectile needs to be created only once. After they are created, the projectiles remain inactive until the user launches them. Later, you will add a method to deactivate a projectile when it flies past the boundaries of the world. To set the projectile flight range and activation state when the projectile is ini- tialized, add this constructor to the Projectile class: public Projectile(float border){ boundary = border; active = false; } The projectile’s position, direction, and activation state are set according to the camera’s position and Look direction at the time of the launch. The rocket speed is actually based on the direction, which is a relative change in X, Y, and Z. Including the Launch() method in the Projectile class will enable proper initialization of these attributes during the launch. public void Launch(Vector3 look, Vector3 start){ position = startPosition = start; // start at camera speed = Vector3.Normalize(look); // unitize direction active = true; // make visible seconds = 0.0f; // used with gravity only } As discussed in Chapter 8, an object’s direction can be calculated from the object’s speed vector. Adding SetDirectionMatrix() to your Projectile class will 311 CHAPTER 19 Ballistics provide the method you need to make your rocket point in the direction it is travel- ing. This routine applies to both the Linear Projectile algorithm and the Arcing Pro- jectile algorithm. For the Linear Projectile algorithm, the rocket direction remains constant as the rocket travels outwards. For the Arcing Projectile algorithm, SetDirectionMatrix() will launch the rocket with the original launcher direc- tion, and then it will gradually drop the rocket, nose downward, as the gravitational pull takes over: private void SetDirectionMatrix(){ Vector3 Look = position - previousPosition; Look.Normalize(); Vector3 Up = new Vector3(0.0f, 1.0f, 0.0f); // fake Up to get Vector3 Right = Vector3.Cross(Up, Look); Right.Normalize(); Up = Vector3.Cross(Look, Right); // calculate Up with Up.Normalize(); // correct vectors Matrix matrix = new Matrix(); // compute direction matrix matrix.Right = Right; matrix.Up = Up; matrix.Forward = Look; matrix.M44 = 1.0f; // W is set to 1 to enable transforms directionMatrix = matrix; } The projectile’s position is updated before being drawn each frame. Also, in every frame, the projectile’s position is incremented by a time-scaled direction vector, which ensures that the rocket flies in the path set by the camera when the rocket is launched. When the projectile location exceeds one of the outer boundaries, it is de- activated so that it can be deactivated and made available for the next launch. The UpdateProjectile() method implements this routine. Adding UpdateProjectile() to the projectile class ensures that your projectile positions are updated while they are active. The method also deactivates the projectiles after they reach the outer limits of your world. public void UpdateProjectile(GameTime gameTime){ previousPosition = position; // archive last position position += speed // update current position * (float)gameTime.ElapsedGameTime.Milliseconds/90.0f; SetDirectionMatrix(); MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE 312 // deactivate if outer border exceeded on X or Z if (position.Z > 2.0f * boundary || position.X > 2.0f * boundary || position.Z <-2.0f * boundary || position.X <-2.0f * boundary) active = false; } The Projectile class for a Linear Projectile algorithm is now complete, so you can reference it from Game1.cs. Adding the namespace reference at the top of the Game1.cs file enables your use of this new class: using Projectiles; To use the Projectile class, you need to declare instances of it inside the game class. An array of 10 is used here, but if you wanted, you could create a more dynamic structure with an ArrayList: const int NUM_ROCKETS = 10; private Projectile[] rocket = new Projectile[NUM_ROCKETS]; With these declarations, you should set up each of these ten projectiles from the Initialize() method when the program begins. Passing the size of the world to the constructor will allow us to deactivate the rocket later when it flies beyond the outer boundaries of the world: for (int i = 0; i < NUM_ROCKETS; i++) rocket[i] = new Projectile(BOUNDARY); To make this example more interesting, a model of a rocket will be used for the projectiles. To reference this model in the game class, add a class-level declaration for the model and the matrix to the game class. Also, the starting Y value used in the rocket and launcher transformations is tracked with the variable declared here as BASE_HEIGHT : Model rocketModel; Matrix[] rocketMatrix; Model launcherModel; Matrix[] launcherMatrix; const float BASE_HEIGHT = 0.6f; // start height for models The rocket and rocket launcher models are loaded from the rocket.fbx and launcher.fbx files. Adding the InitializeModels() method to your game class provides the code to load these models using the ContentManager object. The di- rectory path in this code assumes you have copied the rocket.fbx, rocket.bmp, launcher.fbx, and launcher.bmp files from the Models folder on this book’s website. 313 Once the rocket.fbx and launcher.fbx models are referenced from the Solution Ex- plorer, InitializeModels() can initialize the model objects and their transfor- mation matrices: void InitializeModels(){ rocketModel = Content.Load<Model>("Models\\rocket"); rocketMatrix = new Matrix[rocketModel.Bones.Count]; rocketModel.CopyAbsoluteBoneTransformsTo(rocketMatrix); launcherModel = Content.Load<Model>("Models\\launcher"); launcherMatrix = new Matrix[launcherModel.Bones.Count]; launcherModel.CopyAbsoluteBoneTransformsTo(launcherMatrix); } To initialize the rocket, when the program begins, call InitializeModels() from LoadContent(): InitializeModels(); Remember to only reference the models in your project. Both the models and textures should be placed in the Models folder. However, the content pipeline is unable to load more than one file with the same name from the same folder because no extension is required. Now that the models have been loaded, projectile objects can be created to track each rocket’s direction and whereabouts. Declaring ten projectile objects in the game class in the module declarations area will make them available for your use through- out the game class. Next, we’ll draw the rocket launcher. The rocket launcher travels with the camera and rotates about the X axis—with changes to the view position on Y whenever the user moves the mouse or right thumbstick up or down. The model rocket launcher was designed to simplify the transformations for this movement. The rocket launcher’s base is positioned at the origin, and the barrel is centered around the Z axis and is positioned further out on Z. By design of the camera, the launcher’s rota- tion range about the X axis is half a circle (or π radians). If the rocket launcher is pointed directly upward, the view position on Y would equal 0.5, and if the rocket launcher is pointed directly downward, the view position would be –0.5 (see Figure 19-4). As discussed in Chapter 7, because XNA uses the Right Hand Rule, a negative ro- tation around the X axis will point the launcher upward. Using the same logic, a posi - tive rotation about the X axis will point the launcher downward. The launcher must be rotated about the X axis to match the camera’s Look direction about the Y axis. CHAPTER 19 Ballistics With this information, you can calculate the rocket launcher’s rotation angle about the X axis with the following equation: rotationX = Matrix.CreateRotationX(-MathHelper.Pi*look.Y ); The launcher must also be rotated about the Y axis to match the camera’s Look di- rection about the Y axis. And finally, to finish the transformation using the I.S.R.O.T. sequence, the launcher must be translated by an amount that is equivalent to the distance from the origin to the camera. An extra shift downward on the Y axis is added to this translation to move the launcher downward slightly so it does not block your view. Add DrawLauncher() to your game class to move and rotate the rocket launcher with your camera: private void DrawLauncher(Model model){ // 1: declare matrices Matrix world, translation, scale, rotationX, rotationY; // 2: initialize matrices scale = Matrix.CreateScale(0.002f, 0.002f, 0.002f); translation = Matrix.CreateTranslation(cam.position.X, BASE_HEIGHT, cam.position.Z); MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE 314 FIGURE 19-4 Rocket launcher rotation range around the X axis 315 Vector3 look = cam.view - cam.position; rotationX = Matrix.CreateRotationX(-MathHelper.Pi*look.Y ); rotationY = Matrix.CreateRotationY((float)Math.Atan2(look.X, look.Z)); // 3: build cumulative matrix using I.S.R.O.T. sequence // identity,scale,rotate,orbit(translate & rotate),translate world = scale * rotationX * rotationY * translation; // 4: set shader parameters foreach (ModelMesh mesh in model.Meshes){ foreach (BasicEffect effect in mesh.Effects){ effect.World = launcherMatrix[mesh.ParentBone.Index]*world; effect.View = cam.viewMatrix; effect.Projection = cam.projectionMatrix; effect.EnableDefaultLighting(); effect.SpecularPower = 0.01f; } // 5: draw object mesh.Draw(); } } To actually see the rocket launcher, you obviously need to call the method to draw it. Adding DrawLauncher() to the end of the Draw() method will draw the rocket when other objects are rendered: DrawLauncher(launcherModel); Because the rocket launcher’s rotation angle about the X axis changes with the view position on Y, if the right thumbstick or mouse shifts the view all the way up or all the way down, you can actually see the base of the launcher, which spoils the ef- fect. Inside the camera class in the UpdateView() method, you’ll replace the code that caps the Y view position so that it can no longer exceed 0.30 or fall below –0.10, which prevents you from pointing the launcher into the ground. The end result is that whatever angle you point, it looks as though you are always holding the rocket launcher: const float LOWER_LIMIT = -0.1f; const float UPPER_LIMIT = 0.3f; if (Qlook.Y > LOWER_LIMIT && Qlook.Y < UPPER_LIMIT) The code that you use to launch the rocket (from the game class) is contained in the LaunchRocket() method. This routine searches through the array of projectiles CHAPTER 19 Ballistics and finds the first inactive projectile available. When an inactive projectile is found, LaunchRocket() sets the starting position and direction to equal the camera posi- tion and Look direction. The transformations use the I.S.R.O.T. sequence. Their implementation to angle and position the rocket at the tip of the launcher is summarized in the comments in- cluded with this code. The starting position is needed to help track the location of each rocket. To create the required transformation, and record the initial starting position of the rocket, we can use the matrix math discussed in Chapter 8 and Chapter 16. Once the starting position is computed using matrices, the first row of the matrix that contains the po- sition information is stored in a vector. This position vector can be used later to up- date the position of the rocket by incrementing the position by a time-scaled direction vector. As you can see, it really does pay to understand how to employ linear algebra beyond just using the Matrix objects and methods that are shipped with XNA. Add LaunchRocket() to your game class to find the first available rocket when a launch is triggered and to calculate and store the starting position and direction of the rocket: private void LaunchRocket(int i){ Matrix orbitTranslate, orbitX, orbitY, translate, position; Vector3 look, start; // create matrix and store origin in first row position = new Matrix(); // zero matrix position.M14 = 1.0f; // set W to 1 so you can transform it // move to tip of launcher orbitTranslate = Matrix.CreateTranslation(0.0f, 0.0f, -0.85f); // use same direction as launcher look = cam.view - cam.position; // offset needed to rotate rocket about X to see it with camera float offsetAngle = MathHelper.Pi; // adjust angle about X with changes in Look (Forward) direction orbitX = Matrix.CreateRotationX(offsetAngle-MathHelper.Pi*look.Y); // rocket's Y direction is same as camera's at time of launch orbitY = Matrix.CreateRotationY((float)Math.Atan2(look.X,look.Z)); MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE 316 317 CHAPTER 19 Ballistics // move rocket to camera position where launcher base is also located translate = Matrix.CreateTranslation(cam.position.X,BASE_HEIGHT, cam.position.Z); // use the I.S.R.O.T. sequence to get rocket start position position = position * orbitTranslate * orbitX * orbitY * translate; // convert from matrix back to vector so it can be used for updates start = new Vector3(position.M11, position.M12, position.M13); rocket[i].Launch(look, start); } At this point, the projectile objects are initialized and your launcher is in place. Your rockets are ready, but a mechanism is required to trigger their launch. In this case, you will add code to initiate their launch when the left mouse button is clicked, or when the right trigger on the controller is pressed. To ensure that all ten rockets are not launched during this press event—which lasts over several frames— the current and previous states of the game pad and mouse are compared. To enable this input device state checking, you must add a declaration for game pad and mouse states at the class level: #if !XBOX MouseState mouseCurrent, mousePrevious; #endif GamePadState gamepad, gamepadPrevious; The projectile trigger events can now be handled at the end of the Update() method. In this block of code, you will update the mouse and game-pad states. Then you can determine new mouse button click or trigger-pull events by comparing the button press states in the current frame with release states from the previous frame: // refresh key and button states #if !XBOX mouseCurrent = Mouse.GetState(); #endif gamepad = GamePad.GetState(PlayerIndex.One); // launch rocket for right trigger and left click events if (gamepad.Triggers.Right > 0 && gamepadPrevious.Triggers.Right == 0 #if !XBOX || mouseCurrent.LeftButton == ButtonState.Pressed && mousePrevious.LeftButton == ButtonState.Released #endif [...]...318 MICROSOFT ){ XNA GAME STUDIO CREATOR’S GUIDE // if launch event then launch next available rocket for (int i = 0; i < NUM_ROCKETS; i++) if (rocket[i].active == false){ LaunchRocket(i); break; } } // archive current state for comparison next frame gamepadPrevious = gamepad; #if !XBOX mousePrevious = mouseCurrent; #endif In each frame,... However, the texture coordinate mapping is automatic, so you don’t need to set UV 325 Particle Effects C H A P T E R 326 MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE coordinates in your XNA code or send them to the vertex shader For you to pass the point sprite size to your vertex shader from your XNA code, the size variable defined for your vertex shader input must be tagged with the PSIZE semantic: struct VSinput{... ZWriteEnable is true 329 Particle Effects C H A P T E R 330 MICROSOFT C XNA GAME STUDIO CREATOR’S GUIDE USTOM VERTEX DECLARATIONS As mentioned earlier, XNA s preset VertexDeclarations are convenient but limited For example, these preset vertex formats do not include an element for storing point sprite size To set the point size from your XNA code, you will need to create a custom vertex to include... object cumulative matrix w*v*p texture parameter reduce size & color camera projection viewport height 331 Particle Effects C H A P T E R 332 MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE With these objects in place, your shader can be loaded and compiled, and your XNA code can be given access to its global variables This setup needs to be done when the program begins Therefore, in Initialize(), add the... UpdateParticle(GameTime gameTime, Random rand){ float time = (float)gameTime.ElapsedGameTime.Milliseconds/1000.0f; position += speed*time; // update position life -= fadeRate*time; // update speed // regenerate particle if life falls below zero if (life < 0) ResetParticle(rand); } Back in Game1 .cs, a reference to this new particle class is required The particle class’s namespace must be added at the top of Game1 .cs... the camera // viewportHeight - Height of the current projection in the // window See Chapter 28, "Multiplayer // Gaming," for more detail 327 Particle Effects C H A P T E R 328 MICROSOFT OUT.size OUT.color OUT.UV XNA GAME STUDIO CREATOR’S GUIDE =(IN.size* (projection._m11/OUT.position.w)*(viewportHeight/2)) *fade; = (1.0f, 1.0f, 1.0f, 1.0f); = (1.0f, 1.0f); // pass these values to the pixel shader PSinput... effect, but you could add more to make the effect more varied and even more impressive The image shown here is the one used for the particle texture 333 Particle Effects C H A P T E R 334 MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE To load your image using the ContentManager, you must reference the image file, particle.png, in your project’s Images folder as well as in the Solution Explorer The particle.png... The scale regulates the speed so that the animation appears at the same rate regardless of the processing power of the machine that runs the algorithm 335 Particle Effects C H A P T E R 336 MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE Particle life is reduced by the fade rate at each frame If the life value falls below zero, then the particle is reincarnated In this case, the fire particle is born at the... The UpdateProjectile() method updates the position by factoring speed over time In Y’s case, the height is also adjusted with the pull of gravity over time 319 Ballistics C H A P T E R 320 MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE SetDirectionMatrix() makes this effect even more realistic by adjusting the rocket’s direction The transformation matrix set when calling this method ensures that the rocket... shell: using System; using System.Collections.Generic; using System.Text; namespace Particles{ class Particle { } } To access vital XNA functions from your new class, you need to include the XNA graphics framework declarations at the top of the Particle.cs file: using Microsoft. Xna. Framework; Declarations for the classic particle properties described at the beginning of the chapter belong in the module . start your Projectile class: using Microsoft. Xna. Framework; namespace Projectiles{ public class Projectile{ } } CHAPTER 19 Ballistics MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE 310 Class-level declarations. pow- erful enough to defy gravity—otherwise, there would be insufficient energy to launch MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE 308 FIGURE 19-3 Considering the effect of gravity over time 309 the. position * (float)gameTime.ElapsedGameTime.Milliseconds/90.0f; SetDirectionMatrix(); MICROSOFT XNA GAME STUDIO CREATOR’S GUIDE 312 // deactivate if outer border exceeded on X or Z if (position.Z