Big Tuto SFML 2 / Action-RPG 2 / Expert : Legends of Meruvia

Chapitre 2 : Création et gestion de cinématiques

 

Tutoriel présenté par : Stéphane Barthélémy (Stephantasy)
Date d'écriture : 10 août 2016
Date de révision : -

 

      Introduction

 

   Quel jeu vidéo ne contient pas de scène cinématique ? frown Elles sont souvent essentielles pour renforcer l’immersion du joueur dans l’histoire ou pour introduire des éléments ou personnages-clés du jeu. Nous ne parlerons pas ici de cinématiques précalculées (en 3D, et qui sont en fait des vidéos), telles celles qu’on trouve dans les introductions de la série Final Fantasy par exemple, mais plutôt de cinématiques utilisant le moteur du jeu. wink


   Je vais vous présenter une fonctionnalité que j’ai développée lors de la création de mon jeu vidéo. Après avoir programmé ma première cinématique, et l’avoir modifiée deux fois, je me suis aperçu que cela allait rapidement devenir une vraie galère ! surprise En effet, à chaque modification mineure, il faut recompiler tout le code. devil De plus, à moins d’être un scénariste hors-pair, la réalisation d’un scénario nécessite souvent d’être ajusté et peaufiné; il faut souvent modifier les positionnements et les timings pour arriver à un résultat concluant. cheeky


   Bref, programmer le scénario d’une cinématique directement dans le code peut rapidement mener à la création d’une belle usine à gaz ! surprise Voyons ensemble comment faire autrement. wink


 

     Préambule

 

   Le gestionnaire de cinématiques peut apparaitre comme un outil merveilleux, nous permettant de créer des scénarios aisément et n’importe quand, puis voir notre jeu les jouer sans le moindre ajout de code ! cool Cependant, c’est loin d’être une fonctionnalité magique et cela peut apporter son lot de complications dans le développement de notre jeu. blush


   En effet, l’implémentation de cet outil dans le jeu risque d’avoir un fort impact. Pour un maximum d’efficacité, le moteur du jeu doit offrir le plus d’accesseurs et de modificateurs possible. Parfois, il faudra même revoir l’organisation du code afin de permettre des fonctionnalités particulières qu’on aimerait dans nos cinématiques. frown


   Implanter cet outil trop tôt dans le développement de notre moteur nous obligera peut-être à devoir le modifier souvent. L’implanter trop tard risque de nous obliger à modifier notre moteur.


   Dans l’optique de garder tout cela le plus simple possible, nous nous contenterons de contrôles simples dans un premier temps. wink
 

 

     Présentation

 

   L’objectif est donc de créer un outil qui va nous permettre d’introduire des cinématiques dans notre jeu. Nous allons faire en sorte de pouvoir créer et modifier un scénario sans avoir à modifier la moindre ligne de code ! angel

 

   Pour cela, nous allons avoir besoin de ce que j’appellerai : un gestionnaire de cinématiques. Il s’agit d’un outil permettant de jouer un scénario que l’on aura créé à l’aide de l’éditeur de scénarios.


   Cet éditeur va nous permettre de produire un fichier texte qui sera lu et interprété par notre gestionnaire.

 

 

   I-  Le gestionnaire

 

   Le gestionnaire est le cœur de notre solution, il est l’interface entre le scénario et le jeu.  

 

         I-1. Fonctionnement


   Son fonctionnement est très simple :

1. Il lit le fichier contenant le scénario.
2. Il interprète chaque étape du scénario et indique au moteur de jeu ce qu’il faut faire.

 

   Par exemple, si on voulait déplacer le personnage principal vers la droite jusqu’au milieu de la Map, notre fichier contiendrait une ligne de ce genre :

[1, Player, Déplacer vers la droite, Milieu de la Map]

 

   Notre gestionnaire exécuterait alors une tâche pouvant ressembler à ceci :

Tant que (position joueur < milieu Map)

{
      Déplacer joueur vers la droite
}

 

   Comme vous pouvez le constater, la mécanique est simple et elle se répètera pour les diverses tâches qu’on va vouloir inclure dans nos cinématiques (chargement de Map, variation du volume de la musique, jouer un son, etc.). cool

 

         I-2. La classe Cinematic

 

   Étant relativement indépendante, la classe de notre gestionnaire, nommée « Cinematic », offre peu de contrôle. Seuls, son constructeur (pour créer l’objet), deux méthodes de réinitialisation et sa méthode principale sont publics. Tout le reste ne regarde qu’elle et est donc privé. wink Circulez, il n’y a rien à voir ! laugh

 

         I-3. Définition de la classe (en-tête)

 

Fichier : Créer un nouveau fichier cinematic.h et y copier :

