7 February 2021

HO5 : Structuring collection behavior and callbacks

Structure

This tutorial is structured as follow :

Requirements

This hands-on is based on the final outcome of the Design of a basic interaction hands-on. Thus, you can directly checkout the last commit and can create a new branch for this hands-on.

Reload the baseline project

If your working directory is not clean (i.e. you have non committed changes), you can stash your work or commit your changes if you want to be able to recover those changes later.

Make sure your work is stashed or committed before the following instruction as it will erase all data

From the root directory of your project you can run :

rm -rf *

If you did not add a tag on your baseline commit you can retrieve its hash with the following command :

git log

Otherwise you can directly checkout the commit of the basic integration of the basic integration with the following command :

git checkout basic_interaction_implemented -f

Hands-on overview

The idea here is to take advantage of the double dispatch design pattern combined with inheritance to handle the behavior of the collection by the player of an item.

Thus to handle the collection of an object we will study an example of situation where the player start without the ability to grasp an object. This feature (or upgrade) will be activated once the player collected an item providing him this upgrade.

Collecting items

Checkout a new branch

git checkout -b implement_object_collection

Implementation overview

The idea here is to wait for an object to collide with the player. Once the collision is detected, the player (Main Player Controller) will check if the object implement an interaction. If it does, the player will call the default interaction which, thanks to the inheritance mechanism and the double dispatch pattern, will automatically define the behavior of our item.

As this case is about providing an upgrade to the player, it will call an action onto the player.

N.B. : All scripts can be downloaded in the final version here :

Creating scripts

Abstract Interactive Item class

The first thing to do is to create an abstract class used to define the common behavior of all objects the player can interact that with we will name InteractiveItem. This abstract class will also allow to filter collisions with objects the player can interact with and it will be the entry point of all interactions.

As the common goal of all interactive items is to interact with the player we can prototype this method in the new abstract class InteractiveItem.cs :

Create new script

using UnityEngine;

public abstract class InteractiveItem : MonoBehaviour {

	public abstract void interacted_with ( MainPlayerController player );

}

Thus each interactive item deriving from the Interactive Item will have to implement the method public void interacted_with ( MainPlayerController player ).

N.B. : It might be really interesting to add an additional action such playing an audio sound that would be also stored in this abstract class. This would automatically make every interactive items to produce a sound each time the player touches it !

Abstract collectible object

Among interactive items we can imagine we can implement a sub category representing all the objects a player can collect. Thus we can create an abstract class CollectibleItem.cs deriving from the InteractiveItem.cs.

The idea of object we can collect is that the default interaction is the collection by the player of the object followed by the destruction of the item in the scene.

As we will split the collection of the object into different sub categories a bit later we will just implement the destruction of the item once it is collected :

public virtual void self_collect_by ( MainPlayerController player ) { Destroy( gameObject ); }

From now we can tell that the default behavior of a collected item (i.e. the action performed in the public void interacted_with ( MainPlayerController player ) method) is the method we just wrote. Thus this gives the following CollectibleItem.cs class :

public abstract class CollectibleItem : InteractiveItem {

	public override void interacted_with ( MainPlayerController player ) { self_collect_by( player ); }

	public virtual void self_collect_by ( MainPlayerController player ) { Destroy( gameObject ); }

}

N.B. : This succession of abstract classes easily allows one to create a new type of objects we can interact with without having them to disappear (e.g. a switch, a temporary lamp) by simply creating a new abstract class deriving from InteractiveItem.

Upgrade abstract class

Now we want to specify our item to be collected by the player to be an upgrade among potential other upgrades allowing the player to perfrom new action for instance. Thus, once more, we create a new abstract class Upgrade.cs deriving from the CollectibleItem.cs and representing all objects providing upgrades to the player.

This time the behavior of the collection by the player extends the one of the parent class (CollectibleItem.cs) we must override it :

public abstract class Upgrade : CollectibleItem {

