Needle Engine Dokumentation
Downloads
  • What is Needle Engine?
  • Erfahrungsberichte
  • Get an overview

    • Samples and Showcase
    • Unsere Vision 🔮
    • Funktionsübersicht
    • Technischer Überblick
  • Resources

    • Pricing and Plans
    • Changelog
    • API Documentation
    • Support & Community
  • Integrations

    • Needle Engine für Unity
    • Needle Engine für Blender
    • Needle Engine als Web Component
    • Needle Engine auf Ihrer Website
    • Needle Cloud
  • Topics

    • Web-Projektstruktur
    • Everywhere Actions
    • Assets nach glTF exportieren
    • Frameworks, Bundler, HTML
    • Testen auf lokalen Geräten
    • Bereitstellung und Optimierung
  • Advanced

    • Netzwerkfunktionen
    • VR & AR (WebXR)
    • Needle Engine direkt aus HTML verwenden
    • Editor Synchronisierung
  • Troubleshooting

    • Debugging
    • Fragen und Antworten (FAQ) 💡
    • Get Help
  • Videos

    • Tutorials on Youtube
    • Interviews on Youtube
  • Scripting Overview

    • Scripting in Needle Engine
    • Einführung in das Scripting für Unity-Entwickler
    • Needle-Kernkomponenten
    • Everywhere Actions
  • Components and Lifecycle

    • Erstellen und Verwenden von Komponenten
    • @serializable und andere Decorators
    • Automatische Komponenten-Generierung
    • Scripting Beispiele
    • Community Contributions
    • Zusätzliche Module
  • Settings and APIs

    • <needle-engine> Konfiguration
    • needle.config.json
    • Needle Engine API
    • three.js API
Help
Samples
Pricing
  • Needle Website
  • Needle Cloud
  • Support Community
  • Discord Server
  • X/Twitter
  • YouTube
  • Newsletter
  • Email
  • Feedback
  • Github
  • English
  • 简体中文
  • Español
  • Português
  • Français
  • हिन्दी
  • 日本語
  • Deutsch
  • Tiếng Việt
Downloads
  • What is Needle Engine?
  • Erfahrungsberichte
  • Get an overview

    • Samples and Showcase
    • Unsere Vision 🔮
    • Funktionsübersicht
    • Technischer Überblick
  • Resources

    • Pricing and Plans
    • Changelog
    • API Documentation
    • Support & Community
  • Integrations

    • Needle Engine für Unity
    • Needle Engine für Blender
    • Needle Engine als Web Component
    • Needle Engine auf Ihrer Website
    • Needle Cloud
  • Topics

    • Web-Projektstruktur
    • Everywhere Actions
    • Assets nach glTF exportieren
    • Frameworks, Bundler, HTML
    • Testen auf lokalen Geräten
    • Bereitstellung und Optimierung
  • Advanced

    • Netzwerkfunktionen
    • VR & AR (WebXR)
    • Needle Engine direkt aus HTML verwenden
    • Editor Synchronisierung
  • Troubleshooting

    • Debugging
    • Fragen und Antworten (FAQ) 💡
    • Get Help
  • Videos

    • Tutorials on Youtube
    • Interviews on Youtube
  • Scripting Overview

    • Scripting in Needle Engine
    • Einführung in das Scripting für Unity-Entwickler
    • Needle-Kernkomponenten
    • Everywhere Actions
  • Components and Lifecycle

    • Erstellen und Verwenden von Komponenten
    • @serializable und andere Decorators
    • Automatische Komponenten-Generierung
    • Scripting Beispiele
    • Community Contributions
    • Zusätzliche Module
  • Settings and APIs

    • <needle-engine> Konfiguration
    • needle.config.json
    • Needle Engine API
    • three.js API
