Rozwój
Programowanie SI
- Dokumentacja API
- Wprowadzenie
- plik info.nut
- plik main.nut
- Podstawy
- Poszukiwacz drogi
- Użycie pathfinder`a kolei
- Zapis / Odczyt danych
- Dobrze wiedzieć
- Squirrel
- Listy
- Poradnik na błędy OTTD
- Trams
- Biblioteki SI
- Forum
- Forum FAQ
- Zachowanie SI
- Użycie krótkich nazw
SIy
Wszystko kategorii NoAI
Contents |
Korzyści
Jeśli próbowałeś samodzielnie zbudować urządzenie do śledzenia drogi, być może zauważyłeś, że trudno go poprawnie uzyskać. Problem stanowią głównie narożniki fundamentów. Użycie pathfindera z biblioteki rozwiąże to za Ciebie, więc nie musisz sam wykonywać ciężkiej pracy.
Inicjalizacja
Podobnie jak w przypadku każdej biblioteki, zanim będzie można jej użyć, należy ją zaimportować. Możesz to zrobić, umieszczając następujący wiersz w górnej części kodu.
import("pathfinder.road", "RoadPathFinder", 3);
Spowoduje to zaimportowanie pathfinder wersji 3 i nadanie mu nazwy RoadPathFinder
. Możesz teraz tworzyć instancje jak każdą inną klasę:
local pathfinder = RoadPathFinder();
Teraz ustaw rodzaj drogi, dla której chcesz znaleźć ścieżkę:
AIRoad.SetCurrentRoadType(AIRoad.ROADTYPE_ROAD);
Zanim zaczniesz znajdować trasę, możesz najpierw ustawić niektóre parametry. Dostępne parametry to:
Nazwa | Domyślna wartość | Znaczenie |
---|---|---|
cost.max_cost | 2000000000 | Maksymalny koszt trasy. |
cost.tile | 100 | Koszt pojedynczego kafla. |
cost.no_existing_road | 40 | Koszt dodawany do _cost_tile, jeśli nie ma jeszcze drogi. |
cost.turn | 100 | Koszt dodawany do _cost_tile, jeśli kierunek się zmieni. |
cost.slope | 200 | Dodatkowy koszt, jeśli dachówka jest nachylona. |
cost.bridge_per_tile | 150 | Koszt za kafel nowego mostu jest dodawany do _cost_tile. |
cost.tunnel_per_tile | 120 | Koszt na kafel nowego tunelu jest dodawany do _cost_tile. |
cost.coast | 20 | Dodatkowy koszt za kafel wybrzeża. |
cost.max_bridge_length | 10 | Maksymalna długość mostu, który zostanie zbudowany. Pamiętaj, że wszystkie istniejące mosty zostaną zbadane, niezależnie od ich długości. |
cost.max_tunnel_length | 20 | Maksymalna długość tunelu, który zostanie zbudowany. Pamiętaj, że wszystkie istniejące tunele zostaną zbadane, niezależnie od ich długości. |
Aby ustawić parametr, wystarczy użyć:
pathfinder.cost.tile = 100;
Planowanie trasy
Tak więc, teraz wiemy, jak stworzyć instancję pathfindera i ustawić funkcje kosztów, nadszedł czas, aby tę wiedzę wykorzystać. Zaplanujmy naszą pierwszą drogę. Najpierw potrzebujemy jednego (lub więcej) kafli źródła i jednego (lub więcej) kafli celu. Na razie załóżmy, że już je mamy, ponieważ wykraczanie poza zakres tej strony wyjaśnia, jak je zdobyć. Powiedzmy na przykład, że chcemy zbudować drogę między tile_a a tile_b. Najpierw wywołujemy InitializePath
na naszym obiekcie pathfinder:
pathfinder.InitializePath([tile_a], [tile_b]);
Pathfinder oczekuje tablicy kafli źródłowych i tablicy kafli bramkowych, ponieważ mamy tylko jedno źródło i jeden kafel bramkowy, po prostu utworzymy dwie tablice z jednym elementem. Teraz pathfinder jest gotowy do znalezienia ścieżki.
local path = false; while (path == false) { path = pathfinder.FindPath(100); AIController.Sleep(1); }
Parametr FindPath
accepts to liczba iteracji, które powinien wykonać przed zwróceniem. 100 jest w porządku. Nie zwiększaj tej wartości, bo zauważysz spowolnienia w OpenTTD. Wiersz AIController.Sleep(1);
jest naprawdę potrzebny. FindPath
zwraca false, jeśli nie jest zakończone wyszukiwaniem ścieżek. Po zakończeniu zwraca albo znalezioną ścieżkę, albo zero, aby wskazać, że nie istnieje żadna trasa.
Budowanie zaplanowanej trasy
Załóżmy, że pathfinder zwrócił ścieżkę, jak zacząć ją budować? Na początek miło jest wiedzieć, że FindPath
zwraca instancję klasy AyStar.Path
. Aby wyświetlić dokumentację, otwórz bin/ai/library/graph/aystar/main.nut
. Zasadniczo ma trzy funkcje: GetParent()
, GetCost()
i GetTile()
. GetCost()
na razie nie jest użyteczny, więc użyjemy po prostu GetParent()
i GetTile()
. Aby zbudować trasę znalezioną przez pathfinder, wywołujemy path.GetParent()
, aż zwróci null. Ponieważ pathfinder może również zwracać mosty / tunele, musimy sprawdzić odległość między bieżącym węzłem a poprzednim węzłem. Jeśli ta odległość jest większa niż 1, powinniśmy zbudować most / tunel .
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())) { /* An error occurred while building a piece of road. TODO: handle it. * Note that this could mean the road was already built. */ } } else { /* Build a bridge or tunnel. */ if (!AIBridge.IsBridgeTile(path.GetTile()) && !AITunnel.IsTunnelTile(path.GetTile())) { /* If it was a road tile, demolish it first. Do this to work around expended roadbits. */ if (AIRoad.IsRoadTile(path.GetTile())) AITile.DemolishTile(path.GetTile()); if (AITunnel.GetOtherTunnelEnd(path.GetTile()) == par.GetTile()) { if (!AITunnel.BuildTunnel(AIVehicle.VT_ROAD, path.GetTile())) { /* An error occured while building a tunnel. TODO: handle it. */ } } else { local bridge_list = AIBridgeList_Length(AIMap.DistanceManhattan(path.GetTile(), par.GetTile()) + 1); bridge_list.Valuate(AIBridge.GetMaxSpeed); bridge_list.Sort(AIList.SORT_BY_VALUE, false); if (!AIBridge.BuildBridge(AIVehicle.VT_ROAD, bridge_list.Begin(), path.GetTile(), par.GetTile())) { /* An error occured while building a bridge. TODO: handle it. */ } } } } } path = par; }
Ten kod zapętla się na całej trasie. Dla każdego elementu sprawdza, czy jest już połączony z poprzednim kaflem drogą lub mostem. Jeśli nie jest połączone, próbuje zbudować drogę. Zauważ, że w prawdziwej sztucznej inteligencji prawdopodobnie będziesz chciał sprawdzić, czy AIRoad.BuildRoad
zwraca true, a jeśli nie, ponownie uruchom wyszukiwanie ścieżki.
Note: nie używaj rodzica jako nazwy zmiennej dla GetParent
, jest to słowo kluczowe, dające dziwne wyniki.
A sample AI
import("pathfinder.road", "RoadPathFinder", 3); class SampleAI extends AIController { } function SampleAI::Start() { /* Get a list of all towns on the map. */ local townlist = AITownList(); /* Sort the list by population, highest population first. */ townlist.Valuate(AITown.GetPopulation); townlist.Sort(AIAbstractList.SORT_BY_VALUE, false); /* Pick the two towns with the highest population. */ local townid_a = townlist.Begin(); local townid_b = townlist.Next(); /* Print the names of the towns we'll try to connect. */ AILog.Info("Going to connect " + AITown.GetName(townid_a) + " to " + AITown.GetName(townid_b)); /* Tell OpenTTD we want to build normal road (no tram tracks). */ AIRoad.SetCurrentRoadType(AIRoad.ROADTYPE_ROAD); /* Create an instance of the pathfinder. */ local pathfinder = RoadPathFinder(); /* Set the cost for making a turn extreme high. */ pathfinder.cost.turn = 5000; /* Give the source and goal tiles to the pathfinder. */ pathfinder.InitializePath([AITown.GetLocation(townid_a)], [AITown.GetLocation(townid_b)]); /* Try to find a path. */ local path = false; while (path == false) { path = pathfinder.FindPath(100); this.Sleep(1); } if (path == null) { /* No path was found. */ AILog.Error("pathfinder.FindPath return null"); } /* If a path was found, build a road over it. */ 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())) { /* An error occured while building a piece of road. TODO: handle it. * Note that is can also be the case that the road was already build. */ } } else { /* Build a bridge or tunnel. */ if (!AIBridge.IsBridgeTile(path.GetTile()) && !AITunnel.IsTunnelTile(path.GetTile())) { /* If it was a road tile, demolish it first. Do this to work around expended roadbits. */ if (AIRoad.IsRoadTile(path.GetTile())) AITile.DemolishTile(path.GetTile()); if (AITunnel.GetOtherTunnelEnd(path.GetTile()) == par.GetTile()) { if (!AITunnel.BuildTunnel(AIVehicle.VT_ROAD, path.GetTile())) { /* An error occured while building a tunnel. TODO: handle it. */ } } 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())) { /* An error occured while building a bridge. TODO: handle it. */ } } } } } path = par; } AILog.Info("Done"); }
Utwórz info.nut
dla tej sztucznej inteligencji i spróbuj. Zagraj trochę przy różnych kosztach, na przykład ustawiając pathfinder.cost.no_existing_road = pathfinder.cost.max_cost
. teraz pathfinder znajdzie trasy tylko na istniejącej drodze. Niezbyt przydatne, jeśli chcesz zbudować nowe połączenie, ale może być fajne, jeśli chcesz sprawdzić, czy poprzednio kompilowane połączenie jest nadal OK.