Detaillierte Erklärung, wie CocosCreator-Systemereignisse generiert und ausgelöst werden

Detaillierte Erklärung, wie CocosCreator-Systemereignisse generiert und ausgelöst werden

Umfeld

Cocos-Ersteller 2.4
Chrom 88

Zusammenfassung

Modul Funktion

Ein Mechanismus zur Ereignisüberwachung sollte ein wesentlicher Bestandteil aller Spiele sein. Egal, ob Sie auf eine Schaltfläche klicken oder ein Objekt ziehen, die Ereignisüberwachung und -verteilung ist von entscheidender Bedeutung.
Die Hauptfunktion besteht darin, Systemereignisse (wie Berühren, Klicken) über die Ein/Aus-Funktion des Knotens zu überwachen und dann die entsprechende Spiellogik auszulösen. Gleichzeitig unterstützt es Benutzer auch beim Starten/Abhören benutzerdefinierter Ereignisse. Informationen hierzu finden Sie im offiziellen Dokument „Abhören und Starten von Ereignissen“.

Zugehörige Dokumente

Sowohl CCGame als auch CCInputManager sind an der Registrierung von Ereignissen beteiligt, sie sind jedoch für unterschiedliche Teile verantwortlich.

Quellcodeanalyse

Wie gelangen Ereignisse zur Engine (vom Browser)?

Um diese Frage zu beantworten, müssen wir verstehen, woher die Interaktion zwischen der Engine und dem Browser kommt.
Hier ist der Code.

CCGame.js

// Ereignis system_initEvents initialisieren: function () {
  var win = Fenster, hiddenPropName;

  //_ Systemereignisse registrieren
  //Systemereignisse registrieren, hier rufen wir die Methode CCInputManager auf, wenn (this.config.registerSystemEvent)
    _cc.inputManager.registerSystemEvent(diese.Leinwand);

  // document.hidden bedeutet, dass die Seite ausgeblendet ist. Das folgende if wird verwendet, um die Browserkompatibilität zu gewährleisten, if (typeof document.hidden !== 'undefined') {
    hiddenPropName = "versteckt";
  } sonst wenn (Typ von document.mozHidden !== 'undefiniert') {
    hiddenPropName = "mozHidden";
  } sonst wenn (Typ von Dokument.msHidden !== 'undefined') {
    hiddenPropName = "msHidden";
  } sonst wenn (Typ von Dokument.webkitHidden !== 'undefiniert') {
    hiddenPropName = "webkitHidden";
  }

  // Ist die aktuelle Seite ausgeblendet? var hidden = false;

  //Rückruf, wenn die Seite ausgeblendet ist und das Ereignis game.EVENT_HIDE ausgibt Funktion onHidden () {
    wenn (!versteckt) {
      versteckt = wahr;
      Spiel.emit(Spiel.EVENT_HIDE);
    }
  }
  //_ Um die Onshow-API an die meisten Plattformen anzupassen.
  // Zur Anpassung an die Onshow-API der meisten Plattformen. Es sollte sich auf den Teil zur Parameterübergabe beziehen …
  // Rückruf, wenn die Seite sichtbar ist, und Ausgabe der Ereignisfunktion game.EVENT_SHOW onShown (arg0, arg1, arg2, arg3, arg4) {
    if (versteckt) {
      versteckt = falsch;
      spiel.emit(spiel.EVENT_SHOW, arg0, arg1, arg2, arg3, arg4);
    }
  }

  // Wenn der Browser versteckte Eigenschaften unterstützt, registrieren Sie das Ereignis zur Änderung des visuellen Seitenstatus, wenn (hiddenPropName) {
    var Änderungsliste = [
      "Sichtbarkeitsänderung",
      "mozsichtbarkeitsänderung",
      "msvisibilitychange",
      "WebKitSichtbarkeitsänderung",
      "qbrowserVisibilityChange"
    ];
    // Aus Kompatibilitätsgründen durchlaufe die Ereignisse in der Liste oben // Nachdem sich der verborgene Status geändert hat, rufe die Callback-Funktion onHidden/onShown basierend auf dem sichtbaren Status auf für (var i = 0; i < changeList.length; i++) {
      document.addEventListener(changeList[i], Funktion (Ereignis) {
        var sichtbar = Dokument[hiddenPropName];
        //_QQ App
        sichtbar = sichtbar || Ereignis["versteckt"];
        wenn (sichtbar)
          beiVersteckt();
        anders
          aufAnzeigen();
      });
    }
  }
  // Einige Kompatibilitätscodes zu Änderungen des visuellen Status der Seite werden hier ausgelassen. // Registrieren Sie Ausblend- und Anzeigeereignisse und pausieren oder starten Sie die Hauptlogik des Spiels neu.
  dies.auf(spiel.EVENT_HIDE, funktion () {
    spiel.pause();
  });
  dies.auf(spiel.EVENT_SHOW, funktion () {
    spiel.fortsetzen();
  });
}

Tatsächlich gibt es nur ein bisschen Kerncode ... Um die Kompatibilität mit verschiedenen Plattformen aufrechtzuerhalten,
Es gibt zwei wichtige Punkte:

  1. Aufrufen von CCInputManager-Methoden
  2. Registrieren Sie das Ereignis zur Änderung des visuellen Status der Seite und lösen Sie die Ereignisse game.EVENT_HIDE und game.EVENT_SHOW aus.

Werfen wir einen Blick auf CCInputManager.

CCInputManager.js

