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).
Problématique
Perception
Mémoire
(Connaissances)
Comportement du PNJ
Actions
Le Monde
Lisible pour le joueur
Contrôlé (niveau de difficulté…)
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
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();
}
}
Trouver la princesse
Chercher un cheval
Monter le cheval
Tuer le troll
Sauver la princesse
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.
Pesto
Do-onResume (la supervision) : la force de Pesto
do
{
< 1- actions supervisées >
}
onResume
{
< 2- actions de supervision >
}
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
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
Problématique
Automate à états finis
Combattre
Attaquer au corps à corps
Se cacher
Fuir
Attaquer à distance
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(); }
}
}
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
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; }
}
}
}
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
Trouver la princesse
Se lamenter
Mort de princesse
manger
Avoir faim
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
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
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
Pesto
Les outils, l’implémentation
Le compilateur
PESTO
C++
Pesto, outils et implémentation
Extraction de l’automate à états à partir des scripts Pesto
Pesto, les outils, l’implémentation
L’interpréteur
Pesto
D‘autres trucs
Pesto, d’autres trucs
if (isDead[creature]) { … }
field bool isDead [Creature creature]
func bool isDead [Creature creature]
Pesto
Conclusion
http://www.pastagames.com
fabrice@pastagames.net
Pesto, conclusion
http://www.pastagames.com
fabrice@pastagames.net
Se passer de Pesto ?
une complexité difficile à assumer