Automatically generating Editor components
When working in Unity or Blender then you will notice that when you create a new Needle Engine component in Typescript or Javascript it will automatically generate a Unity C# stub component OR a Blender panel for you.
This is thanks to the magic of the Needle component compiler that runs behind the scenes in an editor environment and watches changes to your script files. When it notices that you created a new Needle Engine component it will then generate the correct Unity component or Blender panel including public variables or properties that you can then set or link from within the Editor.
Controlling component generation
You can use the following comments in your typescript code to control C# code generation behavior:
Attribute | Result |
---|---|
// @generate-component | Force generation of next class |
// @dont-generate-component | Disable generation of next class, this is useful in cases where you already have an existing C# script in your project |
// @serializeField | Decorate generated field with [SerializeField] |
// @type UnityEngine.Camera | Specify generated C# field type |
// @nonSerialized | Skip generating the next field or method |
Examples
Force the component compiler to generate a C# AudioClip field named myAudioClip
import { class Behaviour
Needle Engine component base class. Component's are the main building blocks of the Needle Engine.
Derive from
Behaviour
to implement your own using the provided lifecycle methods.
Components can be added to threejs objects using [addComponent](addComponent)
or [GameObject.addComponent](GameObject.addComponent)
The most common lifecycle methods are awake
, start
, onEnable
, onDisable
update
and onDestroy
.
XR specific callbacks include onEnterXR
, onLeaveXR
, onUpdateXR
, onControllerAdded
and onControllerRemoved
.
To receive pointer events implement onPointerDown
, onPointerUp
, onPointerEnter
, onPointerExit
and onPointerMove
.
Behaviour, const serializable: <T>(type?: Constructor<T> | TypeResolver<T> | (TypeResolver<T> | Constructor<any>)[] | null | undefined) => (_target: any, _propertyKey: string | {
name: string;
}) => void
The serializable attribute should be used to annotate all serialized fields / fields and members that should be serialized and exposed in an editor
serializable } from "@needle-tools/engine";
export class class 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 {
//@type UnityEngine.AudioClip
@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()
MyComponent.myAudioClip?: string | undefined
myAudioClip?: string;
}
Force the component compiler to derive from a specific subclass
//@type MyNamespace.MyCustomBaseClass
export class class MyComponent
MyComponent extends class MyCustomBaseClass
MyCustomBaseClass {
}
Component Compiler in Unity
If you want to add scripts inside the src/scripts
folder in your project then you need to have a Component Generator
on the GameObject with your ExportInfo
component.
Now when adding new components in your/threejs/project/src/scripts
it will automatically generate Unity scripts in Assets/Needle/Components.codegen
.
If you want to add scripts to any NpmDef file you can just create them - each NpmDef automatically watches script changes and handles component generation, so you don't need any additional component in your scene.
For C# fields to be correctly generated it is currently important that you explictly declare a Typescript type. For example myField : number = 5
You can switch between Typescript input and generated C# stub components using the tabs below
import { 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, 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 MyCustomComponent
MyCustomComponent extends class Behaviour
Needle Engine component base class. Component's are the main building blocks of the Needle Engine.
Derive from
Behaviour
to implement your own using the provided lifecycle methods.
Components can be added to threejs objects using [addComponent](addComponent)
or [GameObject.addComponent](GameObject.addComponent)
The most common lifecycle methods are awake
, start
, onEnable
, onDisable
update
and onDestroy
.
XR specific callbacks include onEnterXR
, onLeaveXR
, onUpdateXR
, onControllerAdded
and onControllerRemoved
.
To receive pointer events implement onPointerDown
, onPointerUp
, onPointerEnter
, onPointerExit
and onPointerMove
.
Behaviour {
@serializable<unknown>(type?: Constructor<unknown> | TypeResolver<unknown> | (Constructor<any> | TypeResolver<unknown>)[] | null | undefined): (_target: any, _propertyKey: string | {
...;
}) => void
The serializable attribute should be used to annotate all serialized fields / fields and members that should be serialized and exposed in an editor
serializable()
MyCustomComponent.myFloatValue: number
myFloatValue: number = 42;
@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)
MyCustomComponent.myOtherObject?: Object3D<Object3DEventMap> | undefined
myOtherObject?: 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;
@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)
MyCustomComponent.prefabs: AssetReference[]
prefabs: 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[] = [];
MyCustomComponent.start(): void
called at the beginning of a frame (once per component)
start() {
this.MyCustomComponent.sayHello(): void
sayHello();
}
private MyCustomComponent.sayHello(): void
sayHello() {
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);
}
}
// NEEDLE_CODEGEN_START
// auto generated code - do not edit directly
#pragma warning disable
namespace Needle.Typescript.GeneratedComponents
{
public partial class MyCustomComponent : UnityEngine.MonoBehaviour
{
public float @myFloatValue = 42f;
public UnityEngine.Transform @myOtherObject;
public UnityEngine.Transform[] @prefabs = new UnityEngine.Transform[]{ };
public void start(){}
public void update(){}
}
}
// NEEDLE_CODEGEN_END
using UnityEditor;
// you can add code above or below the NEEDLE_CODEGEN_ blocks
// NEEDLE_CODEGEN_START
// auto generated code - do not edit directly
#pragma warning disable
namespace Needle.Typescript.GeneratedComponents
{
public partial class MyCustomComponent : UnityEngine.MonoBehaviour
{
public float @myFloatValue = 42f;
public UnityEngine.Transform @myOtherObject;
public UnityEngine.Transform[] @prefabs = new UnityEngine.Transform[]{ };
public void start(){}
public void update(){}
}
}
// NEEDLE_CODEGEN_END
namespace Needle.Typescript.GeneratedComponents
{
// This is how you extend the generated component (namespace and class name must match!)
public partial class MyCustomComponent : UnityEngine.MonoBehaviour
{
public void MyAdditionalMethod()
{
}
private void OnValidate()
{
myFloatValue = 42;
}
}
// of course you can also add custom editors
[CustomEditor(typeof(MyCustomComponent))]
public class MyCustomComponentEditor : Editor
{
public override void OnInspectorGUI()
{
EditorGUILayout.HelpBox("This is my sample component", MessageType.None);
base.OnInspectorGUI();
}
}
}
Extending generated components
Component C# classes are generated with the partial
flag so that it is easy to extend them with functionality. This is helpful to draw gizmos, add context menus or add additional fields or methods that are not part of a built-in component.
Member Casing
Exported members will start with a lowercase letter. For example if your C# member is named MyString
it will be assigned to myString
.