XNa shooter games
Now you can create an xNa shooter game. You have all the 3D models, all the effect files and textures and sound effects files, and you don't have to worry about scenarios, because it works well, the scene itself is under the surface (that is, the Z value is less than 0), which means that you only need to place the object in a place where the Z height is 0, so that you can add special effects, collision checks and tests become easier.
Now, you can add your own ship and control it in the player class through the Scene Rendering Method Of The misson class. Rendering only requires the followingCode:
Player. shippos = new vector3 (player. position, allshipszheight) + levelvector; addmodeltorender (shipmodels [(INT) shipmodeltypes. ownship], matrix. createscale (shipmodelsize [(INT) shipmodeltypes. ownship]) * matrix. createrotationz (mathhelper. pi) * matrix. createrotationx (player. shiprotation. y) * matrix. createrotationy (player. shiprotation. x) * matrix. createtranslation (player. shippos); // Add smoke effects for our shippingtmanager. addrocketorshipflareandsmoke (player. shippos + new vector3 (-0.3f,-2.65f, + 0.35f), 1.35f, 5 * player. movementspeedpersecond); implements tmanager. addrocketorshipflareandsmoke (player. shippos + new vector3 (0.3f,-2.65f, + 0.35f), 1.35f, 5 * player. movementspeedpersecond );
All zooming and rotating operations only give the spacecraft the correct size and orientation. Rotating around the Z axis allows the ship to fly to the front, while rotating around the X axis and Y axis allows the ship to move forward and backward or backward. Then, add flame and smoke effects at the rear of the spacecraft engine. The effectmanager class will be discussed immediately.
Game Logic
The control ship code is in the player class, where most game logic is processed, including weapon shooting, mobile spacecraft, scoring, and processing items:
// From player. handlegamelogic: // [show vicloud/defeat messages if game is over] // increase game timegametimems ++ = basegame. elapsedtimethisframeinms; // control our ship position with the keyboard or gamepad. // use keyboard cursor keys and the left thumb stick. the // right hand is used for fireing (CTRL, space, a, B ). vector2 lastposition = position; vector2 lastrotation = shiprotation; float movefacto R = mousesensibility * movementspeedpersecond * basegame. movefactorpersecond; // left/rightif (input. keyboard. iskeydown (moveleftkey) | input. keyboard. iskeydown (keys. left) | input. keyboard. iskeydown (keys. numpad4) | input. gamepad. DPAD. left = buttonstate. pressed) {position. x-= movefactor;} // ifif (input. keyboard. iskeydown (moverightkey) | input. keyboard. iskeydown (keys. right) | input. keyboard. is Keydown (keys. numpad6) | input. gamepad. DPAD. right = buttonstate. pressed) {position. X + = movefactor;} // ifif (input. gamepad. thumbsticks. left. x! = 0.0f) {position. X + = input. gamepad. thumbsticks. left. x; // * 0.75f;} // If // keep position in boundsif (position. x <-maxxposition) position. X =-maxxposition; If (position. x> maxxposition) position. X = maxxposition; // [same for down/up changes position. y, see player. CS] // calculate ship rotation based on the current movementif (lastposition. x> position. x) shiprotation. X =-0.5f; else if (lastposition. x <position. x) shiprotation. X = + 0.5f; else shiprotation. X = 0; // [same for shiprotation. y, see above] // interpolate ship rotation to be more smoothshiprotation = lastrotation * 0.95f + shiprotation * 0.05f;
Handlegamelogic first checks whether the game is over and displays a message on the screen to tell you if you lose or win. Then, the current game time increases. Then, process the ship control, then launch weapons and items for processing, and the Code finally processes the score after shooting down the enemy.
You can use the keyboard and gamepad to control the spacecraft. When the mouse is not supported, it is difficult for the shooting game to debug, and I personally do not like to control the shooting game with the mouse. By using the constant maxxposition and maxyposition, you can ensure that your ship is not moved out of the screen. shiprotation is calculated based on your movement. If the ship is not moved, it will slowly return to zero.
The following code shows how to launch a weapon. More details are included in the player class.
// Fire? If (input. gamepadapressed | input. gamepad. triggers. right> 0.5f | input. keyboard. iskeydown (keys. leftcontrol) | input. keyboard. iskeydown (keys. rightcontrol) {Switch (currentweapon) {Case weapontypes. mg: // shooting cooldown passed? If (gametimems-lastshoottimems> = 150) {// [shooting code goes here...] shootnum ++; lastshoottimems = gametimems; input. gamepadrumble (0.015f, 0.125f); player. score + = 1;} // If break; Case weapontypes. plasma: // [etc. all other weapons are handled here] Break;} // switch} // If
When you press the key of gamepad or the ctrl key of the right board or keyboard, the launch command is entered, but the weapon will not open fire, until you reach the next Cooling Stage (each weapon has a different cool-down time ). Then the weapon is shot and the collision detection is processed. lastshoottimems is reset to the current game time to wait for the next cooling process.
Only one thing left after processing the weapon is to create a condition for winning or losing. If you successfully reach the close end, you will win, and before that, you will fail to use up your life.
If (player. health <= 0) {victory = false; Statistics tmanager. addexplosion (shippos, 20); For (INT num = 0; num <8; num ++) extends tmanager. addflameexplosion (shippos + randomhelper. getrandomvector3 (-12, + 12); player. setgameoveranduploadhighscore ();} // If
The rest of the game logic is enemies, items, and ammunition. These are all handled in their respective classes, which will be seen later in this chapter.
3D effect
Return to 3D effects. You have learned a lot about 3D effects. With the help of the billboard class, it is not difficult to render textures in 3D. But now the special effect is upgraded to a higher level. You are concerned about the length, animation steps, and fade-in and fade-out of the special effect. All 3D polygon generation and rendering are processed by the billboard and texture classes, and all effects are managed by the effectmanager class (see Figure 11-15 ), with the help of many static add methods, you can add special effects from anywhere in the code.
Figure 11-15
In the effect class, you can see many fields, which are used to create many different types of 3D effects. The explosion effect optimization is different from the lighting effect. For example, a general effect only blends an Alpha value, but the light effect cannot work, because if the Alpha value is modified, the light will become darker, which looks strange, the lighting effect must be smaller at the end. Other enumerations help you quickly define the effect types and easily add them using the addeffect method.
The best way to understand the effectmanager class is to look at testeffects unit tests, add new effects and implement them, or write new unit tests for testing more special effects.
Public static void testeffects () {testgame. start ("testeffects", delegate {// No initialization Code necessary here}, delegate {// press 1-0 for creating effects in center of the 3D scene if (input. keyboard. iskeydown (keys. d1) & basegame. everyms (200) addmgeffect (New vector3 (-10.0f, 0,-10), new vector3 (basegame. totaltimems % 3592)/100366f, 25, + 100), 0, 1, true, true); If (input. keyboard. iskey Down (keys. d2) {addplasmaeffect (New vector3 (-50366f, 0.0f, 0.0f), 0.5f, 5); addplasmaeffect (New vector3 (0.0f, 0.0f, 0.0f), 1.5f, 5 ); addplasmaeffect (New vector3 (50366f, 0.0f, 0.0f), 0.0f, 5);} // If (input. keyboard. iskeydown (keys. d2]) if (input. keyboard. iskeydown (keys. d3) {addfireballeffect (New vector3 (-50366f, + 10.0f, 0.0f), 0.0f, 10); addfireballeffect (New vector3 (0.0f, + 10.0f, 0.0f), (float) Ma Th. PI/8, 10); addfireballeffect (New vector3 (50366f, + 10.0f, 0.0f), (float) math. pI * 3/8, 10);} // If (input. keyboard. iskeydown (keys. d3]) if (input. keyboard. iskeydown (keys. d4) addrocketorshipflareandsmoke (New vector3 (basegame. totaltimems % 4000)/40366f, 0, 0), 5.0f, 150366f); If (input. keyboard. iskeydown (keys. d5) & basegame. everyms (1000) addexplosion (vector3.zero, 9.0f); // etc. // play Couple of sound effects if (input. keyboard. iskeydown (keys. p) & basegame. everyms (500) playsoundeffect (effectsoundtype. plasmashoot); // etc. // We have to render the effects ourselfs because // It is usually done in rocketcommanderform (not in testgame )! // Finally render all effects before applying post screen shaders basegame. effectmanager. handlealleffects () ;}) ;}// testeffects ()
Unit class
Maybe this class should be called the enemyunit class because it is used only for enemy ships and your own ships have been processed in the player class. At first, I wanted to integrate all units (your own ships and enemies) in this class, but they were very different in behavior patterns. This would only make the code more messy and complex. In a more complex game, you may need to create a base class for the Unit, and then from the base class enemy unit and our friends unit (for example, in multiplayer games, make sure that the ships of all other players should be in the same way as your ships ). See this class in Figure 11-16.
Figure 11-16
This class uses many fields to track the life value, shooting time, and position of each unit. However, it is very simple from the external perspective. You only need to call a method for each frame: render. The constructor only creates a unit and specifies the unit type, location, default life value, and damage value (these are all obtained from the constant array in the class-more complex games should be from XML files or obtain these values from the external data source ).
As usual, you should take a look at unit testing to learn more about this class:
Public static void testunitai () {unit testunit = NULL; mission dummymission = NULL; testgame. start ("testunitai", delegate {dummymission = new mission (); testunit = new unit (unittypes. corvette, vector2.zero, movementpattern. straightdown); // call dummymission. renderlandscape once to initialize everything dummymission. renderlevelbackground (0); // remove the all enemy units (the start enemies) and // All neutral objects dummymission. numofmodelstorender = 2;}, delegate {// [helper texts are displayed here, press 1-0 or csfra, etc.] resetunitdelegate resetunit = delegate (movementpattern setpattern) {testunit. movementpattern = setpattern; testunit. position = new vector2 (randomhelper. getrandomfloat (-20, + 20), mission. segmentlength/2); testunit. hitpoints = testunit. maxhitpoints; testunit. speed = 0; testunit. lifetimems = 0 ;}; if (input. keyboardkeyjustpressed (keys. d1) resetunit (movementpattern. straightdown); If (input. keyboardkeyjustpressed (keys. d2) resetunit (movementpattern. getfasterandmovedown); // [etc.] if (input. keyboardkeyjustpressed (keys. space) resetunit (testunit. movementpattern); If (input. keyboardkeyjustpressed (keys. c) testunit. unittype = unittypes. corvette; If (input. keyboardkeyjustpressed (keys. s) testunit. unittype = unittypes. smalltransporter; If (input. keyboardkeyjustpressed (keys. f) testunit. unittype = unittypes. firebird; If (input. keyboardkeyjustpressed (keys. r) testunit. unittype = unittypes. rocketfrigate; If (input. keyboardkeyjustpressed (keys. a) testunit. unittype = unittypes. asteroid; // update and render unit if (testunit. render (dummymission) // restart unit if it was removed because it was too far down resetunit (testunit. movementpattern); // render all models the normal way for (INT num = 0; num <dummymission. numofmodelstorender; num ++) dummymission. modelstorender [num]. model. render (dummymission. modelstorender [num]. matrix); basegame. meshrendermanager. render (); // restore number of units as before. dummymission. numofmodelstorender = 2; // show all effects (Unit smoke, etc .) basegame. effectmanager. handlealleffects ();} // testunitai ()
This unit test allows you to change one of the five enemies by C, S, F, R, or a: Corvette, small transport ship, Firebird, rocket-frigate, and minor. By 1-0, you can change the AI behavior of this enemy. AI is a bit crazy here, because all artificial intelligence is just dealing with different forms of motion. The enemy only follows a specific movement pattern and is not smart at all. For example, the getfasterandmovedown code looks like this:
Case movementpattern. getfasterandmovedown: // out of visible area? Then keep speed slow and wait. if (position. y-mission. lookatposition. y> 30) lifetimems = 300; If (lifetimems <3000) speed = lifetimems/3000; Position + = new vector2 (0,-1) * speed * 1.5f * maxspeed * movespeed; break;
Other motion modes are simpler. The name of each motion pattern tells enough information about the behavior. Randomly assign a motion pattern to each new enemy in the game. You can also create a level that contains predefined positions and enemy information about sports AI, but I want to keep it simple. The rest of the unit test is rendering the enemy and background. This is used to create the entire unit class, covering all things except shooting and enemy death, which is directly tested in the game.
Projectile class
I mentioned the enemy's shooting just now. The required code makes Corvette's shooting less complex, because if your ship is at the bottom of it, it will be immediately hit (it only checks it and your X and Y positions and takes corresponding action ). Most Corvette fires fail.
All other enemies launch rockets, fireballs, or plasma balls instead of instant weapons. These bullets fly to the target for a period of time. The projectile class (see Figure 11-17) helps you manage these objects, simplifies the logic of weapons, and eliminates the need to manage them after you launch a projectile. The projectile class works on behalf of you. The projectile class will handle Collision Detection and automatically remove the projectile after the fuel is exhausted or the screen is flown out.
Figure 11-17
There are three different ammunition types, and their behavior is different:
Plasma balls can only be fired by their own ships, provided that you have this plasma weapon. The plasma balls are faster than mg, but Gatling-gun and rocket launchers are more powerful, although they also have their own shortcomings.
- Fireball is fired from an enemy Firebird spacecraft. It flies slowly and does not change its direction. However, the AI of Firebird allows them to launch fireballs earlier than you, so you must first avoid them.
Both you and the enemy's rocket frigate can launch a rocket. Your rocket can cause more damage, but it will only go straight. The enemy's rocket is more intelligent and will adjust the Target Based on your position, making it more difficult to escape.
Compared with the rendering method of the unit class, the rendering method of the projectile class is not very complex. The most interesting part is the collision processing after updating the position and drawing the projectile.
Public bool render (Mission mission) {// [update movement...] // [render projectile, either the 3D model for the rocket or just the // effect for the fireball or plasma weapons] // own projectile? If (ownprojectile) {// hit enemy units, check all of them for (INT num = 0; num <mission. units. count; num ++) {unit enemyunit = mission. units [num]; // near enough to enemy ship? Vector2 distvec = new vector2 (enemyunit. position. x, enemyunit. position. y) New vector2 (position. x, position. y); If (distvec. length () <7 & (enemyunit. position. y-player. shippos. y) <60) {// explode and do damage! Effectmanager. addflameexplosion (position); player. score + = (INT) enemyunit. hitpoints/10; enemyunit. hitpoints-= damage; return true;} // If} // For} // If // else this is an enemy projectile? Else {// near enough to our ship? Vector2 distvec = new vector2 (player. shippos. x, player. shippos. y) New vector2 (position. x, position. y); If (distvec. length () <3) {// explode and do damage! Effectmanager. addflameexplosion (position); player. health-= damage/1000.0f; return true;} // If} // else // don't remove projectile yet return false;} // render ()
If this is your own projectile (plasma or rocket), you must check the collision with an active enemy ship. If a collision causes damage and adds an explosion effect, you can also get some scores (10% of remaining enemy life values ). Then, return true to inform the call function that you have processed the projectile and can remove it from the list of currently active projectile. This also happens when the projectile exges the boundary.
If it is hit by enemy ammunition, collision detection is easier. You only need to check the collision of your ship and cause the same damage. The death of your and enemy ships is handled by their respective render methods. As you have seen before, when your ship's life is exhausted, it will trigger death. The death condition of the enemy unit looks very similar, and you also return true to remove the enemy from the current enemy list.
Item class
The item class is used to process all items. It can be seen from 11 to 18 that this is very simple, but it will be less fun to play games without any prop. Itemtypes enumeration in the class shows that there are 6 items. Four of them are weapons, one is the life value, which completely restores the life value of your ship, and the other is an electromagnetic pulse bomb. Pressing the Space key can eliminate all enemies on the screen, you can have up to three bombs at the same time.
Figure 11-18
The render method is similar to that in the projectile class, except that collision with items does not kill anything. On the contrary, you can collect these items, and the effect is handled immediately. This means you can get a new weapon, return to 100% of your life, or get another electromagnetic pulse bomb.
/// <Summary> // render item, returns false if we are done with it. /// </Summary> /// <returns> true if done, false otherwise </returns> Public bool render (Mission mission) {// remove unit if it is out of visible range! Float distance = mission. lookatposition. y-position. y; const float maxunitdistance = 60; If (distance> maxunitdistance) return true; // render float itemsize = mission. itemmodelsize; float itemrotation = 0; vector3 itempos = new vector3 (Position, mission. allshipszheight); mission. addmodeltorender (mission. itemmodels [(INT) itemtype], matrix. createscale (itemsize) * matrix. createrotationz (itemrotatio N) * matrix. createtranslation (itempos); // Add glow effect the item named tmanager. addeffect (itempos + new vector3 (0, 0, 1.01f), effectmanager. required tType. lightinstant, 7.5f, 0, 0); required tmanager. addeffect (itempos + new vector3 (0, 0, 1.02f), effectmanager. required tType. lightinstant, 5.0f, 0, 0); // collect item and give to player if colliding! Vector2 distvec = new vector2 (player. shippos. x, player. shippos. y) New vector2 (position. x, position. y); If (distvec. length () <5.0f) {If (itemtype = itemtypes. health) {// refresh health sound. play (sound. sounds. health); player. health = 1.0f;} // If else {sound. play (sound. sounds. newweapon); If (itemtype = itemtypes. MG) player. currentweapon = player. weapontypes. mg; else if (itemtype = itemtypes. plasma) player. currentweapon = player. weapontypes. plasma; else if (itemtype = itemtypes. gattling) player. currentweapon = player. weapontypes. gattling; else if (itemtype = itemtypes. rockets) player. currentweapon = player. weapontypes. rockets; else if (itemtype = itemtypes. EMP & player. empbombs <3) player. empbombs ++;} // else player. score + = 500; return true;} // else // don't remove item yet return false;} // render ()
The render Code only places the item in a given position, and adds two lighting effects, that is, the item is a bit luminous. If you do not look at items, you may not be able to notice the luminous effect, but it is more difficult to see them without the luminous effect.
If you are close to a prop, it will be automatically collected if it is less than 5 units. Your ship has a radius of about five units, which means you can still use the ship to get in touch with items. This item is then processed, giving you a life value, a weapon, or a bomb, along with bonus points. This item can now be removed. If you haven't collided with items, it will stay in the same place until you touch them or items fly out of the boundary.
Last
Yeah, it's finished. This is what xNa shooter needs, as shown in Figure 11-19. I hope this chapter is more useful than Chapter 8th, because in Chapter 8th I do not want to repeat the original version of the rocket Commander tutorial.
Figure 11-19
I hope you like xNa shooter. If you want to create your own shooting game, you will find it useful. Remember, this game takes only a few days to complete. Maybe you can make it better, add more levels, enemy ships, or better AI. Have fun!