Help
Samples
Pricing
  • Needle Website
  • Needle Cloud
  • Support Community
  • Discord Server
  • X/Twitter
  • YouTube
  • Newsletter
  • Email
  • Feedback
  • Github
  • English
  • 简体中文
  • Español
  • Português
  • Français
  • हिन्दी
  • 日本語
  • Deutsch
  • Tiếng Việt
  • Getting Started

    • Downloads
    • Needle Engine for Unity
    • Needle Engine for Blender
    • Needle Engine as Web Component
    • Needle Engine on your Website
    • Needle Cloud
    • Custom integrations
    • Support and Community
  • Core Concepts

    • Web-Projektstruktur
    • Everywhere Actions
    • Assets nach glTF exportieren
    • Frameworks, Bundler, HTML
    • Testen auf lokalen Geräten
    • Bereitstellung und Optimierung
    • Debugging
    • Fragen und Antworten (FAQ) 💡
  • Scripting

    • Scripting in Needle Engine
    • Einführung in das Scripting für Unity-Entwickler
    • Erstellen und Verwenden von Komponenten
    • Automatische Komponenten-Generierung
    • Scripting Beispiele
    • Community Contributions
  • Advanced

    • VR & AR (WebXR)
    • Netzwerkfunktionen
    • Editor Synchronisierung
  • Reference

    • Funktionsübersicht
    • Technischer Überblick
    • Needle-Kernkomponenten
    • needle.config.json
    • <needle-engine> Konfiguration
    • @serializable und andere Decorators

Scripting Beispiele

Wenn du neu im Scripting bist, empfehlen wir dringend, zuerst die folgenden Anleitungen zu lesen:

  • Einsteiger-Anleitung: Typescript Grundlagen
  • Einsteiger-Anleitung: Needle Engine für Unity Entwickler
  • Video-Tutorial: Wie man eigene Komponenten schreibt

Unten findest du einige grundlegende Skripte als schnelle Referenz.

Wir bieten auch viele Beispiel-Szenen und komplette Projekte an, die du herunterladen und als Ausgangspunkt verwenden kannst:

  • Besuche die Samples Website
  • Lade das Samples Package herunter
  • Needle Engine Stackblitz Sammlung
  • Needle Engine API

Grundlegende Komponente

import { Behaviour, serializable } from "@needle-tools/engine"
import { Object3D } from "three"

export class MyComponent extends Behaviour {

    @serializable(Object3D)
    myObjectReference?: Object3D;

    start() {
        console.log("Hello world", this);
    }

    update() {
        this.gameObject.rotateY(this.context.time.deltaTime);
    }
}

siehe scripting für alle Komponenten-Events

Referenziere ein Objekt aus Unity

import { Behaviour, serializable, Camera } from "@needle-tools/engine";
import { Object3D } from "three"

export class MyClass extends Behaviour {
    // this will be a "Transform" field in Unity
    @serializable(Object3D) 
    myObjectReference: Object3D | null = null;
    
    // this will be a "Transform" array field in Unity
    // Note that the @serializable decorator contains the array content type! (Object3D and not Object3D[])
    @serializable(Object3D) 
    myObjectReferenceList: Object3D[] | null = null;

    // for component or other objects use the object's type
    @serializable(Camera)
    myCameraComponent: Camera | null = null;
}

Referenziere und lade ein Asset aus Unity (Prefab oder SceneAsset)

import { Behaviour, serializable, AssetReference } from "@needle-tools/engine";

export class MyClass extends Behaviour {

    // if you export a prefab or scene as a reference from Unity you'll get a path to that asset
    // which you can de-serialize to AssetReference for convenient loading
    @serializable(AssetReference)
    myPrefab?: AssetReference;
    
    async start() {
      // directly instantiate
      const myInstance = await this.myPrefab?.instantiate();

      // you can also just load and instantiate later
      // const myInstance = await this.myPrefab.loadAssetAsync();
      // this.gameObject.add(myInstance)
      // this is useful if you know that you want to load this asset only once because it will not create a copy
      // since ``instantiate()`` does create a copy of the asset after loading it
    }  
}

Referenziere und lade Szenen aus Unity

Tips

Finde ein funktionierendes Beispiel in unseren Samples zum Herunterladen und Ausprobieren

import { Behaviour, serializable, AssetReference } from "@needle-tools/engine";

export class LoadingScenes extends Behaviour {
    // tell the component compiler that we want to reference an array of SceneAssets
    // @type UnityEditor.SceneAsset[]
    @serializable(AssetReference)
    myScenes?: AssetReference[];

