/// <summary> /// Constructs the primary or critical path for the dungeon - this is the central path /// that any extra rooms branch off from. /// Primary paths only move forward - they never branch. /// </summary> /// <param name="scopes"></param> private void ConstructPrimaryPath(RandomDungeonScopeData[] scopes) { // After choosing where a new room is going to go, close off all the paths in // the previous room to prevent branching. bool firstRoomPlaced = false; List <int> successfulRoomsPerScope = new List <int>(); for (int i = 0; i < scopes.Length; ++i) { successfulRoomsPerScope.Add(0); } for (int scopeIdx = 0; scopeIdx < scopes.Length; ++scopeIdx) { RandomDungeonScopeData scope = scopes[scopeIdx]; int roomsPerScope = mRNG.Next(scope.criticalPathMinRooms, scope.criticalPathMaxRooms); for (int i = 0; i < roomsPerScope; ++i) { RoomPlacementOptions options = RoomPlacementOptions.None; if (i == 0) { options = RoomPlacementOptions.AllowMismatchScopes; } if (!firstRoomPlaced) { firstRoomPlaced = true; if (TryPlaceRoom("guaranteed_first", true, scopeIdx, options) != null) { successfulRoomsPerScope[scopeIdx]++; } else { // Force another room generation i--; } } else if (TryPlaceRoom("primary", true, scopeIdx, options) != null) { successfulRoomsPerScope[scopeIdx]++; } } } for (int i = 0; i < successfulRoomsPerScope.Count; ++i) { if (successfulRoomsPerScope[i] < scopes[i].criticalPathMinRooms) { mMapInvalid = true; return; } } }
/// <summary> /// Verifies whether room placement is possible. /// We go through every tile in the room and ensure that it's not overlapping something invalid /// (ie: if we try to place a wall that would overlap a floor tile, that's invalid). /// </summary> /// <param name="roomData"></param> /// <param name="mapExit"></param> /// <param name="roomExit"></param> /// <param name="scope"></param> /// <param name="roomPlacementOptions"></param> /// <returns></returns> private bool RoomHasInvalidCollision(RoomData roomData, Vector2Int mapExit, Vector2Int roomExit, int scope, RoomPlacementOptions roomPlacementOptions = RoomPlacementOptions.None) { bool fullOverlap = true; for (int roomX = 0; roomX < roomData.width; ++roomX) { for (int roomY = 0; roomY < roomData.height; ++roomY) { int mapX = mapExit.x - roomExit.x + roomX; int mapY = mapExit.y - roomExit.y + roomY; Vector2Int mapPos = new Vector2Int(mapX, mapY); int roomTile = roomData.Tile(roomX, roomY); if (!mRoomScratch.ContainsKey(mapPos) || roomTile == RandomDungeonTileData.EMPTY_TILE) { // Any kind of tile can be placed over an empty location // However, at this point we do know that the rooms are not 100% overlapping fullOverlap = false; continue; } else { int mapTile = mRoomScratch[mapPos].tileType; int mapTileScope = mRoomScratch[mapPos].scope; if (scope != mapTileScope && !RoomPlacementOptionSet(roomPlacementOptions, RoomPlacementOptions.AllowMismatchScopes)) { // We're trying to connect with a room in a different scope, which we can never do to ensure // that a scope only has a single point of entry. return(true); } else if (RoomPlacementOptionSet(roomPlacementOptions, RoomPlacementOptions.AllowWallsToBecomeExits) && mapTile == RandomDungeonTileData.WALL_TILE && roomTile == RandomDungeonTileData.EXIT_TILE) { // If the options indicate that walls can become exits, support that. continue; } else if (mapTile != roomTile) { // Only identical tiles can be placed over one another. return(true); } } } } // There's still the chance that this room is being placed completely over a previous room, which // we don't want to allow because it throws off our room counts. // We should be trying to place against at least one empty square return(fullOverlap); }
private bool RoomPlacementOptionSet(RoomPlacementOptions options, RoomPlacementOptions testOption) { return((options & testOption) == testOption); }
/// <summary> /// Repeatedly tries to place a room, choosing a random room from the room set and a random exit to place it off of. /// This will retry some maximum number of times and quit if it never finds a valid choice. /// </summary> /// <param name="roomCategory"></param> /// <param name="primaryPath"></param> /// <param name="scope"></param> /// <param name="roomPlacementOptions"></param> /// <param name="roomDataOverride"></param> /// <returns>The room data for the room that was placed, or null if no room could be placed.</returns> private RoomData TryPlaceRoom(string roomCategory, bool primaryPath, int scope, RoomPlacementOptions roomPlacementOptions = RoomPlacementOptions.None, RoomData roomDataOverride = null) { int maxAttempts = 200; while (maxAttempts > 0) { Vector2Int mapExit = ChooseMapExitForNextRoom(); RoomData randomRoom = roomDataOverride != null ? roomDataOverride : mRoomSet.RandomRoomOfCategory(roomCategory, mPreviousRoomPlaced, mRNG); if (randomRoom == null) { --maxAttempts; continue; } Vector2Int roomExit = randomRoom.RandomExit(mRNG); if (scope == -1) { scope = mRoomScratch[mapExit].scope; } if (!RoomHasInvalidCollision(randomRoom, mapExit, roomExit, scope, roomPlacementOptions)) { // If we're constructing a primary path, clear out any exits currently // in our list. Placing a room will add new available exits to build off of. // This also guards against the primary path looping in on itself. if (primaryPath) { mAvailableExits.Clear(); mPrimaryPathPositions.Add(new Vector2Int(mapExit.x, mapExit.y)); } mAvailableExits.Remove(mapExit); PlaceRoom(randomRoom, mapExit, roomExit, scope, primaryPath); // The probability of picking a room lowers each time it's picked randomRoom.probability /= 2; mPreviousRoomPlaced = randomRoom; mPreviousMapExitUsed = mapExit; return(randomRoom); } --maxAttempts; } return(null); }