Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Resolved Creating Multiple Spawn Points

Discussion in 'Scripting' started by Coreviz22, Dec 6, 2022.

  1. Coreviz22

    Coreviz22

    Joined:
    Oct 28, 2022
    Posts:
    8
    This is for an office layout

    I have a main menu that works great. As part of it have a way to select which part of the level to start in. The issue I have right now is I can't figure out how to set the player to a specific position based on which part of the level the player has selected. For example:

    Player selects the "Commons / Break Room" area. I want the player to spawn in that part of the level. I have the scene broken up into 6 different parts to help with loading and I have a trigger system to load and unload the different parts as the player moves around the space. I have decided that the trigger game objects are set up in the positions that I want which helps because then it will trigger the areas around it to load in based on the level area selection.

    I started with an array to grab all of the trigger game objects but I don't know how to grab their transform.position so that I can set my player to that position.

    I would have assumed that I could make an array (or list) that has one field for the trigger game object and another field with a vector3 to auto fill in the position but I was only able to do that manually at one point so I decided to try something else.

    This is my GameManager Code right now:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.SceneManagement;
    5. using UnityEngine.Events;
    6. using UnityEngine.UI;
    7. using TMPro;
    8.  
    9.  
    10. public class GameManager : MonoBehaviour
    11. {
    12.     //Menu and Loading Screen
    13.     [SerializeField] GameObject _mainMenu;
    14.     [SerializeField] GameObject _loadingScreen;
    15.     [SerializeField] Image _progressBar;
    16.     //List for level names to work with trigger load and unload system
    17.     public List<SelectLevel> level = new List<SelectLevel>();
    18.     public TMP_Text levelLabel;
    19.     [SerializeField] int selectedLevel;
    20.     //Spawnpoint system (WIP)
    21.     [SerializeField] GameObject _spawnPoints;
    22.     [SerializeField] GameObject _player;
    23.  
    24.     public Vector3 _spawnLocation;
    25.  
    26.     //set menu active and loading screen inactive
    27.     private void Awake()
    28.     {
    29.         _mainMenu.SetActive(true);
    30.         _loadingScreen.SetActive(false);
    31.     }
    32.  
    33.     //The start of the Spawn system. (WIP)
    34.     private void Start()
    35.     {
    36.         _player = (GameObject)Resources.Load("FirstPersonController", typeof(GameObject));
    37.  
    38.  
    39.  
    40.         _spawnPoints = GameObject.FindGameObjectWithTag("Spawners");
    41.  
    42.         levelLabel.text = level[selectedLevel].levelName.ToString();
    43.         Debug.Log(levelLabel.text);
    44.  
    45.         _spawnLocation = _player.transform.position;
    46.  
    47.         SpawnCharacter();
    48.     }
    49.  
    50.     private void SpawnCharacter()
    51.     {
    52.         GameObject.Instantiate(_player, _spawnPoints.transform.position, Quaternion.identity);
    53.     }
    54.  
    55.     public void SetSpawnPoint()
    56.     {
    57.         levelLabel.text = level[selectedLevel].levelName.ToString();
    58.         Debug.Log(levelLabel.text);
    59.  
    60.      
    61.     }
    62.  
    63.     //Start Coroutine for the level selected.  This will change to only loading the Full Model scene
    64.     ////and then it will async load additively the part of the scene that I want
    65.     //the player to spawn at
    66.     public void LoadLevel()
    67.     {
    68.         StartCoroutine(LoadAsyncOperation(selectedLevel));
    69.     }
    70.  
    71.     //This is for the toggle buttons on the main menu to cycle through the list of spawn locations
    72.     public void LeftToggle()
    73.     {
    74.         selectedLevel--;
    75.         if (selectedLevel < 0)
    76.         {
    77.             selectedLevel = 0;
    78.         }
    79.         UpdateLevelLabel();
    80.     }
    81.  
    82.     public void RightToggle()
    83.     {
    84.         selectedLevel++;
    85.         if (selectedLevel > level.Count - 1)
    86.         {
    87.             selectedLevel = level.Count - 1;
    88.         }
    89.         UpdateLevelLabel();
    90.     }
    91.  
    92.     //this takes the level name form the list and converts it to a string that is tied to the list number.
    93.     public void UpdateLevelLabel()
    94.     {
    95.         levelLabel.text = level[selectedLevel].levelName.ToString();
    96.     }
    97.  
    98.  
    99.     //This was followed from a loading screen tutorial so I can get a progress bar.
    100.     //The selectedLevel is being derived from the list element number.
    101.     ////I then have to add 1 so that it lines up with the index assigned to each scene that needs to be loaded
    102.     IEnumerator LoadAsyncOperation(int selectedLevel)
    103.     {
    104.         _progressBar.fillAmount = 0;
    105.         _loadingScreen.SetActive(true);
    106.  
    107.         AsyncOperation loadLevel = SceneManager.LoadSceneAsync(selectedLevel + 1);
    108.         loadLevel.allowSceneActivation = false;
    109.         float progress = 0f;
    110.  
    111.         while (!loadLevel.isDone)
    112.         {
    113.             progress = Mathf.MoveTowards(progress, loadLevel.progress, Time.deltaTime);
    114.             _progressBar.fillAmount = progress;
    115.             if(progress >= 0.9f)
    116.             {
    117.                 _progressBar.fillAmount = 1;
    118.                 loadLevel.allowSceneActivation = true;
    119.             }
    120.             yield return null;
    121.         }
    122.     }
    123.  
    124.  
    125.     //Quit program from main menu
    126.     public void QuitProgram()
    127.     {
    128.         Debug.Log("Quitting Application");
    129.         Application.Quit();
    130.     }
    131.  
    132.     //Carries the GameManager to the Full Model Scene
    133.     private void OnDestroy()
    134.     {
    135.         Debug.Log("GameStatus was destroyed.");
    136.     }
    137. }
    138.  
    139.  
    140. //This was also part of the menu tutorial I followed.
    141. //I tried to use this sort of system originally for the spawn locations but couldn't figure out how to auto set the vector3 positioning
    142. [System.Serializable]
    143. public class SelectLevel
    144. {
    145.     public string levelName;
    146. }
    This is my current SpawnManager code:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class SpawnManager : MonoBehaviour
    6. {
    7.     [SerializeField] GameObject[] _spawnPoints;
    8.     [SerializeField] GameObject _player;
    9.  
    10.     public Vector3 _spawnLocation;
    11.  
    12.     void Awake()
    13.     {
    14.      
    15.     }
    16.  
    17.     private void Start()
    18.     {
    19.         _player = (GameObject)Resources.Load("FirstPersonController", typeof(GameObject));
    20.  
    21.         _spawnPoints = GameObject.FindGameObjectsWithTag("Spawners");
    22.  
    23.         _spawnLocation = _player.transform.position;
    24.  
    25.         SpawnCharacter();
    26.     }
    27.  
    28.     private void SpawnCharacter()
    29.     {
    30.         GameObject.Instantiate(_player, _spawnPoints.transform.position, Quaternion.identity);
    31.     }
    32. }
    I am still very new to all this so a lot of this code is mashed together from multiple videos and threads to get the pieces that I want and then trying to work out how to reference each other in a uniform way.
     
    Last edited: Dec 6, 2022
  2. Brathnann

    Brathnann

    Joined:
    Aug 12, 2014
    Posts:
    7,186
    Why does gamemanager and spawnmanager both try to spawn the player?
    Also, looking at the SpawnCharacter in SpawnManager, the spawnPoints is an array, but you aren't targetting an index, so this should throw an error.

    Either way, with these things in mind, if you have a gameobject, then you have access to the transform.position. You even try to use this value when you instantiate the player.

    The position is a Vector3.
     
  3. Coreviz22

    Coreviz22

    Joined:
    Oct 28, 2022
    Posts:
    8
    I have it trying to spawn in both scripts as the Spawn Manager script is what I need to actually set the spawn location and spawn the player there. I want the GameManager to send over the selected start area to spawn manager so it knows which area to set as spawn.

    I think that may be where I am struggling. How do I pull from GameManager the Spawn Location Name into the Spawn Manager script so that I can specify which spawner needs to be used in the array.

    I tried to simplify things for me last night by changing the entire Spawn Manager script to just be more basic for me to understand.

    I changed it so that each and every spawn game object is found using GameObject.Find and then I have another field that is set up to pull the vector3 from that game object and save it as a different name. This is working but I know it is not the most efficent. I think the biggest part I need assistance with is figureing out how to detect which spawn name has been selected so that I can have it load additively with the full model scene and also send over the name to SpawnManager so it can specify the spawn location for the player and spawn the player there.

    This is the new code I worked on last night for SpawnManager. I'm sure the original way is much better performance wise but I needed to try and work it out like this for me to better understand what I need.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using TMPro;
    5. using UnityEngine.UI;
    6.  
    7. public class SpawnManager : MonoBehaviour
    8. {
    9.     GameManager _gameManager;
    10.     [SerializeField]GameObject _manager;
    11.  
    12.  
    13.     [SerializeField] GameObject _player;
    14.     [SerializeField] Vector3 _playerSpawnLocation;
    15.  
    16.     [SerializeField] GameObject _receptionName;
    17.     [SerializeField] Vector3 _reception;
    18.  
    19.     [SerializeField] GameObject _marketingName;
    20.     [SerializeField] Vector3 _marketing;
    21.  
    22.     [SerializeField] GameObject _interiorName;
    23.     [SerializeField] Vector3 _interior;
    24.  
    25.     [SerializeField] GameObject _mainBPName;
    26.     [SerializeField] Vector3 _mainBP;
    27.  
    28.     [SerializeField] GameObject _commonsName;
    29.     [SerializeField] Vector3 _commons;
    30.  
    31.     [SerializeField] GameObject _secondaryBPName;
    32.     [SerializeField] Vector3 _secondaryBP;
    33.  
    34.  
    35.  
    36.  
    37.     void Awake()
    38.     {
    39.         _gameManager = _manager.GetComponent<GameManager>();
    40.        
    41.         _receptionName = GameObject.Find("1 Reception");
    42.         _reception = _receptionName.transform.position;
    43.  
    44.         _marketingName = GameObject.Find("2 Marketing Arch Viz");
    45.         _marketing = _marketingName.transform.position;
    46.  
    47.         _interiorName = GameObject.Find("3 Interiors");
    48.         _interior = _interiorName.transform.position;
    49.  
    50.         _mainBPName = GameObject.Find("4 Main Bullpen");
    51.         _mainBP = _mainBPName.transform.position;
    52.  
    53.         _commonsName = GameObject.Find("5 Commons Break Room");
    54.         _commons = _commonsName.transform.position;
    55.  
    56.         _secondaryBPName = GameObject.Find("6 Secondary Bullpen");
    57.         _secondaryBP = _secondaryBPName.transform.position;
    58.  
    59.         _player = GameObject.FindGameObjectWithTag("Player");
    60.  
    61.         SetSpawnLocation();
    62.  
    63.     }
    64.  
    65.     void SetSpawnLocation()
    66.     {
    67.         string levelName = _gameManager.UpdateLevelLabel();
    68.  
    69.         Debug.Log(levelName);
    70.        
    71.     }
    72. }
    73.  
    Thanks for helping. I have been working on this for a month and I have never done c# codeing in my life so I am still trying to figure out everything.
     
  4. Brathnann

    Brathnann

    Joined:
    Aug 12, 2014
    Posts:
    7,186
    Break this down into steps.

    1. Setup your select spawn points. Make sure the points know what they are and can report that info. (even if tapping/clicking to select just spits out a debug message)
    2. Have that info get to your GameManager.
    3. Have the GameManager process it if needed. At this point if you need it to update any UI or whatever, do that.
    4. Have the GameManager pass that string to your SpawnManager.
    5. Have SpawnManager compare it to the list and spawn in your player.
    6. Other things.

    This isn't an exact list, but the idea is you shouldn't jump from step 1 to step 5 in one coding pass. Do each piece and see where that gets you. And if you need help with the smaller steps, ask about it. Explain what you are experiencing, how your things are setup, what you expected to happen.
     
  5. Coreviz22

    Coreviz22

    Joined:
    Oct 28, 2022
    Posts:
    8
    That helps. I think that is were I keep getting stuck is trying to get to far ahead to fast.
     
    Last edited: Dec 7, 2022
  6. Coreviz22

    Coreviz22

    Joined:
    Oct 28, 2022
    Posts:
    8
    I think I am finally making some good progress. I have my spawn script set up that seems to work on its own. Now I want the get the gamemanager script to pass over the right data. Which it sort of will. The issue I have is the gamemanager is referencing to a few gameobjects that I only have in the main menu. AKA the Main Menu, Loading Screen and Progress Bar. I don't need to carry those objects to the next scene as of right now. The future goal will to be able to add floors and each floor will be a scene broken up into smaller parts like I currently have it. When I go from floor 1 to floor 2 then I will have a loading screen then but I am not near that yet.

    I am thinking of changing the Gamemanager to just be the MainMenu script and then have a different Gamemanager that will go between scenes. The issue I face then is it need to be able to reference the MainMenu to pull the name of the spawn location, carry that to the next scene, and pass it to the SpawnManager. I then run into the same issue as before. I still need to reference the MainMenu script for the data I need to be able to pass it along but then when it goes to the next scene it can't find the gameobject MainMenu since it is only in the Main Menu scene.
     
  7. Brathnann

    Brathnann

    Joined:
    Aug 12, 2014
    Posts:
    7,186
    So, if you have a MainMenu script and let's say buttons on the menu that you tap on to set your spawn location, then when a button is tapped on, that MainMenu script needs to pass the spawn location to your GameManager. Your GameManager probably should follow a singleton pattern since you plan to have it float.

    Then when your game scene loads, SpawnManager can ask the GameManager script where it needs to spawn the player and retrieve that info.

    In this particular case, any scripts that are tied to your scene should always send their info to your GameManager if you need that info in other scenes. Your GameManager doesn't need to ask for that info.
     
  8. Coreviz22

    Coreviz22

    Joined:
    Oct 28, 2022
    Posts:
    8
    I have ran into a few tutorials about singltons but I don't fully understand it. I get that it should be set up as a static class right. That way it doesn't have read other scripts by referencing a game object. I think that is where I struggle. Right now my GameManager is needing to reference a gameobject just to pull the data it needs for the spawn name. So when it goes to the next scene it no longer has access to that gameobject as it was in the previous scene and the it gives an error.
     
  9. Coreviz22

    Coreviz22

    Joined:
    Oct 28, 2022
    Posts:
    8
    I think I got it to work. I used PlayerPrefs to setstring and then was able to use it to getstring.

    Now something weird is happening. It wasn't happening earlier but now when I load my Full Model scene it detects the addition scene to load additively, the triggers activate to load the other scenes based on trigger enter. But now I get an endless loop of scenes loading and unloading uncontrollably until they ultimately just stop all together or I close the editor.

    This is my new MainMenu code originally GameManager

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.SceneManagement;
    5. using UnityEngine.Events;
    6. using UnityEngine.UI;
    7. using TMPro;
    8.  
    9.  
    10. public class MainMenu : MonoBehaviour
    11. {
    12.     //Menu and Loading Screen
    13.     [SerializeField] GameObject _mainMenu;
    14.     [SerializeField] GameObject _loadingScreen;
    15.     [SerializeField] Image _progressBar;
    16.  
    17.     //List for level names to work with trigger load and unload system
    18.     [SerializeField] List<SelectLevel> level = new();
    19.     [SerializeField] TMP_Text levelLabel;
    20.     [SerializeField] int selectedLevel;
    21.  
    22.     //set menu active and loading screen inactive
    23.     //Keep on GameManager
    24.     private void Awake()
    25.     {
    26.         _mainMenu.SetActive(true);
    27.         _loadingScreen.SetActive(false);
    28.     }
    29.  
    30.     //Start Coroutine for the level selected.  This will change to only loading the Full Model scene
    31.     ///and then it will async load additively the part of the scene that I want
    32.     //the player to spawn at
    33.     public void LoadLevel()
    34.     {
    35.         StartCoroutine(LoadAsyncOperation());
    36.  
    37.     }
    38.  
    39.     //This is for the toggle buttons on the main menu to cycle through the list of spawn locations
    40.     public void LeftToggle()
    41.     {
    42.         selectedLevel--;
    43.         if (selectedLevel < 0)
    44.         {
    45.             selectedLevel = 0;
    46.         }
    47.         UpdateLevelLabel();
    48.     }
    49.  
    50.     public void RightToggle()
    51.     {
    52.         selectedLevel++;
    53.         if (selectedLevel > level.Count - 1)
    54.         {
    55.             selectedLevel = level.Count - 1;
    56.         }
    57.         UpdateLevelLabel();
    58.     }
    59.  
    60.     //this takes the level name form the list and converts it to a string that is tied to the list number.
    61.     private void UpdateLevelLabel()
    62.     {
    63.         levelLabel.text = level[selectedLevel].levelName.ToString();
    64.         PlayerPrefs.SetString("Level To Load", levelLabel.text);
    65.     }
    66.  
    67.  
    68.  
    69.     //This was followed from a loading screen tutorial so I can get a progress bar.
    70.     //The selectedLevel is being derived from the list element number.
    71.     ////I then have to add 1 so that it lines up with the index assigned to each scene that needs to be loaded
    72.     IEnumerator LoadAsyncOperation()
    73.     {
    74.         _progressBar.fillAmount = 0f;
    75.  
    76.         AsyncOperation mainSceneProgress = SceneManager.LoadSceneAsync("Full Model");
    77.         AsyncOperation secondarySceneProgress = SceneManager.LoadSceneAsync(PlayerPrefs.GetString("Level To Load"), LoadSceneMode.Additive);
    78.  
    79.         mainSceneProgress.allowSceneActivation = false;
    80.         secondarySceneProgress.allowSceneActivation = false;
    81.  
    82.         float progress = 0f;
    83.  
    84.         while (!mainSceneProgress.isDone && !secondarySceneProgress.isDone)
    85.         {
    86.             progress = Mathf.MoveTowards(progress, mainSceneProgress.progress + secondarySceneProgress.progress, Time.deltaTime);
    87.  
    88.             _progressBar.fillAmount = progress;
    89.  
    90.             if (progress >= 0.9f)
    91.             {
    92.                 _progressBar.fillAmount = 1;
    93.                 mainSceneProgress.allowSceneActivation = true;
    94.                 secondarySceneProgress.allowSceneActivation = true;
    95.             }
    96.             yield return null;
    97.         }
    98.     }
    99.  
    100.  
    101.     //Quit program from main menu
    102.     public void QuitProgram()
    103.     {
    104.         Debug.Log("Quitting Application");
    105.         Application.Quit();
    106.     }
    107. }
    108.  
    109.  
    110. //This was also part of the menu tutorial I followed.
    111. //I tried to use this sort of system originally for the spawn locations but couldn't figure out how to auto set the vector3 positioning
    112. [System.Serializable]
    113. public class SelectLevel
    114. {
    115.     public string levelName;
    116. }
    117.  
    And here is my SpawnManager

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.SceneManagement;
    5.  
    6. public class SpawnManager : MonoBehaviour
    7. {
    8.     [SerializeField] GameObject[] _spawnPoints;
    9.     [SerializeField] GameObject _player;
    10.  
    11.     [SerializeField] GameObject _tempGround;
    12.  
    13.     [SerializeField] string _spawnName;
    14.     private string SpawnName;
    15.  
    16.     public GameObject _objectName;
    17.     public Vector3 _spawnLocation;
    18.  
    19.  
    20.  
    21.     private void Awake()
    22.     {
    23.         _spawnName = (PlayerPrefs.GetString("Level To Load"));
    24.  
    25.         _spawnPoints = GameObject.FindGameObjectsWithTag("Spawners");
    26.  
    27.         StartCoroutine(SpawnPoints());
    28.     }
    29.  
    30.  
    31.  
    32.     IEnumerator SpawnPoints()
    33.     {
    34.         for (int i = 0; i < _spawnPoints.Length; i++)
    35.         {
    36.             if (_spawnPoints[i].name == _spawnName)
    37.             {
    38.                 SpawnName = _spawnPoints[i].name;
    39.  
    40.                 _objectName = GameObject.Find(SpawnName);
    41.                 _spawnLocation = _objectName.transform.position;
    42.  
    43.                 yield return new WaitForSecondsRealtime(5);
    44.  
    45.                 GameObject.Instantiate(_player, _spawnLocation + new Vector3(0, 2, 0), Quaternion.identity);
    46.                 GameObject.Instantiate(_tempGround, _spawnLocation, Quaternion.identity);
    47.  
    48.  
    49.             }
    50.             yield return null;
    51.         }
    52.  
    53.     }
    54.  
    55.  
    56. }
    57.  
    I am trying to find a way to get the progress bar to detect the loading progress of both the full model scene and the scene I am adding additively. I am wondering if that is the issue for the loop. However I have noticed that the loop doesn't start happening until my player spawns. So I wonder if something is fighting with my ScenePartLoader which has an if statement to check if there is a scene already loaded and if so to not load that scene again.

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.SceneManagement;
    3.  
    4.  
    5. public class ScenePartLoader : MonoBehaviour
    6. {
    7.     public Transform player;
    8.  
    9.  
    10.     //Scene State
    11.     public bool isLoaded;
    12.     public bool shouldLoad;
    13.  
    14.     void Start()
    15.     {
    16.         //verify if the scene is already open to avoid opening a scene twice
    17.         if (SceneManager.sceneCount > 0)
    18.         {
    19.             for (int i = 0; i < SceneManager.sceneCount; ++i)
    20.             {
    21.                 Scene scene = SceneManager.GetSceneAt(i);
    22.                 if (scene.name == gameObject.name)
    23.                 {
    24.                     isLoaded = true;
    25.                 }
    26.             }
    27.         }
    28.     }
    29.  
    30.     void FixedUpdate()
    31.     {
    32.  
    33.         TriggerCheck();
    34.  
    35.     }
    36.  
    37.  
    38.  
    39.     void LoadScene()
    40.     {
    41.         if (!isLoaded)
    42.         {
    43.             SceneManager.LoadSceneAsync(gameObject.name, LoadSceneMode.Additive);
    44.             isLoaded = true;
    45.         }
    46.     }
    47.  
    48.     void UnLoadScene()
    49.     {
    50.         if (isLoaded)
    51.         {
    52.             SceneManager.UnloadSceneAsync(gameObject.name);
    53.             isLoaded = false;
    54.         }
    55.     }
    56.  
    57.     private void OnTriggerEnter(Collider other)
    58.     {
    59.         if(other.CompareTag("Player"))
    60.         {
    61.             shouldLoad = true;
    62.         }
    63.     }
    64.  
    65.     private void OnTriggerExit(Collider other)
    66.     {
    67.         if (other.CompareTag("Player"))
    68.         {
    69.             shouldLoad = false;
    70.         }
    71.     }
    72.  
    73.     void TriggerCheck()
    74.     {
    75.         if (shouldLoad)
    76.         {
    77.             LoadScene();
    78.         }
    79.         else
    80.         {
    81.             UnLoadScene();
    82.         }
    83.     }
    84. }
    Sorry for this being so long. I am just so close and I think this is the last major issue I need to solve to have the basic game function I want.
     
    Last edited: Dec 9, 2022