//Registrieren Sie das Systemereigniselement auf Canvas
registerSystemEvent (Element) {
  wenn(dieses._istRegisterEvent) zurückgeben;

  // Bereits registriert, direkt zurück
  dies._glView = cc.view;
  lass selfPointer = dies;
  Lassen Sie canvasBoundingRect = this._canvasBoundingRect;

  // Auf Größenänderungsereignisse achten und dies ändern._canvasBoundingRect
  window.addEventListener('Größe ändern', this._updateCanvasBoundingRect.bind(this));

  lassen Sie Verbot = sys.isMobile;
  let supportMouse = ('Maus' in sys.capabilities);
  // Ob Touchlet unterstützt werden soll supportTouches = ('touches' in sys.capabilities);
	
  // Registrierungscode für Mausereignisse weggelassen //_Touch-Ereignis registrieren
  // Touch-Ereignisse registrieren if (supportTouches) {
    // Veranstaltungskarte
    let _touchEventsMap = {
      "touchstart": Funktion (touchesToHandle) {
        selfPointer.handleTouchesBegin(touchesToHandle);
        element.fokus();
      },
      "touchmove": Funktion (berührtZuHandle) {
        selfPointer.handleTouchesMove(touchesToHandle);
      },
      "touchend": Funktion (touchesToHandle) {
        selfPointer.handleTouchesEnd(touchesToHandle);
      },
      "touchcancel": Funktion (touchesToHandle) {
        selfPointer.handleTouchesCancel(touchesToHandle);
      }
    };

    // Durchsuche die Karte, um Ereignisse zu registrieren let registerTouchEvent = function (eventName) {
      let handler = _touchEventsMap[Ereignisname];
      // Ereignisse im Canvas registrieren element.addEventListener(eventName, (function(event) {
        wenn (!event.changedTouches) return;
        lass body = Dokument.body;

        // Den Versatz berechnen canvasBoundingRect.adjustedLeft = canvasBoundingRect.left - (body.scrollLeft || window.scrollX || 0);
        canvasBoundingRect.adjustedTop = canvasBoundingRect.top – (body.scrollTop || window.scrollY || 0);
        // Holen Sie den Berührungspunkt vom Ereignis und rufen Sie die Rückruffunktion handler(selfPointer.getTouchesByEvent(event, canvasBoundingRect)) auf.
        // Stoppen Sie das Aufsteigen des Ereignisses event.stopPropagation();
        event.preventDefault();
      }), FALSCH);
    };
    für (let eventName in _touchEventsMap) {
      registerTouchEvent(Ereignisname);
    }
  }

  // Ändern Sie die Eigenschaft, um anzuzeigen, dass die Ereignisregistrierung abgeschlossen wurde. this._isRegisterEvent = true;
}

Im Code wird vor allem eine Reihe nativer Events wie Touchstart registriert. Im Event-Callback werden die Funktionen in selfPointer(=this) zur Verarbeitung aufgerufen. Hier verwenden wir das Touchstart-Ereignis als Beispiel, nämlich die Funktion handleTouchesBegin.

// Behandeln Sie das Touchstart-Ereignis handleTouchesBegin (touches) {
  lass selTouch, index, curTouch, touchID,
      handleTouches = [], locTouchIntDict = this._touchesIntegerDict,
      jetzt = sys.now();
  // Berührungspunkte durchlaufen für (let i = 0, len = touches.length; i < len; i++) {
    // Aktueller Berührungspunkt selTouch = touches[i];
    // Berührungspunkt-ID
    touchID = selTouch.getID();
    // Die Position des Berührungspunkts in der Berührungspunktliste (this._touches) index = locTouchIntDict[touchID];

    // Wenn der Index nicht abgerufen wird, bedeutet dies, dass es sich um einen neuen Berührungspunkt handelt (gerade gedrückt)
    wenn (index == null) {
      // Einen ungenutzten Index abrufen
      let ungenutzterIndex = this._getUnUsedIndex();
      //Kann nicht abgerufen werden, es tritt ein Fehler auf. Möglicherweise wurde die maximale Anzahl unterstützter Berührungspunkte überschritten.
      wenn (unbenutzterIndex === -1) {
        cc.logID(2300, unbenutzter Index);
        weitermachen;
      }
      //_curTouch = this._touches[unbenutzterIndex] = selTouch;
      //Berührungspunkte speichern curTouch = this._touches[unusedIndex] = new cc.Touch(selTouch._point.x, selTouch._point.y, selTouch.getID());
      curTouch._lastModified = jetzt;
      curTouch._setPrevPoint(selTouch._prevPoint);
      locTouchIntDict[touchID] = unbenutzter Index;
      // Zur Liste der Berührungspunkte hinzufügen, die verarbeitet werden müssen handleTouches.push(curTouch);
    }
  }
  //Bei einem neuen Kontakt ein Touch-Event generieren und an den EventManager verteilen
  wenn (handleTouches.length > 0) {
    // Diese Methode verarbeitet die Position des Berührungspunkts entsprechend der Skala this._glView._convertTouchesWithScale(handleTouches);
    let touchEvent = neues cc.Event.EventTouch(handleTouches);
    touchEvent._eventCode = cc.Event.EventTouch.BEGAN;
    eventManager.dispatchEvent(touchEvent);
  }
},

In der Funktion wird ein Teil des Codes zum Filtern verwendet, ob es neue Berührungspunkte gibt, und der andere Teil wird zum Verarbeiten und Verteilen von Ereignissen (falls erforderlich) verwendet.
An diesem Punkt hat das Ereignis die Transformation vom Browser zur Engine abgeschlossen und den EventManager erreicht. Was passiert also zwischen der Engine und dem Knoten?

Wie gelangen Ereignisse von der Engine zu den Knoten?

Die Weitergabe von Ereignissen an Knoten erfolgt hauptsächlich in der Klasse CCEventManager. Beinhaltet Speicherereignis-Listener, Verteilungsereignisse usw. Beginnen wir mit _dispatchTouchEvent als Einstiegspunkt.

CCEventManager.js

// Versenden von events_dispatchTouchEvent: function (event) {
  // Touch-Listener sortieren // TOUCH_ONE_BY_ONE: Typ des Touch-Event-Listeners, Touch-Punkte werden einzeln versendet // TOUCH_ALL_AT_ONCE: Touch-Punkte werden alle auf einmal versendet this._sortEventListeners(ListenerID.TOUCH_ONE_BY_ONE);
  dies._sortEventListeners(ListenerID.TOUCH_ALL_AT_ONCE);

  // Listener-Liste abrufen var oneByOneListeners = this._getListeners(ListenerID.TOUCH_ONE_BY_ONE);
  var allAtOnceListeners = this._getListeners(ListenerID.TOUCH_ALL_AT_ONCE);

  //_ Wenn keine Touch-Listener vorhanden sind, direkt zurückkehren.
  // Wenn kein Listener vorhanden ist, einfach zurückkehren.
  wenn (null === oneByOneListeners und null === allAtOnceListeners)
    zurückkehren;

  // Variablen speichern var originalTouches = event.getTouches(), mutableTouches = cc.js.array.copy(originalTouches);
  var oneByOneArgsObj = {event: event, needsMutableSet: (oneByOneListeners && allAtOnceListeners), touches: mutableTouches, selTouch: null};

  //
  //_ Verarbeite zuerst die Zielhandler
  // Wird nicht umkippen. Der Sinn besteht darin, zuerst einzelne Berührungsereignisse zu behandeln.
  wenn (oneByOneListeners) {
    // Kontakte durchlaufen und der Reihe nach verteilen for (var i = 0; i < originalTouches.length; i++) {
      event.currentTouch = originalTouches[i];
      event._propagationStopped = event._propagationImmediateStopped = false;
      this._dispatchEventToListeners(oneByOneListeners, this._onTouchEventCallback, oneByOneArgsObj);
    }
  }

  //
  //_ Standardhandler verarbeiten 2.
  // Wird nicht umkippen. Es fühlt sich an, als ob die zweite Sache darin besteht, Multi-Touch-Ereignisse zu verarbeiten (alle auf einmal zu versenden).
  wenn (allAtOnceListeners und mutableTouches.length > 0) {
    this._dispatchEventToListeners(allAtOnceListeners, this._onTouchesEventCallback, {Ereignis: Ereignis, Berührungen: mutableTouches});
    wenn (event.isStopped())
      zurückkehren;
  }
  // Aktualisieren Sie die Touch-Listener-Liste, hauptsächlich um Listener zu entfernen und hinzuzufügen. this._updateTouchListeners(event);
},

