Пример #1
0
        public Result Run(LevelDescriptionGrid2D levelDescription)
        {
            var graph        = levelDescription.GetGraph();
            var nonTreeEdges = graph.Edges.Count() - graph.VerticesCount + 1;
            var result       = new Result();

            result.NumberOfCycles = nonTreeEdges;

            if (nonTreeEdges >= 2)
            {
                result.IsPotentialProblem = true;
                var sb = new StringBuilder();

                sb.AppendLine($"It seems like the level graph has at least {nonTreeEdges} cycles.");
                sb.AppendLine($"The larger the number of cycles, the harder it is for the generator to produce a level.");
                sb.AppendLine($"Graphs without cycles are the easiest for the algorithm to generate. It is usually recommended to have at most 2 cycles.");
                sb.AppendLine($"If you want to see whether the number of cycles causes is too high for the generator, try removing some of the cycles and see if/how much the performance changes.");
                sb.AppendLine($"Or if you really want to have cycles in your levels, make sure that your room templates have as many available door positions as possible.");

                result.Summary = sb.ToString();
            }
            else
            {
                result.IsPotentialProblem = false;
            }

            return(result);
        }
Пример #2
0
        public void Run()
        {
            // Create square room template
            var squareRoomTemplate = new RoomTemplateGrid2D(
                PolygonGrid2D.GetSquare(8),
                new SimpleDoorModeGrid2D(doorLength: 1, cornerDistance: 1)
                );

            // Create rectangle room template
            var rectangleRoomTemplate = new RoomTemplateGrid2D(
                PolygonGrid2D.GetRectangle(6, 10),
                new SimpleDoorModeGrid2D(doorLength: 1, cornerDistance: 1)
                );

            // Create a room description which says that the room is not a corridor and that it can use the two room templates
            var roomDescription = new RoomDescriptionGrid2D(
                isCorridor: false,
                roomTemplates: new List <RoomTemplateGrid2D>()
            {
                squareRoomTemplate, rectangleRoomTemplate
            }
                );

            // Create an instance of the level description
            var levelDescription = new LevelDescriptionGrid2D <int>();

            // Add 4 rooms to the level, use the room description that we created beforehand
            levelDescription.AddRoom(0, roomDescription);
            levelDescription.AddRoom(1, roomDescription);
            levelDescription.AddRoom(2, roomDescription);
            levelDescription.AddRoom(3, roomDescription);

            // Add connections between the rooms - the level graph will be a cycle with 4 vertices
            levelDescription.AddConnection(0, 1);
            levelDescription.AddConnection(0, 3);
            levelDescription.AddConnection(1, 2);
            levelDescription.AddConnection(2, 3);

            // Create an instance of the generate and generate a layout
            var generator = new GraphBasedGeneratorGrid2D <int>(levelDescription);
            var layout    = generator.GenerateLayout();

            // Export the resulting layout as PNG
            var drawer = new DungeonDrawer <int>();

            drawer.DrawLayoutAndSave(layout, "simple_layout.png", new DungeonDrawerOptions()
            {
                Width  = 2000,
                Height = 2000,
            });

            var layout2 = generator.GenerateLayout();

            // Export the resulting layout as PNG
            drawer.DrawLayoutAndSave(layout, "simple_layout_2.png", new DungeonDrawerOptions()
            {
                Width  = 2000,
                Height = 2000,
            });
        }
Пример #3
0
 public BasicLayoutConverterGrid2D(
     LevelDescriptionGrid2D <TNode> mapDescription,
     ConfigurationSpacesGrid2D <TConfiguration, RoomNode <TNode> > configurationSpaces,
     TwoWayDictionary <RoomTemplateInstanceGrid2D, IntAlias <PolygonGrid2D> > intAliasMapping
     )
 {
     MapDescription      = mapDescription;
     ConfigurationSpaces = configurationSpaces;
     IntAliasMapping     = intAliasMapping;
 }
        public IGeneratorRunner GetGeneratorRunner(LevelDescriptionGrid2D <TNode> levelDescription)
        {
            if (benchmarkInitialization)
            {
                return(GetGeneratorRunnerWithInit(levelDescription));
            }

            var configuration = this.configuration.SmartClone();

            configuration.RoomsCanTouch = levelDescription.MinimumRoomDistance == 0;

            var mapDescription = levelDescription.GetMapDescription();

            var chainDecompositionOld = new BreadthFirstChainDecompositionOld <TNode>();
            var chainDecomposition    = new TwoStageChainDecomposition <TNode>(mapDescription, chainDecompositionOld);

            configuration.Chains = chainDecomposition.GetChains(levelDescription.GetGraph());
            configuration.SimulatedAnnealingConfiguration = new SimulatedAnnealingConfigurationProvider(new SimulatedAnnealingConfiguration()
            {
                MaxIterationsWithoutSuccess = 10000,
            });

            var layoutDrawer = new SVGLayoutDrawer <TNode>();

            var layoutGenerator = new DungeonGenerator <TNode>(mapDescription, configuration);

            layoutGenerator.InjectRandomGenerator(new Random(0));

            return(new LambdaGeneratorRunner(() =>
            {
                var simulatedAnnealingArgsContainer = new List <SimulatedAnnealingEventArgs>();

                void SimulatedAnnealingEventHandler(object sender, SimulatedAnnealingEventArgs eventArgs)
                {
                    simulatedAnnealingArgsContainer.Add(eventArgs);
                }

                layoutGenerator.OnSimulatedAnnealingEvent += SimulatedAnnealingEventHandler;
                var layout = layoutGenerator.GenerateLayout();
                layoutGenerator.OnSimulatedAnnealingEvent -= SimulatedAnnealingEventHandler;

                var additionalData = new AdditionalRunData <TNode>()
                {
                    SimulatedAnnealingEventArgs = simulatedAnnealingArgsContainer,
                    GeneratedLayoutSvg =
                        layout != null ? layoutDrawer.DrawLayout(layout, 800, forceSquare: true) : null,
                    GeneratedLayout = layout,
                };

                var generatorRun = new GeneratorRun <AdditionalRunData <TNode> >(layout != null, layoutGenerator.TimeTotal,
                                                                                 layoutGenerator.IterationsCount, additionalData);

                return generatorRun;
            }));
        }
Пример #5
0
        public static List <IDiagnosticResult> Run(LevelDescriptionGrid2D levelDescription)
        {
            var results = new List <IDiagnosticResult>();

            results.Add(new DifferentLengthsOfDoors().Run(levelDescription));
            results.Add(new WrongManualDoors().Run(levelDescription));
            results.Add(new NumberOfCycles().Run(levelDescription));
            results.Add(new NumberOfRooms().Run(levelDescription));

            return(results);
        }
Пример #6
0
        private IGeneratorRunner GetGeneratorRunnerWithInit(LevelDescriptionGrid2D <TNode> levelDescription)
        {
            var configuration = this.configuration.SmartClone();

            configuration.RoomsCanTouch = levelDescription.MinimumRoomDistance == 0;

            configuration.RepeatModeOverride = levelDescription.RoomTemplateRepeatModeOverride;
            if (levelDescription.RoomTemplateRepeatModeDefault.HasValue)
            {
                throw new ArgumentException("Default repeat mode not supported");
            }

            var layoutDrawer  = new SVGLayoutDrawer <TNode>();
            var seedGenerator = new Random();

            var mapDescription = levelDescription.GetMapDescription();

            return(new LambdaGeneratorRunner(() =>
            {
                var stopwatch = new Stopwatch();
                stopwatch.Start();

                var layoutGenerator = new DungeonGenerator <TNode>(mapDescription, configuration);
                layoutGenerator.InjectRandomGenerator(new Random(seedGenerator.Next()));

                var simulatedAnnealingArgsContainer = new List <SimulatedAnnealingEventArgs>();

                void SimulatedAnnealingEventHandler(object sender, SimulatedAnnealingEventArgs eventArgs)
                {
                    simulatedAnnealingArgsContainer.Add(eventArgs);
                }

                layoutGenerator.OnSimulatedAnnealingEvent += SimulatedAnnealingEventHandler;
                var layout = layoutGenerator.GenerateLayout();
                layoutGenerator.OnSimulatedAnnealingEvent -= SimulatedAnnealingEventHandler;

                stopwatch.Stop();

                var additionalData = new AdditionalRunData <TNode>()
                {
                    SimulatedAnnealingEventArgs = simulatedAnnealingArgsContainer,
                    GeneratedLayoutSvg =
                        layout != null ? layoutDrawer.DrawLayout(layout, 800, forceSquare: true) : null,
                    GeneratedLayout = layout,
                };

                var generatorRun = new GeneratorRun <AdditionalRunData <TNode> >(layout != null, stopwatch.ElapsedMilliseconds,
                                                                                 layoutGenerator.IterationsCount, additionalData);

                return generatorRun;
            }));
        }
Пример #7
0
        public override IEnumerator Process()
        {
            if (config.LevelGraph == null)
            {
                throw new ArgumentException("LevelGraph must not be null.");
            }

            if (config.LevelGraph.Rooms.Count == 0)
            {
                throw new ArgumentException("LevelGraph must contain at least one room.");
            }

            var levelDescription = new LevelDescriptionGrid2D();

            // Setup individual rooms
            foreach (var room in config.LevelGraph.Rooms)
            {
                levelDescription.AddRoom(room, GetRoomTemplates(room));
            }

            var typeOfRooms = config.LevelGraph.Rooms.First().GetType();

            // Add passages
            foreach (var connection in config.LevelGraph.Connections)
            {
                if (config.UseCorridors)
                {
                    var corridorRoom = (RoomBase)ScriptableObject.CreateInstance(typeOfRooms);

                    if (corridorRoom is Room basicRoom)
                    {
                        basicRoom.Name = "Corridor";
                    }

                    levelDescription.AddCorridorConnection(connection, corridorRoom,
                                                           GetRoomTemplates(config.LevelGraph.CorridorRoomTemplateSets, config.LevelGraph.CorridorIndividualRoomTemplates));
                }
                else
                {
                    levelDescription.AddConnection(connection);
                }
            }

            Payload.LevelDescription = levelDescription;

            yield return(null);
        }
Пример #8
0
        public Bitmap Draw(LevelDescriptionGrid2D <TRoom> levelDescription, int width, GraphBasedGeneratorGrid2D <TRoom> generator)
        {
            bitmap                = new Bitmap(width, width);
            graphics              = Graphics.FromImage(bitmap);
            this.generator        = generator;
            this.levelDescription = levelDescription;
            this.totalWidth       = width;

            var headingHeight = 0.1f;

            DrawBackground();
            DrawHeading(GetSubRect(GetMainRect(), 0f, 0f, 1f, headingHeight));
            DrawLayout1(GetSubRect(GetMainRect(), 0f, headingHeight + 0.025f, 1f, 1 - headingHeight - 0.025f));

            graphics.Dispose();
            return(bitmap);
        }
Пример #9
0
        private void loadedMapDescriptionsComboBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            var mapDescriptionFile = (string)loadedMapDescriptionsComboBox.SelectedItem;

            try
            {
                levelDescription = configLoader.LoadMapDescriptionFromResources(mapDescriptionFile).GetLevelDescription();
                levelDescription.MinimumRoomDistance = 1;
            }
            catch (Exception exception)
            {
                ShowSettingsError($"Map description could not be loaded. Exception: {exception.Message}. Inner exception: {exception.InnerException}");
                return;
            }

            usingUploaded = false;
            UpdateInfo();
        }
