예제 #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>
 /// Constrctor.
 /// </summary>
 /// <param name="solid">The direction of the solid tile relative to the tile that would be a doorway.</param>
 /// <param name="empty">The direction of the emoty tile relative to teh tile that would be a doorway.</param>
 public DoorwayConfiguration(Level.Tile.AdjacentTileDir solid, Level.Tile.AdjacentTileDir empty)
 {
     mSolid = solid;
     mEmpty = empty;
 }
예제 #5
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;
            }
        }
예제 #6
0
        /// <summary>
        /// Does a kind of flood fill of game objects inside a structure in a way that the user 
        /// would expect, meaning 1 tile opening are treating like doors, which can connect rooms
        /// or be an exit to the outside world.
        /// </summary>
        /// <param name="firstTile">The Tile to start the fill at.</param>
        /// <param name="maxFillCount">
        /// The maximum number of tiles to search before considering 
        /// the SafeHouse filled. Any rooms to completely changed when this cap is hit will not 
        /// be filled.
        /// </param>
        /// <param name="gameObjectTemplateName">The name of the GameObject which will be flood filled.</param>
        /// <returns>True if at least one room was filled.</returns>
        public Boolean FloodFill(Level.Tile firstTile, UInt32 maxFillCount, String gameObjectTemplateName)
        {
            // Tracks whether or not any rooms were filled.
            Boolean success = false;

            // Store for use later.
            mGameObjectTemplateName = gameObjectTemplateName;

            // A new fill means any currently running should stop.
            mTilesToChangeFinal.Clear();

            // This algorithm works by breaking the world into rooms. A room is an area surrounded
            // by wall, with the one cavet that spaces 1 pixel wide are allowed when moving from
            // one room to another, but are considered a wall when exiting the entire structure.
            // Filling is done on a per room basis. If we reach maxFillCount before a room is filled,
            // none of the room gets filled in. However, any rooms previously filled in stay, and 
            // rooms still in the queue can potentially be filled if they are smaller than the one
            // that failed.
            mRoomStarters.Enqueue(firstTile);

            // Keep track of how many tiles have been changed overall in order to make sure
            // we don't go over maxFillCount over all.
            Int32 curFillCount = 0;

            // Go room by room trying to cover it in the desired object.
            while (mRoomStarters.Count > 0 && (curFillCount <= maxFillCount))
            {
                // The starting tile is the tile in the roomStarters Queue.
                Level.Tile startTile = mRoomStarters.Dequeue();

                // Clear out any data that might be sitting around from the previous room.
                mTilesToCheck.Clear();
                mTilesToChange.Clear();
                mRoomStartersToAdd.Clear();

                // There is a chance we were given a Tile that is null or not empty. In those cases
                // we just want to skip over them. It was Dequeued from roomStarters so it will be
                // forgotten.
                if (null != startTile && startTile.mType == Level.Tile.TileTypes.Empty)
                {
                    // Start the tile travesal with the roomStarter.
                    mTilesToCheck.Enqueue(startTile);

                    mProcessedTiles.Add(startTile);
                }

                // Breath first search through the tile map stopping at walls and doors, or at a point
                // where we have visiting more tiles than allowed by maxFillCount.
                while ((mTilesToCheck.Count) > 0 && (mTilesToChange.Count + curFillCount <= maxFillCount))
                {
                    // Grab the next Tile, or in the case of the first interation it will be a roomStarter.
                    Level.Tile currentTile = mTilesToCheck.Dequeue();

                    // Safety check in case we got sent a bad starting tile.
                    if (null == currentTile || currentTile.mType != Level.Tile.TileTypes.Empty)
                    {
                        continue;
                    }

                    // Should this room be completed, this tile should be changed.
                    mTilesToChange.Add(currentTile);

                    // Loop through all the surrounding tiles adding the appropriate ones to the 
                    // tiles Queue.
                    for (UInt32 tileIndex = (UInt32)Level.Tile.AdjacentTileDir.START_HORZ; (tileIndex < (UInt32)Level.Tile.AdjacentTileDir.NUM_DIRECTIONS); tileIndex += 1)
                    {
                        Level.Tile nextTile = currentTile.mAdjecentTiles[tileIndex];

                        if (null != nextTile && nextTile.mType == Level.Tile.TileTypes.Empty && // Safety check
                            false == mProcessedTiles.Contains(nextTile) && // Don't check the same tile more than once.
                            !Level.IsAttemptingInvalidDiagonalMove((Level.Tile.AdjacentTileDir)tileIndex, currentTile)) // Don't clip through diagonal walls.
                        {
                            if (IsDoorway(nextTile))
                            {
                                // If this tile is actually a doorway, it becomes the start of a new room.
                                mRoomStartersToAdd.Add(nextTile);
                            }
                            else
                            {
                                // This is just a regular tile, so it should be changed, should this room be completed.
                                mTilesToCheck.Enqueue(nextTile);
                            }

                            // This tile has been processed so it should not be checked again.
                            mProcessedTiles.Add(nextTile);
                        }
                    }
                }

                // The breath first search has completed. If it exusted all tiles, then the room is considered 
                // completed, and we can start actually changing tiles and adding the connected rooms.
                if (mTilesToCheck.Count <= 0)
                {
                    // Once any room gets filled it is considered a success.
                    success = true;

                    // This is a legit change now, so count it towards to total fill count.
                    curFillCount += mTilesToChange.Count;

                    mTilesToChangeFinal.AddRange(mTilesToChange);

                    // Any new rooms that were queued up can now be safely added as the room that
                    // linked to them was completed.
                    for (Int32 i = 0; i < mRoomStartersToAdd.Count; i++)
                    {
                        mRoomStarters.Enqueue(mRoomStartersToAdd[i]);
                    }
                }
            }

            // Don't want to be hanging onto this data.
            mProcessedTiles.Clear();
            mRoomStarters.Clear();
            mTilesToCheck.Clear();
            mTilesToChange.Clear();
            mRoomStartersToAdd.Clear();

            return success;
        }
