This part will add doors leading a level to another and a logic for adding/moving the PC object in the correct place on the scene.
Previous parts of tutorial
New functionality that I intent to introduce require adding logic to the PC. Lets start that by creating minimal implement of the CharacterInterface. First, I add features I need to implement doors leading from a level to another. I set-up door object so that each door has a spawn point attached to each other (see figure below) and that door is in two levels: one side in one and the other side in the other. (I am using scifi door from Unity assets store by 3DMondra as model in the image).

PlayerLogic.cs
using UnityEngine; using System.Collections; public class PlayerLogic : MonoBehaviour, CharacterInterface { public const string CURRENT_LEVEL_KEY = "c"; public const string LAST_DOOR_KEY = "d"; bool CharacterInterface.CanUseHealth() { return true; } bool CharacterInterface.CanUseEnergy() { return true; } void CharacterInterface.Save(string levelName, string door) { // save will be used to keep track which level we are in and // and which door was used to come to that level Debug.Log ("PlayerLogic.Save(" + levelName + ")"); PlayerPrefs.SetString(CURRENT_LEVEL_KEY, levelName); PlayerPrefs.SetString(LAST_DOOR_KEY, door); } void Start() { Transform startPosition = null; string door = PlayerPrefs.GetString(LAST_DOOR_KEY); Debug.Log ("PlayerLogic.OnLevelWasLoaded(): door = " + door); if (door != "") { // door was used when the door has value. Now we need to find the door GameObject doorObj = GameObject.Find(door); if (doorObj) { // and child object called spawn that indicate where we should spawn // the player object. startPosition = doorObj.transform.Find("spawn"); if(startPosition == null) { Debug.LogError ("PlayerLogic.OnLevelWasLoaded(): " + doorObj.name + " has now spawn point"); } } } if(startPosition == null) { // if we did not come via door, we use object with tag PlayerStartPosition. GameObject ps = GameObject.FindWithTag("PlayerStartPoint"); if (startPosition) { startPosition = ps.transform; } } // now lets place and orient the PC based on the found start position object. transform.rotation = startPosition.transform.rotation; transform.position = startPosition.position; } bool CharacterInterface.IsInvisible () { return false; } void CharacterInterface.ReceiveDamage (int amount) { } void CharacterInterface.PowerUp (int _energy, int _health) { } }
As you noticed, I do not want to place the PC directly in the scenes by place PC based on doors used to come to the level or StartPositon objects. For this we need to have a manager that instantiate the PC when needed. Lets create empty GameObject and call it GameManager. I create GameAgents script that handles instantiating the PC using prefab. GameAgent is singleton. That means that there can be only one instance of the script in the scene. Multiple instances will lead problems.
[EDIT1, added functionality for handling door randomisation, see below.]
GameAgents.cs
using UnityEngine; using System.Collections; using UnityEngine.UI; public class GameAgents : MonoBehaviour { public bool debug = false; public GameObject playerPrefab; public Slider energyBar; public Slider healthBar; public Button debugNewButton; public Text debugSqnText; private static GameAgents instance; private GameObject player; private CharacterInterface characterInterface; private const string SAVE_SQN_KEY = "sqn"; // Use this for initialization void Awake () { Debug.Log("GameAgents.Awake()"); if(instance) { Debug.Log("GameAgents.Awake(): Warning, GameAgents unity singleton already initialized"); } GameAgents[]objs = FindObjectsOfType(); if(objs.Length != 1) { Debug.LogError ("GameAgents.Awake(): Multiple GameAgents in scene."); } instance = objs[0]; player = GameObject.FindWithTag("Player"); if(!player) { player = Instantiate(playerPrefab); } characterInterface = player.GetComponent(); if(characterInterface == null) { Debug.LogError("GameAgents.Awake(): Player object does not contain CharacterInterface component"); } PlayerLogic pc = player.GetComponent(); pc.energyBar = energyBar; pc.healthBar = healthBar; debugNewButton.gameObject.SetActive(debug); } public void NewGameDebug() { PlayerLogic.DeleteSave(); GameAgents.NewSGameSqn(); PlayerLogic pc = player.GetComponent(); pc.ReadSave(); debugSqnText.text = GameAgents.GameSqn().ToString(); } public static GameObject GetPlayer() { return instance.player; } public static CharacterInterface GetCharacterInterface() { return instance.characterInterface; } public static void NewSGameSqn() { int sqn = PlayerPrefs.GetInt(SAVE_SQN_KEY, 0); sqn++; PlayerPrefs.SetInt(SAVE_SQN_KEY, sqn); PlayerPrefs.Save (); } public static int GameSqn() { return PlayerPrefs.GetInt(SAVE_SQN_KEY, 0); } }
Door does not work yet. We need to add Collider in trigger mode to it, add script that catches the trigger event and knows what level should be loaded. Door should also have an AudioSource component that has a sound file attached that can be played when the door is used.
[EDIT1: refactored door to have better support randomising level structure, but keeping the structure stable within one game.]
Door.cs
using UnityEngine; using System.Collections; [System.Serializable] public class DoorLink { public int index; public string level; // the level where this door leads public string door; // the name of door in the level } public class Door : MonoBehaviour { public DoorLink[] doorLink; AudioSource audioSource; public int keyId = 0; //private int doorLinkIndex; private DoorLink activeLink; private const string DOOR_LINK_INDEX_KEY = "doi"; void Awake() { if (doorLink.Length <= 0) { Debug.LogError ("Door.Awake(): You need have at least 1 exit for " + name); } audioSource = GetComponent (); } void Start() { string sqn_key = System.String.Format("{0}_sqn", name); int doorLinkIndex = PlayerPrefs.GetInt(DOOR_LINK_INDEX_KEY, -1); if(GameAgents.GameSqn() > PlayerPrefs.GetInt(sqn_key) || doorLinkIndex == -1) { doorLinkIndex = Random.Range(0,doorLink.Length); PlayerPrefs.SetInt(DOOR_LINK_INDEX_KEY, doorLinkIndex); activeLink = doorLink[doorLinkIndex]; } else { activeLink = null; for(int n = 0; n< doorLink.Length; n++) { if(doorLink[n].index == doorLinkIndex) { activeLink = doorLink[n]; break; } } if(activeLink == null) { Debug.LogError("Door.Start(): cant find Door Link with index " + doorLinkIndex + " door does not lead back to the previous level"); } } Debug.Log("Door.Start(): " + name + " -> " + activeLink.level + " ( door = " + activeLink.door + ", index = " + activeLink.index + " )" ); } void OnTriggerEnter(Collider other) { if(other.gameObject.tag == "Player") { Debug.Log("Door.LoadLevel(): loading level " + activeLink.level + " ( door = " + activeLink.door + " )"); CharacterInterface ci = other.gameObject.GetComponent(); if(ci != null) { if(keyId == 0 || ci.HasKey(keyId)) { ci.Save(activeLink.level, activeLink.door); audioSource.Play(); Application.LoadLevel(activeLink.level); PlayerPrefs.Save (); } else { Debug.Log ("Door.OnTriggerEnter(): " + name + " is locked"); // Display that the door is locked. } } else { Debug.LogError("Door.OnTriggerEnter() GameObject has tag Player, but doens not have CharacterInterface"); } } } }
Now we need to have two levels, lets say level_start and level1. Both levels should also have an empty GameObject tagged as PlayerStartPoint so that the PC gets spawned somewhere before doors have been used first time (or the level does not have a correct door, like when hitting play in some level). Figure below shows a door setup for a door that have two randomly selected alternatives where the door can lead.

After this we need two doors at level1. Door1 and Door2. The DoorLink of the doors at level1 should be as follows:
Door1
- Index: 0
- Level: level_start
- Door: Door1
Doo2
- Index: 1
- Level: level_start
- Door: Door1
Note that when a door have multiple DoorLinks, the doors in different levels needs to have the same name. Otherwise the door might not lead back to the same place (even if the door is otherwise correctly configured).