//Legends of Meruvia - C++ / SFML 2
//Copyright / Droits d'auteur : www.meruvia.fr - Jérémie F. Bellanger
//Coded by: Stéphane Barthélémy
#pragma once
 
#ifndef CINEMATIC_H
#define CINEMATIC_H
 
#include <SFML/System/Clock.hpp>
#include <SFML/Window.hpp>
#include <sstream>
#include <string>
#include <vector>
#include <algorithm>
 
class Map;
class Player;
class Input;
class Sounds;
 
class Cinematic
{
 
public:
 
       //Constructeur
       Cinematic(Input&, Map&, Player&, Sounds&);
 
       //Fonctions
       bool isCinematic();
       void setCinematicReplay(int number);
       void setCinematicReset(void);
      
private:
 
       Input &_input;
       Map &_map;
       Player &_player;   
       Sounds &_sounds;
 
       // Enumérateurs
       enum ActionType {          // Types d'actions possibles dans un scénario
             Action_End = -1,           // Fin du scénario
             Action_Wait,               // Attente
             Action_MoveSprite,         // Action sur un Sprite
             Action_LoadMap,                   // Charge une Map
             Action_PlayMusic,          // Joue une musique
             Action_SetVolumeMusic,     // Contrôle du volume de la musique
             Action_PlaySound,          // Joue un son
             Action_Skip = 99           // Skip la cinématique
       };
       enum Conditions {          // Conditions d'exécution d'une action
             Condition_GoOn,                   // On continue de charger les actions
             Condition_Final                   // On exécute les actions chargées
       };
       enum SpriteControl { // Contrôles d'un Sprite
             SpriteMoveRight,           // Déplacement vers la droite
             SpriteMoveLeft,                  // Déplacement vers la gauche
             SpriteMoveUp,              // Déplacement vers le haut
             SpriteMoveDown,                   // Déplacement vers le bas
             SpriteAttack,              // Attaque
             SpriteLookRight,           // Regarde à droite
             SpriteLookLeft,                   // Regarde à guache
             SpriteLookUp,              // Regarde en haut
             SpriteLookDown,                   // Regarde en bas
       };
 
       // Structure d'une action dans un scénario
       struct Actions
       {
             int step;                         // Étape
             int actionType;                   // Type de l'action
             std::string param1;        // Paramètre 1
             std::string param2;        // Paramètre 2
             std::string param3;        // Paramètre 3
             std::string param4;        // Paramètre 4
             int condition;                    // Condition
       };
       // Liste des actions a exécuter
       struct ExecActions
       {
             int step;                         // Numéro de l'étape
             bool isConditionMeet;      // État de la condition de l'étape
       };
 
       // Constantes
       const std::string FILE_NAME = "scenarios/scenario_";
       // Liste des paramètres utilisés
       int MUSIC_VOLUME_INSTANT = 0;           // Modification du volume instantanément
       int MUSIC_VOLUME_INCREASE = 0;          // Modification du volume progressivement
 
 
       // Variables
       int _currentCinematic;                                // Numéro de la cinématique jouée
       bool _IsRunning;                                      // Cinématique en cours
       std::vector<int> _cinematicPlayed;             // Cinématique déjà jouées
       std::vector<Actions> _actions;                 // Liste des actions du scénario de la cinématique
       std::vector<ExecActions> _execActions;  // Actions à exécuter     
       int _activeStep;                                      // Etape en cours de traitement
       int _previousStep;                                    // Dernière étape exécutée
       bool _skipAsked;                                      // On a demandé à passer la cinématique
 
       sf::Clock clock;                                      // Clock
       sf::Time _actualTime;                                 // Wait : Temps écoulé
       sf::Time _memoTime;                                   // Wait : Mémorisation du temps pour l'action "Wait"
 
 
       // Fonctions 
       void initialize();
       void play();
       bool load(void);
       void end(void);
       bool isAlreadyPlayed(void);
       void setAlreadyPlayed(void);
       void readActions(void);
       void executeActions(void);
 
};
#endif

 

        I-4. La classe cinematic

 

Fichier : Créer un nouveau fichier cinematic.cpp et y copier :

//Legends of Meruvia - C++ / SFML 2
//Copyright / Droits d'auteur : www.meruvia.fr - Jérémie F. Bellanger
//Coded by: Stéphane Barthélémy
 
#include "Cinematic.h"
#include "player.h"
#include "map.h"
#include "input.h"
#include "sounds.h"
#include "magic.h"
#include "menus.h"
 
