Skip to content

Contatti BEGIN STAY END

Alessandro Bria edited this page Nov 1, 2026 · 1 revision

Usare Contact: BEGIN, STAY ed END

Questa guida riguarda CustomPlatformer, CustomPlatformerSlopes e ActionRPG. Quando scrivi un nemico, un oggetto raccoglibile o una zona interattiva, implementa collision(): il sistema rileva il contatto e ti comunica con chi stai interagendo e in quale fase. Non occorre confrontare i collider o conservare la lista dei contatti nella tua entità.

Scegliere quando reagire

enum class Contact { BEGIN, STAY, END };
Fase Significato Uso tipico
Contact::BEGIN Il contatto è appena iniziato. Raccogliere una moneta, colpire con la spada, attivare un interruttore.
Contact::STAY Il contatto continua. Mantenere un effetto, aggiornare il passeggero di una piattaforma, verificare un danno persistente.
Contact::END Il contatto è terminato. Spegnere un effetto, rimuovere un passeggero, riarmare un portale.

Per un personaggio che entra in una zona, rimane al suo interno e poi esce, la sequenza concettuale è:

entra          rimane nella zona                 esce
BEGIN          STAY  STAY  STAY  ...              END

Se rientra, riceve un nuovo BEGIN. Un attraversamento molto breve può produrre BEGIN e poi END senza alcuno STAY: non basare un'azione di ingresso sull'arrivo di STAY.

BEGIN significa una volta per contatto, non una volta per tutta la partita. Per un oggetto raccoglibile che deve scomparire definitivamente usa anche kill(), come fa Coin.

La callback che implementi

Nei due platformer la firma è:

bool collision(CollidableObject* with, Contact contact,
               Direction fromDir) override;
  • with è l'altro oggetto: controllane il tipo oppure confrontalo con l'oggetto che stai osservando.
  • contact indica la fase: scegli quali eventi hanno significato per la tua meccanica.
  • fromDir è il lato del tuo oggetto interessato dal contatto. Un personaggio sopra una piattaforma riceve DOWN; la piattaforma riceve UP. Su END vale Direction::NONE.
  • Il valore restituito indica se hai gestito l'interazione. Restituire false non annulla la collisione fisica e non impedisce la notifica all'altro oggetto. Per escludere una coppia usa collidableWith().

In ActionRPG l'ultimo parametro è const Vec2Df& normal, perché i collider possono essere ruotati. La normale va dal tuo oggetto verso l'altro; l'altro oggetto riceve quella opposta. Su END è il vettore nullo. Le tre fasi hanno lo stesso significato.

Esempio: colpire una volta all'ingresso

Questo è il comportamento della spada in CustomPlatformer:

bool Sword::collision(CollidableObject* with, Contact contact,
                      Direction fromDir)
{
    Enemy* enemy = dynamic_cast<Enemy*>(with);
    if (enemy && contact == Contact::BEGIN)
        enemy->smash();
    return true;
}

Il colpo non si ripete mentre spada e nemico restano a contatto e non viene applicato quando si separano. Anche il Trigger generico esegue il proprio task soltanto su BEGIN, per l'oggetto osservato: restare dentro o uscire non lo riattiva.

Per una reazione valida sia all'ingresso sia durante la permanenza, il controllo è invece:

if (contact != Contact::END)
{
    // Reazione valida su BEGIN e STAY.
}

È la scelta dei nemici dei platformer per il contatto con Mario. Gli ulteriori controlli della meccanica, come invincibilità e tempo di protezione dopo un colpo, restano responsabilità dell'entità.

Esempio: una zona attiva finché il giocatore rimane dentro

Questa classe può essere aggiunta a uno dei due platformer. Osserva un solo oggetto, passato al costruttore; gli altri non ne cambiano lo stato.

class AreaAttiva : public agp::StaticObject
{
    agp::CollidableObject* _watched;
    bool _active = false;

public:
    AreaAttiva(agp::Scene* scene, const agp::RectF& rect,
               agp::CollidableObject* watched)
        : StaticObject(scene, rect), _watched(watched)
    {
        _compenetrable = true;
    }

    bool active() const { return _active; }

