<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://mongoliu.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://mongoliu.github.io/" rel="alternate" type="text/html" /><updated>2026-05-11T15:29:45+00:00</updated><id>https://mongoliu.github.io/feed.xml</id><title type="html">游戏小站</title><subtitle>一些游戏开发的笔记和思考</subtitle><author><name>mongoliu</name><email>nmgliuweiqiang@163.com</email></author><entry><title type="html">Golang 学习笔记</title><link href="https://mongoliu.github.io/golang/" rel="alternate" type="text/html" title="Golang 学习笔记" /><published>2025-12-28T00:00:00+00:00</published><updated>2025-12-28T00:00:00+00:00</updated><id>https://mongoliu.github.io/golang</id><content type="html" xml:base="https://mongoliu.github.io/golang/"><![CDATA[<p>golang学习笔记</p>]]></content><author><name>mongoliu</name><email>nmgliuweiqiang@163.com</email></author><category term="开发笔记" /><category term="Golang" /><category term="后端" /><summary type="html"><![CDATA[golang学习笔记]]></summary></entry><entry><title type="html">事件触发器</title><link href="https://mongoliu.github.io/%E4%BA%8B%E4%BB%B6%E8%A7%A6%E5%8F%91%E5%99%A8-EventBus/" rel="alternate" type="text/html" title="事件触发器" /><published>2025-12-28T00:00:00+00:00</published><updated>2025-12-28T00:00:00+00:00</updated><id>https://mongoliu.github.io/%E4%BA%8B%E4%BB%B6%E8%A7%A6%E5%8F%91%E5%99%A8-EventBus</id><content type="html" xml:base="https://mongoliu.github.io/%E4%BA%8B%E4%BB%B6%E8%A7%A6%E5%8F%91%E5%99%A8-EventBus/"><![CDATA[<h2 id="概述">概述</h2>

<p>EventBus（事件总线）是一种基于发布-订阅模式的组件间通信机制。它通过一个全局的事件中心，实现模块间松耦合的消息传递，避免组件间的直接依赖。</p>

<h3 id="核心思想">核心思想</h3>

<ul>
  <li><strong>发布者（Publisher）</strong>：触发事件，不关心谁在监听</li>
  <li><strong>订阅者（Subscriber）</strong>：注册监听特定事件，不关心谁触发</li>
  <li><strong>事件总线（Bus）</strong>：中间调度层，管理所有事件的注册和分发</li>
</ul>

<h2 id="架构设计">架构设计</h2>

<h3 id="核心组成">核心组成</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│   Publisher  │────▶│   EventBus   │────▶│  Subscriber  │
│   (发布者)    │     │   (事件总线)   │     │   (订阅者)    │
└──────────────┘     └──────────────┘     └──────────────┘
                           │
                     ┌─────┴─────┐
                     │ Event Map │
                     │ 事件注册表  │
                     └───────────┘
</code></pre></div></div>

<h3 id="数据结构">数据结构</h3>

<p>事件总线内部维护一个事件注册表（Event Map），结构如下：</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">interface</span> <span class="nx">EventBus</span> <span class="p">{</span>
  <span class="c1">// 事件名 -&gt; 回调函数列表</span>
  <span class="nl">events</span><span class="p">:</span> <span class="nb">Map</span><span class="o">&lt;</span><span class="kr">string</span><span class="p">,</span> <span class="nb">Set</span><span class="o">&lt;</span><span class="nx">CallbackFunction</span><span class="o">&gt;&gt;</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="执行流程">执行流程</h2>

<h3 id="1-订阅阶段subscribe">1. 订阅阶段（Subscribe）</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Subscriber ──▶ EventBus.on("eventName", callback)
                    │
                    ▼
         events["eventName"].add(callback)
</code></pre></div></div>

<p>订阅者向事件总线注册一个回调函数，绑定到指定事件名称。</p>

<h3 id="2-发布阶段publish--emit">2. 发布阶段（Publish / Emit）</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Publisher ──▶ EventBus.emit("eventName", payload)
                    │
                    ▼
         events["eventName"].forEach(cb =&gt; cb(payload))
</code></pre></div></div>

<p>发布者通过事件总线触发事件，总线遍历该事件的所有回调依次执行。</p>

<h3 id="3-取消订阅unsubscribe">3. 取消订阅（Unsubscribe）</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Subscriber ──▶ EventBus.off("eventName", callback)
                    │
                    ▼
         events["eventName"].delete(callback)
</code></pre></div></div>

<h2 id="实现细节">实现细节</h2>

<h3 id="typescript-完整实现">TypeScript 完整实现</h3>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">type</span> <span class="nx">EventHandler</span> <span class="o">=</span> <span class="p">(...</span><span class="nx">args</span><span class="p">:</span> <span class="kr">any</span><span class="p">[])</span> <span class="o">=&gt;</span> <span class="k">void</span><span class="p">;</span>