        public override void self_collect_by ( MainPlayerController player ) {
		// Trigger the action on the player : Tell the player to acquire the blue grasp
		player.acquire_item( this );

		// As the object is acquired we can now apply the common behavior of collectible items : Call its destruction
		base.self_collect_by( player );
	}

}

N.B. : If the override doesn’t partially take advantage of the parent class (i.e. calling the base method) you should think twice at the design you are setting up !

N.B. : It might be really interesting to add an additional action such as launching a short tutorial explaining to the player how the new collected upgrade works and this might be the right place !

Now, we need to implement on the MainPlayerController.cs side the newly created public void acquire_item ( CollectibleItem item ) method.

As we know that once an object is collected, it’s instance is deleted, we won’t store the instance in the player list of upgrade, but a reference to the class of the instance. We can also create a method telling whether or not an upgrade is already acquired as it will be useful to prevent to store several times the same update or to check if an interaction should occur or not.

This can be done like this within the MainPlayerController.cs class:

protected List<Type> list_of_player_upgrades = new List<Type>();

public void acquire_item ( CollectibleItem item ) {
        // Check that the upgrade is not already acquired
        if ( is_equiped_with( item.GetType() ) ) return;

        // Add the upgrade in the list of upgrade collected
        list_of_player_upgrades.Add( item.GetType() );
}

public bool is_equiped_with ( Type type ) {
        // Check that one element is the right type
        for ( int i = 0; i < list_of_player_upgrades.Count; i++ ) if ( type == list_of_player_upgrades[i] ) return true;
        return false;
}

N.B. : This requires the following references :

using System;
using System.Collections.Generic;
using UnityEngine;

N.B. : In the case of collecting simple object the same pattern can be duplicated to store an inventory of collected items. However in this case the check for duplicated upgrade can be removed if we want to be able to store several times the same object (e.g. coins or a “Bafomdad”).

N.B. : The list can be replaced with an array with checks on the size if we want to introduce a limitation in the amount of object the player can carry with him.

N.B. : Such an approach is completely generic as you don’t need to have multiple bools to store if the UpgradeA is obtained, if the UpgradeB is obtained, etc. However this approach unfortunately prevent to easily enable or visualize the player’s upgrades through the InspectorView. If you prefer having a visualization through the InpsectorView you can add several public booleans in the MainPlayerController and replace public void acquire_item ( CollectibleItem item ) and public bool is_equiped_with ( Type type ) to dispatch the event onto the right boolean.

Triggering the collection

While we are implementing our interaction framework, we can take advantage of the edition of the MainPlayerController.cs class to implement the part calling the entry point of the interaction.

As explained previously, the initial event is a collision with an object whose collider is set as trigger. Such events are automatically called by Unity3D through the method void OnTriggerEnter ( Collider other ) that each GameObject can implement.

In our case we want to retrieve only collision with object deriving from InteractiveItem. Thus we can start by looking into the other object involved in the collision for an InteractiveItem anchor and if none is found (i.e. other.GetComponent<InteractiveItem>() is null) we can skip the collision and leave the method.

In the other case, as the object derivate from InteractiveItem it necessarily implement public void interacted_with ( MainPlayerController player ) that we can call here :

void OnTriggerEnter ( Collider other ) {

        // Retreive the object to be collected if it exits
        InteractiveItem interactive_item = other.GetComponent<InteractiveItem>();
        if ( interactive_item == null ) return;

        // Forward the current player to the object to be collected
        interactive_item.interacted_with( this );

}

This gives in the end the following MainPlayerController.cs class :

using System;
using System.Collections.Generic;
using UnityEngine;

public class MainPlayerController : MonoBehaviour {

	protected List<Type> list_of_player_upgrades = new List<Type>();

	public void acquire_item ( CollectibleItem item ) {
		// Check that the upgrade is not already acquired
		if ( is_equiped_with( item.GetType() ) ) return;

		// Add the upgrade in the list of upgrade collected
		list_of_player_upgrades.Add( item.GetType() );
	}

	public bool is_equiped_with ( Type type ) {
		// Check that one element is the right type
		for ( int i = 0; i < list_of_player_upgrades.Count; i++ ) if ( type == list_of_player_upgrades[i] ) return true;
		return false;
	}