    bool collision(agp::CollidableObject* with, agp::Contact contact,
                   agp::Direction fromDir) override
    {
        if (with != _watched)
            return false;
        if (contact == agp::Contact::BEGIN)
            _active = true;
        else if (contact == agp::Contact::END)
            _active = false;
        return true; // STAY mantiene lo stato già attivo.
    }
};

Includi StaticObject.h. La logica della porta o dell'effetto può leggere active(). Per un effetto che dipende dal tempo, applica la variazione nel tuo update(dt) mentre la zona è attiva, moltiplicandola per dt: non contare le chiamate a STAY come se fossero secondi. Se vuoi reagire direttamente a ogni permanenza, aggiungi un ramo contact == Contact::STAY.

Casi già presenti nelle demo

Entità Reazione alle fasi
Trigger nelle tre demo Esegue il task su BEGIN dell'oggetto osservato.
Sword in CustomPlatformer Colpisce il nemico su BEGIN.
Coin e raccolta del Mushroom in CustomPlatformerSlopes Assegnano la ricompensa su BEGIN, poi richiedono la rimozione.
Block in CustomPlatformerSlopes Reagisce a BEGIN di Mario dal basso; il trasporto resta gestito dalla base cinematica.
KinematicObject e Lift nei platformer Registrano/aggiornano il passeggero su BEGIN e STAY; lo rimuovono su END.
Enemy in ActionRPG Si elimina su BEGIN con una Sword, mai su END.
Portal in ActionRPG Avvia l'ingresso su BEGIN; ignora l'arrivo da un altro portale finché END non lo riarma.

Quando arrivano gli eventi

Gli eventi appartengono agli aggiornamenti della simulazione, non ai frame disegnati. BEGIN ed END vengono notificati ai due oggetti una sola volta per la transizione. Un oggetto che rileva i contatti riceve STAY nel proprio aggiornamento; gli oggetti passivi lo ricevono da chi li tocca. L'ordine degli aggiornamenti può quindi far arrivare BEGIN e uno STAY nello stesso passo della scena.

Un contatto può terminare per separazione, disabilitazione delle collisioni o rimozione dell'altro oggetto. La rimozione ripulisce i riferimenti ed emette END anche per un oggetto congelato. Non conservare il puntatore with dopo la rimozione dell'oggetto.

Nei platformer il CCD registra anche l'attraversamento rapido di un collider compenetrabile: non è necessario trovarsi ancora al suo interno alla fine dello spostamento. Se il contatto non continua, END arriva al controllo successivo. In ActionRPG la detection resta discreta: aggiungere Contact non impedisce di attraversare un oggetto sottile con uno spostamento troppo grande.

Devo cambiare _detectsContacts?

Normalmente no: scegli la classe base adatta e implementa collision().

_detectsContacts stabilisce chi rileva i contatti. È true per gli oggetti attivi; è già false in StaticObject, Trigger e, nei platformer, KinematicObject. Questi oggetti ricevono gli eventi dagli oggetti che li toccano. Un Lift si muove, ma è passivo rispetto alla generazione degli eventi.

Il flag non disabilita le collisioni e non rende l'oggetto attraversabile. Per la tua meccanica distingui:

  • collidableWith(other): decide se accettare quella coppia; devono accettarla entrambi gli oggetti.
  • _collidable: abilita o disabilita le collisioni dell'oggetto.
  • _compenetrable: distingue i collider attraversabili; nei platformer evita la risposta fisica. ActionRPG conserva una separazione morbida fra dinamici, descritta nella sua guida al movimento.

Adattare un vecchio handler con bool begin

Sostituisci il parametro con Contact contact, poi scegli esplicitamente il comportamento:

Intenzione Condizione
Solo all'ingresso contact == Contact::BEGIN
Solo durante la permanenza contact == Contact::STAY
Ingresso e permanenza contact != Contact::END
Solo all'uscita contact == Contact::END

Il vecchio begin == true poteva ripetersi a ogni aggiornamento: sostituirlo sempre con Contact::BEGIN cambierebbe le meccaniche che devono continuare durante il contatto. Non chiamare manualmente collision() per simulare una fase: lascia che sia il sistema a generare gli eventi.

Box2DPlatformer usa un'altra API: mantiene il parametro bool begin e gli eventi nativi di Box2D, senza aggiungere STAY. Vedi Corpi e contatti.

Approfondimenti e sorgenti

Clone this wiki locally