Это первая статья из серии о создании roguelike 2d-игры.
Мы рассмотрим создание карты или подземелья, как сделать
их динамичными, чтобы при каждом запуске игры карта совершенно другое.

По сути, мы хотим создать случайное количество комнат произвольного размера и соединить их все коридорами.

В итоге получилось так:

(Розовый — это просто цвет фона)
У нас есть две разные плитки: одна для пола и одна для стен комнаты.
Дракон представляет игрока.

Процесс создания подземелья

Алгоритмы основаны на генерации подземелий через BSP-дерево [1].
Процесс происходит следующим образом:

мы начинаем с большой комнаты и делим ее пополам до тех пор, пока мы не сможем больше делить комнату на основе заранее заданного минимального размера для каждой комнаты.

// Some basic structures.
struct Position
    {
        int32 x;
        int32 y;
    };

// Sprite is the object that we end up drawing to the screen.
struct Sprite
    {
        Position position;
        int32 height;
        int32 width;
        SDL_Rect *textCoord;
        SDL_Texture *texture;
    };

/* 
final function to create a dungeon
we run it once at the start of the game.
4000 is the width and height of the whole map.
The other two values are the minimum size of a room.
*/ 
createDungeon(4000, 4000, dungeonSprites.floorSprite->height * 8, dungeonSprites.floorSprite->width * 8);



  struct DungeonCorridor{
        Position startPos;
        Position endPos;
    };

/*
 DungeonRoom struct
 we need to know how big is the room, where is it positioned
 if it has a subRoom and an adjacent(sister) room.
 and where is the corridor that connects it to the adjacent room
*/
  struct DungeonRoom
    {
        int32 id;
        int32 height;
        int32 width;
        int32 centerX;
        int32 centerY;
        DungeonCorridor corridor;
        DungeonRoom *subRoom;
        DungeonRoom *sisterRoom; // adjacent room
    };

