Пример #1
0
        /// <summary>
        /// Helper function for creating a Node for an entrace but also checking that it hasn't already been
        /// created, and if it has, just using that one instead to avoid multiple Nodes on top of the same
        /// tile.
        /// </summary>
        /// <param name="cluster">The custer which this node lives in.</param>
        /// <param name="tile">The tile which this node wraps.</param>
        /// <param name="node">
        /// If a node already exists over <paramref name="tile"/> this will be that GraphNode. If not it will 
        /// be a newly created GraphNode.
        /// </param>
        /// <returns>True if a new GraphNode was created.</returns>
        private Boolean CreateEntraceNode(Cluster cluster, Level.Tile tile, out GraphNode node)
        {
            Boolean created = false;

            // First see if this Tile is already managed by this cluster. If it is, it will return us
            // the node which contains it.
            node = cluster.GetNodeContaining(tile);

            // If the node isn't already being managed, we need to create a new one.
            if (null == node)
            {
                node = mNodeFactory.GetNode();
                node.pData = tile;

                created = true;

                // New nodes need to be registers with the Graph.
                AddNode(node);
            }

            return created;
        }
Пример #2
0
        /// <summary>
        /// Helper function to do the work needed to create a single new extrance/exit based on the current tile being walked,
        /// and the Tile at the start of the sequence.
        /// </summary>
        /// <param name="cluster">The cluster we are evaluating. Hitting a Tile outside this cluster will end that wall.</param>
        /// <param name="tile">The current tile to evaluate.</param>
        /// <param name="sequeceStart">The tile which started this sequence. Use null to start.</param>
        /// <param name="dirWalk">The direction to walk along the wall.</param>
        /// <param name="dirCheck">The direction of the neighbouring Cluster to check. It isn't enough to check outselves; the neighbour may be blocking travel.</param>
        /// <param name="dirNeighbourCluster">Same as dirCheck but using the enum that Clusters understand.</param>
        /// <param name="removeSelf">True if the tile that triggered this call should not be included in the sequence. Useful if this sequence ended because you hit a wall.</param>
        private void CreateEntrance(Cluster cluster, Level.Tile tile, ref Level.Tile sequenceStart, Level.Tile.AdjacentTileDir dirWalk, Level.Tile.AdjacentTileDir dirCheck, Cluster.AdjacentClusterDirections dirNeighbourCluster, Boolean removeSelf)
        {
            // Find the center point between the tile at the start of the sequence of enpty tiles
            // and the current tile.
            Vector2 sequenceVector = tile.mCollisionRect.pCenterPoint - sequenceStart.mCollisionRect.pCenterPoint;

            // If we enter this block by hitting a wall, we need to remove that a Tile length from our
            // calculations since that wall is not part of the entrance/exit.
            if (removeSelf)
            {
                sequenceVector -= Vector2.Normalize(sequenceVector) * new Vector2(cluster.pTileDimensions.X, cluster.pTileDimensions.Y);
            }

            // If the sequence is long enough, instead of putting a GraphNode in the center, create 2 GraphNode objects,
            // and place them at opposite ends of the Sequence. This is recommended by the original HPA* white paper.
            if (sequenceVector.LengthSquared() >= (mClusterSize * mGetMapInfoMsg.mInfo_Out.mMapWidth * 0.5f))
            {                
                // Add the length of the Sequence to the starting point to get our ending position.
                Vector2 end = (sequenceVector) + sequenceStart.mCollisionRect.pCenterPoint;

                // We need to find the tile at that position because our GraphNode depends on that data.
                mGetTileAtPositionMsg.mPosition_In = end;
                WorldManager.pInstance.pCurrentLevel.OnMessage(mGetTileAtPositionMsg);

                System.Diagnostics.Debug.Assert(null != mGetTileAtPositionMsg.mTile_Out, "Unable to find tile.");

                CreateEntranceNodes(
                    cluster,
                    cluster.pNeighbouringClusters[(Int32)dirNeighbourCluster],
                    mGetTileAtPositionMsg.mTile_Out,
                    mGetTileAtPositionMsg.mTile_Out.mAdjecentTiles[(Int32)dirCheck]);

                CreateEntranceNodes(
                    cluster,
                    cluster.pNeighbouringClusters[(Int32)dirNeighbourCluster],
                    sequenceStart,
                    sequenceStart.mAdjecentTiles[(Int32)dirCheck]);
            }
            else
            {
                // Add half the length in order to put us in the middle of the sequence.
                Vector2 middle = (sequenceVector * 0.5f) + sequenceStart.mCollisionRect.pCenterPoint;

                // We need to find the tile at that position because our GraphNode depends on that data.
                mGetTileAtPositionMsg.mPosition_In = middle;
                WorldManager.pInstance.pCurrentLevel.OnMessage(mGetTileAtPositionMsg);

                System.Diagnostics.Debug.Assert(null != mGetTileAtPositionMsg.mTile_Out, "Unable to find tile.");

                CreateEntranceNodes(
                    cluster,
                    cluster.pNeighbouringClusters[(Int32)dirNeighbourCluster],
                    mGetTileAtPositionMsg.mTile_Out,
                    mGetTileAtPositionMsg.mTile_Out.mAdjecentTiles[(Int32)dirCheck]);
            }

            // Start a new sequence.
            sequenceStart = null;
        }
