Documentação xr/studio
Entidade Custom Code
Construa uma entidade com o seu próprio código: o scope do Three.js, params com controlos no inspector, carregar assets, interfaces a partir de markup e screen space.
Entidade Custom Code
Uma entidade Custom Code é uma entidade que constrói com o seu próprio código em vez do inspector. Fica na cena como qualquer outra entidade, e tudo o que faz de uma entidade uma entidade continua a aplicar-se: tem transform, pode ser escondida, pode ser animada na timeline, pode ter filhos, e pode ser transformada num prefab.
Para adicionar, use Add Entities → Advanced → Custom Code. Vem com um script funcional, por isso consegue ver a forma do bloco antes de mudar seja o que for.
A diferença para uma ação Custom Code está em quando corre. Uma ação dispara uma vez num evento. O bloco de uma entidade corre quando a entidade aparece, pode voltar a correr em cada frame, e limpa tudo quando a entidade desaparece.
O que o seu código recebe
O Three.js já está em scope como THREE. Nunca importe a sua própria cópia: um segundo Three.js quebra as verificações instanceof em toda a cena.
| Em scope | O que é |
|---|---|
THREE | A instância de Three.js da aplicação |
group | O objeto da própria entidade. Tudo o que constrói pertence aqui |
params | Valores do painel Params, cada um com um controlo no inspector |
entity | Esta entidade, para o nome, id e tags |
scene, camera, renderer | O scene graph ativo |
loaders | Loaders de GLTF, texturas e HDR já configurados |
uikitml() | Transforma markup numa interface |
cyango | A mesma API da história que uma ação Custom Code recebe, incluindo cyango.utils.getAssetUrl() |
console, timers | Logging com namespace, e timers que se limpam sozinhos |
A forma de um bloco
const geometry = new THREE.TorusKnotGeometry(0.4, 0.14, 128, 32);
const material = new THREE.MeshStandardMaterial({ color: params.color });
const mesh = new THREE.Mesh(geometry, material);
group.add(mesh);
return {
update: (delta, elapsed) => {
mesh.rotation.y += delta * params.speed;
},
dispose: () => {
geometry.dispose();
material.dispose();
},
};
As duas funções devolvidas são opcionais. Um bloco que só constrói geometria estática pode não devolver nada.
update(delta, elapsed)corre em cada frame.deltasão os segundos desde o último frame,elapsedos segundos desde o início da história.dispose()corre antes do bloco voltar a correr e quando a entidade desaparece.
Tudo o que deixar em group é removido e libertado por si, incluindo geometrias, materiais e texturas. Escreva dispose para o que criou fora do group, como um render target. Os timers que iniciar são limpos automaticamente.
Params
Cada chave em Params torna-se um controlo no inspector, por isso uma história pode ser afinada sem abrir o código. Os params são uma lista, e cada entrada carrega o seu valor e o seu controlo:
[
{ "key": "color", "value": "#6366f1", "label": "Cor" },
{ "key": "speed", "value": 1, "type": "SLIDER", "min": 0, "max": 5, "step": 0.1 }
]
Só key é obrigatório. Deixe type de fora e é inferido a partir do valor: um número dá um campo numérico, um booleano um switch, uma string #rrggbb um seletor de cor, qualquer outra string um campo de texto, e [x, y, z] três campos numéricos.
Defina type quando quiser algo que o valor não consegue implicar. Os tipos disponíveis são NUMBER, SLIDER, BOOLEAN, TEXT, COLOR, SELECT e VECTOR3, refinados com label, help, min, max, step e options.
Mudar qualquer param volta a correr o bloco todo, por isso o seu código lê sempre os valores atuais.
Carregar assets
Atribua um asset no painel Asset e leia-o com cyango.utils.getAssetUrl(). O loader de GLTF já traz os mesmos decoders DRACO, KTX2 e meshopt que a entidade 3D Model usa.
const url = cyango.utils.getAssetUrl();
if (!url) return;
const gltf = await loaders.gltf.loadAsync(url);
group.add(gltf.scene);
const mixer = new THREE.AnimationMixer(gltf.scene);
if (gltf.animations[0]) mixer.clipAction(gltf.animations[0]).play();
return { update: (delta) => mixer.update(delta) };
cyango.utils.getAssetUrl() sem argumento devolve o asset da própria entidade. Passe um id de asset para chegar a qualquer outro asset da história.
Note que um asset carregado desta forma não segura o loading screen da cena, por isso um modelo grande aparece depois da cena já estar interativa.
Interfaces a partir de markup
uikitml() transforma um subconjunto de HTML numa interface 3D:
await uikitml(`
<div style="flex-direction: column; padding: 20px; background-color: #111827">
<span style="color: white; font-size: 20px">${params.title}</span>
</div>
`);
As tags são div, p, span, li, h1–h6, ol, ul, a, button, img, svg, video, input e textarea. Qualquer outra falha a leitura do markup e a interface inteira é ignorada, com o motivo registado na consola do browser.
Os estilos são escritos com um valor por propriedade: padding: 12px 20px não é aceite, por isso use padding-x e padding-y, ou um único padding. Os nomes das propriedades são os do uikit escritos com traços, ou seja background-color e flex-direction, não backgroundColor.
Mantenha todas as propriedades no style do próprio elemento. Um bloco <style> e um atributo class são aceites mas nunca aplicados, por isso o elemento fica sem estilo.
O resultado é anexado, atualizado e libertado por si, por isso não o adicione nem o atualize manualmente.
Para pôr uma interface no ecrã, arraste a entidade para cima da entidade Screen na árvore da cena. O mesmo markup passa a ser desenhado à frente da câmara, com o layout feito pelo GUI. Deixada na raiz da cena, é desenhada em world space na própria entidade.
Só o markup se move entre os dois. Uma mesh precisa de world space, porque um parent de GUI faz layout de componentes de interface e não tem caixa para um objeto 3D simples.
No ecrã, a caixa da própria entidade começa transparente, por isso não bloqueia os cliques destinados à cena que está por trás, e os elementos que o seu markup constrói mantêm os cliques deles de qualquer forma. Para forçar num sentido ou no outro, use Pointer → Clicks no inspetor em vez do markup. Veja Entidades de interface para saber como essa definição funciona.
Chegar à história
cyango é o mesmo objeto que uma ação Custom Code recebe, por isso um bloco pode ler o estado da história, mudar de cena, ou partilhar valores com ações através de variáveis globais. Veja a API Custom Code para o namespace completo.
cyango.utils.setGlobalVar('spawnedAt', Date.now());
Quando usar outra coisa
As entidades incorporadas cobrem quase tudo o que uma história precisa, e vêm com quality levels, física, localização e suporte de prefabs que o seu código teria de reproduzir. Recorra a uma entidade Custom Code quando as incorporadas mesmo não chegam: instanciar milhares de objetos, shaders personalizados, geometria generativa, ou uma interface gerada a partir de dados.
Para um único modelo animado, use a entidade 3D Model. Para uma interface que desenha à mão, use as entidades GUI.