Exemplo n.º 1
0
    //This method is supposed to provide any character with coordinates to the next step
    //The method is based on Breath First Search method
    //In short: it puts a node into the queue and starts a loop. It then dequeues a node from the queue, check it's neighbours and puts it into the queue. Then it marks the node as visited, to ignore it in the future.
    //The node that followed the node is added to list of previous nodes. Each node on the grid has its entry on that list.
    //In following loop iterations it then dequeues subsequent nodes and repeats the steps.
    //Once node that's supposed to be the end of the path is reached, the loop is stopped.
    //Then, based on the previous list, the path is recreated, from the end
    //Last node of the path is then provided back to the asking character.

    public static GridField GetNextStep(GridField startField, GridField targetField, GridScript grid)
    {
        #region properties
        Queue <GridField> queue    = new Queue <GridField>(); //The queue that's the base of the first part of the method.
        List <GridField>  visited  = new List <GridField>();  //List of nodes that were already handled.
        List <GridField>  previous = new List <GridField>();  //List in which a node prior for each node can be found. For example node previous to node "1,0" can be found in this list's index 10.
        List <GridField>  path     = new List <GridField>();  //This is the path to the final node. Since it's always writen backwards, ending node is already added to it. Subsequent nodes are separated with a ;. It's important that after split(), last element will be empty, so the actual first step is 2nd to last.

        GridScript gameGrid = grid;                           //Grid is necessary to calculate surrounding nodes and to get field information.

        float stime = Time.realtimeSinceStartup;
        queue.Enqueue(startField);     //Enqueueing the 1st node - start - to kickstart the loop.
        visited.Add(startField);       //s node is immediately marked as visited.
        #endregion

        //This loop adds initially empty strings to 'previous' list, for each node on the grid.
        #region fill 'previous' list with empty strings
        for (int i = 0; i < (gameGrid.rows * gameGrid.columns); i++)
        {
            previous.Add(null);
        }
        #endregion

        //This loop is ultimately responssible for filling the 'previous' list with proper data, up to the end node.
        #region 1st loop filling in previous list
        int loop1c = 0;                                     //This is just a safety check. If loop takes too many iterations, we break out of it, based on this value.
        while (queue.Count > 0)
        {
            loop1c++;
            bool          foundEnd        = false;                                      //This is set to true when found neighbour turns out to be the ending node.
            GridField     field           = queue.Dequeue();                            //Dequeueing node from queue. Initially it's the 's' node.
            List <string> nodesNeighbours = GetSurroundingNodes(field, gameGrid);       //Getting surrounding nodes for currently processed node.
            foreach (string z in nodesNeighbours)                                       //Loop within a loop could be dangerous. Luckily, it's 4 iterations at most, in theory.
            {
                if (z == targetField.stringRepresentation)                              //Handling case in which found neighbour turns out to bbe the ending node
                {
                    string[] split = z.Split(',');                                      //Temprorary solution since node handling is a mix of processing a string and two ints. To be changed to a unified format.
                    int      x = int.Parse(split[0]); int y = int.Parse(split[1]);
                    previous[int.Parse(x.ToString() + y.ToString())] = field;           //Replacing empty string in 'previous' for the found NEIGHBOUR list with currently handled (dequeued) node.
                    foundEnd = true;                                                    //Finding the ending node ends the loop.
                    break;
                }
                if (!grid.isFieldWalkable(z))                                           //If field is not walkable, we simply don't process it.
                {
                    visited.Add(grid.GetFieldByCoord(z));
                }
                if (!visited.Contains(grid.GetFieldByCoord(z)))                         //This part of the loop only executes if found neighbour wasn't already processed.
                {
                    queue.Enqueue(grid.GetFieldByCoord(z));                             //Each found neighbour is put to the queue.
                    visited.Add(grid.GetFieldByCoord(z));                               //It's also marked as visited, to not revisit it in the future.

                    string[] split = z.Split(',');
                    int      x = int.Parse(split[0]); int y = int.Parse(split[1]);      //Temprorary solution since node handling is a mix of processing a string and two ints. To be changed to a unified format.
                    previous[int.Parse(x.ToString() + y.ToString())] = field;           //Replacing empty string in 'previous' for the found NEIGHBOUR list with currently handled (dequeued) node.
                }
            }
            if (foundEnd)                                                               //If we've found the end node, not only foreach (inner) loop is ended, but the whole while (outer) loop as well.
            {
                break;
            }
            if (loop1c > 100)                                                           //If the loop has reached 100 iterations we crash.
            {
                throw new Exception("Breaking out of 1st loop of GetNextStep() method as it reached more than 100 iterations, suggesting an infinite loop.");
                break; //Probably unnecessary.
            }
        }
        #endregion

        //This loop recreates the path based on 'previous' list and puts it to a string.
        #region 2nd loop creating path string
        GridField previousField = previous[int.Parse(targetField.intRepresentationX.ToString() + targetField.intRepresentationY.ToString())]; //previousNode represents the previous node of currently handled node. Since we have to start somewhere, we're initially assigning it to the end node.
        int       loop2c        = 0;                                                                                                          //As with the first loop, we also break out of this one if it reaches 100 iterations.
        while (true)
        {
            loop2c++;
            if (previous[int.Parse(previousField.intRepresentationX.ToString() + previousField.intRepresentationY.ToString())] != startField)   //Unless the previousNode is the starting node, we process it.
            {
                path.Add(previous[int.Parse(previousField.intRepresentationX.ToString() + previousField.intRepresentationY.ToString())]);       //We add the the node that's in the 'previous' list in the index reserved for previousNode, so we add previous node of previousNode. Previousception.
                previousField = previous[int.Parse(previousField.intRepresentationX.ToString() + previousField.intRepresentationY.ToString())]; //Then we replace previousNode with previous node of current previousNode. Names are stupid.
            }
            else                                                                                                                                //If previous node turns out to be starting node, we break out of the loop.
            {
                break;
            }
            if (loop2c > 100)                                                                       //If the loop has reached 100 iterations, we crash.
            {
                throw new Exception("Breaking out of 2nd loop of GetNextStep() method as it reached more than 100 iterations, suggesting an infinite loop.");
                break;
            }
        }
        #endregion

        float etime = Time.realtimeSinceStartup;
        Debug.Log("Calculating path fromm " + startField.stringRepresentation + " to " + targetField.stringRepresentation + " took " + ((etime - stime) * 1000).ToString() + "ms.");
        return(path[path.Count - 1]);     //
    }