BeeEngine logo BeeEngine JAVASCRIPT 2D ENGINE

Prefab: Gestione e Spawn di Entità in BeeEngine

Documentazione – BeeEngine
v2.8.0

BeePrefab — Documentazione Tecnica

Fabbrica da dati: define / spawn, catalogo gioco.prefabs, override x/y

📁 Panoramica

BeePrefab è il catalogo. Vive in src/core/BeePrefab.js, il barrel la re-esporta con BEE_PREFAB_DEFAULTS. Non è un object pool, non è un scene graph, non è new BeeEnemy(x, y) sparso nelle wave. Tiene due Map private: #types (nome → factory) e #recipes (nome → spec clonato). Una ricetta (tipo, posa, sprite, vita, collider) si definisce una volta; spawn, wave e oggetti tilemap la istanziamo con override.

Quello che non è: un cloner di entity vive, un serializer di save-game, un despawner. Non chiama destroy() sulle istanze. clear() svuota le ricette, non i tipi e non la scena. Il ciclo di vita dell’oggetto resta su BeeEntity.destroy / BeePool.release.

BeeEngine crea un catalogo: this.prefabs = new BeePrefab({ engine: this }), poi registra i type di stock (entity, enemy, player, shooter, platform, collectible, text). Attenzione: gioco.spawn(name, …) è il pool (engine.pool + addEntity), non il prefab. Il prefab è gioco.prefabs.spawn.

import { BeeEngine } from ‘beeengine’; const gioco = new BeeEngine(‘testCanvas’, 800, 600); gioco.prefabs.define(‘slime’, { type: ‘enemy’, width: 32, height: 32, speed: 45, hp: 3, collider: true, drawLayer: ‘ysort’ }); const a = gioco.prefabs.spawn(‘slime’, { x: 10, y: 20 }); a.prefab; // ‘slime’

📁 Catalogo: type, define, get

type(name, factory) registra una factory per stringa. Name vuoto o factory non-funzione lancia. Fluent. I type di stock li mette il motore; in un new BeePrefab() isolato devi registrarli tu (come fa scripts/test-beeprefab.mjs).

define(name, spec) salva un cloneSpec(spec). Name vuoto o spec non-oggetto lancia. Ridefinire lo stesso nome sovrascrive. has / get / list / remove / clear parlano solo di #recipes. size è #recipes.size. clear() non tocca #types.

get(name) torna una copia o null. Spawn e get non mutano la ricetta salvata: cloneSpec fa spread top-level, clona props / collider / patrol / body, e children.slice(). Gli item di children restano lo stesso reference: mutarli dopo un get sporca il catalogo.

📁 Spawn — resolve, instantiate, finish

spawn(name, override = {}) è l’unico ingresso verso un’istanza. Name sconosciuto (compreso null / '') lancia BeePrefab: ricetta sconosciuta. L’override null diventa {}.

Tre fasi private. La ricetta resta intatta: si merge una copia. L’istanza esce già posata e, di default, in scena.

  1. 1. resolveCerca la ricetta, segue extend, rileva i cicli. Merge con override. Scrive spec.prefab = id.
  2. 2. instantiateacquire, oppure pool.acquire(spec), oppure create / class / type.
  3. 3. finishx/y/w/h, campi, collider/body, setup, children, poi addEntity se addToScene !== false.
  4. 4. marcaentity.prefab è il nome della ricetta figlia, non del padre extend.

Ordine di costruzione in #instantiate: prima spec.acquire(spec, engine) se è funzione; altrimenti spec.pool → engine.pool(name).acquire(spec) (pool mancante lancia); altrimenti create || class || lookup type. Una sottoclasse di BeeEntity viene costruita con new factory(x, y, width, height). Una factory libera riceve (spec, engine) e non può tornare vuoto.

Se type manca, il lookup cade su #types.get('entity') oppure BeeEntity. Una stringa sconosciuta lancia BeePrefab.spawn: type sconosciuto.

📁 Spec — campi applicati

BEE_PREFAB_DEFAULTS (frozen): width / height da BEE_ENTITY_DEFAULTS (32), addToScene: true. I numeri non finiti vanno al fallback (x/y → 0).

