Needle EngineはUnity Editorに密接に統合されています。これにより、開発者とデザイナーの両方が慣れ親しんだ環境で協力し、高速で高性能、かつ軽量なWebエクスペリエンスを提供できます。
以下のガイドは主にUnity3Dのバックグラウンドを持つ開発者を対象としていますが、Webまたはthree.jsのバックグラウンドを持つ開発者にも役立つ可能性があります。Unityとthree.jsまたはNeedle Engineでどのように物事が行われるかに関するトピックを扱います。
もしTypescriptとJavascriptを初めて使用し、Needle Engineのスクリプト作成に深く入りたい場合は、C#とJavascript/Typescriptの違いの基本的な理解のために、Typescript Essentials Guideを読むこともお勧めします。
コードを書きながら進めたい場合は、engine.needle.tools/newを開いて、ブラウザで編集できる小さなプロジェクトを作成できます⚡
基本
Needle Engineは、three.js上で動作する3D Webエンジンです。Three.jsは、Web向けの最も人気のある3D WebGLベースのレンダリングライブラリの1つです。Needle EngineでgameObject
を参照する場合、それは実際にはthree.jsのObject3D
、three.jsのあらゆるオブジェクトの基本タイプも指しています。これらの用語は互換的に使用できます。任意のgameObject
はObject3D
です。
これはまた、もしthree.jsに既に慣れているなら、Needle Engineを使用する上で全く問題がないことを意味します。three.jsでできることはすべて、Needle Engineでも可能です。特定のライブラリを既に使っている場合、Needle Engineベースの環境でもそれらを使用できます。
注:Needle EngineのExporterは、既存のC#コードをWeb Assemblyにコンパイルしません。 Web Assemblyを使用するとランタイムパフォーマンスが向上する可能性がありますが、Webエクスペリエンス構築における反復速度と柔軟性には大きなコストがかかります。ビジョンと技術概要について詳しくはこちらをご覧ください。
Needle Engineを使用して新しいUnityプロジェクトを作成する方法は?(動画)
コンポーネントの作成
Unityでは、MonoBehaviour
から派生させることで新しいコンポーネントを作成します。
using UnityEngine;
public class MyComponent : MonoBehaviour {
}
一方、Needle Engineでのカスタムコンポーネントは次のように書かれます。
import { Behaviour } from "@needle-tools/engine"
export class MyComponent extends Behaviour {
}
スクリプトフィールド
serializable
一部のNeedle Engineスクリプトを見たことがある場合、一部の変数が宣言の上に@serializable
でアノテーションされていることに気づいたかもしれません。これはTypescriptのDecoratorであり、コードを変更またはアノテーションするために使用できます。Needle Engineでは、例えば、glTFに保存されている生のコンポーネント情報からコンポーネントインスタンスに変換する際に、スクリプトでどのようなタイプを期待するかをコアのシリアライゼーションに知らせるために使用されます。 次の例を考えてみましょう。
import { Behaviour, serializable } from "@needle-tools/engine";
import { Object3D } from "three";
class SomeClass extends Behaviour{
@serializable(Behaviour)
myOtherComponent?: Behaviour;
@serializable(Object3D)
someOtherObject?: Object3D;
}
これはNeedle Engineに、myOtherComponent
がBehaviour
タイプであるべきだと伝えます。これにより、シーンがロードされる際にフィールドに正しい参照が自動的に割り当てられます。someOtherObject
も同様で、ここではObject3D
参照にデシリアライズしたいと考えています。
場合によっては型を省略できることに注意してください。これはJavascriptのすべてのプリミティブ型で行うことができます。これらはboolean
、number
、bigint
、string
、null
、undefined
です。
import { Behaviour, serializable } from "@needle-tools/engine";
class SomeClass {
@serializable() // < no type is needed here because the field type is a primitive
myString?: string;
}
public vs private
private
、public
、protected
のようなアクセサー修飾子がないフィールドは、javascriptではデフォルトでpublic
になります。
import { Behaviour, serializable } from "@needle-tools/engine";
class SomeClass {
/// no accessor means it is public:
myNumber?: number;
// explicitly making it private:
private myPrivateNumber?: number;
protected myProtectedNumber?: number;
}
メソッドにも同様のことが言えます。
GameObjectとシーン
コンポーネントから現在のシーンにアクセスするには、this.scene
を使用します。これはthis.context.scene
と同等であり、ルートとなるthree.jsシーンオブジェクトを取得できます。
コンポーネントからヒエラルキーを走査するには、オブジェクトの子をforループで反復するか、
for (let i = 0; i < this.gameObject.children; i++) {
console.log(this.gameObject.children[i]);
}
foreach
と同等のものを使用して反復できます。
for (const child of this.gameObject.children) {
console.log(child);
}
または、traverse
メソッドを使用して、すべてのオブジェクトを再帰的に迅速に反復するためのthree.js固有のメソッドを使用することもできます。
import { GameObject } from "@needle-tools/engine";
//---cut-before---
this.gameObject.traverse((obj: GameObject) => console.log(obj))
または、可視オブジェクトだけを走査するには、代わりにtraverseVisible
を使用します。
レンダリング可能なオブジェクトを反復したい場合に非常に便利な別のオプションとして、すべてのRendererコンポーネントをクエリして、次のように反復することができます。
import { Renderer } from "@needle-tools/engine";
for(const renderer of this.gameObject.getComponentsInChildren(Renderer))
console.log(renderer);
コンポーネントの取得に関する詳細については、次のセクションを参照してください。
コンポーネント
Needle Engineは、Unityのそれに類似したコンポーネントシステムを多用しています。これは、シーン内の任意のObject3D
/ GameObject
にコンポーネントを追加または削除できることを意味します。addNewComponent(<Object3D>, <ComponentType>)
を使用すると、コンポーネントがエンジンに登録されます。 アタッチされたコンポーネントのイベントメソッドは、エンジンによって自動的に呼び出されます(例:update
またはonBeforeRender
)。イベントメソッドの完全なリストは、スクリプトドキュメントで見つけることができます。
シーン内のコンポーネントを見つける
コンポーネントを取得するには、Unityと同様の慣れ親しんだメソッドを使用できます。以下ではAnimator
タイプを例として使用していますが、組み込みまたは自分で作成した任意のコンポーネントタイプを使用することもできます。
メソッド名 | 説明 |
---|---|
this.gameObject.getComponent(Animator) | GameObject/Object3D上のAnimator コンポーネントを取得します。Animatorコンポーネントがある場合はAnimator インスタンスを返し、オブジェクトにそのようなコンポーネントがない場合はnull を返します。 |
this.gameObject.getComponentInChildren(Animator) | GameObject/Object3Dまたはその子にある最初のAnimator コンポーネントを取得します。 |
this.gameObject.getComponentsInParents(Animator) | 親ヒエラルキーにあるすべてのAnimatorコンポーネントを取得します(現在のGameObject/Object3Dを含む)。 |
これらのメソッドは、静的なGameObjectタイプでも利用可能です。例えば、渡されたGameObject/Object3D上のAnimator
コンポーネントを取得するには、GameObject.getComponent(this.gameObject, Animator)
を使用します。
シーン全体から1つまたは複数のコンポーネントを検索するには、GameObject.findObjectOfType(Animator)
またはGameObject.findObjectsOfType(Animator)
を使用できます。
名前が変更されたUnityタイプ
一部のUnity固有のタイプは、当社のエンジンで異なるタイプ名にマッピングされています。以下のリストを参照してください。
Unityでのタイプ | Needle Engineでのタイプ | |
---|---|---|
UnityEvent | EventList | UnityEvent はEventList タイプとしてエクスポートされます(UnityEventをデシリアライズするにはserializable(EventList) を使用します)。 |
GameObject | Object3D | |
Transform | Object3D | three.jsとNeedle Engineでは、GameObjectとTransformは同じです(Transform コンポーネントはありません)。このルールの唯一の例外は、Needle EngineでもコンポーネントであるRectTransform を参照する場合です。 |
Color | RGBAColor | three.jsのcolorタイプにはalphaプロパティがありません。そのため、UnityからエクスポートされるすべてのColorタイプは、カスタムのNeedle EngineタイプであるRGBAColor としてエクスポートされます。 |
Transform
Transformデータは、GameObject
/ Object3D
上で直接アクセスできます。Unityとは異なり、このデータを保持する追加のTransformコンポーネントはありません。
this.gameObject.position
は、ローカルスペースのベクトル3 位置ですthis.gameObject.worldPosition
は、ワールドスペースのベクトル3位置ですthis.gameObject.rotation
は、ローカルスペースのオイラー回転ですthis.gameObject.worldRotation
は、ワールドスペースのオイラー角でのオイラー回転ですthis.gameObject.quaternion
- は、ローカルスペースのクォータニオン回転ですthis.gameObject.worldQuaternion
は、ワールドスペースのクォータニオン回転ですthis.gameObject.scale
- は、ローカルスペースのベクトル3 スケールですthis.gameObject.worldScale
は、ワールドスペースのベクトル3スケールです
ここで覚えておくべき主な違いは、three.jsではposition
がデフォルトでローカルスペース位置であるのに対し、Unityではposition
がワールドスペースであることです。次のセクションでは、three.jsでワールドスペース位置を取得する方法を説明します。
WORLD- 位置、回転、スケール...
three.js(したがってNeedle Engineも)では、object.position
、object.rotation
、object.scale
はすべてローカルスペース座標です。これは、position
がワールドスペースであり、明示的にローカルスペース位置を使用するためにlocalPosition
を使用することに慣れているUnityとは異なります。
Needle Engineでワールド座標にアクセスしたい場合は、オブジェクトとともに使用できるユーティリティメソッドがあります。getWorldPosition(yourObject)
を呼び出してワールド位置を計算します。回転/クォータニオン、スケールにも同様のメソッドが存在します。これらのメソッドにアクセスするには、Needle Engineからimport { getWorldPosition } from "@needle.tools/engine"
のようにインポートするだけです。
getWorldPosition
、getWorldRotation
、getWorldScale
のようなこれらのユーティリティメソッドは、内部的にVector3インスタンスのバッファを持っており、ローカルでのみ使用することを意図していることに注意してください。これは、コンポーネントでそれらをキャッシュすべきではないことを意味します。そうしないと、キャッシュされた値が最終的に上書きされます。ただし、同じインスタンスを再利用することを心配することなく計算を行うために、関数内でgetWorldPosition
を複数回呼び出すことは安全です。これが何を意味するか不明な場合は、Typescript Essentials GuideのPrimitive Typesセクションを参照してください。
時間
時間データにアクセスするには、this.context.time
を使用します。
this.context.time.time
は、アプリケーションが実行を開始してからの時間ですthis.context.time.deltaTime
は、最後のフレームから経過した時間ですthis.context.time.frameCount
は、アプリケーションが開始してから経過したフレーム数ですthis.context.time.realtimeSinceStartup
は、アプリケーションが実行を開始してからのスケールされない時間です
また、this.context.time.timeScale
を使用して、例えばスローモーション効果のために時間を意図的に遅くすることも可能です。
Raycasting
this.context.physics.raycast()
を使用してレイキャストを実行し、交差のリストを取得します。オプションを何も渡さない場合、レイキャストは現在アクティブなmainCamera
を使用して、スクリーン空間でのマウス位置(または最初のタッチ位置)から実行されます。maxDistance
、使用するカメラ、テスト対象のレイヤーなど、様々な設定を持つRaycastOptions
オブジェクトを渡すこともできます。
three.js rayを使用してレイキャストを実行するには、this.context.physics.raycastFromRay(your_ray)
を使用します。
上記の呼び出しは、デフォルトでは可視シーンオブジェクトに対してレイキャストを実行することに注意してください。これは、オブジェクトにヒットするために常にコライダーが必要なUnityとは異なります。デフォルトのthree.jsソリューションには利点と欠点の両方があり、主な欠点の1つは、シーンジオメトリによっては非常に遅くなる可能性があることです。スキニングされたメッシュに対してレイキャストを行う場合は特に遅くなる可能性があります。そのため、通常、UnityでSkinnedMeshRendererを持つオブジェクトをIgnore Raycast
レイヤーに設定することをお勧めします。これにより、Needle Engineでもデフォルトで無視されます。
もう1つのオプションは、シーン内のコライダーを持つヒットのみを返す物理レイキャストメソッドを使用することです。
const hit = this.context.physics.engine?.raycast();
ここに編集可能な物理レイキャストの例があります。
入力
入力状態をポーリングするには、this.context.input
を使用します。
import { Behaviour } from "@needle-tools/engine";
export class MyScript extends Behaviour
{
update(){
if(this.context.input.getPointerDown(0)){
console.log("POINTER DOWN")
}
}
}
InputEvents
enum内のイベントに、次のように購読することもできます。
import { Behaviour, InputEvents, NEPointerEvent } from "@needle-tools/engine";
export class MyScript extends Behaviour
{
onEnable(){
this.context.input.addEventListener(InputEvents.PointerDown, this.inputPointerDown);
}
onDisable() {
// it is recommended to also unsubscribe from events when your component becomes inactive
this.context.input.removeEventListener(InputEvents.PointerDown, this.inputPointerDown);
}
inputPointerDown = (evt: NEPointerEvent) => { console.log(evt); }
}
入力を自分で処理したい場合は、ブラウザが提供するすべてのイベント(たくさんあります)に購読することもできます。例えば、ブラウザのクリックイベントに購読するには、次のように書くことができます。
window.addEventListener("click", () => { console.log("MOUSE CLICK"); });
この場合、すべてのケースを自分で処理する必要があることに注意してください。例えば、ユーザーがデスクトップ、モバイル、VRデバイスのいずれでWebサイトを閲覧しているかによって、異なるイベントを使用する必要がある場合があります。これらのケースはNeedle Engineの入力イベントによって自動的に処理されます(例:PointerDown
は、マウスダウン、タッチダウン、およびVRコントローラーのボタンダウンの場合の両方で発生します)。
InputSystemコールバック
Unityと同様に(UnityのIPointerClickHandler参照)、コンポーネント自体で入力イベントを受け取るように登録することもできます。
これを機能させるには、オブジェクトの親ヒエラルキーにObjectRaycaster
またはGraphicRaycaster
コンポーネントがあることを確認してください。
import { Behaviour, IPointerEventHandler, PointerEventData } from "@needle-tools/engine";
export class ReceiveClickEvent extends Behaviour implements IPointerEventHandler {
onPointerClick(args: PointerEventData) {
console.log("Click", args);
}
}
注:IPointerEventHandler
は、オブジェクトをすべての可能なポインターイベントに購読します。それらのハンドラーは以下の通りです。
onPointerDown
onPointerUp
onPointerEnter
onPointerMove
onPointerExit
onPointerClick
すべてに、イベントを記述するPointerEventData
引数があります。
Debug.Log
javascriptでのDebug.Log()
に相当するのはconsole.log()
です。console.warn()
やconsole.error()
も使用できます。
import { GameObject, Renderer } from "@needle-tools/engine";
const someVariable = 42;
// ---cut-before---
console.log("Hello web");
// You can pass in as many arguments as you want like so:
console.log("Hello", someVariable, GameObject.findObjectOfType(Renderer), this.context);
Gizmos
Unityでは通常、OnDrawGizmos
やOnDrawGizmosSelected
のような特別なメソッドを使用してGizmoを描画する必要があります。一方、Needle Engineにはそのようなメソッドはなく、スクリプトのどこからでも自由にGizmoを描画できます。その際、例えばデプロイされたWebアプリケーションでそれらを描画しないようにすることはあなたの責任であることに注意してください(if(isDevEnvironment))
などでフィルタリングできます)。
例えば、ワールド空間の点を視覚化するために、1秒間赤いワイヤースフィアを描画する例です。
import { Vector3 } from "three";
const hit = { point: new Vector3(0, 0, 0) };
// ---cut-before---
import { Gizmos } from "@needle-tools/engine";
Gizmos.DrawWireSphere(hit.point, 0.05, 0xff0000, 1);
利用可能なGizmoメソッドの一部を以下に示します。
メソッド名 | |
---|---|
Gizmos.DrawArrow | |
Gizmos.DrawBox | |
Gizmos.DrawBox3 | |
Gizmos.DrawDirection | |
Gizmos.DrawLine | |
Gizmos.DrawRay | |
Gizmos.DrawRay | |
Gizmos.DrawSphere | |
Gizmos.DrawWireSphere |
便利なユーティリティメソッド
@needle-tools/engine
からインポートします(例: import { getParam } from "@needle-tools/engine"
)。
メソッド名 | 説明 |
---|---|
getParam() | URLパラメータが存在するか確認します。値がない場合(例: ?help )はtrueを返し、URLに見つからないか0に設定されている場合(例: ?help=0 )はfalseを返し、それ以外の場合は値を返します(例: ?message=test )。 |
isMobileDevice() | アプリがモバイルデバイスからアクセスされている場合はtrueを返します。 |
isDevEnvironment() | 現在のアプリがローカルサーバーで実行されている場合はtrueを返します。 |
isMozillaXR() | |
isiOS | |
isSafari |
import { isMobileDevice } from "@needle-tools/engine"
if( isMobileDevice() )
import { getParam } from "@needle-tools/engine"
// returns true
const myFlag = getParam("some_flag")
console.log(myFlag)
Webプロジェクト
C#では通常、1つまたは複数のプロジェクトを含むソリューションを扱います。Unityでは、このソリューションはUnityによって管理され、C#スクリプトを開くとプロジェクトが開き、ファイルが表示されます。 Unityの組み込みパッケージマネージャーを使用してパッケージをインストールすることで、Unityまたは他の開発者(チーム内やUnityのAssetStoreなど)が提供する機能を追加するのが一般的です。UnityはPackageManagerを使ってパッケージの追加と管理を容易にする優れた仕事をしています。そのため、manifest.json
のようなファイル(Unityがどのパッケージがインストールされているかを追跡するために使用するもの)を手動で編集したり、コマンドラインからコマンドを実行してパッケージをインストールしたりする必要はなかったかもしれません。
Web環境では、npm
(Node Package Manager)を使用して、依存関係/パッケージを管理します。これは基本的にUnityのPackageManagerと同じことを行います。パッケージを何らかのサーバー(この文脈では通常レジストリと呼ばれます)からインストール(ダウンロード)し、node_modules
という名前のフォルダーに入れます。
Webプロジェクトを扱う際、ほとんどの依存関係はnpmjs.comからインストールされます。これは、Webプロジェクトにとって最も人気のあるパッケージレジストリです。
package.jsonの例を以下に示します。
{
"name": "@optional_org/package_name",
"version": "1.0.0",
"scripts": {
"start": "vite --host"
},
"dependencies": {
"@needle-tools/engine": "^3.5.9-beta",
"three": "npm:@needle-tools/three@0.146.8"
},
"devDependencies": {
"@types/three": "0.146.0",
"@vitejs/plugin-basic-ssl": "^1.0.1",
"typescript": "^5.0.4",
"vite": "^4.3.4",
"vite-plugin-compression": "^0.5.1"
}
}
デフォルトのテンプレートはViteをバンドラーとして使用しており、フロントエンドフレームワークはプリインストールされていません。Needle Engineはどのフレームワークを使用するかについて制約がないため、好きなフレームワークで自由に作業できます。Vue.js、Svelte、Next.js、React、React Three Fiberなどの人気フレームワークのサンプルを用意しています。
パッケージと依存関係のインストール
npmから依存関係をインストールするには、コマンドライン(またはターミナル)でWebプロジェクトを開き、npm i <パッケージ名>
(npm install
の短縮形)を実行します。 例えば、Needle Engineをインストールするには、npm i @needle-tools/engine
を実行します。これにより、パッケージがpackage.json
のdependencies
配列に追加されます。 パッケージをdevDependencyとしてのみインストールするには、npm i --save-dev <パッケージ名>
を実行します。dependenciesとdevDependenciesの違いについては、以下で詳しく説明します。
'dependencies' と 'devDependencies' の違い
dependencyを含む2つのエントリがあることに気づいたかもしれません - dependencies
とdevDependencies
です。
dependencies
は、Webプロジェクトがインストールされる場合、またはライブラリを開発していてパッケージが別のプロジェクトの依存関係としてインストールされる場合に、常にインストール(またはバンドル)されます。
devDependencies
は、プロジェクトを開発する際にのみインストールされます(つまり、特定のディレクトリで直接install
を実行する場合)。それ以外の場合はプロジェクトには含まれません。
別のパッケージまたは依存関係をインストールして使用する方法は?
Installingセクションでは、プロジェクトディレクトリでnpm i <package_name>
を実行することで依存関係をインストールできることを学びました。package_name
はnpm.jsで見つけることができる任意のパッケージです。
プロジェクトにトゥイーンライブラリを追加したいと仮定しましょう。この例では@tweenjs/tween.js
を使用します。結果だけを先に確認したい場合はこちらが最終プロジェクトです。
まずターミナルでnpm install @tweenjs/tween.js
を実行し、インストールが完了するまで待ちます。これにより、package.jsonに新しいエントリが追加されます。
"dependencies": {
"@needle-tools/engine": "^3.5.11-beta",
"@tweenjs/tween.js": "^20.0.3",
"three": "npm:@needle-tools/three@0.146.8"
}
次に、トゥイーンを使用したいスクリプトファイルのいずれかを開き、ファイルの先頭でインポートします。
import * as TWEEN from '@tweenjs/tween.js';
ここでは* as TWEEN
と書くことでライブラリ内のすべての型をインポートしていることに注意してください。import { Tween } from @tweenjs/tween.js
のように特定の型だけをインポートすることもできます。
これでスクリプトで使用できます。使用したいライブラリのドキュメントを参照することを常にお勧めします。tween.jsの場合、従うことができるユーザーガイドが提供されています。通常、npm上のパッケージのReadmeページには、パッケージのインストール方法と使用方法に関する情報が含まれています。
キューブを回転させるために、TweenRotation
という新しいコンポーネントタイプを作成します。次に、オブジェクトの回転に対するトゥイーンインスタンス、繰り返しの頻度、使用するイージング、実行したいトゥイーンを作成し、開始します。あとは毎フレームupdate
を呼び出してトゥイーンアニメーションを更新するだけです。最終的なスクリプトは次のようになります。
import { Behaviour } from "@needle-tools/engine";
import * as TWEEN from '@tweenjs/tween.js';
export class TweenRotation extends Behaviour {
// save the instance of our tweener
private _tween?: TWEEN.Tween<any>;
start() {
const rotation = this.gameObject.rotation;
// create the tween instance
this._tween = new TWEEN.Tween(rotation);
// set it to repeat forever
this._tween.repeat(Infinity);
// set the easing to use
this._tween.easing(TWEEN.Easing.Quintic.InOut);
// set the values to tween
this._tween.to({ y: Math.PI * 0.5 }, 1000);
// start it
this._tween.start();
}
update() {
// update the tweening every frame
// the '?' is a shorthand for checking if _tween has been created
this._tween?.update();
}
}
これで、シーン内の任意のオブジェクトに追加するだけで、オブジェクトを永遠に回転させることができます。 最終的なスクリプトの動作はこちらで確認できます。
さらに学ぶ
このページはAIによって自動的に翻訳されました。