	void OnTriggerEnter ( Collider other ) {

		// Retreive the object to be collected if it exits
		InteractiveItem interactive_item = other.GetComponent<InteractiveItem>();
		if ( interactive_item == null ) return;

		// Forward the current player to the object to be collected
		interactive_item.interacted_with( this );

	}
}

Basic grasp upgrade

Now we have a nice structure handling the collection of items including upgrades but all of our classes are abstract. This makes sense as an upgrade is a concept but not an actual item whereas a Basic Interaction Upgrade can be materialized !

N.B. : To make this point a bit more clear there is now raw fruit without a name but we have oranges, apples, etc. which are fruits.

So it’s high time to create a real upgrade and this is really simple, in fact it is as simple as derivate a “real” class from Upgrade.cs. Thus let’s create one of those named BasicGraspUpgrade.cs :

public class BasicGraspUpgrade : Upgrade { }

And now the only thing is to attach this script to a GameObject of the scene equipped with a collider set as Trigger. This will turn the regular object into an object which will provide the player the BasicGraspUpgrade upgrade once it collide with the latter. Finally, once collected, the object will automatically disappear !

Adapt the existing basic interaction behavior

We are able to dispose items on the scene the player can collect and store in memory, but we still need to adapt our grasping algorithm to take into account the upgrade mechanism required to trigger the interaction.

Let’s move back firstly to the ObjectAnchor.cs class and add a method defining whether an object can actually be grasped by the player or not. We want that object equipped with the ObjectAnchor.cs to be grasped only when the player is equipped with the BasicGraspUpgrade ?

Well let’s just implement that like this :

public virtual bool can_be_grasped_by ( MainPlayerController player ) { return player.is_equiped_with( typeof( BasicGraspUpgrade ) ); }

Now in the HandController.cs let’s just add this check to trigger an interaction by replacing :

// Iterate over objects to determine if we can interact with it
for ( int i = 0; i < anchors_in_the_scene.Length; i++ ) {

	// Skip object not available
	if ( !anchors_in_the_scene[i].is_available() ) continue;

	// Compute the distance to the object
	oject_distance = Vector3.Distance( this.transform.position, anchors_in_the_scene[i].transform.position );

	// Keep in memory the closest object
	// N.B. We can extend this selection using priorities
	if ( oject_distance < best_object_distance && oject_distance <= anchors_in_the_scene[i].get_grasping_radius() ) {
		best_object_id = i;
		best_object_distance = oject_distance;
	}
}

by :

// Iterate over objects to determine if we can interact with it
for ( int i = 0; i < anchors_in_the_scene.Length; i++ ) {

	// Skip object not available
	if ( !anchors_in_the_scene[i].is_available() ) continue;



        // ####################################### New check perfomed here ! #######################################
        // Skip object requiring special upgrades
        if ( !anchors_in_the_scene[i].can_be_grasped_by( playerController ) ) continue;



	// Compute the distance to the object
	oject_distance = Vector3.Distance( this.transform.position, anchors_in_the_scene[i].transform.position );

	// Keep in memory the closest object
	// N.B. We can extend this selection using priorities
	if ( oject_distance < best_object_distance && oject_distance <= anchors_in_the_scene[i].get_grasping_radius() ) {
		best_object_id = i;
		best_object_distance = oject_distance;
	}
}

Thus object without the right upgrade won’t be considered in the grasping interaction.

Add the upgrade item on the scene

Now all the scripts are finished and the only remaining task is to create an object in the scene representing the Basic Grasp Upgrade. Once created, the object’s collider needs to be set as Trigger and the BasicGraspUpgrade.cs needs to be attached to this object. Then, once the player will collide with it he will be provided the Basic Grasp !

In order to easily differentiate the cubes from an upgrade, I will use something visually different. Thus, to change from the regular cubes, we will create a sphere to represent the upgrade. (I know after so many cubes : a sphere, that’s really impressive…)

Create sphere

