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) :
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é avecwindow->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
- La présentation sur Youtube
- Le dépôt Git du code