Die wichtigsten Aufgaben der Funktion sind Sortieren, Verteilen an die registrierte Listener-Liste und Aktualisieren der Listener-Liste. Nichts Besonderes. Sie fragen sich vielleicht, warum dieser Befehl so abrupt erfolgt? Ach, das ist das Wichtigste! Informationen zur Rolle der Sortierung finden Sie im offiziellen Dokument zur Übertragung von Berührungsereignissen. Durch diese Sortierung wird das Problem der Kontaktpunktzuordnung zwischen Knoten unterschiedlicher Ebenen/unterschiedlicher zIndex-Werte realisiert. Auf die Sortierung werde ich später noch eingehen, sie ist erstaunlich.
Das Versenden von Ereignissen erfolgt durch den Aufruf der Funktion _dispatchEventToListeners. Werfen wir einen Blick auf die interne Implementierung.

/**
* Ereignisse an Listener-Listen verteilen * @param {*} Listener Listener-Liste * @param {*} onEvent Ereignis-Callback * @param {*} eventOrArgs Ereignis/Parameter */
_dispatchEventToListeners: Funktion (Listener, bei Ereignis, Ereignis oder Argumente) {
  //Müssen Sie die Verteilung stoppen? var shouldStopPropagation = false;
  // Holen Sie sich einen Listener mit fester Priorität (Systemereignis)
  var fixedPriorityListeners = listeners.getFixedPriorityListeners();
  // Holen Sie sich den Listener mit Szenengraph-Priorität (normalerweise sind die Listener, die wir hinzufügen, hier)
  var sceneGraphPriorityListeners = listeners.getSceneGraphPriorityListeners();

  /**
  * Listener-Auslösereihenfolge:
  * Feste Prioritätsstufe < 0
  * Szenengraph-Priorität * Feste Priorität > 0
  */
  var i = 0, j, selListener;
  if (fixedPriorityListeners) { //_ Priorität < 0
    wenn (fixedPriorityListeners.length !== 0) {
      // Durchlaufe die Listener um Ereignisse zu verteilen für (; i < listeners.gt0Index; ++i) {
        selListener = fixedPriorityListeners[i];
        // Wenn der Listener aktiviert und nicht angehalten ist und beim Event-Manager registriert wurde // Das letzte onEvent dient dazu, die Funktion _onTouchEventCallback zu verwenden, um Ereignisse an den Listener zu verteilen // onEvent gibt einen Booleschen Wert zurück, der angibt, ob es notwendig ist, mit der Verteilung von Ereignissen an nachfolgende Listener fortzufahren. Wenn dies zutrifft, beenden Sie die Verteilung, wenn (selListener.isEnabled() && !selListener._isPaused() && selListener._isRegistered() && onEvent(selListener, eventOrArgs)) {
          sollteStopPropagation = wahr;
          brechen;
        }
      }
    }
  }
  //Die Triggercodes der anderen beiden Prioritäten weglassen},

In der Funktion werden die Ereignisse durch Durchlaufen der Listener-Liste nacheinander verteilt. Ob die Verteilung fortgesetzt werden soll, wird anhand des Rückgabewerts von onEvent bestimmt. Nachdem ein Berührungsereignis von einem Knoten empfangen wurde, wird die Weiterleitung im Allgemeinen beendet. Anschließend wird die Logik der Blasenverteilung von diesem Knoten aus ausgeführt. Dies ist auch ein wichtiger Punkt, nämlich, dass nur ein Knoten auf das Berührungsereignis reagiert. Was die Priorität des Knotens betrifft, so ist dies der oben erwähnte Sortieralgorithmus.
Das onEvent hier ist eigentlich die Funktion _onTouchEventCallback. Sehen wir uns das einmal an.