We can move the sphere to make sure we won’t collide with it directly when the game start :

Move sphere

In order to make the collision detected in the void OnTriggerEnter ( Collider other ) method and not into the void OnCollisionEnter(Collision collision) one we must set the collider as a trigger.

Thus we must check the collider’s Trigger checkbox :

Set collider as trigger

And now we can add it our newly created Basic Grasp Upgrade :

Attach basic grasp upgrade

N.B. : Once you created an object you should drag the object from the scene to the project explorer to convert this object into a prefrab you can reuse later if you want to place multiple times the same object in the scene !

N.B. : To reduce the amount of information in this hands-on ; all scripts, materials etc. are placed on the root asset folder of this project. However you should try to organize your files properly : each time you create a prefab place it into a specific folder and create subfolders to stores it’s scripts, materials, images, meshs, sounds, etc. Also you can replicate the structure of the items you can collect to organize your files with all upgrades being store in sub folders of the Upgrade folder :

For instance for the Basic Grasp Upgrade you can use the following structure :

Assets
 ├── Oculus
 │    └── ...
 ├── Resources
 │    └── ...
 ├── Scenes
 │    └── SampleScene.unity
 ├── XR
 │   └── ...
 └── Prefabs
     ├── Upgrades Items
     |    ├── Upgrade.cs
     |    ├── Basic Grasp Upgrade
     |    |    ├── BasicGraspUpgrade.prefab
     |    |    ├── Material
     |    |    |    └── DefaultMaterial.mat
     |    |    └── Scripts
     |    |         └── BasicGraspUpgrade.cs
     |    ├── Double Jump Upgrade
    ...  ...

N.B. : You can place your files anywhere within the Assets folder and subfolders thanks to the .meta files. Thus, you should not destroy those files or move a file manually without moving its associated .meta file !

Build

And now we can build the game and test it on the Oculus Quest / Oculus Quest 2 : Ctrl + B

Normally you should obtain such result as illustrated on this video :

And here is a schema of the architecture we just implemented :

Schema of the implemented structure

Commit

If the project work as expected we can make a small commit of our project state and merge the development branch implement_object_collection into the master one :

# Commit
git add .
git commit -m "Implement the frame to collect items"

# Merge
git checkout master
git merge implement_object_collection --no-ff
git tag object_collection_implemented -a -m "The frame to collect item is implemented"

# Push
git push --all
git push --tags

Derivate an item

Now you can take advantage of the derivation mechanism to make different or sub categories of objects : You can have energy tanks increasing the player maximal amount of energy, different subcategories of energy capsules allowing the player to recover energy, items providing new beams, missiles, grapple beam or even just damages reducing the energy when the player is touched !

Inline with the course, make sure that each sub category of a collectible item share the same visual layout to help the user to clearly guess that this object is an upgrade and this one heals the player.

Here we will expect the user to acquire a Purple Upgrade allowing the player to grasp a Purple Item !

Checkout a new branch

git checkout -b derivate_existing_interaction

Derivate the Upgrade

Now that the structure is defined, creating a new upgrade item is as simply as creating a new script PurpleUpgrade.cs deriving from Upgrade.cs as we saw previously.

However we want to make sure the user can’t acquire this item without possessing the Basic Grasp Upgrade… Well, let’s just override the public void self_collect_by ( MainPlayerController player ) method to do nothing if the player don’t have the Basic Grasp Upgrade and do the regular upgrade inherited from its parent class otherwise.

public class PurpleUpgrade : Upgrade {

	public override void self_collect_by ( MainPlayerController player ) {

		// Prevent the user to collect the upgrade if he doesn't own the basic grasp
		if ( !player.is_equiped_with( typeof( BasicGraspUpgrade ) ) ) return;

		// Acquire the object through the default behavior otherwise
		base.self_collect_by( player );
	}

}

Derivate the object to interact with

Let’s start by overriding the public virtual bool can_be_grasped_by ( MainPlayerController player ) method in a new class PurpleObject.cs for our new type of objects :