Пример #3
0
        /// <summary>
        /// Helper function for creating 2 linked GraphNodes in neighnbouring clusters.
        /// </summary>
        /// <param name="localCluster">The cluster currently being evaluated.</param>
        /// <param name="otherCluster">The neighbouring cluster of <paramref name="localCluster"/>.</param>
        /// <param name="localTile">The Tile on which the new entrance sits on top of.</param>
        /// <param name="otherTile">The Tile on wihich the other new entrace sits on top of.</param>
        private void CreateEntranceNodes(Cluster localCluster, Cluster otherCluster, Level.Tile localTile, Level.Tile otherTile)
        {
            GraphNode localNode;
            GraphNode otherNode;

            // Create the new nodes with the appropriate Tile data.
            Boolean localCreated = CreateEntraceNode(localCluster, localTile, out localNode);
            Boolean otherCreated = CreateEntraceNode(otherCluster, otherTile, out otherNode);

            // Link the two nodes together creating an Intra-Connection.
            localNode.AddNeighbour(otherNode);
            otherNode.AddNeighbour(localNode);

            // Add the nodes the appropriate Cluster objects.
            if (localCreated)
            {
                localCluster.AddNode(localNode);
            }
            if (otherCreated)
            {
                otherCluster.AddNode(otherNode);
            }
        }
