Needle Engine provides a tight integration into the Unity Editor. This allows developers and designers alike to work together in a familiar environment and deliver fast, performant and lightweight web-experiences.
The following guide is mainly aimed at developers with a Unity3D background but it may also be useful for developers with a web or three.js background. It covers topics regarding how things are done in Unity vs in three.js or Needle Engine.
If you are all new to Typescript and Javascript and you want to dive into writing scripts for Needle Engine then we also recommend reading the Typescript Essentials Guide for a basic understanding between the differences between C# and Javascript/Typescript.
If you want to code-along you can open engine.needle.tools/new to create a small project that you can edit in the browser ⚡
The Basics
Needle Engine is a 3d web engine running on-top of three.js. Three.js is one of the most popular 3D webgl based rendering libraries for the web. Whenever we refer to a gameObject
in Needle Engine we are actually also talking about a three.js Object3D
, the base type of any object in three.js. Both terms can be used interchangeably. Any gameObject
is a Object3D
.
This also means that - if you are already familiar with three.js - you will have no problem at all using Needle Engine. Everything you can do with three.js can be done in Needle Engine as well. If you are already using certain libraries then you will be able to also use them in a Needle Engine based environment.
Note: Needle Engine's Exporter does NOT compile your existing C# code to Web Assembly.
While using Web Assembly may result in better performance at runtime, it comes at a high cost for iteration speed and flexibility in building web experiences. Read more about our vision and technical overview.
How to create a new Unity project with Needle Engine? (Video)
Creating a Component
In Unity you create a new component by deriving from MonoBehaviour
:
using UnityEngine;
public class MyComponent : MonoBehaviour {
}
A custom component in Needle Engine on the other hand is written as follows:
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 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 {
}
Script Fields
serializable
If you have seen some Needle Engine scripts then you might have noticed that some variables are annotated with @serializable
above their declaration. This is a Decorator in Typescript and can be used to modify or annotate code. In Needle Engine this is used for example to let the core serialization know which types we expect in our script when it converts from the raw component information stored in the glTF to a Component instance.
Consider the following 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";
class class SomeClass
SomeClass 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<Behaviour>(type?: Constructor<Behaviour> | TypeResolver<Behaviour> | (Constructor<any> | TypeResolver<Behaviour>)[] | 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 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)
SomeClass.myOtherComponent?: Behaviour | undefined
myOtherComponent?: 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<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)
SomeClass.someOtherObject?: Object3D<Object3DEventMap> | undefined
someOtherObject?: 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;
}
This tells Needle Engine that myOtherComponent
should be of type Behaviour
. It will then automatically assign the correct reference to the field when your scene is loaded. The same is true for someOtherObject
where we want to deserialize to an Object3D
reference.
Note that in some cases the type can be ommitted. This can be done for all primitive types in Javascript. These are boolean
, number
, bigint
, string
, null
and undefined
.
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";
class class SomeClass
SomeClass {
@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() // < no type is needed here because the field type is a primitive
SomeClass.myString?: string | undefined
myString?: string;
}
public vs private
Field without any accessor modified like private
, public
or protected
will by default be public
in javascript
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";
class class SomeClass
SomeClass {
/// no accessor means it is public:
SomeClass.myNumber?: number | undefined
myNumber?: number;
// explicitly making it private:
private SomeClass.myPrivateNumber?: number | undefined
myPrivateNumber?: number;
protected SomeClass.myProtectedNumber?: number | undefined
myProtectedNumber?: number;
}
The same is true for methods as well.
GameObjects and 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:
this.gameObject.traverse((obj: GameObject
obj: 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) => 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: GameObject
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.
Components
Needle Engine is making heavy use of a Component System that is similar to that of Unity. This means that you can add or remove components to any Object3D
/ GameObject
in the scene. A component will be registered to the engine when using addNewComponent(<Object3D>, <ComponentType>)
.
The event methods that the attached component will then automatically be called by the engine (e.g. update
or onBeforeRender
). A full list of event methods can be found in the scripting documentation
Finding Components in the Scene
For getting component you can use the familiar methods similar to Unity. Note that the following uses the Animator
type as an example but you can as well use any component type that is either built-in or created by you.
Method name | Desciption |
---|---|
this.gameObject.getComponent(Animator) | Get the Animator component on a GameObject/Object3D. It will either return the Animator instance if it has an Animator component or null if the object has no such componnent. |
this.gameObject.getComponentInChildren(Animator) | Get the first Animator component on a GameObject/Object3D or on any of its children |
this.gameObject.getComponentsInParents(Animator) | Get all animator components in the parent hierarchy (including the current GameObject/Object3D) |
These methods are also available on the static GameObject type. For example GameObject.getComponent(this.gameObject, Animator)
to get the Animator
component on a passed in GameObject/Object3D.
To search the whole scene for one or multiple components you can use GameObject.findObjectOfType(Animator)
or GameObject.findObjectsOfType(Animator)
.
Renamed Unity Types
Some Unity-specific types are mapped to different type names in our engine. See the following list:
Type in Unity | Type in Needle Engine | |
---|---|---|
UnityEvent | EventList | A UnityEvent will be exported as a EventList type (use serializable(EventList) to deserialize UnityEvents) |
GameObject | Object3D | |
Transform | Object3D | In three.js and Needle Engine a GameObject and a Transform are the same (there is no Transform component). The only exception to that rule is when referencing a RectTransform which is a component in Needle Engine as well. |
Color | RGBAColor | The three.js color type doesnt have a alpha property. Because of that all Color types exported from Unity will be exported as RGBAColor which is a custom Needle Engine type |
Transform
Transform data can be accessed on the GameObject
/ Object3D
directly. Unlike to Unity there is no extra transform component that holds this data.
this.gameObject.position
is the position in local spacethis.gameObject.rotation
is the rotation in euler angles in local spacethis.gameObject.quaternion
- is the quaternion in local spacethis.gameObject.scale
- is the scale in local space
The major difference here to keep in mind is that position
in three.js is by default a localspace position whereas in Unity position
would be worldspace. The next section will explain how to get the worldspace position in three.js.
WORLD- Position, Rotation, Scale...
In three.js (and thus also in Needle Engine) the object.position
, object.rotation
, object.scale
are all local space coordinates. This is different to Unity where we are used to position
being worldspace and using localPosition
to deliberately use the local space position.
If you want to access the world coordinates in Needle Engine we have utility methods that you can use with your objects. Call getWorldPosition(yourObject)
to calculate the world position. Similar methods exist for rotation/quaternion and scale. To get access to those methods just import them from Needle Engine like so import { getWorldPosition } from "@needle.tools/engine"
Note that these utility methods like getWorldPosition
, getWorldRotation
, getWorldScale
internally have a buffer of Vector3 instances and are meant to be used locally only. This means that you should not cache them in your component, otherwise your cached value will eventually be overriden. But it is safe to call getWorldPosition
multiple times in your function to make calculations without having to worry to re-use the same instance. If you are not sure what this means you should take a look at the Primitive Types section in the Typescript Essentials Guide
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.
Raycasting
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
Input
Use this.context.input
to poll input state:
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")
}
}
}
You can also subscribe to 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);
}
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(evt: NEPointerEvent
evt); }
}
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:
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", () => { 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("MOUSE CLICK"); });
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).
InputSystem Callbacks
Similar to Unity (see IPointerClickHandler in Unity) you can also register to receive input events on the component itself.
To make this work make sure your object has a ObjectRaycaster
or GraphicRaycaster
component in the parent hierarchy.
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, IPointerEventHandler, class PointerEventData
This pointer event data object is passed to all event receivers that are currently active
It contains hit information if an object was hovered or clicked
If the event is received in onPointerDown or onPointerMove, you can call setPointerCapture
to receive onPointerMove events even when the pointer has left the object until you call releasePointerCapture
or when the pointerUp event happens
You can get additional information about the event or event source via the event
property (of type NEPointerEvent
)
PointerEventData } from "@needle-tools/engine";
export class class ReceiveClickEvent
ReceiveClickEvent 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 implements IPointerEventHandler {
ReceiveClickEvent.onPointerClick(args: PointerEventData): void
Called when an object (or any child object) is clicked (needs a EventSystem in the scene)
onPointerClick(args: PointerEventData
args: class PointerEventData
This pointer event data object is passed to all event receivers that are currently active
It contains hit information if an object was hovered or clicked
If the event is received in onPointerDown or onPointerMove, you can call setPointerCapture
to receive onPointerMove events even when the pointer has left the object until you call releasePointerCapture
or when the pointerUp event happens
You can get additional information about the event or event source via the event
property (of type NEPointerEvent
)
PointerEventData) {
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", args: PointerEventData
args);
}
}
Note: IPointerEventHandler
subscribes the object to all possible pointer events. The handlers for them are:
onPointerDown
onPointerUp
onPointerEnter
onPointerMove
onPointerExit
onPointerClick
All have a PointerEventData
argument describing the event.
Debug.Log
The Debug.Log()
equivalent in javascript is console.log()
. You can also use console.warn()
or console.error()
.
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("Hello web");
// You can pass in as many arguments as you want like so:
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("Hello", const someVariable: 42
someVariable, 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.findObjectOfType<Renderer>(typeName: Constructor<Renderer>, context?: Context | Object3D<Object3DEventMap> | undefined, includeInactive?: boolean | undefined): Renderer | null
findObjectOfType(class Renderer
Renderer), this.context);
Gizmos
In Unity you normally have to use special methods to draw Gizmos like OnDrawGizmos
or OnDrawGizmosSelected
. In Needle Engine on the other hand such methods dont exist and you are free to draw gizmos from anywhere in your script. Note that it is also your responsibility then to not draw them in e.g. your deployed web application (you can just filter them by if(isDevEnvironment))
).
Here is an example to draw a red wire sphere for one second for e.g. visualizing a point in worldspace
import { class Gizmos
Gizmos are temporary objects that are drawn in the scene for debugging or visualization purposes
They are automatically removed after a given duration and cached internally to reduce overhead.
Use the static methods of this class to draw gizmos in the scene.
Gizmos } from "@needle-tools/engine";
class Gizmos
Gizmos are temporary objects that are drawn in the scene for debugging or visualization purposes
They are automatically removed after a given duration and cached internally to reduce overhead.
Use the static methods of this class to draw gizmos in the scene.
Gizmos.Gizmos.DrawWireSphere(center: Vec3, radius: number, color?: ColorRepresentation | undefined, duration?: number | undefined, depthTest?: boolean | undefined): void
DrawWireSphere(const hit: {
point: Vector3;
}
hit.point: Vector3
point, 0.05, 0xff0000, 1);
Here are some of the available gizmo methods:
Method name | |
---|---|
Gizmos.DrawArrow | |
Gizmos.DrawBox | |
Gizmos.DrawBox3 | |
Gizmos.DrawDirection | |
Gizmos.DrawLine | |
Gizmos.DrawRay | |
Gizmos.DrawRay | |
Gizmos.DrawSphere | |
Gizmos.DrawWireSphere |
Useful Utility Methods
Import from @needle-tools/engine
e.g. import { getParam } from "@needle-tools/engine"
Method name | Description |
---|---|
getParam() | Checks if a url parameter exists. Returns true if it exists but has no value (e.g. ?help ), false if it is not found in the url or is set to 0 (e.g. ?help=0 ), otherwise it returns the value (e.g. ?message=test ) |
isMobileDevice() | Returns true if the app is accessed from a mobile device |
isDevEnvironment() | Returns true if the current app is running on a local server |
isMozillaXR() | |
isiOS | |
isSafari |
import { function isMobileDevice(): boolean
isMobileDevice } from "@needle-tools/engine"
if( function isMobileDevice(): boolean
isMobileDevice() )
import { function getParam<T extends string>(paramName: T): Param<T>
Checks if a url parameter exists.
Returns true if it exists but has no value (e.g. ?help)
Returns false if it does not exist
Returns false if it's set to 0 e.g. ?debug=0
Returns the value if it exists e.g. ?message=hello
getParam } from "@needle-tools/engine"
// returns true
const const myFlag: Param<"some_flag">
myFlag = getParam<"some_flag">(paramName: "some_flag"): Param<"some_flag">
Checks if a url parameter exists.
Returns true if it exists but has no value (e.g. ?help)
Returns false if it does not exist
Returns false if it's set to 0 e.g. ?debug=0
Returns the value if it exists e.g. ?message=hello
getParam("some_flag")
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 myFlag: Param<"some_flag">
myFlag)
The Web project
In C# you usually work with a solution containing one or many projects. In Unity this solution is managed by Unity for you and when you open a C# script it opens the project and shows you the file.
You usually install Packages using Unity's built-in package manager to add features provided by either Unity or other developers (either on your team or e.g. via Unity's AssetStore). Unity does a great job of making adding and managing packages easy with their PackageManager and you might never have had to manually edit a file like the manifest.json
(this is what Unity uses to track which packages are installed) or run a command from the command line to install a package.
In a web environment you use npm
- the Node Package Manager - to manage dependencies / packages for you. It does basically the same to what Unity's PackageManager does - it installs (downloads) packages from some server (you hear it usually called a registry in that context) and puts them inside a folder named node_modules
.
When working with a web project most of you dependencies are installed from npmjs.com. It is the most popular package registry out there for web projects.
Here is an example of how a package.json might look like:
{
"name": "@optional_org/package_name",
"version": "1.0.0",
"scripts": {
"start": "vite --host"
},
"dependencies": {
"@needle-tools/engine": "^3.5.9-beta",
"three": "npm:@needle-tools/[email protected]"
},
"devDependencies": {
"@types/three": "0.146.0",
"@vitejs/plugin-basic-ssl": "^1.0.1",
"typescript": "^5.0.4",
"vite": "^4.3.4",
"vite-plugin-compression": "^0.5.1"
}
}
Our default template uses Vite as its bundler and has no frontend framework pre-installed. Needle Engine is unoppionated about which framework to use so you are free to work with whatever framework you like. We have samples for popular frameworks like Vue.js, Svelte, Next.js, React or React Three Fiber.
Installing packages & dependencies
To install a dependency from npm you can open your web project in a commandline (or terminal) and run npm i <the/package_name>
(shorthand for npm install
)
For example run npm i @needle-tools/engine
to install Needle Engine. This will then add the package to your package.json
to the dependencies
array.
To install a package as a devDependency only you can run npm i --save-dev <package_name>
. More about the difference between dependencies and devDependencies below.
What's the difference between 'dependencies' and 'devDependencies'
You may have noticed that there are two entries containing dependency - dependencies
and devDependencies
.
dependencies
are always installed (or bundled) when either your web project is installed or in cases where you develop a library and your package is installed as a dependency of another project.
devDependencies
are only installed when developing the project (meaning that when you directly run install
in the specific directory) and they are otherwise not included in your project.
How do I install another package or dependency and how to use it?
The Installing section taught us that you can install dependencies by running npm i <package_name>
in your project directory where the package_name
can be any package that you find on npm.js.
Let's assume you want to add a tweening library to your project. We will use @tweenjs/tween.js
for this example. Here is the final project if you want to jump ahead and just see the result.
First run npm install @tweenjs/tween.js
in the terminal and wait for the installation to finish. This will add a new entry to our package.json:
"dependencies": {
"@needle-tools/engine": "^3.5.11-beta",
"@tweenjs/tween.js": "^20.0.3",
"three": "npm:@needle-tools/[email protected]"
}
Then open one of your script files in which you want to use tweening and import at the top of the file:
import * as import TWEEN
TWEEN from '@tweenjs/tween.js';
Note that we do here import all types in the library by writing * as TWEEN
. We could also just import specific types like import { Tween } from @tweenjs/tween.js
.
Now we can use it in our script. It is always recommended to refer to the documentation of the library that you want to use. In the case of tween.js they provide a user guide that we can follow. Usually the Readme page of the package on npm contains information on how to install and use the package.
To rotate a cube we create a new component type called TweenRotation
, we then go ahead and create our tween instance for the object rotation, how often it should repeat, which easing to use, the tween we want to perform and then we start it. We then only have to call update
every frame to update the tween animation. The final script looks like this:
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";
import * as import TWEEN
TWEEN from '@tweenjs/tween.js';
export class class TweenRotation
TweenRotation 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 {
// save the instance of our tweener
private TweenRotation._tween?: TWEEN.Tween<any> | undefined
_tween?: import TWEEN
TWEEN.class Tween<T extends UnknownProps = any>
export Tween
Tween.js - Licensed under the MIT license
https://github.com/tweenjs/tween.js
See https://github.com/tweenjs/tween.js/graphs/contributors for the full list of contributors.
Thank you all, you're awesome!
Tween<any>;
TweenRotation.start(): void
called at the beginning of a frame (once per component)
start() {
const const rotation: Euler
rotation = 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>.rotation: Euler
Object's local rotation (
Euler angles
), in radians.
rotation;
// create the tween instance
this.TweenRotation._tween?: TWEEN.Tween<any> | undefined
_tween = new import TWEEN
TWEEN.new Tween<Euler>(object: Euler, group?: TWEEN.Group | undefined): TWEEN.Tween<Euler> (+1 overload)
export Tween
Tween(const rotation: Euler
rotation);
// set it to repeat forever
this.TweenRotation._tween?: TWEEN.Tween<any>
_tween.Tween<any>.repeat(times?: number | undefined): TWEEN.Tween<any>
repeat(var Infinity: number
Infinity);
// set the easing to use
this.TweenRotation._tween?: TWEEN.Tween<any>
_tween.Tween<any>.easing(easingFunction?: EasingFunction | undefined): TWEEN.Tween<any>
easing(import TWEEN
TWEEN.const Easing: Readonly<{
Linear: Readonly<EasingFunctionGroup & {
None: EasingFunction;
}>;
Quadratic: Readonly<EasingFunctionGroup>;
Cubic: Readonly<EasingFunctionGroup>;
Quartic: Readonly<EasingFunctionGroup>;
Quintic: Readonly<EasingFunctionGroup>;
Sinusoidal: Readonly<EasingFunctionGroup>;
Exponential: Readonly<EasingFunctionGroup>;
Circular: Readonly<EasingFunctionGroup>;
Elastic: Readonly<EasingFunctionGroup>;
Back: Readonly<EasingFunctionGroup>;
Bounce: Readonly<EasingFunctionGroup>;
generatePow(power?: number): EasingFunctionGroup;
}>
export Easing
The Ease class provides a collection of easing functions for use with tween.js.
Easing.type Quintic: Readonly<EasingFunctionGroup>
Quintic.type InOut: EasingFunction
InOut);
// set the values to tween
this.TweenRotation._tween?: TWEEN.Tween<any>
_tween.Tween<any>.to(target: UnknownProps, duration?: number | undefined): TWEEN.Tween<any>
to({ y: number
y: var Math: Math
An intrinsic object that provides basic mathematics functionality and constants.
Math.Math.PI: number
Pi. This is the ratio of the circumference of a circle to its diameter.
PI * 0.5 }, 1000);
// start it
this.TweenRotation._tween?: TWEEN.Tween<any>
_tween.Tween<any>.start(time?: number | undefined, overrideStartingValues?: boolean | undefined): TWEEN.Tween<any>
start();
}
TweenRotation.update(): void
regular callback in a frame (called every frame when implemented)
update() {
// update the tweening every frame
// the '?' is a shorthand for checking if _tween has been created
this.TweenRotation._tween?: TWEEN.Tween<any> | undefined
_tween?.Tween<any>.update(time?: number | undefined, autoStart?: boolean | undefined): boolean
update();
}
}
Now we only have to add it to any of the objects in our scene to rotate them forever.
You can see the final script in action here.