📰Getting Started | ⭐**Updates |** 📝 Guides | 🔢 API | ❓FAQ
Web Editor Basics
HoloLens
Mobile
Meta Quest
Chapter Series Documentation
Assets
Scripting
Enklu Embedded
API Reference
Release Notes
FAQ
Contact
The Elements API is a powerful abstraction that allows users to manipulate any property on any element through a handful of simple methods. This is important because these few methods allow for easy networking or persistent state.
To change these properties you will need to reference an object. The most common form to reference an object would be:
var a = this;
You can then use this reference to change other properties.
var a =this;
a.visible = true; // set object visible to on
Many functions use IDs:
var a =this.id; // Read-only. Gets the element's ID
Any created asset will be a ContentWidget by default. Other possible types include: LightWidget, KinectWidget, ButtonWidget, and TextWidget. Some of these types are only created through scripts.
var a =this.type; //Read-only. Get's element type.
This
is a helpful keyword you can use to return most information you will need about your current object.
this = self;
function enter() {
log.info(this.transform.scale);
log.info(this.transform.rotation);
log.info(this.transform.position);
log.info("This element is named: "+ this.name);
}
If your element is an asset (such as an fbx or unity prefab) that contains children (such as arms or legs) you can reference them if you know the name of them.
this = self;
function enter() {
//reference child material
var RightArm = self.findMaterialInsidePrefabByTransformName('RightArm');
//reference child transform
var RightArmTransform = self.findTransformInsidePrefabByName('RightArm');
//change child material
RightArm.setColor('_Color', col(0.5, 0.5, 0, 1));
}
Any element can have another element as a child. These child elements can also have other elements as children, forming a kind of directed graph. This is visualized by the tree component in the web editor.
An element's children are accessible via the readonly children field.
var children = element.children; //Read-only.
for (var i = 0, len = children.length; i < len; i++) {
var child = children[i];
}
Example of turning off a particular child:
var myChildren =this.children;
for(var i =0; i< myChildren.length; i++){
myChildren[2].visible = false;
}
Child elements may be added and removed:
a.children; // []
a.addChild(b); // [b]
a.removeChild(b); // []
Adding an element as a child to one element will remove it from another:
a.addChild(b); // a.children = [b]
c.addChild(b); // a.children = [], c.children = [b]
Adding an element to its parent will reorder the children:
a.addChild(a); // [a]
a.addChild(c); // [a, c]
a.addChild(a); // [c, a]
Relationships can be tested explicitly or with helpers:
b.parent === a; // True
b.isChildOf(a); // True
You can also grab parent elements:
var myParent =this.parent; //This goes up the hierarchy by one parent
var grandParent =this.parent.parent; //This goes up the hierarchy two parents
You can create vine objects and add them as children. As long as the vine is valid, it will accept any argument.
This example creates a text child that will stick to the middle of the screen:
var myVine;
var self =this;
var text = "hi";
var textSize = 500;
function enter() {
myVine = app.elements.createFromVine(self,
'<Screen><Text id="myVineID" label="' + text +
'" fontSize= 300"' +
'" alignment="' + "MidCenter" +
'"/></Screen>'
);
}
/**
* Called before the script is removed or rebuilt.
*/
function exit() {
app.elements.destroy(myVine); //destroy child
}
module.exports = {
enter: enter,
exit: exit
};
Elements can easily be destroyed:
// Destroys element and all children
element.destroy();
Elements are designed to have a flexible method of managing, composing, and synchronizing state. To that end, each element has a schema object that stores all of the element's state. Schema are a grab bag of values, each of which may be watched for changes. Additionally, schema is composable up the graph—that is, if an element's schema does not contain a specific value, it will look up the graph until the value is found.
Use get and set methods to retrieve properties for primitive types.
// strings
element.schema.setString('foo', 'This is foo.');
element.schema.getString('foo'); // This is foo.
// numbers
element.schema.setNumber('bar', 12);
element.schema.getNumber('bar'); // 12
// bools
element.schema.setBool('fizz', true);
element.schema.getBool('fizz'); // true
Here is an example using schema to get the object name and rename. (Will not last outside playmode and does not change in the inspector.)
var name =this.schema.getString('name'); //get current object name
log.info("my name is " + name); //output current name
this.schema.setString('name','Bob'); //change name
log.info("my name is " +this.schema.getString('name')); //output newly changed name
If an element doesn't already have a value, it returns a value up the graph. In this circumstance, the property of b is bound to the property of a.
a.addChild(b);
a.schema.setNumber('foo', 12);
b.schema.getNumber('foo'); // 12
a.schema.setNumber('foo', 17);
b.schema.getNumber('foo'); // 17
Once the child changes its value, the binding is broken.
b.schema.setNumber('foo', -7);
a.schema.getNumber('foo'); // 17
b.schema.getNumber('foo'); // -7
Values can be taken from an element with explicitly avoiding searching up the graph. A default value for the data type will be set & returned if unprovided.
b.schema.getOwnNumber('foo'); // 0
b.schema.getOwnNumber('foo', 12); // 12
b.schema.getOwnString('bar'); // <empty string>
b.schema.getOwnString('bar', 'flip'); // flip
b.schema.getOwnBool('fizz'); // false
b.schema.getOwnBool('fizz', true); // true
Properties may also be watched for changes.
element.schema.setNumber('foo', -1);
element.schema.watchNumber('foo',function (prev, next) {
log.info(
'Value changed from {0} -> {1}',
prev,
next);
});
element.schema.setNumber('foo', 5); // Value changed from -1 -> 5
Watched properties can be unwatched.
function onChange(prev, next){
log.info('Value changes from {0} -> {1}',
prev,
next);
}
element.schema.setNumber('foo', -1);
element.schema.watchNumber('foo', onChange);
element.schema.setNumber('foo', 2); // Value changes from -1 -> 2
element.schema.unwatchNumber('foo', onChange);
element.schema.setNumber('foo', 2); // <no output>
Properties can be watched explicitly for one change.
element.schema.setNumber('foo', -1);
element.schema.watchNumber('foo',function (prev, next) {
log.info(
'Value changed from {0} -> {1}',
prev,
next);
});
element.schema.setNumber('foo', 5); // Value changed from -1 -> 5
element.schema.setNumber('foo', 8); // <no output>
Copy
Watchers are also called for bound properties.
b.schema.watchNumber('foo',function(prev, next) {
log.info('Changed!');
});
a.schema.setNumber('foo', 12); // Changed!
It can become taxing to iterate the element graph to find the elements you need, particularly as it grows dynamically. For this reason, elements have a built-in query language that can be used to filter and find collections of elements that you need.
Using findOne()
retrieves a child with matching id.
// a <-parent
// -b <- child
var b = a.findOne('b');
You can search for immediate children with the dot (.)
operator.
// a
// -b
// -c
// -d
// -e
// -f
var f = a.findOne('d.e.f');
Sometimes you may not know intermediate children. For a recursive search, use the double dot (..)
operator.
var f = a.findOne('..f');
These operators can be used in combination with each other.
// a
// -b
// ...
// -z
var q = a.findOne('b..f..p.q');
Any element property can be used to filter by using the @
operator.
var big = a.findOne('..(@size==10)');
Here is an example of hardcoding a name search for a child element named childObj1. This is not a recommended method since it's not as flexible as using a variable. It's important to note the search filter @name==
when searching for objects.
function enter() {
var child1 = this.findOne('..(@name==childObj1)');
log.info(child1);
}
Here is an example of searching for a child with a name set in the inspector.
'{[Inspector variable label : variable type]}'
allows this to show in the inspector. You can of course use a regular variable if you don't want to use the inspector. Using a variable is a recommended method for its flexibility.
const child = '{[Find this child:string]}'; //type in child name in inspector
function enter() {
child =this.findOne('..(@name==' + child + ')');
var anotherChild = '';
}
If you would also like to set a default name for the child in the inspector (for example onObject), change the code to: const child = '{[Find this child:string = "onObject"]}';
. This can be an easy way to autofill default settings in the inspector.
Here is an example of searching for an object type, which returns the first child object of that type:
const light = this.findOne('..(@type=LightWidget)');
function enter() {
if (light) {
light.schema.setNumber('intensity', 0.7);
}
}
It's also important to note that when searching for Vines, IDs are defined in the Vine and you won't need to use a filter to search for them.
<?Vine>
<Container id='vineExample'>
</Container>
const findVine = this.findOne('..vineExample'); //look for vine ID
function enter() {
log.info("found "+ findVine);
}
Finally, a collection of matching objects can be retrieved by using find.
var allBig = a.find('b..q.(@size==10)');
Several shortcuts are provided, not least of which is the transform
object. While localPosition
, localRotation
, and localScale
may all be set via the schema system, the transform
object also provides these as raw fields.
// These two are equivalent
element.schema.setVec3('position', vec3(1, 1, 1));
element.transform.position = vec3(1, 1, 1);
// These two are equivalent
element.schema.setVec3('scale', vec3(1, 1, 1));
element.transform.scale = vec3(1, 1, 1);
// These two are equivalent
element.schema.setVec3('rotation', vec3(0, 0, 0)); // Internally stored as Euler values.
element.transform.rotation = q.euler(0, 0, 0);
World space values exist, though they should be used with care. On HoloLens, world space values include any transform changes an Anchor uses to locate its place in a space.
element.transform.worldPosition;
element.transform.worldRotation;
element.transform.worldScale;
Helper functions exist to manage conversions between world & local coordinate systems.
// Transforms a Vec3 from local space to world space
element.transform.localToWorld(vec3(0, 0, 1));
// Transforms a Vec3 from world space to local space
element.transform.worldToLocal(vec3(10, 3, 5));
A transform's local forward direction can be used.
element.transform.forward;
An element can be rotated to look towards a given direction.
element.transform.lookAt(v.up);
Use this function to make an element point at a particular point in space
self.transform.lookAtPoint(vec3(0, 0, 0));
Many elements dispatch events. These events can be listened for using on
and off
functions, which attach a callback to a specific event. It is good practice to attach events on enter
and remove them in exit
.
a.on('activated',function(evt) {
//
});
a.off('activated', onActivated);