環境ココスクリエイター2.4 まとめモジュール機能イベント監視メカニズムは、すべてのゲームに不可欠な部分である必要があります。ボタンをクリックする場合でも、オブジェクトをドラッグする場合でも、イベントの監視と配信は不可欠です。 関連文書このうち、CCGame と CCInputManager はどちらもイベントの登録に関与していますが、担当する部分は異なります。 ソースコード分析イベントはどのようにしてエンジンに届きますか (ブラウザから)? この質問に答えるには、エンジンとブラウザ間のやりとりがどこから来るのかを理解する必要があります。 CCGame.js// イベントを初期化する system_initEvents: function () { var win = window、hiddenPropName; //_ システムイベントを登録する // システムイベントを登録します。ここでは CCInputManager メソッドを呼び出します if (this.config.registerSystemEvent) _cc.inputManager.registerSystemEvent(this.canvas); // document.hidden はページが非表示であることを意味します。次の if はブラウザの互換性を処理するために使用されます if (typeof document.hidden !== 'undefined') { hiddenPropName = "隠し"; } そうでない場合 (typeof document.mozHidden !== 'undefined') { 隠しプロパティ名 = "mozHidden"; } そうでない場合 (typeof document.msHidden !== 'undefined') { 隠しプロパティ名 = "msHidden"; } そうでない場合 (typeof document.webkitHidden !== 'undefined') { 隠しプロパティ名 = "webkitHidden"; } // 現在のページは非表示ですか? var hidden = false; //ページが非表示になったときにコールバックし、game.EVENT_HIDEイベントを発行します。function onHidden() { (!隠し)の場合{ 非表示 = true; game.emit(game.EVENT_HIDE); } } //_ ほとんどのプラットフォームに適応するために、onshow API を使用します。 // ほとんどのプラットフォームの onshow API に適応します。パラメータを渡す部分を参照する必要があります... // ページが表示されているときにコールバックし、game.EVENT_SHOW イベントを発行します。 function onShown (arg0, arg1, arg2, arg3, arg4) { if (隠し) { 非表示 = false; game.emit(game.EVENT_SHOW, arg0, arg1, arg2, arg3, arg4); } } // ブラウザが隠しプロパティをサポートしている場合は、ページの視覚状態変更イベントを登録します。if (hiddenPropName) { var チェンジリスト = [ 「可視性の変更」、 「mozvisibilitychange」、 "msvisibilitychange", 「webkitvisibilitychange」、 「qbrowserVisibilityChange」 ]; // 互換性のために上記のリストのイベントをループします // 非表示状態が変更された後、表示状態に基づいて onHidden/onShown コールバック関数を呼び出します for (var i = 0; i < changeList.length; i++) { document.addEventListener(changeList[i], 関数(イベント) { var visible = document[hiddenPropName]; //_QQアプリ visible = 表示 || イベント["hidden"]; (表示)の場合 非表示(); それ以外 表示中(); }); } } // ページの視覚状態の変更に関する一部の互換性コードはここでは省略されています // 非表示および表示イベントを登録し、ゲームのメインロジックを一時停止または再開します。 this.on(game.EVENT_HIDE, 関数() { ゲームを一時停止します。 }); this.on(game.EVENT_SHOW, 関数() { ゲームを再開します。 }); } 実はコアコードはほんの少ししかありません…さまざまなプラットフォームとの互換性を保つために、
CCInputManager を見てみましょう。 CCInputManager.js// システムイベントを登録する要素はキャンバスです registerSystemEvent (要素) { if(this._isRegisterEvent) 戻り値: // すでに登録済みなので直接戻ります this._glView = cc.view; selfPointer を this とします。 キャンバスバウンディングレクトを this._canvasBoundingRect とします。 // サイズ変更イベントをリッスンし、this._canvasBoundingRect を変更します window.addEventListener('resize', this._updateCanvasBoundingRect.bind(this)); 禁止 = sys.isMobile とします。 supportMouse = ('mouse' in sys.capabilities); とします。 // タッチレットをサポートするかどうか supportTouches = ('touches' in sys.capabilities); // マウスイベントの登録コードを省略 //_タッチイベントを登録 // タッチイベントを登録する if (supportTouches) { // イベントマップ _touchEventsMap = { を設定します "touchstart": 関数 (touchesToHandle) { selfPointer.handleTouchesBegin(touchesToHandle); 要素にフォーカスを当てる(); }, "touchmove": 関数 (touchesToHandle) { selfPointer.handleTouchesMove(touchesToHandle); }, "touchend": 関数 (touchesToHandle) { selfPointer.handleTouchesEnd(touchesToHandle); }, "touchcancel": 関数 (touchesToHandle) { selfPointer.handleTouchesCancel(touchesToHandle); } }; // マップをトラバースしてイベントを登録する let registerTouchEvent = function (eventName) { ハンドラーを_touchEventsMap[イベント名]に設定します。 // キャンバスにイベントを登録する element.addEventListener(eventName, (function(event) { if (!event.changedTouches) が return; 本文を document.body とします。 // オフセットを計算します canvasBoundingRect.adjustedLeft = canvasBoundingRect.left - (body.scrollLeft || window.scrollX || 0); canvasBoundingRect.adjustedTop = canvasBoundingRect.top - (body.scrollTop || window.scrollY || 0); // イベントからタッチ ポイントを取得し、コールバック関数を呼び出します。handler(selfPointer.getTouchesByEvent(event, canvasBoundingRect)); // イベントのバブリングを停止します。event.stopPropagation(); イベントをデフォルトにしない(); })、 間違い); }; for (let eventName in _touchEventsMap) { タッチイベントを登録します(イベント名); } } // イベント登録が完了したことを示すプロパティを変更します。this._isRegisterEvent = true; } コードでは、主に touchstart などの一連のネイティブ イベントを登録します。イベント コールバックでは、selfPointer(=this) 内の関数が呼び出されて処理されます。ここでは、touchstart イベント、つまり handleTouchesBegin 関数を例として使用します。 // タッチスタートイベントを処理する handleTouchesBegin (touches) { selTouch、index、curTouch、touchID、 handleTouches = []、locTouchIntDict = this._touchesIntegerDict、 今 = sys.now(); // タッチポイントをトラバースする for (let i = 0, len = touches.length; i < len; i ++) { // 現在のタッチポイント selTouch = touches[i]; // タッチポイントID タッチID = selTouch.getID(); // タッチポイントリスト内のタッチポイントの位置 (this._touches) index = locTouchIntDict[touchID]; // インデックスが取得されない場合は、新しいタッチポイント(押されたばかり)であることを意味します インデックスが null の場合 // 未使用のインデックスを取得する unusedIndex を this._getUnUsedIndex() とします。 // 取得できません。エラーが発生します。サポートされているタッチポイントの最大数を超えている可能性があります。 未使用インデックス === -1 の場合 { cc.logID(2300、未使用インデックス); 続く; } //_curTouch = this._touches[未使用インデックス] = selTouch; //タッチポイントを保存します。 curTouch = this._touches[unusedIndex] = new cc.Touch(selTouch._point.x, selTouch._point.y, selTouch.getID()); curTouch._lastModified = 現在; curTouch._setPrevPoint(selTouch._prevPoint); locTouchIntDict[タッチID] = 未使用インデックス; // 処理する必要があるタッチポイントのリストに追加します。handleTouches.push(curTouch); } } // 新しい接触があった場合は、タッチイベントを生成し、それをeventManagerに配布します ハンドルタッチの長さが0より大きい場合 // このメソッドは、スケールに応じてタッチ ポイントの位置を処理します。this._glView._convertTouchesWithScale(handleTouches); touchEvent を new cc.Event.EventTouch(handleTouches); に設定します。 touchEvent._eventCode = cc.Event.EventTouch.BEGAN; イベントマネージャ。イベントをディスパッチします。 } }, この関数では、コードの一部は新しいタッチポイントがあるかどうかをフィルタリングするために使用され、他の部分はイベントを処理および配布するために使用されます (必要な場合)。 イベントはどのようにしてエンジンからノードに届くのでしょうか?イベントをノードに渡す作業は、主に CCEventManager クラスで行われます。ストレージ イベント リスナー、配布イベントなどが含まれます。エントリ ポイントとして _dispatchTouchEvent から始めましょう。 CCイベントマネージャ.js// イベントのディスパッチ_dispatchTouchEvent: function (event) { // タッチ リスナーを並べ替えます // TOUCH_ONE_BY_ONE: タッチ イベント リスナー タイプ。タッチ ポイントは 1 つずつディスパッチされます // TOUCH_ALL_AT_ONCE: タッチ ポイントは一度にすべてディスパッチされます this._sortEventListeners(ListenerID.TOUCH_ONE_BY_ONE); this._sortEventListeners(リスナーID.TOUCH_ALL_AT_ONCE); // リスナーリストを取得します。 var oneByOneListeners = this._getListeners(ListenerID.TOUCH_ONE_BY_ONE); var allAtOnceListeners = this._getListeners(ListenerID.TOUCH_ALL_AT_ONCE); //_ タッチリスナーが存在しない場合は、直接戻ります。 // リスナーがいない場合は、そのまま戻ります。 if (null === oneByOneListeners && null === allAtOnceListeners) 戻る; // 変数を保存します var originalTouches = event.getTouches(), mutableTouches = cc.js.array.copy(originalTouches); var oneByOneArgsObj = {event: event、needsMutableSet: (oneByOneListeners && allAtOnceListeners)、touches: mutableTouches、selTouch: null}; // //_ 最初にターゲットハンドラを処理する // 反転しません。まず、シングルタッチイベントを処理する感じです。 if (oneByOneListeners) { // コンタクトを走査し、順番に分配します for (var i = 0; i < originalTouches.length; i++) { イベントの現在のタッチ = 元のタッチ[i]; イベント._propagationStopped = イベント._propagationImmediateStopped = false; this._dispatchEventToListeners(oneByOneListeners、this._onTouchEventCallback、oneByOneArgsObj); } } // //_ 標準ハンドラを2番目に処理する // 反転しません。 2番目はマルチタッチイベントを処理する(一度にディスパッチする)ことのようです if (allAtOnceListeners && mutableTouches.length > 0) { this._dispatchEventToListeners(allAtOnceListeners、this._onTouchesEventCallback、{イベント: event、タッチ: mutableTouches}); イベントが停止した場合 戻る; } // タッチ リスナー リストを更新します (主にリスナーの削除と追加) this._updateTouchListeners(event); }, この関数で行われる主な処理は、ソート、登録されたリスナー リストへの配信、リスナー リストの更新です。特に何もないです。なぜ突然このような命令が下されるのかと不思議に思うかもしれません。ああ、これが一番大事なことだよ!ソートの役割については、タッチイベントの送信に関する公式ドキュメントを参照してください。このソートにより、異なるレベル/異なる zIndex のノード間の接触ポイントの帰属問題が実現されます。ソートについては後述しますが、すごいです。 /** * リスナーリストにイベントを配布します * @param {*} listeners リスナーリスト * @param {*} onEvent イベントコールバック * @param {*} eventOrArgs イベント/パラメータ */ _dispatchEventToListeners: 関数 (リスナー、onEvent、eventOrArgs) { // 配布を停止する必要がありますか? var shouldStopPropagation = false; // 固定優先度のリスナー(システムイベント)を取得します var fixedPriorityListeners = listeners.getFixedPriorityListeners(); // シーングラフの優先度を持つリスナーを取得します (通常、追加するリスナーはここにあります) var sceneGraphPriorityListeners = listeners.getSceneGraphPriorityListeners(); /** * リスナーのトリガー順序: * 固定優先度レベル < 0 * シーングラフの優先度 * 固定優先度 > 0 */ var i = 0, j, selListener; if (fixedPriorityListeners) { //_ 優先度 < 0 固定優先度リスナーの長さが 0 の場合 // リスナーをトラバースしてイベントを配布します for (; i < listeners.gt0Index; ++i) { selListener = 固定優先度リスナー[i]; // リスナーがアクティブ化され、一時停止されておらず、イベント マネージャーに登録されている場合 // 最後の onEvent は、_onTouchEventCallback 関数を使用してリスナーにイベントを配布します // onEvent は、後続のリスナーにイベントを配布し続ける必要があるかどうかを示すブール値を返します。true の場合、配布を停止します if (selListener.isEnabled() && !selListener._isPaused() && selListener._isRegistered() && onEvent(selListener, eventOrArgs)) { 伝播を停止する必要があります = true; 壊す; } } } } //他の2つの優先度のトリガーコードを省略します}, 関数内では、リスナーリストをトラバースしてイベントを 1 つずつ配信し、onEvent の戻り値に基づいて配信を継続するかどうかを決定します。通常、タッチ イベントはノードによって受信されると、ディスパッチされなくなります。次に、このノードからバブリング配布のロジックが実行されます。これも重要なポイントで、タッチ イベントに応答するのは 1 つのノードだけです。ノードの優先順位については、前述のソート アルゴリズムに従います。 // タッチイベントのコールバック。イベントを listeners_onTouchEventCallback に配布します: function (listener, argsObj) { //_ リスナーが削除された場合はスキップします。 // リスナーが削除されている場合はスキップします。 if (!listener._isRegistered()) false を返します。 var event = argsObj.event、selTouch = event.currentTouch; イベント.currentTarget = リスナー._node; // isClaimed: イベントを請求するかどうかのリスナー var isClaimed = false, removedIdx; var getCode = event.getEventCode()、EventTouch = cc.Event.EventTouch; // イベントがタッチ開始イベントの場合 if (getCode === EventTouch.BEGAN) { // マルチタッチがサポートされておらず、タッチポイントがすでに存在する場合 if (!cc.macro.ENABLE_MULTI_TOUCH && eventManager._currentTouch) { // タッチ ポイントがノードによって要求され、そのノードがノード ツリー内でアクティブな場合、イベントは処理されません。let node = eventManager._currentTouchListener._node; if (ノード && node.activeInHierarchy) { false を返します。 } } // リスナーに対応するイベントがある場合 if (listener.onTouchBegan) { // リスナーへの配信を試み、リスナーがイベントを要求したかどうかを示すブール値を返します isClaimed = listener.onTouchBegan(selTouch, event); // イベントが要求され、リスナーが登録されている場合、いくつかのデータを保存します if (isClaimed && listener._registered) { listener._claimedTouches.push(selTouch); eventManager._currentTouchListener = リスナー; イベントマネージャの_currentTouch = selTouch; } } } // リスナーがすでにタッチを要求しており、現在のタッチが現在のリスナーによって要求されている場合、else if (listener._claimedTouches.length > 0 && ((removedIdx = listener._claimedTouches.indexOf(selTouch)) !== -1)) { // 直接持ち帰ります isClaimed = true; // マルチタッチがサポートされておらず、タッチポイントがあり、それが現在のタッチポイントでない場合は、イベントを処理しません。if (!cc.macro.ENABLE_MULTI_TOUCH && eventManager._currentTouch && eventManager._currentTouch !== selTouch) { false を返します。 } // イベントをリスナーに配布します // ENDED または CANCELED の場合、リスナーとイベント マネージャーの連絡先をクリーンアップする必要があります if (getCode === EventTouch.MOVED && listener.onTouchMoved) { リスナー.onTouchMoved(selTouch、イベント); } そうでない場合 (getCode === EventTouch.ENDED) { (リスナーがタッチ終了した場合) リスナーの onTouchEnded(selTouch、イベント); if (リスナー._登録済み) リスナー._claimedTouches.splice(削除されたIdx、1); イベントマネージャ。 } そうでない場合 (getCode === EventTouch.CANCELED) { (リスナー.onTouchCancelled) の場合 リスナーの onTouchCancelled(selTouch、イベント); if (リスナー._登録済み) リスナー._claimedTouches.splice(削除されたIdx、1); イベントマネージャ。 } } //_ イベントが停止された場合は直接戻ります。 // イベントが停止された場合は直接戻ります (イベントで stopPropagationImmediate() を呼び出すなど) イベントが停止した場合 イベントマネージャでイベントを更新します。 true を返します。 } // イベントが要求され、リスナーがイベント (x) を消費する場合 (渡す必要はありません。デフォルトは false ですが、Node のタッチ シリーズ イベントでは true になります) if (isClaimed && listener.swallowTouches) { (argsObj.needsMutableSet) の場合 argsObj.touches.splice(selTouch, 1); true を返します。 } false を返します。 }, 主な機能は、複数のタッチポイントに対してイベントを配信し、互換性処理を行うことです。重要なのは戻り値です。リスナーによってイベントが要求されると、イベントが渡されないように true が返されます。 イベントはどこに登録されていますか?ノード上で呼び出される on 関数の場合、関連するコードは当然 CCNode にあります。 on 関数が何をするのか見てみましょう。 /** * 指定されたタイプのコールバック関数をノードに登録します * @param {*} type イベントタイプ * @param {*} callback コールバック関数 * @param {*} target ターゲット(これをバインドするために使用) * @param {*} キャプチャフェーズで登録された useCapture */ on (タイプ、コールバック、ターゲット、useCapture) { // システムイベント(マウス、タッチ)ですか? forDispatch = this._checknSetupSysEvent(type); とします。 if (forDispatch) { //イベントを登録します。 return this._onDispatch(type, callback, target, useCapture); } // 位置の変更、サイズの変更など、システム以外のイベントを省略します。 }, 公式コメントはかなり長いので、簡略版を書きます。つまり、イベントのコールバック関数を登録するために使用されます。 // 配布を登録する event_onDispatch (type, callback, target, useCapture) { //_ (type、callback、useCapture) のようなパラメータも受け入れます // 次のようなパラメータも受け取ることができます: (type, callback, useCapture) // パラメータ互換性処理 if (typeof target === 'boolean') { useCapture = ターゲット; ターゲット = 未定義; } それ以外の場合は、useCapture = !!useCapture; // コールバック関数がない場合は、エラーを報告して戻ります。 if (!コールバック) { cc.エラーID(6800); 戻る; } // useCapture に基づいて異なるリスナーを取得します。 var リスナー = null; キャプチャを使用する場合 リスナー = this._capturingListeners = this._capturingListeners || 新しい EventTarget(); } それ以外 { リスナー = this._bubblingListeners = this._bubblingListeners || 新しい EventTarget(); } // 同じコールバックイベントが登録されている場合、処理は実行されません if ( !listeners.hasEventListener(type, callback, target) ) { // イベントをリスナーに登録します listeners.on(type, callback, target); // これをターゲットの __eventTargets 配列に保存します。これは、ターゲットから targetOff 関数を呼び出してリスナーをクリアするために使用されます。 if (ターゲット && target.__eventTargets) { ターゲット.__eventTargets.push(これを); } } コールバックを返します。 }, ノードには 2 つのリスナーが保持されます。1 つは _capturingListeners、もう 1 つは _bubblingListeners です。違いは何でしょうか?前者はキャプチャフェーズで登録され、後者はバブリングフェーズで登録されます。より具体的な違いについては後で説明します。 イベントターゲット//_イベント ターゲットの特定のイベント タイプのコールバックを登録します。このタイプのイベントは `emit` を使用して発行する必要があります。 proto.on = 関数 (型、コールバック、ターゲット、1回) { // コールバック関数が渡されない場合はエラーを報告して戻ります if (!コールバック) { cc.エラーID(6800); 戻る; } // コールバックがすでに存在する場合は処理しない if ( !this.hasEventListener(type, callback, target) ) { //イベントを登録します this.__on(type, callback, target, once); if (ターゲット && target.__eventTargets) { ターゲット.__eventTargets.push(これを); } } コールバックを返します。 }; 最後に、もう 1 つあります... callbacks-invoker.js(コールバックインボーカー)//_ イベント追加管理 proto.on = function (key, callback, target, once) { // イベントに対応するコールバックリストを取得します。let list = this._callbackTable[key]; // 存在しない場合はプールから取得します if (!list) { リスト = this._callbackTable[キー] = callbackListPool.get(); } // コールバック関連の情報を保存します。let info = callbackInfoPool.get(); info.set(コールバック、ターゲット、1回); リスト.callbackInfos.push(情報); }; ついに終わりました!このうち、callbackListPool と callbackInfoPool はどちらも js.Pool オブジェクトであり、オブジェクト プールです。コールバック関数は最終的に _callbackTable に保存されます。 イベントはどのようにトリガーされますか?トリガーを理解する前に、トリガーの順序を見てみましょう。まずは公式コメントを見てみましょう。
それはどういう意味ですか? on 関数の 4 番目のパラメータ useCapture が true の場合、イベントはキャプチャ フェーズに登録され、最も早く呼び出すことができます。 // システムイベントかどうかをチェックする_checknSetupSysEvent (type) { // 新しいリスナーを追加する必要がありますか? let newAdded = false; // 配布が必要かどうか(システムイベントに必要) forDispatch = false とします。 // イベントがタッチイベントの場合 if (_touchEvents.indexOf(type) !== -1) { // タッチイベントリスナーがない場合は、新しいリスナーを作成します。if (!this._touchListener) { this._touchListener = cc.EventListener.create({ イベント: cc.EventListener.TOUCH_ONE_BY_ONE、 ツバメタッチ: true、 オーナー: これ、 マスク: _searchComponentsInParent(this, cc.Mask), onTouchBegan: _touchStartHandler、 タッチ移動時: _touchMoveHandler、 onTouchEnded: _touchEndHandler、 タッチキャンセル時: _touchCancelHandler }); // イベントマネージャにリスナーを追加する イベントマネージャにリスナーを追加します(this._touchListener、this); 新しく追加されました = true; } ディスパッチ = true; } // 省略されたイベントはマウス イベントのコードで、タッチ イベントに似ています // リスナーが追加され、現在のノードがアクティブでない場合 if (newAdded && !this._activeInHierarchy) { // しばらく経ってもノードがまだアクティブでない場合は、ノードのイベント配信を一時停止します。 cc.director.getScheduler().schedule(関数() { if (!this._activeInHierarchy) { イベントマネージャを一時停止します。 } }, this, 0, 0, 0, false); } ディスパッチを返します。 }, 何がポイントですか? // タッチ開始イベントハンドラー var _touchStartHandler = function (touch, event) { var pos = touch.getLocation(); var ノード = this.owner; // タッチ ポイントがノード範囲内にある場合、イベントがトリガーされ、イベントを取得したことを示す true が返されます。 (node._hitTest(pos、this))の場合{ イベントタイプ = EventType.TOUCH_START; イベントをタッチします。 イベントのバブルは true です。 //このノードに配布しますnode.dispatchEvent(event); true を返します。 } false を返します。 }; とても簡単です。接触点を取得し、接触点がノード内に収まるかどうかを判断します。収まる場合は、配布します。 //_ イベントをイベント ストリームに配信します。 ディスパッチイベント(イベント){ _doDispatchEvent(これ、イベント); _cachedArray.length = 0; }, //イベントを配布する function _doDispatchEvent (owner, event) { var ターゲット、i; イベントターゲット = 所有者; //_ イベント.CAPTURING_PHASE // キャプチャ phase_cachedArray.length = 0; // キャプチャフェーズでノードを取得し、_cachedArray に保存します 所有者._getCapturingTargets(イベントタイプ、_cachedArray); //_ キャプチャ イベント.イベントフェーズ = 1; // 最後から最初まで(つまり、ルートノードからターゲットノードの親ノードまで)トラバースします (i = _cachedArray.length - 1; i >= 0; --i) { ターゲット = _cachedArray[i]; // ターゲットノードがキャプチャフェーズのリスナーを登録する場合 if (target._capturingListeners) { イベント.currentTarget = ターゲット; //_ 火災イベント // ターゲット ノードでイベントを処理します target._capturingListeners.emit(event.type, event, _cachedArray); //_ 伝播が停止したかどうかをチェックする // イベントの配信が停止した場合は、 if (event._propagationStopped) { _cachedArray.length = 0; 戻る; } } } // Clear_cachedArray _cachedArray.length = 0; //_ イベント.AT_TARGET //_ キャプチャコールバックで破棄されたかどうかをチェックします // ターゲットノード自身のフェーズ event.eventPhase = 2; イベント.currentTarget = 所有者; // オーナーがキャプチャフェーズのリスナーを登録している場合は、イベントを処理します。if (owner._capturingListeners) { 所有者._capturingListeners.emit(イベント.type、イベント); } // イベントが停止されておらず、伝播リスナーが登録されている場合は、イベントを処理します if (!event._propagationImmediateStopped && owner._bubblingListeners) { owner._bubblingListeners.emit(イベントタイプ、イベント); } // イベントが停止されておらず、イベントをバブルする必要がある場合(デフォルトは true) if (!event._propagationStopped && event.bubbles) { //_ イベント.BUBBLING_PHASE // バブリング ステージ // バブリング ステージ内のノードを取得します。owner._getBubblingTargets(event.type, _cachedArray); //_ 伝播する イベントフェーズ = 3; // 最初から最後まで(親ノードからルートノードまで)トラバースし、トリガーロジックはキャプチャフェーズと一致します for (i = 0; i < _cachedArray.length; ++i) { ターゲット = _cachedArray[i]; ターゲット_bubblingListenersの場合{ イベント.currentTarget = ターゲット; //_ 火災イベント target._bubblingListeners.emit(イベントタイプ、イベント); //_ 伝播が停止したかどうかをチェックする if (event._propagationStopped) { _cachedArray.length = 0; 戻る; } } } } // Clear_cachedArray _cachedArray.length = 0; } これを読んで、イベントのトリガーシーケンスについて理解が深まったでしょうか? _getCapturingTargets (型、配列) { // 親ノードから開始します var parent = this.parent; // 親ノードが空でない場合(ルートノードの親ノードが空の場合) (親){ // ノードにキャプチャフェーズのリスナーと対応するタイプのリスニングイベントがある場合は、ノードを配列に追加します。if (parent._capturingListeners && parent._capturingListeners.hasEventListener(type)) { 配列をプッシュします(親); } // ノードを親ノードに設定します。parent = parent.parent; } }, ボトムアップのトラバーサルでは、途中で条件を満たすノードが配列に追加され、処理する必要があるすべてのノードが取得されます。 コールバック呼び出し元.jsproto.emit = 関数 (キー、引数1、引数2、引数3、引数4、引数5) { // イベントリストを取得します。const list = this._callbackTable[key]; // イベントリストが存在する場合 if (list) { // list.isInvoking イベントがトリガーされているかどうか const rootInvoker = !list.isInvoking; リストを呼び出し中 = true; // コールバック リストを取得してトラバースします。const infos = list.callbackInfos; (i = 0、len = infos.length、i < len、++i とします) { 定数info = infos[i]; if (情報) { ターゲットを info.target とします。 コールバックを info.callback とします。 // コールバック関数がonceに登録されている場合は、まずこの関数をキャンセルします。if (info.once) { this.off(キー、コールバック、ターゲット); } // ターゲットが渡された場合は、call を使用して、これが正しいターゲットを指していることを確認します。if (target) { callback.call(ターゲット、arg1、arg2、arg3、arg4、arg5); } それ以外 { コールバック(arg1、arg2、arg3、arg4、arg5); } } } // 現在のイベントがトリガーされていない場合 if (rootInvoker) { リスト.isInvoking = false; // キャンセルされたコールバックがある場合は、purgeCanceled関数を呼び出して削除されたコールバックをフィルタリングし、配列を圧縮します。if (list.containCanceled) { リストを消去キャンセルしました(); } } } }; 中心となるのは、イベントに基づいてコールバック関数のリストを取得し、呼び出しを走査し、最後に必要に応じてリサイクルを実行することです。それだけです! 結論興味深いリスナーソートアルゴリズムを追加する前回のコンテンツでは、トリガーの優先順位に従ってリスナーをソートするために使用される _sortEventListeners 関数について説明しました。このアルゴリズムは非常に興味深いと思うので、皆さんと共有したいと思います。
優先順位をつけたい場合、どのようにすればよいでしょうか? p1 と p2 をそれぞれ AB と等しくします。上へ進む: A = A.parent
書かれた言葉がたくさんあり、ここに簡潔で強力なコードがあります! //シーングラフのアルゴリズムのソート優先リスナー// -1(負の数)を返すことはL1がL2より優先されることを意味し、正の数を返すことは逆を意味します。 //リスナーが配置されているノードを取得しますnode1 = l1._getscenegrappriority()、 node2 = l2._getscenegraphpriority(); //リスナー2が空の場合、またはノード2が空またはノード2がアクティブでない場合、またはノード2がルートノードです。L1は優先されます -1 を返します。 //上記と同じelse(!l1 ||!node1 ||!node1._activeinhierarchy || node1._parent === null) 1 を返します。 // P1 P2を使用してノード1とノード2を一時的に保存します //例:私はそれが交換が発生するかどうかを意味すると思います(交換) p1 = node1、p2 = node2、ex = falseとします。 // p1とp2の親ノードが等しくない場合、while(p1._parent._id!== p2._parent._id){p1._parent._Id!== // P1の祖父母ノードが空(P1の親ノードがルートノード)の場合、EXはTRUEに設定され、P1ポイントはノード2に設定されます。それ以外の場合、P1は親ノードP1 = P1._Parent._Parent === nullを指します。 p2 = p2._parent._parent === null?&& node1:p2._parent; } // P1とP2が同じノードを指している場合、つまり、ノード1と2には親子関係があります。つまり、ケース3 if(p1._id === p2._id){ // P1がノード2を指している場合、L1が優先されます。それ以外の場合は、L2が優先されます(p1._id === node2._id) -1 を返します。 if(p1._id === node1._id) 1 を返します。 } //注:この時点で、P1とP2の親ノードは同じです// EXが真である場合、ノード1と2には親子関係、つまりケース2にはありません。 // exがfalseの場合、ノード1と2には同じ親ノードがあります。これはケース1です exを返しますか? }, 要約するゲームはCCGAMEから始まり、CCINPUTMANAGERとCCEVENTMANAGERを呼び出してイベントを登録します。その後のインタラクションでは、エンジンのコールバックがCCEVentManagerのリスナーに呼び出し、CCNodeがイベントを処理します。ヒットすると、EventTargetに保存されているイベントリストに渡され、旅が完了します。 上記は、CocosCreatorシステムイベントの詳細については、CocosCreatorシステムイベントの詳細については、123WordPress.comの他の関連記事に注意してください。 以下もご興味があるかもしれません:
|
<<: ffmpeg コマンドラインを使用してビデオを変換するためのサンプルコード
>>: CentOS7 で MySQL データベースにリモート接続できない理由と解決策
<br />関連記事: Web スキル: 複数の IE バージョンを共存させるソリューシ...
フレックス コンテナーを作成するには、要素に display: flex プロパティを追加するだけで...
目次A. SpringbootプロジェクトのDockerデプロイメント1. Springbootプロ...
1. mysqldump コマンドを使用してデータベースをエクスポートします (このコマンドのパスで...
1.構文TIMESTAMPDIFF(unit,begin,end); 単位に従って時間差を返します。...
この記事では、シンプルなカレンダー効果を実現するためのJavaScriptの具体的なコードを参考まで...
目次JavaScript オブジェクト1. 定義2. オブジェクトの分類3. オブジェクトを定義する...
CSS 属性セレクターは素晴らしいです。大量のクラス名を追加することを回避し、コード内の問題を指摘す...
序文ロックの範囲に応じて、MySQL のロックは、グローバル ロック、テーブル ロック、行ロックに大...
ダウンロード: http://dev.mysql.com/downloads/mysql/ Cドライ...
コードをコピーコードは次のとおりです。 <html> <ヘッド> <m...
Awk は、ソートを含む他の一般的なユーティリティによって実行できるいくつかのタスクを実行できる強...
具体的な方法: 1. [ win+r ] を押して実行ウィンドウを開き、「regedit」と入力して...
目次この期間の目標1. 関数の実装1.1 構造層1.2 スタイルレイヤー1.3 行動層1.3.1 フ...
1. 準備例: 2 台のマシン: 192.168.219.146 (マスター)、192.168.21...