diff --git a/Assets/Scripts/Character.cs b/Assets/Scripts/Character.cs index f65dd46..3b18284 100644 --- a/Assets/Scripts/Character.cs +++ b/Assets/Scripts/Character.cs @@ -17,7 +17,7 @@ public class Character : MonoBehaviour public bool isTuteLevel = false; public bool inWater = false; //Am I in the water? - public bool inPit = false; //Did I fall into a pit? + public bool respawnNeeded = false; //Am I waiting on a respawn? public bool onCrystal = false; public bool underRock = false; public bool stuck = false; //Am I still stuck? @@ -287,13 +287,13 @@ public class Character : MonoBehaviour if (currentBlock != null) { this.transform.position = currentBlock.VisualPosition; - this.inPit = false; + this.respawnNeeded = false; this._currentBlock = currentBlock; Debug.Log("Moved " + this.name + " to " + this.transform.position.x + ", " + this.transform.position.y + ", " + this.transform.position.z + ", " - + " inPit = " + inPit); + + " respawnNeeded = " + respawnNeeded); } else { diff --git a/Assets/Scripts/GameMode/ColorGameMode/RacetrackGameMode.cs b/Assets/Scripts/GameMode/ColorGameMode/RacetrackGameMode.cs index ce4bb2e..431d21a 100644 --- a/Assets/Scripts/GameMode/ColorGameMode/RacetrackGameMode.cs +++ b/Assets/Scripts/GameMode/ColorGameMode/RacetrackGameMode.cs @@ -18,6 +18,10 @@ public class RacetrackGameMode : GameMode public Material OverlayMaterial; public float scrollSpeed = 1.0f; //The rate at which the level will scroll past + [SerializeField] + [Tooltip("Layers to ignore when checking for blocks")] + public LayerMask Ignore; + public int RoundCount { get; private set; } private Dictionary> BlocksOwned; @@ -39,19 +43,121 @@ public class RacetrackGameMode : GameMode cameraCheck(allPlayers); //At the end of each round, any stuck players are freed to resume moving next round - foreach (PlayerData player in allPlayers) + respawnPlayers(allPlayers); + + /*foreach (PlayerData player in allPlayers) { player.character.stuck = false; - if (player.character.inPit && player.client.Lives > 0) + if (player.character.respawnNeeded && player.client.Lives > 0) { player.character.respawnCharacter(); } - } + }*/ RoundCount++; } + void respawnPlayers(PlayerData[] allPlayers) + { + //First step: determine if anyone is waiting to respawn + List respawningPlayers = new List(); + List survivingPlayers = new List(); + foreach (PlayerData player in allPlayers) + { + if (player.client.Lives > 0) + { + if (player.character.respawnNeeded) + { + respawningPlayers.Add(player); + } + else + { + survivingPlayers.Add(player); + } + } + } + + //If nobody needs a respawn, we stop here. Otherwise, we carry on + if (respawningPlayers.Count > 0) + { + //Next step: figure out the appropriate respawn point + //If there are any players left, it will be the average of their positions + //Otherwise, it will be as close to the centre of the screen as possible + Vector3 respawnLocation = new Vector3(0.0f, -0.5f, 0.0f); + + if (survivingPlayers.Count > 0) //If any players are not currently waiting to respawn, we get the average of their locations + { + //We list the positions of all the surviving players + List survivingPlayerLocations = new List(); + foreach (PlayerData player in survivingPlayers) + { + survivingPlayerLocations.Add(player.character.transform.position); + } + + foreach (Vector3 vec in survivingPlayerLocations) + { + respawnLocation += vec; + } + + respawnLocation = respawnLocation / survivingPlayers.Count; + } + else //Otherwise, we go for the middle of the screen + { + //We start in the middle of the track, at the back, and scroll forward until we're past the centre of the camera's view + respawnLocation.x = mapManager.startX; + + Vector3 respawnLocationVP = Camera.main.WorldToViewportPoint(respawnLocation); + + while (respawnLocationVP.x > 0.5f || respawnLocationVP.y > 0.5f) + { + respawnLocation.x += 1.0f; + respawnLocationVP = Camera.main.WorldToViewportPoint(respawnLocation); + } + } + + //Now we randomly pick blocks in the vicinity of this point to respawn players on + //Valid blocks are those within x & +/-1 from the chosen location + //If we don't find enough open, unoccupied blocks within that radius, then we widen it + + int radius = 0; + List respawnBlocks; + do + { + respawnBlocks = new List(); + radius++; + + for (int i = -1 * radius; i <= radius; i++) + { + for (int j = -1 * radius; j <= radius; j++) + { + Block currentBlock; + + Vector3 currentPos = new Vector3(respawnLocation.x + i, respawnLocation.y, respawnLocation.z); + + if (Block.isBlockAtPosition(currentPos, 1, ~Ignore, out currentBlock) //Does a block exist here? + && currentBlock.is_Walkable //Are we allowed on this block? + && !(currentBlock.isWater) && !(currentBlock.isPit) //Don't respawn on top of instant traps + && currentBlock.CurrentPlayer == null) //Block must be unoccupied + { + respawnBlocks.Add(currentBlock); + } + } + } + } while (respawnBlocks.Count < respawningPlayers.Count); + + foreach (PlayerData player in respawningPlayers) + { + //We randomly pick a block for them to respawn on + Block respawnBlock = respawnBlocks[(int)Random.Range(0.0f, (float)respawnBlocks.Count)]; + Vector3 respawnLocationPlayer = respawnBlock.transform.position; + player.character.respawnCharacter(respawnLocationPlayer); + + respawnBlocks.Remove(respawnBlock); //Then we remove it from the list for the next player + } + } + } + void cameraCheck(PlayerData[] allPlayers) { //Get the average x-position of all players @@ -60,14 +166,17 @@ public class RacetrackGameMode : GameMode foreach (PlayerData player in allPlayers) { - if (player.client.Lives > 0) + if (!(player.character.respawnNeeded) && player.client.Lives > 0) { xAvg += player.character.transform.position.x; livePlayerCount++; } } - xAvg = xAvg / livePlayerCount; + if (livePlayerCount > 0) + { + xAvg = xAvg / livePlayerCount; + } //Turn that position into a vector in viewport space Vector3 xAvgPos = new Vector3(xAvg, 0.0f, 0.0f); @@ -107,6 +216,18 @@ public class RacetrackGameMode : GameMode playerVP = Camera.main.WorldToViewportPoint(playerAhead.character.transform.position); }/**/ + //If anyone is out of view at this point, they are dead and need to respawn + foreach (PlayerData player in allPlayers) + { + if (player.client.Lives > 0 && !(player.character.respawnNeeded)) + { + playerVP = Camera.main.WorldToViewportPoint(player.character.transform.position); + if (playerVP.x < 0.0f || playerVP.y < 0.0f) + { + player.character.respawnNeeded = true; + } + } + } //We check for track sections we need to add/remove mapManager.checkTrack(); @@ -158,14 +279,14 @@ public class RacetrackGameMode : GameMode //If a character has fallen in the water or into a pit, we mark that fact, and they lose the rest of their turn character.inWater = currentBlock.isWater; - character.inPit = currentBlock.isPit; + character.respawnNeeded = currentBlock.isPit; - if (character.inWater == true || character.inPit == true) + if (character.inWater == true || character.respawnNeeded == true) { character.stuck = true; } - Debug.Log("inWater = " + character.inWater + ", inPit = " + character.inPit + ", stuck = " + character.stuck);*/ + Debug.Log("inWater = " + character.inWater + ", respawnNeeded = " + character.respawnNeeded + ", stuck = " + character.stuck);*/ //Commented out because we don't do this in the racetrack mode @@ -251,20 +372,20 @@ public class RacetrackGameMode : GameMode { //If a character has fallen in the water or into a pit, we mark that fact, and they lose the rest of their turn character.inWater = currentBlock.isWater; - character.inPit = currentBlock.isPit; + character.respawnNeeded = currentBlock.isPit; character.onCrystal = currentBlock.isCrystals; character.underRock = currentBlock.isRock; - if (didMove && (character.inWater || character.inPit)) + if (didMove && (character.inWater || character.respawnNeeded)) { character.stuck = true; } - //Debug.Log("inWater = " + character.inWater + ", inPit = " + character.inPit + ", stuck = " + character.stuck); + //Debug.Log("inWater = " + character.inWater + ", respawnNeeded = " + character.respawnNeeded + ", stuck = " + character.stuck); } protected override void OnPlayerKilled(Character character, ClientData client) { - if (character.inPit || character.onCrystal) + if (character.respawnNeeded || character.onCrystal) { character.lives -= 1; character.ClientLink.Lives = character.lives; diff --git a/Assets/Scripts/Map Generation/MapManager.cs b/Assets/Scripts/Map Generation/MapManager.cs index d5187f5..720dd02 100644 --- a/Assets/Scripts/Map Generation/MapManager.cs +++ b/Assets/Scripts/Map Generation/MapManager.cs @@ -21,7 +21,7 @@ public class MapManager : ScriptableObject 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? - float startX; //The x-position of the current start of the track + 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