Documentação xr/studio
API de Custom Code
Programe a sua história com a ação Custom Code: o namespace cyango, os seus métodos e tipos, ler inputs GUI, variáveis, navegação e exemplos.
Custom Code
A ação Custom Code corre TypeScript quando o seu evento dispara, como On Click, On Scene Ready ou On Language Changed. Use-a quando os tipos de ação incorporados não chegam, por exemplo para ler inputs de formulário, ramificar por dispositivo ou idioma, guardar valores entre cenas ou controlar a história diretamente.
Para adicionar, abra o separador Actions numa entidade (ou selecione a cena), escolha um event type e adicione uma ação Custom Code. O editor tem autocomplete TypeScript — escreva cyango. para ver o que está disponível.
Quando guarda a história, o Studio compila cada bloco de código personalizado para JavaScript. O player executa apenas o output compilado, por isso obtém edição com tipos sem enviar o compilador TypeScript aos visitantes. Corrija erros de compilação mostrados no editor antes de publicar.
A sandbox
O seu código corre dentro de uma sandbox segura, envolvida numa função async. Na prática:
- Pode usar
awaitdiretamente, ereturnpara parar mais cedo. console.log,setTimeout,setInterval(limitado a 60 segundos) efetchestão disponíveis.- Os globais do DOM estão bloqueados:
window,document,location,navigator,history,eval. Usecyango.utils.getWindowLocation()ecyango.utils.getNavigator()em vez disso. - Os erros são apanhados e registados na consola do browser, por isso um trecho com erro não vai quebrar a história.
Tudo o que o Cyango expõe vive num único objeto global, cyango.
Código de Head e Footer
Uma história tem também dois blocos que correm uma vez, quando a história carrega. Abra Settings e depois Head and Footer Code. O Head corre primeiro e o Footer corre no fim. Ambos usam o mesmo editor e a mesma sandbox de uma ação Custom Code, com os seus próprios snippets no painel ao lado do código.
Use-os para preparar a história antes de o visitante a ver, ou para reportar sobre ela depois:
- Defina o idioma ou o nível de qualidade a partir do URL, antes de a primeira cena ser mostrada.
- Carregue uma configuração do seu backend para variáveis que qualquer ação pode ler.
- Instale o Google Analytics, o Google Tag Manager ou qualquer outra tag.
- Escreva as tags de SEO e de partilha: canonical, robots, Open Graph, Twitter cards e JSON-LD.
O objeto head
Estes dois blocos têm uma coisa que mais nenhum bloco tem: head, que escreve na página. O document e o window continuam bloqueados em todo o lado, por isso é assim que uma tag chega à página.
| Método | O que faz |
|---|---|
head.addScript(src, options) | Carrega um script externo, uma só vez. Use await quando a tag tem de ser configurada a seguir |
head.addInlineScript(code, options) | Corre JavaScript na página, a única forma de chegar a globais do window como dataLayer e gtag |
head.setMeta(name, content) | Define uma meta tag name, como description ou robots |
head.setMetaProperty(property, content) | Define uma meta tag property, que é o que o Open Graph usa |
head.setLink(rel, href, options) | Define uma tag link, como canonical ou um alternate por idioma |
head.setJsonLd(data, options) | Escreve um bloco JSON-LD |
head.setTitle(title) | Define o título da página. Por omissão é usado o nome da história |
Todas as chamadas são idempotentes. Um bloco que corre outra vez atualiza as suas tags em vez de adicionar uma segunda cópia, por isso uma tag nunca é carregada duas vezes nem uma visita é contada duas vezes.
Uma ação Custom Code não tem head. Para enviar um evento a partir de uma ação, use o snippet do Measurement Protocol do Google Analytics, que é apenas um fetch e funciona em qualquer bloco.
O objeto cyango
Existem cinco namespaces:
| Namespace | Para que serve |
|---|---|
cyango.utils | Auxiliares: encontrar entidades, guardar variáveis, ler inputs GUI, detetar o dispositivo |
cyango.storyState | Ler e controlar a história: cenas, idioma, ativos, XR |
cyango.uiState | Estado da app e da interface: carregamento, controlos de câmara, modais |
cyango.timelineState | Timeline da cena e reprodução de media |
cyango.types | Enums usados como argumentos e para comparações |
Os valores que lê estão em direto, como cyango.storyState.activeSceneId. As funções que chama executam uma ação, como cyango.storyState.setActiveScene(id).
Variáveis
As variáveis globais duram toda a sessão e sobrevivem às mudanças de cena. Use-as para passar dados de uma ação para outra.
| Método | Descrição |
|---|---|
cyango.utils.setGlobalVar(key, value) | Guardar qualquer valor sob uma chave |
cyango.utils.getGlobalVar(key) | Ler um valor guardado |
cyango.utils.deleteGlobalVar(key) | Remover uma chave (devolve boolean) |
cyango.utils.clearGlobalVars() | Remover todas as variáveis |
cyango.utils.globalVars | O registo completo, como snapshot |
// Ao clicar num botão: contar os cliques e agir no terceiro
const clicks = (cyango.utils.getGlobalVar('clicks') ?? 0) + 1;
cyango.utils.setGlobalVar('clicks', clicks);
if (clicks < 3) return;
const scene = cyango.storyState.activeStoryJson.scenes.find((s) => s.name === 'Reward');
if (scene) cyango.storyState.setActiveScene(scene.id);
Encontrar entidades e cenas
| Método ou valor | Devolve |
|---|---|
cyango.utils.getActiveScene() | A cena atual (IScene), ou null |
cyango.utils.getEntityByName(entities, name) | A primeira entidade com esse nome, ou null |
cyango.utils.getEntityById(entities, id) | A entidade pelo seu id, ou null |
cyango.utils.thisEntity | A entidade a que esta ação está associada |
cyango.storyState.activeSceneId | Id da cena atual |
cyango.storyState.activeStoryJson | O documento completo da história, incluindo .scenes |
const scene = cyango.utils.getActiveScene();
const logo = cyango.utils.getEntityByName(scene.entities, 'Logo');
Ler campos de input GUI
As entidades de campo de input capturam o que o visitante escreve. Leia ou defina esse valor pelo id da entidade do input. O valor é mantido mesmo que o campo seja escondido e mostrado de novo.
| Método | Descrição |
|---|---|
cyango.utils.getGuiInputValue(entityId) | O texto atual (string), ou null se nunca foi definido |
cyango.utils.setGuiInputValue(entityId, value) | Definir o texto do campo |
cyango.utils.clearGuiInputValue(entityId) | Esvaziar o campo |
// Num botão Submit: ler um campo de email e guardá-lo
const scene = cyango.utils.getActiveScene();
const emailField = cyango.utils.getEntityByName(scene.entities, 'EmailInput');
const email = cyango.utils.getGuiInputValue(emailField.id);
cyango.utils.setGlobalVar('email', email);
cyango.utils.setGuiInputValue(emailField.id, ''); // limpar após submeter
Fazer scroll em contentores GUI
Para contentores GUI cujo conteúdo faz scroll, como listas e painéis:
| Método | Descrição |
|---|---|
cyango.utils.getGuiScrollPosition(entityId) | Deslocamento { x, y } atual (chame trackGuiScroll primeiro) |
cyango.utils.getGuiMaxScrollPosition(entityId) | Deslocamento de scroll máximo |
cyango.utils.setGuiScrollPosition(entityId, x, y) | Saltar para um deslocamento |
cyango.utils.scrollGuiBy(entityId, dx, dy) | Deslocamento a partir da posição atual |
cyango.utils.trackGuiScroll(entityId) e untrackGuiScroll(entityId) | Iniciar e parar o registo do scroll do utilizador |
cyango.utils.lockGuiScroll(entityId) e unlockGuiScroll(entityId) | Bloquear e permitir o scroll do utilizador |
Navegação, idioma e links
| Método ou valor | Descrição |
|---|---|
cyango.storyState.setActiveScene(sceneId) | Ir para uma cena, opcionalmente com uma transição |
cyango.storyState.setActiveLanguage(lang) | Mudar de idioma (dispara On Language Changed) |
cyango.storyState.triggerAction(action, entityId?) | Correr um objeto de ação diretamente |
cyango.uiState.openUrl(url) | Abrir um URL externo |
cyango.storyState.activeLanguage e availableLanguages | Idioma atual e idiomas disponíveis |
// Enviar visitantes portugueses para uma cena localizada
if (cyango.storyState.activeLanguage === cyango.types.LanguageTypes.pt_PT) {
const scene = cyango.storyState.activeStoryJson.scenes.find((s) => s.name === 'Intro PT');
if (scene) cyango.storyState.setActiveScene(scene.id);
}
AR e VR
| Método ou valor | Descrição |
|---|---|
cyango.storyState.enterAR(), enterVR(), exitXR() | Entrar ou sair dos modos imersivos |
cyango.storyState.isAR, isVR, isMobileAR | Se um modo está atualmente ativo |
Timeline e media
| Método ou valor | Descrição |
|---|---|
cyango.timelineState.setPlayingMode(mode) | Reproduzir ou pausar a timeline da cena. Veja cyango.types.PlayingModes |
cyango.timelineState.controlMedia(mode) | Controlar a media da cena: 'play', 'pause', 'stop', 'mute', 'unmute', 'seek' |
cyango.timelineState.setElapsedTime(ms) | Avançar na timeline |
cyango.timelineState.elapsedTime | Tempo atual, em milissegundos |
cyango.timelineState.setPlayingMode(cyango.types.PlayingModes.PLAY);
Dispositivo e ambiente
| Método ou valor | Devolve |
|---|---|
cyango.utils.detectDeviceType() | "iPad", "iPhone", "Android", "Windows", "Mac", ou "Unknown" |
cyango.utils.detectBrowser() | Nome do browser, como "Apple Safari" ou "Google Chrome" |
cyango.uiState.selectedBreakpoint | Breakpoint atual. Veja cyango.types.BreakpointStateTypes |
cyango.utils.getWindowLocation() | O objeto Location, incluindo o URL e a query string |
cyango.utils.getNavigator() | O objeto Navigator |
if (cyango.utils.detectDeviceType() === 'iPhone') {
// mostrar uma dica específica para telemóvel
}
Tipos
cyango.types fornece os enums usados em toda a API. Use-os para argumentos e comparações:
ActionType, EventType, PlayingModes, TransitionType, EntityTypes, MaterialTypes, BreakpointStateTypes, SceneTypes, AssetCategories, AssetFileTypes, AssetMimeTypes, LanguageTypes.
Exemplo: um formulário de inscrição
// No On Click de um botão Continue
const scene = cyango.utils.getActiveScene();
const nameField = cyango.utils.getEntityByName(scene.entities, 'NameInput');
const name = (cyango.utils.getGuiInputValue(nameField.id) ?? '').trim();
// Nada escrito, por isso limpar o campo e ficar nesta cena
if (!name) {
cyango.utils.clearGuiInputValue(nameField.id);
return;
}
// Guardar o nome, enviá-lo para o seu backend, depois continuar
cyango.utils.setGlobalVar('visitorName', name);
await fetch('https://example.com/signups', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name }),
});
const next = cyango.storyState.activeStoryJson.scenes.find((s) => s.name === 'Welcome');
if (next) cyango.storyState.setActiveScene(next.id);