// Constructeur
Cinematic::Cinematic(Input &input, Map &map, Player &player, Sounds &sounds, Magic *magic, Menus &menus) :
       _input(input), _map(map), _player(player), _sounds(sounds), _magic(magic), _menus(menus)
{
       _currentCinematic = 0;
       _IsRunning = false;
}
 
 
//Renvoie si une cinématique est jouée
bool Cinematic::isCinematic()
{
       // Si une cinématique est associée à la Map et que la condition de l'exécuter est à 1, on lance la lecture du scénario
       if ((_map.getCinematics() > 0 && _map.getCinematicsCondition() > 0) || _IsRunning) {
 
             // La 1ère fois, on l'initialise
             if (_map.getCinematicsCondition() > 0) {
                    initialize();
             }
 
             // On joue la cinématique
             play();
            
             return true;
       }
 
       return false;
}
 
// Initialisation
void Cinematic::initialize(){
 
       // Numéro de la cinématique
       _currentCinematic = _map.getCinematics();
      
       // Reset du déclencheur de cinématique afin d'éviter de la relancer sans cesse
       _map.resetCinematicsCondition();
 
       // Est-ce que cette cinématique a déjà été jouée ? Si oui, on quitte.
       if (isAlreadyPlayed())
       {
             end();
             return;
       }
 
       // Initialisation
       _activeStep = 0;
       _previousStep = -1;                           
       _skipAsked = false;
       _player.setState(0);
 
       // Gestion du temps écoulé
       clock.restart();
       _memoTime = clock.getElapsedTime();    
 
       // On vide la liste des actions
       _actions.clear();
 
       // On charge le fichier de la cinématique
       if (load())
       {
             // On indique qu'une cinématique est en cours
             _IsRunning = true;
       }
       else
       {
             end();
       }
}
 
// Contrôle si la cinématique a déjà été jouée
bool Cinematic::isAlreadyPlayed(void)
{
       for (size_t i = 0, size = _cinematicPlayed.size(); i < size; ++i) {
             if (_cinematicPlayed[i] == _currentCinematic) {
                    return true;
             }
       }
       return false;
}
 
// On mémorise le fait fait que cette cinématique a été jouée
void Cinematic::setAlreadyPlayed(void)
{
       _cinematicPlayed.push_back(_currentCinematic);
}
 
// On efface la cinématique de la liste des "déjà jouées"
void Cinematic::setCinematicReplay(int number)
{
       _cinematicPlayed.erase(std::remove(_cinematicPlayed.begin(), _cinematicPlayed.end(), number), _cinematicPlayed.end());
}
 
// On efface la liste des cinématiques déjà jouées
void Cinematic::setCinematicReset(void)
{
       _cinematicPlayed.clear();
}
 
// Chargement du fichier
bool Cinematic::load(void)
{
       bool result = false;
       try
       {
             // On rempli notre vector avec les données du fichier
             std::string path = FILE_NAME + std::to_string(_currentCinematic) + ".txt";
             std::ifstream file(path);
             std::string   line;
             while (std::getline(file, line))
             {
                    std::stringstream   linestream(line);
                    Actions temp = { -1, -1, "-1", "-1", "-1", "-1", -1 };
                    linestream >> temp.step >> temp.actionType >> temp.param1 >> temp.param2 >> temp.param3 >> temp.param4 >> temp.condition;
                    _actions.push_back(temp);
                   
             }
 
             // On contrôle qu'il y ait des champs
             if (_actions.size() > 0)
             {                  
                    // On indique la fin du scénario
                    _actions.push_back({ -1, -1, "-1", "-1", "-1", "-1", -1 });
                    result = true;
             }
            
       }
       catch (...) {
             result = false;
       }
       return result;
}
 