    async awake() {
        if (!this.myScenes) {
            return;
        }
        for (const scene of this.myScenes) {
            // check if it is assigned in unity
            if(!scene) continue;
            // load the scene once
            const myScene = await scene.loadAssetAsync();
            // add it to the threejs scene
            this.gameObject.add(myScene);
            
            // of course you can always just load one at a time
            // and remove it from the scene when you want
            // myScene.removeFromParent();
            // this is the same as scene.asset.removeFromParent()
        }
    }

    onDestroy(): void {
        if (!this.myScenes) return;
        for (const scene of this.myScenes) {
            scene?.unload();
        }
    }
}

Klicks auf Objekte empfangen

Füge dieses Skript zu jedem Objekt in deiner Szene hinzu, das anklickbar sein soll. Stelle sicher, dass sich auch eine ObjectRaycaster Komponente in der übergeordneten Hierarchie dieses Objekts befindet.

test
import { Behaviour, IPointerClickHandler, PointerEventData, showBalloonMessage } from "@needle-tools/engine";

export class ClickExample extends Behaviour implements IPointerClickHandler {

    // Make sure to have an ObjectRaycaster component in the parent hierarchy
    onPointerClick(_args: PointerEventData) {
        showBalloonMessage("Clicked " + this.name);
    }
}

Vernetzte Klicks auf Objekte

Füge dieses Skript zu jedem Objekt in deiner Szene hinzu, das anklickbar sein soll. Stelle sicher, dass sich auch eine ObjectRaycaster Komponente in der übergeordneten Hierarchie dieses Objekts befindet. Die Komponente sendet den empfangenen Klick an alle verbundenen Clients und löst ein Event aus, auf das du dann in deiner App reagieren kannst. Wenn du Unity oder Blender verwendest, kannst du einfach Funktionen dem onClick Event zuweisen, um z. B. eine Animation abzuspielen oder Objekte auszublenden.

import { Behaviour, EventList, IPointerClickHandler, PointerEventData, serializable } from "@needle-tools/engine";

export class SyncedClick extends Behaviour implements IPointerClickHandler {

    @serializable(EventList)
    onClick!: EventList;

    onPointerClick(_args: PointerEventData) {
        console.log("SEND CLICK");
        this.context.connection.send("clicked/" + this.guid);
        this.onClick?.invoke();
    }

    onEnable(): void {
        this.context.connection.beginListen("clicked/" + this.guid, this.onRemoteClick);
    }
    onDisable(): void {
        this.context.connection.stopListen("clicked/" + this.guid, this.onRemoteClick);
    }


    onRemoteClick = () => {
        console.log("RECEIVED CLICK");
        this.onClick?.invoke();
    }
    
}

Animation bei Klick abspielen

import { Behaviour, serializable, Animation, IPointerClickHandler, PointerEventData } from "@needle-tools/engine";

export class PlayAnimationOnClick extends Behaviour implements IPointerClickHandler {

    @serializable(Animation)
    animation?: Animation;

    awake() {
        if (this.animation) {
            this.animation.playAutomatically = false;
            this.animation.loop = false;
        }
    }

    onPointerClick(_args: PointerEventData) {
        if (this.animation) {
            this.animation.play();
        }
    }
}

Referenziere einen Animation Clip

Dies kann nützlich sein, wenn du deine eigene Animationslogik ausführen möchtest. Du kannst auch ein Array von Clips exportieren.

import { Behaviour, serializable } from "@needle-tools/engine";
import { AnimationClip } from "three"

export class ExportAnimationClip extends Behaviour {

    @serializable(AnimationClip)
    animation?: AnimationClip;

    awake() {
        console.log("My referenced animation clip", this.animation);
    }
}

Erstelle und löse ein UnityEvent aus

import { Behaviour, serializable, EventList } from "@needle-tools/engine"

export class MyComponent extends Behaviour {

    @serializable(EventList)
    myEvent? : EventList;

    start() {
        this.myEvent?.invoke();
    }
}

Tips

EventList Events werden auch auf Komponentenebene ausgelöst. Das bedeutet, du kannst das oben deklarierte Event auch mit myComponent.addEventListener("my-event", evt => {...}) abonnieren. Dies ist eine experimentelle Funktion. Bitte gib Feedback in unserem Forum

Deklariere einen benutzerdefinierten Event-Typ

Dies ist nützlich, wenn du ein Event mit benutzerdefinierten Argumenten (wie einem String) in Unity oder Blender verfügbar machen möchtest

