Creating Custom Behaviors

Behaviors are a versatile way to add interactivity to your experience. They’re often used to:

  • Listen to the events emitted by a node and then perform an action, such as playing an animation or calling a function in a context.
  • Change the behavior or properties of the node they're attached to.

Creating a new Custom Behavior

Custom Behaviors extend behaviors by allowing you to customize the functionality to your project needs. They also allow you to affect more than one node at a time.

Creating a new Custom Behavior Component

To add a new custom behavior to a specific node in your Mattercraft project:

  1. Click on a node in the Hierarchy
  2. Click the + (plus) icon button on its Behaviors Panel and Click + New Custom Behavior
  3. Type a name for the behavior then click Create
  4. The Project Panel will now have this behavior script file with the skeleton of a brand new behavior

You can also add a new custom behavior from the Project Panel and then specify which nodes you want it to affect.

Adding a Custom Behavior to your project

Understanding Custom Behavior Structure

Let’s look at the basic structure of a behavior:

import { Component, Behavior, BehaviorConstructorProps, ContextManager, registerBehaviorRunAtDesignTime } from "@zcomponent/core";
import { Box as Box } from "@zcomponent/three/lib/components/meshes/Box";
import { default as Scene} from "./Scene.zcomp";

interface ConstructionProps {
    // Add any constructor props you'd like for your behavior here
}

/** @zbehavior */
export class MyBehavior extends Behavior<Box> {

    protected zcomponent = this.getZComponentInstance(Scene);

    constructor(contextManager: ContextManager, instance: Box, protected constructorProps: ConstructionProps) {
        super(contextManager, instance);

        // Behavior events/script goes here

    }

    dispose() {
        // Clean up any resources
        // ...
        return super.dispose();
    }
}

// Uncomment below to run this behavior at design time
// registerBehaviorRunAtDesignTime(MyBehavior);

Behaviors are JavaScript or TypeScript classes that extend the Behavior base class provided by Mattercraft. When your experience runs, an instance of the class is constructed for each node that it’s attached to in the Mattercraft 3D editor.

See Mattercraft's full API documentation here.

Just above the class definition, the @zbehavior annotation tells the Mattercraft editor that you’d like this behavior to appear in the menu accessed by the + (plus) icon within the Behaviors Panel + (plus) menu and thus make it easy to add to other nodes in your experience.

Using Custom Behaviors

Creating an Instance

When your behavior runs, it’s passed a reference to the node in the Hierarchy that it’s attached to.

In the example above, we created our behavior and attached it directly to a Box node in our 3D scene. As a result, the instance variable in our MyBehavior class constructor points to a Box component.

A Custom Behavior attached to a Node

With the instance variable, we can access the node to change its properties or to call any functions. For example, to change the position of our box, we can set the value of its position prop:

/** @zbehavior */
export class MyBehavior extends Behavior<Box> {
    constructor(contextManager: ContextManager, instance: Box, protected constructorProps: ConstructionProps) {
        super(contextManager, instance);

        // Change the position of this node (this.instance) by
        // programatically entering it's x, y and z position values
        this.instance.position.value = [1, 2, 3];

    }
}

It’s also possible to access the underlying three.js object that the component exposes (in this case, a THREE.Mesh), using the .element value:

/** @zbehavior */
export class MyBehavior extends Behavior<Box> {
    constructor(contextManager: ContextManager, instance: Box, protected constructorProps: ConstructionProps) {
        super(contextManager, instance);

        // Targeting a THREE.Mesh object with
        // this.instance.element
        this.instance.element.visible =  true;

    }
}

Events

Many components emit events, such as when a user taps or clicks on them, and behaviors can listen for those events. To do so, you can use the behavior’s register function, like this:

/** @zbehavior */
export class MyBehavior extends Behavior<Box> {
    constructor(contextManager: ContextManager, instance: Box, protected constructorProps: ConstructionProps) {
        super(contextManager, instance);

        // this.register lets Mattercraft know to listen to an event,
        // in this case, an 'onClick' event which is fired
        // from the node (instance) this behavior is assigned to
        this.register(this.instance.onClick, evt => {

            // Write something to the console when this node
            // was clicked or tapped on
            console.log('The box was clicked!');

        });
    }
}

The register function makes sure that your handler function is called every time the event fires and also ensures that your function is correctly disposed of when the behavior is destroyed.

Accessing other Nodes in the Scene

Sometimes, in addition to accessing the node instance that a behavior is attached to, you need to access the other elements in the scene, such as other Nodes, States or Timelines.

For example, you may have a button that you want to use to trigger animations, states, or any other action on multiple nodes.

The this.zcomponent variable lets you accomplish this. To reference a different node, you can use its nodes property, like this:

/** @zbehavior */
export class MyBehavior extends Behavior<Box> {
    protected zcomponent = this.getZComponentInstance(Scene);

    constructor(contextManager: ContextManager, instance: Box, protected constructorProps: ConstructionProps) {
        super(contextManager, instance);

        // Make the PerspectiveCamera the currently active camera in the scene
        // by searching for it with this.zcomponent.nodes
        this.zcomponent.nodes.PerspectiveCamera.activate();

    }
}

Or to play a timeline from the Animations Panel:

/** @zbehavior */
export class MyBehavior extends Behavior<Box> {
    protected zcomponent = this.getZComponentInstance(Scene);

    constructor(contextManager: ContextManager, instance: Box, protected constructorProps: ConstructionProps) {
        super(contextManager, instance);

        // Play Timeline_1 from Layer_1
        // by searching for it with this.zcomponent.animation
        this.zcomponent.animation.layers.Layer_1.clips.Timeline_1.play();

    }
}

In both cases, Mattercraft’s built-in autocomplete should help you find the node or animation you’re looking for in your scene.

If you have your script file open side-by-side with your scene, you can also drag nodes, states and timelines from the scene tab into your script to automatically create a reference to that object.

Properties

Just as with custom components, you can add properties to your behaviors that can be controlled from the 3D editor.

For more information about controlling properties, see our dedicated Properties article.

The Frame Loop

When 3D experiences run in an end user’s browser, the 3D engine will draw, or ‘render’, the 3D scene up to 60 times every second. You may wish to run a script before each render frame, perhaps to implement your custom animation, or to perform any of your node transformations.

To do so, first, make sure you import this function at the top of your custom behavior using:

import {useOnBeforeRender } from "@zcomponent/core"

You can then register a handler to the event returned by the useOnBeforeRender(contextManager) function.

For a full example, see our dedicated The Frame Loop article.

Documenting and Customizing Your Behaviors

To make it easy to use your behaviors in 3D scenes, it’s possible to add documentation in the script comment above your behavior’s class definition. Documentation is written using the popular JSDoc syntax.

/**
* This is an example behavior to demonstrate how to write custom behaviors.
* Documentation can be added to components and behaviors like this, using JSDoc syntax.
* @zicon favorite
* @zbehavior
*/
export class MyBehavior extends Behavior<Box> {
    // ...
}

In addition, with the @zicon annotation, you can indicate what icon to show in the Behaviors Panel. Any of the icons from Google’s Material Icon Set can be used - just lowercase the name and replace any spaces with an underscore.

For example, the Google Material icon name for Check Box will become @zicon check_box in Mattercraft.

Here’s how the behavior documentation and the custom icon look in the 3D editor:

Documentation tooltip on a Custom Behavior within the Behaviors Panel

zapcode branded_zapcode i