// Rückruf des Touch-Ereignisses. Verteilen Sie Ereignisse an listeners_onTouchEventCallback: function (listener, argsObj) {
  //_ Überspringen, wenn der Listener entfernt wurde.
  // Überspringen, wenn der Listener entfernt wurde.
  wenn (!listener._isRegistered())
    gibt false zurück;

  var event = argsObj.event, selTouch = event.currentTouch;
  Ereignis.aktuellesTarget = listener._node;

  // isClaimed: Listener, ob das Ereignis beansprucht werden soll, var isClaimed = false, removedIdx;
  var getCode = event.getEventCode(), EventTouch = cc.Event.EventTouch;
  // Wenn das Ereignis ein Touch-Start-Ereignis ist, if (getCode === EventTouch.BEGAN) {
    // Wenn Multi-Touch nicht unterstützt wird und bereits ein Berührungspunkt vorhanden ist, if (!cc.macro.ENABLE_MULTI_TOUCH && eventManager._currentTouch) {
      // Wenn der Berührungspunkt von einem Knoten beansprucht wurde und der Knoten im Knotenbaum aktiv ist, wird das Ereignis nicht verarbeitet. let node = eventManager._currentTouchListener._node;
      if (Knoten && node.activeInHierarchy) {
        gibt false zurück;
      }
    }

    // Wenn der Listener ein entsprechendes Ereignis hat if (listener.onTouchBegan) {
      // Versuchen Sie, an den Listener zu verteilen, und geben Sie einen Booleschen Wert zurück, um anzugeben, ob der Listener das Ereignis beansprucht. isClaimed = listener.onTouchBegan(selTouch, event);
      // Wenn das Ereignis beansprucht und der Listener registriert ist, einige Daten speichern, if (isClaimed && listener._registered) {
        listener._claimedTouches.push(selTouch);
        eventManager._currentTouchListener = Listener;
        eventManager._currentTouch = selTouch;
      }
    }
  } 
  // Wenn der Listener bereits eine Berührung beansprucht hat und die aktuelle Berührung vom aktuellen Listener beansprucht wird, sonst wenn (listener._claimedTouches.length > 0
           && ((removedIdx = listener._claimedTouches.indexOf(selTouch)) !== -1)) {
    // Direkt mit nach Hause nehmen isClaimed = true;

    // Wenn Multi-Touch nicht unterstützt wird und ein Berührungspunkt vorhanden ist und es sich nicht um den aktuellen Berührungspunkt handelt, verarbeiten Sie das Ereignis nicht, wenn (!cc.macro.ENABLE_MULTI_TOUCH && eventManager._currentTouch && eventManager._currentTouch !== selTouch) {
      gibt false zurück;
    }

    // Ereignisse an Listener verteilen // Bei BEENDET oder ABGEBROCHEN müssen Sie die Kontakte im Listener und Event-Manager bereinigen, wenn (getCode === EventTouch.MOVED && listener.onTouchMoved) {
      listener.onTouchMoved(selTouch, Ereignis);
    } sonst wenn (getCode === EventTouch.ENDED) {
      wenn (listener.onTouchEnded)
        listener.onTouchEnded(selTouch, Ereignis);
      wenn (listener._registered)
        listener._claimedTouches.splice(entfernteIdx, 1);
      eventManager._clearCurTouch();
    } sonst wenn (getCode === EventTouch.CANCELED) {
      wenn (listener.onTouchCancelled)
        listener.onTouchCancelled(selTouch, Ereignis);
      wenn (listener._registered)
        listener._claimedTouches.splice(entfernteIdx, 1);
      eventManager._clearCurTouch();
    }
  }

  //_ Wenn das Ereignis gestoppt wurde, kehren Sie direkt zurück.
  // Wenn das Ereignis gestoppt wurde, direkt zurückkehren (stopPropagationImmediate() für das Ereignis aufrufen usw.)
  wenn (event.isStopped()) {
    eventManager._updateTouchListeners(Ereignis);
    gibt true zurück;
  }

  // Wenn das Ereignis beansprucht wird und der Listener das Ereignis frisst (x) (es muss nicht weitergegeben werden, der Standardwert ist „false“, aber es ist „true“ in den Touch-Serienereignissen von Node)
  wenn (isClaimed && listener.swallowTouches) {
    wenn (argsObj.needsMutableSet)
      argsObj.touches.splice(selTouch, 1);
    gibt true zurück;
  }
  gibt false zurück;
},

Die Hauptfunktion besteht darin, Ereignisse zu verteilen und Kompatibilitätsverarbeitung für mehrere Berührungspunkte durchzuführen. Wichtig ist der Rückgabewert. Wenn das Event vom Listener beansprucht wird, wird true zurückgegeben, um die Weitergabe des Events zu verhindern.
Beim Verteilen von Ereignissen wird am Beispiel des Touch-Start-Ereignisses die Methode onTouchBegan des Listeners aufgerufen. Das ist seltsam, wird es nicht an die Knoten verteilt? Warum Listener anrufen? Was ist ein Monitor? Dazu müssen wir untersuchen, wo das Ereignis registriert wird, wenn wir die Funktion „On“ aufrufen, um ein Ereignis auf einem Knoten zu registrieren.

Wo wird die Veranstaltung angemeldet?

Für die auf dem Node aufgerufene On-Funktion liegt der entsprechende Code natürlich im CCNode. Schauen wir uns an, was die On-Funktion macht.

/**
* Registrieren Sie eine Rückruffunktion des angegebenen Typs auf dem Knoten * @param {*} Typ Ereignistyp * @param {*} Rückruf Rückruffunktion * @param {*} Ziel Ziel (wird zum Binden verwendet)
* @param {*} useCapture in der Erfassungsphase registriert */
on (Typ, Rückruf, Ziel, useCapture) {
  // Handelt es sich um ein Systemevent (Maus, Berührung)?
  let forDispatch = this._checknSetupSysEvent(Typ);
  if (fürVersand) {
    //Ereignis registrieren return this._onDispatch(type, callback, target, useCapture);
  }
  // Nicht-Systemereignisse weglassen, einschließlich Positionsänderungen, Größenänderungen usw.
},

Die offiziellen Kommentare sind ziemlich lang, deshalb werde ich eine vereinfachte Version schreiben. Kurz gesagt wird es verwendet, um eine Rückruffunktion für ein Ereignis zu registrieren.
Sie denken vielleicht: So wenig Inhalt? ? ? Allerdings gibt es hier zwei Zweige, einer ruft die Funktion _checknSetupSysEvent auf, der andere die Funktion _onDispatch, und der Code steht vollständig in 555.
Die registrierungsbezogene Funktion ist die _onDispatch-Funktion, die andere wird später besprochen.

// Verteilung registrieren event_onDispatch (Typ, Rückruf, Ziel, useCapture) {
  //_ Akzeptiere auch Parameter wie: (Typ, Rückruf, UseCapture)
  // Sie können auch Parameter wie diese erhalten: (Typ, Rückruf, UseCapture)
  // Parameterkompatibilitätsverarbeitung if (typeof target === 'boolean') {
    useCapture = Ziel;
    Ziel = undefiniert;
  }
  sonst useCapture = !!useCapture;
  // Wenn keine Rückruffunktion vorhanden ist, melde einen Fehler und kehre zurück.
  wenn (!Rückruf) {
    Fehlercode 6800.
    zurückkehren;
  }

  // Holen Sie sich unterschiedliche Listener basierend auf useCapture.
  var listeners = null;
  wenn (useCapture) {
    Listener = this._capturingListeners = this._capturingListeners || neues EventTarget();
  }
  anders {
    Listener = this._bubblingListeners = this._bubblingListeners || neues EventTarget();
  }

  // Wenn das gleiche Callback-Ereignis registriert wurde, wird keine Verarbeitung durchgeführt, wenn ( !listeners.hasEventListener(type, callback, target) ) {
    // Ereignisse bei Listenern registrieren listeners.on(type, callback, target);

    // Speichern Sie dies im __eventTargets-Array des Ziels, das verwendet wird, um die Funktion targetOff vom Ziel aus aufzurufen, um den Listener zu löschen.
    wenn (Ziel && Ziel.__eventTargets) {
      target.__eventTargets.push(dies);
    }
  }

  Rückruf zurückgeben;
},

