Développement
Programmation de l'IA
- Documentation API
- Introduction
- Fichier info.nut
- Fichier main.nut
- Principes de base
- Détermination de chemin routier
- Détermination de chemin ferroviaire
- Sauver / Charger des données
- Choses à savoir
- Squirrel
- Listes
- Traiter les erreurs OTTD
- Tramways
- Bibliothèques de l'IA
- Forum
- FAQ du forum
- Comportement de l'IA
IAs
Tous les articles de la catégorie NonIA
Contents |
Avantages
Si vous avez essayez de construire par vous-mêmes une détermination de chemin routier, vous avez remarqué qu'il était difficile de le faire correctement. Principalement, les angles des fondations sont un problème. Utiliser la détermination de chemin depuis la bibliothèque résoudra ce problème pour vous, afin que vous n'ayez pas à faire le travail vous-mêmes.
Initialisation
Comme avec ce genre de bibliothèque, avant de l'utiliser, vous devez l'importer. Vous pouvez le faire en plaçant la ligne suivante au début de votre code.
import("pathfinder.road", "RoadPathFinder", 3);
Cela importe la détermination de chemin routier version 3 et l'appelle RoadPathFinder
. Vous pouvez maintenant l'instancier comme toute autre classe:
local pathfinder = RoadPathFinder();
Maintenant, fixez pour quel type de route vous voulez trouver un chemin:
AIRoad.SetCurrentRoadType(AIRoad.ROADTYPE_ROAD);
Avant de commencer à rechercher un chemin, vous voudrez peut-être positionner quelques paramètres. Ceux qui sont disponibles sont:
Nom | Valeur par défaut | Signification |
---|---|---|
cost.max_cost | 2000000000 | Le coût maximal pour une route. |
cost.tile | 100 | Le coût pour une seule case. |
cost.no_existing_road | 40 | Le coût ajouté à cost.tile si la route n'existe pas encore. |
cost.turn | 100 | Le coût ajouté à cost.tile si la direction est modifiée. |
cost.slope | 200 | Le coût supplémentaire si une case de route est en pente. |
cost.bridge_per_tile | 150 | Le coût par case d'un nouveau pont; il est ajouté à cost.tile. |
cost.tunnel_per_tile | 120 | Le coût par case d'un nouveau tunnel; il est ajouté à cost.tile. |
cost.coast | 20 | Le coût supplémentaire d'une case côtière. |
cost.max_bridge_length | 10 | La longueur maximale d'un pont qui peut être construite. Remarquez que tous les ponts existants seront explorés, quelle que soit leur longueur. |
cost.max_tunnel_length | 20 | La longueur maximale d'un tunnel qui peut être construite. Remarquez que tous les tunnels existants seront explorés, quelle que soit leur longueur. |
Pour positionner un paramètre, utilisez simplement:
pathfinder.cost.tile = 100;
Planifier un trajet
Donc, maintenant que nous savons comment créer une instance de détermination de chemin et paramétrer les fonctions de coût, il est temps de mettre cette connaissance en pratique. Planifions notre premier trajet. Tout d'abord, nous avons besoin d'une (ou plusieurs) case(s) de ressource et d'une (ou plusieurs) case(s) cible. Pour le moment, supposons que nous les connaissons déjà, car expliquer comment les obtenir dépasse le cadre de cette page. Disons donc, par exemple, que nous voulons construire une route entre case_a et case_b. Tout d'abord, nous appelons
InitializePath
sur notre objet de détermination de chemin:
pathfinder.InitializePath([case_a], [case_b]);
La détermination de chemin attend un tableau de cases de départ et un tableau de cases de destination; comme nous n'avons qu'une case de départ et une de destination, nous créons simplement deux tableaux avec un seul élément pour chacun. Maintenant, la détermination de chemin est prête à trouver un chemin.
local path = false; while (path == false) { path = pathfinder.FindPath(100); AIController.Sleep(1); }
Le paramètre que FindPath
accepte est le nombre d'itérations qu'il doit exécuter avant de répondre. 100 convient bien. Ne donnez pas une trop grande valeur, car vous noterez des ralentissements dans OpenTTD. La ligne AIController.Sleep(1);
est vraiment nécessaire. FindPath
renvoie false s'il n'a pas terminé avec la détermination de chemin. Une fois fini, il renvoie soit le chemin trouvé, ou null pour indiquer qu'aucune route n'existe.
Construire la route planifiée
Supposons que la détermination de chemin a renvoyé un trajet, comment commencer à le construire? Pour démarrer, il est intéressant de savoir que FindPath
renvoie une instance de la classe AyStar.Path
. Pour voir la documentation, ouvrez bin/ai/library/graph/aystar/main.nut
. Sommairement, il comporte trois fonctions: GetParent()
, GetCost()
et GetTile()
. GetCost()
N'est pas utile pour le moment, donc nous n'utiliserons que GetParent()
et GetTile()
. Pour construire une route trouvée par la détermination de chemin, nous appelons path.GetParent()
jusqu'à ce qu'il renvoie null. Comme la détermination de chemin renvoie aussi des ponts et des tunnels, nous devons vérifier la distance entre le nœud actuel et le précédent. Si cette distance est supérieure à 1, nous devons construire un pont ou un tunnel.
while (path != null) { local par = path.GetParent(); if (par != null) { local last_node = path.GetTile(); if (AIMap.DistanceManhattan(path.GetTile(), par.GetTile()) == 1 ) { if (!AIRoad.BuildRoad(path.GetTile(), par.GetTile())) { /* Une erreur est survenue lors de la construction d'une portion de route. À FAIRE: la gérer. * Notez que ce peut aussi être le cas si la route était déjà construite. */ } } else { /* Construire un pont ou un tunnel. */ if (!AIBridge.IsBridgeTile(path.GetTile()) && !AITunnel.IsTunnelTile(path.GetTile())) { /* Si c'était une case de route, la démolit d'abord. À faire pour contourner des bouts de route utilisés. */ if (AIRoad.IsRoadTile(path.GetTile())) AITile.DemolishTile(path.GetTile()); if (AITunnel.GetOtherTunnelEnd(path.GetTile()) == par.GetTile()) { if (!AITunnel.BuildTunnel(AIVehicle.VT_ROAD, path.GetTile())) { /* Une erreur s'est produite lors de la construction d'un tunnel. À FAIRE: la gérer. */ } } else { local bridge_list = AIBridgeList_Length(AIMap.DistanceManhattan(path.GetTile(), par.GetTile()) + 1); bridge_list.Valuate(AIBridge.GetMaxSpeed); bridge_list.Sort(AIAbstractList.SORT_BY_VALUE, false); if (!AIBridge.BuildBridge(AIVehicle.VT_ROAD, bridge_list.Begin(), path.GetTile(), par.GetTile())) { /* Une erreur s'est produite lors de la construction d'un pont. À FAIRE: la gérer. */ } } } } } path = par; }
Ce code boucle tout le long de la route. Pour chaque élément, il vérifie s'il est déjà connecté à la case précédente par route ou via un pont. S'il ne l'est pas, il tente de construire une route. Remarquez que dans une vraie IA, vous vérifierez probablement si AIRoad.BuildRoad
renvoie true, et si non, vous relancerez la détermination de chemin.
Note: n'utilisez pas parent comme nom de variable pour GetParent
, c'est un mot-clé, donnant des résultats étranges.
Une IA exemple
import("pathfinder.road", "RoadPathFinder", 3); class SampleAI extends AIController { } function SampleAI::Start() { /* Obtient une liste de toutes les villes de la carte. */ local townlist = AITownList(); /* Trie la liste par population, les plus importantes en premier. */ townlist.Valuate(AITown.GetPopulation); townlist.Sort(AIAbstractList.SORT_BY_VALUE, false); /* Choisit les deux villes avec la plus grande population. */ local townid_a = townlist.Begin(); local townid_b = townlist.Next(); /* Affiche le nom des villes que nous allons essayer de relier. */ AILog.Info("En train de relier " + AITown.GetName(townid_a) + " à " + AITown.GetName(townid_b)); /* Dit à OpenTTD que nous voulons construire une route normale (pas une voie de tramway). */ AIRoad.SetCurrentRoadType(AIRoad.ROADTYPE_ROAD); /* Crée une instance de détermination de chemin. */ local pathfinder = RoadPathFinder(); /* Positionne le coût d'un virage très haut. */ pathfinder.cost.turn = 5000; /* Donne les cases de départ et de destination à la détermination de chemin. */ pathfinder.InitializePath([AITown.GetLocation(townid_a)], [AITown.GetLocation(townid_b)]); /* Tente de trouver un trajet. */ local path = false; while (path == false) { path = pathfinder.FindPath(100); this.Sleep(1); } if (path == null) { /* Pas de trajet trouvé. */ AILog.Error("pathfinder.FindPath a renvoyé null"); } /* Si un chemin a été trouvé, construit une route le long de celui-ci. */ while (path != null) { local par = path.GetParent(); if (par != null) { local last_node = path.GetTile(); if (AIMap.DistanceManhattan(path.GetTile(), par.GetTile()) == 1 ) { if (!AIRoad.BuildRoad(path.GetTile(), par.GetTile())) { /* Une erreur s'est produite en construisant une portion de route. À FAIRE: la gérer. * Remarquez que ce peut être aussi le cas quand la route est déjà construite. */ } } else { /* Construit un pont ou un tunnel. */ if (!AIBridge.IsBridgeTile(path.GetTile()) && !AITunnel.IsTunnelTile(path.GetTile())) { /* S'il y avait une case de route, la démolit d'abord. Faire ainsi contourne une portion de voie utilisée. */ if (AIRoad.IsRoadTile(path.GetTile())) AITile.DemolishTile(path.GetTile()); if (AITunnel.GetOtherTunnelEnd(path.GetTile()) == par.GetTile()) { if (!AITunnel.BuildTunnel(AIVehicle.VT_ROAD, path.GetTile())) { /* Une erreur est survenue en construisant un tunnel. À FAIRE: la traiter. */ } } else { local bridge_list = AIBridgeList_Length(AIMap.DistanceManhattan(path.GetTile(), par.GetTile()) + 1); bridge_list.Valuate(AIBridge.GetMaxSpeed); bridge_list.Sort(AIAbstractList.SORT_BY_VALUE, false); if (!AIBridge.BuildBridge(AIVehicle.VT_ROAD, bridge_list.Begin(), path.GetTile(), par.GetTile())) { /* Une erreur est survenue en construisant un pont. À FAIRE: la traiter. */ } } } } } path = par; } AILog.Info("Terminé"); }
Créez un info.nut
pour cette IA et essayez-la. Faites varier les différents coûts, par exemple en mettant pathfinder.cost.no_existing_road = pathfinder.cost.max_cost
. Maintenant, la détermination de chemin ne trouvera que des routes sur des routes déjà existantes. Ce n'est pas très utile si vous voulez construire une nouvelle liaison, mais ce peut être pratique si vous voulez vérifier si une liaison précédemment construite est toujours OK.