Это первая статья из серии о создании 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