Der Knoten enthält zwei Listener, einen _capturingListeners und einen _bubblingListeners. Was ist der Unterschied? Ersteres wird in der Erfassungsphase registriert, letzteres in der Blasenphase. Die spezifischeren Unterschiede werden später erläutert.
Aus listeners.on(type, callback, target); können wir ersehen, dass das Ereignis tatsächlich in diesen beiden Listenern und nicht im Knoten registriert ist.
Dann schauen wir mal, was drin ist.

event-target.js(Ereignisziel)

//_Registrieren Sie einen bestimmten Ereignistyp-Rückruf für das Ereignisziel. Ereignisse dieser Art sollten mit „emit“ ausgegeben werden.
proto.on = Funktion (Typ, Rückruf, Ziel, einmal) {
    //Wenn keine Callback-Funktion übergeben wird, melde einen Fehler und kehre zurück
    wenn (!Rückruf) {
        Fehlercode 6800.
        zurückkehren;
    }

    // Wenn der Rückruf bereits existiert, nicht verarbeiten, if ( !this.hasEventListener(type, callback, target) ) {
        //Ereignis registrieren this.__on(type, callback, target, once);

        wenn (Ziel && Ziel.__eventTargets) {
            target.__eventTargets.push(dies);
        }
    }
    Rückruf zurückgeben;
};

Am Ende gibt es noch ein weiteres ... Aus js.extend(EventTarget, CallbacksInvoker); können wir ersehen, dass EventTarget CallbacksInvoker erbt. Lassen Sie uns noch eine Ebene tiefer einsteigen!

callbacks-invoker.js(RückrufInvoker)

//_ Ereignis hinzufügen Verwaltung proto.on = function (key, callback, target, once) {
    // Holen Sie sich die dem Ereignis entsprechende Rückrufliste let list = this._callbackTable[key];
    // Wenn es nicht existiert, hol dir eines aus dem Pool if (!list) {
        Liste = this._callbackTable[Schlüssel] = callbackListPool.get();
    }
    // Callback-bezogene Informationen speichern let info = callbackInfoPool.get();
    info.set(Rückruf, Ziel, einmal);
    Liste.callbackInfos.push(info);
};

Endlich ist es vorbei! Unter diesen sind sowohl callbackListPool als auch callbackInfoPool js.Pool-Objekte, also ein Objektpool. Die Callback-Funktionen werden letztendlich in _callbackTable gespeichert.
Nachdem wir nun wissen, wo sich der Speicher befindet, stellt sich die Frage, wie Ereignisse ausgelöst werden.

Wie wird das Event ausgelöst?

Bevor wir uns mit der Auslösung befassen, werfen wir einen Blick auf die Auslösereihenfolge. Schauen wir uns zunächst einen offiziellen Kommentar an.

Maus- oder Berührungsereignisse werden vom System ausgelöst, indem es die Methode dispatchEvent aufruft. Der Auslösevorgang besteht aus drei Phasen:
* 1. Erfassungsphase: Versenden Sie Ereignisse an das Erfassungsziel (erhalten über _getCapturingTargets ). Beispielsweise wird der übergeordnete Knoten der Erfassungsphase im Knotenbaum registriert und vom Stammknoten an den Zielknoten versendet.
* 2. Zielphase: Versand an den Listener des Zielknotens.
* 3. Bubbling-Phase: Versende Ereignisse an Bubbling-Ziele (erhalten über _getBubblingTargets ), beispielsweise wird der übergeordnete Knoten der Bubbling-Phase im Knotenbaum registriert und versendet Ereignisse vom Zielknoten an den Stammknoten.

Was bedeutet das? Der vierte Parameter der On-Funktion, useCapture, bedeutet, dass, wenn er auf „true“ gesetzt ist, das Ereignis in der Erfassungsphase registriert wird, das heißt, es kann frühestens aufgerufen werden.
Es ist wichtig zu beachten, dass die Erfassungsphase der Reihe nach von den übergeordneten Knoten zu den untergeordneten Knoten ausgelöst wird (beginnend beim Stammknoten). Anschließend werden vom Knoten selbst registrierte Ereignisse ausgelöst. Schließlich tritt es in die Sprudelphase ein und übergibt das Ereignis vom übergeordneten Knoten an den Stammknoten.
Einfaches Verständnis: Die Aufnahmephase verläuft von oben nach unten, dann sie selbst und schließlich die Sprudelphase von unten nach oben.
Die Theorie ist vielleicht etwas steif, aber Sie werden es verstehen, wenn Sie sich den Code ansehen!
Erinnern Sie sich an die Funktion _checknSetupSysEvent? Der vorherige Kommentar prüft nur, ob es sich um ein Systemereignis handelt, aber tatsächlich tut er mehr als das.

// Prüfen ob es ein Systemereignis ist_checknSetupSysEvent (Typ) {
  // Müssen Sie einen neuen Listener hinzufügen? let newAdded = false;
  // Ob eine Verteilung erforderlich ist (wird für System-Ereignisse benötigt)
  let fürDispatch = false;
  // Wenn das Ereignis ein Touch-Ereignis ist, if (_touchEvents.indexOf(type) !== -1) {
    // Wenn kein Touch-Event-Listener vorhanden ist, erstellen Sie einen neuen if (!this._touchListener) {
      this._touchListener = cc.EventListener.create({
        Ereignis: cc.EventListener.TOUCH_ONE_BY_ONE,
        SchwalbenBerührungen: wahr,
        Besitzer: dieser,
        Maske: _searchComponentsInParent(diese, cc.Maske),
        beiBeginn der Berührung: _touchStartHandler,
        beiBerührenBewegung: _touchMoveHandler,
        beiBerührenEnde: _touchEndHandler,
        bei abgebrochener Berührung: _touchCancelHandler
      });
      //Füge den Listener zum EventManager hinzu
      eventManager.addListener(dies._touchListener, dies);
      neuHinzugefügt = wahr;
    }
    fürDispatch = wahr;
  }
  // Das ausgelassene Ereignis ist der Code für das Mausereignis, das dem Touch-Ereignis ähnelt. // Wenn ein Listener hinzugefügt wird und der aktuelle Knoten nicht aktiv ist, if (newAdded && !this._activeInHierarchy) {
    // Wenn der Knoten nach einer Weile immer noch nicht aktiv ist, unterbrechen Sie die Ereignisübermittlung des Knotens.
    cc.director.getScheduler().schedule(Funktion () {
      wenn (!this._activeInHierarchy) {
        eventManager.pauseTarget(dieses);
      }
    }, dies, 0, 0, 0, falsch);
  }
  Rücksendung zum Versand;
},

