1 of 26

2 of 26

Pesto, en résumé

2 applications: ExploRobots (Pastagames) et Wild (Wild Sheep Studio).

Pesto est un langage de scripts, dédié au développement de comportements des PNJ dans les jeux vidéo.

Les étapes de l’exposé

Il intègre primitivement le principe de continuation avec l’instruction yield et l’instruction exec.

Il introduit de nouvelles constructions (do/onResume).

Il permet d’implémenter simplement des Automates à états (finally).

3 of 26

Problématique

Perception

Mémoire

(Connaissances)

Comportement du PNJ

Actions

Le Monde

Lisible pour le joueur

Contrôlé (niveau de difficulté…)

4 of 26

Pesto

La continuation (yield)

script approach(Spear spear)

{

float MOVE_DIST = 2.0;

float THRESHOLD = 10.0;

Vec2 goal = spear.pos;

while ( distance[ pos, goal ] > THRESHOLD )

{

Vec2 delta = mul[ MOVE_DIST,

normalized[ goal - pos]];

move( delta ); // appel procédure C++

yield;

}

}

C++, boucle du jeu

Script Pesto

Frame 1

Frame 3

Frame 2

Frame 4

Frame 5

5 of 26

Pesto

Scripts & instructions

class PNJ

{

script equipSpear(Spear spear)

{ … approachSpear(spear ); … }

script killTroll()

{ … equipSpear(); … }

// called from C++

script findPrincess()

{

Horse horse;

exec findHorse() -> (horse);

exec rideHorse(horse);

exec killTroll();

exec savePrincess();

}

}

  • Le script
    • dans une classe
    • séquence d’instructions
    • instructions de contrôle (if, while),
    • appel de procédures, affectation de variables…
  • L’instruction exec
    • exécution d’un sous-script

Trouver la princesse

Chercher un cheval

Monter le cheval

Tuer le troll

Sauver la princesse

6 of 26

Pesto

Scripts

Connaissances

Primitives (Perceptions)

Actions primitives

Connaissances

C++

Script Pesto

modifient

Evénements extérieurs

Le Monde

Arggg !

J’aime quand un plan se déroule sans accroc.

7 of 26

Pesto

Do-onResume (la supervision) : la force de Pesto

do

{

< 1- actions supervisées >

}

onResume

{

< 2- actions de supervision >

}

  • Un yield est déclenché dans < 1 >.
  • La main est rendue au C++.
  • Le retour dans le script se fait dans le onResume. < 2 > est exécuté d’abord.
  • Le déroulement normal reprend après le yield déclencheur dans < 1 >.

8 of 26

Pesto, do-onResume

script findPrincess()

{

Horse horse;

exec findHorse() -> (horse);

do

{

exec rideHorse(horse);

exec killTroll();

}

onResume

{

if (isUnhorsed)

{

retry;

}

}

exec savePrincess();

}

Interrompre & recommencer un script

Trouver la princesse

Chercher un cheval

Monter le cheval

Tuer le troll

Sauver la princesse

Désarçonné du cheval

9 of 26

Pesto, do-onResume

script findPrincess()