예제 #7
0
        /// <summary>
        /// A somewhat complex check to determine if a tile is what would be considered a doorway.
        /// A doorway is an empty tile, surrounded by 2 or more empty tiles on opposite sides. However,
        /// it is not quite as simple as that, since some combination block what would be the room after
        /// the door way, so we also need to verify that for a given combination of walls, there is a
        /// another empty tile at a particular location.
        /// </summary>
        /// <param name="tile"></param>
        /// <returns></returns>
        private Boolean IsDoorway(Level.Tile tile)
        {
            // Build this mapping of walls and empty spaces required to make up a doorway.
            /// <todo>Do this once.</todo>
            List<DoorwayConfiguration> [] relaventSiblings = BuildDoorwayConfigurationTable();

            // We need to loop through every surround tile and see if they are walls. If they ar walls,
            // then we start checking the relaventSiblings for the required corrisponding walls and empty tiles
            // which mean this tile is a doorway.
            for (UInt32 tileIndex = (UInt32)Level.Tile.AdjacentTileDir.START_HORZ; (tileIndex < (UInt32)Level.Tile.AdjacentTileDir.NUM_DIRECTIONS); tileIndex++)
            {
                // Grab the next tile surrounding the one we are checking.
                Level.Tile siblingTile = tile.mAdjecentTiles[tileIndex];

                // None of this matters if the adjacent tile is empty.
                if (null != siblingTile && siblingTile.mType != Level.Tile.TileTypes.Empty)
                {
                    // Every surrounding tile has a list of corrisponding tiles that when in the proper
                    // configuration mean that the center tile is a doorway. Loop through those conigurations.
                    for (Int32 i = 0; i < relaventSiblings[tileIndex].Count; i++)
                    {
                        // When siblingTile is Solid, another tile on the opposite side also needs to be solid.
                        Level.Tile adjTileSolid = tile.mAdjecentTiles[(Int32)relaventSiblings[tileIndex][i].mSolid];

                        // Is a relavent sibling solid?
                        if (null != adjTileSolid && adjTileSolid.mType != Level.Tile.TileTypes.Empty)
                        {
                            // But it isn't enough to have solid tiles on opposite sides of the center tile.
                            // There also needs to be an empty tile in the right direction or else this is
                            // just a dead end and should be considered part of the current room, not the 
                            // start of a new one.
                            Level.Tile adjEmpty = tile.mAdjecentTiles[(Int32)relaventSiblings[tileIndex][i].mEmpty];

                            // Is the relavent sibling Empty?
                            if (null != adjEmpty && adjEmpty.mType == Level.Tile.TileTypes.Empty)
                            {
                                // Once this is a doorway in one case, nothing can change that; it can only
                                // have additional paths, but that will be handled when this Tile becomes
                                // the starting point of the next room.
                                return true;
                            }
                        }
                    }
                }
            }

            return false;
        }