Was ist der Sinn? In der Zeile eventManager.addListener(this._touchListener, this); Wie Sie sehen, enthält jeder Knoten einen _touchListener und fügt ihn dem eventManager hinzu. Kommt es Ihnen bekannt vor? Hey, hat der EventManager das nicht gerade beim Verteilen von Events gemacht? Ist das nicht verbunden? Obwohl der EventManager die Knoten nicht enthält, enthält er diese Listener!
Beim Erstellen eines neuen Listeners werden viele Parameter übergeben und das bekannte Touch-Start-Ereignis wird weiterhin verwendet: onTouchBegan: _touchStartHandler . Was ist das?

// Touch-Start-Ereignishandler var _touchStartHandler = function (touch, event) {
    var pos = touch.getLocation();
    var Knoten = dieser.Besitzer;

    // Wenn sich der Berührungspunkt innerhalb des Knotenbereichs befindet, wird das Ereignis ausgelöst und „true“ zurückgegeben, was bedeutet, dass ich das Ereignis ausgeführt habe!
    wenn (node._hitTest(pos, this)) {
        Ereignistyp = Ereignistyp.TOUCH_START;
        event.touch = berühren;
        Ereignisblasen = wahr;
        //An diesen Knoten verteilenode.dispatchEvent(event);
        gibt true zurück;
    }
    gibt false zurück;
};

Es ist ganz einfach. Holen Sie sich den Kontaktpunkt und bestimmen Sie, ob der Kontaktpunkt innerhalb des Knotens liegt. Wenn ja, verteilen Sie ihn!

//_ Ereignisse in den Ereignisstrom verteilen.
dispatchEvent (Ereignis) {
  _doDispatchEvent(dieses, Ereignis);
  _cachedArray.length = 0;
},
//Ereignisse verteilen Funktion _doDispatchEvent (Besitzer, Ereignis) {
    var Ziel, i;
    Ereignis.Ziel = Eigentümer;

    //_ Ereignis.CAPTURING_PHASE
    // Erfassen phase_cachedArray.length = 0;
    // Holen Sie sich die Knoten in der Erfassungsphase und speichern Sie sie in _cachedArray
    Besitzer._getCapturingTargets(Ereignis.Typ, _cachedArray);
    //_ erfassen
    Ereignis.EreignisPhase = 1;
    // Vom Ende zum Anfang durchlaufen (dh vom Stammknoten zum übergeordneten Knoten des Zielknotens)
    für (i = _cachedArray.length - 1; i >= 0; --i) {
        Ziel = _cachedArray[i];
        // Wenn der Zielknoten einen Listener für die Erfassungsphase registriert, if (target._capturingListeners) {
            event.currentTarget = Ziel;
            //_ Feuerereignis
            // Verarbeiten Sie das Ereignis auf dem Zielknoten target._capturingListeners.emit(event.type, event, _cachedArray);
            //_ prüfen, ob die Ausbreitung gestoppt wurde
            // Wenn die Übermittlung des Ereignisses gestoppt wurde, kehren Sie zurück
            wenn (event._propagationStopped) {
                _cachedArray.length = 0;
                zurückkehren;
            }
        }
    }
    // Zwischengespeichertes Array löschen
    _cachedArray.length = 0;

    //_ Ereignis.AT_TARGET
    //_ prüft, ob beim Erfassen von Rückrufen zerstört wurde
    // Die eigene Phase des Zielknotens event.eventPhase = 2;
    event.currentTarget = Eigentümer;
    // Wenn der Besitzer einen Listener für die Erfassungsphase registriert hat, verarbeite das Ereignis if (owner._capturingListeners) {
        Besitzer._capturingListeners.emit(Ereignis.Typ, Ereignis);
    }
    // Wenn das Ereignis nicht gestoppt wurde und der Propagation-Listener registriert ist, verarbeite das Ereignis, wenn (!event._propagationImmediateStopped && owner._bubblingListeners) {
        Besitzer._bubblingListeners.emit(Ereignis.Typ, Ereignis);
    }

    // Wenn das Ereignis nicht gestoppt wurde und das Ereignis weitergeleitet werden muss (Standard: „true“)
    wenn (!event._propagationStopped && event.bubbles) {
        //_ Ereignis.BUBBLING_PHASE
        // Bubbling-Phase // Holen Sie sich den Knoten in der Bubbling-Phase owner._getBubblingTargets(event.type, _cachedArray);
        //_ propagieren
        Ereignis.EreignisPhase = 3;
        // Von Anfang bis Ende durchlaufen (vom übergeordneten Knoten zum Stammknoten), die Auslöselogik entspricht der Erfassungsphase für (i = 0; i < _cachedArray.length; ++i) {
            Ziel = _cachedArray[i];
            wenn (target._bubblingListeners) {
                event.currentTarget = Ziel;
                //_ Feuerereignis
                target._bubblingListeners.emit(Ereignis.Typ, Ereignis);
                //_ prüfen, ob die Ausbreitung gestoppt wurde
                wenn (event._propagationStopped) {
                    _cachedArray.length = 0;
                    zurückkehren;
                }
            }
        }
    }
    // Zwischengespeichertes Array löschen
    _cachedArray.length = 0;
}

Ich frage mich, ob Sie nach dem Lesen dieses Artikels ein besseres Verständnis der Ereignisauslösesequenz haben?
Die Knoten in der Erfassungsphase und die Knoten in der Blasenphase werden durch andere Funktionen erhalten. Der Code in der Erfassungsphase wird als Beispiel verwendet. Die beiden sind ähnlich.