<span class="kd">class</span> <span class="nx">EventBus</span> <span class="p">{</span>
  <span class="k">private</span> <span class="nx">events</span><span class="p">:</span> <span class="nb">Map</span><span class="o">&lt;</span><span class="kr">string</span><span class="p">,</span> <span class="nb">Set</span><span class="o">&lt;</span><span class="nx">EventHandler</span><span class="o">&gt;&gt;</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Map</span><span class="p">();</span>

  <span class="cm">/**
   * 订阅事件
   */</span>
  <span class="nx">on</span><span class="p">(</span><span class="nx">event</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">handler</span><span class="p">:</span> <span class="nx">EventHandler</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="k">this</span><span class="p">.</span><span class="nx">events</span><span class="p">.</span><span class="nx">has</span><span class="p">(</span><span class="nx">event</span><span class="p">))</span> <span class="p">{</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">events</span><span class="p">.</span><span class="kd">set</span><span class="p">(</span><span class="nx">event</span><span class="p">,</span> <span class="k">new</span> <span class="nb">Set</span><span class="p">());</span>
    <span class="p">}</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">events</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="nx">event</span><span class="p">)</span><span class="o">!</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="nx">handler</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="cm">/**
   * 一次性订阅（触发一次后自动移除）
   */</span>
  <span class="nx">once</span><span class="p">(</span><span class="nx">event</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">handler</span><span class="p">:</span> <span class="nx">EventHandler</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="na">wrapper</span><span class="p">:</span> <span class="nx">EventHandler</span> <span class="o">=</span> <span class="p">(...</span><span class="nx">args</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
      <span class="nx">handler</span><span class="p">(...</span><span class="nx">args</span><span class="p">);</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">off</span><span class="p">(</span><span class="nx">event</span><span class="p">,</span> <span class="nx">wrapper</span><span class="p">);</span>
    <span class="p">};</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="nx">event</span><span class="p">,</span> <span class="nx">wrapper</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="cm">/**
   * 发布事件
   */</span>
  <span class="nx">emit</span><span class="p">(</span><span class="nx">event</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="p">...</span><span class="nx">args</span><span class="p">:</span> <span class="kr">any</span><span class="p">[]):</span> <span class="k">void</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">handlers</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">events</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="nx">event</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">handlers</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">handlers</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">handler</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="k">try</span> <span class="p">{</span>
          <span class="nx">handler</span><span class="p">(...</span><span class="nx">args</span><span class="p">);</span>
        <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
          <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="s2">`EventBus: Error in handler for "</span><span class="p">${</span><span class="nx">event</span><span class="p">}</span><span class="s2">"`</span><span class="p">,</span> <span class="nx">error</span><span class="p">);</span>
        <span class="p">}</span>
      <span class="p">});</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="cm">/**
   * 取消订阅
   */</span>
  <span class="nx">off</span><span class="p">(</span><span class="nx">event</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">handler</span><span class="p">:</span> <span class="nx">EventHandler</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">handlers</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">events</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="nx">event</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">handlers</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">handlers</span><span class="p">.</span><span class="k">delete</span><span class="p">(</span><span class="nx">handler</span><span class="p">);</span>
      <span class="k">if</span> <span class="p">(</span><span class="nx">handlers</span><span class="p">.</span><span class="nx">size</span> <span class="o">===</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="nx">events</span><span class="p">.</span><span class="k">delete</span><span class="p">(</span><span class="nx">event</span><span class="p">);</span>
      <span class="p">}</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="cm">/**
   * 移除某事件的所有监听器
   */</span>
  <span class="nx">offAll</span><span class="p">(</span><span class="nx">event</span><span class="p">?:</span> <span class="kr">string</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">events</span><span class="p">.</span><span class="k">delete</span><span class="p">(</span><span class="nx">event</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">events</span><span class="p">.</span><span class="nx">clear</span><span class="p">();</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="c1">// 单例导出</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">eventBus</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">EventBus</span><span class="p">();</span>
</code></pre></div></div>

<h3 id="c-实现unity-适用">C# 实现（Unity 适用）</h3>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="nn">System</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Collections.Generic</span><span class="p">;</span>

<span class="k">public</span> <span class="k">class</span> <span class="nc">EventBus</span>
<span class="p">{</span>
    <span class="k">private</span> <span class="k">static</span> <span class="n">EventBus</span> <span class="n">_instance</span><span class="p">;</span>
    <span class="k">public</span> <span class="k">static</span> <span class="n">EventBus</span> <span class="n">Instance</span> <span class="p">=&gt;</span> <span class="n">_instance</span> <span class="p">??=</span> <span class="k">new</span> <span class="nf">EventBus</span><span class="p">();</span>

    <span class="k">private</span> <span class="k">readonly</span> <span class="n">Dictionary</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">,</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">Delegate</span><span class="p">&gt;&gt;</span> <span class="n">_events</span> <span class="p">=</span> <span class="k">new</span><span class="p">();</span>

    <span class="c1">/// &lt;summary&gt;</span>
    <span class="c1">/// 订阅事件</span>
    <span class="c1">/// &lt;/summary&gt;</span>
    <span class="k">public</span> <span class="k">void</span> <span class="n">On</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="kt">string</span> <span class="n">eventName</span><span class="p">,</span> <span class="n">Action</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="n">handler</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">if</span> <span class="p">(!</span><span class="n">_events</span><span class="p">.</span><span class="nf">ContainsKey</span><span class="p">(</span><span class="n">eventName</span><span class="p">))</span>
            <span class="n">_events</span><span class="p">[</span><span class="n">eventName</span><span class="p">]</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">Delegate</span><span class="p">&gt;();</span>
        <span class="n">_events</span><span class="p">[</span><span class="n">eventName</span><span class="p">].</span><span class="nf">Add</span><span class="p">(</span><span class="n">handler</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="c1">/// &lt;summary&gt;</span>
    <span class="c1">/// 发布事件</span>
    <span class="c1">/// &lt;/summary&gt;</span>
    <span class="k">public</span> <span class="k">void</span> <span class="n">Emit</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="kt">string</span> <span class="n">eventName</span><span class="p">,</span> <span class="n">T</span> <span class="n">data</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">_events</span><span class="p">.</span><span class="nf">TryGetValue</span><span class="p">(</span><span class="n">eventName</span><span class="p">,</span> <span class="k">out</span> <span class="kt">var</span> <span class="n">handlers</span><span class="p">))</span>
        <span class="p">{</span>
            <span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">handler</span> <span class="k">in</span> <span class="n">handlers</span><span class="p">.</span><span class="nf">ToArray</span><span class="p">())</span>
            <span class="p">{</span>
                <span class="k">try</span>
                <span class="p">{</span>
                    <span class="p">((</span><span class="n">Action</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;)</span><span class="n">handler</span><span class="p">)?.</span><span class="nf">Invoke</span><span class="p">(</span><span class="n">data</span><span class="p">);</span>
                <span class="p">}</span>
                <span class="k">catch</span> <span class="p">(</span><span class="n">Exception</span> <span class="n">ex</span><span class="p">)</span>
                <span class="p">{</span>
                    <span class="n">UnityEngine</span><span class="p">.</span><span class="n">Debug</span><span class="p">.</span><span class="nf">LogError</span><span class="p">(</span>
                        <span class="s">$"EventBus: Error handling '</span><span class="p">{</span><span class="n">eventName</span><span class="p">}</span><span class="s">': </span><span class="p">{</span><span class="n">ex</span><span class="p">.</span><span class="n">Message</span><span class="p">}</span><span class="s">"</span><span class="p">);</span>
                <span class="p">}</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="c1">/// &lt;summary&gt;</span>
    <span class="c1">/// 取消订阅</span>
    <span class="c1">/// &lt;/summary&gt;</span>
    <span class="k">public</span> <span class="k">void</span> <span class="n">Off</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="kt">string</span> <span class="n">eventName</span><span class="p">,</span> <span class="n">Action</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="n">handler</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">_events</span><span class="p">.</span><span class="nf">TryGetValue</span><span class="p">(</span><span class="n">eventName</span><span class="p">,</span> <span class="k">out</span> <span class="kt">var</span> <span class="n">handlers</span><span class="p">))</span>
        <span class="p">{</span>
            <span class="n">handlers</span><span class="p">.</span><span class="nf">Remove</span><span class="p">(</span><span class="n">handler</span><span class="p">);</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">handlers</span><span class="p">.</span><span class="n">Count</span> <span class="p">==</span> <span class="m">0</span><span class="p">)</span>
                <span class="n">_events</span><span class="p">.</span><span class="nf">Remove</span><span class="p">(</span><span class="n">eventName</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="c1">/// &lt;summary&gt;</span>
    <span class="c1">/// 清除所有事件</span>
    <span class="c1">/// &lt;/summary&gt;</span>
    <span class="k">public</span> <span class="k">void</span> <span class="nf">Clear</span><span class="p">()</span> <span class="p">=&gt;</span> <span class="n">_events</span><span class="p">.</span><span class="nf">Clear</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="执行时序">执行时序</h2>

<p>完整的事件从注册到触发的时序流程：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>时间线 ──────────────────────────────────────────────▶

 ModuleA              EventBus              ModuleB
    │                    │                     │
    │  on("login", cb)   │                     │
    │──────────────────▶ │                     │
    │                    │   on("login", cb2)  │
    │                    │ ◀──────────────────  │
    │                    │                     │
    │                    │                     │
    │  emit("login", user)                     │
    │──────────────────▶ │                     │
    │                    │── cb(user) ────────▶ │
    │                    │── cb2(user) ───────▶ │
    │                    │                     │
    │                    │   off("login", cb2) │
    │                    │ ◀──────────────────  │
    │                    │                     │
</code></pre></div></div>

<h2 id="使用场景">使用场景</h2>

<table>
  <thead>
    <tr>
      <th>场景</th>
      <th>说明</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>跨组件通信</td>
      <td>UI 组件间无父子关系时的消息传递</td>
    </tr>
    <tr>
      <td>模块解耦</td>
      <td>业务模块间避免直接 import 依赖</td>
    </tr>
    <tr>
      <td>插件系统</td>
      <td>主程序与插件之间通过事件交互</td>
    </tr>
    <tr>
      <td>游戏事件</td>
      <td>角色死亡、关卡完成等全局事件通知</td>
    </tr>
    <tr>
      <td>状态同步</td>
      <td>数据变化通知多个视图更新</td>
    </tr>
  </tbody>
</table>

<h2 id="mmo-游戏业务系统网状拓扑">MMO 游戏业务系统网状拓扑</h2>

<p>在大型 MMO 游戏中，数十个业务系统通过 EventBus 形成复杂的网状通信拓扑。以下展示典型 MMO 架构中各子系统的事件依赖关系：</p>

<h3 id="核心事件网络">核心事件网络</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>                         ┌─────────────┐
              ┌─────────▶│  成就系统    │◀─────────┐
              │          └──────┬──────┘          │
              │                 │                  │
              │                 ▼                  │
        ┌─────┴─────┐   ┌─────────────┐   ┌──────┴──────┐
        │  任务系统   │◀─▶│  EventBus   │◀─▶│  关卡系统    │
        └─────┬─────┘   └──────┬──────┘   └──────┬──────┘
              │                 │                  │
              │          ┌──────┼──────┐          │
              ▼          ▼      ▼      ▼          ▼
        ┌───────────┐ ┌─────┐┌─────┐┌─────┐┌───────────┐
        │ 背包/道具  │ │角色 ││战斗 ││社交 ││  副本系统  │
        └─────┬─────┘ └──┬──┘└──┬──┘└──┬──┘└──────┬────┘
              │          │      │      │          │
              ▼          ▼      ▼      ▼          ▼
        ┌───────────┐┌──────┐┌─────┐┌──────┐┌─────────┐
        │  交易系统  ││装备  ││技能 ││公会  ││排行榜   │
        └───────────┘└──────┘└─────┘└──────┘└─────────┘
              │          │      │      │          │
              └──────────┴──────┴──────┴──────────┘
                              │
                       ┌──────┴──────┐
                       │  日志/统计   │
                       └─────────────┘
</code></pre></div></div>

<h3 id="事件流向详解">事件流向详解</h3>

<p>以下是 MMO 中各系统通过 EventBus 传递的关键事件：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>┌──────────────────────────────────────────────────────────────────────┐
│                        EventBus 事件注册表                            │
├──────────────────┬───────────────────────────────────────────────────┤
│ 事件名称          │ 订阅者                                            │
├──────────────────┼───────────────────────────────────────────────────┤
│ monster:killed   │ 任务、成就、关卡、背包(掉落)、经验、排行榜          │
│ quest:completed  │ 成就、背包(奖励)、关卡(解锁)、角色(经验)、日志     │
│ level:up         │ 技能(解锁)、装备(解锁)、成就、社交(通知)、副本     │
│ item:acquired    │ 任务(检查)、成就、背包、交易、日志                  │
│ dungeon:cleared  │ 成就、排行榜、关卡、任务、奖励、公会                │
│ player:login     │ 社交、公会、邮件、活动、日志、每日任务              │
│ player:death     │ 战斗、副本、统计、成就(连续存活)、日志              │
│ skill:used       │ 战斗、冷却、统计、成就(技能使用次数)                │
│ trade:completed  │ 背包、日志、成就(交易次数)、社交                    │
│ guild:event      │ 社交、成就、排行榜、副本(公会本)、奖励              │
│ pvp:result       │ 排行榜、成就、赛季、奖励、统计                      │
│ activity:trigger │ 任务、成就、UI、奖励、日志                          │
└──────────────────┴───────────────────────────────────────────────────┘
</code></pre></div></div>

<h3 id="典型事件链路击杀-boss">典型事件链路（击杀 Boss）</h3>

<p>一次 Boss 击杀如何触发多系统级联响应：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>玩家击杀 Boss
    │
    ├──▶ emit("monster:killed", { id, type:"boss", zone })
    │         │
    │         ├──▶ [任务系统]  检查击杀任务进度 → emit("quest:progress")
    │         ├──▶ [成就系统]  累计击杀数 +1 → 达标则 emit("achievement:unlock")
    │         ├──▶ [背包系统]  生成掉落物品 → emit("item:acquired")
    │         ├──▶ [关卡系统]  检查区域清除条件
    │         ├──▶ [排行榜]    更新 DPS/击杀榜
    │         ├──▶ [副本系统]  检查副本通关条件 → emit("dungeon:cleared")
    │         └──▶ [日志系统]  记录战斗日志
    │
    ├──▶ emit("item:acquired", { items: [...] })
    │         ├──▶ [任务系统]  检查收集任务
    │         ├──▶ [成就系统]  稀有物品收集进度
    │         └──▶ [交易系统]  更新市场参考价
    │
    └──▶ emit("dungeon:cleared", { dungeonId, time, team })
              ├──▶ [成就系统]  首次通关成就
              ├──▶ [排行榜]    副本速通排名
              ├──▶ [公会系统]  公会贡献 +N
              └──▶ [关卡系统]  解锁下一难度
</code></pre></div></div>

<h3 id="系统间依赖矩阵">系统间依赖矩阵</h3>

<table>
  <thead>
    <tr>
      <th style="text-align: center">发布者 \ 订阅者</th>
      <th style="text-align: center">任务</th>
      <th style="text-align: center">成就</th>
      <th style="text-align: center">关卡</th>
      <th style="text-align: center">背包</th>
      <th style="text-align: center">战斗</th>
      <th style="text-align: center">社交</th>
      <th style="text-align: center">排行</th>
      <th style="text-align: center">副本</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center"><strong>战斗系统</strong></td>
      <td style="text-align: center">✓</td>
      <td style="text-align: center">✓</td>
      <td style="text-align: center">✓</td>
      <td style="text-align: center">✓</td>
      <td style="text-align: center">-</td>
      <td style="text-align: center">·</td>
      <td style="text-align: center">✓</td>
      <td style="text-align: center">✓</td>
    </tr>
    <tr>
      <td style="text-align: center"><strong>任务系统</strong></td>
      <td style="text-align: center">-</td>
      <td style="text-align: center">✓</td>
      <td style="text-align: center">✓</td>
      <td style="text-align: center">✓</td>
      <td style="text-align: center">·</td>
      <td style="text-align: center">·</td>
      <td style="text-align: center">·</td>
      <td style="text-align: center">·</td>
    </tr>
    <tr>
      <td style="text-align: center"><strong>关卡系统</strong></td>
      <td style="text-align: center">✓</td>
      <td style="text-align: center">✓</td>
      <td style="text-align: center">-</td>
      <td style="text-align: center">✓</td>
      <td style="text-align: center">·</td>
      <td style="text-align: center">·</td>
      <td style="text-align: center">✓</td>
      <td style="text-align: center">✓</td>
    </tr>
    <tr>
      <td style="text-align: center"><strong>角色系统</strong></td>
      <td style="text-align: center">✓</td>
      <td style="text-align: center">✓</td>
      <td style="text-align: center">✓</td>
      <td style="text-align: center">·</td>
      <td style="text-align: center">✓</td>
      <td style="text-align: center">✓</td>
      <td style="text-align: center">✓</td>
      <td style="text-align: center">·</td>
    </tr>
    <tr>
      <td style="text-align: center"><strong>社交系统</strong></td>
      <td style="text-align: center">·</td>
      <td style="text-align: center">✓</td>
      <td style="text-align: center">·</td>
      <td style="text-align: center">·</td>
      <td style="text-align: center">·</td>
      <td style="text-align: center">-</td>
      <td style="text-align: center">·</td>
      <td style="text-align: center">·</td>
    </tr>
    <tr>
      <td style="text-align: center"><strong>副本系统</strong></td>
      <td style="text-align: center">✓</td>
      <td style="text-align: center">✓</td>
      <td style="text-align: center">✓</td>
      <td style="text-align: center">✓</td>
      <td style="text-align: center">·</td>
      <td style="text-align: center">✓</td>
      <td style="text-align: center">✓</td>
      <td style="text-align: center">-</td>
    </tr>
    <tr>
      <td style="text-align: center"><strong>交易系统</strong></td>
      <td style="text-align: center">·</td>
      <td style="text-align: center">✓</td>
      <td style="text-align: center">·</td>
      <td style="text-align: center">✓</td>
      <td style="text-align: center">·</td>
      <td style="text-align: center">·</td>
      <td style="text-align: center">·</td>
      <td style="text-align: center">·</td>
    </tr>
  </tbody>
</table>

<blockquote>
  <p>✓ = 有事件订阅关系　· = 无直接关系　- = 自身</p>
</blockquote>

<h3 id="设计要点">设计要点</h3>

<p>在 MMO 这种复杂网状结构中使用 EventBus 需要注意：</p>

<ol>
  <li><strong>事件分层</strong>：按优先级分为 System / Game / UI 三层，避免跨层级联</li>
  <li><strong>防止循环</strong>：A→B→C→A 的事件循环会导致死锁，需设计单向事件流</li>
  <li><strong>批量合并</strong>：高频事件（如伤害计算）使用节流，每帧只处理一次</li>
  <li><strong>事件溯源</strong>：记录事件链 ID，方便定位级联 Bug</li>
  <li><strong>模块隔离</strong>：每个子系统只监听自己关心的事件，不越权处理</li>
</ol>

<h2 id="优缺点对比">优缺点对比</h2>

<h3 id="优势">优势</h3>

<ul>
  <li><strong>解耦性强</strong>：发布者和订阅者互不感知</li>
  <li><strong>灵活性高</strong>：可动态增删订阅</li>
  <li><strong>易于扩展</strong>：新模块只需订阅感兴趣的事件</li>
</ul>

<h3 id="劣势">劣势</h3>

<ul>
  <li><strong>调试困难</strong>：事件流向不直观，难以追踪</li>
  <li><strong>内存泄漏</strong>：忘记取消订阅会导致引用残留</li>
  <li><strong>执行顺序</strong>：多个订阅者的执行顺序不可控</li>
</ul>

<h2 id="同步回调-vs-异步回调">同步回调 vs 异步回调</h2>

<p>EventBus 最核心的设计决策之一：<strong>回调函数应该同步执行还是异步执行？</strong>两种模式在执行时序、数据一致性、性能表现上有本质差异。</p>

<h3 id="执行模型对比">执行模型对比</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>【同步模型】emit 时立即逐个执行回调，完成后才返回

  emit("kill")   handler_A()   handler_B()   handler_C()   emit返回
      │──────────▶│             │             │             │
      │           │─────执行───▶│             │             │
      │           │             │─────执行───▶│             │
      │           │             │             │─────执行───▶│
      │◀──────────────────────────────────────────────────── │
      │                   总耗时 = A + B + C                 │


【异步模型】emit 时将回调推入队列/微任务，emit 立即返回

  emit("kill")          主线程继续            事件循环处理队列
      │──push queue──▶│                     │
      │◀──立即返回─────│                     │
      │               │──后续逻辑──▶         │
      │               │                     │──handler_A()──▶
      │               │                     │──handler_B()──▶
      │               │                     │──handler_C()──▶
</code></pre></div></div>

<h3 id="同步回调详解">同步回调详解</h3>

<h4 id="工作原理">工作原理</h4>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 同步 EventBus — emit 阻塞式执行</span>
<span class="kd">class</span> <span class="nx">SyncEventBus</span> <span class="p">{</span>
  <span class="k">private</span> <span class="nx">events</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Map</span><span class="o">&lt;</span><span class="kr">string</span><span class="p">,</span> <span class="nb">Set</span><span class="o">&lt;</span><span class="nb">Function</span><span class="o">&gt;&gt;</span><span class="p">();</span>

  <span class="nx">emit</span><span class="p">(</span><span class="nx">event</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="p">...</span><span class="nx">args</span><span class="p">:</span> <span class="kr">any</span><span class="p">[]):</span> <span class="k">void</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">handlers</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">events</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="nx">event</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">handlers</span><span class="p">)</span> <span class="p">{</span>
      <span class="c1">// 逐个同步调用，任何一个慢都会阻塞后续</span>
      <span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">handler</span> <span class="k">of</span> <span class="nx">handlers</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">handler</span><span class="p">(...</span><span class="nx">args</span><span class="p">);</span>  <span class="c1">// 阻塞在这里直到 handler 返回</span>
      <span class="p">}</span>
    <span class="p">}</span>
    <span class="c1">// 所有 handler 执行完毕后才到达这里</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h4 id="优势-1">优势</h4>

<ul>
  <li><strong>数据一致性强</strong>：emit 返回时所有副作用已完成，状态已更新</li>
  <li><strong>执行顺序确定</strong>：回调按注册顺序依次执行</li>
  <li><strong>调试友好</strong>：调用栈完整，断点可从 emit 一路追踪到每个 handler</li>
  <li><strong>事务性</strong>：可以在 emit 之后立即读取被 handler 修改的状态</li>
</ul>

<h4 id="致命问题主线程阻塞">致命问题：主线程阻塞</h4>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>【问题场景】击杀 Boss 触发 7 个同步回调

帧时间预算: 16.6ms (60fps)

  emit("monster:killed")
    ├── 任务系统检查    2ms
    ├── 成就系统累计    1ms
    ├── 背包掉落计算    3ms   ← 包含随机数+权重表查询
    ├── 关卡状态更新    1ms
    ├── 排行榜写入      5ms   ← 排序操作
    ├── 副本条件判断    2ms
    └── 日志写入        4ms   ← I/O 操作
                     ────────
                     总计 18ms  ← 超出帧预算！导致掉帧
</code></pre></div></div>

<h4 id="同步分线策略">同步分线策略</h4>

<p>针对同步阻塞问题，有以下处理方案：</p>

<p><strong>方案一：优先级分层执行</strong></p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">PrioritySyncBus</span> <span class="p">{</span>
  <span class="c1">// 按优先级分桶：critical 必须当帧完成，normal 可延迟</span>
  <span class="k">private</span> <span class="nx">buckets</span> <span class="o">=</span> <span class="p">{</span>
    <span class="na">critical</span><span class="p">:</span> <span class="k">new</span> <span class="nb">Map</span><span class="o">&lt;</span><span class="kr">string</span><span class="p">,</span> <span class="nb">Set</span><span class="o">&lt;</span><span class="nb">Function</span><span class="o">&gt;&gt;</span><span class="p">(),</span>  <span class="c1">// 立即执行</span>
    <span class="na">normal</span><span class="p">:</span>   <span class="k">new</span> <span class="nb">Map</span><span class="o">&lt;</span><span class="kr">string</span><span class="p">,</span> <span class="nb">Set</span><span class="o">&lt;</span><span class="nb">Function</span><span class="o">&gt;&gt;</span><span class="p">(),</span>  <span class="c1">// 当帧执行但在 critical 之后</span>
    <span class="na">deferred</span><span class="p">:</span> <span class="k">new</span> <span class="nb">Map</span><span class="o">&lt;</span><span class="kr">string</span><span class="p">,</span> <span class="nb">Set</span><span class="o">&lt;</span><span class="nb">Function</span><span class="o">&gt;&gt;</span><span class="p">(),</span>  <span class="c1">// 延迟到下一帧</span>
  <span class="p">};</span>

  <span class="nx">emit</span><span class="p">(</span><span class="nx">event</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="p">...</span><span class="nx">args</span><span class="p">:</span> <span class="kr">any</span><span class="p">[]):</span> <span class="k">void</span> <span class="p">{</span>
    <span class="c1">// 1. 立即执行关键回调（如战斗伤害结算）</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">buckets</span><span class="p">.</span><span class="nx">critical</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="nx">event</span><span class="p">)?.</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">h</span> <span class="o">=&gt;</span> <span class="nx">h</span><span class="p">(...</span><span class="nx">args</span><span class="p">));</span>

    <span class="c1">// 2. 执行普通回调</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">buckets</span><span class="p">.</span><span class="nx">normal</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="nx">event</span><span class="p">)?.</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">h</span> <span class="o">=&gt;</span> <span class="nx">h</span><span class="p">(...</span><span class="nx">args</span><span class="p">));</span>

    <span class="c1">// 3. 延迟回调推入下一帧队列</span>
    <span class="kd">const</span> <span class="nx">deferred</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">buckets</span><span class="p">.</span><span class="nx">deferred</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="nx">event</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">deferred</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">requestAnimationFrame</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="nx">deferred</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">h</span> <span class="o">=&gt;</span> <span class="nx">h</span><span class="p">(...</span><span class="nx">args</span><span class="p">));</span>
      <span class="p">});</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>方案二：时间片切割</strong></p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">TimeBudgetBus</span> <span class="p">{</span>
  <span class="k">private</span> <span class="k">readonly</span> <span class="nx">BUDGET_MS</span> <span class="o">=</span> <span class="mi">4</span><span class="p">;</span> <span class="c1">// 每次 emit 最多占用 4ms</span>

  <span class="nx">emit</span><span class="p">(</span><span class="nx">event</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="p">...</span><span class="nx">args</span><span class="p">:</span> <span class="kr">any</span><span class="p">[]):</span> <span class="k">void</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">handlers</span> <span class="o">=</span> <span class="p">[...(</span><span class="k">this</span><span class="p">.</span><span class="nx">events</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="o">||</span> <span class="p">[])];</span>
    <span class="kd">const</span> <span class="nx">start</span> <span class="o">=</span> <span class="nx">performance</span><span class="p">.</span><span class="nx">now</span><span class="p">();</span>
    <span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

    <span class="c1">// 在时间预算内尽可能多执行</span>
    <span class="k">while</span> <span class="p">(</span><span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">handlers</span><span class="p">.</span><span class="nx">length</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">handlers</span><span class="p">[</span><span class="nx">i</span><span class="p">](...</span><span class="nx">args</span><span class="p">);</span>
      <span class="nx">i</span><span class="o">++</span><span class="p">;</span>

      <span class="k">if</span> <span class="p">(</span><span class="nx">performance</span><span class="p">.</span><span class="nx">now</span><span class="p">()</span> <span class="o">-</span> <span class="nx">start</span> <span class="o">&gt;</span> <span class="k">this</span><span class="p">.</span><span class="nx">BUDGET_MS</span> <span class="o">&amp;&amp;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">handlers</span><span class="p">.</span><span class="nx">length</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// 超时，剩余回调推迟到下一帧</span>
        <span class="kd">const</span> <span class="nx">remaining</span> <span class="o">=</span> <span class="nx">handlers</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="nx">i</span><span class="p">);</span>
        <span class="nx">requestAnimationFrame</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
          <span class="nx">remaining</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">h</span> <span class="o">=&gt;</span> <span class="nx">h</span><span class="p">(...</span><span class="nx">args</span><span class="p">));</span>
        <span class="p">});</span>
        <span class="k">break</span><span class="p">;</span>
      <span class="p">}</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>方案三：C# 协程分帧（Unity）</strong></p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">CoroutineEventBus</span> <span class="p">:</span> <span class="n">MonoBehaviour</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="k">void</span> <span class="nf">Emit</span><span class="p">(</span><span class="kt">string</span> <span class="n">eventName</span><span class="p">,</span> <span class="kt">object</span> <span class="n">data</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="nf">StartCoroutine</span><span class="p">(</span><span class="nf">EmitCoroutine</span><span class="p">(</span><span class="n">eventName</span><span class="p">,</span> <span class="n">data</span><span class="p">));</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="n">IEnumerator</span> <span class="nf">EmitCoroutine</span><span class="p">(</span><span class="kt">string</span> <span class="n">eventName</span><span class="p">,</span> <span class="kt">object</span> <span class="n">data</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="kt">var</span> <span class="n">handlers</span> <span class="p">=</span> <span class="nf">GetHandlers</span><span class="p">(</span><span class="n">eventName</span><span class="p">);</span>
        <span class="kt">float</span> <span class="n">frameStart</span> <span class="p">=</span> <span class="n">Time</span><span class="p">.</span><span class="n">realtimeSinceStartup</span><span class="p">;</span>

        <span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">handler</span> <span class="k">in</span> <span class="n">handlers</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="n">handler</span><span class="p">.</span><span class="nf">Invoke</span><span class="p">(</span><span class="n">data</span><span class="p">);</span>

            <span class="c1">// 每个 handler 执行后检查是否超出帧预算</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">Time</span><span class="p">.</span><span class="n">realtimeSinceStartup</span> <span class="p">-</span> <span class="n">frameStart</span> <span class="p">&gt;</span> <span class="m">0.004f</span><span class="p">)</span> <span class="c1">// 4ms</span>
            <span class="p">{</span>
                <span class="k">yield</span> <span class="k">return</span> <span class="k">null</span><span class="p">;</span> <span class="c1">// 让出到下一帧</span>
                <span class="n">frameStart</span> <span class="p">=</span> <span class="n">Time</span><span class="p">.</span><span class="n">realtimeSinceStartup</span><span class="p">;</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="异步回调详解">异步回调详解</h3>

<h4 id="工作原理-1">工作原理</h4>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 异步 EventBus — emit 非阻塞</span>
<span class="kd">class</span> <span class="nx">AsyncEventBus</span> <span class="p">{</span>
  <span class="k">private</span> <span class="nx">events</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Map</span><span class="o">&lt;</span><span class="kr">string</span><span class="p">,</span> <span class="nb">Set</span><span class="o">&lt;</span><span class="nb">Function</span><span class="o">&gt;&gt;</span><span class="p">();</span>

  <span class="nx">emit</span><span class="p">(</span><span class="nx">event</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="p">...</span><span class="nx">args</span><span class="p">:</span> <span class="kr">any</span><span class="p">[]):</span> <span class="k">void</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">handlers</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">events</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="nx">event</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">handlers</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">handler</span> <span class="k">of</span> <span class="nx">handlers</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// 推入微任务队列，不阻塞当前执行</span>
        <span class="nx">queueMicrotask</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="nx">handler</span><span class="p">(...</span><span class="nx">args</span><span class="p">));</span>
      <span class="p">}</span>
    <span class="p">}</span>
    <span class="c1">// 立即返回，handler 尚未执行</span>
  <span class="p">}</span>

  <span class="c1">// 或使用 Promise 包装</span>
  <span class="k">async</span> <span class="nx">emitAsync</span><span class="p">(</span><span class="nx">event</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="p">...</span><span class="nx">args</span><span class="p">:</span> <span class="kr">any</span><span class="p">[]):</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="k">void</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">handlers</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">events</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="nx">event</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">handlers</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">await</span> <span class="nb">Promise</span><span class="p">.</span><span class="nx">all</span><span class="p">(</span>
        <span class="p">[...</span><span class="nx">handlers</span><span class="p">].</span><span class="nx">map</span><span class="p">(</span><span class="nx">h</span> <span class="o">=&gt;</span> <span class="nb">Promise</span><span class="p">.</span><span class="nx">resolve</span><span class="p">().</span><span class="nx">then</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="nx">h</span><span class="p">(...</span><span class="nx">args</span><span class="p">)))</span>
      <span class="p">);</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h4 id="优势-2">优势</h4>