Пример #10
0
        private IGeneratorRunner GetGeneratorRunnerWithInit(LevelDescriptionGrid2D <TNode> levelDescription)
        {
            var configuration = this.configuration.SmartClone();

            configuration.RoomsCanTouch = levelDescription.MinimumRoomDistance == 0;

            var layoutDrawer  = new SVGLayoutDrawer <TNode>();
            var seedGenerator = new Random();

            return(new LambdaGeneratorRunner(() =>
            {
                var stopwatch = new Stopwatch();
                stopwatch.Start();

                var layoutGenerator = new GraphBasedGeneratorGrid2D <TNode>(levelDescription, configuration);
                layoutGenerator.InjectRandomGenerator(new Random(seedGenerator.Next()));

                var simulatedAnnealingArgsContainer = new List <SimulatedAnnealingEventArgs>();

                void SimulatedAnnealingEventHandler(object sender, SimulatedAnnealingEventArgs eventArgs)
                {
                    simulatedAnnealingArgsContainer.Add(eventArgs);
                }

                layoutGenerator.OnSimulatedAnnealingEvent += SimulatedAnnealingEventHandler;
                var layout = layoutGenerator.GenerateLayout();
                layoutGenerator.OnSimulatedAnnealingEvent -= SimulatedAnnealingEventHandler;

                stopwatch.Stop();

                var additionalData = new AdditionalRunData <TNode>()
                {
                    SimulatedAnnealingEventArgs = simulatedAnnealingArgsContainer,
                    //GeneratedLayoutSvg =
                    //    layout != null ? layoutDrawer.DrawLayout(layout, 800, forceSquare: true) : null,
                    // GeneratedLayout = layout,
                };

                var generatorRun = new GeneratorRun <AdditionalRunData <TNode> >(layout != null, stopwatch.ElapsedMilliseconds,
                                                                                 layoutGenerator.IterationsCount, additionalData);

                return generatorRun;
            }));
        }
Пример #11
0
        public Result Run(LevelDescriptionGrid2D levelDescription)
        {
            var result  = new Result();
            var mapping = levelDescription.GetPrefabToRoomTemplateMapping();
            var problematicRoomTemplates = new Dictionary <GameObject, int>();

            foreach (var pair in mapping)
            {
                var roomTemplate = pair.Value;
                var gameObject   = pair.Key;

                var individualResult = RoomTemplateDiagnostics.CheckWrongManualDoors(roomTemplate.Outline, roomTemplate.Doors, out var differentLengthsCount);
                if (individualResult.HasErrors)
                {
                    problematicRoomTemplates.Add(gameObject, differentLengthsCount);
                }
            }

            if (problematicRoomTemplates.Count != 0)
            {
                result.IsPotentialProblem       = true;
                result.ProblematicRoomTemplates = problematicRoomTemplates;
                var sb = new StringBuilder();

                sb.AppendLine($"There are room templates with a high probability of having an incorrect setup of manual doors.");
                sb.AppendLine($"The problematic room templates are:");

                foreach (var pair in problematicRoomTemplates)
                {
                    sb.AppendLine($"- \"{pair.Key.name}\" with {pair.Value} different lengths of doors");
                }

                sb.AppendLine($"Please go through these room templates and check that their setup is correct.");

                result.Summary = sb.ToString();
            }
            else
            {
                result.IsPotentialProblem = false;
            }

            return(result);
        }
Пример #12
0
        private LevelDescriptionGrid2D <RoomWrapper> GetWrappedLevelDescription(LevelDescriptionGrid2D <RoomBase> originalLevelDescription)
        {
            var levelDescription = new LevelDescriptionGrid2D <RoomWrapper>();

            var srcProperties = originalLevelDescription.GetType().GetProperties(
                BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty);
            var dstProperties = levelDescription.GetType().GetProperties(
                BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty);

            foreach (var srcProperty in srcProperties)
            {
                var dstProperty = dstProperties.First(x => x.Name == srcProperty.Name);

                if (dstProperty.CanWrite)
                {
                    dstProperty.SetValue(levelDescription, srcProperty.GetValue(originalLevelDescription));
                }
            }

            var id      = 0;
            var mapping = originalLevelDescription
                          .GetGraphWithoutCorridors()
                          .Vertices
                          .Select(x => (x, new RoomWrapper(id++, x.GetDisplayName())))
                          .ToDictionary(x => x.x, x => x.Item2);

            foreach (var pair in mapping)
            {
                levelDescription.AddRoom(pair.Value, originalLevelDescription.GetRoomDescription(pair.Key));
            }

            foreach (var edge in originalLevelDescription.GetGraphWithoutCorridors().Edges)
            {
                var from = mapping[edge.From];
                var to   = mapping[edge.To];

                levelDescription.AddConnection(from, to);
            }

            return(levelDescription);
        }
Пример #13
0
    /// <summary>
    /// Prepare level description.
    /// </summary>
    public LevelDescriptionGrid2D <int> GetLevelDescription()
    {
        RoomDescriptionGrid2D roomDescription         = GetRoomDescription();
        RoomDescriptionGrid2D startEndRoomDescription = GetStartEndRoomDescription();
        RoomDescriptionGrid2D corridorRoomDescription = GetCorridorDescription();

        var levelDescription = new LevelDescriptionGrid2D <int>();

        var i = 0;

        while (i < numberOfRooms * 2)
        {
            if (i == 0)
            {
                levelDescription.AddRoom(i, startEndRoomDescription);
            }
            else if (i == numberOfRooms * 2 - 2)
            {
                levelDescription.AddRoom(i, startEndRoomDescription);
                levelDescription.AddConnection(i - 1, i);
            }
            else
            {
                levelDescription.AddRoom(i, roomDescription);
                levelDescription.AddConnection(i - 1, i);
            }

            i++;

            if (i != numberOfRooms * 2 - 1)
            {
                levelDescription.AddRoom(i, corridorRoomDescription);
                levelDescription.AddConnection(i - 1, i);
            }

            i++;
        }

        return(levelDescription);
    }
