• Contenu
  • Bas de page
logo ouidoulogo ouidoulogo ouidoulogo ouidou
  • Qui sommes-nous ?
  • Offres
    • 💻 Applications métier
    • 🤝 Collaboration des équipes
    • 🛡️ Sécurisation et optimisation du système d’information
    • 🔗 Transformation numérique
  • Expertises
    • 🖥️ Développement logiciel
    • ♾️ DevSecOps
    • ⚙️ Intégration de logiciels et négoce de licences
      • Atlassian : Jira, Confluence, Bitbucket…
      • Plateforme monday.com
      • GitLab
      • SonarQube
    • 📚​ Logiciel de CRM et de gestion
    • 🎨 UX/UI design
    • 🌐 Accessibilité Numérique
    • 🗂️​ Démarches simplifiées
    • 📝 Formations Atlassian
  • Références
  • Carrières
    • 🧐 Pourquoi rejoindre Ouidou ?
    • ✍🏻 Nous rejoindre
    • 👨‍💻 Rencontrer nos collaborateurs
    • 🚀 Grandir chez Ouidou
  • RSE
  • Ressources
    • 🗞️ Actualités
    • 🔍 Articles techniques
    • 📖 Livres blancs
    • 🎙️ Interviews Clients
Nous contacter
✕
Souligner en CSS ? oui mais avec classe !
Souligner en CSS ? oui mais avec classe !
25 mars 2021
Gemini, l’avenir du Web ?
Gemini, l’avenir du Web ?
9 avril 2021
Ressources > Articles techniques > Gosu et sa boucle principale

Gosu et sa boucle principale

Article écrit par Hugo Fabre

Contrôler la boucle principale de Gosu

L’idée de cet article est d’expliquer pourquoi on peut vouloir prendre le contrôle sur la boucle principale de notre moteur de jeu et comment on peut y arriver avec Gosu. Avant tout il faut expliquer comment fonctionne un jeu vidéo.

La boucle principale

La boucle principale (ou main loop) est au cœur d’un jeu vidéo (ou de son moteur). L’idée est simple :

state = init_game  while game_running?   inputs = poll_inputs   state = update(inputs, state)   draw(state) end 

On peut retrouver une boucle de ce genre (bien sûr ici elle est simplifiée à l’extrême) dans tous les jeux. L’idée est très claire, tant que l’on joue, on récupère les entrées du joueur (appui sur une touche ou encore mouvement de souris), on les utilise pour mettre à jour l’état (position du joueur par exemple) de notre jeu puis on le dessine. Maintenant regardons de plus près comment on retrouve ce concept dans Gosu.

Dans Gosu

Avant tout regardons le hello world de Gosu :

require 'gosu'  class GameWindow < Gosu::Window   def initialize     super(640, 480)   end    def update     # Ici on met à jour notre état en utilisant la logique de notre jeu   end    def draw     # Ici on dessine notre état   end end  GameWindow.new.show 

On retrouve bien la plupart des concepts vus précédemment mais pas de boucle. Si on regarde le code attentivement on remarque qu’on fait appel une méthode show sur notre fenêtre de jeu que l’on n’a jamais définis. Celle-ci doit se trouver dans la classe dont on hérite : Gosu::Window. Pour aller plus loin il faudra donc aller voir dans la documentation ou bien le code source de Gosu.

Mais pourquoi se prendre la tête avec tout ça alors que Gosu nous fournit une API simple à utiliser ?

Pourquoi prendre le contrôle de sa boucle principale ?

Si seule la solution vous intéresse, cette partie n’est pas obligatoire pour la compréhension, vous pouvez passer à la suite.

Je vais prendre l’exemple qui m’a fait me poser cette question, mais il y a très probablement d’autres situations. Je voulais explorer le domaine du réseau dans le jeu vidéo, pour me simplifier la tâche j’ai décidé d’utiliser des outils que je connais et que je maitrise déjà : Ruby et Gosu. Seulement j’ai rencontré une problématique.

Le réseau dans le jeu vidéo

Je vais proposer ici une explication très courte, car ce n’est pas le sujet de l’article et il mériterait un article ou même une suite d’articles à lui tout seul. Il y a plusieurs manières de développer un jeu en réseau, celle qui m’intéresse ici, c’est la technique du client et du serveur avec un serveur autoritaire.