import { Behaviour, serializable, EventList } from "@needle-tools/engine";
import { Object3D } from "three";

/*
Make sure to have a c# file in your project with the following content:

using UnityEngine;
using UnityEngine.Events;

[System.Serializable]
public class MyCustomUnityEvent : UnityEvent<string>
{
}

Unity documentation about custom events: 
https://docs.unity3d.com/ScriptReference/Events.UnityEvent_2.html

*/

// Documentation → https://docs.needle.tools/scripting

export class CustomEventCaller extends Behaviour {

    // The next line is not just a comment, it defines 
    // a specific type for the component generator to use.

    //@type MyCustomUnityEvent
    @serializable(EventList)
    myEvent!: EventList;

    // just for testing - could be when a button is clicked, etc.
    start() {
        this.myEvent.invoke("Hello");
    }
}

export class CustomEventReceiver extends Behaviour {

    logStringAndObject(str: string) {
        console.log("From Event: ", str);
    }
}

Beispielverwendung:20221128-210735_Unity-needle

Verschachtelte Objekte und Serialization verwenden

Du kannst Objekte und ihre Daten verschachteln. Mit passenden @serializable(SomeType) Decorators werden die Daten automatisch in die richtigen Typen serialisiert und deserialisiert.

In deiner Typescript-Komponente:

import { Behaviour, serializable } from "@needle-tools/engine";

// Documentation → https://docs.needle.tools/scripting

class CustomSubData {
    @serializable()
    subString: string = "";
    
    @serializable()
    subNumber: number = 0;
}

class CustomData {
    @serializable()
    myStringField: string = "";
    
    @serializable()
    myNumberField: number = 0;
    
    @serializable()
    myBooleanField: boolean = false;
    
    @serializable(CustomSubData)
    subData: CustomSubData | undefined = undefined;

    someMethod() {
        console.log("My string is " + this.myStringField, "my sub data", this.subData)
    }
}

export class SerializedDataSample extends Behaviour {

    @serializable(CustomData)  
    myData: CustomData | undefined;
    
    onEnable() {
        console.log(this.myData);
        this.myData?.someMethod();
    }
}

In C# in jedem Skript:

using System;

[Serializable]
public class CustomSubData
{
    public string subString;
    public float subNumber;
}
	
[Serializable]
public class CustomData
{
    public string myStringField;
    public float myNumberField;
    public bool myBooleanField;
    public CustomSubData subData;
}

Tips

Ohne die korrekten Typ-Decorators erhältst du die Daten weiterhin, aber nur als einfaches Objekt. Das ist nützlich, wenn du Komponenten portierst, da du Zugriff auf alle Daten hast und die benötigten Typen hinzufügen kannst.

Web APIs verwenden

Tips

Denke daran, dass du weiterhin Zugriff auf alle Web APIs und npm Pakete hast! Das ist die Schönheit von Needle Engine, wenn wir das hier so sagen dürfen 😊

Aktuellen Standort anzeigen

import { Behaviour, showBalloonMessage } from "@needle-tools/engine";

export class WhereAmI extends Behaviour {
    start() {
        navigator.geolocation.getCurrentPosition((position) => {
            console.log("Navigator response:", position);
            const latlong = position.coords.latitude + ", " + position.coords.longitude;
            showBalloonMessage("You are at\nLatLong " + latlong);
        });
    }
}

Aktuelle Zeit mit einer Coroutine anzeigen

import { Behaviour, Text, serializable, WaitForSeconds } from "@needle-tools/engine";

export class DisplayTime extends Behaviour {

    @serializable(Text)
    text?: Text;

    onEnable(): void {
        this.startCoroutine(this.updateTime())
    }

    private *updateTime() {
        while (true) {
            if (this.text) {
                this.text.text = new Date().toLocaleTimeString();
                console.log(this.text.text)
            }
            yield WaitForSeconds(1)
        }
    };
}

Benutzerdefinierte Shader-Eigenschaft ändern

Angenommen, du hast einen benutzerdefinierten Shader mit einer Eigenschaft namens _Speed, die ein Float-Wert ist, so würdest du sie von einem Skript aus ändern. Ein Live-Beispiel zum Herunterladen findest du in unseren Samples

import { Behaviour, serializable } from "@needle-tools/engine";
import { Material } from "three";

