using Networking.Server; using System.Collections.Generic; using UnityEngine; [CreateAssetMenu(menuName = "Major Project/Map Generation/Map Manager")] public class MapManager : ScriptableObject { public ClientList clients; public GameObject spawn; public GameObject spawn4; //The section to use as a spawn-point for games with 2-5 players public GameObject spawn8; //The section to use a spawn-point for games with 5-8 players //In 5-player games, we choose between them at random public List sections; //The list of sections to choose from after starting //Split up the inspector lists to make them easier to manage. They'll be combined on initialisation public List sectionLists; public int minConns = 2; //The minimum number of valid connections between two map sections for them to be allowed to link up public List activeSections; //The list of sections that have been placed on the map (and not removed) MapSection lastSection; //Which map-section was most recently added? public float startX; //The x-position of the current start of the track float startXinit = -16.0f; float endX; //The x-position of the current end of the track int totalSections; //How many sections have been added? Including ones that have been deleted int initialPlayerCount; int diffStart = 0; //Initial difficulty rating int diffCap = 5; //The highest the difficulty rating can go int difficulty; //Current difficulty rating int widthMin; //The minimum widthIn that we want for a new map section int widthMax; //The maximum widthIn that we want for a new map section int widthMinMin = 3; //The minimum to which widthMin can be reduced int widthMaxMin = 5; //The minimum to which widthMax can be reduced public void init() { sections = new List(); startX = startXinit; endX = startX; totalSections = 0; foreach (SectionList sectionList in sectionLists) { foreach (MapSection section in sectionList.sectionList) { sections.Add(section); } } initialPlayerCount = clients.ConnectedClients.Count; activeSections = new List(); if (initialPlayerCount < 5) { addSection(spawn4.GetComponent()); } else if (initialPlayerCount > 5) { addSection(spawn8.GetComponent()); } else { if (Random.Range(0.0f, 1.0f) < 0.5f) { addSection(spawn4.GetComponent()); } else { addSection(spawn8.GetComponent()); } } widthMin = activeSections[0].widthOut - 2; widthMax = activeSections[0].widthOut + 2; difficulty = diffStart; switch (initialPlayerCount) { case 2: foreach (GameObject spawnBlock in lastSection.spawns2) { spawnBlock.GetComponent().isSpawnable = true; } break; case 3: foreach (GameObject spawnBlock in lastSection.spawns3) { spawnBlock.GetComponent().isSpawnable = true; } break; case 4: foreach (GameObject spawnBlock in lastSection.spawns4) { spawnBlock.GetComponent().isSpawnable = true; } break; case 5: foreach (GameObject spawnBlock in lastSection.spawns5) { spawnBlock.GetComponent().isSpawnable = true; } break; case 6: foreach (GameObject spawnBlock in lastSection.spawns6) { spawnBlock.GetComponent().isSpawnable = true; } break; case 7: foreach (GameObject spawnBlock in lastSection.spawns7) { spawnBlock.GetComponent().isSpawnable = true; } break; case 8: foreach (GameObject spawnBlock in lastSection.spawns8) { spawnBlock.GetComponent().isSpawnable = true; } break; default: foreach (GameObject spawnBlock in lastSection.spawns1) { spawnBlock.GetComponent().isSpawnable = true; } break; } checkForward(); } void chooseNextSection() { Debug.Log("Choosing next section "); //First, we determine which sections are valid List validSections = new List(); updateCriteria(); //We update the section selection criteria for the current gamestate int count = -1; foreach (MapSection section in sections) { count++; if (section == null) { continue; } Debug.Log("Checking section " + section.name + ", weight = " + section.weight); if (section.weight > 0 && checkSegments(section)) { //If a segment is a valid continuation of the current most-recent segment, add it to the list //Sections with higher weights get more entries => higher chance of being picked for (int i = 0; i < section.weight; i++) { validSections.Add(section); } } } //Having generated our list, we choose a random segment from it int selectedIndex = Random.Range(0, validSections.Count); Debug.Log("Validmap sections: " + validSections.Count); Debug.Log("Selected section: " + selectedIndex); MapSection nextSection = validSections[selectedIndex]; addSection(nextSection); } void addSection(MapSection section) { //Instantiate new section at x = endX Vector3 pos = new Vector3(endX, 0.0f, 0.0f); GameObject newSection = (GameObject)Instantiate(section.gameObject, pos, Quaternion.identity); MapSection newSectionScript = newSection.GetComponent(); newSectionScript.InitSection(activeSections.Count); newSection.name = newSectionScript.name; activeSections.Add(newSectionScript); lastSection = newSectionScript; endX += newSectionScript.length; totalSections++; } bool checkSegments(MapSection second) { return checkSegments(this.lastSection, second); } bool checkSegments(MapSection first, MapSection second) { Debug.Log("Checking " + first.name + " & " + second.name + ". DiffMin = " + second.difficultyMin + ", DiffMax = " + second.difficultyMax); int connections = 0; //No more than one link section in a row if (first.length == 1 && second.length == 1) { Debug.Log("Disqualified: repeated link sections"); return false; } if (second.difficultyMax < difficulty || second.difficultyMin > difficulty) //Check that we're in the right difficulty range for this section { Debug.Log("Disqualified: wrong difficulty"); return false; } if (second.widthIn < widthMin || second.widthIn > widthMax) //And that it's in the right width range { Debug.Log("Disqualified: wrong width"); return false; } foreach (GameObject exit in first.exits) { foreach (GameObject entry in second.entrances) { if (checkConnection(exit, entry)) { connections++; } } } Debug.Log("Check result: " + (connections >= minConns)); return (connections >= minConns); } bool checkConnection(GameObject exit, GameObject entry) { //If the squares being checked don't line up, the connection is invalid if (exit.transform.localPosition.z != entry.transform.localPosition.z) { return false; } //If both components require jumping (pits or water), the connection is invalid //It's technically possible to cross two water blocks, but we don't count that if (requiresJump(exit) && requiresJump(entry)) { return false; } //Since we currently don't let people jump over walls, if either block is a wall, the connection is invalid if (isWall(exit) || isWall(entry)) { return false; } //If we've passed all these tests, the connection is valid! return true; } bool requiresJump(GameObject block) { if (block.GetComponent() == null) //The object must be a pit trap { return true; } return block.GetComponent().isWater; //If it's not a pit, then whether it requires jumping depends on whether it's water or not } bool isWall(GameObject block) { if (block.GetComponent() == null) { return true; } return !(block.GetComponent().is_Walkable); } //Check whether it's time to extend the track forward void checkForward() { //We check if the end of the last section of track is in sight Vector3 trackEnd = new Vector3(endX, 0.0f); //Get the middle of the end of the last track section //If it is, then we add a new section if (checkView(trackEnd)) { chooseNextSection(); checkForward(); } } //Check whether it's time to delete the oldest section of active track void checkBack() { //We check if the end of the first section of track is still in sight Vector3 firstSectionEnd = new Vector3(startX + activeSections[0].length, 0.0f); //Get the middle of the end of the first track section //If it's not, then we remove it if (!(checkView(firstSectionEnd))) { spawn.GetComponent().updatePositions((int)startX + activeSections[0].length); startX += activeSections[0].length; spawn.GetComponent().updatePositions((int)startX); activeSections[0].destroySection(); activeSections.RemoveAt(0); } } //Check whether a point is in sight or not bool checkView(Vector3 point) { Vector3 screenPoint = Camera.main.WorldToViewportPoint(point); //Map it into viewport space //The camera's field of view is represented by 0 > (x, y) < 1, with z being the distance from the camera return (screenPoint.z > 0 && screenPoint.x > 0 && screenPoint.x < 1 && screenPoint.y > 0 && screenPoint.y < 1); } //Checks in both directions for sections needing to be added or removed public void checkTrack() { checkForward(); checkBack(); } //Updates minimum and maximum difficulty, width, etc, based on current gamestate public void updateCriteria() { //Start with base values difficulty = diffStart; //By default, we can add a section 1 tile wider or narrower on either side than the last section widthMin = lastSection.widthOut - 2; widthMax = lastSection.widthOut + 2; /* Calculate min & max difficulties & width modifications * We recalculate from scratch each time (that is, * each time a section is added) so as to avoid having * to track which one-off increase has been applied * and which hasn't */ //As the number of players shrinks, we ramp up the difficulty and contract the track if (initialPlayerCount > 0) { if (clients.ConnectedClients.Count <= (float)(0.5f * initialPlayerCount)) { difficulty++; widthMin -= 2; widthMax -= 2; } if (clients.ConnectedClients.Count <= (float)(0.33f * initialPlayerCount)) { difficulty += 2; widthMin -= 2; widthMax -= 2; } } //Ramp up the difficulty as the track extends difficulty += ((int)endX - (int)startX) / 7; //Apply caps if (difficulty > diffCap) { difficulty = diffCap; } if (widthMin < widthMinMin) { widthMin = widthMinMin; } if (widthMax < widthMaxMin) { widthMax = widthMaxMin; } Debug.Log("Difficulty = " + difficulty + ", widthMin = " + widthMin + ", widthMax = " + widthMax); } // Update is called once per frame void Update() { checkTrack(); } }