Page web Vincent THOMAS Atelier - Moteurs physiques, jeux vidéos et simulateurs de comportement (ISN - 12/03/2015)Atelier **année 2016**Nous reprendrons au cours de l'atelier les différents éléments ci-dessous qui avaient été développés dans l'atelier 2015. Il n'est donc pas necessaire de les lire avant l'atelier.Pour préparer l'atelier et tirer parti au mieux du temps consacré, je vous demanderai de suivre les consignes suivantes:
Afin de préparer l'atelier, téléchargez en amont le fichier de base "Squelette.py" à partir duquel nous pourrons greffer un moteur physique. Description de l'atelierActuellement, de nombreuses applications numériques utilisent des moteurs physiques que ce soit pour avoir des réactions réalistes dans des jeux (déplacements dans Super Mario, simulation de l'inertie du joueur dans les FPS, ...), pour proposer des simulateurs physiques (pédagogiques comme algodoo ou professionnels) ou pour aider le développement d'animations 3D (moteur dans Blender, ...).Cet atelier cherchera à faire le lien entre la physique et l'informatique.
Contenu de l'atelier
Diaporama et code des exemplesLes documents (pdf) utilisés lors de la présentation de l'atelier
Installation de pygameCet atelier utilisera le langage python et la librairie pygame destinée à faire des animations et des jeux. Cette libraire se révèle très pratique et facile d'accés et l'atelier permettra aussi de faire un point sur son fonctionnement.Cependant, il est nécessaire d'installer pygame et d'avoir des libraires compatibles. Pour cela, vous pouvez récuperer et installer les fichiers suivants (python 3.2 avec pygame adapté)
Les pages suivantes proposent des liens permettant d'aborder facilement la librairie pygame Un moteur simple avec pygame1 - Moteur physiqueLa première étape consiste à écrire un moteur très simple simulant la mécanique du point. On se limitera pour commencer à simuler le comportement d'un objet mobile soumis à la gravité et lancé avec une vitesse initiale.Pour cela, il suffit de partir des équations aux dérivées partielles (acceleration = somme des forces) et de les intégrer en discrétisant le temps. Ces équations se transforment simplement en affectation (avec un dt trés petit) //definition de l'acceleration (gravite uniquement) ax=0 ay=-9 //integration des equations de la dynamique v (temps discret) vx=vx+ax.dt vy=vy+ay.dt x=x+vx.dt y=y+vy.dt Ecrire le code correspondant et afficher à l'écran les valeurs de position, vitesse et accélération. Pour structurer un peu mieux le code construit, il est possible d'encapsuler les lois physiques dans une classe contenant les données de l'objet mobile (par exemple jeu). Les deux fichiers ci-dessous proposent un premier corrige: ![]() ![]() 2 - Présentation de la librairie pygameAfin d'avoir un rendu graphique mis à jour au fur et à mesure du temps, nous allons utiliser un moteur de jeu. En python, nous nous focaliserons sur la librairie pygame.La librairie pygame est une libraire destinée à développer rapidement et facilement des jeux vidéos. Elle propose toutes les primitives d'un moteur de jeu. Un moteur de jeu est un ensemble de classe et de fonctions permettant de faire tourner un jeu indépendamment de son contenu (règles du jeu, ...). Un moteur de jeu consiste (1) à répéter de manière régulière une boucle de jeu constituée de la mise à jour du jeu et de son affichage (2) à prendre en compte les commandes faites par le joueur en entrée (évènement clavier, ...) Une boucle de jeu peut simplement être représenté par l'algorithme suivant:
La librairie pygame propose trois éléments permettant de mettre en place trés rapidement un moteur de jeu
3 - Moteur de jeu sous pygameLe code suivant (inspiré de Programm arcade with pygame) présente un squelette simple permettant de développer un jeu sous pygame.#on cree un jeu (approche objet) jeu=Jeu() # jeu_fini est un boolean qui précise quand le jeu est fini jeu_fini = False # on creer une horloge pour réguler la vitesse de la boucle de jeu clock = pygame.time.Clock() # -------- Boucle principale ----------- # tant que le jeu n'est pas fini while not jeu_fini: # --- on traite les evenements jeu.traiter_evenement() # --- on fait evoluer le jeu jeu.evoluer() # --- on dessine le jeu jeu.dessiner() # on inverse les affichages (double buffering) pygame.display.flip() # --- on demande d'attendre ce qu'il faut pour un FPS de 60 clock.tick(60) # -------- Fin Boucle principale ----------- pygame.quit() Dans ce code:
4 - Simulateur de chuteA partir de ces éléments, il est trés simple d'écrire un simulateur physique avec un rendu graphique.Concernant la mise à jour des données, on se contentera d'utiliser les lois physiques discrétisées présentées ci-dessus: //definition de l'acceleration (gravite uniquement) ax=0 ay=-9 //integration des equations de la dynamique v (temps discret) vx=vx+ax.dt vy=vy+ay.dt x=x+vx.dt y=y+vy.dt Pour l'affichage, on se limitera à dessiner un cercle avec la primitive de dessin pygame.draw.rect. Il faut néanmoins faire attention à changer le repère: les coordonnées (0,0) d'un écran correspondent au coin haut-gauche, il faut donc penser à inverser l'axe y pour passer du monde physique à l'écran. RED = (0xFF, 0x00, 0x00) pygame.draw.rect(screen,RED,(jeu.x,400-jeu.y,10,10)) Dans le code proposé ci-dessous, le changement de repère (monde-écran) est géré par la méthode changerCoordonnes ce qui permet d'ajouter une plus grande flexibilité. On peut ainsi facilement changer les coordonnées de références ou le facteur de zoom en ajoutant des paramètres supplémentaires (cf section ultérieure "changement coordonnées"). # permet de changer de repere pour l'affichage def changerCoordonnes(self,dx,dy): nx = dx ny= 400-dy return( (nx,ny)) Enfin, la seule interaction avec l'utilisateur résidera dans la possibilité de fermer la fenêtre lancée par pygame (on récupère l'évènement pygame.QUIT) # --- on traite les evenements for event in pygame.event.get(): #si l'utilisateur arrete if event.type == pygame.QUIT: done = True ![]() 5 - Affichage de la trajectoireIl est possible d'afficher la trajectoire suivie en stockant les valeurs de (x,y) de l'objet à chaque itération. Cela est fait dans la méthode evoluer() qui stocke les positions successives dans les attributs de type tableau trajX et trajY.self.trajX+=[self.x] self.trajY+=[self.y] L'affichage consiste alors à tracer des lignes entre les points successifs de la trajectoire (à l'aide d'une simple boucle dans la methode dessiner()). #on peut afficher la trajectoire stockee dans trajX et trajY #pour chaque point de la trajectoire for i in range(0,len(self.trajX)-1): x=self.trajX[i] y=self.trajY[i] coord=self.changerCoordonnes(x,y) x2=self.trajX[i+1] y2=self.trajY[i+1] coord2=self.changerCoordonnes(x2,y2) #PS : on peut ameliorer la boucle car le x2 et y2 sont les futurs x,y pygame.draw.line(screen,BLUE,coord,coord2,1) ![]() ![]() 6 - Controleur clavierEn récupérant les évènements claviers, il est possible d'interagir avec l'objet mobile. A la réception d'un évènement correspondant à la touche "haut", il suffit de modifier la vitesse vy de l'objet mobile. Le moteur physique prend ensuite en charge son déplacement et l'intégration de la gravité.La méthode controler() gére les évènements utilisateurs. Dans pygame, ceux-ci sont stockés dans un tableau (accessible via pygame.event.get()) qu'il suffit de parcourir à chaque iteration. On ajoute un paragraphe sur la gestion de l'appui d'une touche (le type de l'évènement correspond à la constante pygame.KEYDOWN) # la gestion des touches # appelee dans la boucle de jeu def controler(self): global done #traitement des evenements for event in pygame.event.get(): #si l'utilisateur arrete if event.type == pygame.QUIT: done = True #gestion de l'appui d'une touche if event.type == pygame.KEYDOWN: # si c'est la touche "haut" if event.key == pygame.K_UP: print("up") jeu.vy=50 ![]() ![]() 7 - Angry birdsUne fois que le système de gestion de la gravité est mis en place, on peut imaginer de nombreux jeux basés sur ce type de moteur.Par exemple, un jeu comme "angry birds" en est la conséquence directe:
![]() Ajouts au moteur1 - Système masse-ressortUne fois qu'on dispose d'un système permettant de simuler la mécanique du point, il est possible d'ajouter trés simplement des ressorts.Il suffit de définir des ressorts entre des objets et de calculer à chaque itération les forces exercées par chaque ressort sur les objets situés à leurs extrémités. Le moteur physique s'occupe ensuite de prendre en compte ces accélérations dans la modification de la vitesse et de la position des objets. Dés que la classe ressort est définie, il est possible de gérer plusieurs ressorts. Sur chaque particule mobile, on ajoute la contribution de chaque ressort à la somme des forces et on fait ensuite évoluer le système. ![]() ![]() ![]() ![]() Il est aussi possible d'afficher à des positions régulières le vecteur force exercé par le ressort (car, dans ce cas, la force ne dépend que de la position de l'objet attaché au ressort). Il est ensuite possible de rendre compte des équilibres stables et instables en raisonnant sur le champ de force. L'application ralentit beaucoup car à chaque itération, toutes les forces sont calculées et réaffichées (comme le champ de force ne bouge pas, l'idéal pour accélérer l'affichage consisterait à sauver ce champ de force dans une image qu'on recopiera à chaque itération) ![]() ![]() 2 - Jeu Gish-likeUne fois le système masse-ressort écrit, il est possible de créer des jeux basés sur des créatures composées de ressorts.Le plus simple consiste à créer une classe Systeme constituée
class Systeme(): # un systeme est constitue d'objets et de ressorts def __init__(self): self.ressorts=[] self.objets=[] # executer def evoluer(self): # initialiser a a g for objet in self.objets: objet.ax=0 objet.ay=-9 # pour chaque ressort for ressort in self.ressorts: ressort.metreAJourAcc() # mettre à jour objet for objet in self.objets: objet.evoluer() ![]() A partir de la classe Systeme, on peut produire des formes constituées de treillis de ressorts. Le comportement général de l'objet dépend de la manières dont les objets mobiles sont connectés. Il est à noter que chaque objet du treillis dispose de sa propre masse (puisqu'on ajoute la gravité à l'accélération de chaque objet). Il est possible d'avoir des objets de masses différentes en modifiant le vecteur accélération (les forces autres que la gravité doivent être modulées par le rapport de masses m/m_unitaire). ![]() ![]() ![]() ![]() ![]() ![]() La version ci-dessous ajoute un controleur qui permet de modifier en temps réel la raideur des ressorts (avec les fleches "haut" "bas" du clavier). En fonction de cette raideur, le blob peut s'affaisser sous son propre poids. ![]() ![]() ![]() En baissant la raideur puis en l'augmentant rapidement il est possible de faire "sauter" la forme (du fait de la reaction des ressorts) ![]() C'est sur cette base, que le jeu gish a été développé (mais il intègre beaucoup plus d'éléments en particulier, la manière de se déplacer en réponse aux contrôles des utilisateurs). ![]() 3 - Gravité et système à plusieurs corpsDe la meme manière, simuler la gravité entre planètes consiste simplement à ajouter à notre moteur de jeu de nouveaux types de force.![]() Jeu, collision et interaction1 - Gestionnaire collision
Afin de faire un jeu, il faut rajouter un gestionnaire de collisions. Un gestionnaire de collision consiste avant tout à determiner quand un objet rectangulaire intersecte un autre objet rectangulaire.if (b.x >= a.x + a.w) or (b.x + b.w <= a.x) or (b.y >= a.y + a.h) or(b.y + b.h <= a.y)): return false else: return true } ![]() |
||||||
|
#passe du monde à l'ecran def transformCoord(self,xm,ym): tx=self.tailleFenetreX/2 ty=self.tailleFenetreY/2 return(tx+(xm-self.x0)*self.zoom,ty+(ym-self.y0)*self.zoom) #passe de l'ecran au monde(ex gerer click souris) def coord_inverse(self,xe,ye): tx=self.tailleFenetreX/2 ty=self.tailleFenetreY/2 return((xe-tx)/self.zoom+self.x0,(ye-ty)/self.zoom+self.y0)