declare type MyCustomShaderMaterial = {
   _Speed: number;
};

export class IncreaseShaderSpeedOverTime extends Behaviour {

   @serializable(Material)
   myMaterial?: Material & MyCustomShaderMaterial;

   update() {
       if (this.myMaterial) {
           this.myMaterial._Speed *= 1 + this.context.time.deltaTime;
           if(this.myMaterial._Speed > 1) this.myMaterial._Speed = .0005;
           if(this.context.time.frame % 30 === 0) console.log(this.myMaterial._Speed)
       }
   }
}
View on GitHub

src Attribut wechseln

Siehe Live-Beispiel auf StackBlitz

Neue Postprocessing Effekte hinzufügen

Stelle sicher, dass du npm i postprocessing in deinem Webprojekt installierst. Dann kannst du neue Effekte hinzufügen, indem du von PostProcessingEffect ableitest.

Um den Effekt zu verwenden, füge ihn demselben Objekt wie deine Volume Komponente hinzu.

Hier ist ein Beispiel, das den Outline Postprocessing Effekt umschließt. Du kannst Variablen und Einstellungen wie gewohnt verfügbar machen, da jeder Effekt auch nur eine Komponente in deiner three.js Szene ist.

import { EffectProviderResult, PostProcessingEffect, registerCustomEffectType, serializable } from "@needle-tools/engine";
import { OutlineEffect } from "postprocessing";
import { Object3D } from "three";

export class OutlinePostEffect extends PostProcessingEffect {

    // the outline effect takes a list of objects to outline
    @serializable(Object3D)
    selection!: Object3D[];

    // this is just an example method that you could call to update the outline effect selection
    updateSelection() {
        if (this._outlineEffect) {
            this._outlineEffect.selection.clear();
            for (const obj of this.selection) {
                this._outlineEffect.selection.add(obj);
            }
        }
    }


    // a unique name is required for custom effects
    get typeName(): string {
        return "Outline";
    }

    private _outlineEffect: void | undefined | OutlineEffect;

    // method that creates the effect once
    onCreateEffect(): EffectProviderResult | undefined {

        const outlineEffect = new OutlineEffect(this.context.scene, this.context.mainCamera!);
        this._outlineEffect = outlineEffect;
        outlineEffect.edgeStrength = 10;
        outlineEffect.visibleEdgeColor.set(0xff0000);
        for (const obj of this.selection) {
            outlineEffect.selection.add(obj);
        }

        return outlineEffect;
    }
}
// You need to register your effect type with the engine
registerCustomEffectType("Outline", OutlinePostEffect);

Benutzerdefiniertes ParticleSystem Verhalten

import { Behaviour, ParticleSystem } from "@needle-tools/engine";
import { ParticleSystemBaseBehaviour, QParticle } from "@needle-tools/engine";

// Derive your custom behaviour from the ParticleSystemBaseBehaviour class (or use QParticleBehaviour)
class MyParticlesBehaviour extends ParticleSystemBaseBehaviour {

    // callback invoked per particle
    update(particle: QParticle): void {
        particle.position.y += 5 * this.context.time.deltaTime;
    }
}
export class TestCustomParticleSystemBehaviour extends Behaviour {
    start() {
        // add your custom behaviour to the particle system
        this.gameObject.getComponent(ParticleSystem)!.addBehaviour(new MyParticlesBehaviour())
    }
}

Benutzerdefinierte 2D Audio Komponente

Dies ist ein Beispiel, wie du deine eigene Audio-Komponente erstellen könntest. Für die meisten Anwendungsfälle kannst du jedoch die Kern-AudioSource Komponente verwenden und musst keinen Code schreiben.

import { AudioSource, Behaviour, serializable } from "@needle-tools/engine";

// declaring AudioClip type is for codegen to produce the correct input field (for e.g. Unity or Blender)
declare type AudioClip = string;

export class My2DAudio extends Behaviour {

    // The clip contains a string pointing to the audio file - by default it's relative to the GLB that contains the component
    // by adding the URL decorator the clip string will be resolved relative to your project root and can be loaded
    @serializable(URL)
    clip?: AudioClip;

    awake() {
        // creating a new audio element and playing it
        const audioElement = new Audio(this.clip);
        audioElement.loop = true;
        // on the web we have to wait for the user to interact with the page before we can play audio
        AudioSource.registerWaitForAllowAudio(() => {
            audioElement.play();
        })
    }
}

