Scripting Examples
Video tutorial: How to write custom components
Below you will find a few basic scripts as a quick reference.
We also offer a lot of sample scenes and complete projects that you can download and use as a starting point:
Basic component
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 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 {
@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)
MyComponent.myObjectReference?: Object3D<Object3DEventMap> | undefined
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;
MyComponent.start(): void
called at the beginning of a frame (once per component)
start() {
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 world", this);
}
MyComponent.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);
}
}
see scripting for all component events
Reference an Object from Unity
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;
}
Reference and load an asset from Unity (Prefab or SceneAsset)
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
}
}
Reference and load scenes from Unity
Tips
Find a working example in our samples to download and try
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 LoadingScenes
LoadingScenes 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 {
// tell the component compiler that we want to reference an array of SceneAssets
// @type UnityEditor.SceneAsset[]
@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)
LoadingScenes.myScenes?: AssetReference[] | undefined
myScenes?: 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 LoadingScenes.awake(): Promise<void>
called once when the component becomes active for the first time (once per component)
This is the first callback to be called
awake() {
if (!this.LoadingScenes.myScenes?: AssetReference[] | undefined
myScenes) {
return;
}
for (const const scene: AssetReference
scene of this.LoadingScenes.myScenes?: AssetReference[]
myScenes) {
// check if it is assigned in unity
if(!const scene: AssetReference
scene) continue;
// load the scene once
const const myScene: any
myScene = await const scene: AssetReference
scene.AssetReference.loadAssetAsync(prog?: ProgressCallback | null | undefined): Promise<any>
Loads the asset and creates one instance (assigned to asset
)
loadAssetAsync();
// add it to the threejs scene
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>.add(...object: Object3D<Object3DEventMap>[]): GameObject
Adds another
[as child of this
{@link
Object3D](Object3D
})
.
add(const myScene: any
myScene);
// of course you can always just load one at a time
// and remove it from the scene when you want
// myScene.removeFromParent();
// this is the same as scene.asset.removeFromParent()
}
}
LoadingScenes.onDestroy(): void
Called when the component gets destroyed
onDestroy(): void {
if (!this.LoadingScenes.myScenes?: AssetReference[] | undefined
myScenes) return;
for (const const scene: AssetReference
scene of this.LoadingScenes.myScenes?: AssetReference[]
myScenes) {
const scene: AssetReference
scene?.AssetReference.unload(): void
frees previously allocated memory and destroys the current asset
instance (if any)
unload();
}
}
}
Receive Clicks on Objects
Add this script to any object in your scene that you want to be clickable. Make sure to also have an ObjectRaycaster
component in the parent hierarchy of that object.
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, IPointerClickHandler, 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, function showBalloonMessage(text: string, logType?: LogType): void
Displays a debug message on screen for a certain amount of time
showBalloonMessage } from "@needle-tools/engine";
export class class ClickExample
ClickExample 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 IPointerClickHandler {
// Make sure to have an ObjectRaycaster component in the parent hierarchy
ClickExample.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) {
function showBalloonMessage(text: string, logType?: LogType | undefined): void
Displays a debug message on screen for a certain amount of time
showBalloonMessage("Clicked " + this.Component.name: string
name);
}
}
Networking Clicks on Objects
Add this script to any object in your scene that you want to be clickable. Make sure to also have an ObjectRaycaster
component in the parent hierarchy of that object.
The component will send the received click to all connected clients and will raise an event that you can then react to in your app. If you are using Unity or Blender you can simply assign functions to call to the onClick
event to e.g. play an animation or hide objects.
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 EventList
The EventList is a class that can be used to create a list of event listeners that can be invoked
EventList, IPointerClickHandler, 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, 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 SyncedClick
SyncedClick 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 IPointerClickHandler {
@serializable<EventList>(type?: Constructor<EventList> | TypeResolver<EventList> | (Constructor<any> | TypeResolver<EventList>)[] | 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 EventList
The EventList is a class that can be used to create a list of event listeners that can be invoked
EventList)
SyncedClick.onClick: EventList
onClick!: class EventList
The EventList is a class that can be used to create a list of event listeners that can be invoked
EventList;
SyncedClick.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("SEND CLICK");
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.connection: NetworkConnection
access networking methods (use it to send or listen to messages or join a networking backend)
connection.NetworkConnection.send<WebsocketSendType>(key: string, data?: WebsocketSendType | undefined, queue?: SendQueue | undefined): void
Send a message to the networking backend - it will broadcasted to all connected users in the same room by default
send("clicked/" + this.Component.guid: string
the unique identifier for this component
guid);
this.SyncedClick.onClick: EventList
onClick?.EventList.invoke(...args: any): boolean
Invoke all the methods that are subscribed to this event
invoke();
}
SyncedClick.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(): void {
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.connection: NetworkConnection
access networking methods (use it to send or listen to messages or join a networking backend)
connection.NetworkConnection.beginListen(key: string, callback: Function): Function
Use to start listening to networking events
beginListen("clicked/" + this.Component.guid: string
the unique identifier for this component
guid, this.SyncedClick.onRemoteClick: () => void
onRemoteClick);
}
SyncedClick.onDisable(): void
called every time the component gets disabled or if a parent object (or this.gameObject) gets set to invisible
onDisable(): void {
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.connection: NetworkConnection
access networking methods (use it to send or listen to messages or join a networking backend)
connection.NetworkConnection.stopListen(key: string, callback: Function | null): void
Use to stop listening to networking events
stopListen("clicked/" + this.Component.guid: string
the unique identifier for this component
guid, this.SyncedClick.onRemoteClick: () => void
onRemoteClick);
}
SyncedClick.onRemoteClick: () => void
onRemoteClick = () => {
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("RECEIVED CLICK");
this.SyncedClick.onClick: EventList
onClick?.EventList.invoke(...args: any): boolean
Invoke all the methods that are subscribed to this event
invoke();
}
}
Play Animation on click
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 Animation
Animation component to play animations on a GameObject
Animation, IPointerClickHandler, 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 PlayAnimationOnClick
PlayAnimationOnClick 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 IPointerClickHandler {
@serializable<Animation>(type?: Constructor<Animation> | TypeResolver<Animation> | (Constructor<any> | TypeResolver<Animation>)[] | 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 Animation
Animation component to play animations on a GameObject
Animation)
PlayAnimationOnClick.animation?: Animation | undefined
animation?: class Animation
Animation component to play animations on a GameObject
Animation;
PlayAnimationOnClick.awake(): void
called once when the component becomes active for the first time (once per component)
This is the first callback to be called
awake() {
if (this.PlayAnimationOnClick.animation?: Animation | undefined
animation) {
this.PlayAnimationOnClick.animation?: Animation
animation.Animation.playAutomatically: boolean
If true, the animation will start playing when the component is enabled
playAutomatically = false;
this.PlayAnimationOnClick.animation?: Animation
animation.Animation.loop: boolean
Set to true to loop the animation
loop = false;
}
}
PlayAnimationOnClick.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) {
if (this.PlayAnimationOnClick.animation?: Animation | undefined
animation) {
this.PlayAnimationOnClick.animation?: Animation
animation.Animation.play(clipOrNumber?: AnimationIdentifier, options?: PlayOptions | undefined): void | Promise<AnimationAction>
Play an animation clip or an clip at the specified index.
play();
}
}
}
Reference an Animation Clip
This can be useful if you want to run your custom animation logic.
You can also export an array of clips.
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 AnimationClip
AnimationClip } from "three"
export class class ExportAnimationClip
ExportAnimationClip 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<AnimationClip>(type?: Constructor<AnimationClip> | TypeResolver<AnimationClip> | (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 AnimationClip
AnimationClip)
ExportAnimationClip.animation?: AnimationClip | undefined
animation?: class AnimationClip
AnimationClip;
ExportAnimationClip.awake(): void
called once when the component becomes active for the first time (once per component)
This is the first callback to be called
awake() {
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 referenced animation clip", this.ExportAnimationClip.animation?: AnimationClip | undefined
animation);
}
}
Create and invoke a UnityEvent
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 EventList
The EventList is a class that can be used to create a list of event listeners that can be invoked
EventList } 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 {
@serializable<EventList>(type?: Constructor<EventList> | TypeResolver<EventList> | (Constructor<any> | TypeResolver<EventList>)[] | 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 EventList
The EventList is a class that can be used to create a list of event listeners that can be invoked
EventList)
MyComponent.myEvent?: EventList | undefined
myEvent? : class EventList
The EventList is a class that can be used to create a list of event listeners that can be invoked
EventList;
MyComponent.start(): void
called at the beginning of a frame (once per component)
start() {
this.MyComponent.myEvent?: EventList | undefined
myEvent?.EventList.invoke(...args: any): boolean
Invoke all the methods that are subscribed to this event
invoke();
}
}
Tips
EventList events are also invoked on the component level. This means you can also subscribe to the event declared above using myComponent.addEventListener("my-event", evt => {...})
as well.
This is an experimental feature. Please provide feedback in our forum
Declare a custom event type
This is useful for when you want to expose an event to Unity or Blender with some custom arguments (like a string)
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 EventList
The EventList is a class that can be used to create a list of event listeners that can be invoked
EventList } 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";
/*
Make sure to have a c# file in your project with the following content:
using UnityEngine;
using UnityEngine.Events;
[System.Serializable]
public class MyCustomUnityEvent : UnityEvent<string>
{
}
Unity documentation about custom events:
https://docs.unity3d.com/ScriptReference/Events.UnityEvent_2.html
*/
// Documentation → https://docs.needle.tools/scripting
export class class CustomEventCaller
CustomEventCaller 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 {
// The next line is not just a comment, it defines
// a specific type for the component generator to use.
//@type MyCustomUnityEvent
@serializable<EventList>(type?: Constructor<EventList> | TypeResolver<EventList> | (Constructor<any> | TypeResolver<EventList>)[] | 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 EventList
The EventList is a class that can be used to create a list of event listeners that can be invoked
EventList)
CustomEventCaller.myEvent: EventList
myEvent!: class EventList
The EventList is a class that can be used to create a list of event listeners that can be invoked
EventList;
// just for testing - could be when a button is clicked, etc.
CustomEventCaller.start(): void
called at the beginning of a frame (once per component)
start() {
this.CustomEventCaller.myEvent: EventList
myEvent.EventList.invoke(...args: any): boolean
Invoke all the methods that are subscribed to this event
invoke("Hello");
}
}
export class class CustomEventReceiver
CustomEventReceiver 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 {
CustomEventReceiver.logStringAndObject(str: string): void
logStringAndObject(str: string
str: string) {
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("From Event: ", str: string
str);
}
}
Example use:
Use nested objects and serialization
You can nest objects and their data. With properly matching @serializable(SomeType)
decorators, the data will be serialized and deserialized into the correct types automatically.
In your typescript component:
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";
// Documentation → https://docs.needle.tools/scripting
class class CustomSubData
CustomSubData {
@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()
CustomSubData.subString: string
subString: string = "";
@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()
CustomSubData.subNumber: number
subNumber: number = 0;
}
class class CustomData
CustomData {
@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()
CustomData.myStringField: string
myStringField: string = "";
@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()
CustomData.myNumberField: number
myNumberField: number = 0;
@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()
CustomData.myBooleanField: boolean
myBooleanField: boolean = false;
@serializable<CustomSubData>(type?: Constructor<CustomSubData> | TypeResolver<CustomSubData> | (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 CustomSubData
CustomSubData)
CustomData.subData: CustomSubData | undefined
subData: class CustomSubData
CustomSubData | undefined = var undefined
undefined;
CustomData.someMethod(): void
someMethod() {
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 string is " + this.CustomData.myStringField: string
myStringField, "my sub data", this.CustomData.subData: CustomSubData | undefined
subData)
}
}
export class class SerializedDataSample
SerializedDataSample 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<CustomData>(type?: Constructor<CustomData> | TypeResolver<CustomData> | (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 CustomData
CustomData)
SerializedDataSample.myData: CustomData | undefined
myData: class CustomData
CustomData | undefined;
SerializedDataSample.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 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.SerializedDataSample.myData: CustomData | undefined
myData);
this.SerializedDataSample.myData: CustomData | undefined
myData?.CustomData.someMethod(): void
someMethod();
}
}
In C# in any script:
using System;
[Serializable]
public class CustomSubData
{
public string subString;
public float subNumber;
}
[Serializable]
public class CustomData
{
public string myStringField;
public float myNumberField;
public bool myBooleanField;
public CustomSubData subData;
}
Tips
Without the correct type decorators, you will still get the data, but just as a plain object. This is useful when you're porting components, as you'll have access to all data and can add types as required.
Use Web APIs
Tips
Keep in mind that you still have access to all web apis and npm packages!
That's the beauty of Needle Engine if we're allowed to say this here 😊
Display current location
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, function showBalloonMessage(text: string, logType?: LogType): void
Displays a debug message on screen for a certain amount of time
showBalloonMessage } from "@needle-tools/engine";
export class class WhereAmI
WhereAmI 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 {
WhereAmI.start(): void
called at the beginning of a frame (once per component)
start() {
var navigator: Navigator
navigator.Navigator.geolocation: Geolocation
geolocation.Geolocation.getCurrentPosition(successCallback: PositionCallback, errorCallback?: PositionErrorCallback | null | undefined, options?: PositionOptions | undefined): void
getCurrentPosition((position: GeolocationPosition
position) => {
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("Navigator response:", position: GeolocationPosition
position);
const const latlong: string
latlong = position: GeolocationPosition
position.GeolocationPosition.coords: GeolocationCoordinates
coords.GeolocationCoordinates.latitude: number
latitude + ", " + position: GeolocationPosition
position.GeolocationPosition.coords: GeolocationCoordinates
coords.GeolocationCoordinates.longitude: number
longitude;
function showBalloonMessage(text: string, logType?: LogType | undefined): void
Displays a debug message on screen for a certain amount of time
showBalloonMessage("You are at\nLatLong " + const latlong: string
latlong);
});
}
}
Display current time using a Coroutine
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 Text
Text, 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, function WaitForSeconds(seconds: number, context?: Context | null): Generator<undefined, void, unknown>
Wait for a number of seconds to pass.
WaitForSeconds } from "@needle-tools/engine";
export class class DisplayTime
DisplayTime 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<Text>(type?: Constructor<Text> | TypeResolver<Text> | (Constructor<any> | TypeResolver<Text>)[] | 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 Text
Text)
DisplayTime.text?: Text | undefined
text?: class Text
Text;
DisplayTime.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(): void {
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.DisplayTime.updateTime(): Generator<Generator<undefined, void, unknown>, void, unknown>
updateTime())
}
private *DisplayTime.updateTime(): Generator<Generator<undefined, void, unknown>, void, unknown>
updateTime() {
while (true) {
if (this.DisplayTime.text?: Text | undefined
text) {
this.DisplayTime.text?: Text
text.Text.text: string
text = new var Date: DateConstructor
new () => Date (+4 overloads)
Date().Date.toLocaleTimeString(locales?: Intl.LocalesArgument, options?: Intl.DateTimeFormatOptions | undefined): string (+2 overloads)
Converts a time to a string by using the current or specified locale.
toLocaleTimeString();
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.DisplayTime.text?: Text
text.Text.text: string
text)
}
yield function WaitForSeconds(seconds: number, context?: Context | null | undefined): Generator<undefined, void, unknown>
Wait for a number of seconds to pass.
WaitForSeconds(1)
}
};
}
Change custom shader property
Assuming you have a custom shader with a property name _Speed
that is a float value this is how you would change it from a script.
You can find a live example to download in our samples
import { Behaviour, serializable } from "@needle-tools/engine";
import { Material } from "three";
declare type MyCustomShaderMaterial = {
_Speed: number;
};
export class IncreaseShaderSpeedOverTime extends Behaviour {
@serializable(Material)
myMaterial?: Material & MyCustomShaderMaterial;
update() {
if (this.myMaterial) {
this.myMaterial._Speed *= 1 + this.context.time.deltaTime;
if(this.myMaterial._Speed > 1) this.myMaterial._Speed = .0005;
if(this.context.time.frame % 30 === 0) console.log(this.myMaterial._Speed)
}
}
}
Switching src attribute
See live example on StackBlitz
Adding new postprocessing effects
Make sure to install npm i postprocessing
in your web project. Then you can add new effects by deriving from PostProcessingEffect
.
To use the effect add it to the same object as your Volume
component.
Here is an example that wraps the Outline postprocessing effect. You can expose variables and settings as usual as any effect is also just a component in your three.js scene.
import { EffectProviderResult, PostProcessingEffect, registerCustomEffectType, serializable } from "@needle-tools/engine";
import { OutlineEffect } from "postprocessing";
import { Object3D } from "three";
export class OutlinePostEffect extends PostProcessingEffect {
// the outline effect takes a list of objects to outline
@serializable(Object3D)
selection!: Object3D[];
// this is just an example method that you could call to update the outline effect selection
updateSelection() {
if (this._outlineEffect) {
this._outlineEffect.selection.clear();
for (const obj of this.selection) {
this._outlineEffect.selection.add(obj);
}
}
}
// a unique name is required for custom effects
get typeName(): string {
return "Outline";
}
private _outlineEffect: void | undefined | OutlineEffect;
// method that creates the effect once
onCreateEffect(): EffectProviderResult | undefined {
const outlineEffect = new OutlineEffect(this.context.scene, this.context.mainCamera!);
this._outlineEffect = outlineEffect;
outlineEffect.edgeStrength = 10;
outlineEffect.visibleEdgeColor.set(0xff0000);
for (const obj of this.selection) {
outlineEffect.selection.add(obj);
}
return outlineEffect;
}
}
// You need to register your effect type with the engine
registerCustomEffectType("Outline", OutlinePostEffect);
Custom ParticleSystem Behaviour
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 ParticleSystem
The ParticleSystem component efficiently handles the rendering of particles.
You can add custom behaviours to the particle system to fully customize the behaviour of the particles. See
[and
{@link
ParticleSystem.addBehaviour](ParticleSystemBaseBehaviour
})
for more information.
ParticleSystem } from "@needle-tools/engine";
import { class ParticleSystemBaseBehaviour
ParticleSystemBaseBehaviour, QParticle } from "@needle-tools/engine";
// Derive your custom behaviour from the ParticleSystemBaseBehaviour class (or use QParticleBehaviour)
class class MyParticlesBehaviour
MyParticlesBehaviour extends class ParticleSystemBaseBehaviour
ParticleSystemBaseBehaviour {
// callback invoked per particle
MyParticlesBehaviour.update(particle: QParticle): void
update(particle: QParticle
particle: QParticle): void {
particle: QParticle
particle.Particle.position: Vector3
position.Vector3.y: number
y += 5 * this.ParticleSystemBaseBehaviour.context: Context
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;
}
}
export class class TestCustomParticleSystemBehaviour
TestCustomParticleSystemBehaviour 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 {
TestCustomParticleSystemBehaviour.start(): void
called at the beginning of a frame (once per component)
start() {
// add your custom behaviour to the particle system
this.Component.gameObject: GameObject
the object this component is attached to. Note that this is a threejs Object3D with some additional features
gameObject.GameObject.getComponent<ParticleSystem>(type: Constructor<ParticleSystem>): ParticleSystem | null
Get a Needle Engine component from the Object3D.
getComponent(class ParticleSystem
The ParticleSystem component efficiently handles the rendering of particles.
You can add custom behaviours to the particle system to fully customize the behaviour of the particles. See
[and
{@link
ParticleSystem.addBehaviour](ParticleSystemBaseBehaviour
})
for more information.
ParticleSystem)!.ParticleSystem.addBehaviour(particleSystemBehaviour: ParticleSystemBaseBehaviour | Behavior): boolean
Add a custom quarks behaviour to the particle system.
You can add a quarks.Behaviour type or derive from
ParticleSystemBaseBehaviour
addBehaviour(new constructor MyParticlesBehaviour(ps?: ParticleSystem | undefined): MyParticlesBehaviour
MyParticlesBehaviour())
}
}
Custom 2D Audio Component
This is an example how you could create your own audio component.
For most usecases however you can use the core AudioSource component and don't have to write code.
import { class AudioSource
The AudioSource can be used to play audio in the scene.
Use clip
to set the audio file to play.
AudioSource, 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";
// declaring AudioClip type is for codegen to produce the correct input field (for e.g. Unity or Blender)
declare type type AudioClip = string
AudioClip = string;
export class class My2DAudio
My2DAudio 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 {
// The clip contains a string pointing to the audio file - by default it's relative to the GLB that contains the component
// by adding the URL decorator the clip string will be resolved relative to your project root and can be loaded
@serializable<URL>(type?: Constructor<URL> | TypeResolver<URL> | (Constructor<any> | TypeResolver<URL>)[] | 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(var URL: {
new (url: string | URL, base?: string | URL | undefined): URL;
prototype: URL;
canParse(url: string | URL, base?: string | undefined): boolean;
createObjectURL(obj: Blob | MediaSource): string;
revokeObjectURL(url: string): void;
}
The URLÂ interface represents an object providing static methods used for creating object URLs.
URL
class is a global reference for require('url').URL
https://nodejs.org/api/url.html#the-whatwg-url-api
URL)
My2DAudio.clip?: string | undefined
clip?: type AudioClip = string
AudioClip;
My2DAudio.awake(): void
called once when the component becomes active for the first time (once per component)
This is the first callback to be called
awake() {
// creating a new audio element and playing it
const const audioElement: HTMLAudioElement
audioElement = new var Audio: new (src?: string | undefined) => HTMLAudioElement
Audio(this.My2DAudio.clip?: string | undefined
clip);
const audioElement: HTMLAudioElement
audioElement.HTMLMediaElement.loop: boolean
Gets or sets a flag to specify whether playback should restart after it completes.
loop = true;
// on the web we have to wait for the user to interact with the page before we can play audio
class AudioSource
The AudioSource can be used to play audio in the scene.
Use clip
to set the audio file to play.
AudioSource.AudioSource.registerWaitForAllowAudio(cb: Function): void
Register a callback that is called when the user has interacted with the page to allow audio playback.
Internally calling
Application.registerWaitForInteraction
registerWaitForAllowAudio(() => {
const audioElement: HTMLAudioElement
audioElement.HTMLMediaElement.play(): Promise<void>
Loads and starts playback of a media resource.
play();
})
}
}
Arbitrary external files
Use the FileReference type to load external files (e.g. a json file)
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 FileReference
Use this if a file is a external file URL. The file can be any arbitrary binary data like a videofile or a text asset
FileReference, class ImageReference
Use this if a file is a external image URL
ImageReference, 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 FileReferenceExample
FileReferenceExample 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 {
// A FileReference can be used to load and assign arbitrary data in the editor. You can use it to load images, audio, text files... FileReference types will not be saved inside as part of the GLB (the GLB will only contain a relative URL to the file)
@serializable<FileReference>(type?: Constructor<FileReference> | TypeResolver<FileReference> | (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 FileReference
Use this if a file is a external file URL. The file can be any arbitrary binary data like a videofile or a text asset
FileReference)
FileReferenceExample.myFile?: FileReference | undefined
myFile?: class FileReference
Use this if a file is a external file URL. The file can be any arbitrary binary data like a videofile or a text asset
FileReference;
// Tip: if you want to export and load an image (that is not part of your GLB) if you intent to add it to your HTML content for example you can use the ImageReference type instead of FileReference. It will be loaded as an image and you can use it as a source for an <img> tag.
async FileReferenceExample.start(): Promise<void>
called at the beginning of a frame (once per component)
start() {
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 is my file: ", this.FileReferenceExample.myFile?: FileReference | undefined
myFile);
// load the file
const const data: Blob | undefined
data = await this.FileReferenceExample.myFile?: FileReference | undefined
myFile?.FileReference.loadRaw(): Promise<Blob>
Load the file binary data
loadRaw();
if (!const data: Blob | undefined
data) {
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.error(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to stderr
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 code = 5;
console.error('error #%d', code);
// Prints: error #5, to stderr
console.error('error', code);
// Prints: error 5, to stderr
If formatting elements (e.g. %d
) are not found in the first string then
util.inspect()
is called on each argument and the
resulting string values are concatenated. See util.format()
for more information.
error("Failed loading my file...");
return;
}
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("Loaded my file. These are the bytes:", await const data: Blob
data.Blob.arrayBuffer(): Promise<ArrayBuffer>
arrayBuffer());
}
}
Receiving html element click in component
import { Behaviour, EventList, serializable, serializeable } from "@needle-tools/engine";
export class HTMLButtonClick extends Behaviour {
/** Enter a button query (e.g. button.some-button if you're interested in a button with the class 'some-button')
* Or you can also use an id (e.g. #some-button if you're interested in a button with the id 'some-button')
* Or you can also use a tag (e.g. button if you're interested in any button
*/
@serializeable()
htmlSelector: string = "button.some-button";
/** This is the event to be invoked when the html element is clicked. In Unity or Blender you can assign methods to be called in the Editor */
@serializable(EventList)
onClick: EventList = new EventList();
private element? : HTMLButtonElement;
onEnable() {
// Get the element from the DOM
this.element = document.querySelector(this.htmlSelector) as HTMLButtonElement;
if (this.element) {
this.element.addEventListener('click', this.onClicked);
}
else console.warn(`Could not find element with selector \"${this.htmlSelector}\"`);
}
onDisable() {
if (this.element) {
this.element.removeEventListener('click', this.onClicked);
}
}
private onClicked = () => {
this.onClick.invoke();
}
}
Disable environment light
import { Behaviour } from "@needle-tools/engine";
import { Texture } from "three";
export class DisableEnvironmentLight extends Behaviour {
private _previousEnvironmentTexture: Texture | null = null;
onEnable(): void {
this._previousEnvironmentTexture = this.context.scene.environment;
this.context.scene.environment = null;
}
onDisable(): void {
this.context.scene.environment = this._previousEnvironmentTexture;
}
}
Use mediapipe package to control the 3D scene with hands
Make sure to install the mediapipe package. Visit the github link below to see the complete project setup.
Try it live here - requires a webcam/camera
import { FilesetResolver, HandLandmarker, HandLandmarkerResult, NormalizedLandmark } from "@mediapipe/tasks-vision";
import { Behaviour, Mathf, serializable, showBalloonMessage } from "@needle-tools/engine";
import { ParticleSphere } from "./ParticleSphere";
export class MediapipeHands extends Behaviour {
@serializable(ParticleSphere)
spheres: ParticleSphere[] = [];
private _video!: HTMLVideoElement;
private _handLandmarker!: HandLandmarker;
async awake() {
showBalloonMessage("Initializing mediapipe...")
const vision = await FilesetResolver.forVisionTasks(
// path/to/wasm/root
"https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm"
);
this._handLandmarker = await HandLandmarker.createFromOptions(
vision,
{
baseOptions: {
modelAssetPath: "https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/latest/hand_landmarker.task",
delegate: "GPU"
},
numHands: 2
});
//@ts-ignore
await this._handLandmarker.setOptions({ runningMode: "VIDEO" });
this._video = document.createElement("video");
this._video.setAttribute("style", "max-width: 30vw; height: auto;");
console.log(this._video);
this._video.autoplay = true;
this._video.playsInline = true;
this.context.domElement.appendChild(this._video);
this.startWebcam(this._video);
}
private _lastVideoTime: number = 0;
update(): void {
if (!this._video || !this._handLandmarker) return;
const video = this._video;
if (video.currentTime !== this._lastVideoTime) {
let startTimeMs = performance.now();
showBalloonMessage("<strong>Control the spheres with one or two hands</strong>!<br/><br/>Sample scene by <a href='https://twitter.com/llllkatjallll/status/1659280435023605773'>Katja Rempel</a>")
const detections = this._handLandmarker.detectForVideo(video, startTimeMs);
this.processResults(detections);
this._lastVideoTime = video.currentTime;
}
}
private processResults(results: HandLandmarkerResult) {
const hand1 = results.landmarks[0];
// check if we have even one hand
if (!hand1) return;
if (hand1.length >= 4 && this.spheres[0]) {
const pos = hand1[4];
this.processLandmark(this.spheres[0], pos);
}
// if we have a second sphere:
if (this.spheres.length >= 2) {
const hand2 = results.landmarks[1];
if (!hand2) {
const pos = hand1[8];
this.processLandmark(this.spheres[1], pos);
}
else {
const pos = hand2[4];
this.processLandmark(this.spheres[1], pos);
}
}
}
private processLandmark(sphere: ParticleSphere, pos: NormalizedLandmark) {
const px = Mathf.remap(pos.x, 0, 1, -6, 6);
const py = Mathf.remap(pos.y, 0, 1, 6, -6);
sphere.setTarget(px, py, 0);
}
private async startWebcam(video: HTMLVideoElement) {
const constraints = { video: true, audio: false };
const stream = await navigator.mediaDevices.getUserMedia(constraints);
video.srcObject = stream;
}
}
Change Color On Collision
import { Behaviour, Collision, Renderer } from "@needle-tools/engine";
import{ Color } from "three";
export class ChangeColorOnCollision extends Behaviour {
private renderer: Renderer | null = null;
private collisionCount: number = 0;
private _startColor? : Color[];
start() {
this.renderer = this.gameObject.getComponent(Renderer);
if (!this.renderer) return;
if(!this._startColor) this._startColor = [];
for (let i = 0; i < this.renderer.sharedMaterials.length; i++) {
this.renderer.sharedMaterials[i] = this.renderer.sharedMaterials[i].clone();
this._startColor[i] = this.renderer.sharedMaterials[i]["color"].clone();
}
}
onCollisionEnter(_col: Collision) {
if (!this.renderer) return;
this.collisionCount += 1;
for (let i = 0; i < this.renderer.sharedMaterials.length; i++) {
this.renderer.sharedMaterials[i]["color"].setRGB(Math.random(), Math.random(), Math.random());
}
}
onCollisionExit(_col: Collision) {
if (!this.renderer || !this._startColor) return;
this.collisionCount -= 1;
if (this.collisionCount === 0) {
for (let i = 0; i < this.renderer.sharedMaterials.length; i++) {
this.renderer.sharedMaterials[i]["color"].copy(this._startColor[i])
// .setRGB(.1, .1, .1);
}
}
}
// more events:
// onCollisionStay(_col: Collision)
// onCollisionExit(_col: Collision)
}
Physics Trigger Relay
Invoke events using an objects physics trigger methods
export class PhysicsTrigger extends Behaviour {
@serializeable(GameObject)
triggerObjects?:GameObject[];
@serializeable(EventList)
onEnter?: EventList;
@serializeable(EventList)
onStay?: EventList;
@serializeable(EventList)
onExit?: EventList;
onTriggerEnter(col: Collider) {
if(this.triggerObjects && this.triggerObjects.length > 0 && !this.triggerObjects?.includes(col.gameObject)) return;
this.onEnter?.invoke();
}
onTriggerStay(col: Collider) {
if(this.triggerObjects && this.triggerObjects.length > 0 && !this.triggerObjects?.includes(col.gameObject)) return;
this.onStay?.invoke();
}
onTriggerExit(col: Collider) {
if(this.triggerObjects && this.triggerObjects.length > 0 && !this.triggerObjects?.includes(col.gameObject)) return;
this.onExit?.invoke();
}
}
Auto Reset
Reset an object's position automatically when it's leaving a physics trigger
import { Behaviour, Collider, GameObject, Rigidbody, serializeable } from "@needle-tools/engine";
import { Vector3 } from "three";
export class StartPosition extends Behaviour {
//@nonSerialized
startPosition?: Vector3;
start() {
this.updateStartPosition();
}
updateStartPosition(){
this.startPosition = this.gameObject.position.clone();
}
resetToStart() {
if (!this.startPosition) return;
const rb = GameObject.getComponent(this.gameObject, Rigidbody);
rb?.teleport(this.startPosition);
}
}
/** Reset to start position when object is exiting the collider */
export class AutoReset extends StartPosition {
@serializeable(Collider)
worldCollider?: Collider;
start(){
super.start();
if(!this.worldCollider) console.warn("Missing collider to reset", this);
}
onTriggerExit(col) {
if(col === this.worldCollider){
this.resetToStart();
}
}
}
Play Audio On Collision
import { AudioSource, Behaviour, serializeable } from "@needle-tools/engine";
export class PlayAudioOnCollision extends Behaviour {
@serializeable(AudioSource)
audioSource?: AudioSource;
onCollisionEnter() {
this.audioSource?.play();
}
}
Set Random Color
Randomize the color of an object on start. Note that the materials are cloned in the start
method
import { Behaviour, serializeable, Renderer } from "@needle-tools/engine";
import { Color } from "three";
export class RandomColor extends Behaviour {
@serializeable()
applyOnStart: boolean = true;
start() {
if (this.applyOnStart)
this.applyRandomColor();
// if materials are not cloned and we change the color they might also change on other objects
const cloneMaterials = true;
if (cloneMaterials) {
const renderer = this.gameObject.getComponent(Renderer);
if (!renderer) {
return;
}
for (let i = 0; i < renderer.sharedMaterials.length; i++) {
renderer.sharedMaterials[i] = renderer.sharedMaterials[i].clone();
}
}
}
applyRandomColor() {
const renderer = this.gameObject.getComponent(Renderer);
if (!renderer) {
console.warn("Can not change color: No renderer on " + this.name);
return;
}
for (let i = 0; i < renderer.sharedMaterials.length; i++) {
renderer.sharedMaterials[i].color = new Color(Math.random(), Math.random(), Math.random());
}
}
}
Spawn Objects Over Time
import { Behaviour, GameObject, LogType, serializeable, showBalloonMessage, WaitForSeconds } from "@needle-tools/engine";
export class TimedSpawn extends Behaviour {
@serializeable(GameObject)
object?: GameObject;
interval: number = 1000;
max: number = 100;
private spawned: number = 0;
awake() {
if (!this.object) {
console.warn("TimedSpawn: no object to spawn");
showBalloonMessage("TimedSpawn: no object to spawn", LogType.Warn);
return;
}
GameObject.setActive(this.object, false);
this.startCoroutine(this.spawn())
}
*spawn() {
if (!this.object) return;
while (this.spawned < this.max) {
const instance = GameObject.instantiate(this.object);
GameObject.setActive(instance!, true);
this.spawned += 1;
yield WaitForSeconds(this.interval / 1000);
}
}
}