// Lecture des actions du scénario
void Cinematic::readActions()
{
       while (_activeStep != _previousStep) {
 
             switch (_actions[_activeStep].actionType)
             {
 
                    // fin du scenario
             case Action_End:
             {
                    end();
             }break;
 
 
             //wait
             case Action_Wait:
             {
                    // On charge l'action a exécuter et on indique qu'elle n'est pas remplie
                    ExecActions tmp = { _activeStep, false }; // Toujours "StopCond" ! (puisqu'on veut attendre...)
                    _execActions.push_back(tmp);
 
                    // On mémorise le temps actuel
                    _memoTime = clock.getElapsedTime();
 
                    // Sur une condition finale, on passe à l'execution des tâches
                    _previousStep = _activeStep;
 
             }break;
 
 
             // Contrôle Sprite
             case Action_SpriteControl:
             {
                    // On charge l'action a exécuter et on indique qu'elle n'est pas remplie
                    ExecActions tmp = { _activeStep, false };
                    _execActions.push_back(tmp);
 
                    // Sur une condition finale, on passe à l'execution des tâches
                    if (_actions[_activeStep].condition == Condition_Final)
                    {
                           _previousStep = _activeStep;
                    }
                    // Sinon on continue de charger les actions
                    else {
                           _activeStep++;
                    }
             }
             break;
 
 
             // Map
             case Action_LoadMap:
             {
                    // On charge l'action a exécuter et on indique qu'elle n'est pas remplie
                    ExecActions tmp = { _activeStep, false };
                    _execActions.push_back(tmp);
 
                    if (_actions[_activeStep].condition == Condition_Final) {
                           _previousStep = _activeStep;
                    }
                    else {
                           _activeStep++;
                    }
             }break;
 
 
             // Musique
             case Action_PlayMusic:
             {
                    // On charge l'action a exécuter et on indique qu'elle n'est pas remplie
                    ExecActions tmp = { _activeStep, false };
                    _execActions.push_back(tmp);
 
                    if (_actions[_activeStep].condition == Condition_Final) {
                           _previousStep = _activeStep;
                    }
                    else {
                           _activeStep++;
                    }
             }break;
 
 
             // Volume musique
             case Action_VolumeMusic:
             {
                    // On charge l'action a exécuter et on indique qu'elle n'est pas remplie
                    ExecActions tmp = { _activeStep, false };
                    _execActions.push_back(tmp);
 
                    if (_actions[_activeStep].condition == Condition_Final) {
                           _previousStep = _activeStep;
                    }
                    else {
                           _activeStep++;
                    }
             }break;
 
 
             // Joue un son
             case Action_PlaySound:
             {
                    // On charge l'action a exécuter et on indique qu'elle n'est pas remplie
                    ExecActions tmp = { _activeStep, false };
                    _execActions.push_back(tmp);
 
                    if (_actions[_activeStep].condition == Condition_Final) {
                           _previousStep = _activeStep;
                    }
                    else {
                           _activeStep++;
                    }
             }break;
 
 
             // Skip
             case Action_Skip:
             {
                    // Si un skip a été demandé, on reset les actions et on execute les actions du Skip
                    if (_skipAsked) {
                           _activeStep++;
                    }
 
                    // Sinon, c'est qu'on a atteint la ligne "Skip" du fichier,
                    // donc on active simplement la fin de la cinématique en sautant les étapes du Skip
                    else {
                           while (_actions[_activeStep].actionType != Action_End) {
                                  _activeStep++;
                           }
                    }
             }break;
 
             }
       }
}
 
