Skip to main content
Skip table of contents

Writing OKO Scripts - Examples

The following section has a series of examples designed to introduce new users to the Script Component. These examples cover a range of common scenarios.

Iterating component array - Foreach

In this example we are subscribing to the entityClick event, which is fired whenever the user clicks on a SpaceEntity component. The registered function (onClick in this case) receives as arguments the message, as well as the params, which is a json string containing the following key/value pairs:

id: entityId, cid: componentId

In the onClick function we use the forEach array iteration function to iterate over a collection of components and call the printComponentInfo function for each.

Example
CODE
OKO.Log("Foreach example running: ", ThisEntity.id);

globalThis.onClick = function(msg, params) {
    OKO.Log("onClick:", msg, params);
    
    var components = ThisEntity.getComponents();
    components.forEach(printComponentInfo);
}

function printComponentInfo(value) {
    OKO.Log("componentId: ", value.id);
    OKO.Log("component type: ", value.type);
}

ThisEntity.subscribeToMessage("entityClick", "onClick");

Iterating component array - For loop

In this example we are using a For loop to iterate over a collection of components. For each iteration we use the variable i to index a component in the collection and log its id and type.

Example
CODE
OKO.Log("For loop example running: ", ThisEntity.id);

globalThis.onClick = function(msg, params) {
    OKO.Log("onClick:", msg, params);

    var components = ThisEntity.getComponents();
    
    for (let i = 0; i < components.length; i++) {
        OKO.Log("componentId: ", components[i].id);
        OKO.Log("component type: ", components[i].type);
    }
}

ThisEntity.subscribeToMessage("entityClick", "onClick");

Accessing component properties

In this example we get a collection of Static Model components and print the first models Asset Id.

Example
CODE
OKO.Log("Component property example running: ", ThisEntity.id);

globalThis.onClick = function(msg, params) {
    OKO.Log("onClick:", msg, params);

    var staticModels = ThisEntity.getStaticModelComponents()
    OKO.Log("modelAssetId:", staticModels[0].modelAssetId);
}

ThisEntity.subscribeToMessage("entityClick", "onClick");

Invoking component actions

In this example we get a collection of Animated Model components and check to see if the component id of the clicked component, matches the id of the first Animated Model in the array. If it does that means the user has clicked on an animated model and we can toggle the animation play state. We check to see if the first animated model (at index 0) is currently playing and if it is we invoke its PauseAnimation action. Else, it is not currently playing and so we invoke its PlayAnimation action.

Please see the Component Actions section below for an Unreal example of how component actions are defined.

Example
CODE
OKO.Log("Component Action example running: ", ThisEntity.id);

globalThis.onClick = function(msg, params) {
    var paramsObj = JSON.parse(params);
    var componentId = paramsObj.cid;

    var animatedModels = ThisEntity.getAnimatedModelComponents();
    if (animatedModels[0].id == componentId) {
        if(animatedModels[0].isPlaying) {
            OKO.Log("Pause Animation");

            animatedModels[0].invokeAction("PauseAnimation", JSON.stringify(animatedModels[0]));
        } else {

            OKO.Log("Play Animation");
            animatedModels[0].invokeAction("PlayAnimation", JSON.stringify(animatedModels[0]));
        }
    }
}

ThisEntity.subscribeToMessage("entityClick", "onClick");

Using Button Components to invoke actions

In this example we use button components to invoke actions on a video player component. The Space Entity features three button components, labelled play, pause and reset. Clicking a button sends the buttonPressed message, which in turn calls the processBtnClick function.

Just like the onClick function in the examples above, the processBtnClick function receives the message name and a json string of parameters. The params string is parsed into an object and the calling component id is extracted. Next we use a For loop to iterate over the button components on this Entity, and check the id of each against the id of the calling button. If we get a match we use the labelText property binding to get the buttons label and string match it against the playback control buttons. If we get a match we then invoke the appropriate action on the video player component.

Example
CODE
OKO.Log("Button Component example running: ", ThisEntity.id);

var videos = ThisEntity.getVideoPlayerComponents();

globalThis.processBtnClick = function(msg, params) {
    var paramsObj = JSON.parse(params);
    var componentId = paramsObj.cid;

    var buttons = ThisEntity.getButtonComponents();
    for (let i = 0; i < buttons.length; i++) {

        if (buttons[i].id === componentId) {
            var buttonLabel = buttons[i].labelText;
            OKO.Log("Button pressed: ", buttonLabel);

            if (buttonLabel === 'play') {
                videos[0].invokeAction('PlayVideo', JSON.stringify(videos[0]));
            } else if (buttonLabel === 'pause') {
                videos[0].invokeAction('PauseVideo', JSON.stringify(videos[0]));
            } else if (buttonLabel === 'reset') {
                videos[0].invokeAction('ResetVideo', JSON.stringify(videos[0]));
            }
            break;
        }
    }
}