L’idée derrière cette technique, c’est que lorsqu’un client (joueur) voudra faire une action il enverra un message au serveur qui simulera l’action et dira au client si oui ou non elle est valide avant de la transmettre aux autres joueurs. Grâce à cette technique ou pourra éviter la triche, car si un joueur peut modifier son client pour tricher il ne pourra pas toucher à notre serveur qui lui est distant.

Pour pouvoir valider les entrées des clients, le serveur doit donc faire tourner le jeu lui-même et c’est lui qui aura l’état du jeu de référence. Il le mettra à jour lorsqu’un client enverra un message. Seulement côté client on ne peut pas attendre que le message arrive jusqu’au serveur et qu’un OK nous revienne, sinon le jeu ne serait pas très réactif ; on veut donc pouvoir faire tourner la simulation côté client aussi.

Partage du code

En simulant le jeu côté serveur et client, on se dit qu’il serait dommage de dupliquer la logique du jeu à deux endroits alors qu’on pourrait partager le code commun. Seulement il serait quand même dommage d’embarquer sur notre serveur la bibliothèque d’affichage alors qu’on n’a aucun besoin d’afficher l’état de notre jeu sur un serveur qui n’aura probablement pas d’écran (à part pour le déboguer en local). De plus pour éviter au maximum les désynchronisations entre le client et le serveur (latence, perte de paquet…) on voudra avoir la même boucle principale des deux côtés. Sauf que comme nous l’avons vu plus haut, Gosu nous cache sa boucle principale, il va donc falloir trouver une solution pour intégrer la nôtre à la place de la sienne.

Prendre le contrôle

Reprenons le hello world de Gosu :

require 'gosu'  class GameWindow < Gosu::Window   def initialize     super(640, 480)   end    def update     # Ici on met à jour notre état en utilisant la logique de notre jeu   end    def draw     # Ici on dessine notre état   end end  GameWindow.new.show 

Donc comme vu plus haut il va falloir aller voir ce que fait cette méthode Gosu::Window#show, rendez-vous donc à la définition de celle-ci. On remarque qu’entre autres choses plus compliquées celle-ci boucle (c’est notre main loop) simplement sur une méthode tick

// Ligne 264 while (tick()) {     // ... } 

Allons regarder de plus près cette méthode. Ici, on peut retrouver tous les concepts sur la main loop vus en début d’article (récupération des inputs, mise à jour de l’état et affichage) :

