Secousse de caméra avec SFML

Dans cette présentation, Squirrel Eiserloh présente de façon générale comment ajouter des mouvements (translations fluides, secousses) à la caméra d’une scène en deux ou trois dimensions. On se propose d’appliquer cela sur un exemple de scène 2D écrite en C++ avec SFML : l’appui sur Espace déclenche une explosion qui fait vaciller la caméra.

Le résultat final ressemble à ceci (la qualité est un peu dégradée par le format GIF) :

Screenshot

Le code final est disponible ici.

Méthode

On se concentre ici sur les éléments permettant d’ajouter une secousse à la caméra. Les autres aspects (sprite animé, gestion de fenêtre SFML) ne seront pas abordés.

Dans le cas de la SFML, il n’y a pas d’objet caméra à part entière et tout repose sur la gestion de la vue qu’on pourra déplacer ou faire tourner.

💡 Note: L’inexistence de l’entité caméra n’est pas propre à SFML. Avec OpenGL (et DirectX de ce que j’en connais) c’est la combinaison des différentes matrices de projection qui permet de calculer la vue.

L’idée est d’appliquer des transformations assez rapides à la vue (qu’on appellera donc maintenant “caméra”) pour donner l’impression d’une secousse.

Définition d’une animation

Chaque secousse peut être paramétrée par une intensité (trauma), ainsi qu’une durée (max). L’intensité décroît au cours du temps, ce qui permet de revenir à un état normal et stable.

struct CameraAnimation {
  sf::Time current;
  sf::Time max;
  float trauma;
};

Le champ current servira à conserver l’avancement temporel de l’animation entre deux appels à la fonction de mise à jour de la caméra.

La caméra

La classe Camera propose les deux méthodes shake() et update() qui permettent respectivement d’initier une secousse avec une intensité et une durée données, et de mettre à jour les propriétés de la caméra (rotation, translation) à chaque image.

class Camera {
public:
  Camera(sf::RenderWindow* window);
  void shake(float trauma, float duration_ms);
  void update(float dt);
};

Comme dit plus haut, les actions portées sur la caméra sont en fait des actions portées sur la vue de la fenêtre. On a donc besoin d’une instance de la fenêtre de rendu (passée au constructeur).

Déclencher la secousse

La fonction shake() se contente de mettre à jour les paramètres souhaités pour la secousse en remplissant une structure (membre de la classe) de type CameraAnimation.

void Camera::shake(float trauma, float duration_ms) {

  // Set animation parameters
  _animation.max = sf::milliseconds(duration_ms);
  _animation.current = sf::seconds(0);

  // Trauma is inscreased each time this function is called: several calls increase shaking
  _animation.trauma += trauma;
}

Elle peut par exemple être appelée sur un évènement, ici ce sera l’appui sur Espace qui déclenche une explosion. À noter que l’intensité de la secousse peut être augmentée à souhait en appelant plusieurs fois de suite la fonction.

Mise à jour de la vue

La fonction update() est à appeler dans la boucle principale, en même temps que la mise à jour des différentes entités du jeu.

Elle calcule d’abord un angle et un offset de déplacement aléatoires, et dépendant de l’intensité souhaitée.

// ... otherwise compute a random angle...
double angle = CAMERA_SHAKE_ANGLE * _animation.trauma * randn();

// ... and a random XY-offset...
sf::Vector2f offset;
offset.x = CAMERA_SHAKE_OFFSET * _animation.trauma * randn();
offset.y = CAMERA_SHAKE_OFFSET * _animation.trauma * randn();

💡 Note: randn() renvoie un nombre flottant entre -1 et 1

Ensuite la transformation est appliquée à la caméra.

// ... and appply them to the view
_view.setRotation(angle);
_view.setCenter(_center+offset);
_window->setView(_view);

💡 Note: le champ _view est initialisé avec window->getView()

Puis le champ current de l’animation est mis à jour pour conserver l’avancement, et enfin l’intensité de la secousse est réduite de façon inversement proportionnelle au carré du temps écoulé.

// Update animation time
_animation.current += sf::seconds(dt);

// Decrease trauma parameter depending on time (squared)
float ratio = _animation.current.asSeconds() / _animation.max.asSeconds();
_animation.trauma *= 1.0 - ratio*ratio;
}

Utilisation

L’utilisation dans un programme SFML se fait de la façon suivante.

Instanciation

La caméra est instanciée en utilisant la fenêtre de rendu.

sf::RenderWindow window(sf::VideoMode(800, 460), "SFML window");
Camera camera(&window);

Mise à jour

La caméra est mise à jour à chaque tour de la boucle principale.

// Start the game loop
while (window.isOpen())
{
  // Process events...

  camera.update(dt.asSeconds());

  // Draw things...
}

Secousse

Dans l’exemple, la secousse se produit sur l’appui sur la barre d’espace.

if (event.key.code == sf::Keyboard::Space)
  camera.shake(0.2, 1200.0);

Conclusion

On pourrait de la même façon ajouter d’autres effets de tremblements, d’oscillations lentes, de translations. Le code final est disponible ici.

Ressources