/*
 dungeonRoomInit allocates a new dungeon room and initializes its values to 0
*/
DungeonRoom *dungeonRoomInit()
    {
        DungeonRoom *subRoom = (DungeonRoom *)malloc(sizeof(DungeonRoom));
        if (subRoom == NULL)
        {
            return NULL;
        }
        subRoom->subRoom = NULL;
        subRoom->sisterRoom = NULL;
        memset(subRoom, 0, sizeof(subRoom));
        return subRoom;
    }

 /*
 createSubRooms will recursively generate subrooms until its not posible anymore.
*/
    void
    createSubRooms(DungeonRoom *masterRoom, uint32 minRoomHeight, uint32 minRoomWidth)
    {
        /*
          because we generate two rooms on every pass of the function, first check
          if there is enough space to accomodate two rooms.
        */
        if (masterRoom->height > 2 * minRoomHeight && masterRoom->width > 2 * minRoomWidth)
        {
            DungeonRoom *subRoom1 = dungeonRoomInit();
            DungeonRoom *subRoom2 = dungeonRoomInit();
            if (subRoom1 == NULL || subRoom2 == NULL)
            {
                return;
            }

            /*
            we can split a big room either vertically or horizontally.
            we accomplish this by selecting a random number between 0 and 100
            and decide odd number for vertical, even for horizontal
           */
            
            int directionOfSplit = dungeonRandomizer.distribution.intD(dungeonRandomizer.generator);
            int guessTries = 10;
            if (directionOfSplit % 2 != 0)
            {
           /*
            Generate rooms with a random width or height base on how we splitted it.
            if no valid random value after 10 tries, then just pick the minimum room sizes.
           */
                while (guessTries >= 0)
                {
                    auto randomWidth = getRandomInt(minRoomWidth, masterRoom->width);
                    int pointOfSplit = randomWidth.distribution.intD(randomWidth.generator);
                    uint32 subRoom1Width = pointOfSplit;
                    uint32 subRoom2Width = masterRoom->width - pointOfSplit;
                    if (subRoom1Width < minRoomWidth || subRoom2Width < minRoomWidth) 
                    {
                        if (guessTries <= 0)
                        {
                            subRoom1Width = minRoomWidth;
                            subRoom2Width = minRoomWidth;
                        }
                        else
                        {
                            guessTries--;
                        }
                    }
                    else
                    {
                        guessTries = -1;

                        subRoom1->id = masterRoom->id + 1;
                        subRoom2->id = masterRoom->id + 1;

                        subRoom1->width = subRoom1Width;
                        subRoom2->width = subRoom2Width;

                        subRoom1->height = masterRoom->height;
                        subRoom2->height = masterRoom->height;

                        subRoom1->centerX = masterRoom->centerX - subRoom1->width / 2;
                        subRoom2->centerX = masterRoom->centerX + subRoom2->width / 2;

                        subRoom1->centerY = masterRoom->centerY;
                        subRoom2->centerY = masterRoom->centerY;

          /*
            Select the start and end position of the corridor (currently corridors
            are straight so the positions have to be on the same line).
            going with the center is therefore a valid approach.
           */
                        DungeonCorridor corridor = {};
                        corridor.startPos.x = subRoom1->centerX;
                        corridor.startPos.y = masterRoom->centerY;
                        corridor.endPos.x = subRoom2->centerX;
                        corridor.endPos.y = masterRoom->centerY;

                        subRoom1->corridor = corridor;
                        subRoom2->corridor = corridor;
                    }
                }
            }
            else
            {
                while (guessTries >= 0)
                {
                    auto randomHeight = getRandomInt(minRoomHeight, masterRoom->height);
                    int pointOfSplit = randomHeight.distribution.intD(randomHeight.generator);
                    uint32 subRoom1Height = pointOfSplit;
                    uint32 subRoom2Height = masterRoom->height - pointOfSplit;
                    if (subRoom1Height < minRoomHeight || subRoom2Height < minRoomHeight)
                    {
                        if (guessTries <= 0)
                        {
                            subRoom1Height = minRoomHeight;
                            subRoom2Height = minRoomHeight;
                        }
                        else
                        {
                            guessTries--;
                        }
                    }
                    else
                    {
                        guessTries = -1;

                        subRoom1->id = masterRoom->id + 1;
                        subRoom2->id = masterRoom->id + 1;

                        subRoom1->height = subRoom1Height;
                        subRoom2->height = subRoom2Height;

                        subRoom1->width = masterRoom->width;
                        subRoom2->width = masterRoom->width;

                        subRoom1->centerX = masterRoom->centerX;
                        subRoom2->centerX = masterRoom->centerX;

                        subRoom1->centerY = masterRoom->centerY - subRoom1->height / 2;
                        subRoom2->centerY = masterRoom->centerY + subRoom2->height / 2;

                        DungeonCorridor corridor = {};
                        corridor.startPos.x = masterRoom->centerX;
                        corridor.startPos.y = subRoom1->centerY;
                        corridor.endPos.x = masterRoom->centerX;
                        corridor.endPos.y = subRoom2->centerY;

                        subRoom1->corridor = corridor;
                        subRoom2->corridor = corridor;
                    }
                }
            }


            subRoom1->sisterRoom = subRoom2;
            masterRoom->subRoom = subRoom1;
            createSubRooms(subRoom1, minRoomHeight, minRoomWidth);
            createSubRooms(subRoom2, minRoomHeight, minRoomWidth);
        }

        return;
    }    

DungeonRoom *createDungeon(uint32 height, uint32 width, uint32 minRoomHeight, uint32 minRoomWidth)
    {
        
        DungeonRoom *masterRoom = dungeonRoomInit();
        masterRoom->id = 0;
        masterRoom->height = height;
        masterRoom->width = width;
        masterRoom->centerX = width / 2;
        masterRoom->centerY = height / 2;
        masterRoom->sisterRoom = nullptr;
        masterRoom->subRoom = nullptr;
        
        // create rooms
        createSubRooms(masterRoom, minRoomHeight, minRoomWidth);
        return masterRoom;
    }

Весь код игры можно найти на github LLschuster/n1 (github.com)[2].

ДЕЛАТЬ:

  • Код генерации довольно медленный -› чтобы исправить это, код должен быть профилирован и соответствующим образом рефакторинг ‹Link_TO_Post›

Следующая статья – › WIP

Ссылки:

[1] Базовая генерация BSP Dungeon — RogueBasin
[2] github project