private static string GetMesssage(string propertyName, CellMetadata existingMetadata, CellMetadata newMetadata)
 {
     return
         (string.Format(
              "{0} property has its metadata set to '{1}'. Can't apply the new metadata '{2}' to {0} property!",
              propertyName, existingMetadata, newMetadata));
 }
Example #2
0
    /// <summary>
    /// Add entrance/exit stairs in rooms furthest from one-another
    /// </summary>
    private void BuildEntranceAndExit()
    {
        // This'll be lazy for now - we scan for the first hit of a ROOM_FLOOR
        // and make that the entrance. We then continue that scan to find the
        // furthest ROOM_FLOOR for an exit.
        float lastDistance = -1;
        float distance;
        int   x;
        int   z;

        entranceCell = null;
        exitCell     = null;

        // Search for a dungeon entrance
        for (x = 0; x < size.x && entranceCell == null; x++)
        {
            for (z = 0; z < size.z && entranceCell == null; z++)
            {
                // If it's a room floor that isn't next to any walls, select it
                if (cells[x, z].Type == CellType.ROOM_FLOOR &&
                    GetAdjacentCells(cells[x, z], false, CellType.ROOM_FLOOR).Count == 4
                    )
                {
                    entranceCell      = cells[x, z];
                    entranceCell.Type = CellType.ENTRANCE;
                }
            }
        }

        // Search for a possible exit furthest from the entrance
        for (x = 0; x < size.x; x++)
        {
            for (z = 0; z < size.z; z++)
            {
                if (cells[x, z].Type == CellType.ROOM_FLOOR &&
                    GetAdjacentCells(cells[x, z], false, CellType.ROOM_FLOOR).Count == 4
                    )
                {
                    // If it's further than the last exit, use
                    distance = Vector2.Distance(
                        cells[x, z].Position.ToVector2(),
                        entranceCell.Position.ToVector2()
                        );

                    if (distance > lastDistance)
                    {
                        exitCell = cells[x, z];
                    }
                }
            }
        }

        // Finalize new cell types
        exitCell.Type = CellType.EXIT;
    }
Example #3
0
    /// <summary>
    /// Determine if a cell is considered isolated (walls on all sides)
    /// </summary>
    /// <param name="position"></param>
    /// <returns></returns>
    private bool IsIsolatedCell(CellMetadata cell)
    {
        foreach (CellMetadata adjacent in GetAdjacentCells(cell, true))
        {
            if (adjacent.Type != CellType.WALL)
            {
                return(false);
            }
        }

        return(true);
    }
Example #4
0
    /// <summary>
    /// Perform a Tree Growing maze algorithm originating from the given (x, z)
    /// to fill all unoccupied (walled) space s.t. all halls are walled in
    /// </summary>
    /// <param name="start"></param>
    private void TreeGrowingFloodFill(CellMetadata start)
    {
        List <CellMetadata> frontier = new List <CellMetadata>();
        List <IntVector2>   validDirections;
        IntVector2?         lastDirection = null;
        CellMetadata        cell;

        // Use a new region for this flood fill maze
        currentRegion += 1;

        // Carve out starting point
        CarveHall(start);
        frontier.Add(start);

        // Loop until we run out of cells we can carve into
        while (frontier.Count > 0)
        {
            // Pick one at "random" (this implementation uses the last
            // in the stack to continue digging until it's at a dead end)
            cell            = frontier[frontier.Count - 1];
            validDirections = GetCarveableDirections(cell);

            if (validDirections.Count > 0)
            {
                // Either walk in our last direction (if it is a valid direction)
                // or walk in a random direction if the RNG god says so
                if (!lastDirection.HasValue ||
                    !validDirections.Contains(lastDirection.Value) ||
                    Random.Range(0.0f, 1.0f) < hallwayRandomness
                    )
                {
                    lastDirection = validDirections[
                        Random.Range(0, validDirections.Count)
                                    ];
                }

                // Carve into the cell at the given direction
                CarveHall(GetCell(cell.Position + lastDirection.Value));

                // Carve into the cell after the cell at the given direction
                // and add that cell as another possible branching path
                cell = GetCell(cell.Position + lastDirection.Value * 2);
                CarveHall(cell);

                frontier.Add(cell);
            }
            else // Can't go anywhere from this cell, we're done using it
            {
                frontier.Remove(cell);
            }
        }
    }
