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; ThisInitializ
Trang 11 for full life, is passed to the shader so it can be used to fade the color of the flame andshrink 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(
Trang 2Inside theDraw()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;
ThisInitializeTorch()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:
InitializeTorch()can be called from theInitialize()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){
foreach (ModelMesh mesh in model.Meshes){
// 3: build cumulative matrix using I.S.R.O.T sequence
Trang 3// 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 theother draw routines that are called.DrawTorch()must be called before the pointsprites are rendered to ensure that the point sprites are layered properly over the 3Dmodel:
DrawTorch(torchModel);
To observe deviant layering when ZWriteEnable is false, try callingDrawTorch()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
340
F I G U R E 2 0 - 3
Draw order issues for point sprites when ZWriteEnable is false
Trang 4SettingZWriteEnablein the shader tofalseensures that the point sprites will
be blended together However, sometimes settingZWriteEnable totruelooks
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 environmdiffer-ents—on differdiffer-ent-sized windows You should consider this in your
de-cision as to whether or not to useZWriteEnable
With theDestBlendstate 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 theDraw()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
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
Trang 5This page intentionally left blank
Trang 6CHAPTER 21
Keyframe Animations
Trang 7KEYFRAME animations combine a timer and inter-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 createthe “in-betweens.” In computer games, the keyframes still define the most importantstages of the animation, but interpolation is used to fill in the frames in between Thiscan 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 cautionflag Using keyframes, you can control the course that the pace car follows as it leadsthe pack and then eventually drives off into the pit By the end of this chapter, youwill be able to use keyframes to map out a route and regulate the speed of this sort ofanimation
The proper technique is to use a timeline to control the speed of animations; thisallows the animation to be rendered at the same speed regardless of the system thatruns 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 thedifference in time between the current and previous frame Interpolation is a similarprocess, but it offers other possibilities for moving objects on linear and curvedpaths 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
Interpolation can be used to project the location of a game object based on the pected time of arrival at the destination For example, if the time between the startingframe and ending frame of an object is 10 seconds, and the object is expected to travel
ex-5 units on the X plane and 10 units on the Z plane, then interpolation can be used toestimate 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
When mapping out keyframes on your timeline, you probably won’t always wantyour vehicles traveling in a straight line You might want to use a curve to map out apath for a keyframe animation This chapter uses Bézier curves to fulfill this role, butyou could use other types of curves for the same task Most splines are calculated bysimilar methods as the Bézier curve, so the Bézier curve provides a good example ofhow 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, andtwo 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”the curve into different shapes
Trang 8The 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
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
Trang 9The code for this example starts with either the MGHWinBaseCode or theMGH360BaseCode project available on this book’s website.
A fixed period is specified for completing the combined sections The total tion time needed to complete all combined routes is 11,200 milliseconds (11.2 sec-onds) At each pass throughUpdate(), the algorithm checks to determine how faralong the path the object should be at that specific time The position of the CF-18 isprojected using the keyframes, which store the fixed end points of the lines and points
anima-on the Bézier curves
The first step is to store each route Two Bézier curves are being used, and twolines 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 fromInitialize():
InitializeRoutes();
346
Trang 10Next, 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 fromInitialize():
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 TheKeyFrameNumber()function performs this check:
private int KeyFrameNumber(){
float timeLapsed = 0.0f;
// retrieve current leg of trip
for (int i = 0; i < NUM_KEYFRAMES; i++)
Trang 11The next function uses the Bézier curve to figure out what part of the curve yourobject is on Unlike the last function, which checked the time, this one is checking thephysical 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 outwhich 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 ofthis example It uses all of the logic that you have added to update the animation Thefunction 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
Trang 12float timeBetweenKeys = tripTime - keyFrameStartTime;
// calculate percentage of current route completed
float fraction = timeBetweenKeys/keyFrameTime[routeNum];
// get current X, Y, Z of object being animated
// find point on line or curve by passing in % completed
switch (routeNum){
case 0: // first curve
currentPosition = GetPositionOnCurve(bezierA, fraction); break;
case 1: // first line
currentPosition = GetPositionOnLine(lineA, fraction); break;
// get rotation angle about Y based on change in X and Z speed
Vector3 speed = currentPosition - previousPosition;
Next, you need to add the jet model to your program To start the process of
load-ing the fighter jet model, add these module declarations:
Model jetModel;
Matrix[] jetMatrix;
When you initialize the CF-18 model, make sure the cf18.x file is referenced in the
Models folder under the Content node within your project (with the matching
cf18Color.jpg file) If the Models folder is not present, you will need to add one You
can find these files in the Models folder on this book’s website Add this code to load
and initialize the jet from inside theLoadContent()method (this code is explained
in Chapter 14):
jetModel = Content.Load<Model>("Models\\cf18");
jetMatrix = new Matrix[jetModel.Bones.Count];
jetModel.CopyAbsoluteBoneTransformsTo(jetMatrix);
Trang 13Now it’s time to actually draw the jet model Most of this code should be familiar
to you—it has been used throughout this book Lighting with theBasicEffectject is explained in Chapter 22
ob-private void DrawCF18(Model model){
// 3: build cumulative world matrix using I.S.R.O.T sequence
// identity, scale, rotate, orbit(translate & rotate), translate
world = scale * rotateX * rotateY * translate;
// set shader parameters
foreach (ModelMesh mesh in model.Meshes){
foreach (BasicEffect effect in mesh.Effects){
effect.World = jetMatrix[mesh.ParentBone.Index] * world; effect.View = cam.viewMatrix;
350
Trang 14The keyframe animation created in this chapter is actually similar to a timeline
an-imation you would create in Macromedia Flash or chUmbaLum sOft’s MilkShape
As you can see, it’s easy to implement a keyframe animation in code
To get the most from this chapter, try out these chapter review exercises
1. Implement the step-by-step example demonstrated in this chapter, if you
have not already done so
2. Begin with the completed airplane example from Chapter 8, and convert
this solution so that it uses three Bézier curves to move the airplane on a
path in the X, Y, and Z planes
Trang 15This page intentionally left blank