Skip to content

Box2DPlatformer Corpi e Contatti

Alessandro Bria edited this page Sep 29, 2026 · 3 revisions

Box2DPlatformer: world, body e contatti

La scena di gioco possiede un Box2D world. Ogni RigidObject del progetto associa un RenderableObject del core a un body; le sottoclassi creano le shape e interpretano i contatti. Box2D calcola il moto e la collision response, mentre collision() notifica le regole di gioco.

Un timestep del world

PlatformerGameScene crea il world da b2DefaultWorldDef(), imposta gravità {0, -24} e abilita lo sleep dei body. Conserva il b2WorldId e lo distrugge nel proprio distruttore. LevelLoader le passa il valore scene.dt del JSON (1/100 s nel livello incluso); _subStepCount vale 4.

tempo accumulato >= _dt
  -> updatePhysicsControls()
  -> b2World_Step(worldId, _dt, 4)
  -> eventi di contatto ordinario
  -> eventi dei sensori
  -> update(_dt) degli oggetti non congelati

Questo ciclo può ripetere più timestep fisici per recuperare il tempo accumulato. Ogni iterazione passa lo stesso _dt sia a b2World_Step() sia a obj->update(): animazioni, scheduler degli oggetti e logica avanzano quindi del tempo effettivamente simulato. L'eventuale resto rimane nell'accumulatore.

Le forze dei controlli vengono applicate da updatePhysicsControls() prima di ogni step, perché Box2D le azzera dopo il passo. Applicarle una sola volta per frame ridurrebbe la spinta quando un frame contiene più step, come può accadere con un proiettore a 60 Hz. VSync e refresh influenzano la presentazione; il livello mantiene 100 passi fisici per secondo di tempo simulato. I quattro substep sono suddivisioni interne del singolo passo Box2D, non quattro frame.

Camera e input discreti restano nel loro percorso per frame/evento; salto, dash e sparo ignorano le ripetizioni automatiche del tasto. Non è previsto un limite al numero di passi di recupero, quindi un frame molto lungo può richiedere molti step consecutivi.

Il ponte RigidObject

Il costruttore crea un b2BodyId con b2DefaultBodyDef(), posizione e angolo dell'_obb (RotatedRectF), tipo di body e userData = this. Non crea una shape: sono le classi derivate a chiamare b2CreatePolygonShape(), b2CreateCapsuleShape() o b2CreateCircleShape(). Questa è l'API a ID di Box2D 3, non l'interfaccia a puntatori di Box2D 2.

Dopo la simulazione, RigidObject::update() legge posizione e rotazione dal body, aggiorna _obb, il rettangolo del RenderableObject e l'angolo di disegno. La fisica è dunque la sorgente della trasformazione; cambiare soltanto il rettangolo visivo non teletrasporta un body. draw() può sovrapporre collider e asse del body quando collidersVisible() è attivo. setFreezed() disabilita o riabilita anche il body Box2D, non solo l'update logico. Il distruttore elimina il body ancora valido.

Tipo nel progetto Tipo Box2D Esempi
StaticObject b2_staticBody Segmenti Terrain; shape poligonale creata nella classe base.
DynamicObject b2_dynamicBody Player, Box, Enemy, Fire; la shape è scelta dalla classe concreta.
KinematicObject b2_kinematicBody Gear; posizione e rotazione seguono velocità imposte.
Trigger b2_staticBody con sensor shape Notifica un ingresso senza produrre una barriera fisica.

b2ShapeDef descrive ciascuna shape: densità, materiale (material.friction e material.restitution), eventi e filtri. Body e shape non sono la stessa cosa: un Gear ha un body e più shape, mentre una texture può non avere alcun body.

Eventi di contatto

Dopo ogni b2World_Step(), la scena legge b2World_GetContactEvents(). Per ciascun inizio/fine contatto risale dagli ID delle shape ai body e dal loro userData ai due RigidObject. Per l'inizio usa b2Contact_GetData(contactId).manifold.normal, poi invoca collision() su entrambi con normali opposte. Alla fine la normale non è più definita: passa il vettore nullo a entrambi. Non usare quindi la normale di un evento begin = false per decidere una direzione d'impatto.

DynamicObject::collision() tiene un insieme di contatti col terreno. Quando il contatto inizia contro uno statico o cinematico e la normale ha y < 0, memorizza l'ID della shape toccata; alla fine rimuove quella shape. I contatti con più shape dello stesso corpo restano quindi distinti. onGround() è vero se l'insieme non è vuoto. move() applica una forza orizzontale al centro, jump() un impulso verso l'alto soltanto se onGround() è vero. L'integrazione e la risposta ai vincoli rimangono in Box2D.

Filtri e sensori

CollisionCategory definisce bit per Static, Dynamic, Kinematic, Player, Weapon ed Enemy. Il player usa categoryBits = Player e maskBits che include Enemy, Static e Kinematic; Fire usa Weapon e maskBits che include Enemy. Il collision filtering restringe quali shape possono produrre contatti: non controlla la visibilità. Per avere le notifiche di contatto, le shape interessate devono inoltre abilitare gli eventi appropriati, come negli esempi di Player, Box, Slime e Fire.

Trigger crea una sensor shape con isSensor = true e enableSensorEvents = true. La scena legge separatamente b2World_GetSensorEvents() e notifica solo il sensore, passando l'oggetto visitatore e un vettore normale nullo. Trigger::collision() esegue il task std::function<void()> passato al costruttore soltanto quando entra l'oggetto sorvegliato. Il livello corrente non istanzia trigger: la classe mostra come aggiungerli.

Cosa osservare nel debugger

  1. Confronta bodyId() e _obb: il body si muove in b2World_Step(), poi la rappresentazione viene sincronizzata.
  2. Mostra i collider per distinguere sprite, rettangolo di riferimento e shape effettive; in particolare player e ingranaggi non sono semplici rettangoli.
  3. Metti un breakpoint in PlatformerGameScene::updateWorld() per separare eventi fisici da RigidObject::collision().
  4. Confronta con le collisioni di CustomPlatformer: lì il solver appartiene al progetto, qui a Box2D.

Sorgenti

Prosegui con Entità e gameplay.

Clone this wiki locally