예제 #8
0
        /// <summary>
        /// Is a particular Tile already part of this Cluster, meaning that a GraphNode in this
        /// Cluster is wrapping that tile. If it is, then that GraphNode gets returned so that it
        /// can be reused.
        /// </summary>
        /// <param name="tile">The tile to check for.</param>
        /// <returns>The GraphNode in this Cluster which wraps the given Tile.</returns>
        public GraphNode GetNodeContaining(Level.Tile tile)
        {
            // Just loop through every GraphNode looking for one that is storing the Tile.
            for (Int32 i = 0; i < pNodes.Count; i++)
            {
                if (pNodes[i].pData as Level.Tile == tile)
                {
                    if (!(pNodes[i] as NavMeshTileGraphNode).pIsTemporary)
                    {
                        return pNodes[i];
                    }
                }
                else if (pNodes[i].pPosition == tile.mCollisionRect.pCenterPoint)
                {
                    System.Diagnostics.Debug.Assert(false, "Tile at same position but claiming to be different.");
                }
            }

            return null;
        }
예제 #9
0
 /// <summary>
 /// Checks if a Tile is inside of this cluster (at all).
 /// </summary>
 /// <param name="tile">The Tile to check for.</param>
 /// <returns>True if tile is inside this cluster.</returns>
 public Boolean IsInBounds(Level.Tile tile)
 {
     return (mBounds.Intersects(tile.mCollisionRect));
 }
예제 #10
0
        /// <summary>
        /// When choosing a path we want to avoid clipping the edges of solid tiles. For example if you are
        /// going LEFT_DOWN, there should be no solid tile LEFT or DOWN or else the character would clip
        /// into them.
        /// It also avoids the problem where the path slips between kitty-cornered tiles.
        /// </summary>
        /// <param name="dir">The direction we want to move.</param>
        /// <param name="rootTile">The tile we are moving from.</param>
        /// <returns>True if this is an invalid move.</returns>
        static public Boolean IsAttemptingInvalidDiagonalMove(Level.Tile.AdjacentTileDir dir, Level.Tile rootTile)
        {
            switch ((Int32)dir)
            {
            // The path wants to move down and to the left...
            case (Int32)Level.Tile.AdjacentTileDir.LEFT_DOWN:
                {
                    // But it should only do so if their are no solid tiles to the left and
                    // no solid tiles below.  If there are, it needs to find another way round.
                    if (IsTileInDirectionSolid(Level.Tile.AdjacentTileDir.LEFT, rootTile) ||
                        IsTileInDirectionSolid(Level.Tile.AdjacentTileDir.DOWN, rootTile))
                    {
                        return true;
                    }
                    break;
                }
            case (Int32)Level.Tile.AdjacentTileDir.LEFT_UP:
                {
                    if (IsTileInDirectionSolid(Level.Tile.AdjacentTileDir.LEFT, rootTile) ||
                        IsTileInDirectionSolid(Level.Tile.AdjacentTileDir.UP, rootTile))
                    {
                        return true;
                    }
                    break;
                }
            case (Int32)Level.Tile.AdjacentTileDir.RIGHT_DOWN:
                {
                    if (IsTileInDirectionSolid(Level.Tile.AdjacentTileDir.RIGHT, rootTile) ||
                        IsTileInDirectionSolid(Level.Tile.AdjacentTileDir.DOWN, rootTile))
                    {
                        return true;
                    }
                    break;
                }
            case (Int32)Level.Tile.AdjacentTileDir.RIGHT_UP:
                {
                    if (IsTileInDirectionSolid(Level.Tile.AdjacentTileDir.RIGHT, rootTile) ||
                        IsTileInDirectionSolid(Level.Tile.AdjacentTileDir.UP, rootTile))
                    {
                        return true;
                    }
                    break;
                }
            };

            return false;
        }
예제 #11
0
  /// <summary>
 /// Checks in an adjecent tile is solid.  Safely avoids cases where their is no adjecent tile.
 /// </summary>
 /// <param name="dir">The direction to check in.</param>
 /// <param name="rootTile">The tile to move from.</param>
 /// <returns>True if the adjecent tile exists and is solid.</returns>
 static private Boolean IsTileInDirectionSolid(Level.Tile.AdjacentTileDir dir, Level.Tile rootTile)
 {
     return (rootTile.mAdjecentTiles[(Int32)dir] != null && rootTile.mAdjecentTiles[(Int32)dir].mType != Level.Tile.TileTypes.Empty);
 }
예제 #12
0
 /// <summary>
 /// Constructor.
 /// </summary>
 /// <param name="tile">The Tile at this Node's location in the world.</param>
 public TileGraphNode(Level.Tile tile)
     : base()
 {
     mTile = tile;
 }