Пример #14
0
        protected virtual LevelDescriptionGrid2D <int> GetLevelDescription(NamedGraph <int> namedGraph, List <int> corridorOffsets)
        {
            var withCorridors           = corridorOffsets[0] != 0;
            var corridorRoomDescription = withCorridors ? GetCorridorRoomDescription(corridorOffsets, roomTemplatesSet == RoomTemplatesSet.Original ? 1 : 2) : null;

            var levelDescription = new LevelDescriptionGrid2D <int>();

            levelDescription.MinimumRoomDistance = 0;

            var graph = namedGraph.Graph;

            foreach (var room in graph.Vertices)
            {
                var basicRoomDescription = GetBasicRoomDescription(graph, room);
                levelDescription.AddRoom(room, basicRoomDescription);
            }

            var counter = graph.VerticesCount;

            foreach (var connection in graph.Edges)
            {
                if (withCorridors)
                {
                    levelDescription.AddRoom(counter, corridorRoomDescription);
                    levelDescription.AddConnection(connection.From, counter);
                    levelDescription.AddConnection(connection.To, counter);
                    counter++;
                }
                else
                {
                    levelDescription.AddConnection(connection.From, connection.To);
                }
            }

            // var name = MapDescriptionUtils.GetInputName(namedGraph.Name, scale, withCorridors, corridorOffsets, canTouch);
            levelDescription.Name = namedGraph.Name;

            return(levelDescription);
        }
Пример #15
0
        public static LevelDescriptionGrid2D <TNode> GetLevelDescription <TNode>(this IMapDescription <TNode> mapDescription)
        {
            var levelDescription = new LevelDescriptionGrid2D <TNode>();
            var graph            = mapDescription.GetGraph();

            var corridorRoomDescriptions = new Dictionary <IRoomDescription, RoomDescriptionGrid2D>();
            var roomTemplateMapping      = new Dictionary <RoomTemplate, RoomTemplateGrid2D>();

            foreach (var room in graph.Vertices)
            {
                var roomDescription = mapDescription.GetRoomDescription(room);

                if (roomDescription.IsCorridor)
                {
                    if (corridorRoomDescriptions.TryGetValue(roomDescription, out var cached))
                    {
                        levelDescription.AddRoom(room, cached);
                    }
                    else
                    {
                        var corridorRoomDescription = new RoomDescriptionGrid2D(true, roomDescription.RoomTemplates.Select(x => GetNewRoomTemplate(x, roomTemplateMapping)).ToList());
                        corridorRoomDescriptions[roomDescription] = corridorRoomDescription;
                        levelDescription.AddRoom(room, corridorRoomDescription);
                    }
                }
                else
                {
                    levelDescription.AddRoom(room, new RoomDescriptionGrid2D(false, roomDescription.RoomTemplates.Select(x => GetNewRoomTemplate(x, roomTemplateMapping)).ToList()));
                }
            }

            foreach (var edge in graph.Edges)
            {
                levelDescription.AddConnection(edge.From, edge.To);
            }

            return(levelDescription);
        }
Пример #16
0
        public static IMapDescription <TNode> GetMapDescription <TNode>(this LevelDescriptionGrid2D <TNode> levelDescription)
        {
            var mapDescription = new MapDescription <TNode>();
            var graph          = levelDescription.GetGraph();

            var corridorRoomDescriptions = new Dictionary <RoomDescriptionGrid2D, CorridorRoomDescription>();
            var roomTemplateMapping      = new Dictionary <RoomTemplateGrid2D, RoomTemplate>();

            foreach (var room in graph.Vertices)
            {
                var roomDescription = levelDescription.GetRoomDescription(room);

                if (roomDescription.IsCorridor)
                {
                    if (corridorRoomDescriptions.TryGetValue(roomDescription, out var cached))
                    {
                        mapDescription.AddRoom(room, cached);
                    }
                    else
                    {
                        var corridorRoomDescription = new CorridorRoomDescription(roomDescription.RoomTemplates.Select(x => GetOldRoomTemplate(x, roomTemplateMapping)).ToList());
                        corridorRoomDescriptions[roomDescription] = corridorRoomDescription;
                        mapDescription.AddRoom(room, corridorRoomDescription);
                    }
                }
                else
                {
                    mapDescription.AddRoom(room, new BasicRoomDescription(roomDescription.RoomTemplates.Select(x => GetOldRoomTemplate(x, roomTemplateMapping)).ToList()));
                }
            }

            foreach (var edge in graph.Edges)
            {
                mapDescription.AddConnection(edge.From, edge.To);
            }

            return(mapDescription);
        }
Пример #17
0
        public Result Run(LevelDescriptionGrid2D levelDescription)
        {
            var graph    = levelDescription.GetGraph();
            var vertices = graph.VerticesCount;
            var result   = new Result();

            result.NumberOfRooms = vertices;

            if (vertices > 20)
            {
                result.IsPotentialProblem = true;
                var sb = new StringBuilder();
                sb.AppendLine($"The level graph has quite a lot of rooms ({vertices}).");
                sb.AppendLine($"The higher the number of rooms, the harder it is for the generator to produce a level.");
                sb.AppendLine($"If you want to have a lot of rooms in the level, it is best to limit the number of cycles.");
                result.Summary = sb.ToString();
            }
            else
            {
                result.IsPotentialProblem = false;
            }

            return(result);
        }
Пример #18
0
        private void uploadButton_Click(object sender, EventArgs e)
        {
            if (mapDescriptionFileDialog.ShowDialog() == DialogResult.OK)
            {
                var filename = mapDescriptionFileDialog.FileName;

                using (var sr = new StreamReader(filename))
                {
                    try
                    {
                        levelDescription = configLoader.LoadMapDescription(sr).GetLevelDescription();
                        levelDescription.MinimumRoomDistance = 1;
                    }
                    catch (Exception exception)
                    {
                        ShowSettingsError($"Map description could not be loaded. Exception: {exception.Message}");
                        return;
                    }

                    usingUploaded = true;
                    UpdateInfo();
                }
            }
        }