bool Gosu::Window::tick() {     // ...      // Ligne 306     SDL_Event e;     while (SDL_PollEvent(&e)) {         // On récupère les entrées du joueur via la SDL     }      // ...      // Ligne 348     // Cette méthode `update` est tout simplement celle que l'on définit dans notre propre classe `GameWindow`     update();      // ...      // Ligne 355     // Idem ici, c'est notre méthode `draw`     draw();      // ... } 

Évidemment le fonctionnement est plus complexe que celui-ci, mais c’est en réalité tout ce qu’il nous faut voir et comprendre. Du coup notre solution est toute simple, on va remplacer la méthode show qui s’occupe de la boucle par notre propre boucle :

require 'gosu'  class GameWindow < Gosu::Window   def initialize     super(640, 480)      @font = Gosu::Font.new(self, Gosu::default_font_name, 15)     @frame = 0   end    def run     while running?       tick     end      close   end    def update     # Ici on met à jour notre état en utilisant la logique de notre jeu     @frame += 1   end    def draw     # Ici on dessine notre état     @font.draw_text("Frame number #{@frame}", 10, 30, 1, 1, 1)   end    private    def running?     @frame < 1000   end end  GameWindow.new.run 

Pour l’exemple j’ai rajouté de la logique à notre fenêtre, on compte le nombre de frame pour l’afficher et on quitte après les 1000 premières. Pour éviter de trop faire chauffer votre machine pensez à limiter le nombre de tours de boucle par seconde (les FPS du jeu) :

while self.running?   tick   sleep(1 / 60.0) end 

Ici on le limite à environ 60 frame par seconde. Encore une fois, c’est un gros raccourcis pour une problématique complexe, si vous voulez aller plus loin je vous invite à lire cet article sur le sujet (en anglais, mais très complet).

Bon c’est un bon début, mais ici notre logique est encore très couplée à notre fenêtre. Il est temps de changer ça et vous allez voir que c’est très simple, le gros du travail a déjà été fait :

require 'gosu'  class GosuRenderer   def initialize     @window = GameWindow.new   end    def draw(state)     @window.state = state     @window.tick # On fait appel à la méthode `tick` de Gosu::Window   end    def terminate     @window.close   end end  class GameWindow < Gosu::Window   attr_writer :state    def initialize     super(640, 480)      @font = Gosu::Font.new(self, Gosu::default_font_name, 15)     @state = {}   end    def update     # Notre état n'est plus géré par notre fenêtre, mais par notre moteur,     # donc il ne se passe rien ici   end    def draw     # Ici on dessine notre état     @font.draw_text("Frame number #{@state[:frame]}", 10, 30, 1, 1, 1) if @state[:frame]   end end  class VoidRenderer   # Implémente "l'interface" de nos moteurs de rendu   def draw(_state); end   def terminate; end end  class Engine   def initialize(renderer: VoidRenderer.new)     @renderer = renderer     @frame = 0   end    def run     while running?       # On affiche notre état dans la console pour vérifier que       # tout fonctionne même sans affichage.       puts @frame       tick       sleep(1 / 60.0)     end      @renderer.terminate   end    private    def tick     @frame += 1     @renderer.draw(frame: @frame)   end    def running?     @frame < 1000   end end  Engine.new(renderer: GosuRenderer.new).run 

Et voilà notre logique est complètement découplée de notre affichage. Vous pouvez faire le teste par vous-même en commentant tout le code au-dessus de la classe VoidRenderer et en appelant :

Engine.new(renderer: VoidRenderer.new).run 

Et notre logique s’exécutera quand même, on peut le vérifier grâce au puts.

À lire aussi

Fresque numérique miniature image
16 avril 2025

Fresque du Numérique

Lire la suite

intelligence artificielle Ouicommit miniature image
17 mars 2025

Ouicommit – L’intelligence artificielle en entreprise, on y est ! 

Lire la suite

Image miniature Hackathon Women in Tech
13 mars 2025

Hackathon Women in Tech :  un engagement pour une tech plus inclusive 

Lire la suite

image miniature les nouveautés Atlassian
26 février 2025

Les nouveautés Atlassian en 2025

Lire la suite

Articles associés

Fresque numérique miniature image
16 avril 2025

Fresque du Numérique


Lire la suite
intelligence artificielle Ouicommit miniature image
17 mars 2025

Ouicommit – L’intelligence artificielle en entreprise, on y est ! 


Lire la suite
Image miniature Hackathon Women in Tech
13 mars 2025

Hackathon Women in Tech :  un engagement pour une tech plus inclusive 


Lire la suite

À propos

  • Qui sommes-nous ?
  • Références
  • RSE
  • Ressources

Offres

  • Applications métier
  • Collaboration des équipes
  • Sécurisation et optimisation du système d’information
  • Transformation numérique

Expertises

  • Développement logiciel
  • DevSecOps
  • Intégration de logiciels et négoce de licences
  • Logiciel de CRM et de gestion
  • UX/UI design
  • Accessibilité Numérique
  • Démarches simplifiées
  • Formations Atlassian

Carrières

  • Pourquoi rejoindre Ouidou ?
  • Nous rejoindre
  • Rencontrer nos collaborateurs
  • Grandir chez Ouidou

SIEGE SOCIAL
70-74 boulevard Garibaldi, 75015 Paris

Ouidou Nord
165 Avenue de Bretagne, 59000 Lille

Ouidou Rhône-Alpes
4 place Amédée Bonnet, 69002 Lyon

Ouidou Grand-Ouest
2 rue Crucy, 44000 Nantes

Ouidou Grand-Est
7 cour des Cigarières, 67000 Strasbourg

  • Linkedin Ouidou
  • GitHub Ouidou
  • Youtube Ouidou
© 2024 Ouidou | Tous droits réservés | Plan du site | Mentions légales | Déclaration d'accessibilité
    Nous contacter