public class PurpleObject : ObjectAnchor {
	public override bool can_be_grasped_by ( MainPlayerController player ) { return player.is_equiped_with( typeof( BasicGraspUpgrade ) ) && player.is_equiped_with( typeof( PurpleUpgrade ) ); }
}

Now we could also change the behavior of the public void attach_to ( HandController hand_controller ) method which would require to add the virtual keyword to the base method and to define the new way the object is linked to the player. However to keep the example simple we won’t do this here.

N.B. : However you are more than welcomed to try to implement any kind of action you want to implement if you want !

Attaching items in the scene

Script can be downloaded here :

Creating the purple material

Let’s start by creating a new purple material :

Create new material

And edit it’s color :

Edit material

Create the upgrade item

We can create a new sphere representing the new purple upgrade :

Create new sphere

Then we can move it, set it’s collider to Trigger, drag it the purple material and attach it the new upgrade PurpleUpgrade :

Add purple upgrade to the sphere

Creating the new special item to interact with

Now we can create our special purple cube :

Create new cube

Then move it, scale it, drag the purple material on it and finally, let’s attach it the new PurpleObject anchor :

Create new sphere

Expect result

Normally you should have the following result once you build and push the application on the Oculus Quest (Ctrl + B) :

Commit

If the project works as expected we can commit our changes and merge it into the master branch :

# Commit
git add .
git commit -m "Derivate the basic interaction into an interaction requiring an extra upgrade to be active"

# Merge
git checkout master
git merge derivate_existing_interaction --no-ff
git tag derivate_interaction_implemented -a -m "Integration of a new interaction based on the derivation mechanism"

# Push
git push --all
git push --tags

Callbacks

In this section we will briefly introduce the notion of callbacks.

The main idea of a callbacks is delegate the call of a method to the object generating an event rather than continuously pooling information from the object generating the event to see whether or not an action should be launched or not.

To illustrate this mechanism we will introduce a use case where we want a fence to move down when the player walks on a floor switch.

Checkout a new branch

git checkout -b add_callback_example

You can download the files described bellow in the final state here :

Implement the fence

As we want a fence to move down we can start by preparing the class Fence.cs handling the behavior of the fence :

using UnityEngine;

public class Fence : MonoBehaviour {

	[Header( "Fence parameters" )]
	[Range( 0.01f, 0.1f )]
	public float animationSpeed = 0.05f;


	// Expose fence actions
	public void open () { movement_direction = true; }
	public void close () { movement_direction = false; }


	// Compute and store opened and closed positions of the fence
	protected Vector3 open_position;
	protected Vector3 closed_position;
	void Start () {
		closed_position = transform.position;
		open_position = transform.position - new Vector3( 0, this.GetComponent<Renderer>().bounds.size.y, 0 );
	}


	// Handle animation of the door
	protected float t = 0;
	protected bool movement_direction = false;
	void Update () {
		// Handle transition rate
		t += movement_direction ? animationSpeed : -animationSpeed;

		// Cap values
		if ( t < 0 ) t = 0;
		if ( t > 1 ) t = 1;

		// Animate the fence
		this.transform.position = ( 1 - t ) * closed_position + t * open_position;
	}
}

N.B. : You can also add some audio when the fence moves

Implement a switch

Now let’s implement the behavior of the floor swith. We can take advantage of the structure previously implemented to create our FloorSwitch as an InteractiveItem. To respect the principle of abstraction we will place an intermediate Switch abstract class deriving from InteractiveItem :

public abstract class Switch : InteractiveItem {
	public override void interacted_with ( MainPlayerController player ) { self_toggled_by( player ); }

	public virtual void self_toggled_by ( MainPlayerController player ) { /* Do something here */ }
}

Now let’s focus a bit more on what we can place in public virtual void self_toggled_by ( MainPlayerController player ) : If we directly want to place something like fence.open() or fence.close() we need to store the instance of the fence and we end up with all switches storing instance of fences whether or not this switch is attached to a fence. Thus we can’t apply such solution.