Пример #4
0
        /// <summary>
        /// Recursive algorithm for finding all entraces/exits in a cluster, and creating GraphNodes at those points
        /// in neighbouring Clusters and linking them together. Has logic to only create on entrance/exit per concurent
        /// set of empty tiles.
        /// </summary>
        /// <param name="cluster">The cluster we are evaluating. Hitting a Tile outside this cluster will end that wall.</param>
        /// <param name="tile">The current tile to evaluate.</param>
        /// <param name="sequeceStart">The tile which started this sequence. Use null to start.</param>
        /// <param name="dirWalk">The direction to walk along the wall.</param>
        /// <param name="dirCheck">The direction of the neighbouring Cluster to check. It isn't enough to check outselves; the neighbour may be blocking travel.</param>
        /// <param name="dirNeighbourCluster">Same as dirCheck but using the enum that Clusters understand.</param>
        /// <returns>The last tile visited on this wall. Useful for walking an entire perimeter.</returns>
        private Level.Tile WalkWall(Cluster cluster, Level.Tile tile, Level.Tile sequeceStart, Level.Tile.AdjacentTileDir dirWalk, Level.Tile.AdjacentTileDir dirCheck, Cluster.AdjacentClusterDirections dirNeighbourCluster)
        {
            // Get the Tile in the neighbouring Cluster. It being solid creates a wall just the same as 
            // if the tile in this Cluster is solid.
            Level.Tile adj = tile.mAdjecentTiles[(Int32)dirCheck];

            Boolean entraceMade = false;

            if (null != adj)
            {
                // If we don't yet have a sequence start point, and this tile is an entrace,
                // it becomes the new sequence start.
                if (null == sequeceStart && tile.mType == Level.Tile.TileTypes.Empty && adj.mType == Level.Tile.TileTypes.Empty)
                {
                    sequeceStart = tile;
                }
                // The sequence has started already and we just hit a wall. Time to create an entrance in the
                // center of this sequence.
                else if (null != sequeceStart && (tile.mType != Level.Tile.TileTypes.Empty || adj.mType != Level.Tile.TileTypes.Empty))
                {
                    CreateEntrance(cluster, tile, ref sequeceStart, dirWalk, dirCheck, dirNeighbourCluster, true);

                    entraceMade = true;
                }
            }

            // Walk to the next Tile.
            adj = tile.mAdjecentTiles[(Int32)dirWalk];

            // Are we still in the Cluster/Level?
            if (null != adj && cluster.IsInBounds(adj))
            {
                // Recursivly visit the next Tile.
                return WalkWall(cluster, adj, sequeceStart, dirWalk, dirCheck, dirNeighbourCluster);
            }
            else
            {
                // We have left either the map or the Cluster. Either way that is considered an end to
                // the current sequence, should one be in progress.
                if (null != sequeceStart)
                {
                    System.Diagnostics.Debug.Assert(!entraceMade, "Entrance made twice.");

                    CreateEntrance(cluster, tile, ref sequeceStart, dirWalk, dirCheck, dirNeighbourCluster, false);
                }

                return tile;
            }
        }
Пример #5
0
        /// <summary>
        /// Links two GraphNode objects as Neighbour. Does the slightly expensive task of calculating actual
        /// A* path between the two, and caches that value as the cost between the two.
        /// </summary>
        /// <param name="a">A node to link to <paramref name="b"/>.</param>
        /// <param name="b">A node to link to <paramref name="a"/>.</param>
        /// <param name="cluster">The cluster containing both nodes.</param>
        private void LinkGraphNodes(GraphNode a, GraphNode b, Cluster cluster)
        {
            Boolean oneWay = false;

            mPlanner.Reset();
            mPlanner.SetSource((a.pData as Level.Tile).mGraphNode);
            mPlanner.SetDestination((b.pData as Level.Tile).mGraphNode);

            // Do a standard A* search with the adde constraint of staying within the 
            // bounds of this cluster.
            Planner.Result result = mPlanner.PlanPath(cluster.pBounds, false);

            // Keep searching unti we either fail or succeed.
            while(result == Planner.Result.InProgress)
            {
                result = mPlanner.PlanPath(cluster.pBounds, false);
            }

            // Only connect the nodes if they can be reached from one another within the same cluster.
            if (result == Planner.Result.Solved)
            {
                PathNode path = mPlanner.pCurrentBest;

                // Link the two neightbours.
                a.AddNeighbour(b, path.pFinalCost);
                if (!oneWay)
                {
                    b.AddNeighbour(a, path.pFinalCost);
                }
            }

            mPlanner.ClearDestination();

            //if (a != null)
            //{
                //DebugWalkGraphForErrors(a as NavMeshTileGraphNode);
            //}

            //DebugCheckNode(a);
            //DebugCheckNode(b);
        }
Пример #6
0
        /// <summary>
        /// Create all the intra-connects within a Cluster.
        /// </summary>
        /// <param name="cluster">The Cluster to perform the links in.</param>
        private void LinkClusterGraphNodes(Cluster cluster)
        {
            if (cluster != null)
            {
                // Iterate throught the nodes of a Cluster 2 at a time, linking each node with all
                // the nodes that follow it (and back), so by the end of the loop everyone should be
                // linked to each other.
                // eg.  A <-> BCD
                //      B <-> CD
                //      C <-> D
                for (Int32 i = 0; i < cluster.pNodes.Count; i++)
                {
                    for (Int32 j = i + 1; j < cluster.pNodes.Count; j++)
                    {
                        // Avoid linking the same node multiple times.
                        if (!cluster.pNodes[i].HasNeighbour(cluster.pNodes[j]))
                        {
                            LinkGraphNodes(cluster.pNodes[i], cluster.pNodes[j], cluster);
                        }
                    }

                    //AddNode(cluster.pNodes[i]);
                }
            }

            //if (cluster.pNodes[0] != null)
            //{
                //DebugWalkGraphForErrors(cluster.pNodes[0] as NavMeshTileGraphNode);
            //}
        }
