Needle Engine is a web-first 3D framework built on three.js for building games, configurators, AR/VR experiences, and interactive websites.
Built-in: Rapier Physics | WebXR (incl. iOS) | Multiplayer & VOIP | Blender & Unity Integration
Built on three.js and the glTF standard, Needle Engine delivers flexible, extensible web experiences with built-in collaboration and XR support. Use it standalone with npm or with powerful editor integrations for Unity and Blender.
Changelog | Documentation | Samples | Showcase | API Reference
npm install @needle-tools/engine
Try it now on StackBlitz | Getting Started Guide
import { Behaviour, serializable } from "@needle-tools/engine";
export class MyComponent extends Behaviour {
@serializable()
speed: number = 1;
start() {
console.log("Component started on:", this.gameObject.name);
}
update() {
this.gameObject.rotateY(this.context.time.deltaTime * this.speed);
}
}
import { Behaviour, Rigidbody, BoxCollider } from "@needle-tools/engine";
export class PhysicsBox extends Behaviour {
awake() {
// Add a physics body — Rapier is built in, no extra install needed
const rb = this.gameObject.addComponent(Rigidbody);
rb.useGravity = true;
// Add a collider
this.gameObject.addComponent(BoxCollider);
}
}
import { Behaviour, syncField } from "@needle-tools/engine";
export class SyncedCounter extends Behaviour {
// Automatically synced across all connected clients
@syncField()
count: number = 0;
onPointerClick() {
// Reassign to trigger sync (this is required for sync to work)
this.count = this.count + 1;
}
}
import { onStart, onUpdate } from "@needle-tools/engine";
onStart((context) => {
console.log("Engine started!");
});
onUpdate((context) => {
// Called every frame
});
WebXR & AR — immersive experiences on Android and iOS
WebXRImageTracking — AR image targets with full tracking lifecycleWebXRPlaneTracking — AR surface detectionScene & Asset Management
SceneSwitcher — load different scenes / hierarchies by URLAssetReference — runtime asset loading by URLPhysics & Interaction — Built-in Rapier physics engine
Rigidbody, BoxCollider, SphereCollider, MeshCollider — full physics simulationDragControls — click-and-drag 3D objects with zero codeSpatialTrigger — proximity and enter-zone detectionMultiplayer & Networking — real-time collaboration out of the box
SyncedRoom + @syncField() — automatic state synchronizationVoip — built-in WebRTC voice chatPlayerSync — player object sync on join/leaveSyncedCamera — camera sync for observer sessionsRendering & Effects
Animation & Media
VideoPlayer — full video playback componentAudioSource — 3D spatial audioFramework Integration — works with React, Vue, Svelte, or vanilla JS/TS
Needle Engine works standalone with just npm — no editor required. For asset-heavy workflows, use our editor integrations:
| Preview | Example | Description | Links |
|---|---|---|---|
![]() |
Multiuser Cross device experience, Desktop, AR & VR (Sandbox) | Real-time collaborative multiplayer sandbox experience with WebXR on Android and iOS | |
![]() |
Image Tracking AR | AR image tracking example (iOS and Android). See docs | |
| Scrollytelling Bike Example | Timeline Animation using ScrollFollow, ViewBox and FocusRect | Project on Github | |
| See-Through Walls | See-Through component sample | ||
| Cursor Follow | Cursor Follow sample | ||
| Animate Anything | Interactive animation system | Code on Stackblitz • three.js Example | |
| Postprocessing Effects | Custom magnifier effect with post-processing | Code on Stackblitz | |
![]() |
Unity ShaderGraph to MaterialX & mtlx materials | Using @needle-tools/materialx | |
| Camera Focus DIV 1 | Responsive layout with camera focus | Code on Stackblitz | |
| Camera Focus DIV 2 | Click-to-move camera focus example | Code on Stackblitz | |
| FastHDR Loading | 10x faster than EXR, non-blocking, 95% less GPU memory | Code on Stackblitz • Learn more | |
| Scrollytelling Example | Scroll, physics and cursor interaction: a playful 3D interactive scrollytelling website | Included in Samples Package | |
| AR Restaurant | Interactive AR restaurant experience | Code on Github | |
| Custom Loading Overlay | Wait for LODs with custom loading states | Code on Stackblitz | |
| React Shopping Cart | E-commerce integration with React | Code on Stackblitz | |
👋 More examples on samples.needle.tools, docs.needle.tools and in the Needle Engine Stackblitz Collection
🌵 Needle •
Github •
Twitter •
Discord •
Forum •
Youtube
WebXRImageTracking.imageTracked event — invoked every frame an image is tracked, with typed access to the tracked object instance:imageTracking.imageTracked.addEventListener(evt => {
console.log(evt.object, evt.image);
});
?stats URL parameter now also logs renderer info (DPR, window DPR, antialias, MSAA samples, drawing buffer resolution)Added
Context.events — typed event bus for decoupled component communication. Known events get autocomplete; custom events can be typed at the call site:context.events.on("scene-content-changed", e => console.log(e.object));
context.events.emit<{ pts: number }>("scored", { pts: 10 });
ContactShadows auto-refit — when autoFit is enabled, shadows automatically refit when scene content changes (e.g. SceneSwitcher load, DropListener asset loading)Input.addEventListener now returns an unsubscribe function (works with autoCleanup):this.autoCleanup(this.context.input.addEventListener("pointerdown", (evt) => { ... }));
AnimationBuilder — low-level API for defining animation tracks with typed keyframes and tween shorthands. Used by AnimatorControllerBuilder and TimelineBuilder for inline .track() callsTimelineBuilder typed track builder interfaces per track type (AnimationTrackBuilder, AudioTrackBuilder, etc.) with inline .track() support for keyframe animationAnimatorControllerBuilder: inline .track() for defining animation directly on states, support for TrackDescriptor arrays as clip sources, simplified exitTime (replaces separate hasExitTime flag)includeTriggers option — opt-in to hitting trigger/sensor colliders (skipped by default)Changed
AnimationTrackHandler → TimelineAnimationTrack, AudioTrackHandler → TimelineAudioTrack, ActivationTrackHandler → TimelineActivationTrack, ControlTrackHandler → TimelineControlTrack, MarkerTrackHandler → TimelineMarkerTrack, TrackHandler → TimelineTrackHandlerOrbitControls.fitCamera deprecated overload removed from type declarations (runtime still accepts Object3D for backwards compat — use fitCamera({ objects: [...] }) instead)Fixed
OrbitControls: programmatic camera transitions (e.g. fitCamera) no longer interrupted continuously during an ongoing drag — only at interaction startfitCamera with centerCamera: "y" producing incorrect camera elevation when camera was above the scene centerAnimator: switching runtimeAnimatorController now properly disposes the previous controllerAnimatorController.dispose() guard against missing mixerCursorFollow.snapToSurface incorrect property description removedautoCleanup on Behaviour — register disposables or cleanup functions tied to the component lifecycle. Automatically cleaned up on disable or destroy depending on when registered:onEnable() {
this.autoCleanup(on(window, "resize", () => { ... }));
this.autoCleanup(this.context.connection.beginListen("my-event", () => { ... }));
}
TimelineBuilder API for building timeline assets from code:const timeline = TimelineBuilder.create("MyTimeline")
.animationTrack("Walk", animator).clip(walkClip, { duration: 2 })
.activationTrack("FX", vfxObject).activate({ start: 1, duration: 0.5 })
.build();
director.playableAsset = timeline;
PlayableDirector: expose tracks and activationTracks getters, support runtime playableAsset assignment with automatic graph rebuildPhysicsCollider: expose density property, add @validate decorator for automatic property updates at runtimeAudioTrack volume getter/setterNetworking: beginListen now returns an unsubscribe function (backwards compatible — stopListen still works):const unsub = this.context.connection.beginListen("my-event", (data) => { ... });
unsub(); // or use this.autoCleanup(unsub)
instantiate()AvatarLoader, AvatarBlink_Simple, AvatarEyeLook_Rotation, Avatar_Brain_LookAt, Avatar_MouthShapes, Avatar_MustacheShake components@validate decorator wrapping __internalAwake multiple times when components were re-activatedSignalReceiver not resolving EventList object referencesinstantiate() not including cloned Object3D GUIDs in the guids mapLight component throwing on unsupported light types (now logs error without breaking deserialization)dispose() not clearing internal state (freed world could cause crashes)AudioClip type with standalone playback control — use with @serializable(AudioClip) for direct audio clip referencesAudioSource spatial blend support via dual-path audio graph for smooth 2D/3D crossfadeDragControls EventList supportAudioSource spatial rolloff factor and play() reliabilityAnimation play() with { exclusive: false } not stopping already-running clipsonLeaveXR calls for scripts that never entered XROrbitControls lookBounds lerp and distance-relative gizmo sizesCursorFollow self-intersection and snapToSurface raycast directionmakeFilesLocal and alias plugins now respect vite.config basethree-mesh-bvh worker failing to load in local dev serverVideoPlayer URL serializer tree-shaken out in code-only projectsReflectionProbe not cleaning up overrides when removed