public Doorway PickRandomDoorway(System.Random randomStream, bool mustBeAvailable, DungeonArchetype archetype) { float straightenChance = (archetype == null) ? 0.0f : archetype.StraightenChance; double rnd = randomStream.NextDouble(); // Try to pick the opposing doorway, based on archetype's StraightenChance if (isOnMainPath && UsedDoorways.Count == 1 && rnd < straightenChance) { // Find the doorway opposite to the first foreach (Doorway d in UnusedDoorways) { if (UsedDoorways[0].transform.forward == -d.transform.forward) { return(d); } } } int index = PickRandomDoorwayIndex(randomStream, mustBeAvailable); return((index == -1) ? null : AllDoorways[index]); }
internal TilePlacementData(PreProcessTileData preProcessData, bool isOnMainPath, DungeonArchetype archetype, TileSet tileSet, Dungeon dungeon) { root = (GameObject)GameObject.Instantiate(preProcessData.Prefab); Bounds = preProcessData.Proxy.GetComponent <Collider>().bounds; IsOnMainPath = isOnMainPath; tile = Root.GetComponent <Tile>(); if (tile == null) { tile = Root.AddComponent <Tile>(); } tile.Placement = this; tile.Archetype = archetype; tile.TileSet = tileSet; tile.Dungeon = dungeon; foreach (var doorway in Root.GetComponentsInChildren <Doorway>(true)) { doorway.Dungeon = dungeon; doorway.Tile = tile; AllDoorways.Add(doorway); } UnusedDoorways.AddRange(AllDoorways); root.SetActive(false); }
protected virtual Tile AddTile(Tile attachTo, IEnumerable <TileSet> useableTileSets, float normalizedDepth, DungeonArchetype archetype, TilePlacementResult result = TilePlacementResult.None) { bool isOnMainPath = (Status == GenerationStatus.MainPath); bool isFirstTile = attachTo == null; // Check list of tiles to inject InjectedTile chosenInjectedTile = null; int injectedTileIndexToRemove = -1; bool isPlacingSpecificRoom = isOnMainPath && (archetype == null); if (tilesPendingInjection != null && !isPlacingSpecificRoom) { float pathDepth = (isOnMainPath) ? normalizedDepth : attachTo.Placement.PathDepth / (float)(targetLength - 1); float branchDepth = (isOnMainPath) ? 0 : normalizedDepth; for (int i = 0; i < tilesPendingInjection.Count; i++) { var injectedTile = tilesPendingInjection[i]; if (injectedTile.ShouldInjectTileAtPoint(isOnMainPath, pathDepth, branchDepth)) { chosenInjectedTile = injectedTile; injectedTileIndexToRemove = i; break; } } } // Select appropriate tile weights IEnumerable <GameObjectChance> chanceEntries; if (chosenInjectedTile != null) { chanceEntries = new List <GameObjectChance>(chosenInjectedTile.TileSet.TileWeights.Weights); } else { chanceEntries = useableTileSets.SelectMany(x => x.TileWeights.Weights); } // Apply constraint overrides bool allowRepeatTile = (isFirstTile) ? true : attachTo.AllowImmediateRepeats; if (OverrideAllowImmediateRepeats) { allowRepeatTile = AllowImmediateRepeats; } bool allowRotation = (isFirstTile) ? false : attachTo.AllowRotation; if (OverrideAllowTileRotation) { allowRotation = AllowTileRotation; } DoorwayPairFinder doorwayPairFinder = new DoorwayPairFinder() { RandomStream = RandomStream, Archetype = archetype, GetTileTemplateDelegate = GetTileTemplate, IsOnMainPath = isOnMainPath, NormalizedDepth = normalizedDepth, PreviousTile = attachTo, PreviousPrefab = (attachTo == null) ? null : placedTilePrefabs[attachTo], UpVector = UpVector, AllowRotation = allowRotation, TileWeights = new List <GameObjectChance>(chanceEntries), IsTileAllowedPredicate = (Tile previousTile, GameObject previousPrefab, GameObject prefab, ref float weight) => { bool isRepeat = (prefab == previousPrefab); return(!isRepeat || allowRepeatTile); }, }; int?maxPairingAttempts = (UseMaximumPairingAttempts) ? (int?)MaxPairingAttempts : null; Queue <DoorwayPair> pairsToTest = doorwayPairFinder.GetDoorwayPairs(maxPairingAttempts); TilePlacementResult lastTileResult = TilePlacementResult.NoValidTile; Tile createdTile = null; while (pairsToTest.Count > 0) { var pair = pairsToTest.Dequeue(); lastTileResult = TryPlaceTile(pair, archetype, out createdTile); if (lastTileResult == TilePlacementResult.None) { break; } else { AddTilePlacementResult(lastTileResult); } } // Successfully placed the tile if (lastTileResult == TilePlacementResult.None) { // We've successfully injected the tile, so we can remove it from the pending list now if (chosenInjectedTile != null) { injectedTiles[createdTile] = chosenInjectedTile; tilesPendingInjection.RemoveAt(injectedTileIndexToRemove); if (isOnMainPath) { targetLength++; } } return(createdTile); } else { return(null); } }
protected TilePlacementResult TryPlaceTile(DoorwayPair pair, DungeonArchetype archetype, out Tile tile) { tile = null; var toTemplate = pair.NextTemplate; var fromDoorway = pair.PreviousDoorway; if (toTemplate == null) { return(TilePlacementResult.TemplateIsNull); } int toDoorwayIndex = pair.NextTemplate.Doorways.IndexOf(pair.NextDoorway); if (fromDoorway != null) { // Move the proxy object into position GameObject toProxyDoor = toTemplate.ProxySockets[toDoorwayIndex]; UnityUtil.PositionObjectBySocket(toTemplate.Proxy, toProxyDoor, fromDoorway.gameObject); Bounds proxyBounds = toTemplate.Proxy.GetComponent <Collider>().bounds; // Check if the new tile is outside of the valid bounds if (RestrictDungeonToBounds && !TilePlacementBounds.Contains(proxyBounds)) { return(TilePlacementResult.OutOfBounds); } // Check if the new tile is colliding with any other if (IsCollidingWithAnyTile(proxyBounds)) { return(TilePlacementResult.TileIsColliding); } } TilePlacementData newTile = new TilePlacementData(toTemplate, (Status == GenerationStatus.MainPath), archetype, pair.NextTileSet, currentDungeon); if (newTile == null) { return(TilePlacementResult.NewTileIsNull); } if (newTile.IsOnMainPath) { if (pair.PreviousTile != null) { newTile.PathDepth = pair.PreviousTile.Placement.PathDepth + 1; } } else { newTile.PathDepth = pair.PreviousTile.Placement.PathDepth; newTile.BranchDepth = (pair.PreviousTile.Placement.IsOnMainPath) ? 0 : pair.PreviousTile.Placement.BranchDepth + 1; } if (fromDoorway != null) { newTile.Root.transform.parent = Root.transform; Doorway toDoorway = newTile.AllDoorways[toDoorwayIndex]; UnityUtil.PositionObjectBySocket(newTile.Root, toDoorway.gameObject, fromDoorway.gameObject); currentDungeon.MakeConnection(fromDoorway, toDoorway, RandomStream); } else { newTile.Root.transform.parent = Root.transform; newTile.Root.transform.localPosition = Vector3.zero; } currentDungeon.AddTile(newTile.Tile); if (!newTile.Tile.OverrideAutomaticTileBounds) { newTile.RecalculateBounds(IgnoreSpriteBounds, UpVector); } else { newTile.Bounds = newTile.Tile.transform.TransformBounds(newTile.Tile.TileBoundsOverride); newTile.LocalBounds = newTile.Tile.TileBoundsOverride; } if (PlaceTileTriggers) { newTile.Tile.AddTriggerVolume(); newTile.Root.layer = TileTriggerLayer; } allDoorways.AddRange(newTile.AllDoorways); tile = newTile.Tile; placedTilePrefabs[tile] = toTemplate.Prefab; return(TilePlacementResult.None); }
protected virtual IEnumerator GenerateMainPath() { ChangeStatus(GenerationStatus.MainPath); nextNodeIndex = 0; List <GraphNode> handledNodes = new List <GraphNode>(DungeonFlow.Nodes.Count); bool isDone = false; int i = 0; // Keep track of these now, we'll need them later when we know the actual length of the dungeon List <List <TileSet> > tileSets = new List <List <TileSet> >(targetLength); List <DungeonArchetype> archetypes = new List <DungeonArchetype>(targetLength); List <GraphNode> nodes = new List <GraphNode>(targetLength); List <GraphLine> lines = new List <GraphLine>(targetLength); // We can't rigidly stick to the target length since we need at least one room for each node and that might be more than targetLength while (!isDone) { float depth = Mathf.Clamp(i / (float)(targetLength - 1), 0, 1); GraphLine lineSegment = DungeonFlow.GetLineAtDepth(depth); // This should never happen if (lineSegment == null) { yield return(Wait(InnerGenerate(true))); yield break; } // We're on a new line segment, change the current archetype if (lineSegment != previousLineSegment) { currentArchetype = lineSegment.DungeonArchetypes[RandomStream.Next(0, lineSegment.DungeonArchetypes.Count)]; previousLineSegment = lineSegment; } List <TileSet> useableTileSets = null; GraphNode nextNode = null; var orderedNodes = DungeonFlow.Nodes.OrderBy(x => x.Position).ToArray(); // Determine which node comes next foreach (var node in orderedNodes) { if (depth >= node.Position && !handledNodes.Contains(node)) { nextNode = node; handledNodes.Add(node); break; } } // Assign the TileSets to use based on whether we're on a node or a line segment if (nextNode != null) { useableTileSets = nextNode.TileSets; nextNodeIndex = (nextNodeIndex >= orderedNodes.Length - 1) ? -1 : nextNodeIndex + 1; archetypes.Add(null); lines.Add(null); nodes.Add(nextNode); if (nextNode == orderedNodes[orderedNodes.Length - 1]) { isDone = true; } } else { useableTileSets = currentArchetype.TileSets; archetypes.Add(currentArchetype); lines.Add(lineSegment); nodes.Add(null); } tileSets.Add(useableTileSets); i++; } int tileRetryCount = 0; int totalForLoopRetryCount = 0; for (int j = 0; j < tileSets.Count; j++) { var attachTo = (j == 0) ? null : currentDungeon.MainPathTiles[currentDungeon.MainPathTiles.Count - 1]; var tile = AddTile(attachTo, tileSets[j], j / (float)(tileSets.Count - 1), archetypes[j]); // if no tile could be generated delete last successful tile and retry from previous index // else return false if (j > 5 && tile == null && tileRetryCount < 5 && totalForLoopRetryCount < 20) { Tile previousTile = currentDungeon.MainPathTiles[j - 1]; foreach (var doorway in previousTile.Placement.AllDoorways) { allDoorways.Remove(doorway); } // If the tile we're removing was placed by tile injection, be sure to place the injected tile back on the pending list InjectedTile previousInjectedTile; if (injectedTiles.TryGetValue(previousTile, out previousInjectedTile)) { tilesPendingInjection.Add(previousInjectedTile); injectedTiles.Remove(previousTile); } currentDungeon.RemoveLastConnection(); currentDungeon.RemoveTile(previousTile); UnityUtil.Destroy(previousTile.gameObject); j -= 2; // -2 because loop adds 1 tileRetryCount++; totalForLoopRetryCount++; } else if (tile == null) { yield return(Wait(InnerGenerate(true))); yield break; } else { tile.Node = nodes[j]; tile.Line = lines[j]; tileRetryCount = 0; // Wait for a frame to allow for animated loading screens, etc if (ShouldSkipFrame(true)) { yield return(GetRoomPause()); } } } yield break; // Required for generation to run synchronously }