// Exécution des actions du scénario
void Cinematic::executeActions()
{
       bool allActionsDone = true;
 
       for (size_t i = 0, size = _execActions.size(); i < size ; ++i)
       {
             // Si la condition n'est pas encore remplie
             if (!_execActions[i].isConditionMeet)   
             {
                    switch (_actions[_execActions[i].step].actionType)
                    {
 
                    // Wait
                    case Action_Wait:
                    {
                           // On compte le temps qui passe
                           _actualTime = clock.getElapsedTime();
                           if ((_actualTime - _memoTime) >= sf::milliseconds(std::stoi(_actions[_execActions[i].step].param1)))
                           {
                                  // La condition est remplie lorsque le temps écoulé dépasse le temps prévue
                                  _execActions[i].isConditionMeet = true;
                           }
                    }break;
 
 
                    // Contrôle Sprite
                    case Action_SpriteControl:
                    {
                           // Contrôle du joueur
                           if (_actions[_execActions[i].step].param1 == "Player")
                           {
                                  // Action demandée
                                  switch ((SpriteControl)std::stoi(_actions[_execActions[i].step].param2))
                                  {
 
                                  case SpriteMoveRight:
                                  {
                                        // Si le joueur a atteint sa destination, c'est fini
                                        if (_player.getX() >= std::stoi(_actions[_execActions[i].step].param4))
                                        {
                                               _player.update(_input, _map, _magic, _menus);
                                               _execActions[i].isConditionMeet = true;
                                        }
                                        // Sinon on le déplace
                                        else
                                        {            
                                               // Est-ce qu'on cours ?
                                               if (std::stoi(_actions[_execActions[i].step].param3) == 1){
                                                      _input.setButton(Input::ButtonName::run, true);
                                               }
                                               _input.setButton(Input::ButtonName::right, true);
                                               _player.update(_input, _map, _magic, _menus);
                                        }
                                  }break;
 
                                  case SpriteMoveLeft:
                                  {
                                        // Si le joueur a atteint sa destination, c'est fini
                                        if (_player.getX() <= std::stoi(_actions[_execActions[i].step].param4))
                                        {
                                               _player.update(_input, _map, _magic, _menus);
                                               _execActions[i].isConditionMeet = true;
                                        }
                                        // Sinon on le déplace
                                        else
                                        {
                                               // Est-ce qu'on cours ?
                                               if (std::stoi(_actions[_execActions[i].step].param3) == 1) {
                                                      _input.setButton(Input::ButtonName::run, true);
                                               }
                                               _input.setButton(Input::ButtonName::left, true);
                                               _player.update(_input, _map, _magic, _menus);
                                        }
                                  }break;
 
                                  case SpriteMoveUp:
                                  {
                                        // Si le joueur a atteint sa destination, c'est fini
                                        if (_player.getY() <= std::stoi(_actions[_execActions[i].step].param4))
                                        {
                                               _player.update(_input, _map, _magic, _menus);
                                               _execActions[i].isConditionMeet = true;
                                        }
                                        // Sinon on le déplace
                                        else
                                        {
                                               // Est-ce qu'on cours ?
                                               if (std::stoi(_actions[_execActions[i].step].param3) == 1) {
                                                      _input.setButton(Input::ButtonName::run, true);
                                               }
                                               _input.setButton(Input::ButtonName::up, true);
                                               _player.update(_input, _map, _magic, _menus);
                                        }
                                  }break;
 
                                  case SpriteMoveDown:
                                  {
                                        // Si le joueur a atteint sa destination, c'est fini
                                        if (_player.getY() >= std::stoi(_actions[_execActions[i].step].param4))
                                        {
                                               _player.update(_input, _map, _magic, _menus);
                                               _execActions[i].isConditionMeet = true;
                                        }
                                        // Sinon on le déplace
                                        else
                                        {
                                               // Est-ce qu'on cours ?
                                               if (std::stoi(_actions[_execActions[i].step].param3) == 1) {
                                                      _input.setButton(Input::ButtonName::run, true);
                                               }
                                               _input.setButton(Input::ButtonName::down, true);
                                               _player.update(_input, _map, _magic, _menus);
                                        }
                                  }break;
 
                                  case SpriteAttack:
                                  {
                                        _input.setButton(Input::ButtonName::attack, true);
                                        _player.update(_input, _map, _magic, _menus);
                                        _execActions[i].isConditionMeet = true;
                                  }break;
 
 
                                  case SpriteLookRight:
                                  {
                                        _player.setDirection(_player.getDirectionRight());
                                        _player.update(_input, _map, _magic, _menus);
                                        _execActions[i].isConditionMeet = true;
                                  }break;
 
                                  case SpriteLookLeft:
                                  {
                                        _player.setDirection(_player.getDirectionLeft());
                                        _player.update(_input, _map, _magic, _menus);
                                        _execActions[i].isConditionMeet = true;
                                  }break;
 
                                  case SpriteLookUp:
                                  {
                                        _player.setDirection(_player.getDirectionUp());
                                        _player.update(_input, _map, _magic, _menus);
                                        _execActions[i].isConditionMeet = true;
                                  }break;
 
                                  case SpriteLookDown:
                                  {
                                        _player.setDirection(_player.getDirectionDown());
                                         _player.update(_input, _map, _magic, _menus);
                                        _execActions[i].isConditionMeet = true;
                                  }break;
 
 
                                  }
                           }
                    }break;
 
 
                    // On charge une Map
                    case Action_LoadMap:
                    {
                           _map.setLevel(std::stoi(_actions[_execActions[i].step].param1));
                           _map.changeLevel();
                           _player.initialize(_map);
                           _player.update(_input, _map, _magic, _menus);
                           // On valide la condition
                           _execActions[i].isConditionMeet = true;
                    }break;
 
                   
                    // On charge une musique
                    case Action_PlayMusic:
                    {
                           // TODO (ajouter la possiblité de charger un musique dans Sounds)
 
                           // On valide la condition
                           _execActions[i].isConditionMeet = true;
                    }break;
 
                    //Volume musique
                    case Action_VolumeMusic:
                    {
                           // Augmentation du volume
                           if (std::stoi(_actions[_execActions[i].step].param1) == MUSIC_VOLUME_INCREASE)
                           {
                                  float newVolume = 0.0;
                                  // Volume instantané...
                                  if (std::stoi(_actions[_execActions[i].step].param2) == MUSIC_VOLUME_INSTANT) {
                                        newVolume = std::stoi(_actions[_execActions[i].step].param3);
                                  }
                                  // ...ou progressif
                                  else
                                  {
                                        newVolume = _sounds.getMusicVolume()+1.0;
                                  }
 
                                  // Si le nouveau volume à atteint la valeur demandée ou la valeur maximale permise
                                  if (newVolume >= std::stoi(_actions[_execActions[i].step].param3) || newVolume >= _sounds.getMusicVolumeMax())
                                  {
                                        if (newVolume >= _sounds.getMusicVolumeMax())
                                        {
                                               _sounds.setMusicVolume(_sounds.getMusicVolumeMax());
                                        }
                                        else {
                                               _sounds.setMusicVolume(std::stoi(_actions[_execActions[i].step].param3));
                                        }
 
                                        // On valide la condition
                                        _execActions[i].isConditionMeet = true;
                                  }
                                  // On applique le nouveau volume
                                  else {
                                        _sounds.setMusicVolume(newVolume);
                                  }
                           }
                           // Diminution du Volume
                           else
                           {
                                  int newVolume = 0;
                                  // Volume instantané...
                                  if (std::stoi(_actions[_execActions[i].step].param2) == MUSIC_VOLUME_INSTANT) {
                                        newVolume = std::stoi(_actions[_execActions[i].step].param3);
                                  }
                                  // ...ou progressif
                                  else
                                  {
                                        newVolume = _sounds.getMusicVolume() - 1.0;
                                  }
 
                                  // Si le nouveau volume à atteint la valeur demandée ou la valeur maximale permise
                                  if (newVolume <= std::stoi(_actions[_execActions[i].step].param3) || newVolume <= 0)
                                  {
                                        if (newVolume <= 0)
                                        {
                                               _sounds.setMusicVolume(0.0);
                                        }
                                        else {
                                               _sounds.setMusicVolume(std::stoi(_actions[_execActions[i].step].param3));
                                        }
 
                                        // On valide la condition
                                        _execActions[i].isConditionMeet = true;
                                  }
                                  // On applique le nouveau volume
                                  else {
                                        _sounds.setMusicVolume(newVolume);
                                  }
                           }
                    }break;
 
 
                    // On joue un son
                    case Action_PlaySound:
                    {
                           _sounds.PlaySoundFx(std::stoi(_actions[_execActions[i].step].param1));
                           // On valide la condition
                           _execActions[i].isConditionMeet = true;
                    }break;
 
 
                    }           
             }
 
             // On contrôle l'avancement des actions
             allActionsDone &= _execActions[i].isConditionMeet;
       }
 
       // Si toutes les actions ont été réalisées, on passe à l'étape suivante
       if (allActionsDone)
             _activeStep++;
}
 