Beliebige externe Dateien

Verwende den FileReference Typ, um externe Dateien (z. B. eine JSON-Datei) zu laden.

import { Behaviour, FileReference, ImageReference, serializable } from "@needle-tools/engine";

export class FileReferenceExample extends Behaviour {

    // A FileReference can be used to load and assign arbitrary data in the editor. You can use it to load images, audio, text files... FileReference types will not be saved inside as part of the GLB (the GLB will only contain a relative URL to the file)
    @serializable(FileReference)
    myFile?: FileReference;
    // Tip: if you want to export and load an image (that is not part of your GLB) if you intent to add it to your HTML content for example you can use the ImageReference type instead of FileReference. It will be loaded as an image and you can use it as a source for an <img> tag.

    async start() {
        console.log("This is my file: ", this.myFile);
        // load the file
        const data = await this.myFile?.loadRaw();
        if (!data) {
            console.error("Failed loading my file...");
            return;
        }
        console.log("Loaded my file. These are the bytes:", await data.arrayBuffer());
    }
}

HTML-Element-Klick in Komponente empfangen

import { Behaviour, EventList, serializable, serializeable } from "@needle-tools/engine";

export class HTMLButtonClick extends Behaviour {

    /** Enter a button query (e.g. button.some-button if you're interested in a button with the class 'some-button') 
     * Or you can also use an id (e.g. #some-button if you're interested in a button with the id 'some-button')
     * Or you can also use a tag (e.g. button if you're interested in any button
    */
    @serializeable()
    htmlSelector: string = "button.some-button";
    
    /** This is the event to be invoked when the html element is clicked. In Unity or Blender you can assign methods to be called in the Editor */
    @serializable(EventList)
    onClick: EventList = new EventList();

    private element? : HTMLButtonElement;

    onEnable() {
        // Get the element from the DOM
        this.element = document.querySelector(this.htmlSelector) as HTMLButtonElement;
        if (this.element) {
            this.element.addEventListener('click', this.onClicked);
        }
        else console.warn(`Could not find element with selector \"${this.htmlSelector}\"`);
    }

    onDisable() {
        if (this.element) {
            this.element.removeEventListener('click', this.onClicked);
        }
    }

    private onClicked = () => {
        this.onClick.invoke();
    }
}
View on GitHub

Umgebungslicht deaktivieren

import { Behaviour } from "@needle-tools/engine";
import { Texture } from "three";

export class DisableEnvironmentLight extends Behaviour {

   private _previousEnvironmentTexture: Texture | null = null;

   onEnable(): void {
       this._previousEnvironmentTexture = this.context.scene.environment;
       this.context.scene.environment = null;
   }

   onDisable(): void {
       this.context.scene.environment = this._previousEnvironmentTexture;
   }
}
View on GitHub

mediapipe Paket verwenden, um die 3D-Szene mit Händen zu steuern

Stelle sicher, dass du das mediapipe Paket installierst. Besuche den GitHub-Link unten, um die komplette Projekteinrichtung zu sehen. Probiere es hier live aus - erfordert eine Webcam/Kamera

import { FilesetResolver, HandLandmarker, HandLandmarkerResult, NormalizedLandmark } from "@mediapipe/tasks-vision";
import { Behaviour, Mathf, serializable, showBalloonMessage } from "@needle-tools/engine";
import { ParticleSphere } from "./ParticleSphere";

export class MediapipeHands extends Behaviour {

    @serializable(ParticleSphere)
    spheres: ParticleSphere[] = [];

    private _video!: HTMLVideoElement;
    private _handLandmarker!: HandLandmarker;

    async awake() {
        showBalloonMessage("Initializing mediapipe...")

        const vision = await FilesetResolver.forVisionTasks(
            // path/to/wasm/root
            "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm"
        );
        this._handLandmarker = await HandLandmarker.createFromOptions(
            vision,
            {
                baseOptions: {
                    modelAssetPath: "https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/latest/hand_landmarker.task",
                    delegate: "GPU"
                },
                numHands: 2
            });
        //@ts-ignore
        await this._handLandmarker.setOptions({ runningMode: "VIDEO" });

        this._video = document.createElement("video");
        this._video.setAttribute("style", "max-width: 30vw; height: auto;");
        console.log(this._video);
        this._video.autoplay = true;
        this._video.playsInline = true;
        this.context.domElement.appendChild(this._video);
        this.startWebcam(this._video);
    }

    private _lastVideoTime: number = 0;

    update(): void {
        if (!this._video || !this._handLandmarker) return;
        const video = this._video;
        if (video.currentTime !== this._lastVideoTime) {
            let startTimeMs = performance.now();
            showBalloonMessage("<strong>Control the spheres with one or two hands</strong>!<br/><br/>Sample scene by <a href='https://twitter.com/llllkatjallll/status/1659280435023605773'>Katja Rempel</a>")
            const detections = this._handLandmarker.detectForVideo(video, startTimeMs);
            this.processResults(detections);
            this._lastVideoTime = video.currentTime;
        }

    }

    private processResults(results: HandLandmarkerResult) {
        const hand1 = results.landmarks[0];
        // check if we have even one hand
        if (!hand1) return;

        if (hand1.length >= 4 && this.spheres[0]) {
            const pos = hand1[4];
            this.processLandmark(this.spheres[0], pos);
        }

        // if we have a second sphere:
        if (this.spheres.length >= 2) {
            const hand2 = results.landmarks[1];
            if (!hand2) {
                const pos = hand1[8];
                this.processLandmark(this.spheres[1], pos);
            }
            else {
                const pos = hand2[4];
                this.processLandmark(this.spheres[1], pos);
            }
        }
    }

    private processLandmark(sphere: ParticleSphere, pos: NormalizedLandmark) {
        const px = Mathf.remap(pos.x, 0, 1, -6, 6);
        const py = Mathf.remap(pos.y, 0, 1, 6, -6);
        sphere.setTarget(px, py, 0);
    }

    private async startWebcam(video: HTMLVideoElement) {
        const constraints = { video: true, audio: false };
        const stream = await navigator.mediaDevices.getUserMedia(constraints);
        video.srcObject = stream;
    }
}
View on GitHub

Farbe bei Kollision ändern

import { Behaviour, Collision, Renderer } from "@needle-tools/engine";
import{ Color } from "three";

export class ChangeColorOnCollision extends Behaviour {

    private renderer: Renderer | null = null;
    private collisionCount: number = 0;

    private _startColor? : Color[];

    start() {
        this.renderer = this.gameObject.getComponent(Renderer);
        if (!this.renderer) return;
        if(!this._startColor) this._startColor = [];
        for (let i = 0; i < this.renderer.sharedMaterials.length; i++) {
            this.renderer.sharedMaterials[i] = this.renderer.sharedMaterials[i].clone();
            this._startColor[i] = this.renderer.sharedMaterials[i]["color"].clone();
        }
    }

    onCollisionEnter(_col: Collision) {
        if (!this.renderer) return;
        this.collisionCount += 1;
        for (let i = 0; i < this.renderer.sharedMaterials.length; i++) {
            this.renderer.sharedMaterials[i]["color"].setRGB(Math.random(), Math.random(), Math.random());
        }
    }

    onCollisionExit(_col: Collision) {
        if (!this.renderer || !this._startColor) return;
        this.collisionCount -= 1;
        if (this.collisionCount === 0) {
            for (let i = 0; i < this.renderer.sharedMaterials.length; i++) {
                this.renderer.sharedMaterials[i]["color"].copy(this._startColor[i])
                // .setRGB(.1, .1, .1);
            }
        }
    }

    // more events:
    // onCollisionStay(_col: Collision)
    // onCollisionExit(_col: Collision)
}
View on GitHub

Physik Trigger Relay

Löse Events mit den Physik-Trigger-Methoden eines Objekts aus

export class PhysicsTrigger extends Behaviour {

    @serializeable(GameObject)
    triggerObjects?:GameObject[];

    @serializeable(EventList)
    onEnter?: EventList;

    @serializeable(EventList)
    onStay?: EventList;

    @serializeable(EventList)
    onExit?: EventList;

    onTriggerEnter(col: Collider) {
        if(this.triggerObjects && this.triggerObjects.length > 0 && !this.triggerObjects?.includes(col.gameObject)) return;
        this.onEnter?.invoke();
    }

    onTriggerStay(col: Collider) {
        if(this.triggerObjects && this.triggerObjects.length > 0 && !this.triggerObjects?.includes(col.gameObject)) return;
        this.onStay?.invoke();
    }

    onTriggerExit(col: Collider) {
        if(this.triggerObjects && this.triggerObjects.length > 0 && !this.triggerObjects?.includes(col.gameObject)) return;
        this.onExit?.invoke();
    }
}
View on GitHub

Auto Reset

Setze die Position eines Objekts automatisch zurück, wenn es einen Physik-Trigger verlässt

import { Behaviour, Collider, GameObject, Rigidbody, serializeable } from "@needle-tools/engine";
import { Vector3 } from "three";

export class StartPosition extends Behaviour {

    //@nonSerialized
    startPosition?: Vector3;

    start() {
        this.updateStartPosition();
    }

    updateStartPosition(){
        this.startPosition = this.gameObject.position.clone();
    }

    resetToStart() {
        if (!this.startPosition) return;
        const rb = GameObject.getComponent(this.gameObject, Rigidbody);
        rb?.teleport(this.startPosition);
    }
}

/** Reset to start position when object is exiting the collider */
export class AutoReset extends StartPosition {

    @serializeable(Collider)
    worldCollider?: Collider;

    start(){
        super.start();
        if(!this.worldCollider) console.warn("Missing collider to reset", this);
    }
    
    onTriggerExit(col) {
        if(col === this.worldCollider){
            this.resetToStart();
        }
    }
}
View on GitHub

Audio bei Kollision abspielen

import { AudioSource, Behaviour, serializeable } from "@needle-tools/engine";

export class PlayAudioOnCollision extends Behaviour {
    @serializeable(AudioSource)
    audioSource?: AudioSource;

    onCollisionEnter() {
        this.audioSource?.play();
    }
}
View on GitHub

Zufällige Farbe setzen

Zufällige Farbe eines Objekts beim Start festlegen. Beachte, dass die Materialien in der start Methode geklont werden

import { Behaviour, serializeable, Renderer } from "@needle-tools/engine";
import { Color } from "three";

export class RandomColor extends Behaviour {

    @serializeable()
    applyOnStart: boolean = true;

    start() {
        if (this.applyOnStart)
            this.applyRandomColor();

        // if materials are not cloned and we change the color they might also change on other objects
        const cloneMaterials = true;
        if (cloneMaterials) {
            const renderer = this.gameObject.getComponent(Renderer);
            if (!renderer) {
                return;
            }
            for (let i = 0; i < renderer.sharedMaterials.length; i++) {
                renderer.sharedMaterials[i] = renderer.sharedMaterials[i].clone();
            }
        }
    }

    applyRandomColor() {
        const renderer = this.gameObject.getComponent(Renderer);
        if (!renderer) {
            console.warn("Can not change color: No renderer on " + this.name);
            return;
        }
        for (let i = 0; i < renderer.sharedMaterials.length; i++) {
            renderer.sharedMaterials[i].color = new Color(Math.random(), Math.random(), Math.random());
        }
    }
}
View on GitHub

Objekte über Zeit spawnen

import { Behaviour, GameObject, LogType, serializeable, showBalloonMessage, WaitForSeconds } from "@needle-tools/engine";

export class TimedSpawn extends Behaviour {
    @serializeable(GameObject)
    object?: GameObject;

    interval: number = 1000;
    max: number = 100;

    private spawned: number = 0;

    awake() {
        if (!this.object) {
            console.warn("TimedSpawn: no object to spawn");
            showBalloonMessage("TimedSpawn: no object to spawn", LogType.Warn);
            return;
        }
        GameObject.setActive(this.object, false);
        this.startCoroutine(this.spawn())
    }

    *spawn() {
        if (!this.object) return;
        while (this.spawned < this.max) {
            const instance = GameObject.instantiate(this.object);
            GameObject.setActive(instance!, true);
            this.spawned += 1;
            yield WaitForSeconds(this.interval / 1000);
        }
    }
}
View on GitHub

Seite automatisch mit AI übersetzt

Suggest changes
Zuletzt aktualisiert:: 22.04.25, 08:44
Prev
Automatische Komponenten-Generierung
Next
Community Contributions