# Needle Engine — Built-in Components Reference ## Table of Contents - [Physics](#physics) - [Animation](#animation) - [Audio](#audio) - [Video](#video) - [Lighting and Shadows](#lighting-and-shadows) - [Post-Processing](#post-processing) - [Camera](#camera) - [Scene Switching](#scene-switching) - [Interaction](#interaction) - [Splines](#splines) - [Debug Tools](#debug-tools) - [Utilities](#utilities) --- ## Physics See [physics.md](physics.md) for the full physics reference (colliders, Rigidbody API, raycasting, async loading). Rapier initializes automatically — just add collider and rigidbody components. Use `SphereCollider` for balls, `CapsuleCollider` for characters/cylinders, not BoxCollider for everything. Use `applyImpulse` for one-shot actions, `applyForce` for continuous. Never access `rb._body` internals. --- ## Animation ### Animation (simple clip playback) ```ts import { Animation } from "@needle-tools/engine"; const anim = this.gameObject.getComponent(Animation); anim.play(); // play default clip anim.play("Idle"); // play by clip name anim.stop(); anim.loop = true; // loop playback (default: true) anim.playAutomatically = true; // auto-play on enable (default: true) ``` ### Animator (state machine — Unity Animator Controller) ```ts import { Animator } from "@needle-tools/engine"; const anim = this.gameObject.getComponent(Animator); anim.play("Run"); // play by state name anim.setFloat("Speed", 1.5); // Animator parameters (match Unity parameter names) anim.setBool("IsGrounded", true); anim.setTrigger("Jump"); anim.speed = 0.5; // global playback speed multiplier ``` ### PlayableDirector (Timeline) ```ts import { PlayableDirector } from "@needle-tools/engine"; const director = this.gameObject.getComponent(PlayableDirector); director.play(); // start playback director.pause(); director.stop(); director.time = 2.5; // scrub to time (seconds) director.evaluate(); // evaluate at current time (use after setting time) director.isPlaying // check playback state director.isPaused director.duration // total duration in seconds ``` --- ## Audio ### AudioSource ```ts import { AudioSource } from "@needle-tools/engine"; const audio = this.gameObject.getComponent(AudioSource); audio.clip = "sounds/music.mp3"; // URL to audio file audio.volume = 0.8; audio.loop = true; audio.spatialBlend = 1; // 0 = 2D, 1 = full 3D positional audio.play(); audio.pause(); audio.stop(); // Browser autoplay policy: audio won't play until user interaction AudioSource.registerWaitForAllowAudio(() => { audio.play(); }); ``` Key properties: `clip` (string/MediaStream), `volume` (0–1), `loop`, `spatialBlend` (0–1), `playOnAwake`, `pitch`, `minDistance`, `maxDistance`, `isPlaying`, `time`, `duration`. ### AudioListener Represents the "ears" in the scene. Attach to the camera (auto-added to main camera if missing). Only one should be active. ```ts import { AudioListener } from "@needle-tools/engine"; this.context.mainCamera?.addComponent(AudioListener); ``` --- ## Video ### VideoPlayer ```ts import { VideoPlayer } from "@needle-tools/engine"; const vp = this.gameObject.addComponent(VideoPlayer); vp.url = "videos/intro.mp4"; // mp4, webm, or m3u8 (HLS) vp.isLooping = true; vp.playOnAwake = true; vp.play(); vp.pause(); vp.stop(); vp.currentTime = 10; // seek to 10 seconds // Webcam / screen capture: vp.setVideo(mediaStream); // HLS livestreams: just set an m3u8 URL — hls.js loads automatically vp.url = "https://stream.example.com/live.m3u8"; ``` Key properties: `url`, `isLooping`, `playbackSpeed`, `muted`, `playInBackground`, `screenspace`, `isPlaying`, `videoElement`, `videoTexture`. The video texture is applied to the object's material by default (MaterialOverride render mode). The object needs a `Renderer` component. --- ## Lighting and Shadows ### Light ```ts import { Light } from "@needle-tools/engine"; const light = this.gameObject.getComponent(Light); light.intensity = 1.5; light.color.set(1, 0.95, 0.9); // warm white light.shadows = 2; // 0=None, 1=Hard, 2=Soft light.shadowResolution = 2048; ``` Light types (set in Unity/Blender, not changeable at runtime): Directional (1), Point (2), Spot (0). Spot lights have `spotAngle` and `innerSpotAngle`. Point/Spot lights have `range`. ### ContactShadows Soft ground shadows based on proximity — no lights needed. ```ts import { ContactShadows } from "@needle-tools/engine"; // Auto-create fitted to scene const shadows = ContactShadows.auto(this.context); shadows.opacity = 0.6; shadows.blur = 5; // Or via HTML attribute: // ``` ### ShadowCatcher Catches real-time shadows from light sources onto a surface. Use for AR ground planes. ```ts import { ShadowCatcher } from "@needle-tools/engine"; const catcher = obj.addComponent(ShadowCatcher); catcher.mode = 0; // 0=ShadowMask, 1=Additive, 2=Occluder ``` ContactShadows = soft ambient-style, no lights needed, better performance. ShadowCatcher = accurate shadows from real lights, higher cost. ### ReflectionProbe Provides per-object environment reflections using cubemap or HDR textures. Objects can reference a specific probe as their reflection source, producing more accurate localized reflections than a single global environment map. ```ts import { ReflectionProbe } from "@needle-tools/engine"; // Typically set up in Unity/Blender: add ReflectionProbe to an object, assign a cubemap texture, // then on Renderer components set the probe as "anchor override" // Check if a material is using a reflection probe: ReflectionProbe.isUsingReflectionProbe(material); ``` Debug: `?debugreflectionprobe` URL param. Disable all: `?noreflectionprobe`. --- ## Post-Processing See [postprocessing.md](postprocessing.md) for the full post-processing reference (all effects, parameters, runtime changes). Key points: Use `this.context.postprocessing.addEffect(effect)` / `.removeEffect(effect)`. Effects use `VolumeParameter` — set values with `.value`. Toggle with `effect.enabled`. Loads async via `NEEDLE_ENGINE_MODULES.POSTPROCESSING`. --- ## Camera ```ts // Access the main camera this.context.mainCamera // THREE.Camera this.context.mainCameraComponent // Needle Camera component // Switch the active camera: import { Camera } from "@needle-tools/engine"; const cam = targetObject.getComponent(Camera); this.context.setCurrentCamera(cam); // make this the active camera // Camera properties cam.fieldOfView = 60; cam.nearClipPlane = 0.1; cam.farClipPlane = 1000; cam.orthographic = false; // Screen to world const ray = cam.screenPointToRay(screenX, screenY); ``` Key properties: `fieldOfView`, `nearClipPlane`, `farClipPlane`, `backgroundColor`, `orthographic`, `orthographicSize`, `clearFlags`, `targetTexture`. ### Custom camera control (first-person, etc.) For code-only scenes where you want full camera control (first-person, fly cam, etc.): 1. Use `` to prevent auto-added OrbitControls 2. Remove any existing OrbitControls — they override camera rotation every frame: ```ts import { OrbitControls } from "@needle-tools/engine"; onStart(ctx => { // Remove OrbitControls so they don't fight your custom camera logic const cam = ctx.mainCamera; const orbit = cam?.getComponent(OrbitControls); if (orbit) orbit.destroy(); }); ``` 3. Write a `Behaviour` component for camera control — use `update()` and the engine's input system (`this.context.input`), not raw DOM events or `requestAnimationFrame` 4. See the [FirstPersonCharacter sample](https://github.com/needle-tools/needle-engine-samples/blob/main/package/Runtime/FirstPersonController/Scripts/FirstPersonController~/FirstPersonCharacter.ts) for a working example --- ## Scene Switching `SceneSwitcher` manages loading/unloading multiple GLB scenes — useful for multi-room apps, configurators, portfolios. ```ts import { SceneSwitcher } from "@needle-tools/engine"; const switcher = this.gameObject.getComponent(SceneSwitcher); await switcher.select(0); // by index await switcher.select("myScene"); // by name/URI await switcher.selectNext(); await switcher.selectPrev(); // Add scenes dynamically switcher.addScene("assets/room2.glb"); // Events switcher.addEventListener("loadscene-finished", (e) => { console.log("Loaded:", e.detail.scene.url); }); ``` Key properties: `scenes` (AssetReference[]), `currentIndex`, `preloadNext`, `preloadPrevious`, `useHistory` (browser back/forward), `useKeyboard` (arrow keys), `useSwipe`, `queryParameterName` (URL param, default `"scene"`). You can also implement scene switching yourself using `AssetReference` or `loadAsset()`: ```ts import { AssetReference, loadAsset } from "@needle-tools/engine"; // With AssetReference (caches by URL): const ref = AssetReference.getOrCreate(baseUrl, "assets/room2.glb"); const instance = await ref.instantiate({ parent: this.context.scene }); // With loadAsset (returns a model wrapper): const model = await loadAsset("assets/room2.glb"); this.context.scene.add(model.scene); ``` --- ## Interaction ### DragControls Enables dragging objects in 3D. Automatically takes ownership in networked scenes. ```ts import { DragControls, DragMode } from "@needle-tools/engine"; const drag = obj.addComponent(DragControls); drag.dragMode = DragMode.XZPlane; // horizontal plane // Modes: XZPlane, Attached, HitNormal, DynamicViewAngle (default), SnapToSurfaces, None ``` ### Duplicatable Add alongside `DragControls` — dragging creates a clone instead of moving the original. ```ts import { Duplicatable } from "@needle-tools/engine"; obj.addComponent(Duplicatable); ``` ### DropListener Enables drag-and-drop of files from the desktop into the 3D scene (GLB, FBX, OBJ, USDZ, VRM, images). ```ts import { DropListener } from "@needle-tools/engine"; const dl = myObject.addComponent(DropListener); dl.fitIntoVolume = true; // auto-scale dropped objects dl.useNetworking = true; // sync drops to other clients // Or load programmatically: const loaded = await dl.loadFromURL("https://example.com/model.glb"); ``` ### CharacterController Capsule collider + rigidbody for character movement. Auto-creates physics components on enable. ```ts import { CharacterController } from "@needle-tools/engine"; const cc = this.gameObject.getComponent(CharacterController); cc.move(new Vector3(0, 0, 0.1)); // move forward cc.isGrounded; // true when touching ground // For jumping, use the rigidbody directly: if (cc.isGrounded) cc.rigidbody.applyImpulse(new Vector3(0, 5, 0)); ``` `CharacterControllerInput` provides a ready-made WASD + Space control scheme with double-jump and animator integration. For a full first-person controller example, see the [FirstPersonCharacter sample](https://github.com/needle-tools/needle-engine-samples/blob/main/package/Runtime/FirstPersonController/Scripts/FirstPersonController~/FirstPersonCharacter.ts). For clickable hotspot labels on 3D objects (common in product configurators), see the [Hotspot sample](https://github.com/needle-tools/needle-engine-samples/blob/main/package/Runtime/Hotspots/Scripts/Needle.Hotspots~/Hotspot.ts). ### needle-menu (built-in UI menu) The `` web component provides a built-in hamburger menu. Components like `SyncedRoom` and `Voip` add buttons to it automatically. Access via `this.context.menu`. ```ts // Add a button using ButtonInfo object (recommended) this.context.menu.appendChild({ label: "My Action", icon: "settings", // Google Material Icons name onClick: () => { /* ... */ }, priority: 50, // higher = further right, always visible }); // Or add a raw HTML button const button = document.createElement("button"); button.textContent = "Click me"; button.onclick = () => { /* ... */ }; this.context.menu.appendChild(button); // Control visibility (hiding requires Needle Engine PRO license in production) this.context.menu.setVisible(false); // Hide the Needle logo (requires license) this.context.menu.showNeedleLogo(false); // Set button priority (controls ordering and which buttons stay visible when space is limited) NeedleMenu.setElementPriority(button, 90); ``` --- ## Splines ### SplineContainer Defines curves/paths in the scene. Can be created in Unity/Blender or from code. ```ts import { SplineContainer } from "@needle-tools/engine"; import { Vector3 } from "three"; const spline = obj.addComponent(SplineContainer); spline.addKnot({ position: new Vector3(0, 0, 0) }) .addKnot({ position: new Vector3(5, 2, 5) }) .addKnot({ position: new Vector3(10, 0, 0) }); spline.closed = false; // Sample the spline (t: 0–1) const point = spline.getPointAt(0.5); // world-space position const tangent = spline.getTangentAt(0.5); // world-space tangent ``` ### SplineWalker Moves an object along a spline path. ```ts import { SplineWalker } from "@needle-tools/engine"; const walker = obj.addComponent(SplineWalker); walker.spline = splineContainer; walker.duration = 5; // seconds for full traversal walker.autoRun = true; walker.useLookAt = true; // face movement direction ``` --- ## Debug Tools ### Gizmos Static methods for runtime debug drawing — shapes auto-remove after a duration (0 = one frame). ```ts import { Gizmos } from "@needle-tools/engine"; Gizmos.DrawLine(start, end, color, duration, depthTest); Gizmos.DrawWireSphere(center, radius, color, duration); Gizmos.DrawRay(origin, direction, color, duration); Gizmos.DrawLabel(position, text, size, duration); Gizmos.DrawArrow(start, end, color, duration); Gizmos.DrawWireBox(center, size, color, duration); ``` --- ## Utilities ### EventList (Unity Events) `EventList` is how Unity Events are serialized and invoked at runtime. Declare with `@serializable(EventList)` and call `.invoke()`. ```ts import { EventList, serializable } from "@needle-tools/engine"; @serializable(EventList) onClick?: EventList; // Invoke from code: this.onClick?.invoke(); // Subscribe from code: const unsub = this.onClick?.addEventListener(() => console.log("Clicked!")); unsub(); // unsubscribe ``` ### Creating Objects from Code `ObjectUtils` provides convenience methods for creating primitives and text. These are helpers — you can always use standard Three.js objects directly (`new Mesh(geometry, material)`). ```ts import { ObjectUtils, PrimitiveType } from "@needle-tools/engine"; const cube = ObjectUtils.createPrimitive(PrimitiveType.Cube, { color: 0xff0000, parent: this.gameObject, position: { x: 0, y: 1, z: 0 } }); const text = ObjectUtils.createText("Hello World"); this.context.scene.add(text); ``` Available primitives: `Cube`, `Sphere`, `Quad`, `Cylinder`. For anything more complex, use Three.js geometry directly or load GLB models. ### ParticleSystem Full particle system with emission, shape, velocity, color/size over lifetime modules. Currently best configured via Unity/Blender — difficult to set up from code only. ```ts import { ParticleSystem } from "@needle-tools/engine"; const ps = this.gameObject.getComponent(ParticleSystem); ps.play(); ps.stop(); ps.pause(); ```