// On joue la cinématique
void Cinematic::play()
{
       /* L'exécution d'un scénario s'effectue en 2 étapes :
             1. Lecture des actions jusqu'à la rencontre d'une condition d'attente
             2. Exécution des actions jusqu'à ce qu'elles aient toutes été jouées.
 
             => Ce cycle ce poursuit tant qu'il y a des actions disponibles dans le scénario.
       */
 
       // Sur demande de Skip, on passe directement à la partie Skip programmée
       if (_input.getButton().enter)
       {
             // Si le skip a déjà été activé, on quitte
             if (!_skipAsked) {
                    _skipAsked = true;
 
                    // On charge les étapes du Skip
                    int cpt = 0;
                    while (_actions[cpt].actionType != Action_Skip) {
                           cpt++;
                           // S'il n'y a pas de skip de prévu, on ignore la commande
                           // sinon on ne peut pas être certain que la cinématique se termine correctement
                           if (_actions[cpt].actionType == Action_End) {
                                  cpt = 0;
                                  break;
                           }
                    }
                    // On passe aux actions "Skip" (si elles existent)
                    if (cpt > 0) {
                           _previousStep = cpt - 1;
                           _activeStep = cpt;
                    }
             }
       }
 
       // On reset les Inputs
       std::vector<int> inputList;
       inputList.push_back(Input::ButtonName::up);
       inputList.push_back(Input::ButtonName::down);
       inputList.push_back(Input::ButtonName::right);
       inputList.push_back(Input::ButtonName::left);
       inputList.push_back(Input::ButtonName::attack);
       inputList.push_back(Input::ButtonName::run);
       inputList.push_back(Input::ButtonName::enter);
       inputList.push_back(Input::ButtonName::magic);
       _input.clearInput(inputList);
 
       // Étape 1
       if (_activeStep != _previousStep)
       {
             _execActions.clear();
             readActions();
       }
       // Étape 2
       else
       {
             executeActions();
       }
      
}
 
