/// <summary>
        /// Clears out a cluster and regenerates all the nodes inside and any links to outside
        /// cluster GraphNode objects.
        /// </summary>
        /// <param name="pos">The position inside a cluster which should be regenerated.</param>
        public void RegenerateCluster(Vector2 pos)
        {
            // To start, find if and what cluster was invalidated.
            Cluster cluster = GetClusterAtPosition(pos);

            // Remove all the GraphNode objects from the Cluster and remove any links between GraphNodes
            // and adjacent Cluster objects.
            ClearCluster(cluster);

            Level.Tile nextStartPoint = cluster.pTopLeft;

            // With the Cluster cleared out, it is now required that entrances/exits be regenerated.
            nextStartPoint = WalkWall(cluster, nextStartPoint, null, Level.Tile.AdjacentTileDir.RIGHT, Level.Tile.AdjacentTileDir.UP, Cluster.AdjacentClusterDirections.Up);
            nextStartPoint = WalkWall(cluster, nextStartPoint, null, Level.Tile.AdjacentTileDir.DOWN, Level.Tile.AdjacentTileDir.RIGHT, Cluster.AdjacentClusterDirections.Right);
            nextStartPoint = WalkWall(cluster, nextStartPoint, null, Level.Tile.AdjacentTileDir.LEFT, Level.Tile.AdjacentTileDir.DOWN, Cluster.AdjacentClusterDirections.Down);
            nextStartPoint = WalkWall(cluster, nextStartPoint, null, Level.Tile.AdjacentTileDir.UP, Level.Tile.AdjacentTileDir.LEFT, Cluster.AdjacentClusterDirections.Left);

            // Link all the GraphNode within this cluster.
            LinkClusterGraphNodes(cluster);

            // Neighbouring Clusters may have had GraphNode added to them, and so they may require
            // new linkages to be set up.
            for (Int32 i = 0; i < cluster.pNeighbouringClusters.Length; i++)
            {
                LinkClusterGraphNodes(cluster.pNeighbouringClusters[i]);
            }
        }
    //Builds a Level XML out of the layout of the level set up inside levelCanvas
    //and the given Level properties.
    public static void BuildLevelPrimitiveAndSaveLevel(GameObject levelCanvas, string levelName, int levelWidth, int levelHeight, float levelGravity, string levelBorder)
    {
        Level levelToSave = new Level();

        levelToSave.levelName    = levelName;
        levelToSave.levelWidth   = levelWidth;
        levelToSave.levelHeight  = levelHeight;
        levelToSave.levelGravity = levelGravity;
        levelToSave.levelBorder  = levelBorder;

        levelToSave.tiles = new List <Level.Tile>();

        foreach (Transform child in levelCanvas.transform)
        {
            if (child.tag == "Tiles")
            {
                Level.Tile tile = new Level.Tile();

                //Truncates the ending (clone) from tiles
                tile.prefab = child.name.Split('(')[0];
                tile.posX   = child.transform.localPosition.x;               //make function to turn into absolute position
                tile.posY   = child.transform.localPosition.y;
                tile.rot    = child.transform.rotation.eulerAngles.z;
                tile.scaleX = child.localScale.x;                 //make function to find absolute scale
                tile.scaleY = child.localScale.y;

                levelToSave.tiles.Add(tile);
            }
        }
        Debug.Log("Level contains " + levelToSave.tiles.Count + " tiles");
        SaveLevel(levelToSave);
    }
        /// <summary>
        /// Check if this GraphNode can be passed; eg. is it solid of empty?
        /// </summary>
        /// <param name="startingNode">The node we are travelling from.</param>
        /// <returns>True if if the node can be travelled to.</returns>
        public override Boolean IsPassable(GraphNode startingNode)
        {
            // If this GraphNode is not storing a tile, or that Tile is not empty, thats
            // an instant fail.
            if (!IsEmpty())
            {
                return(false);
            }

            // We should not have mismatch GraphNode objects, so pData should be Level.Tile in this case.
            Level.Tile tile = startingNode.pData as Level.Tile;

            // Loop through all adjacent tiles to figure out which direction we are travelling in.
            // We need that data in order to determine if this is a legal diagonal move.
            for (Int32 i = 0; i < tile.mAdjecentTiles.Length; i++)
            {
                if (mTile == tile.mAdjecentTiles[i])
                {
                    if (Level.IsAttemptingInvalidDiagonalMove((Level.Tile.AdjacentTileDir)i, tile))
                    {
                        return(false);
                    }
                    else
                    {
                        // No need to continue searching.
                        break;
                    }
                }
            }

            return(true);
        }
        /// <summary>
        /// Update where this Planner is travelling from. The position is used to look up a tile at that
        /// position in the world.
        /// </summary>
        /// <param name="source">The position in the world that the planner will start at.</param>
        public void SetSource(Vector2 source)
        {
            // Grab the tile at the source position.
            mGetTileAtPositionMsg.mPosition_In = source;
            World.WorldManager.pInstance.pCurrentLevel.OnMessage(mGetTileAtPositionMsg);

            // We only care if they moved enough to make it onto a new tile.
            if (mSourceTile != mGetTileAtPositionMsg.mTile_Out)
            {
                // Update the source incase the GO has moved since the last update.
                mSource = source;

                // Update the source tile with the tile the GO has moved to.
                mSourceTile = mGetTileAtPositionMsg.mTile_Out;

                // Let the algorithm know that it needs to recalculate.
                // TODO: With only moving one tile at a time, could this be optimized to
                //       check if this tile is already in the path, or append it to the start
                //       of the path?
                mPathInvalidated = true;

                // Since the source has changed, this path can no longer be considered solved.
                mSolved = false;
            }
        }
        /// <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;
        }
 /// <summary>
 /// These nodes get used over and over again, so we need to make sure they
 /// get completely reset between uses.
 /// </summary>
 public void Reset()
 {
     mTile          = null;
     mPrev          = null;
     mCostFromStart = 0;
     mCostToEnd     = 0;
     mReached       = false;
     mPathSolved    = false;
 }
        /// <summary>
        /// Clear out the destination that the planner is trying to reach. This doubles as a way
        /// to stop the planner from trying to advance.
        /// </summary>
        public void ClearDestination()
        {
            mDestinationTile = null;
            mSolved          = false;

            // Release the Nodes. We don't need them anymore. If a new destination
            // is set, we will need to start from scratch anyway.
            ClearNodeLists();
        }
        /// <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);
            }
        }
