Creating Custom Components
Custom Components are a type of component within Lens Studio that is created from a script and includes all the resources needed in order for the script to work. This allows Lens Developers to bundle together script and resources in a convenient package.
With Lens Studio 4.34, Lens Developers can now build and create their own Custom Components for use within their Lens Studio projects, share among other Lens Developers and help streamline developing Lenses. The benefits of using Custom Components are that they simplify the usage of a complex set of interdependent resources by allowing them to package their necessary assets like Materials, Textures, or Script Modules inside.
Creating a New Custom Component
To create a new Custom Component, you can right-click any Script in the Resources panel and select Create Custom Component
in the drop down menu. This will replace that Script Asset in this project with the Custom Component asset.
You can do this action immediately from an empty script. Alternatively you can set it up as an ordinary script first.
Edit Custom Component
Once you have created a Custom Component, double-click on the Custom Component to edit it in Script Editor or to view more options in the Custom Component Asset Inspector.
Custom Component can exist in a Lens Studio project in one of two forms:
- As an Custom Component Asset in the Resources panel where it represents a type.
- As a Component added to the SceneObject where it represents an instance of that type.
Please pay attention to both the Asset Inspector and Object Inspector, which displays Custom Component Asset or Object properties depending on what is currently selected, when working with Custom Components. It can either be a Resource or a SceneObject.
It is highly recommend to use Visual Studio Code, also known as VSCode, when working with Scripting in Lens Studio. Please visit the VSCode extension guide to learn how to set up your editor for more convenient scripting.
From this point, you can edit your Custom Component as a regular script.
Custom Component Inputs
You can add inputs for Custom Components in a similar way as you would add regular script inputs. Alternatively, you can left-click the Add New Input button in the Custom Component Asset inspector.
Custom Component inputs values can be set either in Custom Component Asset inspector the SceneObjects inspector:
-
Input values set in the Object Inspector represent values for the current SceneObject.
-
Input Values set in the Custom Component Asset Inspector become default values for this Custom Component. If an input is set up to use a resource, it will get bundled with the Custom Component and becomes a default value for the input.
Setting default values allows you to configure Custom Component in a clean manner when it’s first added. This also makes it possible to reset inputs to default input values at any point.
You may mark some inputs as hidden by clicking on the Eye toggle, which won’t allow you to edit the values in Object Inspector.
For all inputs, you may add hints that are shown on mouse hover.
//@input int propertyName {"hint":"Explaining what propertyName does"}
Custom Component Lifecycle
Custom Components have the same lifecycle as a Script component and can be created or destroyed in runtime. Almost all script events may be used as usual, but there are a couple things you should be mindful of when building Custom Components.
OnAwakeEvent
Unlike Script Components, Custom Components can run in a limited capacity in the Viewport panel. Specifically, everything that happens during onAwake will be visible on Scene Panel.
Use this event for Component creation or instantiation of SceneObjects so they can be visible and selectable in the Viewport panel. It is recommended to make sure any changes are cleaned up in the OnDestroyEvent.
OnEnableEvent and OnDisableEvent
All of the effects a Component performs should be paused when component is disabled and resumed when it’s enabled.
OnDestroyEvent
When the Custom Component is destroyed, all created SceneObjects and Components should also be set to be destroyed. Please be careful that even after destruction, any callbacks you’re still listening for will still persist unless specifically destroyed as well.
You can use the DestructionHelperModule
script to easily handle this for you. It can be found in the ScriptModules section of Asset Library. Import this module to your project and open it to see scripting examples
Custom Component API
You can expose some functions to the Custom Component API:
script.myFunction = myFunction;
You can create custom events in the Component API that allows adding script callbacks. It is preferred that any events your Custom Component emits should be done through the EventModule from the Asset Library. This presents a consistent API to other Lens Developers when sharing your Custom Components.
* // Import event module
* var eventModule = require("./EventModule");
*
* // Event Wrapper
* var myEvent = new eventModule.EventWrapper();
*
* script.myEvent = myEvent
The inputs you define also represent runtime properties of the Custom Component. It is recommended to test that setting them at any time will work as expected.
You can define custom getters and setters with a pattern. For example:
var underlyingIndex = script.faceIndex;
Object.defineProperty(script, 'faceIndex', {
get: function () {
return underlyingIndex;
},
set: function (index) {
underlyingIndex = index;
head.faceIndex = underlyingIndex;
},
});
Each Custom Component has a small description field that editable and viewable in the Asset Inspector. You should provide a description and any instruction required in this field. You should also provide a component’s API usage examples if any are available.
Best practices
Listed below are some of the best practices to keep in mind when building your own Custom Components.
- Any SceneObjects your Component creates should be children of the SceneObject it is attached to.
- This respects the Transform position of the Custom Component.
- It allows that content to be selected and moved in the editor window.
- Do not try to recreate all the necessary fields for an engine Component in your Custom Components inputs, instead ask for a Prefab as input or a Component to copy.
- Remember that you can instantiate Prefabs from within Custom Components which allows you to configure your created hierarchy in the GUI.
- For instance a Button Custom Component shouldn’t try to reproduce the necessary fields from Text for its label.
- Asset input (such as a Material) used by your Custom Component will be shared by all instances of that Custom Component. This means changes to its state, such as a Material parameter, will affect all instances.
- Instead, You can clone the Asset to get an independent version.
- The Custom Component should validate any assumptions about where it is in the Scene hierarchy and emit an error if they’re invalid. Examples would be:
- If the Custom Component needs to be in a valid ScreenTransform hierarchy check that the Component exists and it returns true for
isInScreenHierarchy
. - If the Custom Component is expected to be on the same SceneObject as the Camera, check that a Camera is present.
- If the Custom Component needs to be in a valid ScreenTransform hierarchy check that the Component exists and it returns true for
- If your Custom Component conforms to some interface such as
UIWidget => Button,
override the engine methodisOfType
to also return true for your interface:
var base = script.isOfType;
script.isOfType = function (name) {
return name == 'UIWidget' || name == 'Button' || base(name);
};
- It is preferable that the source code for the Custom Component should be documented using JSDoc annotations to best match coding cadence.
Exporting Custom Components
You can right-click on the Custom Component Asset and select Export
in the drop down menu to export it. You have an option to export as Editable
or Locked
.
Exporting will bundle all referenced resources inside the asset. In case of exporting as Editable
, Lens Developer will be allowed to see and edit Custom Components. You can right-click and select Unpack
in the drop down menu to unpack all assets stored in the bundle.
Versioning
Custom Components have a version number associated with them to help manage updates without breaking existing projects.
- The version installed to your Lens Studio or inside a
Installed Library
can be updated by right clicking the Custom Component and selectingPush Update To Library
. - To update a Custom Component being used in a project, navigate to the SceneObject Inspector and from the context menu, select
Update Custom Component
Installed Library
All installed Custom Components are saved in the following locations by default:
- MacOS:
~/Library/Application Support/Snap/Lens Studio/LibraryComponents
- Windows:
C:/Users/<USER>/AppData/Roaming/Lens Studio/LibraryComponents
You may add your own folders to the Installed Library Locations
by selecting Lens Studio
in the menu bar, located at the top, and selecting Preferences
.
You may add a shared or version controlled folder to manage and share Custom Component assets with other Lens Developers.
Building a Custom Component Walkthrough Example
In the following example, you will learn how to build a Custom Component out of an existing asset in the Asset Library. In this example, we will use the Object Layout asset. This asset creates copies of a SceneObject and arranges them into circular or rectangular patterns.
-
Open a new or existing project in Lens Studio version 4.34.0 or greater.
-
Left-click on the Asset Library button, located above the Objects panel.
-
In the Asset Library Search Bar, type in
Object Layout
. The Object Layout asset should be visible in the Asset Library. -
Left-click on the Install button
-
Navigate to the Resources panel and select the
ObjectsLayout
script. -
Right-click on the script and select
Create Custom Component
in the drop down menu.
Setting name and icon
After the Custom Component is made, you can now set a name and icon to use to represent the Asset.
-
Select the Custom Component and set the name to your liking.
-
You can optionally set an icon in the asset inspector as well.
DestructionHelpersModule
Before adding this Component to Script, you will edit it to satisfy the requirements of the Custom Component Lifecycle.
- Double-click on the Object Layout Custom Component asset to open the Script Editor.
The Current Script does the following:
- Takes an array of SceneObjects as input.
- Creates a set amount of copies of the SceneObject array.
- Arranges them in either a circular or rectangular pattern.
Since Custom Components instantiate some objects, you will need to account for that in OnDestroyEvent
.
- Left-click on the Asset Library button, located above the Objects panel.
- In the Asset Library Search Bar, type in
DestructionHelperModule
. The DestructionHelperModule should be visible in the Asset Library. - Left-click on the Install button
- Add it to your Custom Component.
- At the beginning of the Custom Component code in the Script Editor, add the following:
var DestructionHelper = require('./DestructionHelper');
var manager = new DestructionHelper(script);
With this set, you can now use the functions of DestructionHelper to safely create SceneObjects. Shown below are some of the API calls that can be made with the DestructionHelperModule
* ==== API ====
* @api
*
* safeCallback(function callback): function
* Takes a function that will only be called if the script has not been destroyed.
* Returns a function to be passed to other API
*
* createComponent(SceneObject obj, string type): Component
* Creates a Component of type type on the given obj.
* It will be destroyed when the script is
*
* createSceneObject(SceneObject parent): SceneObject
* Creates a SceneObject with the given parent.
* It will be destroyed when the script is
*
*/
Updating createObjects()
- In the Script Editor, navigate to the
createObjects
function of the Object Layout Custom Component. - Replace the function with the following code:
function createObjects(count) {
var parent = manager.createSceneObject(script.getSceneObject()); //this creates SceneObject that will be safely destroyed
objectCount = script.objectsToSpawn.length;
for (var i = 0; i < count; i++) {
var childIndex = getIndex(i, objectCount);
duplicatedObjects[i] = parent.copyWholeHierarchy(
script.objectsToSpawn[childIndex]
);
duplicatedObjects[i].enabled = true;
}
}
This updated createObjects
function sets a dedicated parent SceneObject for all instantiated SceneObjects that is created through the Destruction Helper API. With this function, you do not need to worry about cleaning them when destroyed.
Adding the ObjectLayout Custom Component to the Scene
The quickest way to add a Custom Component to the Scene can be done with the following step:
- Left-click and drag a Custom Component resource from Resources panel into the Objects panel.
- This will create a new SceneObject with Custom Component attached to it.
You can select the new SceneObject and see its inputs in the Inspector panel.
In order for the Custom Component to work properly, you will need to provide some inputs to the Objects array.
-
In the Objects panel, Left-click the + button and select
Sphere
. -
Set the Sphere SceneObject to the Objects array element input of the Object Layout Custom Component.
Once connected, you should see the effect applied in the Scene Panel.
Changing script inputs will applies changes in the Scene as well
While everything is now working, you will still see the original objects in the center. You can disable them at the beginning and then enable them when the Custom Component is destroyed.
- At the top of the Script in the Script editor declare ‘objectCount’ variable at the top of the script (instead of createObjects function):
var objectCount;
- Modify the
createObjects
function to use the following code:
function createObjects(count) {
var parent = manager.createSceneObject(script.getSceneObject()); //this creates SceneObject that will be safely destroyed
objectCount = script.objectsToSpawn.length;
for (var i = 0; i < count; i++) {
var childIndex = getIndex(i, objectCount);
duplicatedObjects[i] = parent.copyWholeHierarchy(
script.objectsToSpawn[childIndex]
);
duplicatedObjects[i].enabled = true;
}
for (var j = 0; j < objectCount; j++) {
//disable original objects
script.objectsToSpawn[j].enabled = false;
}
}
- At the end of the script, add the following code to enable original SceneObjects again:
function onDestroy() {
for (var j = 0; j < objectCount; j++) {
script.objectsToSpawn[j].enabled = true;
}
}
script.createEvent('OnDestroyEvent').bind(onDestroy);
For reference, you can view the final editable version of the Object Layout example in the link provided.
What's Next?
Now that you have learned about how to create your own Custom Components, take a look at some of the other guides to help expand your Scripting knowledge within Lens Studio: