Fichier de rejeu Close

Indication Close

A propos de... Close

Commentaire Close

Téléchargements

Aide

Boucle de simulation

Objectif :

Objectif 5 : Présenter le principe de boucle de simulation et le mettre en œuvre dans le cadre du projet de ipi pour simuler des comportements.

Principe

Nous nous plaçons dans le cadre de la réalisation d’un jeu qui fonctionne plus ou moins en temps réel. La vitesse à laquelle l’état du jeu évolue est essentielle pour l’expérience du joueur. Un jeu trop rapide sera injouable alors qu’un jeu trop lent sera ennuyeux. D’autre part, il faut que le temps s’écoule de manière régulière. Il ne s’agit pas que le jeu ralentisse ou accélère en fonction de la quantité de calculs à effectuer. Il faut donc maîtriser l’écoulement du temps dans le jeu en en fonction du temps physique, celui du joueur.

Durant l’exécution du programme plusieurs activités doivent être réalisées de manière simultanée :

  • La simulation : L’état du jeu évolue avec le temps qui passe.
  • Gestion de l’affichage : L’utilisateur doit voir ce qui se passe dans le jeu.
  • Gestion des événements clavier : Le logiciel doit réagir aux actions du joueur.

A partir de vos connaissances en programmation procédurale, vous savez coder un programme qui réalise plusieurs choses en même temps trois choses?

Définition : Application temps réel

Lorsqu’on parle d’un jeu, d’une simulation numérique ou de réalité virtuelle, une application en temps réelle assure une correspondance entre le temps qui est simulé et le temps physique de l’utilisateur. Le temps réel s’attache également aux questions de latence. Il s’agit de maîtriser le temps qui s’écoule entre une action de l’utilisateur et sa prise en compte dans l’application.

Pour ordonnancer l’ensemble des actions à réaliser il faut un programme qui s’appuie sur une boucle de simulation. Nous concevrons le programme principal d’un jeu comme une boucle de simulation qui donnera tour à tour la main aux sections de code qui s’occupent de l’affichage, de l’interaction clavier ou de l’évolution du jeu en fonction du temps.

Définition : Boucle de simulation

Une boucle de simulation est une boucle infinie qui permet de réaliser les différentes actions nécessaires à la mise en œuvre d’un logiciel de simulation. On peut également utiliser le terme de boucle événementielle si on s’intéresse plus à l’interface homme machine (IHM). Les paramètres de la boucle de simulation peuvent être : le pas de simulation, la fréquence d’affichage et le temps de latence/réponse.

Définition : Pas de simulation

De la même manière que le pas de vis défini le déplacement d’une vis à chaque tour de tournevis, le pas de simulation définit l’écoulement du temps simulé à chaque tour de boucle de simulation. Dans une simulation informatique le temps d’une simulation est nécessairement discret. La pas de simulation définit cette discrétisation.

Exemple : Boucle

Boucle

Voici un exemple de boucle de simulation :

import time
timeStep=0.1 #pas=frequence=latence=timeStep
while True:
    t0=time.time()
    interact()
    live(timeStep)
    show()
    time.sleep(timeStep - (time.time()-t0))

Voici le même exemple dans lequel la boucle de simulation est encapsulée dans une fonction run():

Exemple : Boucle encapsulée

import time
timeStep=0.1 #pas=frequence=latence=timeStep
def init():
    pass #initialisation du jeu

def run():
    global timeStep
    while True:
        t0=time.time()
        interact()
        live(timeStep)
        show()
        time.sleep(timeStep - (time.time()-t0))

if __name__=="__main__":
    init()
    run()

La fonction interact() gère la récupération des événements du clavier et la fonction show() actualise l’affichage du jeu. Ces fonctions seront décrites dans la partie de cours : interface. La fonction live() fait “vivre” les éléments du jeu pendant un petit intervalle de temps.

