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 :
- BasicGraspUpgrade.cs
- CollectibleItem.cs
- HandController.cs
- InteractiveItem.cs
- MainPlayerController.cs
- ObjectAnchor.cs
- Upgrade.cs
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
:
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 bool
s 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…)
We can move the sphere to make sure we won’t collide with it directly when the game start :
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 :
And now we can add it our newly created 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 :
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 :
And edit it’s color :
Create the upgrade item
We can create a new sphere representing the new purple upgrade :
Then we can move it, set it’s collider to Trigger, drag it the purple material and attach it the new upgrade PurpleUpgrade :
Creating the new special item to interact with
Now we can create our special purple cube :
Then move it, scale it, drag the purple material on it and finally, let’s attach it the new PurpleObject anchor :
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 :
- BasicGraspUpgrade.cs
- CollectibleItem.cs
- Controller.cs
- Fence.cs
- FloorSwitch.cs
- HandController.cs
- InteractiveItem.cs
- MainPlayerController.cs
- InteractiveItem.cs
- MainPlayerController.cs
- ObjectAnchor.cs
- PurpleObject.cs
- PurpleUpgrade.cs
- SimpleScenarioController.cs
- Switch.cs
- Upgrade.cs
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 :
Then we can drag this cube to the asset folder to convert it as a prefab :
Now we can open this prefab to edit it’s content :
We can change it’s shape and add the Fence script :
And we can save the prefab with Ctrl+S and leave its edition :
Create FloorSwitch prefab
We can create a new cube representing the floor switch :
Then we can drag this cube to the asset folder to convert it as a prefab :
Now we can open this prefab to edit it’s content :
We can change it’s shape and set the collider as a Trigger :
Here we must increase the vertical size of the collider to ensure that the contact will be detected by Unity :
And now we can attach it the FloorSwitch script :
And finally leave the prefab’s edition :
Place the two fences and floor switches
We can start by moving the fence in the scene :
Now we can move the floor switch :
And duplicate those items :
We can shift the position to the right :
Placing the controller
After renaming duplicates we can create a new empty GameObject in the scene :
And attach it the SimpleScenarioController :
Then we can start to link items to the controller :
And select the right item :
And iterate over all fields :
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 :
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 !