Ejemplos de scripting
Si eres nuevo en scripting, recomendamos encarecidamente leer primero las siguientes guías:
- Guía para principiantes: Fundamentos de Typescript
- Guía para principiantes: Needle Engine para desarrolladores de Unity
- Tutorial en vídeo: Cómo escribir componentes personalizados
A continuación, encontrarás algunos scripts básicos como referencia rápida.
También ofrecemos muchas escenas de ejemplo y proyectos completos que puedes descargar y usar como punto de partida:
- Visitar sitio web de ejemplos
- Descargar paquete de ejemplos
- Colección Stackblitz de Needle Engine
- API de Needle Engine
Componente básico
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);
}
}
consulta scripting para todos los eventos de componente
Referenciar un Objeto desde 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;
}
Referenciar y cargar un asset desde Unity (Prefab o 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
}
}
Referenciar y cargar escenas desde Unity
Consejos
Encuentra un ejemplo funcional en nuestros ejemplos para descargar y probar
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();
}
}
}
Recibir Clicks en Objetos
Añade este script a cualquier objeto en tu escena que quieras que sea clickeable. Asegúrate de tener también un componente ObjectRaycaster
en la jerarquía padre de ese objeto.
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);
}
}
Networking de Clicks en Objetos
Añade este script a cualquier objeto en tu escena que quieras que sea clickeable. Asegúrate de tener también un componente ObjectRaycaster
en la jerarquía padre de ese objeto. El componente enviará el click recibido a todos los clientes conectados y generará un evento al que podrás reaccionar en tu aplicación. Si usas Unity o Blender, puedes simplemente asignar funciones para llamar al evento onClick
para, por ejemplo, reproducir una animación u ocultar objetos.
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();
}
}
Reproducir Animación al hacer click
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();
}
}
}
Referenciar un Animation Clip
Esto puede ser útil si quieres ejecutar tu lógica de animación personalizada. También puedes exportar un array de clips.
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);
}
}
Crear e invocar un UnityEvent
import { Behaviour, serializable, EventList } from "@needle-tools/engine"
export class MyComponent extends Behaviour {
@serializable(EventList)
myEvent? : EventList;
start() {
this.myEvent?.invoke();
}
}
Consejos
Los eventos de EventList también se invocan a nivel de componente. Esto significa que también puedes suscribirte al evento declarado anteriormente usando myComponent.addEventListener("my-event", evt => {...})
. Esta es una característica experimental. Por favor, proporciona feedback en nuestro forum
Declarar un tipo de evento personalizado
Esto es útil cuando quieres exponer un evento a Unity o Blender con argumentos personalizados (como una cadena de texto).
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);
}
}
Ejemplo de uso:
Usar objetos anidados y serialización
Puedes anidar objetos y sus datos. Con los decoradores @serializable(SomeType)
correspondientes, los datos se serializarán y deserializarán en los tipos correctos automáticamente.
En tu componente 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();
}
}
En C# en cualquier script:
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;
}
Consejos
Sin los decoradores de tipo correctos, seguirás obteniendo los datos, pero solo como un objeto plano. Esto es útil al portar componentes, ya que tendrás acceso a todos los datos y podrás añadir tipos según sea necesario.
Usar Web APIs
Consejos
¡Ten en cuenta que sigues teniendo acceso a todas las web apis y paquetes npm! Esa es la belleza de Needle Engine, si se nos permite decirlo aquí 😊
Mostrar ubicación actual
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);
});
}
}
Mostrar hora actual usando una 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)
}
};
}
Cambiar propiedad de shader personalizado
Asumiendo que tienes un shader personalizado con un nombre de propiedad _Speed
que es un valor float, así es como lo cambiarías desde un script. Puedes encontrar un ejemplo funcional para descargar en nuestros ejemplos
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)
}
}
}
Cambiar atributo src
Ver ejemplo funcional en StackBlitz
Añadir nuevos efectos de postprocesado
Asegúrate de instalar npm i postprocessing
en tu proyecto web. Luego puedes añadir nuevos efectos derivando de PostProcessingEffect
.
Para usar el efecto, añádelo al mismo objeto que tu componente Volume
.
Aquí tienes un ejemplo que envuelve el efecto de postprocesado Outline. Puedes exponer variables y configuraciones como de costumbre, ya que cualquier efecto es también simplemente un componente en tu escena de 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);
Comportamiento de ParticleSystem personalizado
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())
}
}
Componente de audio 2D personalizado
Este es un ejemplo de cómo podrías crear tu propio componente de audio. Sin embargo, para la mayoría de los casos de uso, puedes usar el componente principal AudioSource y no tener que escribir código.
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();
})
}
}
Archivos externos arbitrarios
Usa el tipo FileReference para cargar archivos externos (por ejemplo, un archivo 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());
}
}
Recibir click de elemento html en componente
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();
}
}
Deshabilitar luz ambiental
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;
}
}
Usar paquete mediapipe para controlar la escena 3D con las manos
Asegúrate de instalar el paquete mediapipe. Visita el enlace de github a continuación para ver la configuración completa del proyecto. Probarlo aquí en vivo - requiere una webcam/cámara
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;
}
}
Cambiar color al colisionar
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)
}
Relé de Physics Trigger
Invocar eventos usando los métodos physics trigger de un objeto
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();
}
}
Auto Reset
Restablecer la posición de un objeto automáticamente cuando sale de un physics trigger
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();
}
}
}
Reproducir Audio al Colisionar
import { AudioSource, Behaviour, serializeable } from "@needle-tools/engine";
export class PlayAudioOnCollision extends Behaviour {
@serializeable(AudioSource)
audioSource?: AudioSource;
onCollisionEnter() {
this.audioSource?.play();
}
}
Establecer Color Aleatorio
Aleatorizar el color de un objeto al inicio. Ten en cuenta que los materiales se clonan en el método 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());
}
}
}
Generar objetos a lo largo del tiempo
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);
}
}
}
Página traducida automáticamente usando IA