Campo Effetto
type / class / create Factory. Stringa → registro type(). Funzione entity → new. Altra funzione → fn(spec, engine).
extend Nome di un’altra ricetta. Merge padre + figlia. Ciclo → errore.
x y width height Scritti su entity (getter/setter di BeeTransform per x/y). rotation / scaleX / scaleY non sono applicati.
textureKey / sprite Stesso campo: entity.textureKey. Vince textureKey.
speed lives hp health score Copiati se number. health scrive anche hp se hp è ancora null.
drawLayer sortY alpha visible active Layer stringa, sortY/alpha number, flag boolean.
color mode tag name name si applica solo se diverso da spec.prefab.
gravity vx vy Cinematica, se number.
props Spread sull’entity. Chiavi in RESERVED saltate (type, class, create, extend, props, collider, body, patrol, setup, pool, acquire, addToScene, children, prefab, x, y, width, height, textureKey, sprite).
collider true o { offsetX, offsetY, width, height } → addRectCollider. Sostituisce il collider precedente, non lo destroy.
body true o opzioni BeeRigidBody. World di default: engine.physics. Se c’è già un body, addRigidBody lo distrugge prima.
patrol { minX, maxX } oppure array [min, max] → setPatrolBounds.
setup setup(entity, spec, engine) dopo i campi, prima dei children.
children Array di nomi ricetta o spec. Spawnati con addToScene: false, poi entity.addChild.
pool / acquire Reuse invece di new. Vedi sotto.
addToScene Default true. false salta engine.addEntity.

📁 Extend e clone della ricetta

extend è resolve ricorsivo: padre prima, figlia dopo. props si deep-merge (chiavi). collider / body / patrol / children dell’override sostituiscono interi, non si fondono campo per campo. Un ciclo a → b → a lancia BeePrefab: ciclo in extend.

Non esiste entity.clone(). Il “clone” è sempre dati → nuova istanza. Due spawn dello stesso nome sono due object distinti; l’override vale solo per quella chiamata.

📁 Pool

spec.pool è il nome in engine.pools (gioco.createPool). pool.acquire(spec) riceve lo spec mergiato come primo argomento: il tuo reset(item, spec) deve leggerlo da lì. Se il pool è pieno e reclaim: false, acquire torna null e spawn restituisce null senza lanciare.

spec.acquire (funzione) bypassa il lookup: sei tu a dare l’istanza. Finish gira comunque: x/y, campi, collider, body, setup, children, scena. Su un oggetto pooled questo riapplica collider/body a ogni acquire. Il body vecchio viene distrutto; il collider vecchio viene solo sostituito (reference orfano se era già in BeeCollisionSystem). Per i pooled, metti collider/body nel reset del pool, non nella ricetta, oppure accetta il replace.

I children di un pooled nascono di nuovo a ogni spawn (non sono pooled, salvo ricetta figlia con pool suo). BeeEntity.destroy su un item con this.pool fa release, non teardown.

📁 Transform

x/y passano dai setter di BeeEntity, quindi da BeeTransform (dirty + world). width/height sono campi della entity, non della transform. Finish scrive x/y/w/h dopo il costruttore, così un acquire / factory che ignora lo spec viene riallineato.

Non c’è applicazione di rotation, scaleX, scaleY, origin. Se ti servono, mettili in setup o in props (scale/rotation non sono in RESERVED, quindi props: { rotation: 0.2 } finisce su entity.rotation).

📁 Children e id

Stringa in children → spawn(nome, { addToScene: false }). Oggetto con prefab noto → stesso schema, override nello spec figlio. Altrimenti instantiate+finish inline, sempre fuori scena, poi addChild (parent transform, no ciclo ancestor).

I cicli extend sono protetti. I cicli children (nodo che ha figlio nodo) non lo sono: stack overflow. Non fare alberi circolari.

Identità: entity.prefab è il nome della ricetta usata in spawn, non un id istanza. Non viene generato un uuid. type è la factory, prefab è la ricetta. Due slime condividono lo stesso prefab === 'slime'.

📁 fromList, fromObjects — ingest, non save

Non c’è toJSON. Le factory e setup non sono serializzabili. I due ingressi dati sono solo spawn in blocco.

fromList(items, fallbackName) vuole un array. Item non-oggetto saltati. Ogni item deve avere prefab oppure fallbackName, altrimenti lancia. Lo spec item (senza chiave prefab) è l’override.

fromObjects(objects, options) è lo stile Tiled: type / name / properties (array {name, value} oppure mappa). Chiave ricetta: obj[prefabKey] (default 'prefab'), poi props.prefab, obj.type, obj.name. visible === false e tipi senza ricetta si saltano, salvo strict: true (allora lancia). Le properties Tiled vengono sparse in cima all’override: una property chiamata type / pool / create può sovrascrivere la factory della ricetta. Non usare quei nomi nei custom properties.

