---
description: Follow the steps below to complete Project 3
---

# Project 3 - Steps

**Project 3 Learning Objectives: Understanding Event-Driven Systems: to Implement Game Level code-structures.**

## Project 3: Step 1: Install and Configure Inventory System

### **Step 1: Verify that you've installed the** [**Inventory System**](../inventory-system/inventory-scriptableobject)&#x20;

The InventorySystem uses Scriptable-objects to create custom objects that can represent items a player might collect during game-play. The Inventory-System separates game-data storage-functionality from UI-interaction-display functionality. [Inventory-System ](../inventory-system/inventory-scriptableobject)Code, [InventoryDisplay](../inventory-system/inventory-scriptableobject/inventory-display-slot) Prefabs, and required [**Customization Steps**](../inventory-system/inventory-scriptableobject/customization-steps) are provided in these sections of the gitbook.

## Project 3: Step 2: Create LevelManager.cs

Once you have a working Inventory-System, proceed to completing the enhanced MiniGame by adding logic for multi-level game.

### **Step 2A. Create C# Script**  [**LevelManager.cs**](../../mini-game_proj3/level-manager-in-class/code-framework#full-starter-code-updated-4-15-2020) **(Version1)**

**Starter Code:** Get the [**Full Code Framework**](../../mini-game_proj3/level-manager-in-class/code-framework#full-starter-code-updated-4-15-2020) **for** [**LevelManager.cs**](../../mini-game_proj3/level-manager-in-class/code-framework#full-starter-code-updated-4-15-2020)**.** Paste code into C# Script: **LevelManager.cs**

**Includes: Custom UnityEvents:** The LevelManager-System uses custom UnityEvents to manage decoupled object-communication. Scripts were modified to integrate additional logic for Publishing, Listening for custom UnityEvents across several classes: **GameData, PlayerController, LevelManager, MiniGState.**&#x20;

**TODO: After adding additional GameObjects throughout the steps below,  you will make additional modifications to LevelManager.cs code:** The MiniGame will modified in steps below to include enhanced concepts: **Layer gameObjects,** **timer, collect-use items, camera-follow, water-hazard, platforms, scene-fading, scene-reloading.** Consequences for win/lose mini-game must exist.

### Step 2B. Create LevelManager GameObject

* **Create empty gameObject: LevelManager**&#x20;
* **Delete MiniGameManager,** and optionally, MiniGameManager.cs, _functionality will be replaced by LevelManager._ &#x20;
  * **Add script: LevelManager.cs to LevelManager GameObject in Unity Scene**  Create script from Step 2A to create LevelManager.cs, add to LevelManager empty gameObject.  Look at the inspector fields for LevelManager.  Populate In Inspector As Necessary
  * **Verify that MiniGame can be played, with no errors,** such that it has **basic functionality, logic from Project 1** before adding any new GameObjects or LevelManager.cs code modifications

## Project 3 Step 3: GameData.cs Updates&#x20;

### Step 3: Verify Updated, Final Version of [GameData.cs](project-3-code/class-code-examples/gamedata-final)&#x20;

* **Verify that your project has the updated code for** [**GameData.**](project-3-code/class-code-examples/gamedata-final) This should have been included with InventorySystem files. Verify code snippets below are included in your version of GameData. &#x20;
* **Configure play-testing in the MiniGame Scene:**&#x20;
  * **In the MiniGameScene: Add GameData script to an empty-gameObject: GameManager,** make sure you have created a ScriptableObject - Inventory object, select that Inventory on the GameData script.
* **GameData.cs** code snippets below should be included in your version:
  * **`UnityEvent: onPlayerDataUpdate`** has been added to GameData.  The new method: InvokePlayerDataUpdate( ) invokes the event which executes all methods that have been added as Listeners to the event
  * **`InvokePlayerDataUpdate( )`**&#x68;as been added to each method that changes the Player's data.

```java
 //GameData.cs Final
 public UnityEvent onPlayerDataUpdate = new UnityEvent();

```

```java
public void InvokePlayerDataUpdate()
    {
        if (onPlayerDataUpdate != null)
        {
            onPlayerDataUpdate.Invoke();
        }
    }
```

```java
public void TakeDamage( int value){
        health -= value;
        if (health < 0) health = 0;  //makes sure health !< 0
        Debug.Log("Health is updated " + health);
        InvokePlayerDataUpdate();
    }
```

```java
public void Add(int value)
    {
        score += value;
        levelScore += value; ////MAJOR ISSUE SOLVED - update the levelScore

        Debug.Log("Score is updated " + score);
        InvokePlayerDataUpdate();

    }
```

## Project 3: Step 4: Update PlayerController.cs and Player gameObject

### Step 4A:  Update PlayerController

* &#x20;[**Update PlayerController.cs Script**](project-3-code/class-code-examples/playercontrollerv3-mods) This includes 2 custom UnityEvents:
* **`UnityEvent onPlayerDied`**   Invoked when trigger collider w/Tag: **"Water"**
* **`UnityEvent onReachExit`**    Invoked when trigger collider  w/Tag: **"Exit"**

### **Step 4B Update, Create GameObjects - Tags, SpawnPoint**

* **Player GameObject Updates:**
  * **Create New Tags: Exit, Water**&#x20;
    * These tags are used in **`OnTriggerEnter2D`** in **PlayerController.cs**
    * &#x20;If not using Exit or Water features, you can remove the associated code in OnTriggerEnter2D, then you won't need these new Tags, otherwise, you'll get an **error if the Tags don't exist**.
    * **Set Tag: 'Player'** for the Player gameObject if using [**CameraFollow**](project-3-code/class-code-examples/camerafollow)
    * **Audio:&#x20;**_**Optional:**_ you can modify PlayerController to play **Audio clips** when colliding with Water, Hazards, or PickUps.  See [**Adding Audio**](../optional-supplemental-content/optional-content/adding-audio) for steps to include logic in OnTriggerEnter2D( ).

### Step 4C:  PlayerSpawnPoint - Reposition Player at Start of Levels

* **Create an Empty GameObject: PlayerSpawnPoint,** select an icon so it is visible in the scene.  &#x20;
* Position this gameObject at the location that you want the player positioned at the start of each level.&#x20;
* **Create and Populate variable:`playerSpawnPoint`** on: **LevelManager.cs** component in the inspector.&#x20;
  * **Verify the PlayerSpawnPoint has Transform.Position.Z = 0 (if Z= -10 you won't see the player)**
  * **Add following code to declare variable: playerSpawnPoint at top of  LevelManager.cs**

```java
//initialize in inspector 
//Use to reposition player at start of each Level
[SerializeField]
Transform playerSpawnPoint; 
```

### **Step 4D:  Add Code to LevelManager for PlayerController Events**

**Add Following Code to create variables for PlayerController.cs and Player GameObject within LevelManager.cs**

```java
//Declare Variables in LevelManager.cs
PlayerController playerController ;//will find by type
GameObject player;
```

**Add the following Code in LevelManager Start(  ) - AddListeners to PlayerController Events**

```java
  playerController = FindObjectOfType<PlayerController>();
        player = playerController.gameObject;
        //AddListeners for PlayerController UnityEvents:
        playerController.onPlayerDied.AddListener(ReloadMiniGame);
        playerController.onPlayerReachExit.AddListener(NextLevel);

```

* _Simplified MiniGame Version Scripts: PlayerController\_v2: Possible Issue: This is only relevant If using PlayerController\_v2, with no player Animator, you must update code in LevelManager so that it uses data-type PlayerController\_v2, or rename your file to: PlayerController_

## Project 3: Step 5: Create GameObject Layers:&#x20;

### **Step 5A: Create 3 empty GameObjects to Parent each Level's GameObjects**

* **Create 3 Level-Specific: Parent GameObjects**, one for each Level, with children gameObjects: **background, spawner,** etc.  **Ordering in the Hierarchy and Inspector must match images below.**

### Step 5**B**:  Modify LevelManager.cs:  Declare Array: gameObjectLayers

* **Add Code in LevelManager t**o Declare Array of GameObjects

```csharp
[SerializeField]
GameObject[] gameObjectLayers; //populate in Inspector - Parent GameObjects

```

* **Add Code** in LevelManager  To Set Visibility of Level GameObjects: i&#x6E;**`Start( ):`**
  * locate code near bottom of Start( )
  * **Disable gameObjectLayers with index:  1 , 2**   _(correspond to Layers 2, 3)_&#x20;
  * **Activate  gameObject Layer 0**  (Corresponds to Layer1)&#x20;

```java
//In Start(  )
//the code statement exists in LevelManager, Start()
 
 gameObjectLayers[0].SetActive(true); //Level1 gameObjects, spawner, background, floor

 //add this code to disable LayerObjects_levels 2, 3
gameObjectLayers[1].SetActive(false); //disable Level2 gameObjects
gameObjectLayers[2].SetActive(false); //disable Level3 gameObjects
```

### Step 5C:  Populate LevelManager in Inspector w/Layer GameObjects

* **Populate `gameObjectLayers[ ]`** array in the LevelManager Inspector as shown in image below

![](https://1118975008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M0-KLgOacVpbicrqehO%2F-M04eWSVrF1dPNoG3KOs%2F-M04eamiq9UiR1evVbpl%2FScreen%20Shot%202019-04-24%20at%201.11.38%20PM.png?generation=1581716783446629\&alt=media) ![](https://1118975008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M0-KLgOacVpbicrqehO%2F-M5IleWXixlSx12YvGqF%2F-M5ImMZiB7OVIJK-SmAj%2FLevelGameObjects.png?alt=media\&token=8325c465-1138-4572-bf03-6c81575a87b9)

### Step 5D:  Level Specific Child GameObjects - [See Details Below](#step-9b-create-level-gameobjects)

**Create Child GameObjects**  that correspond to different Level Backgrounds, Spawners with different spawned objects per layer.  Example GameObject Names: Level1\_Objects, Level2\_Objects, Level3\_Objects.   See Images Above, [See Step 9 Below](#project-3-step-9-create-new-gameobjects) for details.

### **Step 5E: Results**Panel, StartGameButton Issue in MiniGame

&#x20;**Rename ResultsPanel to StartPanel** since it will not show results, it can give instructions for each Level.

**Add LevelManager.cs LoadLevel3( ), StartLevel3( ) methods logic:** Utility.HideCG( cg ); Utility.ShowCG( cg ); when loading each level,  to insure the StartButton for each level is visible. [See Step 6 for details](#project-3-step-6-levelmanager-cs-add-code-to-change-levels)

You will need to do **additional configuration for LevelManager**, see steps below

### Step 5F: Update PlayerStats.cs

[**PlayerStats.cs - Final**](project-3-code/class-code-examples/playerstats-final) &#x20;

* PlayerStats.cs script must be updated so it uses GameData OnPlayerDataUpdated UnityEvent.  You must add UpdateDisplay as a Listener for the Event. You must eliminate Polling:  eliminate having UpdateDisplay( ) executed in Update( );

### Step 5G:  Spawn Objects: PickUp.cs, Hazard.cs, ItemInstance&#x20;

### Step 5H:Verify updated PickUp.cs, Hazard.cs , custom Item child-class

* **Verify** that you have the updated versions that were installed as part of the InventorySystem.  See [PickUp.cs, Hazard.cs](project-3-code/class-code-examples/pickup-final)  final code versions to verify:&#x20;
* **Verify** you have created a **custom child class of the Item class** so that you have custom itemInstances that can be added to create Collectible prefabs that will add items to the InventorySystem: [Link: ConcreteClass](../inventory-system/inventory-scriptableobject/customization-steps/concrete-class-food).  [**See MiniGameMods with Video**](https://kdoore.gitbook.io/cs2335/inventory-system/inventory-scriptableobject/customization-steps/minigame-mods)
* **Modify each Prefab with Collectible Tag: that Has the updated PickUp.cs script, to add a scriptableObject as the ItemInstance** - **See Image Below**
* _Optional:  You can create a ScorePickUp class if desired, it does not include the PickUp Inventory ItemInstance, it can be used for collisions that just increase score.  You'll need to add code to PlayerController.cs in OnTriggerEnter( )_

![](https://1118975008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M0-KLgOacVpbicrqehO%2F-M5wKjTDdjNBGeEPYa_-%2F-M5wM8_lxEnnrg7Pv6Km%2FScreen%20Shot%202020-04-27%20at%2010.58.03%20AM.png?alt=media\&token=44c68466-1625-467d-b0f6-1ba39f50a179)

### Step 5I: Create new types of Spawned Objects

* Create new types of Spawned Objects for each layer's spawner [Link: MiniGameMods](../inventory-system/inventory-scriptableobject/customization-steps/minigame-mods) shows how to modify project1 collectible objects by adding an ItemInstance scriptableObject.  Make additional new collectible objects so that each level will have new types of spawned collectible items. &#x20;

### Step 5J:  Update Spawner.cs - to stop all spawning

* You Must Update the [Spawner.cs](project-3-code/class-code-examples/spawner-final) code to destroy all types of spawned objects and stop all SpawnPrefab( ) methods that have not yet been Invoked.&#x20;
* You must add the following methods to your Spawner.cs
  * DestroySpawnedObjects( ) //You must modify this code
  * StopAllSpawning( )  //Called in LevelManager.cs

```java
 //Used in MiniGameManager - can be removed if desired
  public void DestroyAllPickups()
    {
        PickUp[] items = FindObjectsOfType<PickUp>();
        foreach( PickUp item in items       )
        {
            Destroy(item.gameObject);
        } 
        
    }
    
    ///ADD THE FOLLOWING METHODS TO Spawner.cs
    //Called in LevelManager.cs
    public void StopAllSpawning()
    {
        CancelInvoke("SpawnPrefab"); //stops any SpawnPrefab( ) that have not been Invoked yet
        activeSpawning = false;
        DestroySpawnedObjects();
    }
    
    //YOU MUST MODIFY TO INCLUDE ALL TYPES OF SPAWNED OBJECT - Components
    void DestroySpawnedObjects()
    {
        PickUp[] items = FindObjectsOfType<PickUp>();
        foreach( PickUp item in items  )
        {
            Destroy(item.gameObject);
        } 
        //YOU MUST ADD Logic for Hazard or any other Types:
    }

```

## Project 3: Step **6** Create Timer

To add a simple timer,  we can create a simple Coroutine Method that will allow us to vary the length that the timer runs.  A Coroutine is a special type of Unity method that only executes a single iteration of some code to be repeatedly executed.  There are several variations how execution is paused between each iterated execution.  The code below shows that the reloadTimer( ... ) method takes an input parameter of seconds that the timer should run.  Within the `while-loop` repeat-structure, we'll add logic to update the displayed timerText. Unity's Time.deltaTime provides a way to get constant time increment in a game where frameRate may vary.  [See Unity Time.deltaTime Scripting API](https://docs.unity3d.com/ScriptReference/Time-deltaTime.html).  When the while loop's condition is no longer true, the timer has ended, so ReloadMiniGame( ) is called within the timer method.  Add this code near the bottom of LevelManager.cs

**Step 6A:** Within each **StartLevel( )** method, add logic for **starting the timer.**\
**Step 6B:** Within each **LoadLevel( )** method, add logic for **stopping the timer**

```java
 //example use within StartLevel( ) methods
 //START TIMER
 StartCoroutine(reloadTimer(30));
 
 //STOP TIMER within LoadLevel( ) methods
 StopAllCoroutines(); //stop timer
```

```java
/// <summary>
    /// Reloads the Scene when the timer runs out
    /// </summary>
    /// <returns>The timer.</returns>
    /// <param name="reloadTimeInSeconds">Reload time in seconds.</param>
    IEnumerator reloadTimer(float reloadTimeInSeconds)
    {
        float counter = 0;

        while (counter < reloadTimeInSeconds)
        {
            counter += Time.deltaTime;
            timerText.text = "Timer: " + Mathf.RoundToInt(counter).ToString() + " of " + Mathf.RoundToInt(reloadTimeInSeconds).ToString(); ;
            yield return null;
        }
        //Timer is over - if the timer runs out
        ReloadMiniGame();
    }
```

## Project 3: Step 7: Helper Methods: Spawner, Player

In this step, we'll add methods to simplify logic for Starting and Stoping the Spawner, and for moving the player gameObject back to it's PlayerSpawnPosition:  Helper methods organize sections of repeated logic to simplify the logic in the StartLevel, LoadLevel methods.  Additional helper methods could be created to organize logic for the startButton, etc.&#x20;

The StartSpawner( ... ) method has an input parameter that is the enum for the  current level so it can access the correct spawner for a given level's gameObjectLayer.  Since the gameObjectLayers are an array, the corresponding array index is 1 less than the curLevel enum value as the enums have been defined at the top of the script.  If there are more than 1 spawners for each level, the code can be modified so that it gets an array of spawners for each level and uses a foreach loop to start each spawner. Similar logic is used in the StopSpawner( ... ) method.  Example:  `Spawner[ ] spawners = GetComponentsInChildren< Spawner >( );`

The ResetPlayerPosition( ) method just sets the player gameObject transform.localPosition to that of the playerSpawnPoint, and sets the Camera back to it's original position if using the CameraFollow script.

```java
/// <summary>
    /// Starts the spawner.
    /// </summary>
    /// <param name="level">Level.</param>
    void StartSpawner( LevelState level)
    {
        int index = (int)level - 1; //array index is one less than level number
        Spawner spawner = gameObjectLayers[index].GetComponentInChildren<Spawner>();
        spawner.gameObject.SetActive(true);
        spawner.activeSpawning = true;
        spawner.StartSpawning(); //Make sure to remove this code from Start in the spawner script
    }

    /// <summary>
    /// Stops the spawner.
    /// </summary>
    /// <param name="level">Level.</param>
    void StopSpawner(LevelState level)
    {
        int index = (int)level - 1; //array index is one less than level number
        Spawner spawner = gameObjectLayers[index].GetComponentInChildren<Spawner>();
        spawner.gameObject.SetActive(false);
        spawner.StopAllSpawning(); 
    }


    //moves player to left-edge when new level loaded
    public void ResetPlayerPosition()
    {   
        player.transform.localPosition = playerSpawnPoint.transform.localPosition;
        cameraFollow.ResetPosition();
    }

```

## Project 3: Step 8: LevelManager.cs Add Code To Change Levels

In this step you will add required code modifications to LevelManager.cs to manage transitioning between gameLevels. &#x20;

The diagrams below show the logic and methods for changing between gameLevels when the **NextLevel( ), StartLevel\_( ) and LoadLevel\_( )** methods are executed.\
&#x20;[Link to Larger Diagram Versions](../mini-game_proj3/level-manager-in-class/loadlevel-startlevel-logic), [Link to LevelManager Logic Diagram](../mini-game_proj3/level-manager-in-class/level-manager-logic-diagram)

* **NextLevel( )** can be executed due to the following events:
  * **Start or RestartGame**&#x20;
    * startButton.onClick.AddListener( **NextLevel** )
  * **Player reaches exit:**
    * playerController.**onPlayerReachExit**.AddListener( **NextLevel** )
  * **Score exceeds maxLevelScore** - CheckLevelEnd( ) executes **NextLevel( )**
    * GameData.instanceRef.**onPlayerDataUpdate**( **CheckLevelEnd )**
* **StartLevel\_X( )** can be executed based on the following events:
  * LoadLevel2( )  has logic: startButton.onClick.AddListener( **StartLevel2** )&#x20;
  * **LoadLevel3( )**  has logic: startButton.onClick.AddListener( **StartLevel3** )

![ LoadLevel( ), StartLevel( ) methods have logic to transition  game levels](https://1118975008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M0-KLgOacVpbicrqehO%2F-M5SqY_UlNF5JH-1jI6N%2F-M5SqnWhXQnEjevrvz2f%2FScreen%20Shot%202020-04-21%20at%2012.48.24%20PM.png?alt=media\&token=4d05e924-2717-49bc-afac-6f9ac63a5dd0)

![](https://1118975008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M0-KLgOacVpbicrqehO%2F-M5SrLg9F77E5Vt0OWQ1%2F-M5SsR9inFZSaTSBOIr-%2FScreen%20Shot%202020-04-21%20at%2012.55.27%20PM.png?alt=media\&token=9f3b30ff-36c0-408b-b938-0a726164115b)

### **Step 8A: Modify, Add, Remove LevelManager.cs code to Activate Levels**

**Important, REMOVE the variable declaration `Spawner spawner;` f**rom the **top of the script** since we are not using that variable for setting the spawner &#x20;

```java
//REMOVE from top of script
[SerializeField]
Spawner spawner;   //This will conflict with variable in StartSpawner( ) helper method
```

Modify the code in **StartLevel1( )**  _Name changed from LoadLevel1( )_\
To include logic to **start the timer**, and to insure you are using the Helper Method for **`StartSpawner( LevelState.level1 )`**    so that you are now finding the Spawner that is a child of for each GameObjectLayer\[  index ]. &#x20;

Additional logic has been added that will help reset when play-testing, optional

```java
//ADDED CODE SO THIS WORKS FOR RELOAD when playtesting 
//if not using logic to actually reload the scene
  void StartLevel1()
    {
        //STARTS Gameplay, Spawner, etc
        //Player in correct position already
        gameObjectLayers[2].SetActive(false); //level3 - if playtest reload, hide
        gameObjectLayers[0].SetActive(true); //level1 - if playtest reload, show
        ResetPlayerPosition(); //move player to spawn point
        player.SetActive(true);
        
        StartSpawner(LevelState.level1);// -- needs changed for l3
        levelText.text = "Level 1"; //- needs changed for l3
        Utility.HideCG(startPanelCg); //hide panel
        StartCoroutine(reloadTimer(20));
   } //end StartLevel1
```

You must add the following code snippets to the LevelManager and modify as necessary to correspond to loading Level2 of your game

```java
  //stop level1, prepare for starting level2
    void LoadLevel2()
    {
        //Stop - Hide
        StopSpawner(LevelState.level1); //- needs changed for l3
        StopAllCoroutines(); //stop time
        player.SetActive(false);
        startGameButton.onClick.RemoveAllListeners();
        //fader.FadeReset(); //fadeOut - fadeIn

        //Start - Show
        startGameButton.onClick.AddListener(StartLevel2); //- needs changed for l3
        startPanelText.text = "Start Level 2"; //-- needs changed for l3
        Utility.ShowCG(startPanelCg);  //show panel
    }

    //When "Start Level 2" button is selected
    public void StartLevel2()
    {
        //Hide 
        gameObjectLayers[0].SetActive(false); //level1 - needs changed for l3
        Utility.HideCG(startPanelCg); //hide panel
         
         //Start- Show
        gameObjectLayers[1].SetActive(true); //level2 - needs changed for l3
        ResetPlayerPosition(); //move player to spawn point
        player.SetActive(true);
        levelText.text = "Level 2"; //- needs changed for l3
        StartSpawner(LevelState.level2);// -- needs changed for l3
        StartCoroutine(reloadTimer(20));  //Start Timer
    }

```

### **Step 8B: Write custom code for 2 methods to activate Level 3:**

* **LoadLevel3()**
*   **StartLevel3()**

    &#x20;**You can use modified logic (similar to: LoadLevel2 ) by modifying code in the indicated statements above.** Each line with comment: //-UPDATE, needs to be modified for Level3 functionality.\*\* Note that the array indexes are different than the level number:

    * index \[0] corresponds to Level 1
    * index \[1] corresponds to Level 2
    * index \[2] corresponds to Level 3

## Project 3: Step 9 Create New GameObjects:&#x20;

### **Step 9A: Create 2 UI-Text fields: LevelText, TimerText:**&#x20;

These will display the level number and the timer in seconds.&#x20;

* Suggested Method to create Text gameObjects within a UI Panel
  * **Duplicate the ScorePanel, rename to LevelPanel**. -&#x20;
  * **Remove StatsDisplay.cs** component
  * **Rename Text** elements to **LevelText, TimerText**
  * **Populate the LevelManager** public fields in the Inspector panel using these gameObjects.
  * **Optional:  Additionally, you can also create a LevelScore UI-Text**, showing the LevelScore and MaxLevel score for each level.  ( The PlayerStats score displays the total accumulated score)

### **Step 9B:  Create: Level GameObjects:**&#x20;

The steps below detail creation of additional GameObjects that will be added to each gameObjectLayer.   These should all be children gameObjects of a specific layer.  Suggestion, Configure all new objects on 1 layer, then duplicate the layer and make modifications to the child gameObjects

* **Spawned ItemInstance Objects** Create at least 3 different types of 'Good' prefabs to be spawned during game-levels. You must have 3 different types of ItemInstance objects, including your custom child-class of Item class. Attach to spawned prefabs using the ItemInstance in the PickUp script. There must be 3 different types of items that are collected in the inventory.
* **Exit GameObject:** Provides a gameObject with trigger-Collider so the player can advance to next level when triggering an exit event.&#x20;
  * Create a **2D sprite gameObjec**t
  * Set **sorting-layer** to make sure it's visible (add new sorting layers ? )
  * **Attach a Collider2D**
  * Select **isTrigger is true**
  * Add Tag: **'Exit'**
  * **Create Prefab** from gameObject&#x20;
  * When the player triggers the collider, PlayerController invokes the **onPlayerReachExit** custom UnityEvent, with the LevelManager as a listener for the event.

### **Step 9C:  Create: Water-Terrain Feature, Floors**

![Empty Parent With Trigger Collider, Tag, 2 Child Sprite GameObjects](https://1118975008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M0-KLgOacVpbicrqehO%2F-M5TUmw1MpwG4ZWZtlyc%2F-M5TVp2jv_Z357Jq6rhu%2FScreen%20Shot%202020-04-21%20at%203.51.35%20PM.png?alt=media\&token=6b2e8201-e1ff-493e-b67d-89f6a2c588d7)

* **Create Water-Terrain Hazard GameObjects** Use multiple tiles to create prefab water-hazard, vary the prefab instance configuration for each level&#x20;
* **Water:**&#x20;
  * For multiple water tiles adjoined: **Create an empty parent gameObject**  (see image below )
  * **Add children:  2D Sprites** - water tiles
    * **Set sorting-layer** to make visible
    * **Set sorting-layer order** to determine layering between tiles:
      * default: 0 is farthest from camera, higher layers move forward toward camera
  * Attach a **collider2D** to the parent gameObject
  * set as **isTrigger** is true.
  * Add tag: **'Water'** to parent gameObject
  * **Create Prefab** of parent
  * If the player collides with the water, the **onPlayerDied event** is invoked in PlayerController with LevelManager as an event listener.
* **Terrain Tiles:**&#x20;
  * For multiple water tiles adjoined: **Create an empty parent gameObject**&#x20;
  * **Add children:  2D Sprites** - terrain tiles
    * **Set sorting-layer** to make visible
    * **Set sorting-layer order** to determine layering between tiles:
      * default: 0 is farthest from camera, higher layers move forward toward camera
  * Attach a **collider2D** to the parent gameObject (not isTrigger)
  * **Set layer** (physics) **to 'Ground'** to make a **jumping surface** for player
  * **Create Prefab** of parent
* **Terrain-Water Feature:**
  * **Create Empty Parent** GameObject
    * Add Water Hazard as child
    * Add Terrain Tiles as child
  * **Create a Prefab** of parent
* **Create new Floor** GameObjects that do not overlay the water-feature ( duplicate ) - Set **layer** (physics) to **'Ground'**
* **Extend Floor into 2nd Background if using CameraFollow**

![Arrangement of Colliders for Floor, Terrain Tiles, Water Feature](https://1118975008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M0-KLgOacVpbicrqehO%2F-M5TPmMaK7kues6zJJpP%2F-M5TUKp2pfXUQdLIUdeZ%2FScreen%20Shot%202020-04-21%20at%203.45.17%20PM.png?alt=media\&token=0051f8f6-fa30-4ba6-8da9-613c6e124fae)

![Extended Background, Right and Left Floor GameObjects:  'Ground' Layer](https://1118975008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M0-KLgOacVpbicrqehO%2F-M5TPmMaK7kues6zJJpP%2F-M5TROcqEbrZdkQF7RgW%2FScreen%20Shot%202020-04-21%20at%203.29.17%20PM.png?alt=media\&token=7c00f52f-ef50-497c-849f-3febe7c3d92f)

## Project 3: Step 10: MiniGameOver: Win - Lose Consequences:

### Step 10A:  CheckLevelEnd(  ) Add Lose Condition: Health <=0

It is necessary to add logic to the CheckLevelEnd( ) method to check if the player's Health <= 0, indicating that the player has lost the MiniGame.  In that case, we'll change the curLevel to the lose state, and we also want to create a record within GameData so that we can access that information from other scenes such as the end scene.  Finally, we'll execute the MiniGameOver( ) method that will have some logic for end of game consequences.

```java
/// <summary>
    /// Checks the level end.
    /// Checks the data to see if the level has ended
    /// </summary>
       public void CheckLevelEnd()
    {
        int levelScore = GameData.instanceRef.LevelScore;
        //Debug.Log("Check if level is over " + levelScore);
        //ADD CODE here to see if health is <=0
        if (GameData.instanceRef.Health <= 0)
        {
            curLevel = LevelState.lose;
            MiniGameOver();
        }
        else if (levelScore >= maxLevelScore)
        { ///level has changed
          ///reset levelScore
            GameData.instanceRef.LevelScore = 0; //reset GameData.LevelScore
            NextLevel(); //go to next level - call FSM
        }

    }//end CheckLevelEnd

```

### Step 10B Create onMiniGameOver UnityEvent in LevelManager

Add this code to the top of LevelManager to create the event: **`onMIniGameOver,`** that is executed to notify MiniGame\_State that the MiniGame is over,&#x20;

Remember to add  2 Directives: **`Using UnityEngine.Events`** at the top of the script&#x20;

```java
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Events;  //MUST ADD for onMiniGameEnd event
using UnityEngine.SceneManagement; //MUST ADD IF USING ScreenFader
```

Add at the top of LevelManager, right below the Enums

```java
 using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Events; //MUST ADD for onMiniGameEnd event
using UnityEngine.SceneManagement;//MUST ADD IF USING ScreenFader

public class LevelManager : MonoBehaviour
{
   //..ENUM LOGIC goes here
    
    //ADD THIS Near the top of the script
    UnityEvent onMiniGameOver = new UnityEvent();
```

### Step 10C: ReloadMiniGame( )

Version 1 of ReloadMiniGame assumes that we're in PlayTesting Mode,&#x20;

```java
//ORIGINAL version
public void ReloadMiniGame()
    {
        Utility.ShowCG(startPanelCg); //show start button
        GameData.instanceRef.ResetMiniGameData();
    }
```

Version 2 requires that StateManager is active, which means the user has started in BeginScene, so that each time the Scene / State are actually reloaded, this also triggers the cameraFader see additional logic below for that option.&#x20;

**Update 4/27/2020:  When play Testing - not starting from BeginScene, the ReloadMiniGame does not correctly reset all things that get reset when the scene is actually reloaded**

```java
 /// <summary>
    ///NEW VERSION
    ///This version also reloads the UnityScene and MiniGameState for using the camera fader
    ///
    /// </summary>
    public void ReloadMiniGame()
    {
        //ADD NEW METHOD TO RESET ALL GameObjects for Restart
        LoadLevel1();

        GameData.instanceRef.ResetMiniGameData();

        Scene curScene = SceneManager.GetActiveScene();
        fader.EndScene(curScene.buildIndex);

        if (StateManager.instanceRef != null)
        {
            StateManager.instanceRef.SwitchState(GameScene.MiniGameScene);  //Going to 
        }
        else
        {
            Debug.Log("Play Testing MiniGame - no StateManager so can't switch scene");
            //Use Unity to Reload the scene
            SceneManager.LoadScene(curScene.buildIndex);
            //or Create method to LoadLevel1, reset all things or use neew method below
            //LoadLevel1();
        }
    } //end ReloadMiniGame
    
    //Optional fix for playTesting - or use method implemented above:  
    //SceneManager.LoadScene(curScene.buildIndex);
    //Put logic here for restarting miniGame - 
    //during PlayTesting or use SceneManager as above to reload the scene
    void LoadLevel1()
    {
        player.SetActive(false);
        StopSpawner(LevelState.level3); //- needs changed for l3

        StopAllCoroutines(); //stop timer
                             // fader.FadeReset(); //fadeOut - fadeIn
        startGameButton.onClick.RemoveAllListeners();

        startGameButton.onClick.AddListener(StartLevel1); //- needs changed for l3
        startPanelText.text = "Start Level 1"; //-- needs changed for l3

        Utility.ShowCG(startPanelCg);  //show panel }
    }

```

### Step 10D:  MiniGameOver(  )

Add this method to LevelManager.cs.  The MiniGameOver Method Has 2 purposes:&#x20;

**1:** **Invoke onMiniGameOver event**, to notify MiniGameState (listener) to execute Scene / State Transition Logic to leave the scene\
**2.** **Store results of the miniGame win/lose state** in GameData so that it can be accessed from a different scene.

```java
//What happens when the MiniGame is over due to winning?
    //How does a player leave the MiniGameScene?
   //What happens when the MiniGame is over due to winning?
    //How does a player leave the MiniGameScene: Win or Lose?
    void MiniGameOver()
    {
        if( curLevel == LevelState.win)
        {
            GameData.instanceRef.miniGameWinner = true;
        }
        else //must be LevelState.Lose
        {
            GameData.instanceRef.miniGameWinner = false;
        }
        if (onMiniGameEnd != null) //some listener (MiniGState)
        {
            onMiniGameOver.Invoke();
        }
        //invoke custom event to notify MiniGState where sceneChange logic an be executed.
    } //end MiniGameOver

```

### Step 10E: Update Code for [MiniGState.cs](project-3-code/class-code-examples/minigame) - Add EventListener

You must add code to your MiniGame State file so that scene transition will occur when the player wins or loses the MiniGame.

* **MiniGState.cs:** When a player wins or loses the MiniGame, an event is invoked in LevelManager:  **onMiniGameOver.**&#x20;
* The MiniGState is a added as a **listener** for the **onMiniGameOver event.** &#x20;
* This provides a way to have S**cene-Transition due to a game-event,** instead of the normal Buttons used to change scenes. &#x20;
* Select code in the provided [MiniGState.cs](project-3-code/class-code-examples/minigame) file for your desired logic.  Be Careful - if you use the provided code, to overwrite your existing MiniGState.cs file, you may remove existing scene-change button logic in your MiniGame Scene.&#x20;
* It is **STRONGLY RECOMMENDED** that you **add code snippets**, rather than **overwrite the entire file.**

The Code Snippet below shows that **LoadEndScene is added as a Listener to onMiniGameOver event**, which is **invoked by LevelManager**

```java
 public void InitializeObjectRefs()
    {
        btnOption1 = GameObject.Find("ButtonOption1").GetComponent<Button>();
        btnOption1.onClick.AddListener(LoadEndScene);

        levelManager = Object.FindObjectOfType<LevelManager>();
        levelManager.onMiniGameOver.AddListener(LoadEndScene);
    }
```

### Step 10F: Updated Code for [EndState.cs: ](project-3-code/class-code-examples/endscenestate)Add Consequences

* **EndState.cs:**  There must be some logic to implement consequences for winning or losing the Minigame. &#x20;
* In the provided EndState.cs, code controls the display of a UI-Text element that gives the player feedback that they won or lost the MiniGame. &#x20;
* Be creative when implementing logic for consequences, the provided code is the minimum viable option. &#x20;
* Be Careful - if you use the provided code, to overwrite your existing EndState.cs file, you may remove existing scene-change button logic in your EndScene.&#x20;
* It is **STRONGLY RECOMMENDED** that you **add code snippets**, rather than **overwrite the entire file.**

## Project 3: Step 11:  Optional Features:  CameraFollow, ScreenFader

### Step 11A: Create CameraFollow.cs (optional)

* **CameraFollow (optional) : Player must have Tag: Player** &#x20;
* **Create a new C# script**, paste code for [**CameraFollow.cs**](https://github.com/kdoore/CS2335_V2/tree/e629ce99d3c4e27e0dc9a8b416a206a1d7051d22/cameraFollow/README.md)**,**&#x20;
* **Attach script to MainCamera** in MiniGame. &#x20;
* **Set Player's RigidBody Interpolate to Interpolate -** _See image below_
* Adjust Values, try reducing MaxX, XSmooth values
* This assumes you have a **background image larger than the camera's viewport**, play around with variables that restrict amount of camera movement, so it works with your backgrounds. Set and adjust MaxX and Y, MinX and Y, to constrain the camera's movement in X,Y directions, see image above
* Extend Floor to cover new background

![](https://1118975008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M0-KLgOacVpbicrqehO%2F-M04eWSVrF1dPNoG3KOs%2F-M04eamg1tQaULzfF1Gf%2FScreen%20Shot%202019-04-24%20at%202.16.21%20PM.png?generation=1581716782372260\&alt=media)

![Modify Interpolate on Player's Rigidbody2D Component](https://1118975008-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M0-KLgOacVpbicrqehO%2F-M5TmWGmPpkjcjKRfBQr%2F-M5TnDuABTqQKiYu6RH1%2FScreen%20Shot%202020-04-21%20at%205.11.21%20PM.png?alt=media\&token=edebe3bc-4ca6-4ee6-8b32-550ade6b61ce)

### Step 11B: Create ScreenFader ( optional )

* **ScreenFader (optional)** The LevelManager includes code for a ScreenFader functionality, either remove that code, or **create a new C# script**, paste code for [ScreenFader.cs](project-3-code/class-code-examples/screenfader).&#x20;
* **Put the ScreenFader script on the MainCamera** gameObject in any scene you want Fade-in during start. Code must be modified in State scripts if you want Fade-out at the end of a scene.&#x20;
* For any scene that uses ScreenFader, you **must create a UI-Image  gameObject**,  **(Child of Canvas),** move it out of the camera's view, **set the color to black.**
* **Important:** **If you don't use ScreenFader**, you must **remove code in LevelManager: ReloadMiniGame()**&#x20;

### &#x20;Step 11C: Update Logic in ReloadMiniGame( ) for Camera Fader

```java
 /// <summary>
    ///This version also reloads the UnityScene and MiniGameState for using the camera fader
    ///
    /// </summary>
    public void ReloadMiniGame()
    {
        GameData.instanceRef.ResetMiniGameData();
        Utility.ShowCG(startPanelCg); //show start button
        
        //Reloads the Scene and State if StateManager active
        Scene curScene = SceneManager.GetActiveScene();
        fader.EndScene(curScene.buildIndex); //fade out 
        if (StateManager.instanceRef != null)
        {
            StateManager.instanceRef.SwitchState(GameScene.MiniGameScene);  //Going to 
        }else{
        Debug.Log("Play Testing MiniGame - no StateManager so can't switch scene");
        LoadLevel1( );  //ADD THIS
        }
    }
```

```java
 //LevelManager.cs
 // in ReloadMiniGame() ~ approx at line 270 in file
 //fader.EndScene(curScene.buildIndex); //remove
  SceneManager.LoadScene(curScene.buildIndex); //add this if not using fader
```
