4.3 Spawn Scripts
4.3.1 A Simple Spawn Controller
The reason we use a spawn controller is so that we can track what has been added to the game world, if required, or switch out the spawning system for something more robust (such as a third-party library from the Unity Asset Store like PoolManager).
The spawn controller we will build in this chapter will have a few extra functions that may be useful to game development such as SetUpPlayers, which is a function that takes a list of prefabs to instantiate, a list of start positions, start rotations, and the total number of players. The function will instantiate all of the players at the correct positions and rotations:
public void SetUpPlayers (GameObject[] playerPrefabs, Vector3[] playerStartPositions, Quaternion[] playerStartRotations, Transform theParentObj, int totalPlayers)
An array of player prefabs can easily be set up and passed into the spawn controller to instantiate, letting the spawn controller take care of building them rather than having to write instantiation functions for each game. For example, the racing game example Motor
Vehicle Doom will have an array of vehicles that may be set up in the Inspector window of
the Unity editor. This array gets passed to the spawn controller and instantiated. By deal- ing with players in this way, we can easily switch out player types between levels or even build the list dynamically if required.
Below is the SpawnController.cs script:
using UnityEngine; using System.Collections;
public class SpawnController : ScriptableObject {
private ArrayList playerTransforms; private ArrayList playerGameObjects;
private Transform tempTrans; private GameObject tempGO;
private GameObject[] playerPrefabList; private Vector3[] startPositions; private Quaternion[] startRotations;
// singleton structure based on AngryAnt’s fantastic wiki entry // over at http://wiki.unity3d.com/index.php/Singleton
private static SpawnController instance;
public SpawnController () {
// this function will be called whenever an instance of the // SpawnController class is made
// first, we check that an instance does not already exist // (this is a singleton, after all!)
if (instance != null) {
// drop out if instance exists, to avoid generating // duplicates
Debug.LogWarning("Tried to generate more than one instance of singleton SpawnController.");
return; }
// as no instance already exists, we can safely set instance // to this one
instance = this; }
public static SpawnController Instance {
// to every other script, this getter setter is the way they // get access to the singleton instance of this script get
{
// the other script is trying to access an instance // of this script, so we need to see if an instance // already exists
if (instance == null) {
// no instance exists yet, so we go ahead and // create one
ScriptableObject.CreateInstance<SpawnController>(); // new SpawnController (); }
// now we pass the reference to this instance back
// to the other script so it can communicate with it
return instance;
} }
public void Restart () {
playerTransforms=new ArrayList();
playerGameObjects=new ArrayList();
}
public void SetUpPlayers (GameObject[] playerPrefabs, Vector3[] playerStartPositions, Quaternion[] playerStartRotations, Transform theParentObj, int totalPlayers)
{
// we pass in everything needed to spawn players and take // care of spawning players in this class so that we don't // have to replicate this code in every game controller
playerPrefabList= playerPrefabs;
startPositions= playerStartPositions;
51 4.3 Spawn Scripts
// call the function to take care of spawning all the // players and putting them in the right places
CreatePlayers( theParentObj, totalPlayers ); }
public void CreatePlayers ( Transform theParent, int totalPlayers ) {
playerTransforms=new ArrayList();
playerGameObjects=new ArrayList();
for(int i=0; i<totalPlayers;i++) {
// spawn a player
tempTrans= Spawn ( playerPrefabList[i], startPositions[i], startRotations[i] );
// if we have passed in an object to parent the // players to, set the parent
if(theParent!=null) {
tempTrans.parent= theParent;
// as we are parented, let's set the local // position
tempTrans.localPosition= startPositions[i];
}
// add this transform into our list of player // transforms
playerTransforms.Add(tempTrans);
// add its gameobject into our list of player // gameobjects (we cache them separately)
playerGameObjects.Add (tempTrans.gameObject);
} }
public GameObject GetPlayerGO (int indexNum) {
return (GameObject)playerGameObjects[indexNum];
}
public Transform GetPlayerTransform (int indexNum) {
return (Transform)playerTransforms[indexNum];
}
public Transform Spawn(GameObject anObject, Vector3 aPosition, Quaternion aRotation)
{
tempGO=(GameObject)Instantiate(anObject, aPosition, aRotation);
tempTrans= tempGO.transform;
// return the object to whatever was calling
return tempTrans;
}
// here we just provide a convenient function to return the spawned // objects gameobject rather than its transform
public GameObject SpawnGO(GameObject anObject, Vector3 aPosition, Quaternion aRotation)
{
// instantiate the object
tempGO=(GameObject)Instantiate(anObject, aPosition, aRotation);
tempTrans= tempGO.transform;
// return the object to whatever was calling
return tempGO;
}
public ArrayList GetAllSpawnedPlayers() {
return playerTransforms;
} }
4.3.1.1 Script Breakdown
The SpawnController class derives from ScriptableObject:
public class SpawnController : ScriptableObject {
After the variable declarations, SpawnController.cs sets up as a singleton. As it says in the comments, this code is based on the work of AngryAnt (published on the Unity wiki site http://wiki.unity3d.com/index.php?title=Singleton). Recall that the singleton pattern was discussed earlier in Chapter 2. If you missed it, this just takes care of only ever having one instance of the script in existence at any time so that we always use the same instance regardless of where or how it is accessed by other scripts.
private static SpawnController instance; public SpawnController ()
{
// this function will be called whenever an instance of the // SpawnController class is made
// first, we check that an instance does not already exist // (this is a singleton, after all!)
53 4.3 Spawn Scripts
{
// drop out if instance exists, to avoid generating // duplicates
Debug.LogWarning("Tried to generate more than one instance of singleton SpawnController.");
return; }
// as no instance already exists, we can safely set instance // to this one
instance = this; }
public static SpawnController Instance {
// to every other script, this getter setter is the way they // get access to the singleton instance of this script get
{
// the other script is trying to access an instance // of this script, so we need to see if an instance // already exists
if (instance == null) {
// no instance exists yet, so we go ahead and // create one
ScriptableObject.CreateInstance<SpawnController>(); // new SpawnController (); }
// now we pass the reference to this instance back
// to the other script so it can communicate with it
return instance;
} }
}
After the setup functions, the script moves on to a simple Restart() function that clears out all of the ArrayLists used later on by the script.
public void Restart () {
playerTransforms=new ArrayList();
playerGameObjects=new ArrayList();
objectList=new ArrayList();
}
The SetUpPlayers function is a very specific function designed for a specific purpose. That is, it functions to pass in information about the players in the game represented by a series of arrays and an integer to say how many players are in the game in total. This information is not used immediately and will be used later on in the script by the CreatePlayers() function.
public void SetUpPlayers (GameObject[] playerPrefabs, Vector3[] playerStartPositions, Quaternion[] playerStartRotations, Transform theParentObj, int totalPlayers)
// we pass in everything needed to spawn players and take // care of spawning players in this class so that we don't // have to replicate this code in every game controller
playerPrefabList= playerPrefabs;
startPositions= playerStartPositions;
startRotations= playerStartRotations;
// call the function to take care of spawning all the // players and putting them in the right places
CreatePlayers( theParentObj, totalPlayers ); }
CreatePlayers() takes all of the information passed into the SetUpPlayers() function and deals with the actual instantiation of the player objects into the game scene:
public void CreatePlayers ( Transform theParent, int totalPlayers ) {
playerTransforms=new ArrayList();
playerGameObjects=new ArrayList();
for(int i=0; i<totalPlayers;i++) {
// spawn a player
tempTrans= Spawn ( playerPrefabList[i], startPositions[i], startRotations[i] );
// if we have passed in an object to parent the // players to, set the parent
if(theParent!=null) {
tempTrans.parent= theParent;
// as we are parented, let's set the local // position
tempTrans.localPosition= startPositions[i];
}
// add this transform into our list of player // transforms
playerTransforms.Add(tempTrans);
// add its gameobject into our list of player // gameobjects (we cache them separately)
playerGameObjects.Add (tempTrans.gameObject);
} }
The GetPlayerGO(indexNum) function allows us to get back some information about the players spawned by the CreatePlayers() function. It simply grabs an entry from the playerGameObjects array and returns it, so that we can always get access to players via a simple integer index number.
public GameObject GetPlayerGO (int indexNum) {
55 4.3 Spawn Scripts
return (GameObject)playerGameObjects[indexNum];
}
As with GetPlayerGO(), the GetPlayerTransform() function allows us to quickly access the transform of a particular player indexed via an integer.
public Transform GetPlayerTransform (int indexNum) {
return (Transform)playerTransforms[indexNum];
}
For incidental spawning, such as particle effects, the SpawnController.cs script provides a Spawn() method.
At this stage, you need to understand what prefabs are, and in turn, how they can be dynamically added to the scene. To quote the Unity documentation:
[Prefabs are] … a collection of predefined GameObjects & Components that are re-usable throughout your game.
They are saved into the project, available in the same way regular assets are, and may be dragged into a scene or added dynamically through code with the Instantiate keyword. Instantiate makes a clone of an object you pass in as a parameter and returns a reference to the clone.
Instantiate takes three parameters:
object An existing object (such as a prefab or gameObject) to make a copy of position A Vector3 position for the new object
rotation A Quaternion rotation for the new object
One important consideration to keep in mind is that there is a CPU hit for object instan- tiation as memory is freed and allocated for it. This is much more noticeable on mobile plat- forms, particularly those lower performing systems such as older (3–4-year-old) devices. In situations where performance is an issue, it is advisable to use an object pooling system. Test early, test often, and keep an eye out for performance hits like this.
By passing in a prefab (gameObject), a 3D position vector, and a Quaternion rotation value, the Spawn function will instantiate (make an instance of) an object in the correct place with the correct rotation and return the transform of the newly instantiated object:
public Transform Spawn(GameObject anObject, Vector3 aPosition, Quaternion aRotation)
{
if(objectList==null)
objectList=new ArrayList();
// instantiate the object
tempGO=(GameObject)Instantiate(anObject, aPosition,
aRotation);
// store this object in our list of objects objectList.Add(tempTrans);
// return the object to whatever was calling
return tempTrans;
}
For occasions where it is preferable to return a newly instantiated object’s gameObject rather than the transform, the SpawnGO (GO being short for GameObject) function is available. It works exactly the same as the Spawn function, but instead of returning the newly instantiated object’s transform, it returns its gameObject.
// here we just provide a convenient function to return the spawned // objects gameobject rather than its transform
public GameObject SpawnGO(GameObject anObject, Vector3 aPosition, Quaternion aRotation)
{
if(objectList==null)
objectList=new ArrayList();
// instantiate the object
tempGO=(GameObject)Instantiate(anObject, aPosition,
aRotation);
tempTrans= tempGO.transform;
// store this object in our list of objects objectList.Add(tempTrans);
// return the object to whatever was calling
return tempGO;
}
The SpawnController.cs script track every object that it has created. Each one is stored in the array (an ArrayList type variable) named objectList. To have SpawnController.cs return the whole list, the function GetAllSpawnedTransforms() is provided.
public ArrayList GetAllSpawnedTransforms() {
return objectList;
} }