Пример #7
0
        /// <summary>
        /// Nasty little function which essentially removes a Cluster's nodes and any links associated
        /// with them. Things get complicated with the fact that adjecent Clusters have GraphNode in them
        /// which link back to GraphNode in this Cluster, which need to be cleaned up, and sometimes completely
        /// removed. The method handles it all.
        /// </summary>
        /// <param name="cluster"></param>
        private void ClearCluster(Cluster cluster)
        {
            if (null != cluster)
            {
                // Loop through every node in the cluster cleaning up its links to other nodes
                // as we go.
                for (Int32 i = cluster.pNodes.Count - 1; i >= 0; i--)
                {
                    GraphNode node = cluster.pNodes[i];

                    // Loop through all the neighbours removing the links as we go.
                    for (Int32 j = node.pNeighbours.Count - 1; j >= 0; j--)
                    {
                        GraphNode neighbourNode = node.pNeighbours[j].mGraphNode;

                        // Since this is in the cluster being regenerated, all links to other nodes
                        // should be removed; this node/cluster isn't going to exist in a moment.
                        UnlinkGraphNodes(node, neighbourNode);

                        //
                        // Next comes the convoluted process for checking if the GraphNode just unlinked
                        // actually lives in another cluster, and if so, possibly removing that GraphNode
                        // as well, but only in the case where the neighbour ONLY links to GraphNodes 
                        // within its own Cluster (remember we already removed the link to the cluster we 
                        // are destroying. In the case where it links out to another Cluster, that node 
                        // still has some purpose, so it shouldn't be removed; just the links to the
                        // Cluster being destroyed.
                        //

                        Cluster neighbourCluster = GetClusterAtPosition(neighbourNode.pPosition);

                        // Is this a GraphNode that lives in a Cluster outside the one being cleared?
                        if (neighbourCluster != cluster)
                        {
                            // Search through all the neighbours trying to find one that is in a different Cluster,
                            // signifying that this GraphNode needs to live on. Remember that nodes in corners can
                            // be linked to multiple adjacent Clusters.
                            Boolean foundOther = false;

                            for (Int32 k = neighbourNode.pNeighbours.Count - 1; k >= 0; k--)
                            {
                                // Slicks naming...
                                GraphNode otherNeighbourNode = neighbourNode.pNeighbours[k].mGraphNode;

                                Cluster otherNeighbourCluster = GetClusterAtPosition(otherNeighbourNode.pPosition);

                                // If the neighbour lives outside this Cluster than we don't want to remove the current
                                // neighbour being evaluated.
                                if (otherNeighbourCluster != neighbourCluster)
                                {
                                    foundOther = true;
                                    break;
                                }
                            }

                            if (!foundOther)
                            {
                                // Now loop through all the neighbours AGAIN, this time removing all links between the
                                // GraphNode about to be removed, and all the others that will live on.
                                for (Int32 k = neighbourNode.pNeighbours.Count - 1; k >= 0; k--)
                                {
                                    GraphNode otherNeighbourNode = neighbourNode.pNeighbours[k].mGraphNode;

                                    UnlinkGraphNodes(neighbourNode, otherNeighbourNode);
                                }

                                // Remove this neighbour from the Graph objects.
                                //DebugCheckForReferences(neighbourNode);

                                mNodeFactory.RecycleNode(neighbourNode);
                                RemoveNode(neighbourNode);
                                neighbourCluster.RemoveNode(neighbourNode);
                            }
                        }
                    }

                    // If this is a temporary node it should not be removed from the Graph because it will not
                    // be automatically re-added. It still gets all the links removed because those WILL be 
                    // automatically generated.
                    if (!(node as NavMeshTileGraphNode).pIsTemporary)
                    {
                        //DebugCheckForReferences(node);
                        mNodeFactory.RecycleNode(node);
                        RemoveNode(node);
                        cluster.RemoveNode(node);
                    }
                }
            }

            //DebugCheckNodes();
        }