Пример #19
0
        public static DungeonGeneratorLevelGrid2D TransformLayout(LayoutGrid2D <RoomBase> layout, LevelDescriptionGrid2D levelDescription, GameObject rootGameObject)
        {
            // var layoutCenter = GetLayoutCenter(layout);
            var prefabToRoomTemplateMapping = levelDescription.GetPrefabToRoomTemplateMapping();
            var corridorToConnectionMapping = levelDescription.GetCorridorToConnectionMapping();

            // Prepare an object to hold instantiated room templates
            var roomTemplateInstancesRoot = new GameObject(GeneratorConstantsGrid2D.RoomsRootName);

            roomTemplateInstancesRoot.transform.parent = rootGameObject.transform;

            // Initialize rooms
            var layoutData  = new Dictionary <RoomBase, RoomInstanceGrid2D>();
            var layoutRooms = layout.Rooms.ToDictionary(x => x.Room, x => x);

            foreach (var layoutRoom in layoutRooms.Values)
            {
                var roomTemplatePrefab = prefabToRoomTemplateMapping.GetByValue(layoutRoom.RoomTemplate);

                // Instantiate room template
                var roomTemplateInstance = Object.Instantiate(roomTemplatePrefab);
                roomTemplateInstance.transform.SetParent(roomTemplateInstancesRoot.transform);
                roomTemplateInstance.name = $"{layoutRoom.Room.GetDisplayName()} - {roomTemplatePrefab.name}";

                // Compute correct room position
                var position = layoutRoom.Position.ToUnityIntVector3();
                roomTemplateInstance.transform.position = position;

                // Correct the position based on the grid
                // This is important when there is some cell spacing or when the level is isometric
                var tilemapsHolder = roomTemplateInstance.transform.Find(GeneratorConstantsGrid2D.TilemapsRootName).gameObject;
                if (tilemapsHolder != null)
                {
                    var grid = tilemapsHolder.GetComponent <Grid>();
                    roomTemplateInstance.transform.position = grid.CellToLocal(position);
                }

                // Compute outline polygon
                var polygon = new Polygon2D(layoutRoom.Outline + layoutRoom.Position);

                var connection   = layoutRoom.IsCorridor ? corridorToConnectionMapping[layoutRoom.Room] : null;
                var roomInstance = new RoomInstanceGrid2D(layoutRoom.Room, layoutRoom.IsCorridor, connection, roomTemplatePrefab, roomTemplateInstance, position, polygon);

                // Add room info to the GameObject
                var roomInfo = roomTemplateInstance.GetComponent <RoomInfoGrid2D>();

                if (roomInfo != null)
                {
                    PostProcessUtilsGrid2D.Destroy(roomInfo);
                }

                roomInfo = roomTemplateInstance.AddComponent <RoomInfoGrid2D>();
                roomInfo.RoomInstance = roomInstance;

                layoutData.Add(layoutRoom.Room, roomInstance);
            }

            foreach (var roomInstance in layoutData.Values)
            {
                roomInstance.SetDoors(TransformDoorInfo(layoutRooms[roomInstance.Room].Doors, layoutData));
            }

            // Add level info
            var levelInfo = rootGameObject.GetComponent <LevelInfoGrid2D>();

            if (levelInfo != null)
            {
                PostProcessUtilsGrid2D.Destroy(levelInfo);
            }

            levelInfo = rootGameObject.AddComponent <LevelInfoGrid2D>();
            levelInfo.RoomInstances = layoutData.Values.ToList();

            return(new DungeonGeneratorLevelGrid2D(layoutData, layout, rootGameObject, levelDescription));
        }
        public Result Run(LevelDescriptionGrid2D levelDescription)
        {
            var roomTemplates = levelDescription.GetPrefabToRoomTemplateMapping();
            var doorLengths   = new Dictionary <int, List <GameObject> >();

            foreach (var pair in roomTemplates)
            {
                var gameObject   = pair.Key;
                var roomTemplate = pair.Value;

                foreach (var doorLength in GetDoorLengths(roomTemplate))
                {
                    if (!doorLengths.ContainsKey(doorLength))
                    {
                        doorLengths[doorLength] = new List <GameObject>();
                    }

                    doorLengths[doorLength].Add(gameObject);
                }
            }

            var result = new Result();

            result.DoorLengths = doorLengths;

            if (doorLengths.Count == 1)
            {
                var doorLength = doorLengths.Keys.First();
                result.IsPotentialProblem = false;
                result.Summary            = $"All doors have the same length ({doorLength}). This is the recommended setup.";
            }
            else
            {
                var sb = new StringBuilder();
                sb.AppendLine("There are room templates with different lengths of doors:");

                foreach (var pair in doorLengths.OrderBy(x => x.Key))
                {
                    var doorLength            = pair.Key;
                    var affectedRoomTemplates = pair.Value;
                    var roomTemplatesExample  = string.Join(", ", affectedRoomTemplates.Take(3).Select(x => $"\"{x.name}\""));
                    sb.AppendLine($"- Door length {doorLength} (room template{(affectedRoomTemplates.Count > 1 ? "s" : "")} {roomTemplatesExample}{(affectedRoomTemplates.Count > 3 ? "..." : "")})");
                }

                if (doorLengths.Count == 2)
                {
                    sb.Append($"This can be completely fine if you know what you are doing.");
                }
                else
                {
                    sb.Append($"Having more than 2 different lengths of doors looks very suspicious.");
                }

                sb.AppendLine($"If it was not intentional to have doors of different lengths, go through individual room templates and check their door lengths.");
                sb.Append($"While doing so, give more attention to room templates with the manual door mode.");

                result.IsPotentialProblem = true;
                result.Summary            = sb.ToString();
            }

            return(result);
        }