<ul>
  <li><strong>不阻塞主线程</strong>：emit 立即返回，游戏帧率不受影响</li>
  <li><strong>天然并发</strong>：多个 handler 可并行处理（如果运行在 Worker 中）</li>
  <li><strong>容错性好</strong>：单个 handler 异常不影响其他 handler 的调度</li>
</ul>

<h4 id="致命问题数据镜像与一致性">致命问题：数据镜像与一致性</h4>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>【问题场景】异步回调读到的数据可能已经过时

帧 N:  玩家血量 = 100
       emit("player:damaged", { hp: 100, damage: 30 })
       │── handler 推入异步队列
       │── 主线程继续执行
       │── 又收到一次伤害 → 血量变为 40

帧 N+1: 异步 handler 开始执行
        handler 拿到的数据: { hp: 100, damage: 30 }
        但此时玩家实际血量 = 40  ← 数据已过期！
        handler 按 hp=100 计算 → 逻辑错误
</code></pre></div></div>

<h4 id="数据镜像问题的解决方案">数据镜像问题的解决方案</h4>

<p><strong>方案一：深拷贝快照（Snapshot）</strong></p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">SnapshotEventBus</span> <span class="p">{</span>
  <span class="nx">emit</span><span class="p">(</span><span class="nx">event</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">data</span><span class="p">:</span> <span class="kr">any</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
    <span class="c1">// 在 emit 时刻冻结数据快照</span>
    <span class="kd">const</span> <span class="nx">snapshot</span> <span class="o">=</span> <span class="nx">structuredClone</span><span class="p">(</span><span class="nx">data</span><span class="p">);</span>

    <span class="kd">const</span> <span class="nx">handlers</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">events</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="nx">event</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">handlers</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">handler</span> <span class="k">of</span> <span class="nx">handlers</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">queueMicrotask</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="nx">handler</span><span class="p">(</span><span class="nx">snapshot</span><span class="p">));</span>
      <span class="p">}</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="c1">// 使用：handler 拿到的永远是 emit 时刻的一致数据</span>