Exemple #9
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);
        }
        /// <summary>
        /// Update the location that the Planner is attempting to reach. The position will be used
        /// to look up the tile at that position in the world.
        /// </summary>
        /// <param name="destination"></param>
        public void SetDestination(Vector2 destination)
        {
            mGetTileAtPositionMsg.mPosition_In = destination;
            World.WorldManager.pInstance.pCurrentLevel.OnMessage(mGetTileAtPositionMsg);

            if (mDestinationTile != mGetTileAtPositionMsg.mTile_Out)
            {
                mDestination     = destination;
                mPathInvalidated = true;
                mSolved          = false;

                // We will need to do a couple checks to see if we have found the destination tile, so cache that
                // now.
                mDestinationTile = mGetTileAtPositionMsg.mTile_Out;
            }
        }
Exemple #11
0
        /// <summary>
        /// Once the Flood Fill has been started with a call to FloodFill, calling this function
        /// will do the actual flooding.
        /// </summary>
        public void ProcessFill()
        {
            // Go through all the tiles that we determined as value tiles to change, and change them.
            if (mTilesToChangeFinal.Count > 0)
            {
                Level.Tile tile = mTilesToChangeFinal[0];

                // Create a new game object and place it at the location of the tile.
                GameObject newFloor = GameObjectFactory.pInstance.GetTemplate(mGameObjectTemplateName);

                newFloor.pPosition = tile.mCollisionRect.pCenterPoint;

                GameObjectManager.pInstance.Add(newFloor);

                mTilesToChangeFinal.RemoveAt(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);
        }
        /// <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);
        }
