Tutoriel de développement XNA / Partie II : Mon premier jeu avec XNA 4.0 (Part 4)
Dans le chapitre précédent, nous avons vu comment faire défiler un background. Comme vous avez pu vous en rendre compte, le style du jeu se rapproche petit à petit d’un shoot’em up. C’est pourquoi, dans cette partie, nous allons apprendre à notre petit vaisseau Tequila Rapido, à faire feu.
Ce chapitre va être l’occasion structurer un peu notre code, la base étant toujours la classe Sprite. Nous allons créer une classe Ship et une classe Bullet qui héritent de Sprite.
Modifier la classe Sprite
Pour commencer, modifiez la classe Sprite en fonction des lignes suivantes :
class Sprite { //Position actuelle du sprite public Vector2 Position = new Vector2(0,0); //Texture utilisée lors de l'affichage dans la méthode Draw private Texture2D _texture; //Taille du Sprite public Rectangle Size; //Utilisé pour augmenter ou diminuer la taille du Sprite private float _scale = 1.0f; public float Scale { get { return _scale; } set { _scale = value; //Recalule la taille du Sprite avec la nouvelle echelle (scale) Size = new Rectangle(0, 0, (int)(_texture.Width * Scale), (int)(_texture.Height * Scale)); } } //Nom de l'image en ressource public string AssetName; //Charge la texture du sprite en utilsant le ContentPipeLine public void LoadContent(ContentManager monContentManager, string nomTexture) { _texture = monContentManager.Load<Texture2D>(nomTexture); AssetName = nomTexture; Size = new Rectangle(0, 0, (int)(_texture.Width * Scale), (int)(_texture.Height * Scale)); } public void Update(GameTime theGameTime, Vector2 theSpeed, Vector2 theDirection) { Position += theDirection * theSpeed * (float)theGameTime.ElapsedGameTime.TotalSeconds; } //Affiche le sprite à l’écran public virtual void Draw(SpriteBatch monSpriteBatch) { monSpriteBatch.Draw(_texture, Position, new Rectangle(0,0,_texture.Width,_texture.Height) , Color.White,0.0f,Vector2.Zero,Scale,SpriteEffects.None,0); } }
Notez le mot-clé virtual de la méthode Draw. Cela permettra de réaliser un override de cette méthode dans les classes héritées.
Ajouter la classe Bullet
Maintenant ajoutez la classe Bullet, celle-ci hérite de la classe Sprite et va correspondre à un tir de vaisseau. Dans cette classe nous allons gérer la vitesse du tir ainsi que son affichage en fonction sa position. C’est-à -dire que si le tire sort de l’écran on ne l’affiche plus. Il serait dommage de perdre des ressources en affichant quelque chose que l’on ne voit pas!
class Bullet : Sprite { const int MAX_DISTANCE = 500; public bool Visible = false; Vector2 _startPosition; Vector2 _speed; Vector2 _direction; public void LoadContent(ContentManager theContentManager) { base.LoadContent(theContentManager, "red_bullet"); Scale = 1.0f; } public void Update(GameTime theGameTime) { if (Vector2.Distance(_startPosition, Position) > MAX_DISTANCE) { Visible = false; } if (Visible == true) { base.Update(theGameTime, _speed, _direction); } } public override void Draw(SpriteBatch theSpriteBatch) { if (Visible == true) { base.Draw(theSpriteBatch); } } public void Fire(Vector2 theStartPosition, Vector2 theSpeed, Vector2 theDirection) { Position = theStartPosition; _startPosition = theStartPosition; _speed = theSpeed; _direction = theDirection; Visible = true; } }
Comme vous pouvez le constater, la méthode Draw de notre classe Bullet remplace celle de la classe Sprite. On ne lance la méthode Draw de la classe Sprite uniquement si notre objet Bullet est visible ou du moins si la distance parcourue est inférieure à MAX_DISTANCE.
Ajouter la classe Ship
Nous allons maintenant ajouter la classe Ship qui elle aussi hérite de la classe Sprite. La classe Ship représente le vaisseau et possède dans ses propriétés une liste de Bullet, ainsi on peut savoir à qui appartiennent les tirs.
Commencez par ajouter ces quelques lignes en haut de la classe
class Ship :Sprite { const string SHIP_ASSETNAME = "logo_HD"; const int START_POSITION_X = 240; const int START_POSITION_Y = 400; const int SHIP_SPEED = 160; const int MOVE_UP = -1; const int MOVE_DOWN = 1; const int MOVE_LEFT = -1; const int MOVE_RIGHT = 1; const int BackBufferWidth = Game1.BackBufferWidth; const int BackBufferHeight = Game1.BackBufferHeight; enum State { Moving } State _currentState = State.Moving; Vector2 _direction = Vector2.Zero; Vector2 _speed = Vector2.Zero; TouchCollection _previousTouchCollection; List _bullets = new List(); ContentManager _contentManager;
Les constantes (position de départ, vitesse max , etc) ainsi que l’état (State) serviront à définir le comportement du vaisseau. Le vaisseau possède également une liste de Bullet correspondant aux tirs.
Dans la méthode LoadContent, nous allons charger les textures, initialiser la position du vaisseau et récupérer l’état de l’écran (via TouchPanel). Ajoutez les lignes suivantes :
public void LoadContent(ContentManager theContentManager) { _contentManager = theContentManager; foreach (Bullet aBullet in _bullets) { aBullet.LoadContent(theContentManager); } Position = new Vector2(START_POSITION_X, START_POSITION_Y); base.LoadContent(theContentManager, SHIP_ASSETNAME); _previousTouchCollection = TouchPanel.GetState(); }
Vous remarquerez cette ligne base.LoadContent(theContentManager, SHIP_ASSETNAME). Elle permet de faire appel à la méthode LoadContent de la classe mère autrement dit la classe Sprite.
Nous allons à présent ajouter la méthode Update.
public void Update(GameTime theGameTime) { TouchCollection _currentTouchCollection = TouchPanel.GetState(); UpdateMovement(_currentTouchCollection); UpdateBullet(theGameTime, _currentTouchCollection); _previousTouchCollection = _currentTouchCollection; base.Update(theGameTime, _speed, _direction); }
La première chose à faire est de mettre à jour l’état de l’écran pour pouvoir gérer les commandes (Déplacement). C’est ce que font les deux premières lignes. La méthode UpdateMovement sera détaillée dans quelques secondes suivie de la méthode UpdateBullet. La 4e ligne permettra de comparer au prochain tour de boucle, l’état actuel et l’état précédent.
Voilà la méthode UpdateMovement. Ajoutez la à votre code.
private void UpdateMovement(TouchCollection _currentTouchCollection) { bool buttonTouched = false; //interprete le touch sur l'écran foreach (TouchLocation location in _currentTouchCollection) { switch (location.State) { case TouchLocationState.Pressed: buttonTouched = true; break; case TouchLocationState.Moved: buttonTouched = true; break; case TouchLocationState.Released: break; } } if (_currentState == State.Moving) { _speed = Vector2.Zero; _direction = Vector2.Zero; if (buttonTouched) { if (_currentTouchCollection[0].Position.X < this.Position.X && this.Position.X > 0) { _speed.X = SHIP_SPEED; _direction.X = MOVE_LEFT; } if (_currentTouchCollection[0].Position.X > this.Position.X && this.Position.X < BackBufferWidth - this.Size.Width) { _speed.X = SHIP_SPEED; _direction.X = MOVE_RIGHT; } if (_currentTouchCollection[0].Position.Y < this.Position.Y && this.Position.Y > 0) { _speed.Y = SHIP_SPEED; _direction.Y = MOVE_UP; } if (_currentTouchCollection[0].Position.Y > this.Position.Y && this.Position.Y < BackBufferHeight - this.Size.Height) { _speed.Y = SHIP_SPEED; _direction.Y = MOVE_DOWN; } } } }
La première partie de la méthode détermine la nature des « touch » sur l’écran et la seconde partie permet de déplacer le vaisseau en fonction de la position du « touch » sur l’écran.
Nous allons maintenant passer à la méthode UpdateBullet qui va mettre à jour la liste de Bullet en fonction du TouchPanel. Ajoutez ces lignes :
private void UpdateBullet(GameTime theGameTime, TouchCollection aCurrentTouchCollection) { foreach (Bullet aBullet in _bullets) { aBullet.Update(theGameTime); } bool buttonTouched = false; //interprete le touch sur l'écran foreach (TouchLocation location in aCurrentTouchCollection) { switch (location.State) { case TouchLocationState.Pressed: buttonTouched = true; break; case TouchLocationState.Moved: buttonTouched = true; break; case TouchLocationState.Released: break; } } if (buttonTouched) { if (aCurrentTouchCollection[0].Position.X > this.Position.X && this.Position.X < BackBufferWidth - this.Size.Width) { ShootBullet(); } } }
Comme pour la méthode UpdateMovement, la première partie du code permet de déterminer la nature des « touch ». La seconde partie fait tirer le vaisseau (ShootBullet) si ce dernier se déplace vers l’avant.
Et voici le code la méthode ShootBullet :
private void ShootBullet() { if (_currentState == State.Moving) { bool aCreateNew = true; foreach (Bullet aBullet in _bullets) { if (aBullet.Visible == false) { aCreateNew = false; aBullet.Fire(Position + new Vector2(Size.Width / 2, Size.Height / 2), new Vector2(200, 0), new Vector2(1, 0)); break; } } if (aCreateNew == true) { Bullet aFireball = new Bullet(); aFireball.LoadContent(_contentManager); aFireball.Fire(Position + new Vector2(Size.Width / 2, Size.Height / 2), new Vector2(200, 200), new Vector2(1, 0)); _bullets.Add(aFireball); } } }
Si un des Bullet se trouve en dehors de l’écran, plutôt que d’en créer un nouveau, on le déplace au niveau du vaisseau sans que le joueur ne s’en aperçoive. Ceci permet d’économiser de la ressource en évitant de créer infiniment de nouveaux objets à chaque tir. Dans le cas où aucun Bullet ne se trouve en dehors de l’écran, on crée un nouvel objet.
Enfin, il reste à ajouter la méthode Draw dans laquelle nous faire appel à la méthode Draw de chaque Bullet du vaisseau. Ajoutez ces lignes :
public override void Draw(SpriteBatch theSpriteBatch) { foreach (Bullet aBullet in _bullets) { aBullet.Draw(theSpriteBatch); } base.Draw(theSpriteBatch); }
Voilà pour ce qui est de la classe Ship, le plus gros du travail est fait.
Modifier la classe Game1
Nous allons maintenant modifier la classe Game1 pour qu’elle s’occupe de coordonner tout le reste. Elle s’occupe toujours de faire défiler le Background. Modifiez la classe Game1 en fonction des lignes suivantes, il n’y a pas beaucoup de changements à faire :
public class Game1 : Microsoft.Xna.Framework.Game { GraphicsDeviceManager graphics; SpriteBatch spriteBatch; Ship monVaisseau; Sprite BackgroundOne; Sprite BackgroundTwo; Sprite BackgroundThree; Sprite BackgroundFour; Sprite BackgroundFive; TouchCollection touchCollection; public const int BackBufferWidth = 480; public const int BackBufferHeight = 800; public Game1() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; graphics.PreferredBackBufferWidth = BackBufferWidth; graphics.PreferredBackBufferHeight = BackBufferHeight; // Frame rate is 30 fps by default for Windows Phone. TargetElapsedTime = TimeSpan.FromTicks(333333); } protected override void Initialize() { // TODO: Add your initialization logic here touchCollection = new TouchCollection(); monVaisseau = new Ship(); BackgroundOne = new Sprite(); BackgroundTwo = new Sprite(); BackgroundThree = new Sprite(); BackgroundFour = new Sprite(); BackgroundFive = new Sprite(); base.Initialize(); } protected override void LoadContent() { // Create a new SpriteBatch, which can be used to draw textures. spriteBatch = new SpriteBatch(GraphicsDevice); monVaisseau.LoadContent(this.Content); monVaisseau.Scale = 2.0f; BackgroundOne.LoadContent(this.Content, "BackgroundOne"); BackgroundOne.Position = new Vector2(0, 0); BackgroundTwo.LoadContent(this.Content, "BackgroundTwo"); BackgroundTwo.Position = new Vector2(BackgroundOne.Position.X + BackgroundOne.Size.Width, 0); BackgroundThree.LoadContent(this.Content, "BackgroundThree"); BackgroundThree.Position = new Vector2(BackgroundTwo.Position.X + BackgroundTwo.Size.Width, 0); BackgroundFour.LoadContent(this.Content, "BackgroundFour"); BackgroundFour.Position = new Vector2(BackgroundThree.Position.X + BackgroundThree.Size.Width, 0); BackgroundFive.LoadContent(this.Content, "BackgroundFive"); BackgroundFive.Position = new Vector2(BackgroundFour.Position.X + BackgroundFour.Size.Width, 0); } protected override void UnloadContent() { // TODO: Unload any non ContentManager content here } protected override void Update(GameTime gameTime) { // Allows the game to exit if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit(); monVaisseau.Update(gameTime); if (BackgroundOne.Position.X < -BackgroundOne.Size.Width) { BackgroundOne.Position.X = BackgroundFive.Position.X + BackgroundFive.Size.Width; } if (BackgroundTwo.Position.X < -BackgroundTwo.Size.Width) { BackgroundTwo.Position.X = BackgroundOne.Position.X + BackgroundOne.Size.Width; } if (BackgroundThree.Position.X < -BackgroundThree.Size.Width) { BackgroundThree.Position.X = BackgroundTwo.Position.X + BackgroundTwo.Size.Width; } if (BackgroundFour.Position.X < -BackgroundFour.Size.Width) { BackgroundFour.Position.X = BackgroundThree.Position.X + BackgroundThree.Size.Width; } if (BackgroundFive.Position.X < -BackgroundFive.Size.Width) { BackgroundFive.Position.X = BackgroundFour.Position.X + BackgroundFour.Size.Width; } Vector2 aDirection = new Vector2(-1, 0); Vector2 aSpeed = new Vector2(160, 0); BackgroundOne.Position += aDirection * aSpeed * (float)gameTime.ElapsedGameTime.TotalSeconds; BackgroundTwo.Position += aDirection * aSpeed * (float)gameTime.ElapsedGameTime.TotalSeconds; BackgroundThree.Position += aDirection * aSpeed * (float)gameTime.ElapsedGameTime.TotalSeconds; BackgroundFour.Position += aDirection * aSpeed * (float)gameTime.ElapsedGameTime.TotalSeconds; BackgroundFive.Position += aDirection * aSpeed * (float)gameTime.ElapsedGameTime.TotalSeconds; base.Update(gameTime); } protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.CornflowerBlue); spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend); BackgroundOne.Draw(this.spriteBatch); BackgroundTwo.Draw(this.spriteBatch); BackgroundThree.Draw(this.spriteBatch); BackgroundFour.Draw(this.spriteBatch); BackgroundFive.Draw(this.spriteBatch); monVaisseau.Draw(this.spriteBatch); spriteBatch.End(); base.Draw(gameTime); } }
Et voilà , c’est terminé. Lancez un Debug et lorsque vous vous déplacez d’arrière en avant vous devriez voir le vaisseau Tequila apparaître à l’écran tirant des boules de feu !

































subscribe to comments RSS
Pas de commentaire pour cet article