Пример #8
0
        /// <summary>
        /// Steps through all Tile objects in the level and generates a nave mesh from it.
        /// </summary>
        /// <param name="level"></param>
        public void CreateNavMesh(GameObject.GameObject level)
        {
            // The gets used over and over again throughout the life if this object.
            level.OnMessage(mGetMapInfoMsg);

            Int32 mapWidth = mGetMapInfoMsg.mInfo_Out.mMapWidth;
            Int32 mapHeight = mGetMapInfoMsg.mInfo_Out.mMapHeight;

            Int32 tileWidth = mGetMapInfoMsg.mInfo_Out.mTileWidth;
            Int32 tileHeight = mGetMapInfoMsg.mInfo_Out.mTileHeight;

            // Clusters get indexed based on their X, Y positions in the world.
            mClusters = new Cluster[mapWidth / mClusterSize, mapHeight / mClusterSize];

            // Loop through Cluster by Cluster initializing them.
            for (Int32 y = 0; y < mClusters.GetLength(1); y++)
            {
                for (Int32 x = 0; x < mClusters.GetLength(0); x++)
                {
                    Cluster temp = new Cluster(mClusterSize, tileWidth, tileHeight);

                    mGetTileAtPositionMsg.mPosition_In.X = x * (tileWidth * mClusterSize);
                    mGetTileAtPositionMsg.mPosition_In.Y = y * (tileHeight * mClusterSize);

                    level.OnMessage(mGetTileAtPositionMsg);

                    // The top left tile becomes a handy spot to start iterations over all Tile objects
                    // in a Cluster.
                    temp.pTopLeft = mGetTileAtPositionMsg.mTile_Out;

                    // Link Left <-> Right
                    if (x > 0)
                    {
                        temp.pNeighbouringClusters[(Int32)Cluster.AdjacentClusterDirections.Left] = mClusters[x - 1, y];
                        mClusters[x - 1, y].pNeighbouringClusters[(Int32)Cluster.AdjacentClusterDirections.Right] = temp;
                    }

                    // Link Up <-> Down
                    if (y > 0)
                    {
                        temp.pNeighbouringClusters[(Int32)Cluster.AdjacentClusterDirections.Up] = mClusters[x, y - 1];
                        mClusters[x, y - 1].pNeighbouringClusters[(Int32)Cluster.AdjacentClusterDirections.Down] = temp;
                    }

                    // Only walk the top and left walls for each cluster. The neighbouring clusters will do the same
                    // and as a result all walls will have been evaluated.
                    WalkWall(temp, temp.pTopLeft, null, Level.Tile.AdjacentTileDir.RIGHT, Level.Tile.AdjacentTileDir.UP, Cluster.AdjacentClusterDirections.Up);
                    WalkWall(temp, temp.pTopLeft, null, Level.Tile.AdjacentTileDir.DOWN, Level.Tile.AdjacentTileDir.LEFT, Cluster.AdjacentClusterDirections.Left);

                    // Store the cluster in the array index relative to its position in the world.
                    mClusters[x, y] = temp;
                }
            }

            // At this point all intra-connections have been made (cluster crossing connections), so now we need to
            // make all inter-connections (nodes linked inside of a cluster). We have to wait till now since the WallWalk 
            // done above only does 2 sides at a time.
            for (Int32 y = 0; y < mClusters.GetLength(1); y++)
            {
                for (Int32 x = 0; x < mClusters.GetLength(0); x++)
                {
                    Cluster temp = mClusters[x, y];

                    LinkClusterGraphNodes(temp);
                }
            }
        }