Example #1
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,
            });
        }
Example #2
0
        /// <summary>
        /// Adds a given connection without a corridor between the two rooms.
        /// </summary>
        /// <param name="connection">Connection that is added to the level description</param>
        public void AddConnection(ConnectionBase connection)
        {
            if (connection == null)
            {
                throw new ArgumentNullException(nameof(connection));
            }

            connections.Add(connection);
            levelDescription.AddConnection(connection.From, connection.To);
        }
Example #3
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);
    }
        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);
        }
Example #5
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);
        }
        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);
        }
Example #7
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);
        }
        /// <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);
        }
Example #9
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);
        }
Example #10
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);
        }