Пример #21
0
        /// <summary>
        /// Prepare level description.
        /// </summary>
        public LevelDescriptionGrid2D <Room> GetLevelDescription()
        {
            //md In this example, we will create a level description that should be close to what we could use in a game. We will cover the following:
            //md - create a `RoomType` enum for individual types of rooms - spawn, boss, shop, reward, etc.
            //md - create a custom `Room` class to identify rooms in the level graph
            //md - assign room templates based on the type of the room
            //md - use corridors with different lengths

            //md ## Room type
            //md To distinguish between different types of rooms, we create a `RoomType` enum. We will use to when deciding which room templates should be assigned to individual rooms. *Normal* rooms are basic combat-oriented rooms, *Hub* rooms are rather large rooms which we can use when a room has many neighbors, and the meaning of the rest of room types should be obvious.

            //md_sc enum:RoomType

            //md ## Room class
            //md In the previous examples, we used integers to identify individual rooms. We could theoretically still use integers and have a mapping from these integers to room types, but there exists a better way. We can implement a custom room class which will hold the room type. And if we also override the `ToString()` method, we will get the name of each room written over the room when we export the level.

            //md_sc class:Room

            //md ## Room templates
            //md The next step is to create room templates. We will create a mapping from the name of a room template to the instance of that room template so that we can refer to that instance later. We create at least a single room template for each room type.

            //md_sc method:GetRoomTemplates

            //md Below we can see a visualization of all the room templates:

            //md ![](./complex-dungeon/room_templates.png)

            //md The last thing we have to do is to create a mapping from `RoomType` to its room templates.

            //md_sc method:GetRoomTemplatesForRoom

            //md ## Corridor room templates
            //md We will use corridors of 3 different sizes:

            //md ![](./complex-dungeon/corridor_room_templates.png)

            //md ## Graph of rooms

            //md_sc method_content:GetGraph

            //md ## Level description
            //md Now we are ready to create the level description.

            var levelDescription = new LevelDescriptionGrid2D <Room>
            {
                MinimumRoomDistance = 2,
            };

            //md First, we get the graph and room templates.

            var graph         = GetGraph();
            var roomTemplates = GetRoomTemplates();

            //md Next, we add all the non-corridor rooms. For each room, we get room templates based on the type of the room.

            foreach (var room in graph.Vertices)
            {
                levelDescription.AddRoom(room, new RoomDescriptionGrid2D
                                         (
                                             isCorridor: false,
                                             roomTemplates: GetRoomTemplatesForRoom(room, roomTemplates)
                                         ));
            }

            //md Then we prepare a corridor room description. All corridors use the same room templates so we can reuse the instance of the room description for all corridors.

            var corridorRoomDescription = new RoomDescriptionGrid2D
                                          (
                isCorridor: true,
                roomTemplates: GetCorridorRoomTemplates()
                                          );

            //md And the final step is to add all the connections.

            foreach (var edge in graph.Edges)
            {
                var corridorRoom = new Room("Corridor", RoomType.Corridor);

                levelDescription.AddRoom(corridorRoom, corridorRoomDescription);
                levelDescription.AddConnection(edge.From, corridorRoom);
                levelDescription.AddConnection(edge.To, corridorRoom);
            }

            return(levelDescription);
        }
Пример #22
0
        public Bitmap DrawGraph(LevelDescriptionGrid2D <TRoom> levelDescription, Dictionary <TRoom, Vector2Int> positions, DungeonDrawerOptions options)
        {
            var configurations = GetConfigurations(positions);

            var outlines    = configurations.Select(x => x.Outline + x.Position).ToList();
            var boundingBox = DrawingUtils.GetBoundingBox(outlines);

            var(width, height, scale) = DrawingUtils.GetSize(boundingBox, options.Width, options.Height, options.Scale, options.PaddingAbsolute, options.PaddingPercentage);
            var offset = DrawingUtils.GetOffset(boundingBox, width, height, scale);

            bitmap   = new Bitmap(width, height);
            graphics = Graphics.FromImage(bitmap);
            graphics.SmoothingMode = SmoothingMode.HighQuality;

            using (SolidBrush brush = new SolidBrush(Color.FromArgb(248, 248, 244)))
            {
                graphics.FillRectangle(brush, 0, 0, width, height);
            }

            var outlinePen = new Pen(Color.FromArgb(50, 50, 50), 0.25f)
            {
                EndCap   = LineCap.Round,
                StartCap = LineCap.Round
            };

            var shadePen = new Pen(Color.FromArgb(204, 206, 206), 1.3f)
            {
                EndCap   = LineCap.Round,
                StartCap = LineCap.Round
            };

            graphics.TranslateTransform(offset.X, offset.Y);
            graphics.ScaleTransform(scale, scale);


            foreach (var configuration in configurations)
            {
                DrawShading(GetOutline(configuration.Outline, null, configuration.Position), shadePen);
            }

            //var hatchingUsedPoints = new List<Vector2>();
            //foreach (var configuration in configurations)
            //{
            //    DrawHatching(configuration.Outline + configuration.Position, hatchingUsedPoints, options.HatchingClusterOffset, options.HatchingLength);
            //}

            DrawEdges(levelDescription.GetGraphWithoutCorridors(), configurations, outlinePen);

            foreach (var configuration in configurations)
            {
                DrawRoomBackground(configuration.Outline + configuration.Position, options.RoomBackgroundColor);
                DrawGrid(configuration.Outline + configuration.Position);
                DrawOutline(configuration.Outline + configuration.Position, GetOutline(configuration.Outline, null, configuration.Position), outlinePen);
            }

            foreach (var configuration in configurations)
            {
                if (options.ShowRoomNames)
                {
                    DrawTextOntoPolygon(configuration.Outline + configuration.Position, configuration.Room.ToString(), options.FontSize);
                }
            }

            shadePen.Dispose();
            outlinePen.Dispose();

            return(bitmap);
        }
