Unity डेवलपर्स के लिए स्क्रिप्टिंग परिचय
Needle Engine यूनिटी एडिटर के साथ गहरा एकीकरण प्रदान करता है। यह डेवलपर्स और डिजाइनरों दोनों को परिचित वातावरण में एक साथ काम करने और तेज़, प्रदर्शनकारी और हल्के वेब अनुभव प्रदान करने की अनुमति देता है।
निम्नलिखित गाइड मुख्य रूप से Unity3D पृष्ठभूमि वाले डेवलपर्स के लिए है, लेकिन यह वेब या 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 वेब इंजन है। Three.js वेब के लिए सबसे लोकप्रिय 3D webgl-आधारित रेंडरिंग लाइब्रेरी में से एक है। जब भी हम 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 का उपयोग रनटाइम पर बेहतर प्रदर्शन दे सकता है, यह वेब अनुभव बनाने में पुनरावृति गति और लचीलेपन के लिए उच्च लागत पर आता है। हमारे विजन और तकनीकी अवलोकन के बारे में और पढ़ें।
:::details 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
जैसे किसी भी एक्सेस संशोधक के बिना फ़ील्ड्स जावास्क्रिप्ट में डिफ़ॉल्ट रूप से 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;
}
यही विधियों के लिए भी सच है।
GameObjects और सीन
एक कॉम्पोनेन्ट से वर्तमान सीन तक पहुंचने के लिए आप 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
का उपयोग करें।
एक और विकल्प जो बहुत उपयोगी है जब आप केवल रेंडर करने योग्य वस्तुओं को पुनरावृति करना चाहते हैं तो आप सभी रेंडरर कॉम्पोनेन्ट को क्वेरी कर सकते हैं और उन पर इस तरह से पुनरावृति कर सकते हैं:
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.getComponent(this.gameObject, Animator)
पास किए गए GameObject/Object3D पर Animator
कॉम्पोनेन्ट प्राप्त करने के लिए।
एक या अधिक कॉम्पोनेन्ट के लिए पूरे सीन को खोजने के लिए आप GameObject.findObjectOfType(Animator)
या GameObject.findObjectsOfType(Animator)
का उपयोग कर सकते हैं।
नाम बदले गए Unity प्रकार
हमारे इंजन में कुछ Unity-विशिष्ट प्रकारों को अलग-अलग प्रकार के नामों पर मैप किया गया है। निम्नलिखित सूची देखें:
UnityEvent
EventList
एक UnityEvent को EventList
प्रकार के रूप में निर्यात किया जाएगा (UnityEvents
को डीसेरियलाइज़ करने के लिए serializable(EventList)
का उपयोग करें)
GameObject
Object3D
Transform
Object3D
three.js और Needle Engine में एक GameObject और एक Transform समान हैं (कोई Transform
कॉम्पोनेन्ट नहीं है)। इस नियम का एकमात्र अपवाद RectTransform
को संदर्भित करना है जो Needle Engine में भी एक कॉम्पोनेन्ट है।
Color
RGBAColor
three.js रंग प्रकार में अल्फा प्रॉपर्टी नहीं होती है। इस कारण से Unity से निर्यात किए गए सभी Color प्रकारों को RGBAColor
के रूप में निर्यात किया जाएगा जो एक कस्टम Needle Engine प्रकार है
Transform
Transform डेटा को सीधे GameObject
/ Object3D
पर एक्सेस किया जा सकता है। Unity के विपरीत, ऐसा कोई अतिरिक्त transform कॉम्पोनेन्ट नहीं है जो इस डेटा को धारण करता हो।
this.gameObject.position
स्थानीय स्पेस में Vector3 position हैthis.gameObject.worldPosition
वर्ल्ड स्पेस में Vector3 position हैthis.gameObject.rotation
स्थानीय स्पेस में euler rotation हैthis.gameObject.worldRotation
वर्ल्ड स्पेस में euler कोणों में euler rotation हैthis.gameObject.quaternion
- स्थानीय स्पेस में quaternion rotation हैthis.gameObject.worldQuaternion
वर्ल्ड स्पेस में quaternion rotation हैthis.gameObject.scale
- स्थानीय स्पेस में Vector3 scale हैthis.gameObject.worldScale
वर्ल्ड स्पेस में Vector3 scale है
यहां ध्यान रखने योग्य मुख्य अंतर यह है कि three.js में position
डिफ़ॉल्ट रूप से एक स्थानीय स्पेस स्थिति है, जबकि Unity में position
वर्ल्ड स्पेस होगी और स्थानीय स्पेस स्थिति का जानबूझकर उपयोग करने के लिए localPosition
का उपयोग किया जाएगा।
WORLD- Position, Rotation, Scale...
in three.js (and thus also in Needle Engine) the object.position
, object.rotation
, object.scale
are all local space coordinates. This is different to Unity where we are used to position
being worldspace and using localPosition
to deliberately use the local space position.
If you want to access the world coordinates in Needle Engine we have utility methods that you can use with your objects. Call getWorldPosition(yourObject)
to calculate the world position. Similar methods exist for rotation/quaternion and scale. To get access to those methods just import them from Needle Engine like so import { getWorldPosition } from "@needle.tools/engine"
Note that these utility methods like getWorldPosition
, getWorldRotation
, getWorldScale
internally have a buffer of Vector3 instances and are meant to be used locally only. This means that you should not cache them in your component, otherwise your cached value will eventually be overriden. But it is safe to call getWorldPosition
multiple times in your function to make calculations without having to worry to re-use the same instance. If you are not sure what this means you should take a look at the Primitive Types section in the Typescript Essentials Guide
Time
Use this.context.time
to get access to time data:
this.context.time.time
is the time since the application started runningthis.context.time.deltaTime
is the time that has passed since the last framethis.context.time.frameCount
is the number of frames that have passed since the application startedthis.context.time.realtimeSinceStartup
is the unscaled time since the application has started running
It is also possible to use this.context.time.timeScale
to deliberately slow down time for e.g. slow motion effects.
Raycasting
Use this.context.physics.raycast()
to perform a raycast and get a list of intersections. If you dont pass in any options the raycast is performed from the mouse position (or first touch position) in screenspace using the currently active mainCamera
. You can also pass in a RaycastOptions
object that has various settings like maxDistance
, the camera to be used or the layers to be tested against.
Use this.context.physics.raycastFromRay(your_ray)
to perform a raycast using a three.js ray
Note that the calls above are by default raycasting against visible scene objects. That is different to Unity where you always need colliders to hit objects. The default three.js solution has both pros and cons where one major con is that it can perform quite slow depending on your scene geometry. It may be especially slow when raycasting against skinned meshes. It is therefor recommended to usually set objects with SkinnedMeshRenderers in Unity to the Ignore Raycast
layer which will then be ignored by default by Needle Engine as well.
Another option is to use the physics raycast methods which will only return hits with colliders in the scene.
const hit = this.context.physics.engine?.raycast();
Here is a editable example for physics raycast
Input
Use this.context.input
to poll input state:
import { Behaviour } from "@needle-tools/engine";
export class MyScript extends Behaviour
{
update(){
if(this.context.input.getPointerDown(0)){
console.log("POINTER DOWN")
}
}
}
You can also subscribe to events in the InputEvents
enum like so:
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); }
}
If you want to handle inputs yourself you can also subscribe to all events the browser provides (there are a ton). For example to subscribe to the browsers click event you can write:
window.addEventListener("click", () => { console.log("MOUSE CLICK"); });
Note that in this case you have to handle all cases yourself. For example you may need to use different events if your user is visiting your website on desktop vs mobile vs a VR device. These cases are automatically handled by the Needle Engine input events (e.g. PointerDown
is raised both for mouse down, touch down and in case of VR on controller button down).
InputSystem Callbacks
Similar to Unity (see IPointerClickHandler in Unity) you can also register to receive input events on the component itself.
import { Behaviour, PointerEventData } from "@needle-tools/engine";
export class ReceiveClickEvent extends Behaviour {
onPointerClick(args: PointerEventData) {
console.log("Click", args);
}
}
Available pointer events for all components:
onPointerDown
onPointerUp
onPointerEnter
onPointerMove
onPointerExit
onPointerClick
All have a PointerEventData
argument describing the event.
Debug.Log
The Debug.Log()
equivalent in javascript is console.log()
. You can also use console.warn()
or 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
In Unity you normally have to use special methods to draw Gizmos like OnDrawGizmos
or OnDrawGizmosSelected
. In Needle Engine on the other hand such methods dont exist and you are free to draw gizmos from anywhere in your script. Note that it is also your responsibility then to not draw them in e.g. your deployed web application (you can just filter them by if(isDevEnvironment))
).
Here is an example to draw a red wire sphere for one second for e.g. visualizing a point in worldspace
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);
Here are some of the available gizmo methods:
Gizmos.DrawArrow
Gizmos.DrawBox
Gizmos.DrawBox3
Gizmos.DrawDirection
Gizmos.DrawLine
Gizmos.DrawRay
Gizmos.DrawRay
Gizmos.DrawSphere
Gizmos.DrawWireSphere
Useful Utility Methods
Import from @needle-tools/engine
e.g. import { getParam } from "@needle-tools/engine"
getParam()
Checks if a url parameter exists. Returns true if it exists but has no value (e.g. ?help
), false if it is not found in the url or is set to 0 (e.g. ?help=0
), otherwise it returns the value (e.g. ?message=test
)
isMobileDevice()
Returns true if the app is accessed from a mobile device
isDevEnvironment()
Returns true if the current app is running on a local server
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)
The Web project
In C# you usually work with a solution containing one or many projects. In Unity this solution is managed by Unity for you and when you open a C# script it opens the project and shows you the file.
You usually install Packages using Unity's built-in package manager to add features provided by either Unity or other developers (either on your team or e.g. via Unity's AssetStore). Unity does a great job of making adding and managing packages easy with their PackageManager and you might never have had to manually edit a file like the manifest.json
(this is what Unity uses to track which packages are installed) or run a command from the command line to install a package.
In a web environment you use npm
- the Node Package Manager - to manage dependencies / packages for you. It does basically the same to what Unity's PackageManager does - it installs (downloads) packages from some server (you hear it usually called a registry in that context) and puts them inside a folder named node_modules
.
When working with a web project most of you dependencies are installed from npmjs.com. It is the most popular package registry out there for web projects.
Here is an example of how a package.json might look like:
{
"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"
}
}
Our default template uses Vite as its bundler and has no frontend framework pre-installed. Needle Engine is unoppionated about which framework to use so you are free to work with whatever framework you like. We have samples for popular frameworks like Vue.js, Svelte, Next.js, React or React Three Fiber.
Installing packages & dependencies
To install a dependency from npm you can open your web project in a commandline (or terminal) and run npm i <the/package_name>
(shorthand for npm install
)
For example run npm i @needle-tools/engine
to install Needle Engine. This will then add the package to your package.json
to the dependencies
array.
To install a package as a devDependency only you can run npm i --save-dev <package_name>
. More about the difference between dependencies and devDependencies below.
What's the difference between 'dependencies' and 'devDependencies'
You may have noticed that there are two entries containing dependency - dependencies
and devDependencies
.
dependencies
are always installed (or bundled) when either your web project is installed or in cases where you develop a library and your package is installed as a dependency of another project.
devDependencies
are only installed when developing the project (meaning that when you directly run install
in the specific directory) and they are otherwise not included in your project.
How do I install another package or dependency and how to use it?
The Installing section taught us that you can install dependencies by running npm i <package_name>
in your project directory where the package_name
can be any package that you find on npm.js.
Let's assume you want to add a tweening library to your project. We will use @tweenjs/tween.js
for this example. Here is the final project if you want to jump ahead and just see the result.
First run npm install @tweenjs/tween.js
in the terminal and wait for the installation to finish. This will add a new entry to our package.json:
"dependencies": {
"@needle-tools/engine": "^3.5.11-beta",
"@tweenjs/tween.js": "^20.0.3",
"three": "npm:@needle-tools/three@0.146.8"
}
Then open one of your script files in which you want to use tweening and import at the top of the file:
import * as TWEEN from '@tweenjs/tween.js';
Note that we do here import all types in the library by writing * as TWEEN
. We could also just import specific types like import { Tween } from @tweenjs/tween.js
.
Now we can use it in our script. It is always recommended to refer to the documentation of the library that you want to use. In the case of tween.js they provide a user guide that we can follow. Usually the Readme page of the package on npm contains information on how to install and use the package.
To rotate a cube we create a new component type called TweenRotation
, we then go ahead and create our tween instance for the object rotation, how often it should repeat, which easing to use, the tween we want to perform and then we start it. We then only have to call update
every frame to update the tween animation. The final script looks like this:
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();
}
}
Now we only have to add it to any of the objects in our scene to rotate them forever. You can see the final script in action here.
Learning more
यह पेज AI का उपयोग करके स्वचालित रूप से अनुवादित किया गया है
Last updated