Exemple #14
0
    private Color GetTileColor(Level.Tile tile)
    {
        switch (tile)
        {
        case Level.Tile.Path:
            return(new Color(0.827f, 0.827f, 0.827f));       // Light grey

        case Level.Tile.Buildable:
            return(Color.grey);

        case Level.Tile.Spawner:
            return(Color.red);

        case Level.Tile.Goal:
            return(Color.green);

        case Level.Tile.Empty:
            return(Color.black);

        default:
            return(Color.magenta);
        }
    }
        /// <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);
            }
        }
        /// <summary>
        /// Does a search for a valid location to try to patrol to.
        /// </summary>
        private void FindNextRepairPoint()
        {
            // How far do we want to search for damaged walls?
            // This is in "tiles" not pixels.
            const int range      = 16;
            const int range_half = range / 2;

            // Start by finding a center point for the search. For that we find a random
            // spot in the safe house.
            List <GameObject> safeHouses = GameObjectManager.pInstance.GetGameObjectsOfClassification(GameObjectDefinition.Classifications.SAFE_HOUSE);
            Int32             index      = RandomManager.pInstance.pRand.Next(safeHouses.Count);

            // We are going to search for damaged walls in a square around that safehouse position.
            // We also need to convert it to world space.
            Vector2 startPos = safeHouses[index].pPosition - new Vector2(range_half * 8.0f, range_half * 8.0f);

            mDebugOriginPos = safeHouses[index].pPosition;

            mGetTileAtPositionMsg.mPosition_In = startPos;

            WorldManager.pInstance.pCurrentLevel.OnMessage(mGetTileAtPositionMsg, pParentGOH);

            // The top left most tile in our search.
            Level.Tile tile = mGetTileAtPositionMsg.mTile_Out;

            mDamagedTiles.Clear();

            // Loop through the tiles surrounding the randomly chosen center point.
            for (Int32 y = 0; y < range && tile != null; y++)
            {
                // The tile a the start of this row.
                Level.Tile startTile = tile;

                for (Int32 x = 0; x < range && tile != null; x++)
                {
                    // Is this a wall?
                    if (tile.mType == Level.Tile.TileTypes.Solid)
                    {
                        // The level only stores collision info, but we need the actual wall object so that
                        // we can check its health levels.
                        mFoundTileObjects.Clear();
                        GameObjectManager.pInstance.GetGameObjectsInRange(tile.mCollisionRect.pCenterPoint, 1.0f, ref mFoundTileObjects, mTileClassifications);

                        System.Diagnostics.Debug.Assert(mFoundTileObjects.Count == 1, "Unexpected number of objects at tile position: " + mFoundTileObjects.Count);

                        // This should never happen.
                        if (null != mFoundTileObjects[0])
                        {
                            mFoundTileObjects[0].OnMessage(mGetHealthMsg, pParentGOH);

                            // Is this wall damaged?
                            if (mGetHealthMsg.mCurrentHealth_Out < mGetHealthMsg.mMaxHealth_Out)
                            {
                                // Add it to a list so that a random damaged wall can be chosen.
                                mDamagedTiles.Add(tile);
                            }
                        }
                    }

                    // Move to the next tile.
                    tile = tile.mAdjecentTiles[(Int32)Level.Tile.AdjacentTileDir.RIGHT];
                }

                // Move down to the start of the next row.
                tile = startTile.mAdjecentTiles[(Int32)Level.Tile.AdjacentTileDir.DOWN];
            }

            bool tileFound = false;

            // Keep goind until we find a valid tile, or all posibilites have been exhusted.
            while (mDamagedTiles.Count > 0 && !tileFound)
            {
                // Pick a random tile to try.
                Int32 damagedTileIndex = RandomManager.pInstance.pRand.Next(mDamagedTiles.Count);

                Level.Tile chosenTile = mDamagedTiles[damagedTileIndex];

                // Remove it from the list so that it can't be considered again if it is not
                // chosen now.
                mDamagedTiles.RemoveAt(damagedTileIndex);

                // Loop through all surrounding tiles to see if any of them are empty.
                // A tile cannot be repaired if the character cannot stand next to it.
                for (UInt32 tileIndex = (UInt32)Level.Tile.AdjacentTileDir.START_HORZ; (tileIndex < (UInt32)Level.Tile.AdjacentTileDir.NUM_DIRECTIONS); tileIndex += 1)
                {
                    // Is the tile next to the damaged one empty, giving us a place to stand?
                    if (null != chosenTile.mAdjecentTiles[tileIndex] &&
                        chosenTile.mAdjecentTiles[tileIndex].mType == Level.Tile.TileTypes.Empty)
                    {
                        Level.Tile nearby = chosenTile.mAdjecentTiles[tileIndex];

                        // On the offset chance that the Follow Behaviour already has a target set.
                        mSetTargetObjectMsg.mTarget_In = null;
                        pParentGOH.OnMessage(mSetTargetObjectMsg);

                        mSetDestinationMsg.mDestination_In = nearby.mCollisionRect.pCenterPoint;
                        pParentGOH.OnMessage(mSetDestinationMsg);

                        mSetSourceMsg.mSource_In = pParentGOH.pCollisionRect.pCenterPoint;
                        pParentGOH.OnMessage(mSetSourceMsg);

                        // Store for debug drawing.
                        mDebugStart = pParentGOH.pPosition;
                        mDebugEnd   = nearby.mCollisionRect.pCenterPoint;

                        mFoundTileObjects.Clear();
                        GameObjectManager.pInstance.GetGameObjectsInRange(chosenTile.mCollisionRect.pCenterPoint, 1.0f, ref mFoundTileObjects, mTileClassifications);

                        System.Diagnostics.Debug.Assert(mFoundTileObjects.Count == 1, "Unexpected number of objects at tile position: " + mFoundTileObjects.Count);

                        // This should never happen.
                        if (null != mFoundTileObjects[0])
                        {
                            mSetTileToRepairMsg.mTile_In = mFoundTileObjects[0];
                            pParentGOH.OnMessage(mSetTileToRepairMsg);
                        }

                        tileFound = true;
                    }
                }
            }

            mNoValidTargets = !tileFound;
        }
        /// <summary>
        /// Put the Node back into a default state.
        /// </summary>
        public override void Reset()
        {
            mTile = null;

            base.Reset();
        }
 /// <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;
 }
 /// <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));
 }