// Fin de la cinématique
void Cinematic::end(void)
{
       setAlreadyPlayed();
       _currentCinematic = 0;
       _IsRunning = false;
       _activeStep = _previousStep = -1;
}

 

         I-5. Le constructeur


   Le constructeur prend en paramètre les principaux objets du jeu, soit : Input, Map, Player, Sounds, Magic et Menus. On stocke une référence de chacun d’entre eux dans des attributs privés afin de ne pas avoir à les passer en paramètre à toutes les fonctions. wink

 

         I-6. La méthode isCinematic()


   La méthode « isCinematic() » se charge de contrôler s’il y a une cinématique à jouer. Si c’est le cas, elle prend la main et la garde jusqu’à la fin du scénario.

   C’est la classe « Map » qui nous informe qu’il y a une cinématique à jouer. Cette classe possède deux membres intitulés « cinematics » et « cinematics_condition », déjà présents dans la classe (pas besoin de les ajouter donc - et on peut modifier leurs valeurs via le level editor wink).

   Le premier désigne le numéro du scénario à jouer et le second nous indique s’il faut jouer la cinématique. L’attribut « cinematics_condition » sert donc de déclencheur et pourrait être mis à 1 après qu’une série d’évènements se soit déroulée (par exemple, après avoir fini une quête en particulier).

 

   Donc, pour qu’une cinématique soit jouée, il faut :
         - que la Map fasse référence à un scénario* : cinematics > 0
         - que l’on reçoive l’ordre d’exécuter le scénario : cinematics_condition > 0
   *Vous noterez qu’en l’état actuel des choses, on ne peut pas associer plus d’une cinématique à une Map.

 

         I-7. Autres méthodes


   « setCinematicReplay() » permet d’ôter une cinématique de la liste des « déjà jouées » et donc de pouvoir la rejouer. Cela peut se produire lorsque le joueur meurt, par exemple. 

   « setCinematicReset() » permet d’effacer la liste des cinématiques déjà jouées. Cela est utile lorsque l’on souhaite charger une nouvelle partie ou une autre sauvegarde. 

 

         I-8. Fonctionnement : initialisation


   Voici comment se déroule le déclenchement d’une cinématique.
   On arrive dans une nouvelle Map. Celle-ci nous indique que cinematics == 2 et que cinematics_condition == 1. Cela se traduit par « autorisation de jouer le scénario numéro 2 ».

   On commence par initialiser notre gestionnaire :
         1. Initialisation de variables
         2. Remise à 0 de cinematics_condition
         3. On s’assure que cette cinématique n’a pas déjà été jouée
         4. Chargement du fichier « scenario_2.txt »

   L’initialisation est terminée, nous passons à l’exécution.

 

         I-9. Fonctionnement : Play


   Ca y est, on joue notre scénario ! C’est le rôle de la fonction « play() ». Voici comment cette dernière fonctionne. wink

   Avant toute chose, on contrôle à tout moment si l’action « Skip » est demandée (affectée à la touche [ENTRÉE]). Mais nous reviendrons sur cette fonctionnalité plus tard. cheeky

   Ensuite, on remet à zéro les « Input » afin que le joueur ne puisse pas interférer dans le bon déroulement de la cinématique. wink

 

   Finalement, on exécute le scénario. Cela se passe en deux temps :

1. Lecture d’une ou de plusieurs actions.
2. Exécution d’une ou de plusieurs actions.

 

   La lecture des actions se fait tant que la condition de l’action ne lui dit pas d’arrêter. La condition vaut 0 ou 1, signifiant respectivement « Continue » et « Arrête-toi » (Condition_GoOn et Condition_Final dans le code).

   Une fois la lecture interrompue par la condition « Condition_Final », on passe à l’exécution des actions chargées. A son tour, l’exécution dure tant que toutes les actions n’ont pas rempli leur propre condition (nous les appellerons « conditions d’actions1 »).

   Une fois que toutes les actions ont rempli leur condition, on retourne à la lecture des actions suivantes et cela se répète tant qu’il y a des actions dans le scénario. wink

   Enfin, lorsqu’il n’y a plus d’action à lire, on indique qu’il n’y a plus de cinématique en cours de lecture et on mémorise le fait que cette cinématique vient d’être jouée (voir et revoir la même scène devient rapidement irritant ! laugh).

   Le jeu reprend ses droits.

 

En résumé, le gestionnaire traite les actions de la façon suivante :
      1. S’il y a des actions à charger => étape 2 ; sinon FIN
      2. Chargement des actions tant que la condition est à 0
      3. Dès qu’une action a une condition à 1 => fin du chargement, on passe à l’exécution
      4. Exécution de toutes les actions chargées
      5. Toutes les actions chargées ont été exécutées => retour à l’étape 1

Note :
   Les conditions d’actions dépendent de l’action. En effet, chaque action a une condition qui lui est propre.
   Par exemple, si l’action est « Déplace le joueur vers la droite », sa condition sera une destination (une position du joueur en x). Pour une variation du volume de la musique, la condition sera un niveau sonore (exprimé de 0 à 100 avec la SFML).

 

         I-10. Skip 


   Cette option permet de passer une cinématique. Bien que cela soit fort dommage (surtout quand on voit le mal qu’on se donne pour les créer cheeky), on peut offrir cette possibilité. wink

   En effet, on a le choix, car le Skip, pour être effectif, doit être prévu. Et c’est très important ! surprise Sinon imaginez la pagaille qu’il peut y avoir à interrompre une cinématique n’importe quand.

 

   Considérez le cas suivant :

