Files
AR-Menu/.agents/skills/needle-engine/references/integration.md
pelpanagiotis a7c53a08a0 Initial commit: Unity Needle AR Menu project with MenuScene and SampleScene web apps
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
2026-04-19 22:41:05 +03:00

4.5 KiB

Needle Engine — Framework Integration

React

Listen for 3D events in a component

import { useEffect, useState } from "react";

function ScoreDisplay() {
  const [score, setScore] = useState(0);

  useEffect(() => {
    const ne = document.querySelector("needle-engine");
    const handler = (e: CustomEvent) => setScore(e.detail.score);
    ne?.addEventListener("score-changed", handler as EventListener);
    return () => ne?.removeEventListener("score-changed", handler as EventListener);
  }, []);

  return <div>Score: {score}</div>;
}

Call into the 3D scene from React

function GameControls() {
  const addScore = async () => {
    const { findObjectOfType } = await import("@needle-tools/engine");
    const { MyScoreManager } = await import("./scripts/MyScoreManager.js");
    findObjectOfType(MyScoreManager)?.addScore(10);
  };
  return <button onClick={addScore}>+10</button>;
}

Use engine hooks from React

useEffect(() => {
  import("@needle-tools/engine").then(({ onStart }) => {
    onStart((ctx) => {
      // safe to access components here
    });
  });
}, []);

Svelte / SvelteKit

<script>
  import { onMount } from "svelte";
  let score = 0;

  onMount(async () => {
    // Dynamic import required for SSR — engine needs browser APIs
    const { onStart } = await import("@needle-tools/engine");

    // onStart fires once when the context/scene is ready — never poll with setInterval
    onStart(ctx => {
      // Safe to access components, add components, etc.
      console.log("Scene ready:", ctx.scene);
    });

    // Listen for custom events from 3D components
    const ne = document.querySelector("needle-engine");
    const handler = (e) => (score = e.detail.score);
    ne?.addEventListener("score-changed", handler);
    return () => ne?.removeEventListener("score-changed", handler);
  });

  async function addScore() {
    const { findObjectOfType, Context } = await import("@needle-tools/engine");
    const { MyScoreManager } = await import("../scripts/MyScoreManager.js");
    findObjectOfType(MyScoreManager, Context.Current)?.addScore(10);
  }
</script>

<p>Score: {score}</p>
<button on:click={addScore}>+10</button>
<needle-engine src="assets/scene.glb" />

Vue / Nuxt

<template>
  <div>Score: {{ score }}</div>
  <needle-engine src="assets/scene.glb" ref="ne" />
</template>

<script setup>
import { ref, onMounted, onUnmounted } from "vue";

const score = ref(0);
const ne = ref(null);

function onScore(e) { score.value = e.detail.score; }

onMounted(() => {
  ne.value?.addEventListener("score-changed", onScore);
  import("@needle-tools/engine").then(({ onStart }) => {
    onStart((ctx) => {
      // safe to access components here
    });
  });
});
onUnmounted(() => ne.value?.removeEventListener("score-changed", onScore));
</script>

Vanilla JS / No Framework

<needle-engine src="assets/scene.glb"></needle-engine>

<script type="module">
  import { onStart, onUpdate } from "@needle-tools/engine";

  onStart((ctx) => {
    console.log("Scene ready:", ctx.scene);
  });
</script>

Engine Hooks Reference

These standalone functions from @needle-tools/engine mirror the component lifecycle but work outside of a class:

Hook When it fires
onInitialized(cb) Once after context creation and first content load
onStart(cb) Once when the context/scene is ready
onUpdate(cb) Every frame (before rendering)
onBeforeRender(cb) Just before Three.js renders
onAfterRender(cb) Just after Three.js renders
onClear(cb) Before context is cleared (e.g. when src changes)
onDestroy(cb) When the context is torn down

All callbacks receive (ctx: Context) as their argument.

Client-only (no SSR)

When server-side rendering is disabled, import and call hooks directly:

import { onStart, onUpdate, onBeforeRender, onDestroy } from "@needle-tools/engine";

onStart((ctx) => { /* setup */ });
onUpdate((ctx) => { /* per-frame logic */ });
onDestroy((ctx) => { /* cleanup */ });

With SSR (Next.js, SvelteKit, Nuxt, etc.)

@needle-tools/engine depends on WebGL / browser APIs and cannot be imported on the server. Use a dynamic import so the module is only loaded client-side (same pattern as with any three.js-based engine):

import("@needle-tools/engine").then(({ onStart, onUpdate, onDestroy }) => {
  onStart((ctx) => { /* setup */ });
  onUpdate((ctx) => { /* per-frame logic */ });
  onDestroy((ctx) => { /* cleanup */ });
});