public void DestroyIfRequired() { if (IsTrackGenerated) { foreach (GameObject trackPiece in GeneratedTrackPieces) { NetworkServer.Destroy(trackPiece); } GeneratedTrackPieces.Clear(); IsTrackGenerated = false; CheckpointsInRace = null; } }
IEnumerator GenerateTrack(int trackLength, IReadOnlyList <GameObject> availableTrackPiecePrefabs, IReadOnlyCollection <Player> playersToSpawn) { SentrySdk.AddBreadcrumb("Starting track generation."); GameObject origin = new GameObject("Temporary Origin for Track Generator"); GameObject currentTrackPiece = firstTrackPiecePrefab; int numTracks = 0; // Stores a validity map for the current track marked by numTrack index, where all of the possible track piece candidates are either valid or invalid. bool[,] validAvailableTracks = new bool[trackLength, availableTrackPiecePrefabs.Count]; for (int numTracksIndex = 0; numTracksIndex < trackLength; numTracksIndex++) { for (int candidateTrackPiece = 0; candidateTrackPiece < availableTrackPiecePrefabs.Count; candidateTrackPiece++) { validAvailableTracks[numTracksIndex, candidateTrackPiece] = true; } } while (numTracks < trackLength) { // Compile a list of valid track piece options. List <int> validTrackOptions = new List <int>(); for (int candidateTrackPiece = 0; candidateTrackPiece < availableTrackPiecePrefabs.Count; candidateTrackPiece++) { if (validAvailableTracks[numTracks, candidateTrackPiece] == true && IsSameTrackPieceStyle(availableTrackPiecePrefabs[candidateTrackPiece])) { validTrackOptions.Add(candidateTrackPiece); } } // BACKTRACK // Check if there exists any valid track pieces to choose from. If not, delete the recently placed piece. if (validTrackOptions.Count == 0) { // All track options for the current track piece are exhausted with no valid tracks. // Must backtrack from the current track piece by destroying the current track piece. NetworkServer.Destroy(currentTrackPiece); Destroy(currentTrackPiece); GeneratedTrackPieces.RemoveAt(GeneratedTrackPieces.Count - 1); currentTrackPiece = GeneratedTrackPieces[GeneratedTrackPieces.Count - 1]; // Reset validAvailableTracks memory of this track's options for the future track pieces to use this space. for (int candidateTrackPiece = 0; candidateTrackPiece < availableTrackPiecePrefabs.Count; candidateTrackPiece++) { validAvailableTracks[numTracks, candidateTrackPiece] = true; } numTracks--; continue; } Transform trackPieceLinkTransform; GameObject newTrackPiecePrefab; if (numTracks == 0) { newTrackPiecePrefab = firstTrackPiecePrefab; trackPieceLinkTransform = origin.transform; } else if (numTracks == trackLength - 1) { // We want to force the algorithm to place the Final Track Piece only, so mark every other track piece as invalid. // This way, if the Final Track Piece cannot be placed, the algorithm will backtrack. for (int candidateTrackPiece = 0; candidateTrackPiece < availableTrackPiecePrefabs.Count; candidateTrackPiece++) { validAvailableTracks[numTracks, candidateTrackPiece] = false; } // If the Final Track Piece doesn't have the same track piece style as the previously placed track, // then it cannot be placed, and we must backtrack. if (!IsSameTrackPieceStyle(finalTrackPiecePrefab)) { // We marked every track as invalid above, so backtracking will occur on the next iteration. continue; } newTrackPiecePrefab = finalTrackPiecePrefab; trackPieceLinkTransform = LoadTrackPieceLinkTransform(currentTrackPiece); } else { int randomTrack = validTrackOptions[UnityEngine.Random.Range(0, validTrackOptions.Count)]; newTrackPiecePrefab = availableTrackPiecePrefabs[randomTrack]; validAvailableTracks[numTracks, randomTrack] = false; trackPieceLinkTransform = LoadTrackPieceLinkTransform(currentTrackPiece); } if (trackPieceLinkTransform == null) { throw new MissingComponentException("An error has occurred loading the track piece link during track generation for this track piece."); } GameObject newTrackPiece = Instantiate(newTrackPiecePrefab); newTrackPiece.name = $"Auto Generated Track Piece { numTracks + 1 } ({ newTrackPiecePrefab.name })"; newTrackPiece.transform.position = trackPieceLinkTransform.transform.position; newTrackPiece.transform.rotation *= trackPieceLinkTransform.rotation; // Spawn the players cars onto the starting piece of the track if (numTracks == 0) { yield return(SpawnManager.Singleton.SpawnAllRaceCarsOnStartingGrid(newTrackPiece, playersToSpawn)); } // Wait for next physics calculation so that Track Piece Collision Detector works properly. yield return(new WaitForSeconds(0.15f)); if (newTrackPiece.GetComponent <TrackGeneratorCollisionDetector>().IsValidTrackPlacementUponConnection) { NetworkServer.Spawn(newTrackPiece); GeneratedTrackPieces.Add(newTrackPiece); currentTrackPiece = newTrackPiece; numTracks++; } else { Destroy(newTrackPiece); } } // By default track piece props are kinematic so they don't fall over when track pieces collide. // Once done we should set kinematic to false so cars can collide into them. foreach (GameObject trackPiece in GeneratedTrackPieces) { TrackPieceManager trackPieceState = trackPiece.GetComponent <TrackPieceManager>(); trackPieceState.MakeReadyForRace(); trackPieceState.MakePropsNonKinematic(); } if (finalTrackPiecePrefab.transform.Find(GameObjectIdentifiers.FinishLineCheckpoint) == null) { throw new MissingComponentException($"Final Track Piece Prefab must have a GameObject named { GameObjectIdentifiers.FinishLineCheckpoint } in order for the finish line to function."); } // Cleanup and finish track generation Destroy(origin); IsTrackGenerating = false; IsTrackGenerated = true; CheckpointsInRace = GeneratedTrackPieces .SelectMany(trackPiece => trackPiece.GetComponentsInChildren <TrackPieceCheckpointDetector>()) .Select(trackPieceCheckpointDetector => trackPieceCheckpointDetector.gameObject) .ToArray(); RpcInvokeTrackGeneratedEvent(); SentrySdk.AddBreadcrumb("Track generator finished."); }