{

Horse horse;

do {

exec findHorse() -> (horse);

do {

exec rideHorse(horse);

exec killTroll();

}

onResume {

if (isUnhorsed)

{ retry; }

}

}

onResume {

if (horse != Horse#null and horse.isDead)

{ horse = Horse#null; retry; }

}

exec savePrincess();

}

do-onResume imbriqués

on reprend en partant du onResume le plus haut.

Trouver la princesse

Chercher un cheval

Monter le cheval

Tuer le troll

Sauver la princesse

Désarçonné du cheval

Mort du cheval

10 of 26

Problématique

Automate à états finis

  • Extension de la séquentialité
    • Plus générale
    • Plus flexible
    • Écriture plus lourde (surtout texte, id d’état, id de super-états...)
  • Repose sur l’interruption
    • Changement d’état

Combattre

Attaquer au corps à corps

Se cacher

Fuir

Attaquer à distance

11 of 26

Pesto

Automate à états finis

script combattre()

{

int strategie;

while (strategie != STRAT_ARRETER_COMBAT)

{

strategie= choisirStrategie();

if (strategie = STRAT_FUIR) { exec fuir(); }

if (strategie = STRAT_ATTAQUER_AU_CORPS_A_CORPS) { exec attaquerAuCorpsACorps(); }

if (strategie = STRAT_ATTAQUER_A_DISTANCE) { exec attaquerADistance(); }

if (strategie = STRAT_SE_CACHER) { exec seCacher(); }

}

}

12 of 26

Pesto & FSM

Un état

script attaquerADistance()

{

exec sortirArc(); // Initialisation

do {

while (nbFleches = 0)

{ // exécution

exec preparerFleche();

exec viser( cible );

exec tirer();

}

} finally

{ // finalisation

rentrerArc();

} onResume

{ // conditions d’arrêt

if (cible.isDead) { return; }

}

}

finally { <instructions> }

Exécutées quoi qu’il arrive

Même si le script est interrompu par l’appelant (break, retry, return)

Libération des ressources allouées pour le script

13 of 26

Pesto, FSM

Interruptions des états

script combattre()

{

int strategie = STRAT_AUCUNE;

while (true)

{

do {

if (strategie = STRAT_AUCUNE) { srategie = choisirStrategie(); }

if (strategie = STRAT_FUIR) { exec fuir(); }

if (strategie = STRAT_ATTAQUER_AU_CORPS_A_CORPS) { exec attaquerAuCorpsACorps(); }

if (strategie = STRAT_ATTAQUER_A_DISTANCE) { exec attaquerADistance(); }

if (strategie = STRAT_SE_CACHER) { exec seCacher(); }

strategie = STRAT_AUCUNE;

} onResume {

if ( vie < 5 ) { strategie= STRAT_FUIR; break; }

if ( distanceEnnemi > 100 ) { return; }

}

}

}

14 of 26

Pesto, do-onResume

script execute()

{

do

{

exec findPrincess();

}

onResume

{

if (isPrincessDead)

{

exec lament();

break;

}

if (isHungry)

{

exec eat();

}

}

}

Interruption d’un script par un autre script

dans le onResume

    • Exécution d’un script temporaire
    • Suivi d’un éventuel break

Trouver la princesse

Se lamenter

Mort de princesse

manger

Avoir faim

15 of 26

Pesto, do-onResume

Auto-adaptation

script follow(Ennemi ennemi)

{

Vec2 destination = ennemi.position;

findPath(destination);

do

{

while (true)

{ followPath(); yield; }

}

finally

{

releasePath();

}

onResume

{

if (norm[destination - ennemi.position] > 10)

{

destination = ennemi.position;

releasePath();

findPath(destination);

}

}

}

Le script follow(Ennemi)

L’ennemi est mouvant.

La supervision permet de re-déclencher le path finding quand nécessaire

16 of 26

Pesto, do-onResume

Time-out

script followDuring(Ennemi ennemi, float timeout)

{

float startTime = currentTime;

do

{

follow( ennemi );

}

onResume

{

if (currentTime - startTime > timeout)

{

return;

}

}

}

Ajouter un time-out à un script existant

En ré-utilisant ce script

Sans le réécrire

17 of 26

Pesto

L’instruction “as” (à venir)

script derouleJournee()

{

while (true) {

as ( curTime < time[8,00]) { exec Dormir(); }

as ( heureCourante < temps[10,00]) { exec SOccuperDesEnfants(); }

as( heureCourante < temps[12,00]) { exec PreparerLeRepas(); }

as ( heureCourante < temps[14,00]) { exec Dejeuner(); }

as ( heureCourante >= temps[20,00]) { exec Dormir(); }

}

}

as (<condition>) { <instructions> }

do { while (condition) { <instructions> } } onResume { if (not <condition>) { break; } }

Un emploi du temps

18 of 26

Pesto

Les outils, l’implémentation

Le compilateur

  • Un script Pesto ⟶ une classe C++
    • une méthode execute
    • La classe est un automate à état (state = id de l’instruction en cours)
    • Les variables ⟶ champs
    • Appel à un sous-script = instanciation d’un sous-automate à état (autre classe c++)
    • Utilisation d’une resume stack pour gérer l’ordre d’exécution des onResume, onYield, finally
  • Le code obtenu est proche du script Pesto
    • Lisible
    • debuggable

19 of 26

PESTO

C++

20 of 26

Pesto, outils et implémentation

Extraction de l’automate à états à partir des scripts Pesto

    • Création d’un fichier texte définissant le graphe
    • Utilisation GraphViz (vieux projet MIT) pour générer un png

21 of 26

Pesto, les outils, l’implémentation

L’interpréteur

  • Mini IDE basé sur ImGui (https://github.com/ocornut/imgui)
  • Le do-onResume n’utilise qu’une seule stack,
    • Mais effectue des déplacements des data dans cette stack.
  • Utilisation d’une “resume stack” pour gérer l’ordre d’exécution des onResume, finally
  • Hot reload de scripts

22 of 26

Pesto

D‘autres trucs

  • Classe / Objet
    • Champs, Fonction, Procédure, script
    • Utilisation d’un pool d’objets
    • Manipulation des objets par des refs
    • Référence null typée ( Creature#null )
  • Struct
    • Manipulation par valeur sur la stack ou comme champs
    • Passage de paramètre par référence
  • Les classes externes
    • L’allocation, l’itération et la représentation interne et celle du pointeur sont gérées par le C++

23 of 26

Pesto, d’autres trucs

  • for ( creature : Creature; creature.isDangerous ) { … }
    • itération sur tous le pool d’objets d’une classe
    • pas de liste
  • do { } onYield { }
    • exécution des onYield avant de rendre la main au c++
    • du bas vers le haut si imbrication des onYield
  • do for ()
    • = parallelFor
  • Syntaxes : regroupement fonction/champs

if (isDead[creature]) { … }

field bool isDead [Creature creature]

func bool isDead [Creature creature]

24 of 26

Pesto

Conclusion

  • Evolution progressive du code
    • Procedure -> script
    • Séquence d’instructions -> automate à états
    • Séquence d’instructions -> emploi du temps
    • Ajout de condition d’interruptions
  • Lisibilité, concision
    • Pas de code technique
  • Modularité & réutilisabilité
    • Grâce au do-onResume
  • Performant
    • Grâce au compilateur
  • Sérialisation possible de l’état d’un pnj
    • Pour la persistence
    • Transmission réseau -> multi

http://www.pastagames.com

fabrice@pastagames.net

25 of 26

Pesto, conclusion

  • Inconvénients
    • Encore des trous d’implem, constructions manquantes (switch/case)
    • Une syntaxe à améliorer (elseif)
    • IDE encore trop sommaire
  • Retour d’XP
    • Le compilateur est nécessaire.
    • Et c’est beaucoup plus facile et plus léger à coder !
    • ⇒ commencer par le compilateur.

http://www.pastagames.com

fabrice@pastagames.net

26 of 26

Se passer de Pesto ?

  • En C++ (langage performant),
    • mélange code technique / fonctionnel
    • automates à micro-états gigantesques
    • Beaucoup de classes, beaucoup de code...

une complexité difficile à assumer

  • Lua
    • Continuation = la co-routine = technique
    • interprété : pas aussi efficace que C++
    • pas typé, pas de classe, peu structurant
    • La supervision est possible mais pas lisible