_getCapturingTargets (Typ, Array) {
  // Beginnen Sie beim übergeordneten Knoten. var parent = this.parent;
  // Wenn der übergeordnete Knoten nicht leer ist (der übergeordnete Knoten des Stammknotens ist leer)
  während (Elternteil) {
    // Wenn der Knoten einen Listener in der Erfassungsphase und ein Abhörereignis des entsprechenden Typs hat, fügen Sie den Knoten dem Array hinzu, wenn (parent._capturingListeners && parent._capturingListeners.hasEventListener(Typ)) {
      array.push(übergeordnet);
    }
    //Setze den Knoten auf seinen übergeordneten Knoten parent = parent.parent;
  }
},

Bei einer Bottom-Up-Traversierung werden die Knoten, die die Bedingungen erfüllen, auf dem Weg zum Array hinzugefügt, und dann erhalten Sie alle Knoten, die verarbeitet werden müssen!
Das scheint ein wenig vom Thema abzuweichen ... Zurück zur Ereignisverteilung von vorhin. Da sowohl der Listener in der Erfassungsphase als auch der Listener in der Sprudelphase ein EventTarget sind, nehmen wir hier die Auslösung selbst als Beispiel.
owner._bubblingListeners.emit(event.type, event);
Die obige Codezeile verteilt das Ereignis an den Bubbling-Listener des eigenen Knotens. Sehen wir uns also an, was in der Ausgabe enthalten ist.
emit ist eigentlich eine Methode in CallbacksInvoker.

Rückrufe-invoker.js

proto.emit = Funktion (Schlüssel, arg1, arg2, arg3, arg4, arg5) {
    // Ereignisliste abrufen const list = this._callbackTable[key];
    // Wenn die Ereignisliste existiert if (list) {
        // Wird das Ereignis „list.isInvoking“ ausgelöst? const rootInvoker = !list.isInvoking;
        list.isInvoking = true;

        // Rückrufliste abrufen und durchlaufen const infos = list.callbackInfos;
        für (sei i = 0, len = infos.length; i < len; ++i) {
            const info = infos[i];
            wenn (info) {
                sei Ziel = info.ziel;
                lassen Sie Rückruf = info.rückruf;
                // Wenn die Callback-Funktion mit once registriert ist, breche diese Funktion zuerst ab if (info.once) {
                    this.off(Schlüssel, Rückruf, Ziel);
                }

                // Wenn das Ziel übergeben wurde, verwenden Sie den Aufruf, um sicherzustellen, dass dieser auf das richtige if (Ziel) { zeigt.
                    Rückruf.Anruf(Ziel, arg1, arg2, arg3, arg4, arg5);
                }
                anders {
                    Rückruf(arg1, arg2, arg3, arg4, arg5);
                }
            }
        }
        // Wenn das aktuelle Ereignis nicht ausgelöst wird if (rootInvoker) {
            list.isInvoking = falsch;
            // Wenn abgebrochene Rückrufe vorhanden sind, rufen Sie die Funktion purgeCanceled auf, um die entfernten Rückrufe zu filtern und das Array zu komprimieren, wenn (list.containCanceled) {
                list.purgeCanceled();
            }
        }
    }
};

Der Kern besteht darin, basierend auf dem Ereignis eine Liste von Rückruffunktionen zu erhalten, die Anrufe zu durchlaufen und schließlich nach Bedarf ein Recycling durchzuführen. Das ist alles!

Abschluss

Fügen Sie einen interessanten Listener-Sortieralgorithmus hinzu

Im vorherigen Inhalt wurde die Funktion _sortEventListeners erwähnt, mit der die Listener entsprechend der Triggerpriorität sortiert werden. Ich finde diesen Algorithmus ziemlich interessant und möchte ihn mit Ihnen teilen.
Zuerst die Theorie. Wie der Name schon vermuten lässt, handelt es sich bei dem Knotenbaum durchaus um eine Baumstruktur. Wenn wir zufällig zwei Knoten A und B aus dem Baum auswählen, gibt es folgende Sonderfälle:

  1. A und B gehören zum selben übergeordneten Knoten
  2. A und B gehören nicht zum selben übergeordneten Knoten
  3. A ist ein übergeordneter Knoten von B (und umgekehrt)

Wenn Sie Prioritäten setzen möchten, wie sollten Sie dabei vorgehen? Lassen Sie p1 und p2 jeweils gleich AB sein. Nach oben: A = A.parent

  1. Am einfachsten ist es, _localZOrder direkt zu vergleichen
  2. Wenn A und B ihren Ursprung nach oben verfolgen, werden sie früher oder später einen gemeinsamen übergeordneten Knoten haben. Wenn wir zu diesem Zeitpunkt _localZOrder vergleichen, kann dies etwas unfair sein, da es möglicherweise einen Knoten gibt, der einen langen Weg zurückgelegt hat (höher auf der Ebene) und zuerst ausgelöst werden sollte. An dieser Stelle ergibt sich eine andere Situation: A und B liegen auf gleicher Ebene. Dann gehen p1 und p2 nach oben und erreichen denselben übergeordneten Knoten und vergleichen _localZOrder. Die Ebene von A ist höher als die von B. Wenn p den Wurzelknoten erreicht, tauschen Sie p mit einem anderen Startpunkt. Beispiel: p2 erreicht zuerst den Stammknoten. Setzen Sie p2 zu diesem Zeitpunkt an Position A und fahren Sie fort. Früher oder später werden sie die gleiche Distanz zurückgelegt haben, und an diesem Punkt ist der übergeordnete Knoten derselbe. Sortieren Sie einfach gemäß der _localZOrder von p1 p2 und nehmen Sie die Umkehrung. Denn die größeren wurden auf die andere Seite getauscht. Dieser Absatz muss überprüft werden, er ist wunderbar.
  3. Das Gleiche gilt für die Rückkehr zur Quelle, aber der Unterschied besteht darin, dass sich p1 und p2 aufgrund der Eltern-Kind-Beziehung nach dem Austausch und dem Zurücklegen derselben Entfernung schließlich am Knoten A oder B treffen! Zu diesem Zeitpunkt müssen Sie also nur beurteilen, ob es sich um A oder B handelt. Wenn es A ist, ist der Pegel von A niedriger und umgekehrt. Die Knoten, die sich treffen, haben also eine geringere Priorität.

Es sind viele Wörter geschrieben, und hier ist der Code, prägnant und mächtig!