Ainsi une boucle de simulation permet de gérer les propriétés suivantes:
  • le pas de simulation qui permet de régler la précision des calculs réalisés dans le jeu.
  • la fréquence d’affichage qui doit être inférieure à celle de l’écran (souvent 60Hz) et assez élevé pour le confort de l’utilisateur.
  • le temps de réponse/latence qui représente le temps maximum entre le moment ou un joueur appuie sur une touche et le moment ou cela se traduit par un effet dans le jeu.

C’est trois propriétés peuvent être identiques ou différentes selon la complexité de la boucle de simulation. Le réglage de ces propriétés permettent le compromis entre capacité de calcul et ergonomie du jeu. Les exercices suivants permettent de produire des boucles de simulations jouant sur ces propriétés.

  1. Boucle simple

    compléter la fonction run() pour obtenir la sortie suivante:

    5 :  interact show live
    4 :  interact show live
    3 :  interact show live
    2 :  interact show live
    1 :  interact show live
    
    Votre réponse :
    python : code_run414.py
    Sorties
    
                
    Commentaires

    ajouter une boucle et pensez à gérer une condition d’arrêt.

    Une solution possible :
    import time
    
    dt=0.2
    
    def show():
        print 'show',
    
    def interact():
        print 'interact',
    
    def live(dt):
        print 'live',
    
    def run(nbIterations):
        global dt
        while(nb_iterations):
            print nb_iterations,': ',
            interact()
            show()
            live(dt)
            nb_iterations=nb_iterations-1
            print ''
        return
    run(5)
    
  2. Boucle à différents pas de temps

    Les calculs de physique mis en œuvre par la fonction live() sont parfois assez sensibles. Cette fonction mérite parfois d’être appelée à une fréquence supérieure à celle de la fonction show(). Pouvez vous modifier le code de la boucle de simulation pour faire en sorte que la fonction show() soit appelée 5 fois moins souvent?

    Un programme correct devrait produire la sortie suivante:

    show live live live live live show live live live live live

    Votre réponse :
    python : code_run418.py
    Sorties
    
                
    Commentaires

    Vous pouvez utiliser une variable comme compteur d’itérations.

    Une solution possible :
    dt=0.2
    
    def run(nb_iterations):
        global dt
        cpt=0
        while(nb_iterations):
            if(cpt==0):
                show()
                cpt=5
            live(dt)
            time.sleep(dt)
            nb_iterations=nb_iterations-1
            cpt=cpt-1
        return
    
  3. Boucle à deux fréquences

    Soit un ordonnanceur ayant pour objet l’exécution de 2 actions act1(dt) et act2(dt) à des fréquences différentes. L’action 1 possède une fréquence de 0.5Hz et l’action 2 est réalisée à une fréquence de 0.333Hz. Coder la boucle de simulation correspondante.

    Un programme correct devrait produire une sortie de type:
    0.0s  act1
    0.0s  act2
    2.0s  act1
    3.0s  act2
    4.0s  act1
    6.0s  act1
    6.0s  act2
    8.0s  act1
    9.0s  act2
    
    Votre réponse :
    python : code_run422.py
    Sorties
    
                
    Commentaires

    Il est possible d’utiliser des variables pour planifier les prochaines actions à réaliser. La boucle de simulation a alors pour but d’attendre la prochaine action planifiée, de l’exécuter et de la replanifier.

    Une solution possible :
    act_1_frequency=0.5
    act_2_frequency=0.333
    
    def run(stopTime):
        global act_1_frequency, act_2_frequency
    
        t=time.time()
    
        #action=[date,fonction,pas de temps]
        action_1=[t,act_1,1.0/act_1_frequency]
        action_2=[t,act_2,1.0/act_2_frequency]
    
        #liste des actions plannifiees
        actions=[action_1,action_2]
        while(t<stopTime):
            t=time.time()
            nextTime=time.time()+1.0
            #realisation des actions correspondant au temps courant
            for a in actions:
                if a[0]<=t:
                    #realisation de l action
                    a[1](a[2])
                    #planifictaion de la prochaine occurence
                    a[0]=a[0]+a[2]
                #recherche de la prochaine action
                if a[0]<nextTime:
                    nextTime=a[0]
            t=time.time()
            time.sleep(nextTime-t)
        return
    
  4. Animation Turtle avec une courbe paramétrique

    Soient x et y les coordonnées d’un solide en mouvement sur une trajectoire circulaire:

    \(\left\{ \begin{array}{l} \displaystyle x(t) = R.cos(t) \\ \displaystyle y(t) = R.sin(t) \\ \end{array} \right.\)

    Votre réponse :
    python : code_run426.py
    Sorties
    
                
    Commentaires

    Faire une boucle de simulation pour simuler l’évolution de t de 0 à au moins 2 PI.

    Une solution possible :
    import time
    import turtle
    import math
    
    tu = turtle.Turtle()
    tu.shape('circle')
    
    t=0.0
    dt=0.01
    r=100.0
    mobile={'x':r,'y':0.0}
    
    def run():
        global tu, t, mobile, dt, r
        while t<6.3:
            mobile['x']= r * math.cos(t)
            mobile['y']= r * math.sin(t)
            tu.goto(mobile['x'],mobile['y'])
            t=t+dt
            time.sleep(dt)
    run()
    

Résolution d’équations différentielles

Il est possible d’utiliser une boucle de simulation pour simuler le déplacement d’un objet au cours du temps. L’idée est de réaliser un petit déplacement de l’objet en question à chaque tour de boucle. Si le pas de temps est suffisamment petit, l’utilisateur aura l’illusion d’observer un objet se déplaçant de manière continue.

Pour rendre le déplacement de l’objet réaliste on peut assez facilement lui appliquer des lois physiques ou mécaniques. Par exemple, on peut doter l’objet d’une vitesse et appliquer cette vitesse à chaque petit déplacement en fonction du pas de temps choisi.

En faisant cela, à chaque passage dans la boucle de simulation on réalise en fait un calcul d’intégration d’équations différentielles sur un pas de temps, en utilisant une méthode de résolution. Cette méthode de résolution se nomme méthode d’Euler explicite.

Soient x et y les coordonnées d’un solide en mouvement:
\(\left\{ \begin{array}{l} \displaystyle\frac {dx}{dt} = v_x\\[8pt] \displaystyle\frac {dy}{dt} = v_y\\ \end{array} \right.\)
On peut calculer la postion à l’instant t en fonction de la position précédente:

\(x_t=x_{t-1} + v_x * dt\)

\(y_t=y_{t-1} + v_y * dt\)

Il est très facile de traduire ces équations en algorithme:

while True:
    position[0]=position[0] + (dt * vitesse[0])
    position[1]=position[1] + (dt * vitesse[1])

Ici, votre formation d’ingénieur généraliste va pouvoir vous servir. Vous pouvez traduire les différentes lois dynamiques abordées en mécanique ou en électronique en algorithme décrivant le comportement d’objets de votre jeu. Par exemple, la charge d’un condensateur ou le déplacement d’un mobile s’exprime avec des équations différentielles. Cela vous permet d’être créatif tout en proposant rapidement des comportements réalistes. Par ailleurs vous n’avez pas à vous soucier de résoudre le système d’équations différentielles, vous devez simplement le simuler. Vous devez par contre vous assurer que le pas de temps choisi pour les calculs est assez petit pour garantir une précision des calculs adaptée au contexte. Bien sûr, lorsque les lois se compliquent il faut faire appelle à des outils de résolution plus efficaces. Le cours de méthodes numériques de S5 vous présentera quelques éléments à ce sujet. En attendant, la méthode d’Euler qui vous est donnée ici permet déjà beaucoup de choses.

Quand utiliser une résolution d'équations différentielles?

”...la balle se déplace à une certaine vitesse...”

”...les boulets de canon ont une vitesse initiale et sont soumis à la gravitée...”

Exemple : Alcootest

python : code_run430.py
Sorties

            

  1. Une simple balle

    Coder la fonction live() qui simule le déplacement de la balle b.

    Votre réponse :
    python : code_run433.py
    Sorties
    
                
    Commentaires

    Appliquer la méthode d’Euler

    Une solution possible :
    def live(dt):
        global b
    
        b['x']=b['x']+dt*b['vx']
        b['y']=b['y']+dt*b['vy']
    
        return
    
  2. Boulet de canon avec gravité

    Coder la fonction live() qui simule le déplacement d’un boulet de canon cb

    Votre réponse :
    python : code_run437.py
    Sorties
    
                
    Commentaires

    Appliquer la méthode d’Euler

    Une solution possible :
    def live(dt):
        global cb
        cb['vx']=cb['vx']+dt*cb['ax']
        cb['vy']=cb['vy']+dt*cb['ay']
        cb['x']=cb['x']+dt*cb['vx']
        cb['y']=cb['y']+dt*cb['vy']
        return
    
  3. Boulet de canon avec gravité et force du vent.

    La vitesse horizontale du boulet de canon est influencée par le vent.

    \(\Delta_{\overset{•}x}=\overset{•}x_{boulet} - \overset{•}x_{vent}\\ \overset{••}x_{boulet} = \Delta_{\overset{•}x} . |\Delta_\overset{•}x| . k_{vent}\)

    \(k_{vent}\) est un coefficient global qui dépend en fait de la masse du boulet, de la pénétration dans l’air et de la masse volumique de l’air.

    Coder la fonction live() qui simule le déplacement d’un boulet de canon cb

    Votre réponse :
    python : code_run441.py
    Sorties
    
                
    Commentaires

    Appliquer la méthode d’Euler

    Une solution possible :
    def live(dt):
    
        deltaV=cb['vx']-wind
        cb['ax']= -abs(deltaV)*deltaV*k_wind
        cb['vx']=cb['vx']+dt*cb['ax']
        cb['vy']=cb['vy']+dt*cb['ay']
        cb['x']=cb['x']+dt*cb['vx']
        cb['y']=cb['y']+dt*cb['vy']
    
        return
    

Machine à états

Définition : Machine à états finis

Une machine à états finis peut être vue comme un graphe auquel on associe les sommets à des états et les liens à des transitions. De plus, la machine à états possède un état courant.

Nous savons mettre en œuvre une boucle de simulation et déplacer les objets de notre jeu selon une des lois physiques ou plus ou moins réalistes. Maintenant, comment pourrions nous associer plusieurs loi de déplacement à un même objet et sélectionner une en particulier selon le contexte du jeu? Par Exemple, un ennemi dans l’état “normal” possède une vitesse de déplacement constante et dans l’état “en colère”, se déplace de manière aléatoire. Il faut pour cela conserver en mémoire une information concernant l’état de la donnée pour laquelle on souhaite définir le comportement.

Le fait de conserver en mémoire l’état d’un élément d’un tour à l’autre de la boucle de simulation, le fait d’associer des actions différentes à chaque état et des conditions faisant évoluer l’état

Le Grafcet est aussi une machine à état.

Quand utiliser une machine à état?
  • ”...Les monstres se déplacent aléatoirement. Dès qu’ils repèrent le joueur, ils se dirigent dans sa direction...”
  • ”...Le jeux reprend le principe des livres dont vous êtes le héros...”
En travaux :

En travaux certes! mais étudiez quand même les codes suivants.

machine minimaliste

state="a"
t=0
#boucle de simulation
while state!="exit":
    if (state=="a"):
        if t > 10 :
            state ="b"
    elif (state=="b"):
            state ="c"
    elif (state=="c"):
        if t > 100 :
            state ="exit"
    print state
    t=t+1
    print ("fin de boucle")

machine complexe
...