Example #5
0
    /// <summary>
    /// Fill in dead end paths (uncarving)
    /// </summary>
    private void FillDeadEnds()
    {
        CellMetadata cell = FindDeadEnd();

        while (cell != null)
        {
            // Refill the cell
            cell.Type = CellType.WALL;

            // Search for the next one
            cell = FindDeadEnd();
        }
    }
Example #6
0
    /// <summary>
    /// Get all unique region IDs of adjacent cells, excluding any
    /// that don't have a region ID
    /// </summary>
    /// <param name="cell"></param>
    /// <returns></returns>
    private HashSet <int> GetAdjacentRegions(CellMetadata cell)
    {
        HashSet <int> regions = new HashSet <int>();

        foreach (CellMetadata adjacent in GetAdjacentCells(cell, false))
        {
            regions.Add(adjacent.Region);
        }

        // Remove any "no region" results
        regions.Remove(0);
        return(regions);
    }
Example #7
0
    /// <summary>
    /// Performs a Tree Growing Maze flood fill for every location
    /// we identify as fillable entry points (any isolated cell)
    /// </summary>
    private void BuildMazes()
    {
        CellMetadata cell = FindIsolatedCell();

        // lazy safety net until I can figure out unity coroutines
        int iterations = 0;

        while (cell != null && iterations < 100)
        {
            TreeGrowingFloodFill(cell);
            cell = FindIsolatedCell();

            iterations++;
        }
    }
Example #8
0
    /// <summary>
    /// Get all directions around the given cell that we can carve a path.
    /// </summary>
    /// <param name="cell"></param>
    /// <returns></returns>
    private List <IntVector2> GetCarveableDirections(CellMetadata cell)
    {
        List <IntVector2> directions = new List <IntVector2>();

        foreach (IntVector2 dir in IntVector2.Cardinals)
        {
            if (Contains(cell.Position + dir * 3))
            {
                if (GetCell(cell.Position + dir * 2).Type == CellType.WALL)
                {
                    directions.Add(dir);
                }
            }
        }

        return(directions);
    }
Example #9
0
    /// <summary>
    /// Get all cells adjacent in the cardinal (and intercardinal) directions of the given cell
    /// </summary>
    /// <param name="cell"></param>
    /// <param name="includeIntercardinals"></param>
    /// <returns></returns>
    private List <CellMetadata> GetAdjacentCells(
        CellMetadata cell,
        bool includeIntercardinals,
        CellType?typeFilter = null
        )
    {
        List <CellMetadata> adjacencyList = new List <CellMetadata>();
        CellMetadata        adjacent;
        IntVector2          position;

        // Grab cells in all cardinal directions
        foreach (IntVector2 direction in IntVector2.Cardinals)
        {
            position = cell.Position + direction;
            if (Contains(position))
            {
                adjacent = GetCell(position);

                if (!typeFilter.HasValue || adjacent.Type == typeFilter.Value)
                {
                    adjacencyList.Add(adjacent);
                }
            }
        }

        // If they ask for intercardinals, grab those too
        if (includeIntercardinals)
        {
            foreach (IntVector2 direction in IntVector2.Intercardinals)
            {
                position = cell.Position + direction;
                if (Contains(position))
                {
                    adjacent = GetCell(position);

                    if (!typeFilter.HasValue || adjacent.Type == typeFilter.Value)
                    {
                        adjacencyList.Add(adjacent);
                    }
                }
            }
        }

        return(adjacencyList);
    }
Example #10
0
    /// <summary>
    /// Run through the full generator process
    /// </summary>
    public void Generate()
    {
        // Run sanity checks before we do anything
        CheckConfigurations();

        currentRegion = 0;

        if (randomSeed != 0)
        {
            Random.InitState(randomSeed);
        }

        cells     = new CellMetadata[size.x, size.z];
        openCells = new List <CellMetadata>();

        // Initialize everything in the cell metadata array
        for (int x = 0; x < size.x; x++)
        {
            for (int z = 0; z < size.z; z++)
            {
                cells[x, z] = new CellMetadata(new IntVector2(x, z));
            }
        }

        // Populate the dungeon with open rooms
        BuildRooms();

        // In open spaces between rooms, generate perfect mazes
        BuildMazes();

        // Bridge rooms and mazes with doors
        BuildDoors();

        // Fill in dead ends of the maze halls, if chosen
        if (fillDeadEnds)
        {
            FillDeadEnds();
        }

        // Build an entrance and exit point
        BuildEntranceAndExit();

        // Finally, generate prefabs for every cell
        BuildPrefabs();
    }
