Guest User

Untitled

a guest
Feb 1st, 2026
81
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 15.53 KB | Source Code | 0 0
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using NUnit.Framework.Internal;
  4. using PurrNet;
  5. using Unity.Cinemachine;
  6. using Unity.VisualScripting;
  7. using UnityEngine;
  8. using UnityEngine.InputSystem;
  9.  
  10. public class PlayerSpawnManager : MonoBehaviour, IProvidePrefabInstantiated
  11. {
  12. // ✅ Dynamic list of all active players
  13. public static PlayerSpawnManager Instance { get; private set; }
  14. public List<GameObject> AllPlayers { get; private set; } = new List<GameObject>();
  15. //The reason that "public static PlayerSpawnManager Instance" is neccessary is that enables other files to access AllPlayers by doing PlayerSpawnManager.Instance.AllPlayers, rather than having to do [SerializeField] private PlayerSpawnManager manager;
  16.  
  17. private Dictionary<GameObject, int> playerTeams = new Dictionary<GameObject, int>();
  18.  
  19. [SerializeField] private GameObject playerPrefab;
  20. [SerializeField] private GameManager gManager;
  21. [SerializeField] private Transform[] spawnPoints;
  22. [SerializeField] private Transform[] respawnPoints;
  23. [SerializeField] private Transform[] teleportPoints;
  24.  
  25. [SerializeField] private bool isCouchCOOP = true;
  26. [SerializeField] private int maxPlayers = 4;
  27. [SerializeField] private bool autoDetectControllers = true;
  28. [SerializeField] private int manualPlayerCount = 2; //network only!
  29. private bool couchCOOPAudioAlreadyAssigned = false;
  30.  
  31. private int nextPlayerIndex = 0;
  32. private Dictionary<InputDevice, GameObject> deviceToPlayer = new Dictionary<InputDevice, GameObject>();
  33.  
  34. [Header("Team Color Materials")]
  35. [SerializeField] private Material team1Material; //Blue
  36. [SerializeField] private Material team2Material; //Red
  37.  
  38. void Awake() //This generates Instance.AllPlayers before anything happens, which is neccesary so it always exists.
  39. {
  40. if (Instance != null && Instance != this) //Is there already a different manager named instance active?
  41. {
  42. Destroy(gameObject); //If there’s already another manager, we destroy the entire GameObject this script is attached to (the duplicate)
  43. return;
  44. }
  45. Instance = this; //If there wasn’t an existing different instance, assign the static Instance to this instance. Now other classes can use PlayerSpawnManager.Instance to access it!
  46. }
  47.  
  48. void Start()
  49. {
  50. if (isCouchCOOP){
  51. // Enable all connected displays
  52. //Todo change to actual split-screen, or add option for both
  53. for (int i = 0; i < Display.displays.Length; i++)
  54. {
  55. Display.displays[i].Activate();
  56. }
  57. SpawnCouchCoopPlayers();
  58.  
  59. // DISABLE the network PlayerSpawner if it exists
  60. var playerSpawner = FindAnyObjectByType<PlayerSpawner>();
  61. if (playerSpawner != null)
  62. {
  63. playerSpawner.enabled = false;
  64. }
  65. }
  66.  
  67. else
  68. {
  69. // Network mode - register with PlayerSpawner
  70. var playerSpawner = FindAnyObjectByType<PlayerSpawner>();
  71. if (playerSpawner != null)
  72. {
  73. playerSpawner.SetPrefabInstantiatedProvider(this);
  74. }
  75. }
  76. }
  77.  
  78. // Update is called once per frame
  79. void Update() //todo Limits game to 1 keyboard!
  80. {
  81. // Only allow dynamic joining in couch co-op mode if you want
  82. if (isCouchCOOP && autoDetectControllers && AllPlayers.Count <= maxPlayers)
  83. {
  84. // Check for new keyboard input
  85. if (Keyboard.current != null && Keyboard.current.spaceKey.wasPressedThisFrame)
  86. {
  87. if (!deviceToPlayer.ContainsKey(Keyboard.current))
  88. {
  89. SpawnLocalPlayer(Keyboard.current, "Keyboard&Mouse");
  90. }
  91. }
  92.  
  93. // Check for new gamepad input
  94. foreach (var gamepad in Gamepad.all)
  95. {
  96. if (gamepad.buttonSouth.wasPressedThisFrame)
  97. {
  98. if (!deviceToPlayer.ContainsKey(gamepad))
  99. {
  100. SpawnLocalPlayer(gamepad, "Gamepad");
  101. }
  102. }
  103. }
  104. }
  105.  
  106. //By pressing L, counts number of players and what player number they are
  107. if (Keyboard.current.lKey.wasPressedThisFrame)
  108. {
  109. Debug.Log("=== Current Players in AllPlayers List ===");
  110.  
  111. for (int i = 0; i < AllPlayers.Count; i++)
  112. {
  113. GameObject player = AllPlayers[i];
  114. Debug.Log($"Player {i}: {player.name}");
  115. }
  116.  
  117. Debug.Log($"Total players: {AllPlayers.Count}");
  118. }
  119.  
  120. if (Keyboard.current.mKey.wasPressedThisFrame)
  121. {
  122. Debug.Log("RESTART");
  123. gManager.RestartGame();
  124. }
  125. }
  126.  
  127. void SpawnCouchCoopPlayers()
  128. {
  129. int playerCount = 0;
  130.  
  131. if (autoDetectControllers)
  132. {
  133. // Count connected input devices
  134. List<InputDevice> devices = new List<InputDevice>();
  135.  
  136. // Add keyboard if present
  137. if (Keyboard.current != null)
  138. {
  139. devices.Add(Keyboard.current);
  140. }
  141.  
  142. // Add all gamepads
  143. foreach (var gamepad in Gamepad.all)
  144. {
  145. devices.Add(gamepad);
  146. }
  147.  
  148. playerCount = Mathf.Min(devices.Count, maxPlayers);
  149.  
  150. // Spawn a player for each device
  151. for (int i = 0; i < playerCount; i++)
  152. {
  153. InputDevice device = devices[i];
  154. string controlScheme = (device is Keyboard) ? "Keyboard&Mouse" : "Gamepad";
  155. SpawnLocalPlayer(device, controlScheme);
  156. }
  157. }
  158. else
  159. {
  160. // Use manual player count
  161. playerCount = Mathf.Min(manualPlayerCount, maxPlayers);
  162.  
  163. // Spawn players with available devices
  164. List<InputDevice> availableDevices = new List<InputDevice>();
  165.  
  166. if (Keyboard.current != null)
  167. availableDevices.Add(Keyboard.current);
  168.  
  169. foreach (var gamepad in Gamepad.all)
  170. availableDevices.Add(gamepad);
  171.  
  172. for (int i = 0; i < playerCount; i++)
  173. {
  174. if (i < availableDevices.Count)
  175. {
  176. InputDevice device = availableDevices[i];
  177. string controlScheme = (device is Keyboard) ? "Keyboard&Mouse" : "Gamepad";
  178. SpawnLocalPlayer(device, controlScheme);
  179. }
  180. else
  181. {
  182. // Not enough controllers, spawn without input
  183. Debug.LogWarning($"Player {i + 1} spawned without controller (not enough devices)");
  184. SpawnPlayerWithoutInput(i);
  185. }
  186. }
  187. }
  188.  
  189. Debug.Log($"Spawned {playerCount} couch co-op players");
  190. }
  191.  
  192. void SpawnLocalPlayer(InputDevice device, string controlScheme)
  193. {
  194. var player = PlayerInput.Instantiate(playerPrefab, controlScheme: controlScheme, pairWithDevice: device);
  195. deviceToPlayer[device] = player.gameObject;
  196. SetupPlayer(player.gameObject, nextPlayerIndex, controlScheme);
  197. }
  198.  
  199. void SpawnPlayerWithoutInput(int playerIndex)
  200. {
  201. GameObject player = Instantiate(playerPrefab);
  202. SetupPlayer(player, playerIndex, "NoInput");
  203. }
  204.  
  205. private void SetupPlayer(GameObject player, int playerIndex, string controlScheme)
  206. {
  207. // Add to tracking
  208. AllPlayers.Add(player);
  209.  
  210. // Set spawn position
  211. if (spawnPoints.Length > 0)
  212. {
  213. int spawnIndex = nextPlayerIndex % spawnPoints.Length;
  214. player.transform.SetPositionAndRotation(spawnPoints[spawnIndex].position,spawnPoints[spawnIndex].rotation);
  215. }
  216.  
  217. // Set player index
  218. var pHealth = player.GetComponent<PlayerHealth>();
  219. if (pHealth != null)
  220. {
  221. pHealth.PlayerIndex = playerIndex;
  222. GameManager.Instance.UpdateLives(pHealth.PlayerIndex, pHealth.lives);
  223. //Gives GameManager lives, and tells it that new player has joined, which is then given to pHealth's RefreshHearts
  224. pHealth.SetSpawnManager(this);
  225. }
  226.  
  227. // Assign team & color
  228. int team = (AllPlayers.Count % 2 == 1) ? 1 : 2;
  229. playerTeams[player] = team;
  230. AssignTeamMaterial(player, team);
  231.  
  232. //Assign name
  233. player.name = $"P{playerIndex + 1}_{controlScheme}";
  234.  
  235. // Setup UI
  236. var invUI = player.GetComponent<InventoryUI>();
  237. if (invUI != null && controlScheme == "Gamepad")
  238. {
  239. // Check if this is a gamepad player somehow
  240. invUI.OnControllerUI();
  241. }
  242.  
  243.  
  244.  
  245. // Configure camera and layers
  246. var cam = player.GetComponentInChildren<CinemachineCamera>();
  247. if (cam != null)
  248. {
  249. Camera actualCamera = cam.GetComponentInChildren<Camera>();
  250. if (isCouchCOOP){
  251. actualCamera.targetDisplay = playerIndex;}
  252.  
  253. ConfigureCamera(actualCamera, playerIndex);
  254. /*
  255. var colorScript = actualCamera.GetComponent<PerCameraTeamColors>(); //failed attempt at using a different method for team colors
  256. if (colorScript != null)
  257. {
  258. colorScript.myTeam = team;
  259. }*/
  260.  
  261. AssignPlayerLayers(player.gameObject, nextPlayerIndex);
  262.  
  263. // Add audio listener if needed
  264. if (actualCamera.GetComponent<AudioListener>() == null && !isCouchCOOP){
  265. actualCamera.gameObject.AddComponent<AudioListener>();}
  266. if (actualCamera.GetComponent<AudioListener>() == null && isCouchCOOP && !couchCOOPAudioAlreadyAssigned){
  267. actualCamera.gameObject.AddComponent<AudioListener>();
  268. couchCOOPAudioAlreadyAssigned = true;}
  269. }
  270.  
  271. Debug.Log($"Player {playerIndex + 1} ({controlScheme}) setup complete");
  272. nextPlayerIndex++;
  273. }
  274.  
  275. void AssignPlayerLayers(GameObject player, int index)
  276. {
  277. Transform fp = player.transform.Find("FirstPerson");
  278. Transform tp = player.transform.Find("ThirdPerson");
  279.  
  280. if (fp != null && tp != null)
  281. {
  282. int fpLayer = LayerMask.NameToLayer($"P{index + 1}_FirstPerson");
  283. int tpLayer = LayerMask.NameToLayer($"P{index + 1}_ThirdPerson");
  284.  
  285. SetLayerRecursively(fp.gameObject, fpLayer);//Takes the FirstPerson root GameObject, Sets its layer and all children’s layers to fpLayer
  286. SetLayerRecursively(tp.gameObject, tpLayer);
  287. }
  288. }
  289.  
  290. void SetLayerRecursively(GameObject obj, int layer)
  291. {
  292. obj.layer = layer;
  293. foreach (Transform child in obj.transform)
  294. SetLayerRecursively(child.gameObject, layer);
  295. }
  296.  
  297.  
  298. void ConfigureCamera(Camera cam, int playerIndex)
  299. {
  300. int mask = 0;
  301.  
  302. // Add common layers everyone can see
  303. mask |= 1 << LayerMask.NameToLayer("Default");
  304. mask |= 1 << LayerMask.NameToLayer("Ground");
  305. mask |= 1 << LayerMask.NameToLayer("UI");
  306. mask |= 1 << LayerMask.NameToLayer("Grenade");
  307. mask |= 1 << LayerMask.NameToLayer("NoCollideWithGrenades");
  308.  
  309. // Add this player's own first-person body
  310. string ownFPLayer = $"P{playerIndex + 1}_FirstPerson";
  311. mask |= 1 << LayerMask.NameToLayer(ownFPLayer);
  312.  
  313. // Add this player's own UI
  314. string ownUILayer = $"UI_Player{playerIndex + 1}";
  315. mask |= 1 << LayerMask.NameToLayer(ownUILayer);
  316.  
  317. // Add third-person bodies of ALL other players (P1-P4)
  318. for (int i = 0; i < maxPlayers; i++)
  319. {
  320. if (i != playerIndex) // Don't add our own third-person body
  321. {
  322. string otherTPLayer = $"P{i + 1}_ThirdPerson";
  323. mask |= 1 << LayerMask.NameToLayer(otherTPLayer);
  324. }
  325. }
  326.  
  327. // Explicitly hide other players' UI layers
  328. for (int i = 0; i < maxPlayers; i++)
  329. {
  330. if (i != playerIndex)
  331. {
  332. string otherUILayer = $"UI_Player{i + 1}";
  333. mask &= ~(1 << LayerMask.NameToLayer(otherUILayer));
  334. }
  335. }
  336.  
  337. cam.cullingMask = mask;
  338. }
  339.  
  340. public static void SetPlayerHeavenVisibility(GameObject player, bool canSee)
  341. {
  342. var cam = player.GetComponentInChildren<Camera>();
  343. if (cam == null) return;
  344.  
  345. int heavenLayer = LayerMask.NameToLayer("HeavenStuff");
  346.  
  347. if (canSee)
  348. cam.cullingMask |= (1 << heavenLayer);
  349. else
  350. cam.cullingMask &= ~(1 << heavenLayer);
  351. }
  352.  
  353. public void RespawnPlayer(GameObject player) //Teleports them into heaven
  354. {
  355. int playerIndex = AllPlayers.IndexOf(player);
  356. if (playerIndex >= 0 && respawnPoints.Length > 0)
  357. {
  358. int spawnIndex = playerIndex % respawnPoints.Length;
  359. player.transform.position = respawnPoints[spawnIndex].position;
  360. player.transform.rotation = respawnPoints[spawnIndex].rotation;
  361. }
  362. }
  363.  
  364. void AssignTeamMaterial(GameObject player, int team)
  365. {
  366. Material teamMaterial = (team == 1) ? team1Material : team2Material;
  367.  
  368. // Find all renderers in FirstPerson and ThirdPerson
  369. Transform firstPerson = player.transform.Find("FirstPerson");
  370. Transform thirdPerson = player.transform.Find("ThirdPerson");
  371.  
  372. if (firstPerson != null)
  373. {
  374. foreach (Renderer renderer in firstPerson.GetComponentsInChildren<Renderer>()){
  375. renderer.material = teamMaterial;}
  376. }
  377.  
  378. if (thirdPerson != null)
  379. {
  380. foreach (Renderer renderer in thirdPerson.GetComponentsInChildren<Renderer>()){
  381. renderer.material = teamMaterial;}
  382. }
  383. }
  384.  
  385. // public method to get a player's team
  386. public int GetPlayerTeam(GameObject player)
  387. {
  388. if (playerTeams.ContainsKey(player)){
  389. return playerTeams[player];} //returns 1 or 2
  390. return -1; // Player not found
  391. }
  392.  
  393. public void RemovePlayer(GameObject player)
  394. {
  395. AllPlayers.Remove(player);
  396. playerTeams.Remove(player);
  397.  
  398. // Remove from device tracking
  399. InputDevice deviceToRemove = null;
  400. foreach (var kvp in deviceToPlayer)
  401. {
  402. if (kvp.Value == player)
  403. {
  404. deviceToRemove = kvp.Key;
  405. break;
  406. }
  407. }
  408. if (deviceToRemove != null)
  409. {
  410. deviceToPlayer.Remove(deviceToRemove);
  411. }
  412. }
  413.  
  414. // This is called by PlayerSpawner in network mode
  415. public void OnPrefabInstantiated(GameObject prefabInstance, PlayerID player, SceneID scene)
  416. {
  417. if (!isCouchCOOP)
  418. {
  419. SetupPlayer(prefabInstance, AllPlayers.Count, "Network"); //use count as player index
  420. }
  421. }
  422.  
  423. }
  424.  
Advertisement
Add Comment
Please, Sign In to add comment