Project 4 - Steps
Follow the steps below to complete Project 3
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
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 Code, InventoryDisplay Prefabs, and required 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 (Version1)
Starter Code: Get the Full Code Framework for LevelManager.cs. 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.
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
Delete MiniGameManager, and optionally, MiniGameManager.cs, functionality will be replaced by LevelManager.
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
Step 3: Verify Updated, Final Version of GameData.cs
Verify that your project has the updated code for GameData. This should have been included with InventorySystem files. Verify code snippets below are included in your version of GameData.
Configure play-testing in the MiniGame Scene:
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 eventInvokePlayerDataUpdate( )
has been added to each method that changes the Player's data.
Project 3: Step 4: Update PlayerController.cs and Player gameObject
Step 4A: Update PlayerController
Update PlayerController.cs Script 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
These tags are used in
OnTriggerEnter2D
in PlayerController.csIf 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
Audio: Optional: you can modify PlayerController to play Audio clips when colliding with Water, Hazards, or PickUps. See 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.
Position this gameObject at the location that you want the player positioned at the start of each level.
Create and Populate variable:
playerSpawnPoint
on: LevelManager.cs component in the inspector.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
Step 4D: Add Code to LevelManager for PlayerController Events
Add Following Code to create variables for PlayerController.cs and Player GameObject within LevelManager.cs
Add the following Code in LevelManager Start( ) - AddListeners to PlayerController Events
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:
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 5B: Modify LevelManager.cs: Declare Array: gameObjectLayers
Add Code in LevelManager to Declare Array of GameObjects
Add Code in LevelManager To Set Visibility of Level GameObjects: in
Start( ):
locate code near bottom of Start( )
Disable gameObjectLayers with index: 1 , 2 (correspond to Layers 2, 3)
Activate gameObject Layer 0 (Corresponds to Layer1)
Step 5C: Populate LevelManager in Inspector w/Layer GameObjects
Populate
gameObjectLayers[ ]
array in the LevelManager Inspector as shown in image below
Step 5D: Level Specific Child GameObjects - See Details Below
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 for details.
Step 5E: ResultsPanel, StartGameButton Issue in MiniGame
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
You will need to do additional configuration for LevelManager, see steps below
Step 5F: Update PlayerStats.cs
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
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 final code versions to verify:
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. See MiniGameMods with Video
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( )
Step 5I: Create new types of Spawned Objects
Create new types of Spawned Objects for each layer's spawner Link: MiniGameMods 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.
Step 5J: Update Spawner.cs - to stop all spawning
You Must Update the Spawner.cs code to destroy all types of spawned objects and stop all SpawnPrefab( ) methods that have not yet been Invoked.
You must add the following methods to your Spawner.cs
DestroySpawnedObjects( ) //You must modify this code
StopAllSpawning( ) //Called in LevelManager.cs
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. 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
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.
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.
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.
The diagrams below show the logic and methods for changing between gameLevels when the NextLevel( ), StartLevel_( ) and LoadLevel_( ) methods are executed. Link to Larger Diagram Versions, Link to LevelManager Logic Diagram
NextLevel( ) can be executed due to the following events:
Start or RestartGame
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 )
LoadLevel3( ) has logic: startButton.onClick.AddListener( StartLevel3 )
Step 8A: Modify, Add, Remove LevelManager.cs code to Activate Levels
Important, REMOVE the variable declaration Spawner spawner;
from the top of the script since we are not using that variable for setting the spawner
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 ].
Additional logic has been added that will help reset when play-testing, optional
You must add the following code snippets to the LevelManager and modify as necessary to correspond to loading Level2 of your game
Step 8B: Write custom code for 2 methods to activate Level 3:
LoadLevel3()
StartLevel3()
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:
Step 9A: Create 2 UI-Text fields: LevelText, TimerText:
These will display the level number and the timer in seconds.
Suggested Method to create Text gameObjects within a UI Panel
Duplicate the ScorePanel, rename to LevelPanel. -
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:
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.
Create a 2D sprite gameObject
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
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
Create Water-Terrain Hazard GameObjects Use multiple tiles to create prefab water-hazard, vary the prefab instance configuration for each level
Water:
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:
For multiple water tiles adjoined: Create an empty parent gameObject
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
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.
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,
Remember to add 2 Directives: Using UnityEngine.Events
at the top of the script
Add at the top of LevelManager, right below the Enums
Step 10C: ReloadMiniGame( )
Version 1 of ReloadMiniGame assumes that we're in PlayTesting Mode,
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.
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
Step 10D: MiniGameOver( )
Add this method to LevelManager.cs. The MiniGameOver Method Has 2 purposes:
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.
Step 10E: Update Code for MiniGState.cs - 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.
The MiniGState is a added as a listener for the onMiniGameOver event.
This provides a way to have Scene-Transition due to a game-event, instead of the normal Buttons used to change scenes.
Select code in the provided MiniGState.cs 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.
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
Step 10F: Updated Code for EndState.cs: Add Consequences
EndState.cs: There must be some logic to implement consequences for winning or losing the Minigame.
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.
Be creative when implementing logic for consequences, the provided code is the minimum viable option.
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.
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
Create a new C# script, paste code for CameraFollow.cs,
Attach script to MainCamera in MiniGame.
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
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.
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.
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()
Step 11C: Update Logic in ReloadMiniGame( ) for Camera Fader
Last updated