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
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
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
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
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
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:
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. TheplaybackState
property is the components 12th property (we start counting at 0).
ThisEntity.subscribeToPropertyChange(videos[0].id, 12, "videoPlaybackStateChange");
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
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.
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.
void UOKOVideoPlayerSpaceComponent::DestroyOKOSpaceComponent()
{
BoundSpaceComponent->UnregisterActionHandler("PlayVideo");
// The above is repeated for the pause and reset actions.
}