1. Début de la cinématique
2. Le joueur doit se déplacer au milieu de la Map
3. Déplacement du joueur vers le milieu
4. Il discute avec le méchant
5. À la fin du dialogue, le méchant ouvre une trappe sous les pieds du joueur
6. Le joueur tombe et on charge une nouvelle Map (une grotte sombre infestée de vermines !)
7. Fin du scénario


   Bon, maintenant, ça fois trois fois que je meurs dans la grotte et que le jeu me renvoie à la surface. Forcément, cela fait également trois fois que je me farcis la même cinématique et je décide de la passer. Voici alors ce qu’il se passe :

1. Début de la cinématique
2. Le joueur doit se déplacer au milieu de la Map
3. Déplacement du joueur vers… Je presse [ENTRÉE] => Skip !
4. Fin de la cinématique


   « …Euh, la cinématique est terminée, mais le jeu n’a pas chargé la Map de la grotte. Je fais quoi maintenant ?! crying »

  Vous comprenez alors qu’on ne peut pas passer n’importe quand, ni n’importe comment une cinématique !

   Ainsi, dans notre scénario, il va falloir prévoir ce qui se passe lorsqu’on Skip notre cinématique. Nous verrons cela en détails dans la partie dédiée à l’éditeur (au chapitre suivant wink).

 

         II- Intégration au moteur du jeu 


   L’intégration est très simple. On demande à la fonction « isCinematic() » s’il y a une cinématique à jouer. S’il n’y en a pas, le déroulement du jeu se poursuit normalement. S’il y en a une, on n’appelle plus la fonction « update() » dans le Main(), car la classe « Cinématic » se charge alors de mettre à jour le jeu. wink

   Ce qui donne :

 

Fichier : main.cpp : Modifier le code :

// S'il y a une cinématique en cours, on ne met à jour que le gestionnaire de cinématique

if(!Cinematic.isCinematic() )

{

       //Updates

       update(input, map, player, monster, sounds);

}  

 

   Une fois qu’il est lancé, le gestionnaire s’occupe de jouer le scénario jusqu’à la fin ou jusqu’à ce qu’un « Skip » soit demandé. Puis, il rend la main une fois qu’il a terminé. wink

   N'oubliez pas non plus d'instancier la classe cinematic ! wink

   Voilà le code complet du main(), mis à jour :

 

Fichier : main.cpp : Modifier le code :

int main(int argc, char *argv[])
{
// Création d'une fenêtre en SFML
RenderWindow window(VideoMode(SCREEN_WIDTH, SCREEN_HEIGHT, 32),
"Meruvia - Big Tuto A-RPG 2 / Expert - Chap 02 - www.meruvia.net");
 
//On active la synchro verticale
window.setVerticalSyncEnabled(true);
 
//Instanciation des classes
Input input;
Map map;
Player player;
Sounds sounds;
Menus menus;
 
//On instancie autant de classes que de monstres gérables
Monster monster[MONSTRES_MAX];
//On instancie autant de classes que de projectiles magiques gérables
Magic magic[MAGIC_MAX];
//On instancie autant de classes que d'explosions gérables
Explosions explosion[EXPLOSIONS_MAX];
 
// Cnématique [SB - Ajout]
Cinematic Cinematic(input, map, player, sounds, magic, menus);
 
//On lance la musique en boucle
sounds.PlayMusic(true);
 
//On commence au premier niveau
map.setLevel(1);
map.changeLevel();
 
//On initialise le player
player.initialize(map);
player.setGold(100);
 
//On commence par le menu start
menus.setOnMenu(true, START);
 
// Boucle infinie, principale, du jeu
while (window.isOpen())
{
 
// Gestion des inputs
input.gestionInputs(window);
 
// S'il y a une cinématique en cours, on ne met à jour que le gestionnaire de cinématique [SB - Ajout]
if(!Cinematic.isCinematic() )
{
//Updates
update(input, map, player, monster, sounds, magic, menus, explosion);
}
 
 
// Dessin - draw
draw(window, map, player, monster, magic, menus, explosion);
 
window.display();
}
 
// On quitte
return 0;
 
} 

 

   Il faudra aussi rajouter les fonctions resetCinematicsCondition(void), getCinematics() et getCinematicsCondition() à notre classe Map :

 

Fichier : map.h : Rajouter les prototypes :

int getCinematics(void) const;
int getCinematicsCondition(void) const;
void resetCinematicsCondition(void);  

  

Fichier : map.cpp : Rajouter les fonctions:

int Map::getCinematics(void) const { return cinematics; }
int Map::getCinematicsCondition(void) const { return cinematics_condition; }
void Map::resetCinematicsCondition(void) { cinematics_condition = 0; }  
 
   Voilà, c'est tout pour ce chapitre ! wink
   Dans le prochain, on verra l'éditeur de cinématiques ! A bientôt ! angel
         Stephantasy

   

 

 

This site uses cookies to enable you to log in. We do not store or sell any personal data. By continuing to use this website, you agree to their use. Thanks!