Пример #23
0
        /// <summary>
        /// Prepare level description.
        /// </summary>
        public LevelDescriptionGrid2D <int> GetLevelDescription()
        {
            //md By default, rooms in generated levels are connected directly - there are no corridors between them. If we want to use corridors, we have to add a corridor room between all pairs of neighboring rooms.

            //md > **Note:** As of now, the whole level geometry is fixed - the generator works only with the room templates that we create at design time. That means that we have to manually create all the shapes of corridor rooms. In the future, I would like to experiment with using path-finding for corridors instead of predefined room templates.

            //md ## Corridor room description
            //md First, we create the outline for the corridor room template. The performance of the generator is the best with rather short corridors, so we will use a 2x1 rectangle:

            var corridorOutline = PolygonGrid2D.GetRectangle(2, 1);

            //md The next step is to add doors. We can no longer use the simple door mode because we want to have exactly two door positions on the opposite sides of the corridor, which is not possible with the simple mode. With the manual mode, we have to specify all the door positions manually.

            var corridorDoors = new ManualDoorModeGrid2D(new List <DoorGrid2D>()
            {
                new DoorGrid2D(new Vector2Int(0, 0), new Vector2Int(0, 1)),
                new DoorGrid2D(new Vector2Int(2, 0), new Vector2Int(2, 1))
            }
                                                         );

            //md Now we can create the corridor room template. We must not forget to allow the 90 degrees rotation because, otherwise we would not be able to connect rooms both vertically and horizontally.

            var corridorRoomTemplate = new RoomTemplateGrid2D(
                corridorOutline,
                corridorDoors,
                allowedTransformations: new List <TransformationGrid2D>()
            {
                TransformationGrid2D.Identity,
                TransformationGrid2D.Rotate90
            }
                );

            //md We can also add another corridor room template which is a little bit longer then the previous one:

            var corridorRoomTemplateLonger = new RoomTemplateGrid2D(
                PolygonGrid2D.GetRectangle(4, 1),
                new ManualDoorModeGrid2D(new List <DoorGrid2D>()
            {
                new DoorGrid2D(new Vector2Int(0, 0), new Vector2Int(0, 1)),
                new DoorGrid2D(new Vector2Int(4, 0), new Vector2Int(4, 1))
            }
                                         ),
                allowedTransformations: new List <TransformationGrid2D>()
            {
                TransformationGrid2D.Identity,
                TransformationGrid2D.Rotate90
            }
                );

            //md Below we can see a visualization of the two room templates:

            //md ![](./corridors/room_templates.png)

            //md And finally, we can create the corridor room description. We must not forget to set the `IsCorridor` flag to `true`.

            var corridorRoomDescription = new RoomDescriptionGrid2D
                                          (
                isCorridor: true,
                roomTemplates: new List <RoomTemplateGrid2D>()
            {
                corridorRoomTemplate, corridorRoomTemplateLonger
            }
                                          );

            //md ## Basic room description
            //md For non-corridor rooms, we will use three rectangular room templates - 6x6 square, 8x8 square and 6x10 rectangle. The full code is omitted for simplicity.

            var basicRoomDescription = GetBasicRoomDescription();

            //md ## Level description
            //md First, we create a level description.

            var levelDescription = new LevelDescriptionGrid2D <int>();

            //md Next, we create a graph of rooms. Instead of adding rooms and connections directly to the level description, it might be sometimes beneficial to first prepare the graph data structure itself and then go through individual rooms and connections and add them to the level description.

            var graph = new UndirectedAdjacencyListGraph <int>();

            graph.AddVerticesRange(0, 13);

            graph.AddEdge(0, 1);
            graph.AddEdge(0, 2);
            graph.AddEdge(0, 8);
            graph.AddEdge(1, 3);
            graph.AddEdge(1, 4);
            graph.AddEdge(1, 5);
            graph.AddEdge(2, 6);
            graph.AddEdge(2, 7);
            graph.AddEdge(5, 9);
            graph.AddEdge(6, 9);
            graph.AddEdge(8, 10);
            graph.AddEdge(8, 11);
            graph.AddEdge(8, 12);

            //md > **Note:** As we want to have corridors between all neighboring rooms, it is better to create the graph only with non-corridor rooms and then add corridor rooms programatically.

            //md When we have the graph ready, we can add non-corridor rooms:

            foreach (var room in graph.Vertices)
            {
                levelDescription.AddRoom(room, basicRoomDescription);
            }

            //md Before we add corridor rooms, we have to figure out how to identify them. As we use integers, probably the easiest way is to number the corridor rooms and keep track which was the last used number:

            var corridorCounter = graph.VerticesCount;

            //md Now we can add corridor rooms. For each edge of the original graph, we create a corridor room and connect it to the two non-corridor rooms:

            foreach (var connection in graph.Edges)
            {
                // We manually insert a new room between each pair of neighboring rooms in the graph
                levelDescription.AddRoom(corridorCounter, corridorRoomDescription);

                // And instead of connecting the rooms directly, we connect them to the corridor room
                levelDescription.AddConnection(connection.From, corridorCounter);
                levelDescription.AddConnection(connection.To, corridorCounter);

                corridorCounter++;
            }

            return(levelDescription);
        }
