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.
📁 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. resolveCerca la ricetta, segue
extend, rileva i cicli. Merge con override. Scrivespec.prefab = id. -
2. instantiate
acquire, oppurepool.acquire(spec), oppurecreate/class/type. -
3. finishx/y/w/h, campi, collider/body,
setup, children, poiaddEntityseaddToScene !== false. -
4. marca
entity.prefabè il nome della ricetta figlia, non del padreextend.
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
📁 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. |