Example #11
0
    /// <summary>
    /// Place a prefab from the cell metadata in the given position
    /// </summary>
    /// <param name="position"></param>
    private void CreatePrefab(IntVector2 position)
    {
        CellMetadata cell = cells[position.x, position.z];
        DungeonCell  gameObject;

        // Load a different prefab based on the cell type
        switch (cell.Type)
        {
        case CellType.DOOR:
            gameObject = Instantiate(doorPrefab) as DungeonCell;
            break;

        case CellType.HALL_FLOOR:
            gameObject = Instantiate(hallFloorPrefab) as DungeonCell;
            break;

        case CellType.ROOM_FLOOR:
            gameObject = Instantiate(roomFloorPrefab) as DungeonCell;
            break;

        case CellType.ENTRANCE:
            gameObject = Instantiate(entrancePrefab) as DungeonCell;
            break;

        case CellType.EXIT:
            gameObject = Instantiate(exitPrefab) as DungeonCell;
            break;

        default:     // Assume to be a wall
            gameObject = Instantiate(wallPrefab) as DungeonCell;
            break;
        }

        gameObject.LoadMetadata(cell);

        // Transform cell to a local space position relative to its (x, z)
        // Ensuring the parent Dungeon is centered in the generated cells
        gameObject.transform.parent        = transform;
        gameObject.transform.localPosition = new Vector3(
            position.x - size.x * 0.5f + 0.5f,
            0f,
            position.z - size.z * 0.5f + 0.5f
            );
    }
Example #12
0
    /// <summary>
    /// Returns true if the cell has only one exit
    /// </summary>
    /// <param name="cell"></param>
    /// <returns></returns>
    private bool IsDeadEnd(CellMetadata cell)
    {
        // Ignore walls that are surrounded by walls and rooms that are 1x1
        if (cell.Type == CellType.WALL || cell.Type == CellType.ROOM_FLOOR)
        {
            return(false);
        }

        int walls = 0;

        foreach (CellMetadata adjacent in GetAdjacentCells(cell, false))
        {
            if (adjacent.Type == CellType.WALL)
            {
                walls++;
            }
        }

        return(walls > 2);
    }
Example #13
0
    /// <summary>
    /// Return the spawn point for a player entering this dungeon
    /// </summary>
    /// <returns></returns>
    public Vector3 GetSpawnPoint()
    {
        // Spawn in a cell adjacent to the entrance
        List <CellMetadata> adjacent = GetAdjacentCells(entranceCell, true);
        CellMetadata        selected = null;

        foreach (CellMetadata cell in adjacent)
        {
            if (cell.Type != CellType.WALL)
            {
                selected = cell;
                break;
            }
        }

        return(new Vector3(
                   selected.Position.x - size.x * 0.5f + 0.5f,
                   0.2f,
                   selected.Position.z - size.z * 0.5f + 0.5f
                   ));
    }
Example #14
0
        public void AddCellMetadata(string propertyName, string fileName, string sheetName, string columnLetter,
                                    int rowNumber)
        {
            if (string.IsNullOrWhiteSpace(propertyName))
            {
                throw new ArgumentException("Value cannot be null or whitespace.", nameof(propertyName));
            }

            var metadataToAdd = new CellMetadata(
                fileName,
                sheetName,
                columnLetter,
                rowNumber
                );

            if (_metadataDictionary.ContainsKey(propertyName))
            {
                throw new PropertyCanHaveOnlyOneCellMetadataException(propertyName, _metadataDictionary[propertyName],
                                                                      metadataToAdd);
            }

            _metadataDictionary.Add(propertyName, metadataToAdd);
        }
Example #15
0
 /// <summary>
 /// Mark a cell as a floor for the current hall
 /// </summary>
 /// <param name="cell"></param>
 private void CarveHall(CellMetadata cell)
 {
     cell.Type   = CellType.HALL_FLOOR;
     cell.Region = currentRegion;
 }
 public PropertyCanHaveOnlyOneCellMetadataException(string propertyName, CellMetadata existingMetadata,
                                                    CellMetadata newMetadata)
     : base(GetMesssage(propertyName, existingMetadata, newMetadata))
 {
 }
Example #17
0
 /// <summary>
 /// Mark a cell as a door that can be passed through
 /// </summary>
 /// <param name="cell"></param>
 private void CarveDoor(CellMetadata cell)
 {
     cell.Type = CellType.DOOR;
 }