A better solution would be to store a Controller instead of a Fence letting the inheritance of controllers to handle the behavior of a fence, a light, an animation, etc. Thus we would have something like :

using UnityEngine;

public abstract class Controller : MonoBehaviour {
	public abstract void on_action ();
}

and we would call in our Switch class controller.on_action().

However, let’s assume that our controller expect inputs from 4 different switches and not just once. With this implementation we cannot directly tell the controller : “Hey ! I’m the 4th switch !” as all switches share the same entry point on the controller. We cannot also add a Switch as parameter as other controllers might expect different types of objects as entry points and we don’t want either to override the root class of all objects to fix this succession of issues…

So a simple solution to this problem is to use callbacks to trigger events on our controller !

The idea is that the switch will store in its instance a reference to a function that it will trigger once the player interact with. To make the link with the controller this function will point to one of the method of the controller.

So let’s implement this assuming the function called on the controller doesn’t expect any argument in the first place :

using System;

public abstract class Switch : InteractiveItem {
	public override void interacted_with ( MainPlayerController player ) { self_toggled_by( player ); }

	// Store the reference to the function to call when an interaction with the player occurs
	protected Action on_toggled_callback;

	// Allow external objects (such as a controller) to tell which function to call when an interaction with the player occurs
	public void on_toggled ( Action callback ) { on_toggled_callback = callback; }

	// Once the switch is toggled, call the stored function if it is defined
	public virtual void self_toggled_by ( MainPlayerController player ) { if ( on_toggled_callback != null ) on_toggled_callback(); }
}

From now, any object can tell to an instance of the Switch (actually more a real class deriving from Switch) :

“Hey my_switch ! Once you are toggled execute my_method() !” by calling my_switch.on_toggled( my_method ); !

N.B. : To tell “Hey my_switch ! Once you are toggled do nothing !” you can simply call my_switch.on_toggled( null );

N.B. : If multiple items must listen for event from an object you can store a list of callbacks rather than just a single item and iterate on the list to call each callback.

Implement the floor switch

To go even further, we will consider our FloorSwitch to be a bi-stable switch taking consecutively the values On and Off.

This can be achieved by creating a new type of events through the keyword delegate. Thus to call function expecting a boolean as input we can create the new type SwitchEvent as :

public delegate void SwitchEvent ( bool is_turned_on );

Finally, with the implementation of the state toggling this gives for FloorSwitch.cs :

public class FloorSwitch : Switch {

	// Define new event type (i.e. we want the function to be called to expect one argument with the boolean type)
	public delegate void SwitchEvent ( bool is_turned_on );

	// Store the reference to the function to call with an argument when an interaction with the player occurs
	protected SwitchEvent on_toggled_callback_with_arg;

	// Allow external objects (such as a controller) to tell which function to call with an argument when an interaction with the player occurs
	public void on_toggled ( SwitchEvent callback ) { on_toggled_callback_with_arg = callback; }


	// Store switch state
	public bool turnedOn = false;

	public override void self_toggled_by ( MainPlayerController player ) {

		// Inherit the parent behavior
		base.self_toggled_by( player );

		// Change the switch state
		turnedOn = !turnedOn;

		// Call the referenced of the stored function with the switch state
		if ( on_toggled_callback_with_arg != null ) on_toggled_callback_with_arg( turnedOn );
	}
}

From now, any object can tell to an instance of the FloorSwitch :

“Hey my_floot_switch ! Once you are toggled execute my_method( bool switch_state ) with your state as argument !” by calling my_floot_switch.on_toggled( my_method ); !

N.B. : To tell “Hey my_floot_switch ! Once you are toggled do nothing !” you can simply call my_floot_switch.on_toggled( null );

Implement a controller

Now as we want our FloorSwitch to toggle the sate of our Fence we need to create a controller handling the behavior of this scenario.

As usual, let’s start by creating an abstract class representing all common behavior of all controllers : Controller.cs

using UnityEngine;

public abstract class Controller : MonoBehaviour {}

And finally we can create our SimpleScenarioController.cs. In order to illustrate the limitations of multiple inputs without callbacks, our controller won’t listen for one floor switch but two of them controlling each one a given fence !

