In this example, the scripting functionality within DeepAR will be explained by creating a ball that bounces off the user's head. Callbacks onStart, onUpdate and onCollisionEnter will be showcased, alongside some animation features.

The steps to recreate this concrete example are given in the rest of the article. The given property values are all exemplary and can be adjusted if some value change doesn't result in the expected behaviour.

Step 0.a

Open the scripting_football_start effect containing already set up object hierarchy and corresponding physics mechanics given in the zip that is located at the bottom of the article. The file should be opened as an already existing effect using FileOpen effect or Cmd+O keyboard shortcut.

Step 0.b - Setting up the animation controller

When the ball collides with the user's head, a sound effect should be played and such behaviour can be achieved through the animation controller (refer to further explanation about animations here) accessed through AssetsAnimations state editor. After selecting the first RootNode in the controller as the chosen node to animate, a new state should be added and named, for example, ball_bounce. In the sound area, the desired track should be selected - in this case the ball_bounce.mp3 audio track and the Duration property will implicitly change to the length of the track after making the selection. A new trigger for this new state should be added - making it an on_end trigger with idle being the next state.

The idle state should be also adjusted since a new state was added. A new trigger should be added and given a custom name. That name will later be used in the script in order to fire that specific trigger - here the given trigger name corresponds to the state name the trigger should switch to from the idle state, ball_bounce. Therefore, the next state should be also set to ball_bounce.

Step 1 - initialisation

In a new JavaScript file, create variables ball and head that will contain the respective nodes, as well as the prevFaceVisible variable, which will hold the information whether the user's face was visible in the previous frame.

var ball, head;
var counter = 0;
var prevFaceVisible;

Step 2 - onStart

The onStart callback is called only once, immediately after the effect is loaded.

In this function, the ball and head nodes are retrieved from the hierarchy, located starting from the root node.

ball = Node.root.getChild('RootNode').getChild('Hedra2');
head = Node.root.getChild('HEADCOL');

Information about the visibility of the user's face is retrieved using

prevFaceVisible = Context.isFaceVisible();

and if the face is not visible, then the ball should be disabled, otherwise it should be reset to the default position from which it falls down.

if (prevFaceVisible) 
resetBall();
else
ball.enabled = false;

The reset action is extracted in a separate function, since it will be reused multiple times in the script. The ball's position is determined by the user's head position, but with z = 0, achieved by subtracting the z value of the head position from itself.

function resetBall() {
ball.position = head.position.add(new Vec3(0, -head.position.z, 0));
}

Step 3 - onUpdate

The onUpdate callback is called on every frame update and checks whether the face visibility status changed in comparison to the previous frame. If the face is visible after not being visible previously, the ball node gets reenabled and is reset to the default position. If it is the case that the face disappeared after being visible in the previous frame, the ball gets disabled.

var faceVisible = Context.isFaceVisible();
if (faceVisible && !prevFaceVisible) {
ball.enabled = true;
resetBall();
} else if (prevFaceVisible)
ball.enabled = false;

No matter the previously discussed visibility combinations, the ball's position gets checked - if the ball went out of the boundaries determined through the renderResolution property of the context the effect is currently being run in, with a margin error allowed (10%-500%, depending on the boundary). If the ball isn't located in the defined boundaries in this given frame, then the ball's position is reset.

if ((ball.positionOnScreen.x < -0.1 * Context.renderResolution.x) || (ball.positionOnScreen.x > 1.1 * Context.renderResolution.x) || (ball.positionOnScreen.y < -5 * Context.renderResolution.y) || (ball.positionOnScreen.y > 1.1 * Context.renderResolution.y))
resetBall();

Lastly, the current information about the visibility of the user's face is stored in the prevFaceVisible to be used for the next call of the onUpdate function.

prevFaceVisible = faceVisible;

Step 4 - onCollisionEnter

The onCollisionEnter callback is called when two nodes with physical characteristics, in this case the ball and the head, start colliding. A local variable collided is used to determine whether the correct objects collided - assigned a truth value if the pair of the collided nodes is the ball and the head node, no matter the order of collision (unordered pair).

var collided = false;

if (firstNode.equals(ball))
collided = secondNode.equals(head);
else if (firstNode.equals(head))
collided = secondNode.equals(ball);

If the correct pair collided, then the ball_bounce trigger described in Step 0 gets fired, which will then play the bounce sound effect.

if (collided) {
counter++;
Utility.fireTrigger('ball_bounce');
}

Step 5 - loading the finished script

The Script component needed to load the script can be added by selecting the root node RootNode and choosing it through Add ComponentScript.

The finished script can opened by choosing Choose Script File... option within the component and it should be located where it was previously stored on the computer.

The final effect can also be obtained by opening the scripting_football_finished file as an already existing effect using FileOpen effect or Cmd+O keyboard shortcut in the Studio.

Download the baseline and the final effect

Did this answer your question?