Introdução à Scripting para Developers Unity
Last updated
Last updated
O Needle Engine oferece uma integração estreita com o Unity Editor. Isto permite que developers e designers trabalhem juntos num ambiente familiar e entreguem experiências web rápidas, performáticas e leves.
Este guia destina-se principalmente a developers com background em Unity3D, mas também pode ser útil para developers com background em web ou three.js. Abrange tópicos sobre como as coisas são feitas no Unity vs no three.js ou Needle Engine.
Se for completamente novo em Typescript e Javascript e quiser aprofundar-se na escrita de scripts para o Needle Engine, recomendamos também a leitura do para uma compreensão básica das diferenças entre C# e Javascript/Typescript.
Se quiser acompanhar a codificação, pode para criar um pequeno projeto que pode editar no browser ⚡
Needle Engine é um motor web 3D que corre sobre . Three.js é uma das bibliotecas de renderização 3D baseadas em webgl mais populares para a web. Sempre que nos referimos a um gameObject
no Needle Engine, estamos na verdade também a falar de um Object3D
do three.js, o tipo base de qualquer objeto no three.js. Ambos os termos podem ser usados indistintamente. Qualquer gameObject
é um Object3D
.
Isto também significa que - se já estiver familiarizado com three.js - não terá nenhum problema em usar o Needle Engine. Tudo o que pode fazer com three.js pode ser feito também no Needle Engine. Se já estiver a usar certas bibliotecas, também poderá usá-las num ambiente baseado em Needle Engine.
Nota: O Exporter do Needle Engine NÃO compila o seu código C# existente para Web Assembly. Embora o uso de Web Assembly possa resultar em melhor performance em tempo de execução, tem um alto custo para a velocidade de iteração e flexibilidade na construção de experiências web. Leia mais sobre a nossa e .
:::details Como criar um novo projeto Unity com Needle Engine? (Vídeo):::
No Unity, cria um novo componente derivando de MonoBehaviour
:
Um componente custom no Needle Engine, por outro lado, é escrito da seguinte forma:
Se viu alguns scripts do Needle Engine, deve ter notado que algumas variáveis são anotadas com @serializable
acima da sua declaração. Este é um Decorator em Typescript e pode ser usado para modificar ou anotar código. No Needle Engine, é usado, por exemplo, para permitir que a serialização principal saiba quais os tipos que esperamos no nosso script quando converte da informação raw do componente armazenada no glTF para uma instância de Componente.
Considere o seguinte exemplo:
Isto indica ao Needle Engine que myOtherComponent
deve ser do tipo Behaviour
. Ele irá então atribuir automaticamente a referência correta ao campo quando a sua cena for carregada. O mesmo se aplica a someOtherObject
onde queremos desserializar para uma referência de Object3D
.
Campos sem qualquer modificador de acesso como private
, public
ou protected
serão por padrão public
em javascript
O mesmo se aplica a métodos também.
Para aceder à cena atual a partir de um componente, use this.scene
que é equivalente a this.context.scene
, isto dá-lhe o objeto raiz da cena three.js.
Para percorrer a hierarquia a partir de um componente, pode iterar sobre os filhos de um objeto com um ciclo for:
ou pode iterar usando o equivalente a foreach
:
Outra opção que é bastante útil quando quer apenas iterar objetos renderizáveis é consultar todos os componentes de renderização e iterar sobre eles assim:
Para mais informações sobre como obter componentes, consulte a próxima secção.
Encontrar Componentes na Cena
Para obter componentes, pode usar os métodos familiares semelhantes aos do Unity. Note que o seguinte usa o tipo Animator
como exemplo, mas pode usar qualquer tipo de componente que seja built-in ou criado por si.
this.gameObject.getComponent(Animator)
Obtém o componente Animator
num GameObject/Object3D. Retornará a instância de Animator
se tiver um componente Animator ou null
se o objeto não tiver tal componente.
this.gameObject.getComponentInChildren(Animator)
Obtém o primeiro componente Animator
num GameObject/Object3D ou em qualquer um dos seus filhos
this.gameObject.getComponentsInParents(Animator)
Obtém todos os componentes Animator na hierarquia pai (incluindo o GameObject/Object3D atual)
Estes métodos também estão disponíveis no tipo estático GameObject. Por exemplo, GameObject.getComponent(this.gameObject, Animator)
para obter o componente Animator
num GameObject/Object3D passado.
Para procurar na cena inteira por um ou múltiplos componentes, pode usar GameObject.findObjectOfType(Animator)
ou GameObject.findObjectsOfType(Animator)
.
Alguns tipos específicos do Unity são mapeados para nomes de tipos diferentes no nosso engine. Veja a seguinte lista:
UnityEvent
EventList
Um UnityEvent será exportado como um tipo EventList
(use serializable(EventList)
para desserializar UnityEvents)
GameObject
Object3D
Transform
Object3D
No three.js e Needle Engine, um GameObject e um Transform são o mesmo (não há componente Transform
). A única exceção a essa regra é ao referenciar um RectTransform
que é um componente no Needle Engine também.
Color
RGBAColor
O tipo de cor do three.js não tem uma propriedade alpha. Por causa disso, todos os tipos Color exportados do Unity serão exportados como RGBAColor
que é um tipo custom do Needle Engine
Os dados de Transform podem ser acedidos diretamente no GameObject
/ Object3D
. Ao contrário do Unity, não há um componente de Transform extra que armazene esses dados.
this.gameObject.worldPosition
é a posição vector3 no espaço mundo
this.gameObject.worldRotation
é a euler rotation em ângulos de euler no espaço mundo
this.gameObject.worldQuaternion
é a quaternion rotation no espaço mundo
this.gameObject.worldScale
é a scale vector3 no espaço mundo
A principal diferença a ter em mente aqui é que position
no three.js é por padrão uma posição no espaço local, enquanto no Unity position
seria no espaço mundo e usaria localPosition
para usar deliberadamente a posição no espaço local. A próxima secção explicará como obter a posição no espaço mundo no three.js.
No three.js (e, portanto, também no Needle Engine), object.position
, object.rotation
, object.scale
são todas coordenadas de espaço local. Isso é diferente do Unity, onde estamos acostumados a position
ser no espaço mundo e usar localPosition
para usar deliberadamente a posição no espaço local.
Se quiser aceder às coordenadas do mundo no Needle Engine, temos métodos de utilidade que pode usar com os seus objetos. Chame getWorldPosition(yourObject)
para calcular a posição do mundo. Métodos semelhantes existem para rotação/quaternion e scale. Para ter acesso a esses métodos, importe-os simplesmente do Needle Engine assim: import { getWorldPosition } from "@needle.tools/engine"
Use this.context.time
para aceder aos dados de tempo:
this.context.time.time
é o tempo desde que a aplicação começou a correr
this.context.time.deltaTime
é o tempo que passou desde o último frame
this.context.time.frameCount
é o número de frames que passaram desde que a aplicação começou
this.context.time.realtimeSinceStartup
é o tempo não escalonado desde que a aplicação começou a correr
Também é possível usar this.context.time.timeScale
para deliberadamente abrandar o tempo, por exemplo, para efeitos de slow motion.
Use this.context.physics.raycast()
para realizar um raycast e obter uma lista de interseções. Se não passar nenhuma opção, o raycast é realizado a partir da posição do rato (ou primeira posição de toque) no espaço de ecrã usando a mainCamera
atualmente ativa. Também pode passar um objeto RaycastOptions
que tem várias configurações como maxDistance
, a câmara a ser usada ou as layers a testar.
Note que as chamadas acima estão por padrão a fazer raycasting contra objetos visíveis na cena. Isso é diferente do Unity, onde precisa sempre de colliders para acertar em objetos. A solução padrão do three.js tem prós e contras, onde um dos principais contras é que pode ter um desempenho bastante lento dependendo da geometria da sua cena. Pode ser especialmente lento ao fazer raycasting contra skinned meshes. É, portanto, recomendado geralmente definir objetos com SkinnedMeshRenderers no Unity para a layer Ignore Raycast
, que então serão ignorados por padrão pelo Needle Engine também.
Outra opção é usar os métodos de raycast da física, que só retornarão hits com colliders na cena.
Use this.context.input
para obter o estado de input:
Também pode subscrever eventos na enum InputEvents
assim:
Note que neste caso, tem que lidar com todos os casos sozinho. Por exemplo, pode precisar de usar eventos diferentes se o seu utilizador estiver a visitar o seu website no desktop vs mobile vs um dispositivo VR. Estes casos são automaticamente tratados pelos eventos de input do Needle Engine (por exemplo, PointerDown
é acionado tanto para mouse down, touch down e, no caso de VR, para controller button down).
Para que isto funcione, certifique-se de que o seu objeto tem um componente ObjectRaycaster
ou GraphicRaycaster
na hierarquia pai.
Nota: IPointerEventHandler
subscreve o objeto a todos os eventos de ponteiro possíveis. Os manipuladores para eles são:
onPointerDown
onPointerUp
onPointerEnter
onPointerMove
onPointerExit
onPointerClick
Todos têm um argumento PointerEventData
que descreve o evento.
O equivalente a Debug.Log()
em javascript é console.log()
. Pode também usar console.warn()
ou console.error()
.
No Unity, normalmente tem que usar métodos especiais para desenhar Gizmos como OnDrawGizmos
ou OnDrawGizmosSelected
. No Needle Engine, por outro lado, tais métodos não existem e é livre para desenhar gizmos de qualquer lugar no seu script. Note que também é sua responsabilidade não desenhá-los, por exemplo, na sua aplicação web implementada (pode simplesmente filtrá-los por if(isDevEnvironment))
).
Aqui está um exemplo para desenhar uma esfera de arame vermelha por um segundo, por exemplo, para visualizar um ponto no espaço mundo.
Aqui estão alguns dos métodos de gizmo disponíveis:
Gizmos.DrawArrow
Gizmos.DrawBox
Gizmos.DrawBox3
Gizmos.DrawDirection
Gizmos.DrawLine
Gizmos.DrawRay
Gizmos.DrawRay
Gizmos.DrawSphere
Gizmos.DrawWireSphere
Importe de @needle-tools/engine
, por exemplo, import { getParam } from "@needle-tools/engine"
getParam()
Verifica se existe um parâmetro de URL. Retorna true se existir mas não tiver valor (por exemplo, ?help
), false se não for encontrado no URL ou for definido como 0 (por exemplo, ?help=0
), caso contrário, retorna o valor (por exemplo, ?message=test
)
isMobileDevice()
Retorna true se a app for acedida a partir de um dispositivo móvel
isDevEnvironment()
Retorna true se a app atual estiver a correr num servidor local
isMozillaXR()
isiOS
isSafari
Em C#, geralmente trabalha com uma solution contendo um ou muitos projetos. No Unity, esta solution é gerida pelo Unity para si, e quando abre um script C#, ele abre o projeto e mostra-lhe o ficheiro.
Normalmente instala Packages usando o package manager built-in do Unity para adicionar funcionalidades fornecidas pelo Unity ou por outros developers (seja da sua equipa ou, por exemplo, via AssetStore do Unity). O Unity faz um excelente trabalho ao tornar a adição e gestão de packages fácil com o seu PackageManager, e talvez nunca tenha tido que editar manualmente um ficheiro como o manifest.json
(isto é o que o Unity usa para rastrear quais packages estão instalados) ou executar um comando da linha de comandos para instalar um package.
Num ambiente web, usa npm
- o Node Package Manager - para gerir dependências / packages para si. Ele faz basicamente o mesmo que o PackageManager do Unity - instala (faz download) packages de algum servidor (geralmente chamado de registry nesse contexto) e os coloca dentro de uma pasta chamada node_modules
.
Aqui está um exemplo de como um package.json pode parecer:
O nosso template padrão usa Vite como o seu bundler e não tem nenhum framework frontend pré-instalado. Needle Engine não tem opinião sobre qual framework usar, por isso é livre para trabalhar com o framework que preferir. Temos exemplos para frameworks populares como Vue.js, Svelte, Next.js, React ou React Three Fiber.
Deve ter notado que existem duas entradas contendo dependency - dependencies
e devDependencies
.
dependencies
são sempre instaladas (ou agrupadas) quando o seu projeto web é instalado ou em casos em que desenvolve uma biblioteca e o seu package é instalado como dependência de outro projeto.
devDependencies
são apenas instaladas durante o desenvolvimento do projeto (o que significa que quando executa diretamente install
na diretoria específica) e caso contrário não são incluídas no seu projeto.
Primeiro, execute npm install @tweenjs/tween.js
no terminal e espere que a instalação termine. Isso adicionará uma nova entrada ao nosso package.json:
Depois, abra um dos seus ficheiros de script em que quer usar tweening e importe no topo do ficheiro:
Note que importamos aqui todos os tipos na biblioteca escrevendo * as TWEEN
. Poderíamos também importar apenas tipos específicos como import { Tween } from @tweenjs/tween.js
.
Para rodar um cubo, criamos um novo tipo de componente chamado TweenRotation
, depois procedemos a criar a nossa instância de tween para a rotação do objeto, quantas vezes deve repetir, qual easing usar, o tween que queremos realizar e depois iniciamos. Temos apenas que chamar update
a cada frame para atualizar a animação do tween. O script final parece assim:
Página traduzida automaticamente usando IA
Note que em alguns casos o tipo pode ser omitido. Isso pode ser feito para todos os . São eles: boolean
, number
, bigint
, string
, null
e undefined
.
Também pode usar métodos específicos do three.js para iterar rapidamente todos os objetos recursivamente usando o método :
ou para percorrer apenas objetos visíveis, use em vez disso.
Needle Engine faz uso intenso de um Sistema de Componentes que é semelhante ao do Unity. Isto significa que pode adicionar ou remover componentes a qualquer Object3D
/ GameObject
na cena. Um componente será registado no engine ao usar addNewComponent(<Object3D>, <ComponentType>)
.
Os métodos de evento que o componente anexado terá serão então automaticamente chamados pelo engine (por exemplo, update
ou onBeforeRender
). Uma lista completa de métodos de evento pode ser encontrada na .
this.gameObject.position
é a posição vector3 no espaço local
this.gameObject.rotation
é a no espaço local
this.gameObject.quaternion
- é a no espaço local
this.gameObject.scale
- é a vector3 no espaço local
Note que estes métodos de utilidade como getWorldPosition
, getWorldRotation
, getWorldScale
internamente têm um buffer de instâncias de Vector3 e destinam-se a ser usados apenas localmente. Isso significa que não deve guardá-los em cache no seu componente, caso contrário, o seu valor em cache será eventualmente substituído. Mas é seguro chamar getWorldPosition
várias vezes na sua função para fazer cálculos sem ter que se preocupar em reutilizar a mesma instância. Se não tiver a certeza do que isto significa, deve dar uma vista de olhos na secção Primitive Types no .
Use this.context.physics.raycastFromRay(your_ray)
para realizar um raycast usando um .
Aqui está um .
Se quiser lidar com os inputs você mesmo, pode também subscrever (há imensos). Por exemplo, para subscrever o evento de clique do browser, pode escrever:
Semelhante ao Unity (veja ), também pode registar-se para receber eventos de input no próprio componente.
Ao trabalhar com um projeto web, a maioria das suas dependências são instaladas de . É o registry de packages mais popular para projetos web.
Para instalar uma dependência do npm, pode abrir o seu projeto web numa linha de comandos (ou terminal) e executar npm i <the/package_name>
(abreviação para npm install
)
Por exemplo, execute npm i @needle-tools/engine
para instalar . Isso adicionará então o package ao seu package.json
no array dependencies
.
Para instalar um package apenas como devDependency, pode executar npm i --save-dev <package_name>
. Mais sobre a diferença entre dependencies e devDependencies abaixo.
A secção ensinou-nos que pode instalar dependências executando npm i <package_name>
na diretoria do seu projeto, onde package_name
pode ser qualquer package que encontre em .
Vamos supor que quer adicionar uma biblioteca de tweening ao seu projeto. Usaremos para este exemplo. está o projeto final se quiser saltar à frente e apenas ver o resultado.
Agora podemos usá-lo no nosso script. É sempre recomendado consultar a documentação da biblioteca que quer usar. No caso do tween.js, eles fornecem um que podemos seguir. Geralmente, a página Readme do package no npm contém informações sobre como instalar e usar o package.
Agora só temos que adicioná-lo a qualquer um dos objetos na nossa cena para os rodar para sempre. Pode ver o script final em ação .