Le personnage doit pouvoir tirer au laser pour dégommer les aliens. Voyons comment gérer les entrées, ajouter du son, animer le tir, etc…

Un tir très simple pour commencer

Dans cette première version il est impossible de tirer plus d’une fois. Impossible aussi de tirer à gauche, le laser part toujours à droite. Bref, pas très utile, mais il faut bien commencer quelque part.

Je commence par ajouter l’état shooting dans le hash hero pour savoir si un tir est en cours.

    state.hero ||= {
      # ...
      shooting: false,
    }

Il nous faut un hash pour conserver les sprites de laser, et il faut les afficher.

    state.shots ||= []

    outputs.sprites << state.shots

On déclenche le tir avec la touche ALT ou un bouton de la manette. Sans la ligne if state.shots.empty? un tir serait déclenché à chaque frame. Essayez pour voir le problème.

    if inputs.keyboard.alt || inputs.controller_one.b
      if state.shots.empty?
        state.hero.shooting = true
        audio[:laser] = { input: 'sounds/laser.wav' }
      end
    end

Si un tir à eu lieu, on ajoute un sprite dans le hash shots. Ce sprite sera déplacé de 5 pixels vers la droite à chaque frame.

  def calc_shot
    if state.hero.shooting
      state.shots << {
        x: state.hero.x,
        y: state.hero.y + 20,
        w: 24,
        h: 10,
        path: 'sprites/laser.png',
      }
      state.hero.shooting = false
    end
    state.shots.each do |shot|
      shot.x += 5
    end
  end

Pouvoir tirer et re-tirer

On ajoute un état dead à chaque tir, qui passe à true quand le sprite disparait de l’écran. Ça permet de supprimer les tirs hors jeu avec reject!. De cette manière on obtient un tir à la space invaders.

  def calc_shot
    if state.hero.shooting
      state.shots << {
        # ...
        dead: false,
      }
      state.hero.shooting = false
    end

    state.shots.each do |shot|
      shot.x += 5
      shot.dead = true if shot.x > Grid.w
    end
    state.shots.reject!(&:dead)
  end

Tirer à droite et à gauche

L’état hero.moving nous permet de savoir si le perso bouge vers la droite ou la gauche, mais ne nous apprend rien quand à la direction à laquelle il fait face lorsqu’il ne bouge pas. On va ajouter hero.facing pour toujours savoir où le perso regarde, même quand il est à l’arrêt.

    state.hero ||= {
      # ...
      moving: :none,
      facing: :right,
      # ...
    }

  def input
    if inputs.left
      state.hero.moving = :left
      state.hero.facing = :left
    elsif inputs.right
      state.hero.moving = :right
      state.hero.facing = :right
    else
      state.hero.moving = :none
    end
  end

Maintenant on peut tirer dans la direction du regard du perso, par l’intermédiaire de speed.

  def calc_shot
    if state.hero.shooting
      state.shots << {
        # ...
        speed: state.hero.facing == :right ? LASER_SPEED : -LASER_SPEED,
      }
      state.hero.shooting = false
    end

    state.shots.each do |shot|
      shot.x += shot.speed
      shot.dead = true if shot.x > Grid.w || shot.x < 0
    end
    state.shots.reject!(&:dead)
  end

Cadence de tir

Disons qu’on veut pouvoir tirer toutes les 1/2 secondes, donc toutes les 30 frames :

    FIRE_RATE = 30 # Maximum is one shot every FIRE_RATE frames

On va se souvenir du moment du dernier tir :

    state.hero ||= {
      # ...
      last_shot_at: 0,
    }

On autorisera un tir seulement si le dernier a eu lieu il y a plus d’une demi seconde :

    if inputs.keyboard.alt || inputs.controller_one.b
      if state.hero.last_shot_at + FIRE_RATE < Kernel.tick_count
        state.hero.shooting = true
        audio[:laser] = { input: 'sounds/laser.wav' }
      end
    end

On met à jour le moment du tir :

  def calc_shot
    if state.hero.shooting
      state.shots << {
        # ...
      }
      state.hero.shooting = false
      state.hero.last_shot_at = Kernel.tick_count
    end

Animation

Pour finir, voici une animation toute simple du laser. Celui-ci est retourné verticallement toutes les 10 frames :

  LASER_ANIMATION = 10

  def calc_shot
    if state.hero.shooting
      state.shots << {
        # ...
        animation_counter: LASER_ANIMATION,
        flip_vertically: false,
      }
      state.hero.shooting = false
      state.hero.last_shot_at = Kernel.tick_count
    end

    state.shots.each do |shot|
      shot.animation_counter -= 1
      if shot.animation_counter == 0
        shot.animation_counter = LASER_ANIMATION
        shot.flip_vertically = !shot.flip_vertically
      end
      shot.x += shot.speed
      shot.dead = true if shot.x > Grid.w || shot.x < 0
    end
    state.shots.reject!(&:dead)
  end

Références

  1. Vous trouverez le code de Jetpack Hero sur github
  2. Documentation de DragonRuby


/ / / / / / / / / /


Cet article fait partie d’une série :

  1. Jetpack Hero
  2. Partie II
  3. Une platforme, des collisions
  4. Première animation du personnage
  5. Ajouter des platformes
  6. Du carburant pour le jetpack
  7. Collecte de minerai
  8. Effets sonores
  9. Du rangement avec la classe Game
  10. Apparition des aliens
  11. Tir du personnage
  12. On dégomme de l’alien
  13. GAME OVER
  14. Les aliens bougent enfin