/// <summary> /// performs a sphere check on a placement to look for objects /// matching 'vp_Layer.Mask.PhysicsBlockers'. upon finding any, /// re-executes itself for up to 'attempts' iterations /// </summary> public static bool AdjustPosition(vp_Placement p, float physicsRadius, int attempts = 1000) { attempts--; if (attempts > 0) { // TIP: this can be expanded upon to check for alternative object layers if (p.IsObstructed(physicsRadius)) { // adjust the position with a random horizontal distance of up to 1 meter Vector3 newPos = Random.insideUnitSphere; p.Position.x += newPos.x; p.Position.z += newPos.z; AdjustPosition(p, physicsRadius, attempts); } } else { // ran out of attempts! set position to world origin Debug.LogWarning("(vp_Placement.AdjustPosition) Failed to find valid placement."); return(false); } return(true); }
/// <summary> /// performs a sphere check on a placement to look for objects /// matching 'vp_Layer.Mask.PhysicsBlockers'. upon finding any, /// re-executes itself for up to 'attempts' iterations /// </summary> public static bool AdjustPosition(vp_Placement p, float physicsRadius, int attempts = 1000) { attempts--; if (attempts > 0) { // TIP: this can be expanded upon to check for alternative object layers if (p.IsObstructed(physicsRadius)) { // adjust the position with a random horizontal distance of up to 1 meter Vector3 newPos = Random.insideUnitSphere; p.Position.x += newPos.x; p.Position.z += newPos.z; AdjustPosition(p, physicsRadius, attempts); } } else { // ran out of attempts! set position to world origin Debug.LogWarning("(vp_Placement.AdjustPosition) Failed to find valid placement."); return false; } return true; }
/// <summary> /// teleports all players to new positions as determined by /// their team spawnpoint settings /// </summary> public static void TransmitRespawnAll() { if (!PhotonNetwork.isMasterClient) { return; } foreach (vp_MPNetworkPlayer p in vp_MPNetworkPlayer.Players.Values) { if (p == null) { continue; } p.Player.Platform.Set(null); vp_Placement placement = null; if (vp_MPTeamManager.Exists) { placement = vp_MPPlayerSpawner.GetRandomPlacement(vp_MPTeamManager.GetTeamName(p.TeamNumber)); } else { placement = vp_MPPlayerSpawner.GetRandomPlacement(); } p.photonView.RPC("ReceivePlayerRespawn", PhotonTargets.All, placement.Position, placement.Rotation); } }
/// <summary> /// returns a placement confined to this spawnpoint's settings /// if 'physicsCheckRadius' is set collision checking will be /// performed against pre-existing objects /// </summary> public virtual vp_Placement GetPlacement(float physicsCheckRadius = 0.0f) { vp_Placement p = new vp_Placement(); p.Position = transform.position; if (Radius > 0.0f) { Vector3 newPos = (Random.insideUnitSphere * Radius); p.Position.x += newPos.x; p.Position.z += newPos.z; } // stay clear of other physics blockers and snap to ground if (physicsCheckRadius != 0.0f) { if (!vp_Placement.AdjustPosition(p, physicsCheckRadius)) { return(null); } vp_Placement.SnapToGround(p, physicsCheckRadius, GroundSnapThreshold); } // if spawnpoint is set to use random rotation - create one. // otherwise use the rotation of the spawnpoint if (RandomDirection) { p.Rotation = Quaternion.Euler(Vector3.up * Random.Range(0.0f, 360.0f)); } else { p.Rotation = transform.rotation; } return(p); }
/// <summary> /// tries to snap a placement to the ground (meaning any object above /// or below it with an upwards-facing collider). a 'snapDistance' of /// '10' means that a unit spawning up to 10 meters above or below a /// collider will snap on top of it. NOTE: you may want to reduce the /// snap distance inside rooms with floors above it, and increase it /// in terrain with steep hills, respectively /// </summary> public static void SnapToGround(vp_Placement p, float radius, float snapDistance) { if (snapDistance == 0.0f) return; RaycastHit hitInfo; Physics.SphereCast(new Ray(p.Position + (Vector3.up * snapDistance), Vector3.down), radius, out hitInfo, snapDistance * 2.0f, vp_Layer.Mask.ExternalBlockers); if (hitInfo.collider != null) p.Position.y = hitInfo.point.y + 0.05f; // spawn 5 centimeters above the surface just to be sure }
/// <summary> /// snaps the player to a placement decided by the specified spawnpoint, taking into /// account the spawnpoint's radius, ground snap and random rotation settings. if the /// player has a vp_Respawner component, its 'ObstructionRadius' setting will be used /// for obstruction checking. otherwise the obstruction radius will be 1 meter. /// </summary> public static void Teleport(vp_SpawnPoint spawnPoint) { if (spawnPoint == null) { return; } float obstructionRadius = ((m_Respawner != null) ? m_Respawner.ObstructionRadius : 1.0f); vp_Placement placement = spawnPoint.GetPlacement(obstructionRadius); Position = placement.Position; Rotation = placement.Rotation.eulerAngles; Stop(); }
/// <summary> /// /// </summary> public void TransmitInitialSpawnInfo(PhotonPlayer player, int id, string name) { // allocate team number, player type and spawnpoint int teamNumber = 0; string playerTypeName = null; vp_Placement placement = null; if (vp_MPTeamManager.Exists) { teamNumber = ((vp_MPTeamManager.Instance.Teams.Count <= 1) ? 0 : vp_MPTeamManager.Instance.GetSmallestTeam()); playerTypeName = vp_MPTeamManager.Instance.GetTeamPlayerTypeName(teamNumber); placement = vp_MPPlayerSpawner.GetRandomPlacement(vp_MPTeamManager.GetTeamName(teamNumber)); } if (placement == null) { placement = vp_MPPlayerSpawner.GetRandomPlacement(); } if (string.IsNullOrEmpty(playerTypeName)) { vp_MPPlayerType playerType = vp_MPPlayerSpawner.GetDefaultPlayerType(); if (playerType != null) { playerTypeName = playerType.name; } else { Debug.LogError("Error (" + this + ") Failed to assign PlayerType to player " + id.ToString() + ". Make sure player types are assigned in your vp_MPPlayerSpawner and vp_MPMaster components."); } } // spawn photonView.RPC("ReceiveInitialSpawnInfo", PhotonTargets.All, id, player, placement.Position, placement.Rotation, playerTypeName, teamNumber); // if JOINING player is the master, refresh the game clock since // there are no other players and the game needs to get started if (player.isMasterClient) { StartGame(); } // send the entire game state to the joining player // NOTE: we don't need to send the game state of the joinee to all // the other players since it has just spawned in the form of a // fresh, clean copy of the remote player prefab in question TransmitGameState(player); }
/// <summary> /// tries to snap a placement to the ground (meaning any object above /// or below it with an upwards-facing collider). a 'snapDistance' of /// '10' means that a unit spawning up to 10 meters above or below a /// collider will snap on top of it. NOTE: you may want to reduce the /// snap distance inside rooms with floors above it, and increase it /// in terrain with steep hills, respectively /// </summary> public static void SnapToGround(vp_Placement p, float radius, float snapDistance) { if (snapDistance == 0.0f) { return; } RaycastHit hitInfo; Physics.SphereCast(new Ray(p.Position + (Vector3.up * snapDistance), Vector3.down), radius, out hitInfo, snapDistance * 2.0f, vp_Layer.Mask.ExternalBlockers); if (hitInfo.collider != null) { p.Position.y = hitInfo.point.y + 0.05f; // spawn 5 centimeters above the surface just to be sure } }
/// <summary> /// this method responds to a 'TransmitRespawn' event raised by an object in the /// master scene, and sends out an RPC to trigger the respawn on remote machines. /// it is typically initiated by vp_DamageHandler. /// </summary> protected virtual void TransmitRespawn(Transform targetTransform, vp_Placement placement) { // --- respawning a PLAYER --- if (vp_MPNetworkPlayer.Get(targetTransform) != null) { vp_MPPlayerSpawner.TransmitPlayerRespawn(targetTransform, placement); return; } // --- respawning an OBJECT --- int viewID = vp_MPMaster.GetViewIDOfTransform(targetTransform); if (viewID > 0) { photonView.RPC("ReceiveObjectRespawn", PhotonTargets.Others, viewID, placement.Position, placement.Rotation); } }
/// <summary> /// gets a vp_Placement object based on the optional 'spawnPointTag'. /// if no tag is provided a random spawnspoint will be returned /// </summary> public static vp_Placement GetRandomPlacement(string spawnPointTag = null) { if (spawnPointTag == "NoTeam") { spawnPointTag = null; } //Debug.Log("spawnPointTag: " + spawnPointTag); vp_Placement placement = vp_SpawnPoint.GetRandomPlacement(0.5f, spawnPointTag); if (placement == null) { placement = new vp_Placement(); } return(placement); }
/// <summary> /// respawns the network player of 'transform' at 'placement' /// </summary> public static void TransmitPlayerRespawn(Transform transform, vp_Placement placement) { if (!PhotonNetwork.isMasterClient) { return; } //UnityEngine.Debug.Log("respawning " + t.gameObject.name); vp_MPNetworkPlayer.RefreshPlayers(); vp_MPNetworkPlayer player = vp_MPNetworkPlayer.Get(transform); if (player != null) { player.photonView.RPC("ReceivePlayerRespawn", PhotonTargets.All, placement.Position, placement.Rotation); } }
/// <summary> /// respawns the object if no other object is occupying the respawn area. /// otherwise reschedules respawning. NOTE: this method can only run in /// singleplayer and by a master in multiplayer. multiplayer _clients_ /// will instead use the version of 'GetSpawnPoint' that takes a position /// and rotation (called directly by the master) /// </summary> public virtual void PickSpawnPoint() { // return if the object has been destroyed (for example // as a result of loading a new level while it was gone) if (this == null) { return; } // if mode is 'SamePosition' or the level has no spawnpoints, go to initial position if ((m_SpawnMode == SpawnMode.SamePosition) || (vp_SpawnPoint.SpawnPoints.Count < 1)) { Placement.Position = m_InitialPosition; Placement.Rotation = m_InitialRotation; // if an object the size of 'RespawnCheckRadius' can't fit at // 'm_InitialPosition' ... if (Placement.IsObstructed(ObstructionRadius)) { switch (m_ObstructionSolver) { case ObstructionSolver.Wait: // ... just try again later! vp_Timer.In(UnityEngine.Random.Range(MinRespawnTime, MaxRespawnTime), PickSpawnPoint, m_RespawnTimer); return; case ObstructionSolver.AdjustPlacement: // try to adjust the position ... if (!vp_Placement.AdjustPosition(Placement, ObstructionRadius)) { // ... and only if we failed to adjust the position, try again later vp_Timer.In(UnityEngine.Random.Range(MinRespawnTime, MaxRespawnTime), PickSpawnPoint, m_RespawnTimer); return; } break; } } } else { // placement will be calculated by the spawnpoint system. // NOTE: the obstruction solution logic becomes slightly // different with spawnpoints switch (m_ObstructionSolver) { case ObstructionSolver.Wait: // if an object the size of 'RespawnCheckRadius' can't fit at // this random spawnpoint ... Placement = vp_SpawnPoint.GetRandomPlacement(0.0f, SpawnPointTag); if (Placement == null) { Placement = new vp_Placement(); m_SpawnMode = SpawnMode.SamePosition; PickSpawnPoint(); } // NOTE: no 'snap to ground' in this mode since the snap logic // of 'GetRandomPlacement' is dependent on its input value if (Placement.IsObstructed(ObstructionRadius)) { // ... skip trying to adjust the position and try again later vp_Timer.In(UnityEngine.Random.Range(MinRespawnTime, MaxRespawnTime), PickSpawnPoint, m_RespawnTimer); return; } break; case ObstructionSolver.AdjustPlacement: // if an object the size of 'RespawnCheckRadius' can't fit at // this random spawnpoint and we fail to adjust the position ... Placement = vp_SpawnPoint.GetRandomPlacement(ObstructionRadius, SpawnPointTag); if (Placement == null) { // ... try again later vp_Timer.In(UnityEngine.Random.Range(MinRespawnTime, MaxRespawnTime), PickSpawnPoint, m_RespawnTimer); return; } break; } } Respawn(); }
/// <summary> /// respawns the object if no other object is occupying the respawn area. /// otherwise reschedules respawning. NOTE: this method can only run in /// singleplayer and by a master in multiplayer. multiplayer _clients_ /// will instead use the version of 'GetSpawnPoint' that takes a position /// and rotation (called directly by the master) /// </summary> public virtual void PickSpawnPoint() { // return if the object has been destroyed (for example // as a result of loading a new level while it was gone) if (this == null) return; // if mode is 'SamePosition' or the level has no spawnpoints, go to initial position if ((m_SpawnMode == SpawnMode.SamePosition) || (vp_SpawnPoint.SpawnPoints.Count < 1)) { Placement.Position = m_InitialPosition; Placement.Rotation = m_InitialRotation; // if an object the size of 'RespawnCheckRadius' can't fit at // 'm_InitialPosition' ... if (Placement.IsObstructed(ObstructionRadius)) { switch (m_ObstructionSolver) { case ObstructionSolver.Wait: // ... just try again later! vp_Timer.In(UnityEngine.Random.Range(MinRespawnTime, MaxRespawnTime), PickSpawnPoint, m_RespawnTimer); return; case ObstructionSolver.AdjustPlacement: // try to adjust the position ... if (!vp_Placement.AdjustPosition(Placement, ObstructionRadius)) { // ... and only if we failed to adjust the position, try again later vp_Timer.In(UnityEngine.Random.Range(MinRespawnTime, MaxRespawnTime), PickSpawnPoint, m_RespawnTimer); return; } break; } } } else { // placement will be calculated by the spawnpoint system. // NOTE: the obstruction solution logic becomes slightly // different with spawnpoints switch (m_ObstructionSolver) { case ObstructionSolver.Wait: // if an object the size of 'RespawnCheckRadius' can't fit at // this random spawnpoint ... Placement = vp_SpawnPoint.GetRandomPlacement(0.0f, SpawnPointTag); if (Placement == null) { Placement = new vp_Placement(); m_SpawnMode = SpawnMode.SamePosition; PickSpawnPoint(); } // NOTE: no 'snap to ground' in this mode since the snap logic // of 'GetRandomPlacement' is dependent on its input value if (Placement.IsObstructed(ObstructionRadius)) { // ... skip trying to adjust the position and try again later vp_Timer.In(UnityEngine.Random.Range(MinRespawnTime, MaxRespawnTime), PickSpawnPoint, m_RespawnTimer); return; } break; case ObstructionSolver.AdjustPlacement: // if an object the size of 'RespawnCheckRadius' can't fit at // this random spawnpoint and we fail to adjust the position ... Placement = vp_SpawnPoint.GetRandomPlacement(ObstructionRadius, SpawnPointTag); if (Placement == null) { // ... try again later vp_Timer.In(UnityEngine.Random.Range(MinRespawnTime, MaxRespawnTime), PickSpawnPoint, m_RespawnTimer); return; } break; } } Respawn(); }
/// <summary> /// gets a vp_Placement object based on the optional 'spawnPointTag'. /// if no tag is provided a random spawnspoint will be returned /// </summary> public static vp_Placement GetRandomPlacement(string spawnPointTag = null) { if (spawnPointTag == "NoTeam") spawnPointTag = null; //Debug.Log("spawnPointTag: " + spawnPointTag); vp_Placement placement = vp_SpawnPoint.GetRandomPlacement(0.5f, spawnPointTag); if (placement == null) placement = new vp_Placement(); return placement; }
/// <summary> /// respawns the network player of 'transform' at 'placement' /// </summary> public static void TransmitPlayerRespawn(Transform transform, vp_Placement placement) { if (!PhotonNetwork.isMasterClient) return; //UnityEngine.Debug.Log("respawning " + t.gameObject.name); vp_MPNetworkPlayer.RefreshPlayers(); vp_MPNetworkPlayer player = vp_MPNetworkPlayer.Get(transform); if (player != null) player.photonView.RPC("ReceivePlayerRespawn", PhotonTargets.All, placement.Position, placement.Rotation); }
public static vp_Placement GetRandomPlacement(float physicsCheckRadius, string tag) { // abort if scene contains no spawnpoints if ((SpawnPoints == null) || (SpawnPoints.Count < 1)) { return(null); } // fetch a random spawnpoint vp_SpawnPoint spawnPoint = null; if (string.IsNullOrEmpty(tag)) { spawnPoint = GetRandomSpawnPoint(); } else { spawnPoint = GetRandomSpawnPoint(tag); if (spawnPoint == null) { spawnPoint = GetRandomSpawnPoint(); Debug.LogWarning("Warning (vp_SpawnPoint --> GetRandomPlacement) Could not find a spawnpoint tagged '" + tag + "'. Falling back to 'any random spawnpoint'."); } } // if no spawnpoint was found, revert to world origin if (spawnPoint == null) { Debug.LogError("Error (vp_SpawnPoint --> GetRandomPlacement) Could not find a spawnpoint" + (!string.IsNullOrEmpty(tag) ? (" tagged '" + tag + "'") : ".") + " Reverting to world origin."); return(null); } // found a spawnpoint! set placement position to that of // the spawnpoint and apply horizontal spawnpoint radius // offset (if any) vp_Placement p = new vp_Placement(); p.Position = spawnPoint.transform.position; if (spawnPoint.Radius > 0.0f) { Vector3 newPos = (Random.insideUnitSphere * spawnPoint.Radius); p.Position.x += newPos.x; p.Position.z += newPos.z; } // stay clear of other physics blockers and snap to ground if (physicsCheckRadius != 0.0f) { if (!vp_Placement.AdjustPosition(p, physicsCheckRadius)) { return(null); } vp_Placement.SnapToGround(p, physicsCheckRadius, spawnPoint.GroundSnapThreshold); } // if spawnpoint is set to use random rotation - create one. // otherwise use the rotation of the spawnpoint if (spawnPoint.RandomDirection) { p.Rotation = Quaternion.Euler(Vector3.up * Random.Range(0.0f, 360.0f)); } else { p.Rotation = spawnPoint.transform.rotation; } return(p); }
public static vp_Placement GetRandomPlacement(float physicsCheckRadius, string tag) { // abort if scene contains no spawnpoints if((SpawnPoints == null) || (SpawnPoints.Count < 1)) return null; // fetch a random spawnpoint vp_SpawnPoint spawnPoint = null; if (string.IsNullOrEmpty(tag)) spawnPoint = GetRandomSpawnPoint(); else { spawnPoint = GetRandomSpawnPoint(tag); if (spawnPoint == null) { spawnPoint = GetRandomSpawnPoint(); Debug.LogWarning("Warning (vp_SpawnPoint --> GetRandomPlacement) Could not find a spawnpoint tagged '" + tag + "'. Falling back to 'any random spawnpoint'."); } } // if no spawnpoint was found, revert to world origin if (spawnPoint == null) { Debug.LogError("Error (vp_SpawnPoint --> GetRandomPlacement) Could not find a spawnpoint" + (!string.IsNullOrEmpty(tag) ? (" tagged '" + tag + "'") : ".") + " Reverting to world origin."); return null; } // found a spawnpoint! set placement position to that of // the spawnpoint and apply horizontal spawnpoint radius // offset (if any) vp_Placement p = new vp_Placement(); p.Position = spawnPoint.transform.position; if(spawnPoint.Radius > 0.0f) { Vector3 newPos = (Random.insideUnitSphere * spawnPoint.Radius); p.Position.x += newPos.x; p.Position.z += newPos.z; } // stay clear of other physics blockers and snap to ground if (physicsCheckRadius != 0.0f) { if (!vp_Placement.AdjustPosition(p, physicsCheckRadius)) return null; vp_Placement.SnapToGround(p, physicsCheckRadius, spawnPoint.GroundSnapThreshold); } // if spawnpoint is set to use random rotation - create one. // otherwise use the rotation of the spawnpoint if(spawnPoint.RandomDirection) p.Rotation = Quaternion.Euler(Vector3.up * Random.Range(0.0f, 360.0f)); else p.Rotation = spawnPoint.transform.rotation; return p; }