spawnMany(name, spots) è una wave dello stesso prefab. Elemento array [x, y] oppure oggetto override. Non-array lancia.

📁 Motore e scena

Con engine e addToScene !== false, finish chiama engine.addEntity. addEntity rifiuta i destroyed, non duplica il reference, e se c’è una scena corrente delega a scenes.addEntity. I children nascono con addToScene: false: li tiene il parent, non l’array engine.entities (a meno che tu non li aggiunga a mano).

Senza engine (test, catalogo isolato) spawn funziona uguale, semplicemente non iscrive nessuno al loop.

📁 Edge e leak

spawn(null) / nome assente / ricetta sconosciuta → throw, non undefined silenzioso. fromObjects è l’unico ingresso permissivo (skip). Factory che torna falsy lancia.

Leak tipici: dimenticare destroy sulle entity spawnate (restano in engine.entities e, se hanno body, in BeePhysicsWorld). prefabs.clear() non le tocca. Children non aggiunti al parent (entity senza addChild) restano solo se le hai messe in scena. Su pool: collider riapplicato ogni acquire può lasciare il vecchio box nel collision system.

📁 Dove è facile sbagliare

gioco.spawn ≠ gioco.prefabs.spawn. Il primo è il pool nominato. Il secondo è la ricetta. Non sono alias.

Senza type non è un errore. Cade su BeeEntity. Se volevi un nemico e hai solo define('slime', { hp: 3 }), ottieni un’entity nuda.

Children circolari. extend è sicuro. Un albero children: ['se-stesso'] no.

Tiled type vs factory type. fromObjects usa obj.type come nome ricetta. Poi sparge le properties sopra lo spec. Una property type cambia la factory.

Pool + collider in ricetta. Finish riapplica. Preferisci reset sul pool, o spec.acquire che torna l’item già armato e tieni collider fuori dallo spec.

Transform parziale. Solo x/y/w/h. Rotazione e scala: setup o props.

📁 Esempio: extend, wave, tilemap

import { BeeEngine } from ‘beeengine’; const gioco = new BeeEngine(‘testCanvas’, 800, 600); gioco.prefabs.define(‘slime’, { type: ‘enemy’, width: 32, height: 32, speed: 45, hp: 3, collider: true }); gioco.prefabs.define(‘slime-fast’, { extend: ‘slime’, speed: 90, hp: 2 }); const wave = gioco.prefabs.spawnMany(‘slime’, [ [0, 1], { x: 4, y: 5, hp: 8 } ]); gioco.prefabs.fromList([ { prefab: ‘slime’, x: 1, y: 2 }, { prefab: ‘slime-fast’, x: 3, y: 4 } ]); gioco.prefabs.fromObjects([ { type: ‘slime’, x: 50, y: 60, width: 16, height: 16, properties: [{ name: ‘hp’, value: 7 }] }, { type: ‘decor’, x: 0, y: 0 } ]);

📁 Riepilogo API

Fluent: type, define, remove, clear ritornano this. I default sono in BEE_PREFAB_DEFAULTS (frozen). engine sull’istanza è pubblico.

Membro Ritorna Scopo
BEE_PREFAB_DEFAULTS oggetto frozen width / height da entity (32) · addToScene true.
new BeePrefab({ engine }) BeePrefab Catalogo vuoto. Senza engine non aggiunge alla scena.
size number Quante ricette in #recipes. Non conta i type.
type(name, factory) this Registra factory. Name o factory mancanti lanciano.
define(name, spec) this Salva cloneSpec(spec). Spec non-oggetto lancia.
has(name) boolean Presenza ricetta. null → false.
get(name) spec o null Copia. Mutare il risultato non deve (salvo item di children) toccare il catalogo.
list() string[] Nomi ricetta, ordine di inserimento Map.
remove(name) / clear() this Toglie ricette. I type restano. Le entity già spawnate restano.
spawn(name, override) entity o null Resolve + instantiate + finish. Sconosciuto lancia. Pool pieno può tornare null.
spawnMany(name, spots) array Wave. Spot [x, y] o oggetto. Non-array lancia.
fromList(items, fallbackName) array Ingest array di override. Manca prefab → throw.
fromObjects(objects, options) array Oggetti Tiled. Skip hidden/unknown. strict lancia.
engine oggetto o null Pubblico. Serve a pool, physics, addEntity.