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.
To add a new custom behavior to a specific node in your Mattercraft project:
- Click on a node in the Hierarchy
- Click the + (plus) icon button on its Behaviors Panel and Click + New Custom Behavior
- Type a name for the behavior then click Create
- 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.
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.
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: