WIP 03 | Creating Timed Events to affect Motion
Goal: Use an Instructor to stop an Actor at a cue, wait a certain time using a timer and continue moving.
Video Tutorial: https://www.youtube.com/watch?v=ylYPKgnJVXg
In the previous tutorial, we used a Cue to log information in the console using an Instructor. We can also use these Instructor to trigger events.
The same scene is used as in ‘2 – Responding To Actor Motion Using Cues’ but with a different Instructor.
InstructorToMakeAnActorWaitAtTheCues script
#if PRESPECTIVE_SOURCE || ALPHA
using System;
using System.Collections.Generic;
using u040.prespective.prescripted.des.activities.motiontensor;
using u040.prespective.prescripted.des.activities.timer;
using u040.prespective.prescripted.des.instructors;
using u040.prespective.prescripted.des.participant.actor;
using u040.prespective.prescripted.des.participant.cue;
using u040.prespective.utility.collectionmanagement;
using UnityEngine;
namespace u040.prespective.tutorials.desbasics
{
/// <summary>
/// Demo script to show how an Instructor can respond to events related to an Actors' motion, actually affect the motion of the Actor by forcing its velocity and to introduce using timers.
/// </summary>
public class InstructorToMakeAnActorWaitAtTheCues : DESInstructor
{
/// <summary>
/// When an Instructor has Cues linked to it, all intersection events with these Cues are sent to this method.
/// The intersecting Actor can then receive instruction from this Instructor as a result of that intersection.
/// This Instructor stalls Actors for 1 second when intersecting with their center point.
/// </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)
{
// Only stall the Actor when it is intersection with its center. Otherwise, ignore the intersection.
if (_intersectionEvent.IntersectionType != ActorCueIntersectionEvent.CueIntersectionType.Center)
{
return false;
}
// Try to stop this Actor from moving
makeAnActorStopAllMotion(_actor, _intersectionEvent.EventTime, out double[] bufferedVelocities);
Debug.Log("Instructor " + this.name + ": The Actor " + _actor.name + " passed the Cue " + _cue.name + " at simulated time " + (SimulationController.TotalSimTimePassed + _intersectionEvent.EventTime) + "s and is now stalled for 1 second.\n" +
"This Actor was previously moving at " + TypedList.ToString(bufferedVelocities) + "m/s");
// Create a timer using the QuickstartTimer in the DES TimerUtility. This method creates, adds and immediately starts a timer on the Actor.
TimerUtility.QuickStartTimer(
_actor, // Register the timer on the specific Actor.
"WaitTimerFor_" + _actor.name, // The name of the timer. It is good practice to give timers a proper name for debugging purposes.
1d, // The duration for the timer, in this case 1 second.
_frameTimePassedAfterTimerCompletion => // The previously created action, invoked as soon as the timer has been completed.
{
// After the timer had completed, start moving the Actor again at the same velocity it had before stopping
Debug.Log("Instructor " + this.name + ": The Actor " + _actor.name + " has completed waiting at simulated time " + (SimulationController.TotalSimTimePassed + _frameTimePassedAfterTimerCompletion) + "s and will now resume moving.\n");
makeAnActorResumeAllMotion(_actor, bufferedVelocities, _frameTimePassedAfterTimerCompletion);
return true;
},
_intersectionEvent.EventTime // The frame time this timer should start at.
);
// The Actor has succefully stalled and waits until it resumes movement. This is always breaking behavior since the future for this Actor has now changed.
return true;
}
/// <summary>
/// Calling this method stops all motions on an Actor, and outputs the previous velocities it had.
/// </summary>
/// <param name="_actor">The Actor to stop all motion for</param>
/// <param name="_passedFrameTime">The time passed in the current frame</param>
/// <param name="_bufferedVelocities">The Actors velocities before stopping all motion</param>
/// <returns></returns>
private void makeAnActorStopAllMotion(ADESActor _actor, double _passedFrameTime, out double[] _bufferedVelocities)
{
// Retrieve all MotionTensor Activities on this Actor
List<MotionTensor> tensors = _actor.GetActivitiesByType<MotionTensor>();
// Create a buffer of the velocities for each of these activities
_bufferedVelocities = new double[tensors.Count];
// Loop through all tensor activities, buffer their current velocity, so we can resume them later, and set the current velocity to 0.
for (int i = 0; i < tensors.Count; i++)
{
// Make sure we are able to get the velocity on the first MotionTensor
if (tensors[i].TensorComponents.Count == 0 || !tensors[i].TensorComponents[0].GetVelocityAtFrameTimePassed(SimulationController, _passedFrameTime, out double currentVelocity))
{
continue;
}
// Buffer the velocity
_bufferedVelocities[i] = currentVelocity;
// Force the velocity to be 0m/s
tensors[i].ForceVelocity(0d, SimulationController, _passedFrameTime);
}
}
/// <summary>
/// Calling this method restores the velocity of this Actor to the values we set to 0 in the method 'makeAnActorStopAllMotion'
/// </summary>
/// <param name="_actor">the Actor affected</param>
/// <param name="_previousVelocities">the velocity values we had before stopping them</param>
/// <param name="_passedFrameTime">the passed simulation frametime at the time we want to start this Actor</param>
/// <returns></returns>
private void makeAnActorResumeAllMotion(ADESActor _actor, double[] _bufferedVelocities, double _passedFrameTime)
{
// Retrieve and loop through all MotionTensorActivities on this Actor and restore their velocity to the value it had before stopping
List<MotionTensor> tensors = _actor.GetActivitiesByType<MotionTensor>();
for (int i = 0; i < tensors.Count; i++)
{
tensors[i].ForceVelocity(_bufferedVelocities[i], SimulationController, _passedFrameTime);
}
}
}
}
#endif
public class InstructorToMakeAnActorWaitAtTheCues : DESInstructor
The Instructor is defined as a DES Instructor making use of functionality belonging to that class.
public override bool ApplyInstruction(ADESActor _actor, ADESCue _cue, ActorCueIntersectionEvent _intersectionEvent)
{
// Only stall the Actor when it is intersection with its center. Otherwise, ignore the intersection.
if (_intersectionEvent.IntersectionType != ActorCueIntersectionEvent.CueIntersectionType.Center)
{
return false;
}
...
The ApplyInstruction is the main method and it overrides and adds its own instructions to the ApplyInstruction in the base class.
This if-statement makes sure that only Center Intersection events apply the instruction. Other trigger events (Enter, Exit etc.) returns false and execution of ApplyInstruction stops.
The makeAnActorStopAllMotion method is called first. This will make sure that the velocity of all the Actors' Motion Tensors is set to 0m/s. Their previous values are returned by the method. These are important in order to resume motion after the Timer has completed.
A Timer is then started. The Timer runs for 1 second, after which it will run an action we give with it as a parameter. In this case we tell it to resume all motion for the Actor that has just been stopped.
Important: Always use a double value for the Timer duration for simulation precision reasons. Using single values will result in rounding issues and lead to imprecise calculations.
We immediately return true after creating the Timer. This is because we have stopped all Motion for the Actor, which is a breaking event.
In order to stop all Motion on an Actor, you need to get all the MotionTensors on the Actor.
For each MotionTensor on the Actor, first we buffer the original velocity in order to resume it later. We then force that velocity to be 0m/s.
In order to resume all Motion on an Actor, you need to once again get all the Motion Tensors on the Actor.
For each Motion Tensor on the Actor force the velocity to be whatever the original value was.
Play Mode
When we press play in the editor, we see that the Actor starts moving over the Spline. When it crosses one of the Cue’s center points, it will stall for one second (velocity is 0) and then resume motion at its original velocity.
Prespective Documentation