If you are new to scripting we recommend reading the following guides first:
If you know what you're doing, feel free to jump right into the Needle Engine API documentation.
Runtime code for Needle Engine is written in TypeScript (recommended) or JavaScript. We automatically generate C# stub components out of that, which you can add to GameObjects in the editor. The C# components and their data are recreated by the runtime as JavaScript components with the same data and attached to three.js objects.
Both custom components as well as built-in Unity components can be mapped to JavaScript components in this way. For example, mappings for many built-in components related to animation, rendering or physics are already included in Needle Engine.
If you want to code-along with the following examples without having to install anything you just click the following link:
Our web runtime engine adopts a component model similar to Unity and thus provides a lot of functionality that will feel familiar. Components attached to three's Object3D objects have lifecycle methods like awake
, start
, onEnable
, onDisable
, update
and lateUpdate
that you can implement. You can also use Coroutines.
When you don't need to write code
Often, interactive scenes can be realized using Events in Unity and calling methods on built-in components. A typical example is playing an animation on button click - you create a button, add a Click event in the inspector, and have that call Animator.SetTrigger or similar to play a specific animation.
Needle Engine translates Unity Events into JavaScript method calls, which makes this a very fast and flexible workflow - set up your events as usual and when they're called they'll work the same as in Unity.
An example of a Button Click Event that is working out-of-the-box in Needle Engine — no code needed.
Creating a new component
Scripts are written in TypeScript (recommended) or JavaScript.
There are two ways to add custom scripts to your project:
Simply add a file with an
.ts
or.js
extension insidesrc/scripts/
in your generated project directory, for examplesrc/scripts/MyFirstScript.ts
Unity specific:
Organize your code into NPM Definition Files (npm packages). These help you to modularize and re-use code between projects and if you are familiar with web development they are in fact regular npm packages that are installed locally.
In Unity you can create NpmDef files viaCreate > NPM Definition
and then add TypeScript files by right-clicking an NpmDef file and selectingCreate > TypeScript
. Please see this chapter for more information.
In both approaches, source directories are watched for changes and C# stub components or Blender panels are regenerated whenever a change is detected.
Changes to the source files also result in a hot reload of the running website – you don't have to wait for Unity to recompile the C# components. This makes iterating on code pretty much instant.
You can even have multiple component types inside one file (e.g. you can declare export class MyComponent1
and export class MyOtherComponent
in the same Typescript file).
If you are new to writing Javascript or Typescript we recommend reading the Typescript Essentials Guide guide first before continuing with this guide.
Example: Creating a Component that rotates an object
- Create a component that rotates an object
Createsrc/scripts/Rotate.ts
and add the following code:
import { class Behaviour
Needle Engine component base class. Component's are the main building blocks of the Needle Engine.
Derive from
Behaviour
to implement your own using the provided lifecycle methods.
Components can be added to threejs objects using [addComponent](addComponent)
or [GameObject.addComponent](GameObject.addComponent)
The most common lifecycle methods are awake
, start
, onEnable
, onDisable
update
and onDestroy
.
XR specific callbacks include onEnterXR
, onLeaveXR
, onUpdateXR
, onControllerAdded
and onControllerRemoved
.
To receive pointer events implement onPointerDown
, onPointerUp
, onPointerEnter
, onPointerExit
and onPointerMove
.
Behaviour, const serializable: <T>(type?: Constructor<T> | TypeResolver<T> | (TypeResolver<T> | Constructor<any>)[] | null | undefined) => (_target: any, _propertyKey: string | {
name: string;
}) => void
The serializable attribute should be used to annotate all serialized fields / fields and members that should be serialized and exposed in an editor
serializable } from "@needle-tools/engine";
export class class Rotate
Rotate extends class Behaviour
Needle Engine component base class. Component's are the main building blocks of the Needle Engine.
Derive from
Behaviour
to implement your own using the provided lifecycle methods.
Components can be added to threejs objects using [addComponent](addComponent)
or [GameObject.addComponent](GameObject.addComponent)
The most common lifecycle methods are awake
, start
, onEnable
, onDisable
update
and onDestroy
.
XR specific callbacks include onEnterXR
, onLeaveXR
, onUpdateXR
, onControllerAdded
and onControllerRemoved
.
To receive pointer events implement onPointerDown
, onPointerUp
, onPointerEnter
, onPointerExit
and onPointerMove
.
Behaviour
{
@serializable<unknown>(type?: Constructor<unknown> | TypeResolver<unknown> | (Constructor<any> | TypeResolver<unknown>)[] | null | undefined): (_target: any, _propertyKey: string | {
...;
}) => void
The serializable attribute should be used to annotate all serialized fields / fields and members that should be serialized and exposed in an editor
serializable()
Rotate.speed: number
speed : number = 1;
Rotate.start(): void
called at the beginning of a frame (once per component)
start(){
// logging this is useful for debugging in the browser.
// You can open the developer console (F12) to see what data your component contains
var console: Console
The console
module provides a simple debugging console that is similar to the
JavaScript console mechanism provided by web browsers.
The module exports two specific components:
- A
Console
class with methods such as console.log()
, console.error()
and console.warn()
that can be used to write to any Node.js stream.
- A global
console
instance configured to write to process.stdout
and
process.stderr
. The global console
can be used without calling require('console')
.
Warning: The global console object's methods are neither consistently
synchronous like the browser APIs they resemble, nor are they consistently
asynchronous like all other Node.js streams. See the note on process I/O
for
more information.
Example using the global console
:
console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr
Example using the Console
class:
const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err
console.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to stdout
with newline. Multiple arguments can be passed, with the
first used as the primary message and all additional used as substitution
values similar to printf(3)
(the arguments are all passed to util.format()
).
const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout
See util.format()
for more information.
log(this);
}
// update will be called every frame
Rotate.update(): void
regular callback in a frame (called every frame when implemented)
update(){
this.Component.gameObject: GameObject
the object this component is attached to. Note that this is a threejs Object3D with some additional features
gameObject.Object3D<Object3DEventMap>.rotateY(angle: number): GameObject
Rotates the object around y axis in local space.
rotateY(this.Component.context: Context
Use the context to get access to many Needle Engine features and use physics, timing, access the camera or scene
context.Context.time: Time
access timings (current frame number, deltaTime, timeScale, ...)
time.Time.deltaTime: number
The time in seconds it took to complete the last frame (Read Only).
deltaTime * this.Rotate.speed: number
speed);
}
}
Now inside Unity a new script called Rotate.cs
will be automatically generated. Add the new Unity component to a Cube and save the scene.
The cube is now rotating inside the browser.
Open the chrome developer console by F12
to inspect the log from the Rotate.start
method. This is a helpful practice to learn and debug what fields are exported and currently assigned. In general all public and serializable fields and all public properties are exported.
Now add a new field public float speed = 5
to your Unity component and save it. The Rotate component inspector now shows a speed
field that you can edit. Save the scene (or click the Build
button) and note that the javascript component now has the exported speed
value assigned.
Create component with a custom function
Refer to the Typescript Essentials Guide to learn more about the syntax and language.
import { class Behaviour
Needle Engine component base class. Component's are the main building blocks of the Needle Engine.
Derive from
Behaviour
to implement your own using the provided lifecycle methods.
Components can be added to threejs objects using [addComponent](addComponent)
or [GameObject.addComponent](GameObject.addComponent)
The most common lifecycle methods are awake
, start
, onEnable
, onDisable
update
and onDestroy
.
XR specific callbacks include onEnterXR
, onLeaveXR
, onUpdateXR
, onControllerAdded
and onControllerRemoved
.
To receive pointer events implement onPointerDown
, onPointerUp
, onPointerEnter
, onPointerExit
and onPointerMove
.
Behaviour } from "@needle-tools/engine";
export class class PrintNumberComponent
PrintNumberComponent extends class Behaviour
Needle Engine component base class. Component's are the main building blocks of the Needle Engine.
Derive from
Behaviour
to implement your own using the provided lifecycle methods.
Components can be added to threejs objects using [addComponent](addComponent)
or [GameObject.addComponent](GameObject.addComponent)
The most common lifecycle methods are awake
, start
, onEnable
, onDisable
update
and onDestroy
.
XR specific callbacks include onEnterXR
, onLeaveXR
, onUpdateXR
, onControllerAdded
and onControllerRemoved
.
To receive pointer events implement onPointerDown
, onPointerUp
, onPointerEnter
, onPointerExit
and onPointerMove
.
Behaviour
{
PrintNumberComponent.start(): void
called at the beginning of a frame (once per component)
start(){
this.PrintNumberComponent.printNumber(myNumber: number): void
printNumber(42);
}
private PrintNumberComponent.printNumber(myNumber: number): void
printNumber(myNumber: number
myNumber : number){
var console: Console
The console
module provides a simple debugging console that is similar to the
JavaScript console mechanism provided by web browsers.
The module exports two specific components:
- A
Console
class with methods such as console.log()
, console.error()
and console.warn()
that can be used to write to any Node.js stream.
- A global
console
instance configured to write to process.stdout
and
process.stderr
. The global console
can be used without calling require('console')
.
Warning: The global console object's methods are neither consistently
synchronous like the browser APIs they resemble, nor are they consistently
asynchronous like all other Node.js streams. See the note on process I/O
for
more information.
Example using the global console
:
console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr
Example using the Console
class:
const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err
console.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to stdout
with newline. Multiple arguments can be passed, with the
first used as the primary message and all additional used as substitution
values similar to printf(3)
(the arguments are all passed to util.format()
).
const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout
See util.format()
for more information.
log("My Number is: " + myNumber: number
myNumber);
}
}
Version Control & Unity
While generated C# components use the type name to produce stable GUIDs, we recommend checking in generated components in version control as a good practice.
Component architecture
Components are added to three.js Object3Ds
. This is similar to how Components in Unity are added to GameObjects
. Therefore when we want to access a three.js Object3D, we can access it as this.gameObject
which returns the Object3D
that the component is attached to.
Note: Setting visible
to false on a Object3D will act like SetActive(false)
in Unity - meaning it will also disable all the current components on this object and its children. Update events for inactive components are not being called until visible
is set to true again. If you want to hide an object without affecting components you can just disable the Needle Engine Renderer
component.
Lifecycle methods
Note that lifecycle methods are only being called when they are declared. So only declare update
lifecycle methods when they are actually necessary, otherwise it may hurt performance if you have many components with update loops that do nothing.
Method name | Description |
---|---|
awake() | First method being called when a new component is created |
onEnable() | Called when a component is enabled (e.g. when enabled changes from false to true) |
onDisable() | Called when a component is disabled (e.g. when enabled changes from true to false) |
onDestroy() | called when the Object3D or component is being destroyed |
start() | Called on the start of the first frame after the component was created |
earlyUpdate() | First update event |
update() | Default update event |
lateUpdate() | Called after update |
onBeforeRender() | Last update event before render call |
onAfterRender() | Called after render event |
Physic event methods
Method name | Description |
---|---|
onCollisionEnter(col : Collision) | |
onCollisionStay(col : Collision) | |
onCollisionExit(col : Collision) | |
onTriggerEnter(col : Collision) | |
onTriggerStay(col : Collision) | |
onTriggerExit(col : Collision) |
Input event methods
Method name | Description |
---|---|
onPointerEnter(args : PointerEventData) | Called when a cursor starts to hover over an object (or any of it's children) |
onPointerMove(args : PointerEventData) | Called when a cursor moves over an object (or any of it's children) |
onPointerExit(args : PointerEventData) | Called when a cursor exists (stops hovering) an object |
onPointerDown(args : PointerEventData) | Called when a cursor is pressed over an object |
onPointerUp(args : PointerEventData) | Called when a cursor is released over an object |
onPointerClick(args : PointerEventData) | Called when a cursor is clicked over an object |
XR event methods
requires Needle Engine >= 3.32.0
Method name | Description |
---|---|
supportsXR(mode: XRSessionMode) | Optionally implement if you only want to receive XR callbacks for specific XR modes like immersive-vr or immersive-ar . Return true to notify the system that you want callbacks for the passed in mode |
onBeforeXR(mode: XRSessionMode, init: XRSessionInit) | Called right before a XRSession is requested and can be used to modify the XRSessionInit object |
onEnterXR(args: NeedleXREventArgs) | Callback when this component joins a xr session (or becomes active in a running XR session) |
onUpdateXR(args: NeedleXREventArgs) | Callback when a xr session updates (while it is still active in XR session) |
onLeaveXR(args: NeedleXREventArgs) | allback when this component exists a xr session (or when it becomes inactive in a running XR session) |
onControllerAdded(args: NeedleXRControllerEventArgs) | Callback when a controller is connected/added while in a XR session OR when the component joins a running XR session that has already connected controllers OR when the component becomes active during a running XR session that has already connected controllers |
onControllerRemoved(args: NeedleXRControllerEventArgs) | callback when a controller is removed while in a XR session OR when the component becomes inactive during a running XR session |
Additional XR events
Method name | Description |
---|---|
window.addEventListener("needle-xrsession-start") | CustomEvent that is invoked when a XRSession starts. details contains the NeedleXRSession |
window.addEventListener("needle-xrsession-end") | CustomEvent that is invoked when a XRSession starts. details contains the NeedleXRSession |
onXRSessionStart(args: { session:NeedleXRSession } ) | global event hook. To unsubscribe use offXRSessionStart |
Coroutines
Coroutines can be declared using the JavaScript Generator Syntax.
To start a coroutine, call this.startCoroutine(this.myRoutineName());
Example
import { class Behaviour
Needle Engine component base class. Component's are the main building blocks of the Needle Engine.
Derive from
Behaviour
to implement your own using the provided lifecycle methods.
Components can be added to threejs objects using [addComponent](addComponent)
or [GameObject.addComponent](GameObject.addComponent)
The most common lifecycle methods are awake
, start
, onEnable
, onDisable
update
and onDestroy
.
XR specific callbacks include onEnterXR
, onLeaveXR
, onUpdateXR
, onControllerAdded
and onControllerRemoved
.
To receive pointer events implement onPointerDown
, onPointerUp
, onPointerEnter
, onPointerExit
and onPointerMove
.
Behaviour, enum FrameEvent
FrameEvent } from "@needle-tools/engine";
export class class Rotate
Rotate extends class Behaviour
Needle Engine component base class. Component's are the main building blocks of the Needle Engine.
Derive from
Behaviour
to implement your own using the provided lifecycle methods.
Components can be added to threejs objects using [addComponent](addComponent)
or [GameObject.addComponent](GameObject.addComponent)
The most common lifecycle methods are awake
, start
, onEnable
, onDisable
update
and onDestroy
.
XR specific callbacks include onEnterXR
, onLeaveXR
, onUpdateXR
, onControllerAdded
and onControllerRemoved
.
To receive pointer events implement onPointerDown
, onPointerUp
, onPointerEnter
, onPointerExit
and onPointerMove
.
Behaviour {
Rotate.start(): void
called at the beginning of a frame (once per component)
start() {
// the second argument is optional and allows you to specifiy
// when it should be called in the current frame loop
// coroutine events are called after regular component events of the same name
// for example: Update coroutine events are called after component.update() functions
this.Component.startCoroutine(routine: Generator<unknown, any, unknown>, evt?: FrameEvent | undefined): Generator<unknown, any, unknown>
starts a coroutine (javascript generator function)
yield
will wait for the next frame:
- Use
yield WaitForSeconds(1)
to wait for 1 second.
- Use
yield WaitForFrames(10)
to wait for 10 frames.
- Use
yield new Promise(...)
to wait for a promise to resolve.
startCoroutine(this.Rotate.rotate(): Generator<undefined, void, unknown>
rotate(), enum FrameEvent
FrameEvent.function (enum member) FrameEvent.Update = 1
Update);
}
// this method is called every frame until the component is disabled
*Rotate.rotate(): Generator<undefined, void, unknown>
rotate() {
// keep looping forever
while (true) {
yield;
}
}
}
To stop a coroutine, either exit the routine by returning from it, or cache the return value of startCoroutine
and call this.stopCoroutine(<...>)
. All Coroutines are stopped at onDisable
/ when disabling a component.
Special Lifecycle hooks
Needle Engine also exposes a few lifecycle hooks that you can use to hook into the update loop without having to write a full component.
Those hooks can be inserted at any point in your web application (for example in toplevel scope or in a svelte component)
Method name | Description |
---|---|
onInitialized(cb, options) | Called when a new context is initialized (before the first frame) |
onClear(cb, options) | Register a callback before the engine context is cleared |
onDestroy(cb, options) | Register a callback in the engine before the context is destroyed |
onStart(cb, options) | Called directly after components start at the beginning of a frame |
onUpdate(cb, options) | Called directly after components update |
onBeforeRender(cb, options) | called before calling render |
onAfterRender(cb, options) | called before calling render |
For example (See example on stackblitz)
// this can be put into e.g. main.ts or a svelte component (similar to onMount)
import { class Context
The context is the main object that holds all the data and state of the Needle Engine.
It can be used to access the scene, renderer, camera, input, physics, networking, and more.
Context, function onUpdate(cb: LifecycleMethod, opts?: LifecycleMethodOptions): () => void
Register a callback in the engine update event
This is called every frame
onUpdate, function onBeforeRender(cb: LifecycleMethod, opts?: LifecycleMethodOptions): () => void
Register a callback in the engine onBeforeRender event
This is called every frame before the main camera renders
onBeforeRender, function onAfterRender(cb: LifecycleMethod, opts?: LifecycleMethodOptions): () => void
Register a callback in the engine onAfterRender event
This is called every frame after the main camera has rendered
onAfterRender } from "@needle-tools/engine"
function onUpdate(cb: LifecycleMethod, opts?: LifecycleMethodOptions | undefined): () => void
Register a callback in the engine update event
This is called every frame
onUpdate((ctx: Context
ctx: class Context
The context is the main object that holds all the data and state of the Needle Engine.
It can be used to access the scene, renderer, camera, input, physics, networking, and more.
Context) => {
// do something... e.g. access the scene via ctx.scene
var console: Console
The console
module provides a simple debugging console that is similar to the
JavaScript console mechanism provided by web browsers.
The module exports two specific components:
- A
Console
class with methods such as console.log()
, console.error()
and console.warn()
that can be used to write to any Node.js stream.
- A global
console
instance configured to write to process.stdout
and
process.stderr
. The global console
can be used without calling require('console')
.
Warning: The global console object's methods are neither consistently
synchronous like the browser APIs they resemble, nor are they consistently
asynchronous like all other Node.js streams. See the note on process I/O
for
more information.
Example using the global console
:
console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr
Example using the Console
class:
const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err
console.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to stdout
with newline. Multiple arguments can be passed, with the
first used as the primary message and all additional used as substitution
values similar to printf(3)
(the arguments are all passed to util.format()
).
const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout
See util.format()
for more information.
log("UPDATE", ctx: Context
ctx.Context.time: Time
access timings (current frame number, deltaTime, timeScale, ...)
time.Time.frame: number
same as frameCount
frame);
});
function onBeforeRender(cb: LifecycleMethod, opts?: LifecycleMethodOptions | undefined): () => void
Register a callback in the engine onBeforeRender event
This is called every frame before the main camera renders
onBeforeRender((ctx: Context
ctx: class Context
The context is the main object that holds all the data and state of the Needle Engine.
It can be used to access the scene, renderer, camera, input, physics, networking, and more.
Context) => {
// this event is only called once because of the { once: true } argument
var console: Console
The console
module provides a simple debugging console that is similar to the
JavaScript console mechanism provided by web browsers.
The module exports two specific components:
- A
Console
class with methods such as console.log()
, console.error()
and console.warn()
that can be used to write to any Node.js stream.
- A global
console
instance configured to write to process.stdout
and
process.stderr
. The global console
can be used without calling require('console')
.
Warning: The global console object's methods are neither consistently
synchronous like the browser APIs they resemble, nor are they consistently
asynchronous like all other Node.js streams. See the note on process I/O
for
more information.
Example using the global console
:
console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr
Example using the Console
class:
const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err
console.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to stdout
with newline. Multiple arguments can be passed, with the
first used as the primary message and all additional used as substitution
values similar to printf(3)
(the arguments are all passed to util.format()
).
const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout
See util.format()
for more information.
log("ON BEFORE RENDER", ctx: Context
ctx.Context.time: Time
access timings (current frame number, deltaTime, timeScale, ...)
time.Time.frame: number
same as frameCount
frame);
}, { once?: boolean | undefined
If true, the callback will only be called once
once: true } );
// Every event hook returns a method to unsubscribe from the event
const const unsubscribe: () => void
unsubscribe = function onAfterRender(cb: LifecycleMethod, opts?: LifecycleMethodOptions | undefined): () => void
Register a callback in the engine onAfterRender event
This is called every frame after the main camera has rendered
onAfterRender((ctx: Context
ctx: class Context
The context is the main object that holds all the data and state of the Needle Engine.
It can be used to access the scene, renderer, camera, input, physics, networking, and more.
Context) => {
var console: Console
The console
module provides a simple debugging console that is similar to the
JavaScript console mechanism provided by web browsers.
The module exports two specific components:
- A
Console
class with methods such as console.log()
, console.error()
and console.warn()
that can be used to write to any Node.js stream.
- A global
console
instance configured to write to process.stdout
and
process.stderr
. The global console
can be used without calling require('console')
.
Warning: The global console object's methods are neither consistently
synchronous like the browser APIs they resemble, nor are they consistently
asynchronous like all other Node.js streams. See the note on process I/O
for
more information.
Example using the global console
:
console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr
Example using the Console
class:
const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err
console.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to stdout
with newline. Multiple arguments can be passed, with the
first used as the primary message and all additional used as substitution
values similar to printf(3)
(the arguments are all passed to util.format()
).
const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout
See util.format()
for more information.
log("ON AFTER RENDER", ctx: Context
ctx.Context.time: Time
access timings (current frame number, deltaTime, timeScale, ...)
time.Time.frame: number
same as frameCount
frame);
});
// Unsubscribe from the event at any time
function setTimeout<[]>(callback: () => void, ms?: number | undefined): NodeJS.Timeout (+2 overloads)
Schedules execution of a one-time callback
after delay
milliseconds.
The callback
will likely not be invoked in precisely delay
milliseconds.
Node.js makes no guarantees about the exact timing of when callbacks will fire,
nor of their ordering. The callback will be called as close as possible to the
time specified.
When delay
is larger than 2147483647
or less than 1
, the delay
will be set to 1
. Non-integer delays are truncated to an integer.
If callback
is not a function, a TypeError
will be thrown.
This method has a custom variant for promises that is available using timersPromises.setTimeout()
.
setTimeout(()=> const unsubscribe: () => void
unsubscribe(), 1000);
Finding, adding and removing components
To access other components, use the static methods on GameObject
or this.gameObject
methods. For example, to access a Renderer
component in the parent use GameObject.getComponentInParent(this.gameObject, Renderer)
or this.gameObject.getComponentInParent(Renderer)
.
Example:
import { class Behaviour
Needle Engine component base class. Component's are the main building blocks of the Needle Engine.
Derive from
Behaviour
to implement your own using the provided lifecycle methods.
Components can be added to threejs objects using [addComponent](addComponent)
or [GameObject.addComponent](GameObject.addComponent)
The most common lifecycle methods are awake
, start
, onEnable
, onDisable
update
and onDestroy
.
XR specific callbacks include onEnterXR
, onLeaveXR
, onUpdateXR
, onControllerAdded
and onControllerRemoved
.
To receive pointer events implement onPointerDown
, onPointerUp
, onPointerEnter
, onPointerExit
and onPointerMove
.
Behaviour, class GameObject
All {@type Object3D} types that are loaded in Needle Engine do automatically receive the GameObject extensions like addComponent
etc.
Many of the GameObject methods can be imported directly via @needle-tools/engine
as well:
import { addComponent } from "@needle-tools/engine";
GameObject, class Renderer
Renderer } from "@needle-tools/engine";
export class class MyComponent
MyComponent extends class Behaviour
Needle Engine component base class. Component's are the main building blocks of the Needle Engine.
Derive from
Behaviour
to implement your own using the provided lifecycle methods.
Components can be added to threejs objects using [addComponent](addComponent)
or [GameObject.addComponent](GameObject.addComponent)
The most common lifecycle methods are awake
, start
, onEnable
, onDisable
update
and onDestroy
.
XR specific callbacks include onEnterXR
, onLeaveXR
, onUpdateXR
, onControllerAdded
and onControllerRemoved
.
To receive pointer events implement onPointerDown
, onPointerUp
, onPointerEnter
, onPointerExit
and onPointerMove
.
Behaviour {
MyComponent.start(): void
called at the beginning of a frame (once per component)
start() {
const const renderer: Renderer | null
renderer = class GameObject
All {@type Object3D} types that are loaded in Needle Engine do automatically receive the GameObject extensions like addComponent
etc.
Many of the GameObject methods can be imported directly via @needle-tools/engine
as well:
import { addComponent } from "@needle-tools/engine";
GameObject.GameObject.getComponentInParent<Renderer>(go: IGameObject | Object3D<Object3DEventMap>, typeName: Constructor<Renderer>): Renderer | null
getComponentInParent(this.Component.gameObject: GameObject
the object this component is attached to. Note that this is a threejs Object3D with some additional features
gameObject, class Renderer
Renderer);
var console: Console
The console
module provides a simple debugging console that is similar to the
JavaScript console mechanism provided by web browsers.
The module exports two specific components:
- A
Console
class with methods such as console.log()
, console.error()
and console.warn()
that can be used to write to any Node.js stream.
- A global
console
instance configured to write to process.stdout
and
process.stderr
. The global console
can be used without calling require('console')
.
Warning: The global console object's methods are neither consistently
synchronous like the browser APIs they resemble, nor are they consistently
asynchronous like all other Node.js streams. See the note on process I/O
for
more information.
Example using the global console
:
console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr
Example using the Console
class:
const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err
console.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to stdout
with newline. Multiple arguments can be passed, with the
first used as the primary message and all additional used as substitution
values similar to printf(3)
(the arguments are all passed to util.format()
).
const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout
See util.format()
for more information.
log(const renderer: Renderer | null
renderer);
}
}
Some of the available methods:
Method | |
---|---|
GameObject.instantiate(Object3D, InstantiateOptions) | creates a new instance of this object including new instances of all its components |
GameObject.destroy(Object3D | Component) | destroy a component or Object3D (and its components) |
GameObject.addNewComponent(Object3D, Type) | adds (and creates) a new component for a type to the provided object. Note that awake and onEnable is already called when the component is returned |
GameObject.addComponent(Object3D, Component) | moves a component instance to the provided object. It is useful if you already have an instance e.g. when you create a component with e.g. new MyComponent() and then attach it to a object |
GameObject.removeComponent(Component) | removes a component from a gameObject |
GameObject.getComponent(Object3D, Type) | returns the first component matching a type on the provided object. |
GameObject.getComponents(Object3D, Type) | returns all components matching a type on the provided object. |
GameObject.getComponentInChildren | same as getComponent but also searches in child objects. |
GameObject.getComponentsInChildren | same as getComponents but also searches in child objects. |
GameObject.getComponentInParent | same as getComponent but also searches in parent objects. |
GameObject.getComponentsInParent | same as getComponents but also searches in parent objects. |
GameObject.findObjectOfType | searches the whole scene for a type. |
GameObject.findObjectsOfType | searches the whole scene for all matching types. |
Three.js and the HTML DOM
The context refers to the runtime inside a web component.
The three.js scene lives inside a custom HTML component called <needle-engine>
(see the index.html in your project). You can access the <needle-engine>
web component using this.context.domElement
.
This architecture allows for potentially having multiple needle WebGL scenes on the same webpage, that can either run on their own or communicate between each other as parts of your webpage.
Access the scene
To access the current scene from a component you use this.scene
which is equivalent to this.context.scene
, this gives you the root three.js scene object.
To traverse the hierarchy from a component you can either iterate over the children of an object
with a for loop:
for(let let i: number
i = 0; let i: number
i < this.gameObject.children; let i: number
i++)
var console: Console
The console
module provides a simple debugging console that is similar to the
JavaScript console mechanism provided by web browsers.
The module exports two specific components:
- A
Console
class with methods such as console.log()
, console.error()
and console.warn()
that can be used to write to any Node.js stream.
- A global
console
instance configured to write to process.stdout
and
process.stderr
. The global console
can be used without calling require('console')
.
Warning: The global console object's methods are neither consistently
synchronous like the browser APIs they resemble, nor are they consistently
asynchronous like all other Node.js streams. See the note on process I/O
for
more information.
Example using the global console
:
console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr
Example using the Console
class:
const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err
console.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to stdout
with newline. Multiple arguments can be passed, with the
first used as the primary message and all additional used as substitution
values similar to printf(3)
(the arguments are all passed to util.format()
).
const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout
See util.format()
for more information.
log(this.gameObject.children[let i: number
i]);
or you can iterate using the foreach
equivalent:
for(const const child: any
child of this.gameObject.children) {
var console: Console
The console
module provides a simple debugging console that is similar to the
JavaScript console mechanism provided by web browsers.
The module exports two specific components:
- A
Console
class with methods such as console.log()
, console.error()
and console.warn()
that can be used to write to any Node.js stream.
- A global
console
instance configured to write to process.stdout
and
process.stderr
. The global console
can be used without calling require('console')
.
Warning: The global console object's methods are neither consistently
synchronous like the browser APIs they resemble, nor are they consistently
asynchronous like all other Node.js streams. See the note on process I/O
for
more information.
Example using the global console
:
console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr
Example using the Console
class:
const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err
console.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to stdout
with newline. Multiple arguments can be passed, with the
first used as the primary message and all additional used as substitution
values similar to printf(3)
(the arguments are all passed to util.format()
).
const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout
See util.format()
for more information.
log(const child: any
child);
}
You can also use three.js specific methods to quickly iterate all objects recursively using the traverse
method:
import { class Object3D<TEventMap extends Object3DEventMap = Object3DEventMap>
This is the base class for most objects in three.js and provides a set of properties and methods for manipulating objects in 3D space.
Object3D } from "three";
this.gameObject.traverse((obj: Object3D<Object3DEventMap>
obj: class Object3D<TEventMap extends Object3DEventMap = Object3DEventMap>
This is the base class for most objects in three.js and provides a set of properties and methods for manipulating objects in 3D space.
Object3D) => var console: Console
The console
module provides a simple debugging console that is similar to the
JavaScript console mechanism provided by web browsers.
The module exports two specific components:
- A
Console
class with methods such as console.log()
, console.error()
and console.warn()
that can be used to write to any Node.js stream.
- A global
console
instance configured to write to process.stdout
and
process.stderr
. The global console
can be used without calling require('console')
.
Warning: The global console object's methods are neither consistently
synchronous like the browser APIs they resemble, nor are they consistently
asynchronous like all other Node.js streams. See the note on process I/O
for
more information.
Example using the global console
:
console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr
Example using the Console
class:
const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err
console.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to stdout
with newline. Multiple arguments can be passed, with the
first used as the primary message and all additional used as substitution
values similar to printf(3)
(the arguments are all passed to util.format()
).
const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout
See util.format()
for more information.
log(obj: Object3D<Object3DEventMap>
obj));
or to just traverse visible objects use traverseVisible
instead.
Another option that is quite useful when you just want to iterate objects being renderable you can query all renderer components and iterate over them like so:
import { class Renderer
Renderer } from "@needle-tools/engine";
for(const const renderer: any
renderer of this.gameObject.getComponentsInChildren(class Renderer
Renderer))
var console: Console
The console
module provides a simple debugging console that is similar to the
JavaScript console mechanism provided by web browsers.
The module exports two specific components:
- A
Console
class with methods such as console.log()
, console.error()
and console.warn()
that can be used to write to any Node.js stream.
- A global
console
instance configured to write to process.stdout
and
process.stderr
. The global console
can be used without calling require('console')
.
Warning: The global console object's methods are neither consistently
synchronous like the browser APIs they resemble, nor are they consistently
asynchronous like all other Node.js streams. See the note on process I/O
for
more information.
Example using the global console
:
console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr
Example using the Console
class:
const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err
console.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to stdout
with newline. Multiple arguments can be passed, with the
first used as the primary message and all additional used as substitution
values similar to printf(3)
(the arguments are all passed to util.format()
).
const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout
See util.format()
for more information.
log(const renderer: any
renderer);
For more information about getting components see the next section.
Time
Use this.context.time
to get access to time data:
this.context.time.time
is the time since the application started runningthis.context.time.deltaTime
is the time that has passed since the last framethis.context.time.frameCount
is the number of frames that have passed since the application startedthis.context.time.realtimeSinceStartup
is the unscaled time since the application has started running
It is also possible to use this.context.time.timeScale
to deliberately slow down time for e.g. slow motion effects.
Input
Receive input data for the object the component is on:
import { class Behaviour
Needle Engine component base class. Component's are the main building blocks of the Needle Engine.
Derive from
Behaviour
to implement your own using the provided lifecycle methods.
Components can be added to threejs objects using [addComponent](addComponent)
or [GameObject.addComponent](GameObject.addComponent)
The most common lifecycle methods are awake
, start
, onEnable
, onDisable
update
and onDestroy
.
XR specific callbacks include onEnterXR
, onLeaveXR
, onUpdateXR
, onControllerAdded
and onControllerRemoved
.
To receive pointer events implement onPointerDown
, onPointerUp
, onPointerEnter
, onPointerExit
and onPointerMove
.
Behaviour } from "@needle-tools/engine";
export class class MyScript
MyScript extends class Behaviour
Needle Engine component base class. Component's are the main building blocks of the Needle Engine.
Derive from
Behaviour
to implement your own using the provided lifecycle methods.
Components can be added to threejs objects using [addComponent](addComponent)
or [GameObject.addComponent](GameObject.addComponent)
The most common lifecycle methods are awake
, start
, onEnable
, onDisable
update
and onDestroy
.
XR specific callbacks include onEnterXR
, onLeaveXR
, onUpdateXR
, onControllerAdded
and onControllerRemoved
.
To receive pointer events implement onPointerDown
, onPointerUp
, onPointerEnter
, onPointerExit
and onPointerMove
.
Behaviour
{
MyScript.onPointerDown(): void
Called when a button is started to being pressed on an object (or a child object)
onPointerDown() {
var console: Console
The console
module provides a simple debugging console that is similar to the
JavaScript console mechanism provided by web browsers.
The module exports two specific components:
- A
Console
class with methods such as console.log()
, console.error()
and console.warn()
that can be used to write to any Node.js stream.
- A global
console
instance configured to write to process.stdout
and
process.stderr
. The global console
can be used without calling require('console')
.
Warning: The global console object's methods are neither consistently
synchronous like the browser APIs they resemble, nor are they consistently
asynchronous like all other Node.js streams. See the note on process I/O
for
more information.
Example using the global console
:
console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr
Example using the Console
class:
const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err
console.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to stdout
with newline. Multiple arguments can be passed, with the
first used as the primary message and all additional used as substitution
values similar to printf(3)
(the arguments are all passed to util.format()
).
const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout
See util.format()
for more information.
log("POINTER DOWN on " + this.Component.gameObject: GameObject
the object this component is attached to. Note that this is a threejs Object3D with some additional features
gameObject.Object3D<Object3DEventMap>.name: string
Optional name of the object
name);
}
}
You can also subscribe to global events in the InputEvents
enum like so:
import { class Behaviour
Needle Engine component base class. Component's are the main building blocks of the Needle Engine.
Derive from
Behaviour
to implement your own using the provided lifecycle methods.
Components can be added to threejs objects using [addComponent](addComponent)
or [GameObject.addComponent](GameObject.addComponent)
The most common lifecycle methods are awake
, start
, onEnable
, onDisable
update
and onDestroy
.
XR specific callbacks include onEnterXR
, onLeaveXR
, onUpdateXR
, onControllerAdded
and onControllerRemoved
.
To receive pointer events implement onPointerDown
, onPointerUp
, onPointerEnter
, onPointerExit
and onPointerMove
.
Behaviour, const enum InputEvents
InputEvents, class NEPointerEvent
The Needle Engine Pointer Event is a custom event that extends the PointerEvent. It holds additional information like the device index, the origin of the event, the mode of the event (e.g. screen or spatial), the ray in world space, the space of the device, and more.
NEPointerEvent } from "@needle-tools/engine";
export class class MyScript
MyScript extends class Behaviour
Needle Engine component base class. Component's are the main building blocks of the Needle Engine.
Derive from
Behaviour
to implement your own using the provided lifecycle methods.
Components can be added to threejs objects using [addComponent](addComponent)
or [GameObject.addComponent](GameObject.addComponent)
The most common lifecycle methods are awake
, start
, onEnable
, onDisable
update
and onDestroy
.
XR specific callbacks include onEnterXR
, onLeaveXR
, onUpdateXR
, onControllerAdded
and onControllerRemoved
.
To receive pointer events implement onPointerDown
, onPointerUp
, onPointerEnter
, onPointerExit
and onPointerMove
.
Behaviour
{
MyScript.onEnable(): void
called every time when the component gets enabled (this is invoked after awake and before start)
or when it becomes active in the hierarchy (e.g. if a parent object or this.gameObject gets set to visible)
onEnable() {
this.Component.context: Context
Use the context to get access to many Needle Engine features and use physics, timing, access the camera or scene
context.Context.input: Input
input.Input.addEventListener(type: InputEvents | InputEventNames, callback: InputEventListener, options?: EventListenerOptions | undefined): void
Adds an event listener for the specified event type. The callback will be called when the event is triggered.
addEventListener(const enum InputEvents
InputEvents.function (enum member) InputEvents.PointerDown = "pointerdown"
PointerDown, this.MyScript.inputPointerDown: (evt: NEPointerEvent) => void
inputPointerDown);
}
MyScript.onDisable(): void
called every time the component gets disabled or if a parent object (or this.gameObject) gets set to invisible
onDisable() {
// it is recommended to also unsubscribe from events when your component becomes inactive
this.Component.context: Context
Use the context to get access to many Needle Engine features and use physics, timing, access the camera or scene
context.Context.input: Input
input.Input.removeEventListener(type: InputEvents | InputEventNames, callback: InputEventListener, options?: EventListenerOptions | undefined): void
Removes the event listener from the specified event type. If no queue is specified the listener will be removed from all queues.
removeEventListener(const enum InputEvents
InputEvents.function (enum member) InputEvents.PointerDown = "pointerdown"
PointerDown, this.MyScript.inputPointerDown: (evt: NEPointerEvent) => void
inputPointerDown);
}
// @nonSerialized
MyScript.inputPointerDown: (evt: NEPointerEvent) => void
inputPointerDown = (evt: NEPointerEvent
evt: class NEPointerEvent
The Needle Engine Pointer Event is a custom event that extends the PointerEvent. It holds additional information like the device index, the origin of the event, the mode of the event (e.g. screen or spatial), the ray in world space, the space of the device, and more.
NEPointerEvent) => { var console: Console
The console
module provides a simple debugging console that is similar to the
JavaScript console mechanism provided by web browsers.
The module exports two specific components:
- A
Console
class with methods such as console.log()
, console.error()
and console.warn()
that can be used to write to any Node.js stream.
- A global
console
instance configured to write to process.stdout
and
process.stderr
. The global console
can be used without calling require('console')
.
Warning: The global console object's methods are neither consistently
synchronous like the browser APIs they resemble, nor are they consistently
asynchronous like all other Node.js streams. See the note on process I/O
for
more information.
Example using the global console
:
console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr
Example using the Console
class:
const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err
console.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to stdout
with newline. Multiple arguments can be passed, with the
first used as the primary message and all additional used as substitution
values similar to printf(3)
(the arguments are all passed to util.format()
).
const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout
See util.format()
for more information.
log("POINTER DOWN anywhere on the <needle-engine> element"); }
}
Or use this.context.input
if you want to poll input state every frame:
import { class Behaviour
Needle Engine component base class. Component's are the main building blocks of the Needle Engine.
Derive from
Behaviour
to implement your own using the provided lifecycle methods.
Components can be added to threejs objects using [addComponent](addComponent)
or [GameObject.addComponent](GameObject.addComponent)
The most common lifecycle methods are awake
, start
, onEnable
, onDisable
update
and onDestroy
.
XR specific callbacks include onEnterXR
, onLeaveXR
, onUpdateXR
, onControllerAdded
and onControllerRemoved
.
To receive pointer events implement onPointerDown
, onPointerUp
, onPointerEnter
, onPointerExit
and onPointerMove
.
Behaviour } from "@needle-tools/engine";
export class class MyScript
MyScript extends class Behaviour
Needle Engine component base class. Component's are the main building blocks of the Needle Engine.
Derive from
Behaviour
to implement your own using the provided lifecycle methods.
Components can be added to threejs objects using [addComponent](addComponent)
or [GameObject.addComponent](GameObject.addComponent)
The most common lifecycle methods are awake
, start
, onEnable
, onDisable
update
and onDestroy
.
XR specific callbacks include onEnterXR
, onLeaveXR
, onUpdateXR
, onControllerAdded
and onControllerRemoved
.
To receive pointer events implement onPointerDown
, onPointerUp
, onPointerEnter
, onPointerExit
and onPointerMove
.
Behaviour
{
MyScript.update(): void
regular callback in a frame (called every frame when implemented)
update() {
if(this.Component.context: Context
Use the context to get access to many Needle Engine features and use physics, timing, access the camera or scene
context.Context.input: Input
input.Input.getPointerDown(i: number): boolean
getPointerDown(0)){
var console: Console
The console
module provides a simple debugging console that is similar to the
JavaScript console mechanism provided by web browsers.
The module exports two specific components:
- A
Console
class with methods such as console.log()
, console.error()
and console.warn()
that can be used to write to any Node.js stream.
- A global
console
instance configured to write to process.stdout
and
process.stderr
. The global console
can be used without calling require('console')
.
Warning: The global console object's methods are neither consistently
synchronous like the browser APIs they resemble, nor are they consistently
asynchronous like all other Node.js streams. See the note on process I/O
for
more information.
Example using the global console
:
console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr
Example using the Console
class:
const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err
console.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to stdout
with newline. Multiple arguments can be passed, with the
first used as the primary message and all additional used as substitution
values similar to printf(3)
(the arguments are all passed to util.format()
).
const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout
See util.format()
for more information.
log("POINTER DOWN anywhere")
}
}
}
If you want to handle inputs yourself you can also subscribe to all events the browser provides (there are a ton). For example to subscribe to the browsers click event you can write:
import { class Behaviour
Needle Engine component base class. Component's are the main building blocks of the Needle Engine.
Derive from
Behaviour
to implement your own using the provided lifecycle methods.
Components can be added to threejs objects using [addComponent](addComponent)
or [GameObject.addComponent](GameObject.addComponent)
The most common lifecycle methods are awake
, start
, onEnable
, onDisable
update
and onDestroy
.
XR specific callbacks include onEnterXR
, onLeaveXR
, onUpdateXR
, onControllerAdded
and onControllerRemoved
.
To receive pointer events implement onPointerDown
, onPointerUp
, onPointerEnter
, onPointerExit
and onPointerMove
.
Behaviour } from "@needle-tools/engine";
export class class MyScript
MyScript extends class Behaviour
Needle Engine component base class. Component's are the main building blocks of the Needle Engine.
Derive from
Behaviour
to implement your own using the provided lifecycle methods.
Components can be added to threejs objects using [addComponent](addComponent)
or [GameObject.addComponent](GameObject.addComponent)
The most common lifecycle methods are awake
, start
, onEnable
, onDisable
update
and onDestroy
.
XR specific callbacks include onEnterXR
, onLeaveXR
, onUpdateXR
, onControllerAdded
and onControllerRemoved
.
To receive pointer events implement onPointerDown
, onPointerUp
, onPointerEnter
, onPointerExit
and onPointerMove
.
Behaviour
{
MyScript.onEnable(): void
called every time when the component gets enabled (this is invoked after awake and before start)
or when it becomes active in the hierarchy (e.g. if a parent object or this.gameObject gets set to visible)
onEnable() {
var window: Window & typeof globalThis
window.addEventListener<"click">(type: "click", listener: (this: Window, ev: MouseEvent) => any, options?: boolean | AddEventListenerOptions | undefined): void (+1 overload)
Appends an event listener for events whose type attribute value is type. The callback argument sets the callback that will be invoked when the event is dispatched.
The options argument sets listener-specific options. For compatibility this can be a boolean, in which case the method behaves exactly as if the value was specified as options's capture.
When set to true, options's capture prevents callback from being invoked when the event's eventPhase attribute value is BUBBLING_PHASE. When false (or not present), callback will not be invoked when event's eventPhase attribute value is CAPTURING_PHASE. Either way, callback will be invoked if event's eventPhase attribute value is AT_TARGET.
When set to true, options's passive indicates that the callback will not cancel the event by invoking preventDefault(). This is used to enable performance optimizations described in § 2.8 Observing event listeners.
When set to true, options's once indicates that the callback will only be invoked once after which the event listener will be removed.
If an AbortSignal is passed for options's signal, then the event listener will be removed when signal is aborted.
The event listener is appended to target's event listener list and is not appended if it has the same type, callback, and capture.
addEventListener("click", this.MyScript.windowClick: () => void
windowClick);
}
MyScript.onDisable(): void
called every time the component gets disabled or if a parent object (or this.gameObject) gets set to invisible
onDisable() {
// unsubscribe again when the component is disabled
var window: Window & typeof globalThis
window.removeEventListener<"click">(type: "click", listener: (this: Window, ev: MouseEvent) => any, options?: boolean | EventListenerOptions | undefined): void (+1 overload)
Removes the event listener in target's event listener list with the same type, callback, and options.
removeEventListener("click", this.MyScript.windowClick: () => void
windowClick);
}
MyScript.windowClick: () => void
windowClick = () => { var console: Console
The console
module provides a simple debugging console that is similar to the
JavaScript console mechanism provided by web browsers.
The module exports two specific components:
- A
Console
class with methods such as console.log()
, console.error()
and console.warn()
that can be used to write to any Node.js stream.
- A global
console
instance configured to write to process.stdout
and
process.stderr
. The global console
can be used without calling require('console')
.
Warning: The global console object's methods are neither consistently
synchronous like the browser APIs they resemble, nor are they consistently
asynchronous like all other Node.js streams. See the note on process I/O
for
more information.
Example using the global console
:
console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr
Example using the Console
class:
const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err
console.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to stdout
with newline. Multiple arguments can be passed, with the
first used as the primary message and all additional used as substitution
values similar to printf(3)
(the arguments are all passed to util.format()
).
const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout
See util.format()
for more information.
log("CLICK anywhere on the page, not just on <needle-engine>"); }
}
Note that in this case you have to handle all cases yourself. For example you may need to use different events if your user is visiting your website on desktop vs mobile vs a VR device. These cases are automatically handled by the Needle Engine input events (e.g. PointerDown
is raised both for mouse down, touch down and in case of VR on controller button down).
Physics
Use this.context.physics.raycast()
to perform a raycast and get a list of intersections. If you dont pass in any options the raycast is performed from the mouse position (or first touch position) in screenspace using the currently active mainCamera
. You can also pass in a RaycastOptions
object that has various settings like maxDistance
, the camera to be used or the layers to be tested against.
Use this.context.physics.raycastFromRay(your_ray)
to perform a raycast using a three.js ray
Note that the calls above are by default raycasting against visible scene objects. That is different to Unity where you always need colliders to hit objects. The default three.js solution has both pros and cons where one major con is that it can perform quite slow depending on your scene geometry. It may be especially slow when raycasting against skinned meshes. It is therefor recommended to usually set objects with SkinnedMeshRenderers in Unity to the Ignore Raycast
layer which will then be ignored by default by Needle Engine as well.
Another option is to use the physics raycast methods which will only return hits with colliders in the scene.
const const hit: any
hit = this.context.physics.engine?.raycast();
Here is a editable example for physics raycast
Networking
Networking methods can be accessed via this.context.connection
. Please refer to the networking docs for further information.
Accessing Needle Engine and components from anywhere
It is possible to access all the functionality described above using regular JavaScript code that is not inside components and lives somewhere else. All the components and functionality of the needle runtime is accessible via the global Needle
namespace (you can write console.log(Needle)
to get an overview)
You can find components using Needle.findObjectOfType(Needle.AudioSource)
for example. It is recommended to cache those references, as searching the whole scene repeatedly is expensive. See the list for finding adding and removing components above.
For getting callbacks for the initial scene load see the following example:
<needle-engine loadstart="loadingStarted" progress="loadingProgress" loadfinished="loadingFinished"></needle-engine>
<script type="text/javascript">
function loadingStarted() { console.log("START") }
function loadingProgress() { console.log("LOADING...") }
function loadingFinished() { console.log("FINISHED!") }
</script>
You can also subscribe to the globale NeedleEngine
(sometimes also referred to as ContextRegistry) to receive a callback when a Needle Engine context has been created or to access all available contexts:
import { class NeedleEngine
Use to register to various Needle Engine context events and to get access to all current instances
e.g. when being created in the DOM
NeedleEngine, class GameObject
All {@type Object3D} types that are loaded in Needle Engine do automatically receive the GameObject extensions like addComponent
etc.
Many of the GameObject methods can be imported directly via @needle-tools/engine
as well:
import { addComponent } from "@needle-tools/engine";
GameObject, class Behaviour
Needle Engine component base class. Component's are the main building blocks of the Needle Engine.
Derive from
Behaviour
to implement your own using the provided lifecycle methods.
Components can be added to threejs objects using [addComponent](addComponent)
or [GameObject.addComponent](GameObject.addComponent)
The most common lifecycle methods are awake
, start
, onEnable
, onDisable
update
and onDestroy
.
XR specific callbacks include onEnterXR
, onLeaveXR
, onUpdateXR
, onControllerAdded
and onControllerRemoved
.
To receive pointer events implement onPointerDown
, onPointerUp
, onPointerEnter
, onPointerExit
and onPointerMove
.
Behaviour } from "@needle-tools/engine";
class NeedleEngine
Use to register to various Needle Engine context events and to get access to all current instances
e.g. when being created in the DOM
NeedleEngine.ContextRegistry.addContextCreatedCallback(callback: ContextCallback): void
Register a callback to be called when a context is created
addContextCreatedCallback((args: ContextEventArgs
args) => {
const const context: Context
context = args: ContextEventArgs
args.context: Context
context;
const const scene: Scene
scene = const context: Context
context.Context.scene: Scene
scene;
const const myInstance: YourComponentType | null
myInstance = class GameObject
All {@type Object3D} types that are loaded in Needle Engine do automatically receive the GameObject extensions like addComponent
etc.
Many of the GameObject methods can be imported directly via @needle-tools/engine
as well:
import { addComponent } from "@needle-tools/engine";
GameObject.GameObject.getComponentInChildren<YourComponentType>(go: IGameObject | Object3D<Object3DEventMap>, typeName: Constructor<YourComponentType>): YourComponentType | null
getComponentInChildren(const scene: Scene
scene, class YourComponentType
YourComponentType);
});
Another option is using the onInitialized(ctx => {})
lifecycle hook
You can also access all available contexts via NeedleEngine.Registered
which returns the internal array. (Note that this array should not be modified but can be used to iterate all active contexts to modify settings, e.g. set all contexts to context.isPaused = true
)
Below you find a list of available events on the static NeedleEngine
type.
You can subscribe to those events via NeedleEngine.registerCallback(ContextEvent.ContextCreated, (args) => {})
ContextEvent options | |
---|---|
ContextEvent.ContextRegistered | Called when the context is registered to the registry. |
ContextEvent.ContextCreationStart | Called before the first glb is loaded and can be used to initialize the physics engine. Can return a promise |
ContextEvent.ContextCreated | Called when the context has been created before the first frame |
ContextEvent.ContextDestroyed | Called when the context has been destroyed |
ContextEvent.MissingCamera | Called when the context could not find a camera, currently only called during creation |
ContextEvent.ContextClearing | Called when the context is being cleared: all objects in the scene are being destroyed and internal state is reset |
ContextEvent.ContextCleared | Called after the context has been cleared |
Gizmos
The static Gizmos
class can be used to draw lines, shapes and text which is mostly useful for debugging.
All gizmos function have multiple options for e.g. colors or for how long they should be displayed in the scene. Internally they are cached and re-used.
Gizmos | |
---|---|
Gizmos.DrawLabel | Draws a label with a background optionally. It can be attached to an object. Returns a Label handle which can be used to update the text. |
Gizmos.DrawRay | Takes an origin and direction in worldspace to draw an infinite ray line |
Gizmos.DrawDirection | Takes a origin and direction to draw a direction in worldspace |
Gizmos.DrawLine | Takes two vec3 worldspace points to draw a line |
Gizmos.DrawWireSphere | Draws a wireframe sphere in worldspace |
Gizmos.DrawSphere | Draws a solid sphere in worldspace |
Gizmos.DrawWireBox | Draws a wireframe box in worldspace |
Gizmos.DrawWireBox3 | Draws a wireframe box3 |
Gizmos.DrawArrow | Draws an arrow taking two points in worldspace |
Serialization / Components in glTF files
To embed components and recreate components with their correct types in glTF, we also need to save non-primitive types (everything that is not a Number
, Boolean
or String
). You can do so is adding a @serializable(<type>)
decorator above your field or property.
Example:
import { class Behaviour
Needle Engine component base class. Component's are the main building blocks of the Needle Engine.
Derive from
Behaviour
to implement your own using the provided lifecycle methods.
Components can be added to threejs objects using [addComponent](addComponent)
or [GameObject.addComponent](GameObject.addComponent)
The most common lifecycle methods are awake
, start
, onEnable
, onDisable
update
and onDestroy
.
XR specific callbacks include onEnterXR
, onLeaveXR
, onUpdateXR
, onControllerAdded
and onControllerRemoved
.
To receive pointer events implement onPointerDown
, onPointerUp
, onPointerEnter
, onPointerExit
and onPointerMove
.
Behaviour, const serializable: <T>(type?: Constructor<T> | TypeResolver<T> | (TypeResolver<T> | Constructor<any>)[] | null | undefined) => (_target: any, _propertyKey: string | {
name: string;
}) => void
The serializable attribute should be used to annotate all serialized fields / fields and members that should be serialized and exposed in an editor
serializable } from "@needle-tools/engine";
import { class Object3D<TEventMap extends Object3DEventMap = Object3DEventMap>
interface Object3D<TEventMap extends Object3DEventMap = Object3DEventMap>
This is the base class for most objects in three.js and provides a set of properties and methods for manipulating objects in 3D space.
Object3D } from "three"
export class class MyClass
MyClass extends class Behaviour
Needle Engine component base class. Component's are the main building blocks of the Needle Engine.
Derive from
Behaviour
to implement your own using the provided lifecycle methods.
Components can be added to threejs objects using [addComponent](addComponent)
or [GameObject.addComponent](GameObject.addComponent)
The most common lifecycle methods are awake
, start
, onEnable
, onDisable
update
and onDestroy
.
XR specific callbacks include onEnterXR
, onLeaveXR
, onUpdateXR
, onControllerAdded
and onControllerRemoved
.
To receive pointer events implement onPointerDown
, onPointerUp
, onPointerEnter
, onPointerExit
and onPointerMove
.
Behaviour {
// this will be a "Transform" field in Unity
@serializable<Object3D<Object3DEventMap>>(type?: Constructor<Object3D<Object3DEventMap>> | TypeResolver<Object3D<Object3DEventMap>> | (Constructor<...> | TypeResolver<...>)[] | null | undefined): (_target: any, _propertyKey: string | {
...;
}) => void
The serializable attribute should be used to annotate all serialized fields / fields and members that should be serialized and exposed in an editor
serializable(class Object3D<TEventMap extends Object3DEventMap = Object3DEventMap>
This is the base class for most objects in three.js and provides a set of properties and methods for manipulating objects in 3D space.
Object3D)
MyClass.myObjectReference: Object3D<Object3DEventMap> | null
myObjectReference: class Object3D<TEventMap extends Object3DEventMap = Object3DEventMap>
interface Object3D<TEventMap extends Object3DEventMap = Object3DEventMap>
This is the base class for most objects in three.js and provides a set of properties and methods for manipulating objects in 3D space.
Object3D | null = null;
// this will be a "Transform" array field in Unity
// Note that the @serializable decorator contains the array content type! (Object3D and not Object3D[])
@serializable<Object3D<Object3DEventMap>>(type?: Constructor<Object3D<Object3DEventMap>> | TypeResolver<Object3D<Object3DEventMap>> | (Constructor<...> | TypeResolver<...>)[] | null | undefined): (_target: any, _propertyKey: string | {
...;
}) => void
The serializable attribute should be used to annotate all serialized fields / fields and members that should be serialized and exposed in an editor
serializable(class Object3D<TEventMap extends Object3DEventMap = Object3DEventMap>
This is the base class for most objects in three.js and provides a set of properties and methods for manipulating objects in 3D space.
Object3D)
MyClass.myObjectReferenceList: Object3D<Object3DEventMap>[] | null
myObjectReferenceList: class Object3D<TEventMap extends Object3DEventMap = Object3DEventMap>
interface Object3D<TEventMap extends Object3DEventMap = Object3DEventMap>
This is the base class for most objects in three.js and provides a set of properties and methods for manipulating objects in 3D space.
Object3D[] | null = null;
}
To serialize from and to custom formats, it is possible to extend from the TypeSerializer
class and create an instance. Use super()
in the constructor to register supported types.
Note: In addition to matching fields, matching properties will also be exported when they match to fields in the typescript file.
Loading Scenes
Referenced Prefabs, SceneAssets and AssetReferences
in Unity will automatically be exported as glTF files (please refer to the Export Prefabs documentation).
These exported gltf files will be serialized as plain string URIs. To simplify loading these from TypeScript components, we added the concept of AssetReference
types. They can be loaded at runtime and thus allow to defer loading parts of your app or loading external content.
Example:
import { class Behaviour
Needle Engine component base class. Component's are the main building blocks of the Needle Engine.
Derive from
Behaviour
to implement your own using the provided lifecycle methods.
Components can be added to threejs objects using [addComponent](addComponent)
or [GameObject.addComponent](GameObject.addComponent)
The most common lifecycle methods are awake
, start
, onEnable
, onDisable
update
and onDestroy
.
XR specific callbacks include onEnterXR
, onLeaveXR
, onUpdateXR
, onControllerAdded
and onControllerRemoved
.
To receive pointer events implement onPointerDown
, onPointerUp
, onPointerEnter
, onPointerExit
and onPointerMove
.
Behaviour, const serializable: <T>(type?: Constructor<T> | TypeResolver<T> | (TypeResolver<T> | Constructor<any>)[] | null | undefined) => (_target: any, _propertyKey: string | {
name: string;
}) => void
The serializable attribute should be used to annotate all serialized fields / fields and members that should be serialized and exposed in an editor
serializable, class AssetReference
AssetReferences can be used to easily load glTF or GLB assets
You can use AssetReference.getOrCreate
to get an AssetReference for a URL to be easily loaded.
When using the same URL multiple times the same AssetReference will be returned, this avoids loading or creating the same asset multiple times.
myAssetReference.preload()
to load the asset binary without creating an instance yet.
myAssetReference.loadAssetAsync()
to load the asset and create an instance.
myAssetReference.instantiate()
to load the asset and create a new instance.
myAssetReference.unload()
to dispose allocated memory and destroy the asset instance.
AssetReference } from "@needle-tools/engine";
export class class MyClass
MyClass extends class Behaviour
Needle Engine component base class. Component's are the main building blocks of the Needle Engine.
Derive from
Behaviour
to implement your own using the provided lifecycle methods.
Components can be added to threejs objects using [addComponent](addComponent)
or [GameObject.addComponent](GameObject.addComponent)
The most common lifecycle methods are awake
, start
, onEnable
, onDisable
update
and onDestroy
.
XR specific callbacks include onEnterXR
, onLeaveXR
, onUpdateXR
, onControllerAdded
and onControllerRemoved
.
To receive pointer events implement onPointerDown
, onPointerUp
, onPointerEnter
, onPointerExit
and onPointerMove
.
Behaviour {
// if you export a prefab or scene as a reference from Unity you'll get a path to that asset
// which you can de-serialize to AssetReference for convenient loading
@serializable<AssetReference>(type?: Constructor<AssetReference> | TypeResolver<AssetReference> | (Constructor<any> | TypeResolver<...>)[] | null | undefined): (_target: any, _propertyKey: string | {
...;
}) => void
The serializable attribute should be used to annotate all serialized fields / fields and members that should be serialized and exposed in an editor
serializable(class AssetReference
AssetReferences can be used to easily load glTF or GLB assets
You can use AssetReference.getOrCreate
to get an AssetReference for a URL to be easily loaded.
When using the same URL multiple times the same AssetReference will be returned, this avoids loading or creating the same asset multiple times.
myAssetReference.preload()
to load the asset binary without creating an instance yet.
myAssetReference.loadAssetAsync()
to load the asset and create an instance.
myAssetReference.instantiate()
to load the asset and create a new instance.
myAssetReference.unload()
to dispose allocated memory and destroy the asset instance.
AssetReference)
MyClass.myPrefab?: AssetReference | undefined
myPrefab?: class AssetReference
AssetReferences can be used to easily load glTF or GLB assets
You can use AssetReference.getOrCreate
to get an AssetReference for a URL to be easily loaded.
When using the same URL multiple times the same AssetReference will be returned, this avoids loading or creating the same asset multiple times.
myAssetReference.preload()
to load the asset binary without creating an instance yet.
myAssetReference.loadAssetAsync()
to load the asset and create an instance.
myAssetReference.instantiate()
to load the asset and create a new instance.
myAssetReference.unload()
to dispose allocated memory and destroy the asset instance.
AssetReference;
async MyClass.start(): Promise<void>
called at the beginning of a frame (once per component)
start() {
// directly instantiate
const const myInstance: Object3D<Object3DEventMap> | null | undefined
myInstance = await this.MyClass.myPrefab?: AssetReference | undefined
myPrefab?.AssetReference.instantiate(parent?: Object3D<Object3DEventMap> | IInstantiateOptions | undefined): Promise<Object3D<Object3DEventMap> | null>
loads and returns a new instance of asset
instantiate();
// you can also just load and instantiate later
// const myInstance = await this.myPrefab.loadAssetAsync();
// this.gameObject.add(myInstance)
// this is useful if you know that you want to load this asset only once because it will not create a copy
// since ``instantiate()`` does create a copy of the asset after loading it
}
}
AssetReferences are cached by URI, so if you reference the same exported glTF/Prefab in multiple components/scripts it will only be loaded once and then re-used.