// Algorithmus für Szenegrafik Prioritätsanhörer sortieren // Returning -1 (negative Zahl) bedeutet.
  // den Knoten holen, in dem sich der Hörer befindet, let node1 = l1._getscenegraphpriority (),
      node2 = l2._getScenegraphPriority ();

  // Wenn der Hörer 2 leer ist oder der Knoten 2 leer ist oder der Knoten 2 nicht aktiv ist oder der Knoten 2 der Stammknoten ist, hat L1 Vorrang, wenn (! L2 ||! Node2 ||!
    Rückgabe -1;
  // gleichen wie oben else if (! L1 ||! Node1 ||! Node1._activeInhierarchy || node1._parent === null)
    Rückgabe 1;

  // Verwenden Sie P1 P2, um den Knoten 1 und den Knoten 2 vorübergehend zu speichern
  // Ex: Ich denke, es bedeutet, ob ein Austausch auftritt (Austausch)
  Sei p1 = node1, p2 = node2, ex = false;
  // Wenn die übergeordneten Knoten von P1 und P2 nicht gleich sind, verfolgen Sie die Quelle, wobei (p1._parent._id! == p2._parent._id) {
    // Wenn der Großelternknoten von P1 leer ist (der übergeordnete Knoten von P1 ist der Stammknoten), wird Ex auf True und P1 auf Knoten 2 gesetzt. Ansonsten zeigt P1 auf den übergeordneten Knoten p1 = p1._parent._parent === null?
    p2 = p2._parent._parent === null?
  }

  // Wenn P1 und P2 auf denselben Knoten verweisen, dh die Knoten 1 und 2 eine Eltern-Kind-Beziehung, dh Fall 3
  if (p1._id === p2._id) {
    // Wenn P1 auf Knoten 2 zeigt, hat L1 Vorrang. Andernfalls hat L2 Vorrang if (p1._id === node2._id) 
      Rückgabe -1;
    if (p1._id === node1._id)
      Rückgabe 1;
  }

  // Hinweis: Zu diesem Zeitpunkt sind die übergeordneten Knoten von P1 und P2 gleich // Wenn Ex wahr ist, haben die Knoten 1 und 2 keine Eltern-Kind-Beziehung, dh Fall 2
  // Wenn Ex falsch ist, haben die Knoten 1 und 2 denselben übergeordneten Knoten, nämlich Fall 1
  EX?
},

Zusammenfassen

Das Spiel beginnt mit CCGame, das CcInputManager und CCEventManager für die Registrierung von Ereignissen bezeichnet. In den nachfolgenden Interaktionen ruft der Rückruf der Engine die Zuhörer in CCEventManager auf und verarbeitet dann die Ereignisse. Wenn es trifft, wird es an die in EventTarget gespeicherte Veranstaltungsliste übergeben, und die Reise ist abgeschlossen.
Das Modul ist eigentlich nicht sehr kompliziert, aber es umfasst mehrere Dateien sowie verschiedene Kompatibilität und Sicherheitsverarbeitung. Es scheint also viel zu sein.

Das obige Erläuterung ist eine detaillierte Erklärung, wie Cocoscreator -Systemereignisse generiert und ausgelöst werden.

Das könnte Sie auch interessieren:
  • CocosCreator Erste Schritte Tutorial: Netzwerkkommunikation
  • Cocos2d-x 3.x Erste Schritte Tutorial (Teil 2): ​​Node-Klasse
  • Cocos2d-x 3.x Erste Schritte Tutorial (I): Grundkonzepte
  • Cocos2d-x Erste Schritte Tutorial (ausführliche Beispiele und Erklärungen)
  • Detaillierte Erklärung zur Erstellung von Schießspielen mit CocosCreator
  • So zeichnen Sie in CocosCreator ein cooles Radardiagramm
  • Detaillierte Erklärung der CocosCreator MVC-Architektur
  • Detaillierte Erläuterung des CocosCreator-Nachrichtenverteilungsmechanismus
  • So erstellen Sie WeChat-Spiele mit CocosCreator
  • So verwenden Sie einen Gamecontroller in CocosCreator
  • Detaillierte Erklärung des digitalen Puzzles CocosCreator Huarongdao
  • CocosCreator-Tutorial für den Einstieg: Erstellen Sie Ihr erstes Spiel mit TS

<<:  Beispielcode zum Konvertieren von Videos mit der ffmpeg-Befehlszeile

>>:  Gründe und Lösungen für die fehlende Remote-Verbindung zur MySQL-Datenbank unter CentOS7

Artikel empfehlen

Eine Zusammenfassung der Gründe, warum MySQL keinen Datumsfeldindex verwendet

Inhaltsverzeichnis Hintergrund erkunden Zusammenf...

So erstellen Sie einen SVN-Server unter Linux

1: SVN installieren yum install -y Subversion 2. ...

Analyse von drei Parametern des MySQL-Replikationsproblems

Inhaltsverzeichnis 01 sql_slave_skip_counter-Para...

Beispielcode zum Erstellen eines Dropdown-Menüs mit reinem CSS

Einführung: Als ich mir in letzter Zeit die Frage...

Vite führt die Implementierung virtueller Dateien ein

Inhaltsverzeichnis Hintergrund Virtuelle Dateien ...

Mysql-Abfrageanweisung mit mehreren Bedingungen und dem Schlüsselwort „And“

MySQL-Abfrage mit mehreren Bedingungen und dem Sc...

Webdesign-Erfahrung: Effizientes Schreiben von Webcode

Ursprünglich sollte dieses siebte Kapitel eine aus...

Erläuterung der Lösung zur mobilen H5-Bildgenerierung in JavaScript

Derzeit gibt es viele Betriebsaktivitäten für öff...

JavaScript BOM-Standortobjekt + Navigatorobjekt + Verlaufsobjekt

Inhaltsverzeichnis 1. Standortobjekt 1. URL 2. Ei...

Installieren Sie Docker für Windows unter Windows 10 Home Edition

0. Hintergrund Hardware: Xiaomi Notebook Air 13/I...

Verwendung von MySQL-Triggern

Inhaltsverzeichnis 1. Trigger-Einführung 1. Was i...

Design-Tipps: Wir glauben, es wird Ihnen gefallen

<br />Wenn Sie sich diesen Titel ansehen, ko...

Zusammenfassung der MySQL-Nutzungsspezifikationen

1. Es muss die InnoDB-Speicher-Engine verwendet w...