Needle Engine ドキュメント
Downloads
  • What is Needle Engine?
  • お客様の声
  • Get an overview

    • Samples and Showcase
    • 私たちのビジョン 🔮
    • 機能概要
    • 技術概要
  • Resources

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

    • Unity用Needle Engine
    • Blender 用 Needle Engine
    • Web ComponentとしてのNeedle Engine
    • ウェブサイトでのNeedle Engine
    • Needle Cloud
  • Topics

    • Webプロジェクトの構造
    • Everywhere Actions
    • glTFへのアセットのエクスポート
    • フレームワーク、バンドラー、HTML
    • ローカルデバイスでのテスト
    • デプロイと最適化
  • Advanced

    • ネットワーキング
    • VR & AR (WebXR)
    • HTMLからNeedle Engineを直接使用する
    • Editor Sync
  • Troubleshooting

    • デバッグの方法
    • よくある質問(FAQ)💡
    • Get Help
  • Videos

    • Tutorials on Youtube
    • Interviews on Youtube
  • Scripting Overview

    • Needle Engineでのスクリプティング
    • Scripting Introduction for Unity Developers
    • Needleコアコンポーネント
    • Everywhere Actions
  • Components and Lifecycle

    • Creating and using Components
    • @serializable およびその他のデコレーター
    • 自動コンポーネント生成
    • Scripting Examples
    • Community Contributions
    • 追加モジュール
  • Settings and APIs

    • <needle-engine> 設定
    • 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?
  • お客様の声
  • Get an overview

    • Samples and Showcase
    • 私たちのビジョン 🔮
    • 機能概要
    • 技術概要
  • Resources

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

    • Unity用Needle Engine
    • Blender 用 Needle Engine
    • Web ComponentとしてのNeedle Engine
    • ウェブサイトでのNeedle Engine
    • Needle Cloud
  • Topics

    • Webプロジェクトの構造
    • Everywhere Actions
    • glTFへのアセットのエクスポート
    • フレームワーク、バンドラー、HTML
    • ローカルデバイスでのテスト
    • デプロイと最適化
  • Advanced

    • ネットワーキング
    • VR & AR (WebXR)
    • HTMLからNeedle Engineを直接使用する
    • Editor Sync
  • Troubleshooting

    • デバッグの方法
    • よくある質問(FAQ)💡
    • Get Help
  • Videos

    • Tutorials on Youtube
    • Interviews on Youtube
  • Scripting Overview

    • Needle Engineでのスクリプティング
    • Scripting Introduction for Unity Developers
    • Needleコアコンポーネント
    • Everywhere Actions
  • Components and Lifecycle

    • Creating and using Components
    • @serializable およびその他のデコレーター
    • 自動コンポーネント生成
    • Scripting Examples
    • Community Contributions
    • 追加モジュール
  • Settings and APIs

    • <needle-engine> 設定
    • 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プロジェクトの構造
    • Everywhere Actions
    • glTFへのアセットのエクスポート
    • フレームワーク、バンドラー、HTML
    • ローカルデバイスでのテスト
    • デプロイと最適化
    • デバッグの方法
    • よくある質問(FAQ)💡
  • Scripting

    • Needle Engineでのスクリプティング
    • Scripting Introduction for Unity Developers
    • Creating and using Components
    • 自動コンポーネント生成
    • Scripting Examples
    • Community Contributions
  • Advanced

    • VR & AR (WebXR)
    • ネットワーキング
    • Editor Sync
  • Reference

    • 機能概要
    • 技術概要
    • Needleコアコンポーネント
    • needle.config.json
    • <needle-engine> 設定
    • @serializable およびその他のデコレーター

スクリプティング例

スクリプト初心者の方は、まず以下のガイドを読むことを強くお勧めします。

  • 初心者ガイド: Typescript Essentials
  • 初心者ガイド: Unity開発者向けNeedle Engine
  • ビデオチュートリアル: カスタムコンポーネントの書き方

以下に、クイックリファレンスとしていくつかの基本的なスクリプトを示します。

また、開始点としてダウンロードして使用できるサンプルシーンと完全なプロジェクトを多数提供しています。

  • サンプルWebサイトを見る
  • サンプルパッケージをダウンロード
  • Needle Engine Stackblitzコレクション
  • Needle Engine API

基本コンポーネント

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);
    }
}

すべてのコンポーネントイベントについては、スクリプティングを参照してください。

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;
}

Unityからのアセット(Prefabまたは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
    }  
}

Unityからのシーンを参照してロード

ヒント

サンプルの動作例をダウンロードしてお試しください。

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();
        }
    }
}

オブジェクトのクリックを受け取る

