WIP 09 | Enabling Material Transport through Parenting & Unparenting
Goal: Learn to use parenting to enable material transport
Video Tutorial: https://www.youtube.com/watch?v=ypeAerQaOiY
In previous tutorials, we learned how to move an Actor using a Motion Tensor and how Actors can block each other due to their size. But for many simulations, transportation is required. This tutorial describes how to transport the Material Actor by using the Transport Actor.
Within the hierarchy of the scene, all simulation objects are children of the DES Controller. This means that the DES Controller is dominant over the simulation objects. A Cue must always be the child of a Spline and must always be positioned directly on the Spline. We can make a Material Actor the child of a carrier Actor so that the Material will move together with the carrier Actor.
In this tutorial we will use a scene containing a DES Controller, two Actors, one Instructor and a Spline with two Cues.
TransportActor script
The Transport Actor object uses the TransportActor script. This is a similar script as in our first tutorials, where a Motion Tensor is created from within the inspector. A new Load
property was added, which the Instructor will use to verify whether the transporter is carrying a load or not. Because it is a public variable, other scripts have access to the variable.
#if PRESPECTIVE_SOURCE || ALPHA
using System.Collections.Generic;
using u040.prespective.prescripted.des;
using u040.prespective.prescripted.des.activities.motiontensor;
using u040.prespective.prescripted.des.events;
using u040.prespective.prescripted.des.participant.actor;
using u040.prespective.prescripted.des.viewmodel;
namespace u040.prespective.tutorials.desbasics
{
/// <summary>
/// Demo script to show how to parent one Actor under another
/// </summary>
public class TransportActor : DESActor
{
/// <summary>
/// This property allows to create a Motion Tensor on an Actor from within the inspector
/// </summary>
public MotionTensorViewModel<MotionTensorComponentViewModel> FieldForCreatingMotionTensorFromInspector;
/// <summary>
/// The DESActor assigned as load for this Actor
/// </summary>
public DESActor Load;
/// <summary>
/// This method is called a single when the simulation starts
/// </summary>
/// <param name="_resultingEvents">container for the events that are calculated in this frame (they are invoked after the updates on all simulated objects are completed)</param>
/// <param name="_resultRecords">container for the records that are created in this frame (they are processed for events after the updates on all simulated objects are completed)</param>
public override void OnSimulationStart(ref List<DESEvent> _resultingEvents, ref List<DESRecord> _resultRecords)
{
// Invoking the TryGenerateMotionTensor method will actually create an activity based on the settings you input in this Actor's inspector
// If any critical parameters aren't set when invoking this method, it will log an error in the Unity Editor Console, and return false
if (FieldForCreatingMotionTensorFromInspector.TryGenerateMotionTensor(this, out MotionTensor motionTensorInstance))
{
// After succesfull creation we must still add this activity to start it
this.AddActivity(motionTensorInstance);
}
// Run all inherrited code for this method
base.OnSimulationStart(ref _resultingEvents, ref _resultRecords);
}
}
}
#endif
MaterialActor script
The material Actor within the scene contains an empty script, just defining the object as a DES Actor.
MaterialLoadingInstructor script
The following InstructorLoadingMaterial script is described below.
#if PRESPECTIVE_SOURCE || ALPHA
using u040.prespective.prescripted.des.activities.motiontensor;
using u040.prespective.prescripted.des.instructors;
using u040.prespective.prescripted.des.participant.actor;
using u040.prespective.prescripted.des.participant.cue;
using UnityEngine;
namespace u040.prespective.tutorials.desbasics
{
/// <summary>
/// Demo script to show how to parent one Actor under another
/// </summary>
public class MaterialLoadingInstructor : DESInstructor
{
/// <summary>
/// This field will allow you to assign the Material Actor from within the inspector
/// The defined parameter is a global parameter, meaning it can be called within any function
/// </summary>
public MaterialActor Material;
/// <summary>
/// Load or unload the MaterialActor from the intersecting Actor
/// </summary>
/// <param name="_actor">The intersecting Actor</param>
/// <param name="_cue">The intersecting Cue</param>
/// <param name="_intersectionEvent">Detailed information about the intersection</param>
/// <returns>Whether this Instructors' response was breaking, thus requiring the simulation to recalculate behavior in this simulation frame from this point in time in the simulation</returns>
public override bool ApplyInstruction(ADESActor _actor, ADESCue _cue, ActorCueIntersectionEvent _intersectionEvent)
{
// We only want the center intersection to trigger an event
if (_intersectionEvent.IntersectionType != ActorCueIntersectionEvent.CueIntersectionType.Center)
{
return false;
}
// This instruction can only be applied if the intersecting Actor is a Transport Actor
if (!(_actor is TransportActor transporterActor))
{
return false;
}
// If the Transporter Actor currently has no load, assign the Material Actor as a load and parent it under the Transporter Actor
if (transporterActor.Load == null)
{
Debug.Log("Transporter Actor currently has no load. Loading Material Actor");
Material.ChangeTransformParent(_intersectionEvent.EventTime, SimulationController.FrameTime, transporterActor);
transporterActor.Load = Material;
}
// If the Transporter Actor currently has a load, unparent its current load and unassign it
else
{
Debug.Log("Transporter Actor currently has a load. Unloading Material Actor");
Material.ChangeTransformParent(_intersectionEvent.EventTime, SimulationController.FrameTime, null);
transporterActor.Load = null;
}
// Since we changed an Actor, this is a breaking event
return true;
}
}
}
#endif
In the script, the center intersection event is used to trigger the method. The instruction can only be applied iIf the intersecting Actor is a Transport Actor.
If the Transport Actor has a Load during the intersection, the Material Actor it currently had loaded is unloaded. It is unparented and the Load property is set to null.
If the Transport Actor does not have a Load during the intersection, the prepared Material Actor is loaded onto the Transport Actor. It is parented under the Transport Actor and set as the Load.
Since the hierarchy was changed for Actors, this is a breaking event.
Play Mode
When we press play, the Transport Actor will move along the Spline and when it reaches the first Cue, it becomes the parent of the Material Actor. While it is the parent, it is dominant over the Material Actor, which will follow the motion of its parent. When it reaches the second Cue, the Material Actor will no longer be the child of the Transporter Actor and thus will not continue to follow its motion.
Important: When an Actor becomes the child of another Actor, it will not automatically translate toward its parent. So if the Material Actor is not be at the same position as the Transport Actor while becoming the child, it will still follow the motion of its parent, but from its own starting position.
Prespective Documentation