using UnityEngine;

public class SimpleScenarioController : Controller {

	[Header( "Contolled Items" )]
	public Fence fenceA;
	public Fence fenceB;

	[Header( "Inputs" )]
	public FloorSwitch floorSwitchA;
	public FloorSwitch floorSwitchB;

	void Start () {
		floorSwitchA.on_toggled( on_swich_a_toggled );
		floorSwitchB.on_toggled( on_swich_b_toggled );
	}

	public void on_swich_a_toggled( bool switch_state ) {
		if ( switch_state ) fenceA.open();
		else fenceA.close();
	}

	public void on_swich_b_toggled ( bool switch_state ) {
		if ( switch_state ) fenceB.open();
		else fenceB.close();
	}

}

N.B. : We can also use anonymous functions and replace

void Start () {
        floorSwitchA.on_toggled( on_swich_a_toggled );
        floorSwitchB.on_toggled( on_swich_b_toggled );
}

public void on_swich_a_toggled( bool switch_state ) {
        if ( switch_state ) fenceA.open();
        else fenceA.close();
}

public void on_swich_b_toggled ( bool switch_state ) {
        if ( switch_state ) fenceB.open();
        else fenceB.close();
}

by :

void Start () {
        floorSwitchA.on_toggled( ( switch_state ) => { if ( switch_state ) fenceA.open(); else fenceA.close();} );
        floorSwitchB.on_toggled( ( switch_state ) => { if ( switch_state ) fenceB.open(); else fenceB.close();} );
}

Place all these items in the scene

The first thing to do is to create the prefab of the items we want to place on the scene (i.e. the Fence and the FloorSwitch).

Create Fence prefab

We can create a new cube representing a fence :

Create a cube as a fence

Then we can drag this cube to the asset folder to convert it as a prefab :

Drag fence to convert it into a prefab

Now we can open this prefab to edit it’s content :

Open fence prefab

We can change it’s shape and add the Fence script :

Add fence script

And we can save the prefab with Ctrl+S and leave its edition :

Leave fence prefab edition

Create FloorSwitch prefab

We can create a new cube representing the floor switch :

Create a cube as a floor switch

Then we can drag this cube to the asset folder to convert it as a prefab :

Drag floor switch to convert it into a prefab

Now we can open this prefab to edit it’s content :

Open floor switch prefab

We can change it’s shape and set the collider as a Trigger :

Set floor switch collider as trigger

Here we must increase the vertical size of the collider to ensure that the contact will be detected by Unity :

Increase floor switch collider size

And now we can attach it the FloorSwitch script :

Attach floor switch script

And finally leave the prefab’s edition :

Leave the floor switch edition

Place the two fences and floor switches

We can start by moving the fence in the scene :

Move fence

Now we can move the floor switch :

Move floor switch

And duplicate those items :

Duplicate items

We can shift the position to the right :

Shift object positions

Placing the controller

After renaming duplicates we can create a new empty GameObject in the scene :

Create empty GameObject

And attach it the SimpleScenarioController :

Attach the SimpleScenarioController script

Then we can start to link items to the controller :

Open the window to select the GameObject to link

And select the right item :

Select the right item in the list

And iterate over all fields :

Final links established

N.B. : Still in order to reduce the amount of information in this hands-on we didn’t structured our scene. As a result it might be difficult to have a clear view of a large scene.

We can easily fix that by regrouping items working together into empty GameObjects used as a shell :

Well structured scene

Build

Now you can build your application (Ctrl + B) and you should obtain such kind of result :

Commit

If the project works as expected we can commit the project and merge the development branch add_callback_example into the master one :

# Commit
git add .
git commit -m "Add an example of a scenario controller based on callbacks"

# Merge
git checkout master
git merge add_callback_example --no-ff
git tag add_simple_callback_scenario -a -m "Integration of a simple scenario based on callbacks"

# Push
git push --all
git push --tags

Please feel free to use the forum if you have a question or want to give your feedback on this hands-on !