Exemple #20
0
    public override void OnInspectorGUI()
    {
        serializedObject.Update();

        EditorGUILayout.PropertyField(m_PreviewSpriteProperty);
        EditorGUILayout.PropertyField(m_PathTileProperty);
        EditorGUILayout.PropertyField(m_BuildableTileProperty);
        EditorGUILayout.PropertyField(m_SpawnerTileProperty);
        EditorGUILayout.PropertyField(m_GoalTileProperty);
        EditorGUILayout.PropertyField(m_HeightProperty);
        EditorGUILayout.PropertyField(m_WidthProperty);


        if (m_HeightProperty.intValue != m_TargetLevel.m_Map.GetLength(0) || m_WidthProperty.intValue != m_TargetLevel.m_Map.GetLength(1))
        {
            // Height or width changed, so provide a button to update the map
            if (GUILayout.Button("Update Map Dimensions"))
            {
                Level.Tile[,] newMap = new Level.Tile[m_HeightProperty.intValue, m_WidthProperty.intValue];

                // Copy what can be copied from old map, and either discard excess or fill new space with empty tiles
                for (int row = 0; row < newMap.GetLength(0); row++)
                {
                    for (int col = 0; col < newMap.GetLength(1); col++)
                    {
                        if (row < m_TargetLevel.m_Map.GetLength(0) && col < m_TargetLevel.m_Map.GetLength(1))
                        {
                            // This square is within the old map bounds, so just copy the old value
                            newMap[row, col] = m_TargetLevel.m_Map[row, col];
                        }
                        else
                        {
                            // This square is outside the old map bounds, so fill with empty
                            newMap[row, col] = Level.Tile.Empty;
                        }
                    }
                }

                m_TargetLevel.m_Map = newMap;
            }
        }

        EditorGUILayout.Space();

        EditorGUILayout.LabelField("Map (" + m_TargetLevel.m_Map.GetLength(0) + "x" + m_TargetLevel.m_Map.GetLength(1) + ")", EditorStyles.boldLabel);

        m_WaypointListScrollPosition = EditorGUILayout.BeginScrollView(m_WaypointListScrollPosition);
        m_WaypointsList.DoLayoutList();
        EditorGUILayout.EndScrollView();

        EditorGUILayout.LabelField("Place");
        m_SelectedTileToPlace = GUILayout.SelectionGrid(m_SelectedTileToPlace, m_TileNames, m_TileNames.Length);

        EditorGUILayout.Space();

        EditorGUILayout.LabelField("Mouse Position", "Col(X): " + m_MouseCol + ", Row(Y): " + m_MouseRow);
        m_MapScrollPosition = EditorGUILayout.BeginScrollView(m_MapScrollPosition);

        GUILayoutUtility.GetRect(
            m_PreviewBuffer * 2 + m_TileSize * m_TargetLevel.m_Map.GetLength(1) + m_TargetLevel.m_Map.GetLength(1),
            m_PreviewBuffer * 2 + m_TileSize * m_TargetLevel.m_Map.GetLength(0) + m_TargetLevel.m_Map.GetLength(0)
            );

        for (int row = 0; row < m_TargetLevel.m_Map.GetLength(0); row++)
        {
            for (int col = 0; col < m_TargetLevel.m_Map.GetLength(1); col++)
            {
                Rect rect = new Rect(
                    m_PreviewBuffer + m_TileSize * col + col,
                    m_PreviewBuffer + m_TileSize * row + row,
                    m_TileSize,
                    m_TileSize
                    );

                if (Event.current.isMouse && rect.Contains(Event.current.mousePosition))
                {
                    if (Event.current.button == 0)
                    {
                        // The left mouse button was clicked, so change this tile to the currently selected tile
                        Level.Tile newTile = (Level.Tile)m_SelectedTileToPlace;

                        if (newTile == Level.Tile.Spawner || newTile == Level.Tile.Goal)
                        {
                            // Attempting to place a spawner or goal, so remove any pre-existing ones
                            for (int r = 0; r < m_TargetLevel.m_Map.GetLength(0); r++)
                            {
                                for (int c = 0; c < m_TargetLevel.m_Map.GetLength(1); c++)
                                {
                                    if (m_TargetLevel.m_Map[r, c] == newTile)
                                    {
                                        // Found a pre-existing spawner or goal, so remove it
                                        m_TargetLevel.m_Map[r, c] = Level.Tile.Empty;
                                    }
                                }
                            }

                            if (newTile == Level.Tile.Spawner)
                            {
                                // Move first waypoint to new spawner location

                                if (!m_SpawnerPlaced)
                                {
                                    if (m_WaypointsProperty.arraySize < 2)
                                    {
                                        // There's less than 2 waypoints, so add a new one for the spawner
                                        m_WaypointsProperty.InsertArrayElementAtIndex(0);
                                    }

                                    m_SpawnerPlaced = true;
                                }

                                SerializedProperty spawnerWaypoint = m_WaypointsProperty.GetArrayElementAtIndex(0);
                                spawnerWaypoint.vector2IntValue = new Vector2Int(col, row);
                            }
                            else if (newTile == Level.Tile.Goal)
                            {
                                // Move last waypoint to new goal location
                                if (!m_GoalPlaced)
                                {
                                    if (m_WaypointsProperty.arraySize < 2)
                                    {
                                        // There's less than 2 waypoints, so add a new one for the goal
                                        m_WaypointsProperty.InsertArrayElementAtIndex(m_WaypointsProperty.arraySize - 1);
                                    }

                                    m_GoalPlaced = true;
                                }
                                SerializedProperty goalWaypoint = m_WaypointsProperty.GetArrayElementAtIndex(m_WaypointsProperty.arraySize - 1);
                                goalWaypoint.vector2IntValue = new Vector2Int(col, row);
                            }
                        }

                        m_TargetLevel.m_Map[row, col] = newTile;
                        // Also need to update serialized property for serializablemap, otherwise all changes will be lost on restart for some reason...
                        m_SerializableMapProperty.GetArrayElementAtIndex(row * m_TargetLevel.m_Map.GetLength(1) + col).enumValueIndex = (int)newTile;
                    }
                    else if (Event.current.button == 1)
                    {
                        // The right mouse button was clicked, so replace this tile with empty
                        m_TargetLevel.m_Map[row, col] = Level.Tile.Empty;
                    }
                }

                if (rect.Contains(Event.current.mousePosition))
                {
                    m_MouseRow = row;
                    m_MouseCol = col;
                }

                EditorGUI.DrawRect(rect,
                                   GetTileColor(m_TargetLevel.m_Map[row, col])
                                   );
            }
        }

        Handles.BeginGUI();
        Handles.color = Color.yellow;
        List <Vector3> path = new List <Vector3>();

        foreach (Vector2Int point in m_TargetLevel.m_Waypoints)
        {
            path.Add(new Vector3(
                         m_PreviewBuffer + m_TileSize * point.x + point.x + m_TileSize / 2,
                         m_PreviewBuffer + m_TileSize * point.y + point.y + m_TileSize / 2
                         )
                     );
            EditorGUI.DrawRect(new Rect(
                                   m_PreviewBuffer + m_TileSize * point.x + point.x + m_TileSize / 2 - m_TileSize / 10,
                                   m_PreviewBuffer + m_TileSize * point.y + point.y + m_TileSize / 2 - m_TileSize / 10,
                                   m_TileSize / 5,
                                   m_TileSize / 5),
                               Color.yellow
                               );
        }
        Handles.DrawPolyLine(path.ToArray());
        Handles.EndGUI();

        EditorGUILayout.EndScrollView();

        serializedObject.ApplyModifiedProperties();

        Repaint();
    }
Exemple #21
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);
        }