ThisEntity.subscribeToMessage("buttonPressed", "processBtnClick");

Subscribe to component property change

This example builds on the previous one to show how we can subscribe to a component property change and use that to drive the behaviour of another component. In this example we are listening for a playbackState property change on the Video Player Component - please see here for a list of the video component properties as well as those of the other components. To subscribe to a property change we are required to do the following:

  1. Call the subscribeToPropertyChange method passing in the Component whose property we are listening to, the key of the property and a message that will be sent when the property changes. The playbackState property is the components 12th property (we start counting at 0).

ThisEntity.subscribeToPropertyChange(videos[0].id, 12, "videoPlaybackStateChange");

 

  1. Call the subscribeToMessage method specifying the message we want to listen for and the name of the function we want to call in response.

ThisEntity.subscribeToMessage("videoPlaybackStateChange", "videoStateChange");

Example
CODE
OKO.Log("Component Property Update example running: ", ThisEntity.id);

var executionQueue = [];

var videos = ThisEntity.getVideoPlayerComponents();
var models = ThisEntity.getStaticModelComponents();

globalThis.onTick = function(msg, params)
{
    // Execute our queued commands
    while (executionQueue.length > 0) {
        executionQueue.shift()();
    }
}

globalThis.processBtnClick = function(msg, params)
{
    var paramsObj = JSON.parse(params);
    var componentId = paramsObj.cid;

    var buttons = ThisEntity.getButtonComponents();
    for (let i = 0; i < buttons.length; i++) {

        if (buttons[i].id === componentId) {
            var buttonLabel = buttons[i].labelText;
            OKO.Log("Button pressed: ", buttonLabel);

            if (buttonLabel === 'play') {
                videos[0].invokeAction('PlayVideo', JSON.stringify(videos[0]));
            } else if (buttonLabel === 'pause') {
                videos[0].invokeAction('PauseVideo', JSON.stringify(videos[0]));
            } else if (buttonLabel === 'reset') {
                videos[0].invokeAction('ResetVideo', JSON.stringify(videos[0]));
            }
            break;
        }
    }
}

// Called in response to change to playbackState property of video component
globalThis.videoStateChange = function(msg, params)
{
    if(videos[0].playbackState === 0) {
        // Video reset
        executionQueue.push(onVideoStateReset);
    } else if (videos[0].playbackState === 1) {
        // Video pause
        executionQueue.push(onVideoStatePause);
    } else if (videos[0].playbackState === 2) {
        OKO.Log("Play");
        executionQueue.push(onVideoStatePlay);
    }
}

function onVideoStatePlay() {
    models[0].position = [10,200,100];
}

function onVideoStatePause() {
    models[0].position = [20,0,100];
}

function onVideoStateReset() {
    models[0].position = [30,-200,100];
}

// Subscribing to a playbackState property change.
// A change to this property will cause a "videoPlaybackStateChange" message to be sent.
ThisEntity.subscribeToPropertyChange(videos[0].id, 11, "videoPlaybackStateChange");
// Subscribe to the resultant "videoPlaybackStateChange" message.
ThisEntity.subscribeToMessage("videoPlaybackStateChange", "videoStateChange");

ThisEntity.subscribeToMessage("buttonPressed", "processBtnClick"); 
ThisEntity.subscribeToMessage("entityTick", "onTick");

It is worth drawing attention to the executionQueue array above. Due to the way a user currently receives callbacks for local changes, it is not possible to immediately change properties on another component. Instead those commands must be deferred to the next tick, which is why they are being queued in the executionQueue array for processing later.

Component Actions

Each component is responsible for managing its own component actions. The following is an example of how this is achieved in the Unreal OKO Plugin via the following two methods; BindFoundationSpaceComponent and DestroyOKOSpaceComponent.

In this example we are defining ActionHandlers for the play, pause and reset actions.

BindFoundationSpaceComponent is used to register the action handlers.

CODE
void UOKOVideoPlayerSpaceComponent::BindFoundationSpaceComponent(csp::multiplayer::ComponentBase* InSpaceComponent)
{
	...
	
	const csp::multiplayer::ComponentBase::EntityActionHandler PlayVideoHandler = [this](
		csp::multiplayer::ComponentBase* Component, csp::common::String ActionId, csp::common::String ActionParams)
	{
		Play();
	};
	BoundSpaceComponent->RegisterActionHandler("PlayVideo", PlayVideoHandler);

	// The above is repeated for the pause and reset actions.
}

DestroyOKOSpaceComponent is used to unregister action handlers.

CODE
void UOKOVideoPlayerSpaceComponent::DestroyOKOSpaceComponent()
{
	BoundSpaceComponent->UnregisterActionHandler("PlayVideo");
	
	// The above is repeated for the pause and reset actions.
}

JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.