このスクリプトをクリック可能にしたいシーン内の任意のオブジェクトに追加します。また、そのオブジェクトの親階層にObjectRaycasterコンポーネントがあることを確認してください。

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);
    }
}

オブジェクトのクリックをネットワーク化

このスクリプトをクリック可能にしたいシーン内の任意のオブジェクトに追加します。また、そのオブジェクトの親階層にObjectRaycasterコンポーネントがあることを確認してください。 このコンポーネントは、受信したクリックを接続されているすべてのクライアントに送信し、アプリで反応できるイベントを発生させます。UnityまたはBlenderを使用している場合、単にonClickイベントに呼び出す関数を割り当てることで、たとえばアニメーションを再生したり、オブジェクトを非表示にしたりできます。

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();
    }
    
}

クリックでアニメーションを再生

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();
        }
    }
}

アニメーションクリップを参照

これは、カスタムアニメーションロジックを実行したい場合に便利です。 クリップの配列をエクスポートすることもできます。

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);
    }
}

UnityEventを作成および起動

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

export class MyComponent extends Behaviour {

    @serializable(EventList)
    myEvent? : EventList;

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

ヒント

EventListイベントはコンポーネントレベルでも起動されます。つまり、上記で宣言されたイベントをmyComponent.addEventListener("my-event", evt => {...})を使用して購読することも可能です。 これは実験的機能です。フォーラムでフィードバックを提供してください。

カスタムイベント型を宣言

これは、UnityまたはBlenderにカスタム引数(文字列など)を持つイベントを公開したい場合に便利です。

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);
    }
}

使用例:20221128-210735_Unity-needle

ネストされたオブジェクトとシリアライゼーションを使用

オブジェクトとそのデータをネストできます。適切に一致する@serializable(SomeType)デコレーターを使用すると、データは自動的に正しい型にシリアライズおよびデシリアライズされます。

Typescriptコンポーネント内:

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();
    }
}

C#スクリプト内:

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;
}

ヒント

正しい型デコレーターがない場合でもデータは取得できますが、それは単なるプレーンオブジェクトとしてです。これはコンポーネントを移植する際に便利で、すべてのデータにアクセスでき、必要に応じて型を追加できます。

Web APIを使用

ヒント

すべてのWeb APIとnpmパッケージに引き続きアクセスできることを忘れないでください! もしここでそう言っても許されるなら、それがNeedle Engineの魅力です😊

現在位置を表示

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);
        });
    }
}

Coroutineを使用して現在時刻を表示

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)
        }
    };
}

カスタムシェーダープロパティを変更

_Speedというfloat値のプロパティを持つカスタムシェーダーがある場合、スクリプトからそれを変更する方法を以下に示します。 サンプルのライブ例をダウンロードして試すことができます。

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属性の切り替え

StackBlitzのライブ例を参照してください。

新しいポストプロセス効果を追加

Webプロジェクトでnpm i postprocessingをインストールしていることを確認してください。その後、PostProcessingEffectから派生させることで新しい効果を追加できます。

効果を使用するには、Volumeコンポーネントと同じオブジェクトに追加します。

これは、Outlineポストプロセス効果をラップする例です。任意の効果も単なるthree.jsシーン内のコンポーネントであるため、通常通り変数や設定を公開できます。

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);

カスタムParticleSystemビヘイビア

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())
    }
}

カスタム2Dオーディオコンポーネント

これは、独自のオーディオコンポーネントを作成する方法の例です。 ただし、ほとんどのユースケースでは、コアAudioSourceコンポーネントを使用でき、コードを書く必要はありません。

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();
        })
    }
}

任意の外部ファイル

FileReference型を使用して外部ファイル(JSONファイルなど)をロードします。

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要素のクリックを受け取る

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

環境光を無効にする

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パッケージを使用して手で3Dシーンを制御

mediapipeパッケージをインストールしていることを確認してください。完全なプロジェクトセットアップについては、以下のgithubリンクを参照してください。 ライブで試す - Webカメラ/カメラが必要です

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

衝突時に色を変更

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

Physicsトリガーリレー

オブジェクトの物理トリガーメソッドを使用してイベントを起動します

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

オートリセット

オブジェクトが物理トリガーから離れるときに、その位置を自動的にリセットします

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

衝突時にオーディオを再生

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

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

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

ランダムな色を設定

開始時にオブジェクトの色をランダム化します。マテリアルはstartメソッドでクローンされることに注意してください

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

時間をかけてオブジェクトをスポーン

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

ページはAIによって自動的に翻訳されました。

Suggest changes
最終更新:: 2025/04/22 17:38
Prev
自動コンポーネント生成
Next
Community Contributions