<span class="nx">eventBus</span><span class="p">.</span><span class="nx">emit</span><span class="p">(</span><span class="dl">"</span><span class="s2">player:damaged</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span>
  <span class="na">hp</span><span class="p">:</span> <span class="nx">player</span><span class="p">.</span><span class="nx">hp</span><span class="p">,</span>        <span class="c1">// 发射时的值</span>
  <span class="na">damage</span><span class="p">:</span> <span class="mi">30</span><span class="p">,</span>
  <span class="na">timestamp</span><span class="p">:</span> <span class="nb">Date</span><span class="p">.</span><span class="nx">now</span><span class="p">()</span> <span class="c1">// 附带时间戳供 handler 判断时效性</span>
<span class="p">});</span>
</code></pre></div></div>

<p><strong>方案二：版本号校验</strong></p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">VersionedEventBus</span> <span class="p">{</span>
  <span class="k">private</span> <span class="nx">version</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Map</span><span class="o">&lt;</span><span class="kr">string</span><span class="p">,</span> <span class="kr">number</span><span class="o">&gt;</span><span class="p">();</span>

  <span class="nx">emit</span><span class="p">(</span><span class="nx">event</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">data</span><span class="p">:</span> <span class="kr">any</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">ver</span> <span class="o">=</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">version</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="o">||</span> <span class="mi">0</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">version</span><span class="p">.</span><span class="kd">set</span><span class="p">(</span><span class="nx">event</span><span class="p">,</span> <span class="nx">ver</span><span class="p">);</span>

    <span class="kd">const</span> <span class="nx">handlers</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">events</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="nx">event</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">handlers</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">handler</span> <span class="k">of</span> <span class="nx">handlers</span><span class="p">)</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">capturedVer</span> <span class="o">=</span> <span class="nx">ver</span><span class="p">;</span>
        <span class="nx">queueMicrotask</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
          <span class="c1">// handler 执行时检查：如果版本已更新，说明有更新的事件</span>
          <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">version</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="o">===</span> <span class="nx">capturedVer</span><span class="p">)</span> <span class="p">{</span>
            <span class="nx">handler</span><span class="p">(</span><span class="nx">data</span><span class="p">);</span>  <span class="c1">// 仍是最新 → 执行</span>
          <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="c1">// 已过期 → 跳过或合并处理</span>
            <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`Skipped stale </span><span class="p">${</span><span class="nx">event</span><span class="p">}</span><span class="s2"> v</span><span class="p">${</span><span class="nx">capturedVer</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
          <span class="p">}</span>
        <span class="p">});</span>
      <span class="p">}</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>方案三：不可变数据 + 事件溯源</strong></p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">interface</span> <span class="nx">GameEvent</span><span class="o">&lt;</span><span class="nx">T</span> <span class="o">=</span> <span class="kr">any</span><span class="o">&gt;</span> <span class="p">{</span>
  <span class="k">readonly</span> <span class="na">type</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
  <span class="k">readonly</span> <span class="na">payload</span><span class="p">:</span> <span class="nb">Readonly</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span><span class="p">;</span>  <span class="c1">// 不可变</span>
  <span class="k">readonly</span> <span class="na">eventId</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>       <span class="c1">// 唯一 ID</span>
  <span class="k">readonly</span> <span class="na">timestamp</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span>     <span class="c1">// 发生时间</span>
  <span class="k">readonly</span> <span class="na">frameId</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span>       <span class="c1">// 发生的帧号</span>
<span class="p">}</span>

<span class="kd">class</span> <span class="nx">ImmutableEventBus</span> <span class="p">{</span>
  <span class="k">private</span> <span class="nx">frameCounter</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

  <span class="nx">emit</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span><span class="p">(</span><span class="kd">type</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">payload</span><span class="p">:</span> <span class="nx">T</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
    <span class="c1">// 创建不可变事件对象</span>
    <span class="kd">const</span> <span class="na">event</span><span class="p">:</span> <span class="nx">GameEvent</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span> <span class="o">=</span> <span class="nb">Object</span><span class="p">.</span><span class="nx">freeze</span><span class="p">({</span>
      <span class="kd">type</span><span class="p">,</span>
      <span class="na">payload</span><span class="p">:</span> <span class="nb">Object</span><span class="p">.</span><span class="nx">freeze</span><span class="p">({</span> <span class="p">...</span><span class="nx">payload</span> <span class="p">}),</span>
      <span class="na">eventId</span><span class="p">:</span> <span class="nx">crypto</span><span class="p">.</span><span class="nx">randomUUID</span><span class="p">(),</span>
      <span class="na">timestamp</span><span class="p">:</span> <span class="nx">performance</span><span class="p">.</span><span class="nx">now</span><span class="p">(),</span>
      <span class="na">frameId</span><span class="p">:</span> <span class="k">this</span><span class="p">.</span><span class="nx">frameCounter</span><span class="p">,</span>
    <span class="p">});</span>

    <span class="c1">// handler 拿到的是冻结对象，无法被任何人修改</span>
    <span class="nx">queueMicrotask</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">events</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="kd">type</span><span class="p">)?.</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">h</span> <span class="o">=&gt;</span> <span class="nx">h</span><span class="p">(</span><span class="nx">event</span><span class="p">));</span>
    <span class="p">});</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="同步-vs-异步-全面对比">同步 vs 异步 全面对比</h3>

<table>
  <thead>
    <tr>
      <th>维度</th>
      <th>同步回调</th>
      <th>异步回调</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>执行时机</strong></td>
      <td>emit 内部立即执行</td>
      <td>推入队列/微任务，延迟执行</td>
    </tr>
    <tr>
      <td><strong>emit 返回时</strong></td>
      <td>所有 handler 已完成</td>
      <td>handler 尚未开始</td>
    </tr>
    <tr>
      <td><strong>帧率影响</strong></td>
      <td>handler 越多/越慢，帧率越低</td>
      <td>不影响当前帧（但可能影响下一帧）</td>
    </tr>
    <tr>
      <td><strong>数据一致性</strong></td>
      <td>强一致：读到的就是 emit 时刻的状态</td>
      <td>弱一致：数据可能已被后续操作修改</td>
    </tr>
    <tr>
      <td><strong>调用栈</strong></td>
      <td>完整可追踪</td>
      <td>被截断，需额外工具追踪</td>
    </tr>
    <tr>
      <td><strong>异常传播</strong></td>
      <td>handler 异常会中断后续 handler</td>
      <td>handler 异常互不影响</td>
    </tr>
    <tr>
      <td><strong>执行顺序</strong></td>
      <td>确定（注册顺序）</td>
      <td>不确定（取决于调度器）</td>
    </tr>
    <tr>
      <td><strong>调试难度</strong></td>
      <td>低</td>
      <td>高（时序问题难复现）</td>
    </tr>
    <tr>
      <td><strong>吞吐量</strong></td>
      <td>低（串行阻塞）</td>
      <td>高（可并行调度）</td>
    </tr>
    <tr>
      <td><strong>适用场景</strong></td>
      <td>状态查询、数据校验、事务操作</td>
      <td>UI 更新、日志、统计、网络请求</td>
    </tr>
  </tbody>
</table>

<h3 id="游戏中的选型判断依据">游戏中的选型判断依据</h3>

<p>根据业务特征选择同步或异步：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>                    是否需要 handler 的返回值/副作用？
                              │
                    ┌─── YES ─┴── NO ───┐
                    │                    │
              结果是否影响               对帧率是否敏感？
              当前帧的逻辑？              │
                    │              ┌─ YES ┴─ NO ─┐
              ┌─YES─┴─NO──┐       │              │
              │            │    【异步】       【都行】
           【同步】     handler                优先同步
           必须同步     是否耗时？             （简单直观）
                          │
                    ┌─YES─┴─ NO ─┐
                    │             │
                【异步】       【同步】
                推入下帧       当帧完成
</code></pre></div></div>

<h4 id="按业务系统推荐">按业务系统推荐</h4>

<table>
  <thead>
    <tr>
      <th>系统</th>
      <th>推荐模式</th>
      <th>理由</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>伤害结算</td>
      <td><strong>同步</strong></td>
      <td>后续逻辑依赖结果（死亡判定、护盾计算）</td>
    </tr>
    <tr>
      <td>任务进度检查</td>
      <td><strong>同步</strong></td>
      <td>需要立即知道是否达成完成条件</td>
    </tr>
    <tr>
      <td>技能冷却触发</td>
      <td><strong>同步</strong></td>
      <td>影响当前帧的输入响应</td>
    </tr>
    <tr>
      <td>背包物品增删</td>
      <td><strong>同步</strong></td>
      <td>UI 需要立即刷新，避免闪烁</td>
    </tr>
    <tr>
      <td>成就累计统计</td>
      <td><strong>异步</strong></td>
      <td>纯累加操作，不影响游戏逻辑</td>
    </tr>
    <tr>
      <td>排行榜更新</td>
      <td><strong>异步</strong></td>
      <td>排序耗时，不影响当前帧</td>
    </tr>
    <tr>
      <td>日志/埋点上报</td>
      <td><strong>异步</strong></td>
      <td>纯记录，零游戏逻辑依赖</td>
    </tr>
    <tr>
      <td>社交通知推送</td>
      <td><strong>异步</strong></td>
      <td>网络 I/O，必须异步</td>
    </tr>
    <tr>
      <td>副本通关判定</td>
      <td><strong>同步</strong></td>
      <td>需要立即触发结算流程和奖励发放</td>
    </tr>
    <tr>
      <td>音效/特效播放</td>
      <td><strong>异步</strong></td>
      <td>容忍 1-2 帧延迟，减轻主线程压力</td>
    </tr>
  </tbody>
</table>

<h4 id="混合模式最佳实践">混合模式最佳实践</h4>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">HybridEventBus</span> <span class="p">{</span>
  <span class="k">private</span> <span class="nx">syncHandlers</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Map</span><span class="o">&lt;</span><span class="kr">string</span><span class="p">,</span> <span class="nb">Set</span><span class="o">&lt;</span><span class="nb">Function</span><span class="o">&gt;&gt;</span><span class="p">();</span>
  <span class="k">private</span> <span class="nx">asyncHandlers</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Map</span><span class="o">&lt;</span><span class="kr">string</span><span class="p">,</span> <span class="nb">Set</span><span class="o">&lt;</span><span class="nb">Function</span><span class="o">&gt;&gt;</span><span class="p">();</span>

  <span class="c1">// 注册时指定同步/异步</span>
  <span class="nx">on</span><span class="p">(</span><span class="nx">event</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">handler</span><span class="p">:</span> <span class="nb">Function</span><span class="p">,</span> <span class="nx">mode</span><span class="p">:</span> <span class="dl">'</span><span class="s1">sync</span><span class="dl">'</span> <span class="o">|</span> <span class="dl">'</span><span class="s1">async</span><span class="dl">'</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">sync</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">map</span> <span class="o">=</span> <span class="nx">mode</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">sync</span><span class="dl">'</span> <span class="p">?</span> <span class="k">this</span><span class="p">.</span><span class="nx">syncHandlers</span> <span class="p">:</span> <span class="k">this</span><span class="p">.</span><span class="nx">asyncHandlers</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">map</span><span class="p">.</span><span class="nx">has</span><span class="p">(</span><span class="nx">event</span><span class="p">))</span> <span class="nx">map</span><span class="p">.</span><span class="kd">set</span><span class="p">(</span><span class="nx">event</span><span class="p">,</span> <span class="k">new</span> <span class="nb">Set</span><span class="p">());</span>
    <span class="nx">map</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="nx">event</span><span class="p">)</span><span class="o">!</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="nx">handler</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="nx">emit</span><span class="p">(</span><span class="nx">event</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="p">...</span><span class="nx">args</span><span class="p">:</span> <span class="kr">any</span><span class="p">[]):</span> <span class="k">void</span> <span class="p">{</span>
    <span class="c1">// 第一阶段：同步回调立即执行（保证数据一致性）</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">syncHandlers</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="nx">event</span><span class="p">)?.</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">h</span> <span class="o">=&gt;</span> <span class="nx">h</span><span class="p">(...</span><span class="nx">args</span><span class="p">));</span>

    <span class="c1">// 第二阶段：异步回调推入微任务（不阻塞主线程）</span>
    <span class="kd">const</span> <span class="nx">snapshot</span> <span class="o">=</span> <span class="nx">structuredClone</span><span class="p">(</span><span class="nx">args</span><span class="p">);</span> <span class="c1">// 快照数据</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">asyncHandlers</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="nx">event</span><span class="p">)?.</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">h</span> <span class="o">=&gt;</span> <span class="p">{</span>
      <span class="nx">queueMicrotask</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="nx">h</span><span class="p">(...</span><span class="nx">snapshot</span><span class="p">));</span>
    <span class="p">});</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="c1">// 使用示例</span>
<span class="kd">const</span> <span class="nx">bus</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">HybridEventBus</span><span class="p">();</span>

<span class="c1">// 伤害结算必须同步 —— 后续需要知道目标是否死亡</span>
<span class="nx">bus</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="dl">'</span><span class="s1">monster:killed</span><span class="dl">'</span><span class="p">,</span> <span class="nx">handleQuestProgress</span><span class="p">,</span> <span class="dl">'</span><span class="s1">sync</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">bus</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="dl">'</span><span class="s1">monster:killed</span><span class="dl">'</span><span class="p">,</span> <span class="nx">handleLootDrop</span><span class="p">,</span> <span class="dl">'</span><span class="s1">sync</span><span class="dl">'</span><span class="p">);</span>

<span class="c1">// 统计和日志可以异步 —— 不影响游戏逻辑</span>
<span class="nx">bus</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="dl">'</span><span class="s1">monster:killed</span><span class="dl">'</span><span class="p">,</span> <span class="nx">handleAchievementCount</span><span class="p">,</span> <span class="dl">'</span><span class="s1">async</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">bus</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="dl">'</span><span class="s1">monster:killed</span><span class="dl">'</span><span class="p">,</span> <span class="nx">handleBattleLog</span><span class="p">,</span> <span class="dl">'</span><span class="s1">async</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">bus</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="dl">'</span><span class="s1">monster:killed</span><span class="dl">'</span><span class="p">,</span> <span class="nx">handleLeaderboard</span><span class="p">,</span> <span class="dl">'</span><span class="s1">async</span><span class="dl">'</span><span class="p">);</span>
</code></pre></div></div>

<h2 id="最佳实践">最佳实践</h2>

<h3 id="1-使用常量定义事件名">1. 使用常量定义事件名</h3>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// events.ts</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">EVENTS</span> <span class="o">=</span> <span class="p">{</span>
  <span class="na">USER_LOGIN</span><span class="p">:</span> <span class="dl">'</span><span class="s1">user:login</span><span class="dl">'</span><span class="p">,</span>
  <span class="na">USER_LOGOUT</span><span class="p">:</span> <span class="dl">'</span><span class="s1">user:logout</span><span class="dl">'</span><span class="p">,</span>
  <span class="na">DATA_LOADED</span><span class="p">:</span> <span class="dl">'</span><span class="s1">data:loaded</span><span class="dl">'</span><span class="p">,</span>
  <span class="na">THEME_CHANGED</span><span class="p">:</span> <span class="dl">'</span><span class="s1">theme:changed</span><span class="dl">'</span><span class="p">,</span>
<span class="p">}</span> <span class="k">as</span> <span class="kd">const</span><span class="p">;</span>
</code></pre></div></div>

<h3 id="2-组件销毁时必须取消订阅">2. 组件销毁时必须取消订阅</h3>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">MyComponent</span> <span class="p">{</span>
  <span class="k">private</span> <span class="nx">handler</span> <span class="o">=</span> <span class="p">(</span><span class="nx">data</span><span class="p">:</span> <span class="kr">any</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="cm">/* ... */</span> <span class="p">};</span>

  <span class="nx">mount</span><span class="p">()</span> <span class="p">{</span>
    <span class="nx">eventBus</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="nx">EVENTS</span><span class="p">.</span><span class="nx">DATA_LOADED</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nx">handler</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="nx">unmount</span><span class="p">()</span> <span class="p">{</span>
    <span class="nx">eventBus</span><span class="p">.</span><span class="nx">off</span><span class="p">(</span><span class="nx">EVENTS</span><span class="p">.</span><span class="nx">DATA_LOADED</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nx">handler</span><span class="p">);</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="3-带类型约束的事件定义">3. 带类型约束的事件定义</h3>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">interface</span> <span class="nx">EventMap</span> <span class="p">{</span>
  <span class="dl">'</span><span class="s1">user:login</span><span class="dl">'</span><span class="p">:</span> <span class="p">{</span> <span class="nl">userId</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> <span class="nl">timestamp</span><span class="p">:</span> <span class="kr">number</span> <span class="p">};</span>
  <span class="dl">'</span><span class="s1">user:logout</span><span class="dl">'</span><span class="p">:</span> <span class="k">void</span><span class="p">;</span>
  <span class="dl">'</span><span class="s1">data:loaded</span><span class="dl">'</span><span class="p">:</span> <span class="p">{</span> <span class="nl">items</span><span class="p">:</span> <span class="kr">any</span><span class="p">[];</span> <span class="nl">total</span><span class="p">:</span> <span class="kr">number</span> <span class="p">};</span>
<span class="p">}</span>

<span class="kd">class</span> <span class="nx">TypedEventBus</span> <span class="p">{</span>
  <span class="nx">on</span><span class="o">&lt;</span><span class="nx">K</span> <span class="kd">extends</span> <span class="kr">keyof</span> <span class="nx">EventMap</span><span class="o">&gt;</span><span class="p">(</span>
    <span class="nx">event</span><span class="p">:</span> <span class="nx">K</span><span class="p">,</span>
    <span class="nx">handler</span><span class="p">:</span> <span class="p">(</span><span class="nx">payload</span><span class="p">:</span> <span class="nx">EventMap</span><span class="p">[</span><span class="nx">K</span><span class="p">])</span> <span class="o">=&gt;</span> <span class="k">void</span>
  <span class="p">):</span> <span class="k">void</span> <span class="p">{</span> <span class="cm">/* ... */</span> <span class="p">}</span>

  <span class="nx">emit</span><span class="o">&lt;</span><span class="nx">K</span> <span class="kd">extends</span> <span class="kr">keyof</span> <span class="nx">EventMap</span><span class="o">&gt;</span><span class="p">(</span>
    <span class="nx">event</span><span class="p">:</span> <span class="nx">K</span><span class="p">,</span>
    <span class="nx">payload</span><span class="p">:</span> <span class="nx">EventMap</span><span class="p">[</span><span class="nx">K</span><span class="p">]</span>
  <span class="p">):</span> <span class="k">void</span> <span class="p">{</span> <span class="cm">/* ... */</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="与其他模式对比">与其他模式对比</h2>

<table>
  <thead>
    <tr>
      <th>模式</th>
      <th>耦合度</th>
      <th>通信方向</th>
      <th>适用规模</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>直接调用</td>
      <td>高</td>
      <td>一对一</td>
      <td>小型</td>
    </tr>
    <tr>
      <td>回调函数</td>
      <td>中</td>
      <td>一对一</td>
      <td>小型</td>
    </tr>
    <tr>
      <td>EventBus</td>
      <td>低</td>
      <td>多对多</td>
      <td>中型</td>
    </tr>
    <tr>
      <td>Redux/Vuex</td>
      <td>低</td>
      <td>单向流</td>
      <td>大型</td>
    </tr>
    <tr>
      <td>RxJS/响应式</td>
      <td>低</td>
      <td>流式</td>
      <td>复杂异步</td>
    </tr>
  </tbody>
</table>]]></content><author><name>mongoliu</name><email>nmgliuweiqiang@163.com</email></author><category term="技术组件" /><category term="EventBus" /><category term="设计模式" /><category term="解耦" /><summary type="html"><![CDATA[概述]]></summary></entry><entry><title type="html">KBEngine 学习笔记</title><link href="https://mongoliu.github.io/%E6%B8%B8%E6%88%8F%E5%BC%95%E6%93%8E-KBEngine/" rel="alternate" type="text/html" title="KBEngine 学习笔记" /><published>2025-12-28T00:00:00+00:00</published><updated>2025-12-28T00:00:00+00:00</updated><id>https://mongoliu.github.io/%E6%B8%B8%E6%88%8F%E5%BC%95%E6%93%8E-KBEngine</id><content type="html" xml:base="https://mongoliu.github.io/%E6%B8%B8%E6%88%8F%E5%BC%95%E6%93%8E-KBEngine/"><![CDATA[<p>KBEngine学习笔记</p>]]></content><author><name>mongoliu</name><email>nmgliuweiqiang@163.com</email></author><category term="游戏引擎" /><category term="KBEngine" /><category term="服务端" /><summary type="html"><![CDATA[KBEngine学习笔记]]></summary></entry><entry><title type="html">UE 学习笔记</title><link href="https://mongoliu.github.io/%E6%B8%B8%E6%88%8F%E5%BC%95%E6%93%8E-UE/" rel="alternate" type="text/html" title="UE 学习笔记" /><published>2025-12-28T00:00:00+00:00</published><updated>2025-12-28T00:00:00+00:00</updated><id>https://mongoliu.github.io/%E6%B8%B8%E6%88%8F%E5%BC%95%E6%93%8E-UE</id><content type="html" xml:base="https://mongoliu.github.io/%E6%B8%B8%E6%88%8F%E5%BC%95%E6%93%8E-UE/"><![CDATA[<p>UE学习笔记</p>]]></content><author><name>mongoliu</name><email>nmgliuweiqiang@163.com</email></author><category term="游戏引擎" /><category term="Unreal Engine" /><category term="C++" /><summary type="html"><![CDATA[UE学习笔记]]></summary></entry><entry><title type="html">Unity 学习笔记</title><link href="https://mongoliu.github.io/%E6%B8%B8%E6%88%8F%E5%BC%95%E6%93%8E-Unity/" rel="alternate" type="text/html" title="Unity 学习笔记" /><published>2025-12-28T00:00:00+00:00</published><updated>2025-12-28T00:00:00+00:00</updated><id>https://mongoliu.github.io/%E6%B8%B8%E6%88%8F%E5%BC%95%E6%93%8E-Unity</id><content type="html" xml:base="https://mongoliu.github.io/%E6%B8%B8%E6%88%8F%E5%BC%95%E6%93%8E-Unity/"><![CDATA[<h2 id="unity-基础概念">Unity 基础概念</h2>

<p>Unity 是一款跨平台的游戏引擎，支持 2D 和 3D 游戏开发。其核心基于组件式架构（Component-Based Architecture），所有游戏逻辑都通过组件附加到 GameObject 上实现。</p>

<h3 id="场景与游戏对象">场景与游戏对象</h3>

<ul>
  <li><strong>Scene</strong>：游戏的一个关卡或界面</li>
  <li><strong>GameObject</strong>：场景中的基本实体，本身不具有功能</li>
  <li><strong>Component</strong>：赋予 GameObject 行为和表现的模块</li>
</ul>

<h3 id="生命周期函数">生命周期函数</h3>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">void</span> <span class="nf">Awake</span><span class="p">()</span>    <span class="c1">// 对象初始化时调用</span>
<span class="k">void</span> <span class="nf">Start</span><span class="p">()</span>    <span class="c1">// 第一帧更新前调用</span>
<span class="k">void</span> <span class="nf">Update</span><span class="p">()</span>   <span class="c1">// 每帧调用</span>
<span class="k">void</span> <span class="nf">FixedUpdate</span><span class="p">()</span> <span class="c1">// 固定时间步长调用（物理）</span>
<span class="k">void</span> <span class="nf">LateUpdate</span><span class="p">()</span>  <span class="c1">// Update 之后调用</span>
</code></pre></div></div>

<h2 id="物理系统">物理系统</h2>

<p>Unity 内置 PhysX（3D）和 Box2D（2D）物理引擎。</p>

<h3 id="rigidbody">Rigidbody</h3>

<p>刚体组件使对象受物理引擎控制：</p>

<ul>
  <li><strong>Mass</strong>：质量</li>
  <li><strong>Drag</strong>：空气阻力</li>
  <li><strong>Use Gravity</strong>：是否受重力影响</li>
  <li><strong>Is Kinematic</strong>：是否仅通过脚本控制</li>
</ul>

<h3 id="碰撞检测">碰撞检测</h3>

<table>
  <thead>
    <tr>
      <th>回调函数</th>
      <th>触发条件</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>OnCollisionEnter</td>
      <td>碰撞开始</td>
    </tr>
    <tr>
      <td>OnCollisionStay</td>
      <td>碰撞持续</td>
    </tr>
    <tr>
      <td>OnCollisionExit</td>
      <td>碰撞结束</td>
    </tr>
    <tr>
      <td>OnTriggerEnter</td>
      <td>触发器进入</td>
    </tr>
  </tbody>
</table>

<h2 id="渲染管线">渲染管线</h2>

<p>Unity 提供三种渲染管线：</p>

<ul>
  <li><strong>Built-in</strong>：传统管线，兼容性好</li>
  <li><strong>URP</strong>：通用渲染管线，移动端友好</li>
  <li><strong>HDRP</strong>：高清渲染管线，追求画质</li>
</ul>

<h3 id="shader-基础">Shader 基础</h3>

<p>Unity 使用 ShaderLab 语法，配合 HLSL 编写着色器。Shader Graph 提供可视化着色器编辑。</p>

<h2 id="资源管理">资源管理</h2>

<h3 id="assetbundle">AssetBundle</h3>

<p>将资源打包为独立文件，实现动态加载和热更新。适用于大型项目的增量更新。</p>

<h3 id="addressable-assets">Addressable Assets</h3>

<p>新一代资源管理系统，基于 AssetBundle 封装，提供：</p>
<ul>
  <li>统一的资源引用方式</li>
  <li>自动依赖管理</li>
  <li>内存管理优化</li>
</ul>

<h2 id="性能优化">性能优化</h2>

<h3 id="drawcall-优化">DrawCall 优化</h3>

<ul>
  <li>静态/动态批处理</li>
  <li>GPU Instancing</li>
  <li>SRP Batcher</li>
</ul>

<h3 id="内存优化">内存优化</h3>

<ul>
  <li>对象池模式</li>
  <li>纹理压缩与 Mipmap</li>
  <li>合理使用 <code class="language-plaintext highlighter-rouge">Resources.UnloadUnusedAssets()</code></li>
</ul>]]></content><author><name>mongoliu</name><email>nmgliuweiqiang@163.com</email></author><category term="游戏引擎" /><category term="Unity" /><category term="C#" /><summary type="html"><![CDATA[Unity 基础概念]]></summary></entry></feed>