Пример #24
0
        /// <summary>
        /// Prepare level description.
        /// </summary>
        public LevelDescriptionGrid2D <int> GetLevelDescription()
        {
            //md In this example, we will generate a very simple level consisting of 5 rooms with rectangular shapes.

            //md ## Room templates
            //md First, we will create our room templates. To do that, we need to create a *polygon* that defines the outline of the room template and also provide a list of possible door positions.

            //md ### Outline
            //md In the *Grid2D* setting, the outline of a room template is an orthogonal polygon where each point has integer coordinates. In other words, it is a polygon that we can draw on an integer grid using 1x1 square tiles.

            //md The first outline that we create is a 6x10 rectangle:

            var squareRoomOutline = new PolygonGrid2DBuilder()
                                    .AddPoint(0, 0)
                                    .AddPoint(0, 10)
                                    .AddPoint(6, 10)
                                    .AddPoint(6, 0)
                                    .Build();

            //md > **Note:** Orthogonal (or rectilinear) polygon is a polygon of whose edge intersections are at right angles. When on an integer grid, each side of an orthogonal polygon is aligned with one of the axes.

            //md > **Note:** There are several ways of constructing polygons:
            //md    - `PolygonGrid2D.GetSquare(width)` for squares
            //md    - `PolygonGrid2D.GetRectangle(width, height)` for rectangles
            //md    - `PolygonGrid2DBuilder` with the `.AddPoint(x, y)` method
            //md    - or the `PolygonGrid2D(IEnumerable<Vector2Int> points)` constructor

            //md ### Doors
            //md In order to tell the generator how it can connect individual room templates, we need to specify all the available door positions. The main idea is that the more door positions we provide, the easier it is for the generator to find a valid layout. To define door positions, we use the `IDoorModeGrid2D` interface. The most simple *door mode* is the `SimpleDoorModeGrid2D` - it lets us specify the length of doors and how far from corners of the outline they must be. In this tutorial, we will use doors with length of 1 tile and at least 1 tile away from corners.

            var doors = new SimpleDoorModeGrid2D(doorLength: 1, cornerDistance: 1);

            //md > **Note:** There is also an additional door mode available - `ManualDoorModeGrid2D`. This mode lets you specify exactly which door positions are available. It is useful for example when we want to have doors only on the two opposite sides of a corridor.

            //md ### Allowed transformations
            //md Optionally, it is also possible to let the generator apply some transformations to the room, e.g. rotate it by 90 degrees or mirror it by the X axis. An advantage of this approach is that the algorithm automatically handles door positions and we do not have to manually define all the variations of the room template.

            var transformations = new List <TransformationGrid2D>()
            {
                TransformationGrid2D.Identity,
                TransformationGrid2D.Rotate90
            };

            //md ### Putting it all together
            //md We can now combine the *outline*, *door mode* and *allowed transformations* together to create our first room template. We also provide a *name* which is optional but it may come in handy if we need to debug something.

            var rectangleRoomTemplate = new RoomTemplateGrid2D(
                squareRoomOutline,
                doors,
                allowedTransformations: transformations,
                name: "Rectangle 6x10"
                );

            //md We can also create a room template in-place with a single expression.

            var squareRoomTemplate = new RoomTemplateGrid2D(
                PolygonGrid2D.GetSquare(8),
                new SimpleDoorModeGrid2D(doorLength: 1, cornerDistance: 1),
                name: "Square 8x8"
                );

            //md Below we can see a visualization of the two room templates. Individual door positions are shown in red.

            //md ![](./basics/room_templates.png)

            //md ## Room description
            //md When we have our room templates ready, we need to create an instance of the `RoomDescriptionGrid2D` class which describes the properties of individual rooms in the level. In this tutorial, all the rooms use the same pool of room templates, so we can create only a single room description and reuse it. However, it is also possible to use different room description for different types of rooms. For example, we may want to have a boss room and a spawn room that should use different room templates than normal rooms.

            var roomDescription = new RoomDescriptionGrid2D
                                  (
                isCorridor: false,
                roomTemplates: new List <RoomTemplateGrid2D>()
            {
                rectangleRoomTemplate, squareRoomTemplate
            }
                                  );

            //md ## Level description
            //md The final step is to describe the structure of the level. We will use a very simple graph of rooms that we can see below:

            //md ![](./basics/graph.png)

            //md First, we have to create an instance of the `LevelDescriptionGrid2D<TRoom>` class. For simplicity, We will use `integers` to identify individual rooms. But it is also possible to use a custom room type by using a different generic type parameter.

            var levelDescription = new LevelDescriptionGrid2D <int>();

            //md Next, we add individual rooms to the level description.

            levelDescription.AddRoom(0, roomDescription);
            levelDescription.AddRoom(1, roomDescription);
            levelDescription.AddRoom(2, roomDescription);
            levelDescription.AddRoom(3, roomDescription);
            levelDescription.AddRoom(4, roomDescription);

            //md And lastly, we describe how should individual rooms be connected.

            levelDescription.AddConnection(0, 1);
            levelDescription.AddConnection(0, 3);
            levelDescription.AddConnection(0, 4);
            levelDescription.AddConnection(1, 2);
            levelDescription.AddConnection(2, 3);


            //md_sc method_content:Run

            return(levelDescription);
        }
Пример #25
0
        public void Run()
        {
            #region hidden

            var levelDescription = new BasicsExample().GetLevelDescription();

            #endregion

            //md In this tutorial, we will see how to save and load level descriptions and generated layouts.

            //md ## Level descriptions (JSON)
            //md To save a level description to a JSON file, we can call the [`SaveToJson()`][LevelDescriptionGrid2D.SaveToJson(String, Boolean)] method:

            levelDescription.SaveToJson("levelDescription.json");

            //md And to load a level description from a JSON file, we can use the [`LoadFromJson()`][LevelDescriptionGrid2D.LoadFromJson(String)] method:

            levelDescription = LevelDescriptionGrid2D <int> .LoadFromJson("levelDescription.json");

            //md By default, the JSON serializer is configured to preserve references to objects. That means that when it first encounters an object, it assigns a unique id to id and when it encounters the same object later, it only references that id and does not serialize the object itself. This is good for when we want load the level description back in C# later, but it may cause problems if we want to use the JSON outside C#. Therefore, it is possible to disable this feature:

            levelDescription.SaveToJson("levelDescription.json", preserveReferences: false);

            //md > **Note:**: API reference for the `LevelDescriptionGrid2D` class can be found [here][LevelDescriptionGrid2D].

            //md ## Layouts (JSON)

            #region hidden

            levelDescription = new BasicsExample().GetLevelDescription();
            var generator = new GraphBasedGeneratorGrid2D <int>(levelDescription);
            var layout    = generator.GenerateLayout();

            #endregion

            //md It is possible to save a generated layout to JSON and then load it back:

            layout.SaveToJson("layout.json");
            layout = LayoutGrid2D <int> .LoadFromJson("layout.json");

            //md And it is also possible to disable the `preserveReferences` feature:

            layout.SaveToJson("layout.json", preserveReferences: false);

            //md > **Note:**: API reference for the `LayoutGrid2D` class can be found [here][LayoutGrid2D].

            //md ## Layouts (PNG)

            #region hidden

            layout = generator.GenerateLayout();

            #endregion

            //md It is possible to save a generated layout as a PNG. To do that, we have to create an instance of the `DungeonDrawer` class:

            var dungeonDrawer = new DungeonDrawer <int>();

            //md Then we can save the layout as a PNG:

            dungeonDrawer.DrawLayoutAndSave(layout, "dungeon.png", new DungeonDrawerOptions()
            {
                Width  = 2000,
                Height = 2000,
            });

            //md The dungeon drawer produces images that can be seen in all the examples in this documentation. The API reference of the `DungeonDrawerOptions` class can be found [here][DungeonDrawerOptions].
        }
 public DungeonGeneratorLevelGrid2D(Dictionary <RoomBase, RoomInstanceGrid2D> roomInstances, LayoutGrid2D <RoomBase> mapLayout, GameObject rootGameObject, LevelDescriptionGrid2D levelDescription) : base(roomInstances, mapLayout, rootGameObject, levelDescription)
 {
 }
Пример #27
0
 public LevelDescriptionResult(LevelDescriptionGrid2D <TNode> levelDescription, List <IGeneratorRun> runs)
 {
     LevelDescription = levelDescription;
     Runs             = runs;
 }