🎮 Park Clean-up Game
In this project, we will continue developing Park Clean-Up, the casual puzzle/exploration game we previously introduced. Players take on the challenge of restoring a park by collecting and disposing of trash in designated bins.
In our previous chapter on visualizing game loops in flowcharts, we outlined the core gameplay loops for this project. These loops define how players interact with objects, clean up the environment, and unlock new areas by meeting objectives. Mapping out these interactions through flowcharts is a crucial step in the development process, providing a clear framework for implementing the game's mechanics, whether through visual scripting or traditional coding.
Trash Collection​
The main game loop for the first level (area) consists of the player collecting a set number of trash bags and disposing of them in designated bins.
🛠Trash Bag Interactions​
Tutorial: Trash Bag Interactions
đź“ťVisual scripting | đź•’15 minutes | đź“‚Required File: Park Clean-Up Game
In this tutorial we will set up the visual scripts for the trash bag interactions.
To begin, we’ll focus on the interaction between the player and the trash bags. The following actions should occur when a player picks up a trash bag:
- Increase the count of collected trash
- Play a sound effect
- Destroy the trash bag prefab
Since all trash bags will function the same way, we need to update the trash bag prefab to ensure consistent behavior across all instances.
Step 1: Prepare the Trash Bag Prefab​
To set up the trash bag prefab for interaction, follow these steps:
-
Open the Trash Bag Prefab
- In the Hierarchy, locate a trash bag object in the Park Scene.
- Right-click the object and select
Prefab > Open Asset in Context
to edit the prefab directly.
-
Add a Box Collider (Trigger)
- With the trash bag prefab selected, go to the Inspector panel.
- Click
Add Component
and search forBox Collider
. - Enable
Is Trigger
to allow interaction without physical collision. - Set the size of the box collider to allow for easy interaction with the player and trash bag.
-
Add an Audio Source
- Click
Add Component
and search forAudio Source
. - In the
Audio Source
component, uncheckPlay on Awake
so the sound only plays when triggered. - Set the
Loop
to unchecked as this clip will only play once. - Click the
Audio Clip
field and assign the SX_collected-trash.wav sound file.
- Click
-
Add a Script Machine
- Click
Add Component
and search forScript Machine
. - In the
Script Machine
component, clickNew
to create a new Script Graph. - Name the graph TrashBag and save it in your Scripts folder.
- Click
The following is a screenshot of the trash bag prefab with the above setting applied
With these steps completed, the trash bag prefab is ready for scripting interactions.
Step 2: Set Up OnTriggerEnter for Player Interaction​
With the Trash Bag Prefab still open in Prefab Mode, follow these steps to set up the interaction logic:
-
Open the Script Graph
- In the Inspector, locate the
Script Machine
component. - Click
Edit Graph
to open the visual scripting editor.
- In the Inspector, locate the
-
Remove Default Nodes
- In the script graph, delete the default
OnStart
andOnUpdate
nodes, as they are not needed for this interaction.
- In the script graph, delete the default
-
Add an OnTriggerEnter Node
- In the Graph Editor, right-click and search for
OnTriggerEnter
. - This node will trigger when another object enters the trash bag's
Box Collider
.
- In the Graph Editor, right-click and search for
-
Check If the Player Entered the Trigger
- Add a
Collider → Get Game Object
node and connect it toOnTriggerEnter
. - Add a
Game Object → Compare Tag
node and set the tag to Player. - Connect the
Get Game Object
node to theCompare Tag
node.
- Add a
Before we get much further, we will test to make sure that the OnTriggerEnter
event is even working. We can easily test this with a Debug Log
message sent to the console.
-
Test the Condition with Debug Logs
- Add an
If
(Branching Node) and connect theCompare Tag
output. - Create a
Debug Log
node and a String Literal node. - Set the String Literal to
"Player Entered"
and connect it to the True output of the If node. - Add a second Debug Log and String Literal, setting the message to
"Player did not Enter"
. - Connect this to the False output of the If node.
- Add an
-
Test the Interaction
- Save the graph and close the Script Graph window.
- Enter Play Mode in Unity and move the player toward a trash bag.
- Check the Console to verify the correct debug message appears when entering the trash bag's trigger.
Once the debug messages confirm the interaction is working, we can proceed to implementing the actual trash collection logic.
Step 3: Calling a Global Event​
In object-oriented programming, there's a common design pattern called the observer pattern. This pattern allows one object (the subject) to send out a message, and multiple other objects (observers) can listen for and react to that message.
This approach prevents objects from being tightly connected to one another. For example, if Object A directly tells Object B what to do, Object A must know that Object B exists. If Object B is missing or changes, the interaction could break. Instead, Object A can simply send out a message, and any object listening for the message will respond accordingly.
Unity’s Visual Scripting doesn’t include a built-in observer system, but we can achieve similar behavior using Global Events. A Global Event acts like a shared broadcast station—any object can send an event, and any other object can listen for it without needing a direct connection.
In this section, we’ll set up a Global Event that lets our trash bag objects notify the game when they’ve been collected, so the game can update the counter on the trash bins without the trash bags needing to know about the trash bins directly.
-
Create the Global Event Object
- In the Hierarchy, create an Empty GameObject.
- Rename it to
GlobalEvent
. - Drag
GlobalEvent
into the Project Window to convert it into a Prefab. - Delete
GlobalEvent
from the scene (we only need the prefab).
-
Modify the Trash Bag Prefab
- Open the Trash Bag Prefab in Context.
- In the Inspector, locate the
Script Machine
component. - Click
Edit Graph
to open the visual scripting editor.
-
Add a Custom Event Trigger
- Locate the
Debug Log
node connected to the True branch of theIf
node. - After this, add a
Custom Event Trigger
node. - Set the Event Name to
AddToCounter
. - Set the Target Object to the
GlobalEvent
prefab.
- Locate the
At this point, the event trigger has been set up, but nothing will happen until we configure the receiving event on the Trash Bin.
Step 4: Playing a Sound Clip​
Next, we want to play the sound clip when the trash bag is interacted with.
- Flowing from the
Custom Event Trigger
node, add anAudio Source Play
node. - Make sure that the target is set to
This
. This ensures that the sound clip assigned to theAudio Source
component (in the inspector) will play.
Step 5: Destroying the Object​
In Unity, calling the Destroy
method immediately removes the object from the scene and stops any behaviors associated with it. This means that if the object is in the middle of performing an action, such as playing a sound clip, the action will be interrupted. To avoid this, we can introduce a slight delay before the object is destroyed, ensuring that the sound clip can finish playing first.
Here’s how to set this up:
- Add a Destroy node: Flowing from the
Audio Source Play
node, add aDestroy
node with an object and time delay parameter. - Set the target to
This
, which refers to the trash bag object. - Set the delay time to 0.5 seconds to match the length of the sound clip.
- Save your graph, then test it in Play mode to ensure the trash bag is destroyed only after the sound has finished playing.
This approach ensures that the trash bag remains in the scene for the entire duration of the sound clip before being destroyed, providing a smoother gameplay experience.
The following is a screenshot of the trash bag script graph for the OnTriggerEnter
Now that the trash bag is set up we can continue on with the trash bin behaviors.
🛠Trash Bin Interactions​
Tutorial: Trash bin Interactions
đź“ťVisual scripting | đź•’30 minutes | đź“‚Required File: Park Clean-Up Game
In this tutorial we will set up the visual scripts for the trash bin interactions.
The trash bin will listen for events broadcast by the trash bags and keep track of the collected trash. By the end of this section, you'll have set up the trash bin prefab to listen for the event from the trash bags and manage the collection count.
Step 1: Prepare the Trash Bin Prefab​
Similar to the trash bags, the behavior of the trash bin is consistent across all instances. To maintain this consistency, we will add the script graph to the trash bin prefab, ensuring that all trash bins interact the same way.
-
Open the Trash Bin Prefab
- In the Hierarchy, locate a trash bin object in the Park Scene.
- Right-click the object and select
Prefab > Open Asset in Context
to edit the prefab directly.
-
Add a Box Collider (Trigger)
- With the trash bin prefab selected, go to the Inspector panel.
- Click
Add Component
and search forBox Collider
. - Enable the
Is Trigger
option to allow interaction without physical collision. - Adjust the size of the box collider to ensure it can easily interact with the player and the trash bags.
-
Add a Script Machine
- Click
Add Component
and search forScript Machine
. - In the
Script Machine
component, clickNew
to create a new Script Graph. - Name the graph TrashBin and save it in your Scripts folder.
- Click
The following is a screenshot of the trash bin prefab with the above settings applied.
Step 2: Set Up AddToCounter Event​
Decoupling is a good programming practice that ensures objects are independent and don’t rely too heavily on each other. In this case, we’re focusing on making the trash bins responsible for tracking the amount of trash collected, while the trash bags are simply responsible for triggering the event when they are interacted with.
In this setup, the trash bin will listen for the AddToCounter event, which is broadcast by the trash bags. The trash bin doesn't need to know anything about the trash bag itself, only that it should increase the count of collected trash when the event occurs.
To keep track of the amount of trash collected, we need a variable. For this variable we use an int
type as it only records a whole number (integer) value, and we will name this variable trashCollected
. Since only the trash bins need to know and update this value, it’s best to set it up as a graph variable. Graph variables are private to the script graph, meaning they are only accessible within the graph itself and not from other objects or scripts. This ensures the trashCollected
variable remains hidden (encapsulated) and maintains the decoupling of the trash bins and trash bags, avoiding unnecessary dependencies.
-
Open the Script Graph
- In the Inspector, locate the
Script Machine
component. - Click
Edit Graph
to open the visual scripting editor.
- In the Inspector, locate the
-
Create Graph Variable
- In the Blackboard (the sidebar for managing variables), create a new graph variable named
trashCollected
. This will be anint
variable that stores the number of trash bags collected.
- In the Blackboard (the sidebar for managing variables), create a new graph variable named
-
Remove Default Nodes
- In the script graph, delete the default
OnStart
andOnUpdate
nodes, as they are not needed for this interaction.
- In the script graph, delete the default
-
Add a Custom Trigger Event Node
- In the Graph Editor, right-click and search for
Custom Event
. - Name the event
AddToCounter
, this should match the name of the Custom Trigger Event set in the Trash Bag script graph. - Set the Target Object to the
GlobalEvent
prefab. This prefab acts as a global reference for all events and ensures that the event is properly broadcasted.
- In the Graph Editor, right-click and search for
The flow looks clear, but I've made a few corrections for typos and improved consistency in the language. Here's the refined version:
Step 3: Add to trashCollected
​
When the AddToCounter
event is called, it should increment the trashCollected
variable.
- Get the Graph Variable
- Add a
Get Graph Variable
node. - From the dropdown, select the
trashCollected
variable.
- Add a
-
Add to the Variable
- Right-click in the graph window and find the
Add Inputs
node. - Attach the
Get Graph Variable
node from the previous step to Value A. - Add an
Integer
(literal) node and set its value to 1. - Attach the
Integer
node to Value B on theAdd Inputs
node.
- Right-click in the graph window and find the
-
Set the
trashCollected
Variable- Search for the
Set Graph Variable
node and set it totrashCollected
. - Attach the
Add Inputs
node to the Value input. - Set the flow from the
AddToCounter
event into theSet Graph Variable
.
- Search for the
To ensure the trashCollected
variable is updating, we will log its value using a Debug Log
.
-
Debugging
trashCollected
- Create a
Debug Log
node. - Add a
String Concat
node to combine multiple arguments (values) as the message. - Add a
String Literal
node with the value set to Trash collected =. Make sure to include a space after the equals sign to avoid the value printing too close. - Set the
String Literal
node as the first value in theString Concat
arguments. - Add a
Get Graph Variable
node to retrieve the value oftrashCollected
and connect it to the second argument of theString Concat
node. - Set the
String Concat
node as the message value for theDebug Log
node.
- Create a
-
Test the Interaction
- Save the graph and close the Script Graph window.
- Enter Play Mode in Unity and move the player toward a trash bag.
- Check the Console to verify the correct debug message appears after the trash bag is collected.
- Note that while the
AddToCounter
event is properly called, no actual interaction with the trash bin is needed for this test.
The final AddToCounter
event on the Trash Bin should look like the following image:
Good programming practices involve using a consistent naming convention to quickly identify the type of variables. While it's not as crucial in visual scripting, maintaining this practice helps as you transition to full code. One common naming convention is to use camelCase for private variables (those only accessible by the script or graph) and PascalCase for public (scene) variables.
Step 4: Set Up OnTriggerEnter for Player Interaction​
While the AddToCounter
event tracks trash collection, it doesn't allow the player to check if all required trash has been collected. To address this, an OnTriggerEnter
event will be used when the player interacts with a trash bin. This interaction provides visual feedback and confirms whether all trash has been collected.
-
Update the Trash Bin Prefab
- Open the Trash Bin Prefab in Context.
- In the Inspector, locate the
Script Machine
component. - Click
Edit Graph
to open the Visual Scripting Editor.
-
Add an
OnTriggerEnter
Node- In the Graph Editor, right-click and search for
OnTriggerEnter
. - This node will trigger when another object enters the trash bin's
Box Collider
.
- In the Graph Editor, right-click and search for
-
Check If the Player Entered the Trigger
- Add a
Collider → Get Game Object
node and connect it toOnTriggerEnter
. - Add a
Game Object → Compare Tag
node and set the tag to Player. - Connect the
Get Game Object
node to theCompare Tag
node. - Add an
If
(Branching Node) and connect theCompare Tag
output.
- Add a
Step 5: Check if Player HasCollectedAllTrash​
We only want the OnTriggerEnter
event to proceed if the player has not already collected all the trash. If all trash is collected, there’s no need to recheck the condition.
To track this, we will create a scene variable named HasCollectedAllTrash
. Unlike a graph variable, which is tied to a specific object’s script graph, a scene variable is shared across all objects in the scene. This ensures that all trash bins reference the same global value, preventing inconsistencies and allowing any bin to check if all required trash has been collected.
- Create a Scene Variable
- In the Blackboard (variable management sidebar), create a new scene variable named
HasCollectedAllTrash
. - Set its type to Boolean (bool) and ensure its default value is false (unchecked).
- In the Blackboard (variable management sidebar), create a new scene variable named
- Check the Variable’s Value
- Add a
Get Graph Variable
node forHasCollectedAllTrash
. - Add an
If
(Branching Node). - Set the
Get Graph Variable
node as the condition to check. - Connect the true flow from the previous
If
node (which checks thePlayer
tag) to this newIf
node.
- Add a
Step 6: Compare trashCollected with requiredTrashAmount​
In order to properly check if we collected all the trash, we need to know the required amount of trash there is to be collected. There are many different ways we could handle this, but for simplicity we are simply going to create a graph variable that stores this value, one that we will need to set up here in the script graph.
- Create Graph Variable
- In the Blackboard (the sidebar for managing variables), create a new graph variable named
requiredTrashAmount
. This will be anint
variable. - Set the value of
requiredTrashAmount
to whatever the amount is. - Tip: For testing, set it to a small number like 1 or 2 to avoid unnecessary collection.
- In the Blackboard (the sidebar for managing variables), create a new graph variable named
- Comparing Variables
- Add two
Get Graph Variable
nodes:- One for
trashCollected
. - One for
requiredTrashAmount
.
- One for
- Add a
Greater or Equal
node.- Connect
trashCollected
to A - Connect
requiredTrashAmount
to B
- Connect
- Add an
If
(Branching Node) and use theGreater or Equal
node as the condition. - Connect the false flow from the
HasCollectedAllTrash
check to this newIf
node.
- Add two
Remember here we only want to check if the trash collected is greater or equal to the required trash amount only if HasCollectedAllTrash
is FALSE. If it is true, that means we have collected all the trash and no need to check again.
Step 7: Mission Completed​
When all trash is collected, we need to notify the game that the mission is complete. This information isn’t just for the trash bins, it may also be needed by other objects in the game, such as UI elements or mission trackers. To handle this, we will send a global event, ensuring that any relevant game system can respond appropriately.
-
Set the HasCompletedAll Trash to True
- Add a
Set Graph Variable
forHasCollectedAllTrash
node - Add a
Bool
(literal) node and set it to true (checked) - Set the flow of the previous
if
node, that tested for theGreater or Equal
true value to theSet Graph Variable
forHasCollectedAllTrash
node
- Add a
-
Mission Completed Event
- Create a new
Custom Event
node and name itMissionCompleted
- Set the target object to the
GlobalEvent
- Create a new
The final OnTriggerEnter
logic for the Trash Bin should look like this:
To test the OnTriggerEnter
behaviors of the trash bins, add a Debug Log
after the initial Get Graph Variable
check for HasCollectedAllTrash
. Connect the false
and true
flows from the if
node both into the Debug Log to output its value. Set requiredTrashAmount
to a small number like 2, then play the game. Collect one trash bag and interact with a trash bin, the log should show false. After collecting the second bag and returning to the bin, it should now output true.
Managing Missions​
As mentioned earlier, each game object should be responsible for managing its own data. However, in some cases, we need to track broader aspects of the game. For example, we need a way to manage the progression of different missions (or quests, objectives—whatever you choose to call them). The completion of these missions determines how the player advances through the level or scene.
To handle this, we will use a game manager—specifically, a Level Manager. In our lesson on visualizing loops, we developed a Level Manager flowchart that outlines its key behaviors. This includes storing information about each level and monitoring when a mission is completed to trigger progression.
🛠Creating a Level Manager​
Tutorial: Creating a Level Manager
đź“ťVisual scripting | đź•’30 minutes | đź“‚Required File: Park Clean-Up Game
In this tutorial we will set up the visual scripts for the level manager.
Step 1: Setting Up the Level Manager​
Unity operates on an object-based system, meaning every action or behavior in the game must be associated with a GameObject. While our Level Manager does not need to be a visible object in the game world, it still needs to exist as a GameObject. To achieve this, we will create an empty GameObject to serve as the Level Manager.
-
Create the Level Manager Object
- In the Hierarchy, create a new Empty GameObject.
- Rename it to
Level Manager
. - Drag
Level Manager
into the Project Window to turn it into a Prefab. - In the Hierarchy, select
Level Manager
. - Right-click the object and choose Prefab > Open Asset in Context to edit the prefab directly.
-
Add a Script Machine
- Click Add Component and search for
Script Machine
. - In the Script Machine component, click
New
to create a new Script Graph. - Name the graph
Level Manager
and save it in your Scripts folder.
- Click Add Component and search for
-
Remove Update Node
- In the script graph, delete the default
OnUpdate
nodes, as it will not be needed for this interaction. - Keep the
OnStart
node, we will use this later.
- In the script graph, delete the default
Step 2: Defining the Number of Missions​
The Level Manager needs to track the number of missions in the scene or level. To do this, we will create an integer variable called NumberOfMissions
. Since the number of missions may vary between levels or scenes, each instance of the Level Manager should maintain its own count.
Using a graph variable wouldn't work, as each scene may have a different number of missions. A scene variable is also unnecessary because the value doesn’t need to be accessed globally. Instead, we will use an object variable, which stores data specific to the instance of the Level Manager within the scene.
- Create an Object Variable
- Open the Blackboard (variable management sidebar).
- Create a new object variable named
NumberOfMissions
. - Set its type to int and assign it a default value greater than zero.
The exact number of missions will vary depending on the scene or level. However, we can set a default value as a starting point and for testing purposes. This value can be adjusted for each scene by selecting the Level Manager in the Inspector and updating the object variable accordingly.
Step 3: Creating a Mission List​
Next, we will use the value of NumberOfMissions
to create a MissionList
, which will track whether each mission is complete (true
or false
). This will be implemented as a List of Booleans, where each item in the list corresponds to a mission and is referenced by its index number (e.g., MissionList[0]
, MissionList[1]
). The associated value will indicate whether the mission is completed (true
) or not (false
).
Since multiple game objects may need to check mission progress—for example, a gate may need to verify mission completion before opening—the MissionList
must be accessible beyond the Level Manager. For this reason, it will be created as a scene variable.
- Create a Scene Variable
- Open the Blackboard (variable management sidebar).
- Create a new scene variable named
MissionList
. - Set its type to List of Booleans.
In the Scene Variables tab of the Script Graph Blackboard, you may also see the HasCollectedAllTrash
variable created for the Trash Bin. Remember that scene variables are global and can be accessed from any graph. As a result, all scene variables will always appear in the Blackboard of any Script Graph in the scene.
Step 4: Populating the Mission List​
Currently, we have a MissionList
, but it doesn't contain any items. We need to populate it by adding one item for each NumberOfMissions
. Since this initialization only needs to happen once, we’ll add it to the existing OnStart
node.
-
Creating a For Loop
- Right-click in the graph window and add a
For Loop
node. - Connect the flow from the
OnStart
node to theFor Loop
node. - Add a
Get Object Variable
node forNumberOfMissions
and set this as the value for Last in theFor Loop
node.
- Right-click in the graph window and add a
-
Adding List Items
- Create an
Add List Item
node. - Connect the body output of the
For Loop
to theAdd List Item
node.- Add a
Get Scene Variable
node forMissionList
, and set it as the list input forAdd List Item
. - Add a
Bool
(literal) node set to false (unchecked) and connect it to the value input ofAdd List Item
.
- Add a
- Create an
Step 5: Debugging the List​
To ensure that the list populates correctly, we can log a message to the Console, displaying each list item’s index and value. To format the message in a readable way, we will use String Concat
nodes. The output should follow this format below, where i is the index number of the list item.
Value of MissionList i false
-
Add String Nodes
- Add a
String
(literal) node and set its value to "Value of MissionList " (include a space at the end). - Add a
String Concat
node with two args:- Connect the index output from the
For Loop
to arg 0 ofString Concat
. - Add another
String
(literal) node containing a single space and connect it to arg 1.
- Connect the index output from the
- Create a second
String Concat
node with three args:- Set arg 0 to the
"Value of MissionList "
string. - Set arg 1 to the first
String Concat
output.
- Set arg 0 to the
- Add a
-
Get List Value
- Add a
Get List Item
node to retrieve each mission’s value:- Connect the list input of
Get List Item
to theMissionList
scene variable. - Connect the index from the
For Loop
to the index input ofGet List Item
.
- Connect the list input of
- Set the arg 2 input of the second
String Concat
node to the output ofGet List Item
.
- Add a
-
Debug Log Message
- Add a
Debug Log
node. - Connect the output of the second
String Concat
to theDebug Log
node’s message input.
- Add a
-
Testing the List
- Save the graph and close the Script Graph window.
- Enter Play Mode.
- Check the Console to confirm that a debug log message appears for each mission.
After implementing these steps, the OnStart
logic for the Level Manager should resemble the following:
Step 6: Handling Mission Completion​
Whenever a mission is completed, the related objects will trigger the MissionCompleted
event, similar to how we set up the Trash Bin earlier. The LevelManager
will listen for this event and update the corresponding mission value in the MissionList
.
- Add a Custom Trigger Event Node
- Open the Graph Editor, right-click, and search for
Custom Event
. - Name the event
MissionCompleted
. This should match the Custom Trigger Event set in the Trash Bin script graph. - Set the Target Object to the
GlobalEvent
prefab. This ensures the event is broadcasted properly across all game objects.
- Open the Graph Editor, right-click, and search for
Step 7: Updating the MissionList​
To track which mission has been completed, we need a way to reference the appropriate index in the MissionList
. For simplicity, we assume that missions are completed in sequential order. To manage this, we'll use an integer variable called completedMissionCounter
, which will be stored as a graph variable in the LevelManager
.
-
Create a Graph Variable
- Open the Blackboard (variable management sidebar).
- Create a new graph variable named
completedMissionCounter
. - Set its type to int and initialize it with a value of 0.
-
Update the Mission Item
- Add a
Set List Item
node:- Set the list input to a
Get Scene Variable
node forMissionList
. - Set the index input to a
Get Graph Variable
node forcompletedMissionCounter
. - Set the item value to a
Bool
(literal) node set to true (checked).
- Set the list input to a
- Add a
Step 8: Debugging Updated Missions​
Now, we'll add a Debug Log message to confirm that mission completion is being updated correctly. This will be similar to how we debugged MissionList
creation in Step 5.
-
Create a Debug Message
- Add a
String Concat
node with two args:- Arg 0: A
Get Graph Variable
node forcompletedMissionCounter
. - Arg 1: A
String
(literal) node with the text " completed " (include spaces before and after).
- Arg 0: A
- Connect the flow output from the
Set List Item
node to theString Concat
node.
- Add a
-
Format the Debug Message
- Add another
String
(literal) node with the value "Mission " (including a trailing space). - Create a second
String Concat
node with three args:- Arg 0: The
"Mission "
string node. - Arg 1: The output from the first
String Concat
node. - Arg 2: The value of the completed mission from the list (retrieved in the next step).
- Arg 0: The
- Add another
-
Retrieve the Mission's Value
- Add a
Get List Item
node to fetch the mission's current status:- Set the list input to a
Get Scene Variable
forMissionList
. - Set the index input to a
Get Graph Variable
forcompletedMissionCounter
.
- Set the list input to a
- Connect the output of
Get List Item
to Arg 2 of the secondString Concat
node.
- Add a
-
Output to the Console
- Add a
Debug Log
node. - Connect the output of the second
String Concat
node to the message input ofDebug Log
.
- Add a
-
Test the Mission Completion
- Save the graph and close the Script Graph window.
- Enter Play Mode.
- Complete a mission by collecting the required trash bags and delivering them to the trash bin.
- Check the Console for a debug log message confirming mission completion (e.g., "Mission 0 completed true").
Step 9: Incrementing the Counter​
After completing a mission, we need to increment completedMissionCounter
so that the next mission can be tracked correctly.
- Update the Counter
- From the
Debug Log
node, connect the flow output into aSet Graph Variable
node forcompletedMissionCounter
. - Right-click in the Graph Editor and add an
Add (int)
node:- Set A to a
Get Graph Variable
node forcompletedMissionCounter
. - Set B to an
Integer
(literal) node with a value of 1.
- Set A to a
- Connect the output of
Add (int)
to the value input ofSet Graph Variable
.
- From the
Now, each time MissionCompleted
is triggered, the system will update the correct mission in MissionList
and move on to the next one. The final MissionCompleted
event in the script graph should appear as the following:
Unlocking Gates​
Now that our Park Clean-Up Game has the trash collection mission built and the LevelManager keeping track of completed missions, the last step is to set up the level (area) gates to unlock once a mission is completed.
Previously, we created an Animator controller to manage the gate's animation states. This controller consists of three states:
- Idle State – The default state when the gate is neither opening nor closing.
- ANIM_GateOpen – The animation clip of the gate opening.
- ANIM_GateClose – The animation clip of the gate closing.
State transitions are controlled by two Animator parameters (think of these as variables) named isGateOpen
and canGateOpen
. When the player interacts with the gate (by entering its trigger zone), we will use a script graph to get and set these variables, triggering the correct animation.
For a more detailed breakdown of the gate’s interaction flow and the game logic challenges associated with transition triggers, refer to the Object Flowchart example in the Visualizing Loops lesson.
🛠Gate Interactions​
Tutorial: Creating a Level Manager
đź“ťVisual scripting | đź•’30 minutes | đź“‚Required File: Park Clean-Up Game
In this tutorial we will set up the visual scripts for the gate
Step 1: Prepare the Gate Prefab​
To set up the Gate prefab for interaction, follow these steps:
-
Open the Gate Prefab
- In the Hierarchy, locate a Gate instance in the Park Scene.
- Right-click the object and select
Prefab > Open Asset in Context
to edit the prefab directly.
-
Add a Box Collider (Trigger)
- With the trash bag prefab selected, go to the Inspector panel.
- Click
Add Component
and search forBox Collider
. - Enable
Is Trigger
to allow interaction without physical collision. - Set the size of the box collider to allow for easy interaction with the player and trash bag.
-
Add an Audio Source
- Click
Add Component
and search forAudio Source
. - In the
Audio Source
component, uncheckPlay on Awake
so the sound only plays when triggered. - Set the
Loop
to unchecked as this clip will only play once. - Click the
Audio Clip
field and assign the SX_ScratchingMetal.wav sound file. - Exit Prefab mode and return to the scene.
- Click
The following is a screenshot of the trash bag prefab with the above setting applied
Step 2: Creating the Gate Script Graph​
Because not every gate will be an unlock able gate to the next level, we do not necessarily need the script graph on every gate prefab. In this case, we can simply create new script graph in our project window and them add them to the gate instances that will need them.
-
Create Script Graph
- In the project window navigate to the Scripts folder
- Right-click and choose
Create> Visual SCripting > Script Graph
- Name this script graph
Gate
-
Add a Script Machine
- In the Hierarchy window find and select the Gate prefab to is the first gate to the next level (area).
- In the Inspector window add click
Add Component
and search forScript Machine
. - In the
Script Machine
component, drag and drop the Gate script graph from the project window into to theScriptMachine
- Click
Edit Graph
to open the visual scripting editor.
Step 2: Set Up OnTriggerEnter for Player Interaction​
-
Remove Unnecessary Nodes
- In the Script Graph, delete the default
OnStart
andOnUpdate
nodes, as they are not needed for this interaction.
- In the Script Graph, delete the default
-
Add an OnTriggerEnter Node
- In the Graph Editor, right-click and search for
OnTriggerEnter
. - This node will trigger when another object enters the trash bag's
Box Collider
.
- In the Graph Editor, right-click and search for
-
Check if the Player Entered the Trigger
- Add a
Collider → Get Game Object
node and connect it toOnTriggerEnter
. - Add a
Game Object → Compare Tag
node and set the tag to Player. - Connect the
Get Game Object
node to theCompare Tag
node.
- Add a
-
Test OnTriggerEnter with Debug Logs
- Add an
If
(Branching) node and connect the output ofCompare Tag
. - Create a
Debug Log
node and a String Literal node. - Set the String Literal to "Player Entered" and connect it to the True output of the
If
node. - Add a second
Debug Log
node and String Literal, setting its message to "Player did not enter". - Connect this to the False output of the
If
node.
- Add an
-
Test the Interaction
- Save the graph and close the Script Graph window.
- Enter Play Mode in Unity and move the player toward a Gate.
- Check the Console to verify the correct debug message appears when entering the trash bag's trigger.
Once the debug messages confirm the interaction is working, we can proceed with implementing the actual Gate logic.
Step 3: Ensuring the Gate is Closed​
When the player enters the gate's trigger, the gate should be closed. However, if the gate was previously opened, we need to reset the isGateOpen
animator parameter to false to ensure the correct behavior. For more details, refer to the Object Flowchart example.
-
Set the Animator Bool
- In the Graph Editor, right-click and search for
Animator Set Bool
. - Set the parameter name to
isGateOpen
- Ensure that it is spelt exactly as it appears in the Animator, or it will not work.
- Set the value to false (unchecked).
- In the Graph Editor, right-click and search for
-
Connect the Flow
- Connect the True output of the branching
Debug Log
message ("Player Entered") to theAnimator Set Bool
node.
- Connect the True output of the branching
Step 4: Link Gate and Mission​
Whether a player can open a gate depends on whether the mission associated with that gate has been completed. To associate each gate with its corresponding mission, we will assign a unique number to each gate using an object variable named GateNumber
. This number will correspond to the index of the mission in the MissionList
, which we set up with the LevelManager. Since list indexes start at zero, the first gate should have an index of 0
.
-
Create an Object Variable
- Select the correct Gate object in the scene and open its script graph.
- Open the Blackboard (variable management sidebar).
- Create a new object variable named
GateNumber
. - Set its type to int and assign it a default value of
0
.
-
Retrieve the Mission Status
- Add a Get Scene Variable node and select
MissionList
. - Add a Get Object Variable node and select
GateNumber
. - Add a Get List Item node to retrieve the mission's value:
- Connect the list input of
Get List Item
to theMissionList
scene variable. - Connect the index input to the
GateNumber
object variable.
- Connect the list input of
- Set the Animator Bool
- In the Graph Editor, right-click and search for
Animator Set Bool
. - Set the parameter name to
canGateOpen
.- Ensure it is spelled exactly as it appears in the Animator; otherwise, it will not work.
- Set the value to the output of the
Get List Item
node.- This ensures the gate’s
canGateOpen
parameter matches the completion status of the associated mission in theMissionList
.
- This ensures the gate’s
- Connect the Flow
- Connect the
Animator Set Bool
node forisGateOpen
to theAnimator Set Bool
node forcanGateOpen
, ensuring the logic executes in order.
- Test the canGateOpen
- Save the graph and close the Script Graph window.
- Enter Play Mode.
- Complete a mission by collecting the required trash bags and delivering them to the trash bin.
- Navigate towards the gate to the next area. If everything is set up correctly the gate should open.
At this point, once the value of canGateOpen
is set, the animator controller on the gate object will automatically perform its check. If canGateOpen
is true, it will trigger the gate's opening animation. However, we might want additional actions to occur, so we need to explicitly check the value of canGateOpen
ourselves.
Step 4: Checking if the Player Can Open the Gate​
-
Get the Animator Bool
- In the Graph Editor, right-click and search for
Animator Get Bool
.- Set the parameter name to
canGateOpen
.
- Set the parameter name to
- Connect the flow of the
Animator Set Bool
node forcanGateOpen
to theAnimator Get Bool
node.
- In the Graph Editor, right-click and search for
-
Branching (If)
- Add an
If
(Branching) node and connect the output from theAnimator Get Bool
node.
- Add an
Step 5: Trigger a Custom Event​
For this game we want a sound clip on the gate to play alongside the animation. While we could trigger the sound inside the OnTriggerEnter
event, the OnTriggerExit
will require the same sound effect. To avoid redundancy, we'll create a custom event to handle the sound, which both trigger events can call.
- If
canGateOpen
is True- From the
If
branching node’s True output, add aCustom Event Trigger
. - Set the name of the event to
PlaySound
. - Set the target to
this
.
- From the
Since the event is in the same script graph, we can use this
(referring to the current game object) as the target, instead of using GlobalEvent
like we did previously. The final OnTriggerEvent
script for the gate should appear as follows:
Step 6: Play Delayed​
To play the sound clip, we can use the Audio Source Play
node, just like we did with the trash bags earlier. However, the sound of the scratching metal for the gate plays a bit faster than the animation. To ensure that the clip aligns properly with the animation, we will use the Play Delayed
node.
-
Create a Custom Event
- In the project window double-click the Gate script graph to open it in the script editor.
- Create a new
Custom Event
and name itPlaySound
.
-
Play Delayed
- Create a
Audio Source Play Delayed
node.- Set the target to this.
- Set the Delay value to 0.5 for a half-second delay.
- Connect the flow from the
Custom Event
to thePlay Delayed
node.
- Create a
The image below shows the final PlaySound
event in the Gate script graph.
Step 7: OnTriggerExit​
Now that our gate can transition to the open animation, we need it to transition back to the closed state when we exit the gate trigger. Since the behavior for this is almost identical to the OnTriggerEnter
event, we’ll start by copying it.
-
Copy the OnTriggerEnter Event
- Select all the nodes that make up the
OnTriggerEnter
event. - Right-click and select Copy.
- Move to a blank area of the script graph window and right-click to Paste.
- Delete the
OnTriggerEnter
node from the copied event. - Add a
OnTriggerExit
node and set it as the starting node for the copied nodes.
- Select all the nodes that make up the
-
Update Debug Messages
- Update the
String
nodes for theDebug Log
messages:- Change them from Entered to Exited.
- Update the
While it might seem logical to check isGateOpen
, we have never set this variable to true because the instant we do, the gate will close. Instead, we first need to check if the player’s canOpenGate
is true.
-
Get Animator Bool
- Delete the
Animator Set Bool
forisGateOpen
. - Add a new
Animator Get Bool
forcanGateOpen
. - Connect the flow from the
Debug Log
true message into theAnimator Get Bool
forcanGateOpen
.
- Delete the
-
Set Animator Bool
- Delete the following nodes that were copied from
OnTriggerEnter
:Get Scene Variable
for MissionListGet Object Variable
for GateNumberGet List Item
Animator Get Bool
for canOpenGate
- Detach the
if
branching node and connect theAnimator Get Bool
forcanGateOpen
to it. - Reassign the remaining
Animator Set Bool
toisGateOpen
.- Set its value to true.
- Flow the
if
true branch into theAnimator Set Bool
forisGateOpen
. - Finally, flow the
Animator Set Bool
forisGateOpen
into theCustom Trigger Event
.
- Delete the following nodes that were copied from
Essentially, we are checking if canOpenGate
is true. If so, we set isGateOpen
to true, which will trigger the gate's close animation. The final event should look like the following:
📝 Next Mission​
Objective:​
Now that you have the first section of the Park Clean-Up Game working you will extend it by creating two new mission objectives in the next two sections of the game. These objectives will involve interacting with the environment and require visual scripting to handle their logic. For each new mission, you will need to:
- Design the mission mechanics.
- Create scripts to track mission completion.
- Trigger the mission completion event when the task is finished.
The mission can be based on various activities such as picking up recyclables, returning lost items, or fixing objects in the park. Your goal is to use visual scripting to implement the mission functionality while keeping the object behaviors decoupled and passing only necessary information between components.
Requirements:​
-
Mission Design and Objectives
-
Mission Design: Plan out two missions for the next two sections of the park. Think about all the objects the player will need to interact with in order to complete the mission.
-
Sketch a Flowchart: Once the missions are decided, sketch out a quick flowchart outlining the baseline interactions for all objects involved. Consider how the player will interact with each object and how these interactions will lead to mission completion.
-
-
Visual Scripting Logic
-
For both missions, create separate visual scripts to:
- Track any criteria of the mission (e.g. items collected)
- Check if the objective is completed.
- Trigger the
MissionCompleted
event once the mission objective is finished.
-
Reuse the pattern established in previous missions, such as the
HasCollectedAllTrash
variable for checking task completion.- The mission’s script should check whether the conditions for mission completion are met (e.g., all recyclables collected or lost items returned).
-
-
Mission Completion Trigger
- Once the objective is completed, the script should call the
MissionCompleted
event to notify the Level Manager that the mission has been finished. - The next gate should automatically detect the mission's completion and unlock to proceed to the next objective.
- Once the objective is completed, the script should call the
-
Prefab and Graph Variable Organization
- Prefab Behavior: If the behavior needs to be applied to all instances of an object (e.g., recyclables, lost items), place the script graph on the prefab.
- Graph Variables: If the variables are only relevant for an individual object (e.g., specific collectible items or bins), set them as graph variables within the script.
- Object Variables: If the variables are unique to specific instances (e.g., different gates or bins), they should be object variables.
- Scene Variables: If the variables are needed globally across the scene (e.g., tracking mission progress), they should be scene variables.
-
Testing
- Implement
Debug Log
messages as needed to test your scripts - Test both missions to ensure that:
- The player can interact with the environment and complete the objectives (collect recyclables, return lost items).
- The completion of the objective triggers the
MissionCompleted
event and unlocks the next mission. - Ensure that no unnecessary changes are made to the Level Manager or Gate scripts, as these are designed to be dynamic and should not need revision.
- Implement
Submission Instructions:​
- Ensure that all changes are committed and pushed to your repository.
- Submit the URL link to your repository.
This assignment builds upon your previous work with visual scripting, focusing on expanding the game's mission logic and ensuring smooth progression between levels. It will help you gain experience in creating decoupled systems that communicate through events and variables, while also maintaining an organized and modular structure.