SI:RoadPathfinder/Pl

From OpenTTD
Jump to: navigation, search



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 instalizować to 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 pojedynczej płytki.
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 kafelek nowego mostu jest dodawany do _cost_tile.
cost.tunnel_per_tile 120 Koszt na kafelek nowego tunelu jest dodawany do _cost_tile.
cost.coast 20 Dodatkowy koszt za płytkę 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) kafelków źródła i jednego (lub więcej) kafelków 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 płytek źródłowych i tablicy płytek bramkowych, ponieważ mamy tylko jedno źródło i jedną płytkę bramkową, 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 kafelkiem 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.

Personal tools