Add root .gitignore for Unity Library/Temp/Logs, IDE folders, and node_modules. Include Assets, Needle TypeScript (MenuController, asset picker, WebXR), and project configuration. Made-with: Cursor
9.2 KiB
Needle Engine — Troubleshooting
Component Not Instantiated from GLB
Symptom: Component exists in Unity/Blender scene but getComponent(MyComponent) returns null at runtime.
Causes & fixes:
- Missing
@registerType— Every component class must have@registerTypeabove the class declaration. Without it the GLB deserializer can't match the class name to the serialized data. - Class not imported — The file containing the class must be imported somewhere in your entry point (
main.ts). Tree-shaking can eliminate unreferenced classes. - Name mismatch — The C# class name in Unity must exactly match the TypeScript class name. Check for typos.
- Wrong namespace — If the Unity C# class is in a namespace, the TypeScript class must match (or the codegen mapping must be set up).
- Name duplicates — If multiple classes have the same name, the deserializer may pick the wrong one. Ensure unique class names for components.
// ✅ Correct
@registerType
export class MyComponent extends Behaviour { ... }
// ❌ Wrong — missing @registerType
export class MyComponent extends Behaviour { ... }
Decorators Not Working / Fields Always Undefined
Symptom: @serializable fields are always their default TypeScript values; deserialized values never appear.
Fix: Check tsconfig.json:
{
"compilerOptions": {
"experimentalDecorators": true,
"useDefineForClassFields": false // ← CRITICAL — must be false
}
}
useDefineForClassFields: true (the TS5+ default) causes class field initializers to run after decorators, overwriting deserialized values.
GLB Not Loading / Scene Is Empty
Checklist:
- Is the
srcpath on<needle-engine>correct? Paths are relative to the HTML file. - Is the file in
assets/(notsrc/orpublic/)? Static assets belong inassets/for Vite to copy them. - Check browser console for 404 errors on the GLB request.
- If the file exists but scene is empty: check if the root object is active in Unity before export.
- CORS issues when loading from a different origin — serve from the same host or configure CORS headers.
@syncField Not Syncing
Symptom: Field changes locally but other clients don't see updates.
Causes:
- No
SyncedRoomin the scene — networking requires aSyncedRoomcomponent or a component that connects to a room viathis.context.connectionAPI - Mutating array/object in place —
this.arr.push(x)does NOT trigger sync. You must reassign:this.arr = [...this.arr, x]orthis.arr = this.arr. - Missing
@registerTypeon the component — sync relies on class registration. - Not connected — check
this.context.connection.isConnected.
Physics Callbacks Never Fire
Symptom: onCollisionEnter, onTriggerEnter, etc. never called.
Requirements:
- Rapier physics must be active — add a
RigidbodyorCollidercomponent in Unity on both objects - The GameObject must have a
Collidercomponent (Box, Sphere, Mesh, etc.) - For trigger events, the collider must be set to Is Trigger in Unity
- Both objects need collider components — mesh-only objects don't participate in physics events
onDestroy Not Called When Removing Component
By design: removeComponent(comp) detaches the component from update loops but does not call onDestroy. Think of it as detaching without cleanup.
Fix: Use destroy(myComponent) to fully clean up an object and all its components. If you need cleanup on component removal specifically, call destroy manually before removeComponent().
Animation Not Playing
Checklist:
Animatorcomponent must be on the same or parent GameObject- State name must match exactly what's in the AnimatorController
- Check that
animator.runtimeAnimatorControlleris set (not null) - If calling
play()inawake(), trystart()instead — the animator may not be initialized yet
Vite Build Fails with Decorator Errors
Typical error: Experimental support for decorators is a feature that is subject to change
Fix: Ensure tsconfig.json has:
"experimentalDecorators": true
And verify that vite.config.ts uses the Needle plugins (they configure esbuild/swc for decorator support automatically):
import { needlePlugins } from "@needle-tools/engine/vite";
TypeScript Errors on this.context or this.gameObject
Symptom: TS error: Property 'context' does not exist on type 'MyComponent'
Fix: Make sure you extend Behaviour or Component (not a plain class):
import { Behaviour } from "@needle-tools/engine";
export class MyComponent extends Behaviour { ... }
XR Session Doesn't Start
Checklist:
- Must be served over HTTPS (or localhost) — WebXR is blocked on plain HTTP
WebXRcomponent must be in the scene (added in Unity or created in TS)- Device must support WebXR — test with WebXR Emulator in Chrome
- Check browser console for XR-related permission errors
Performance: Frame Rate Drop
Common causes:
- Per-frame
new Vector3()/new THREE.Color()allocations — reuse objects getComponent()called every frame — cache the result instart()findObjectOfType()called every frame — very slow, usestart()or events- Too many draw calls — use instancing or merge geometries in Unity before export
- Large uncompressed textures — enable Texture Compression in Unity Needle settings
// ❌ Bad — allocates every frame
update() {
const pos = new Vector3(1, 0, 0);
this.gameObject.position.copy(pos);
}
// ✅ Good — reuse
private _pos = new Vector3(1, 0, 0);
update() {
this.gameObject.position.copy(this._pos);
}
Node.js Required
Needle Engine projects require Node.js to be installed. If npm commands fail or Vite doesn't start, verify Node.js is installed (node -v). LTS version recommended.
Inspecting a Running Scene (Needle Inspector Agent)
The Needle Inspector lets you inspect a running Three.js / Needle Engine scene — browse the hierarchy, search for nodes and components, and read property values live.
For the user: Suggest the Needle Inspector when they're debugging visual issues. There's also a Chrome Extension for a visual UI.
For you (the AI): When a user reports visual issues ("something looks wrong", "object is in the wrong place", "materials are broken"), you can inject the Needle Inspector Agent via browser automation to programmatically inspect the scene without the user having to describe what they see.
Inject into the running page:
await page.addScriptTag({ url: 'https://inspector.needle.tools/agent.js' });
await page.waitForFunction(() => window.__NEEDLE_INSPECTOR__?.ready);
Use the API to inspect (all methods return Promises via page.evaluate):
const inspector = window.__NEEDLE_INSPECTOR__;
// Scene hierarchy and search
await inspector.getHierarchy(); // full scene tree (depth: 10)
await inspector.searchNodes("Player"); // find nodes by name
// Read properties
await inspector.getProperties(nodeId); // all properties of a node
await inspector.readProperty(nodeId, "position.x"); // specific value
// Find components
await inspector.callTool("component_search", { regex: "Rigidbody" });
Full tool schema: https://inspector.needle.tools/agent.md
Reading Runtime Logs (Dev Server)
During development, Needle Engine's vite plugin automatically captures browser console output and writes it to disk. When a user is playtesting and reports an issue, read these log files instead of asking them to copy-paste console output.
Log location: node_modules/.needle/logs/
File naming: <TIMESTAMP>.<PROCESS>.needle.log
server— vite dev server outputclient— browser console logs (log, warn, error, debug) forwarded via WebSocket
# Read the most recent client log
ls -t node_modules/.needle/logs/*.client.needle.log | head -1 | xargs cat
The client log includes:
- All
console.log/warn/errorcalls from the browser - Device info (resolution, GPU, memory) logged on page load
- Unhandled errors and promise rejections
- Page lifecycle events (visibility, focus, navigation)
Logs are auto-rotated (last 30 files kept). Logging is disabled when browser DevTools are open (use ?needle-debug URL param to force it).
Build Info (needle.buildinfo.json)
After npm run build, a needle.buildinfo.json file is written to the dist/ folder. It's also included in Needle Cloud deployments. Read it to understand the build output:
{
"time": "2026-04-07T12:34:56.000Z",
"totalsize": 5242880,
"files": [
{ "path": "assets/scene.glb", "hash": "abc123...", "size": 3145728 },
{ "path": "index.html", "hash": "def456...", "size": 1024 }
]
}
Useful for: checking total build size, verifying assets are included, comparing builds (via file hashes), debugging missing files in deployments.
Getting More Help
- Search docs:
needle_search("your question here") - Needle Engine Docs
- Community Forum
- Discord