~cytrogen/blog-public

ref: 88eebf3dfdd8ab819fa1a84e1976a8a75d5af2b6 blog-public/posts/c4d6.html -rw-r--r-- 220.1 KiB
88eebf3dCytrogen Deploy 2026-02-19 08:34:27 4 days ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
<!DOCTYPE html><html lang="zh" data-theme="dark"><head><meta charset="utf-8"><meta name="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1"><title>用 JavaScript 自制 GameBoy 模拟器(下) · Cytrogen 的个人博客</title><meta name="robots" content="noindex"><meta name="description" content="在上篇文章中,我们已经构建了 GameBoy 模拟器的基础架构,包括 CPU 指令处理、内存管理、GPU 时序和图形渲染系统。虽然我们的模拟器能够显示图像,但还无法与用户进行交互。这篇文章将继续完善该模拟器。"><link rel="icon" href="../favicon.png"><link rel="canonical" href="https://cytrogen.icu/posts/c4d6.html"><link rel="webmention" href="https://webmention.io/cytrogen.icu/webmention"><link rel="me" href="https://m.otter.homes/@Cytrogen"><link rel="me" href="https://github.com/cytrogen"><meta name="fediverse:creator" content="@Cytrogen@m.otter.homes"><link rel="preload" href="../fonts/opensans-regular-latin.woff2" as="font" type="font/woff2" crossorigin="anonymous"><style>@font-face {
  font-family: 'Open Sans';
  src: url('../fonts/opensans-regular-latin.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
  size-adjust: 107%;
  ascent-override: 97%;
  descent-override: 25%;
  line-gap-override: 0%;
}
</style><script>(function() {
  try {
    // 优先级:用户选择 > 系统偏好 > 默认浅色
    const saved = localStorage.getItem('theme');
    const theme = saved || 
      (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
    
    document.documentElement.setAttribute('data-theme', theme);
    document.documentElement.style.colorScheme = theme;
  } catch (error) {
    // 失败时使用默认主题,不阻塞渲染
    document.documentElement.setAttribute('data-theme', 'light');
  }
})();
</script><link rel="stylesheet" href="../css/ares.css"><script data-netlify-skip-bundle="true">(function() {
  document.addEventListener('DOMContentLoaded', function() {
    const theme = document.documentElement.getAttribute('data-theme');
    const pageWrapper = document.getElementById('page-wrapper');
    if (pageWrapper && theme) {
      pageWrapper.setAttribute('data-theme', theme);
    }
  });
})();

</script><!-- hexo injector head_end start -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css">

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/hexo-math@4.0.0/dist/style.css">
<!-- hexo injector head_end end --><meta name="generator" content="Hexo 8.1.1"><link rel="alternate" href="atom.xml" title="Cytrogen 的个人博客" type="application/atom+xml">
</head><body><div id="page-wrapper"><a class="skip-link" href="#main-content">跳到主要内容</a><div class="wrap"><header><a class="logo-link" href="../index.html"><img src="../favicon.png" alt="logo"></a><div class="h-card visually-hidden"><img class="u-photo" src="https://cytrogen.icu/favicon.png" alt="Cytrogen"><a class="p-name u-url u-uid" href="https://cytrogen.icu">Cytrogen</a><p class="p-note">Cytrogen 的个人博客,Cytrogen's Blog</p><a class="u-url" rel="me noopener" target="_blank" href="https://m.otter.homes/@Cytrogen">Mastodon</a><a class="u-url" rel="me noopener" target="_blank" href="https://github.com/cytrogen">GitHub</a></div><nav class="site-nav"><div class="nav-main"><div class="nav-primary"><ul class="nav-list hidden-mobile"><li class="nav-item"><a class="nav-link" href="../index.html">首页</a></li></ul><div class="nav-tools"><div class="language-menu"><button class="language-toggle" type="button"><svg class="icon icon-globe" width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" focusable="false"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm7.5-6.923c-.67.204-1.335.82-1.887 1.855A7.97 7.97 0 0 0 5.145 4H7.5V1.077zM4.09 4a9.267 9.267 0 0 1 .64-1.539 6.7 6.7 0 0 1 .597-.933A7.025 7.025 0 0 0 2.255 4H4.09zm-.582 3.5c.03-.877.138-1.718.312-2.5H1.674a6.958 6.958 0 0 0-.656 2.5h2.49zM4.847 5a12.5 12.5 0 0 0-.338 2.5H7.5V5H4.847zM8.5 5v2.5h2.99a12.495 12.495 0 0 0-.337-2.5H8.5zM4.51 8.5a12.5 12.5 0 0 0 .337 2.5H7.5V8.5H4.51zm3.99 0V11h2.653c.187-.765.306-1.608.338-2.5H8.5zM5.145 12c.138.386.295.744.468 1.068.552 1.035 1.218 1.65 1.887 1.855V12H5.145zm.182 2.472a6.696 6.696 0 0 1-.597-.933A9.268 9.268 0 0 1 4.09 12H2.255a7.024 7.024 0 0 0 3.072 2.472zM3.82 11a13.652 13.652 0 0 1-.312-2.5h-2.49c.062.89.291 1.733.656 2.5H3.82zm6.853 3.472A7.024 7.024 0 0 0 13.745 12H11.91a9.27 9.27 0 0 1-.64 1.539 6.688 6.688 0 0 1-.597.933zM8.5 12v2.923c.67-.204 1.335-.82 1.887-1.855A7.97 7.97 0 0 0 10.855 12H8.5zm3.68-1h2.146c.365-.767.594-1.61.656-2.5h-2.49a13.65 13.65 0 0 1-.312 2.5zm2.802-3.5a6.959 6.959 0 0 0-.656-2.5H12.18c.174.782.282 1.623.312 2.5h2.49zM11.27 2.461c.247.464.462.98.64 1.539h1.835a7.024 7.024 0 0 0-3.072-2.472c.218.284.418.598.597.933zM10.855 4a7.966 7.966 0 0 0-.468-1.068C9.835 1.897 9.17 1.282 8.5 1.077V4h2.355z"></path></svg><span>中文</span></button><div class="language-dropdown"></div></div></div><div class="nav-controls"><div class="more-menu hidden-mobile"><button class="more-toggle" type="button"><span>更多</span><svg class="icon icon-chevron-down" width="12" height="12" viewBox="0 0 12 12" fill="currentColor" aria-hidden="true" focusable="false"><path d="M6 8.825c-.2 0-.4-.1-.5-.2l-3.3-3.3c-.3-.3-.3-.8 0-1.1s.8-.3 1.1 0l2.7 2.7 2.7-2.7c.3-.3.8-.3 1.1 0s.3.8 0 1.1l-3.3 3.3c-.1.1-.3.2-.5.2z"></path></svg></button><div class="more-dropdown"><ul class="dropdown-list"><li class="dropdown-item"><a class="nav-link" href="../archives/index.html">归档</a></li><li class="dropdown-item"><a class="nav-link" href="../categories/index.html">分类</a></li><li class="dropdown-item"><a class="nav-link" href="../tags/index.html">标签</a></li><li class="dropdown-item"><a class="nav-link" href="../about/index.html">关于</a></li><li class="dropdown-item"><a class="nav-link" href="../sitemap/index.html">领地地图</a></li></ul></div></div><div class="theme-switcher"><button class="theme-toggle" type="button" role="switch" aria-pressed="false" aria-label="切换主题"><div class="theme-icon moon-icon"><svg class="icon icon-moon" width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" focusable="false"><path d="M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278z"></path></svg></div><div class="theme-icon sun-icon"><svg class="icon icon-sun" width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" focusable="false"><path d="M8 11a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0 1a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z"></path></svg></div></button></div><details class="mobile-menu-details hidden-desktop"><summary class="hamburger-menu" aria-label="nav.menu"><svg class="icon icon-bars" width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" focusable="false"><path d="M2.5 12a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5z"></path></svg><span class="menu-text">nav.menu</span></summary><div class="mobile-menu-dropdown"><ul class="mobile-nav-list"><li class="mobile-nav-item"><a class="mobile-nav-link" href="../index.html">首页</a></li><li class="mobile-nav-item"><a class="mobile-nav-link" href="../archives/index.html">归档</a></li><li class="mobile-nav-item"><a class="mobile-nav-link" href="../categories/index.html">分类</a></li><li class="mobile-nav-item"><a class="mobile-nav-link" href="../tags/index.html">标签</a></li><li class="mobile-nav-item"><a class="mobile-nav-link" href="../about/index.html">关于</a></li><li class="mobile-nav-item"><a class="mobile-nav-link" href="../sitemap/index.html">领地地图</a></li></ul></div></details></div></div></div></nav></header><main class="container" id="main-content" tabindex="-1"><div class="post"><article class="post-block h-entry"><div class="post-meta p-author h-card visually-hidden"><img class="author-avatar u-photo" src="../favicon.png" alt="Cytrogen"><span class="p-name">Cytrogen</span><a class="u-url" href="https://cytrogen.icu">https://cytrogen.icu</a></div><a class="post-permalink u-url u-uid visually-hidden" href="https://cytrogen.icu/posts/c4d6.html">永久链接</a><div class="p-summary visually-hidden"><p><a href="/posts/4148/">上篇文章</a>中,我们已经构建了 GameBoy 模拟器的基础架构,包括 CPU 指令处理、内存管理、GPU 时序和图形渲染系统。虽然我们的模拟器能够显示图像,但还无法与用户进行交互。这篇文章将继续完善该模拟器。</p></div><div class="visually-hidden"><a class="p-category" href="../categories/%E7%BC%96%E7%A8%8B%E7%AC%94%E8%AE%B0/">编程笔记</a><a class="p-category" href="../tags/JavaScript/">JavaScript</a></div><h1 class="post-title p-name">用 JavaScript 自制 GameBoy 模拟器(下)</h1><div class="post-info"><time class="post-date dt-published" datetime="2025-07-05T04:00:00.000Z">7/5/2025</time><time class="dt-updated visually-hidden" datetime="2026-02-09T17:16:55.549Z"></time></div><div class="post-content e-content"><html><head></head><body><p><a href="/posts/4148/">上篇文章</a> 中,我们已经构建了 GameBoy 模拟器的基础架构,包括 CPU 指令处理、内存管理、GPU 时序和图形渲染系统。虽然我们的模拟器能够显示图像,但还无法与用户进行交互。这篇文章将继续完善该模拟器。</p>
<span id="more"></span>
<h1 id="6-输入系统让玩家与游戏交互"><a class="markdownIt-Anchor" href="#6-输入系统让玩家与游戏交互"></a> 6. 输入系统:让玩家与游戏交互</h1>
<p>与现代游戏手柄复杂的按键布局不同,GameBoy 的输入系统相对简单但非常巧妙。它只有 8 个按键,但通过矩阵式的硬件设计,能够有效地检测任意组合的按键状态。</p>
<h2 id="61-gameboy-输入硬件原理"><a class="markdownIt-Anchor" href="#61-gameboy-输入硬件原理"></a> 6.1. GameBoy 输入硬件原理</h2>
<p>GameBoy 的 8 个按键被组织为一个 2 列 × 4 行的矩阵:</p>
<table>
<thead>
<tr>
<th>列 1(功能键)</th>
<th>列 2(方向键)</th>
</tr>
</thead>
<tbody>
<tr>
<td>A 按键</td>
<td>右方向键</td>
</tr>
<tr>
<td>B 按键</td>
<td>左方向键</td>
</tr>
<tr>
<td>Select</td>
<td>上方向键</td>
</tr>
<tr>
<td>Start</td>
<td>下方向键</td>
</tr>
</tbody>
</table>
<p>这种矩阵设计的工作原理类似于键盘扫描:</p>
<ol>
<li>
<p><strong>列选择</strong>:CPU 通过写入 <code>0xFF00</code> 寄存器的第 4、5 位来选择要扫描的列</p>
<ul>
<li>写入 <code>0x10</code> 选择列 1(功能键)</li>
<li>写入 <code>0x20</code> 选择列 2(方向键)</li>
</ul>
</li>
<li>
<p><strong>行读取</strong>:选择列后,CPU 读取 <code>0xFF00</code> 寄存器的低 4 位,获取该列中各行的按键状态</p>
</li>
<li>
<p><strong>状态反转</strong>:硬件使用反转逻辑,即 <em>未按下 = 1,按下 = 0</em></p>
</li>
</ol>
<blockquote>
<p>为什么用矩阵?</p>
<p>如果直接为每个按键分配一位,需要 8 位。但用 2×4 矩阵只需要 6 根线(2 列 + 4 行),节省了硬件成本。</p>
<p>这在 1989 年是很重要的优化,每减少一根线都能降低成本。</p>
</blockquote>
<h2 id="62-输入控制器实现"><a class="markdownIt-Anchor" href="#62-输入控制器实现"></a> 6.2. 输入控制器实现</h2>
<p>在实现输入控制器之前,我们需要先设计完整的常量体系和架构。GameBoy 的输入系统虽然简单,但需要精确的位操作和状态管理。</p>
<p>输入系统的所有魔法数字都需要明确的常量定义:</p>
<figure class="highlight javascript"><figcaption><span>input.JS</span></figcaption><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 输入常量定义 - 每个数值都有其硬件意义</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">INPUT_CONSTANTS</span> = {</span><br><span class="line">  <span class="comment">// 寄存器地址</span></span><br><span class="line">  <span class="attr">JOYPAD_REGISTER</span>: <span class="number">0xFF00</span>,        <span class="comment">// GameBoy输入寄存器的内存地址</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// 列选择位掩码 - 控制扫描哪一列按键</span></span><br><span class="line">  <span class="attr">COLUMN_SELECT_MASK</span>: <span class="number">0x30</span>,       <span class="comment">// 二进制: 00110000 - 提取第4、5位</span></span><br><span class="line">  <span class="attr">COLUMN_1_SELECT</span>: <span class="number">0x10</span>,          <span class="comment">// 二进制: 00010000 - 选择功能键列</span></span><br><span class="line">  <span class="attr">COLUMN_2_SELECT</span>: <span class="number">0x20</span>,          <span class="comment">// 二进制: 00100000 - 选择方向键列</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// 行状态位掩码 - 各行按键的状态</span></span><br><span class="line">  <span class="attr">ROW_MASK</span>: <span class="number">0x0F</span>,                 <span class="comment">// 二进制: 00001111 - 提取低4位行状态</span></span><br><span class="line">  <span class="attr">ROW_0</span>: <span class="number">0x01</span>, <span class="attr">ROW_1</span>: <span class="number">0x02</span>,       <span class="comment">// 第0、1行</span></span><br><span class="line">  <span class="attr">ROW_2</span>: <span class="number">0x04</span>, <span class="attr">ROW_3</span>: <span class="number">0x08</span>,       <span class="comment">// 第2、3行</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// 默认状态:所有按键未按下</span></span><br><span class="line">  <span class="attr">DEFAULT_ROW_STATE</span>: <span class="number">0x0F</span>         <span class="comment">// 所有行为高电平(未按下状态)</span></span><br><span class="line">};</span><br></pre></td></tr></tbody></table></figure>
<p>首先创建输入控制器的核心类:</p>
<figure class="highlight javascript"><figcaption><span>input.JS</span></figcaption><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * GameBoy 输入控制器</span></span><br><span class="line"><span class="comment"> * 管理键盘输入矩阵和寄存器交互</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">GameBoyInputController</span> {</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params"></span>) {</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">initializeInputMatrix</span>();</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">initializeKeyMapping</span>();</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">initializeEventListeners</span>();</span><br><span class="line">  }</span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * 初始化输入矩阵</span></span><br><span class="line"><span class="comment">   * 2 列 × 4 行的按键矩阵,每列用一个字节表示</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="title function_">initializeInputMatrix</span>(<span class="params"></span>) {</span><br><span class="line">    <span class="comment">// 按键状态矩阵:[列1, 列2]</span></span><br><span class="line">    <span class="comment">// 每列 4 位,每位代表一行的状态</span></span><br><span class="line">    <span class="comment">// 0 = 按键按下,1 = 按键未按下</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">keyMatrix</span> = [</span><br><span class="line">      <span class="variable constant_">INPUT_CONSTANTS</span>.<span class="property">DEFAULT_ROW_STATE</span>, <span class="comment">// 列1:功能键列</span></span><br><span class="line">      <span class="variable constant_">INPUT_CONSTANTS</span>.<span class="property">DEFAULT_ROW_STATE</span>  <span class="comment">// 列2:方向键列</span></span><br><span class="line">    ];</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 当前选择的列</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">selectedColumn</span> = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 按键状态缓存(用于调试)</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">pressedKeys</span> = <span class="keyword">new</span> <span class="title class_">Set</span>();</span><br><span class="line">  }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>
<p>按键映射定义了 JavaScript 键码到 GameBoy 按键的对应关系:</p>
<figure class="highlight javascript"><figcaption><span>input.JS</span></figcaption><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 按键映射常量</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">KEY_MAPPING</span> = {</span><br><span class="line">  <span class="comment">// 功能键(列1)</span></span><br><span class="line">  <span class="attr">A_BUTTON</span>: { <span class="attr">column</span>: <span class="number">0</span>, <span class="attr">row</span>: <span class="number">0</span>, <span class="attr">keyCode</span>: <span class="number">90</span>, <span class="attr">name</span>: <span class="string">'A'</span>, <span class="attr">key</span>: <span class="string">'Z'</span> },        <span class="comment">// Z键</span></span><br><span class="line">  <span class="attr">B_BUTTON</span>: { <span class="attr">column</span>: <span class="number">0</span>, <span class="attr">row</span>: <span class="number">1</span>, <span class="attr">keyCode</span>: <span class="number">88</span>, <span class="attr">name</span>: <span class="string">'B'</span>, <span class="attr">key</span>: <span class="string">'X'</span> },        <span class="comment">// X键</span></span><br><span class="line">  <span class="attr">SELECT</span>: { <span class="attr">column</span>: <span class="number">0</span>, <span class="attr">row</span>: <span class="number">2</span>, <span class="attr">keyCode</span>: <span class="number">32</span>, <span class="attr">name</span>: <span class="string">'Select'</span>, <span class="attr">key</span>: <span class="string">'Space'</span> }, <span class="comment">// 空格键</span></span><br><span class="line">  <span class="attr">START</span>: { <span class="attr">column</span>: <span class="number">0</span>, <span class="attr">row</span>: <span class="number">3</span>, <span class="attr">keyCode</span>: <span class="number">13</span>, <span class="attr">name</span>: <span class="string">'Start'</span>, <span class="attr">key</span>: <span class="string">'Enter'</span> },   <span class="comment">// 回车键</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// 方向键(列2)</span></span><br><span class="line">  <span class="attr">RIGHT</span>: { <span class="attr">column</span>: <span class="number">1</span>, <span class="attr">row</span>: <span class="number">0</span>, <span class="attr">keyCode</span>: <span class="number">39</span>, <span class="attr">name</span>: <span class="string">'Right'</span>, <span class="attr">key</span>: <span class="string">'→'</span> },       <span class="comment">// 右箭头</span></span><br><span class="line">  <span class="attr">LEFT</span>: { <span class="attr">column</span>: <span class="number">1</span>, <span class="attr">row</span>: <span class="number">1</span>, <span class="attr">keyCode</span>: <span class="number">37</span>, <span class="attr">name</span>: <span class="string">'Left'</span>, <span class="attr">key</span>: <span class="string">'←'</span> },         <span class="comment">// 左箭头</span></span><br><span class="line">  <span class="attr">UP</span>: { <span class="attr">column</span>: <span class="number">1</span>, <span class="attr">row</span>: <span class="number">2</span>, <span class="attr">keyCode</span>: <span class="number">38</span>, <span class="attr">name</span>: <span class="string">'Up'</span>, <span class="attr">key</span>: <span class="string">'↑'</span> },             <span class="comment">// 上箭头</span></span><br><span class="line">  <span class="attr">DOWN</span>: { <span class="attr">column</span>: <span class="number">1</span>, <span class="attr">row</span>: <span class="number">3</span>, <span class="attr">keyCode</span>: <span class="number">40</span>, <span class="attr">name</span>: <span class="string">'Down'</span>, <span class="attr">key</span>: <span class="string">'↓'</span> }          <span class="comment">// 下箭头</span></span><br><span class="line">};</span><br></pre></td></tr></tbody></table></figure>
<p>这个映射表的设计考虑了几个重要因素:</p>
<ul>
<li><strong>人体工程学</strong>:A/B 键放在左手位置(Z/X),方向键用标准箭头</li>
<li><strong>避免冲突</strong>:避开常用的网页快捷键(如 Ctrl + C 等)</li>
<li><strong>易于记忆</strong>:功能键集中,方向键直观</li>
</ul>
<p>输入控制器需要将浏览器的键盘事件转换为 GameBoy 格式:</p>
<figure class="highlight javascript"><figcaption><span>input.JS</span></figcaption><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 初始化键盘事件监听</span></span><br><span class="line"><span class="comment"> * 建立浏览器事件与GameBoy输入系统的桥梁</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="title function_">initializeKeyMapping</span>(<span class="params"></span>) {</span><br><span class="line">  <span class="comment">// 创建keyCode到按键信息的快速查找表</span></span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">keyCodeMap</span> = {};</span><br><span class="line">  </span><br><span class="line">  <span class="title class_">Object</span>.<span class="title function_">values</span>(<span class="variable constant_">KEY_MAPPING</span>).<span class="title function_">forEach</span>(<span class="function"><span class="params">keyInfo</span> =&gt;</span> {</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">keyCodeMap</span>[keyInfo.<span class="property">keyCode</span>] = keyInfo;</span><br><span class="line">  });</span><br><span class="line"></span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'🗝️ 按键映射已初始化'</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="title function_">initializeEventListeners</span>(<span class="params"></span>) {</span><br><span class="line">  <span class="comment">// 绑定全局键盘事件</span></span><br><span class="line">  <span class="variable language_">document</span>.<span class="title function_">addEventListener</span>(<span class="string">'keydown'</span>, <span class="variable language_">this</span>.<span class="property">handleKeyDown</span>.<span class="title function_">bind</span>(<span class="variable language_">this</span>));</span><br><span class="line">  <span class="variable language_">document</span>.<span class="title function_">addEventListener</span>(<span class="string">'keyup'</span>, <span class="variable language_">this</span>.<span class="property">handleKeyUp</span>.<span class="title function_">bind</span>(<span class="variable language_">this</span>));</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 防止箭头键等按键的默认行为(如页面滚动)</span></span><br><span class="line">  <span class="variable language_">document</span>.<span class="title function_">addEventListener</span>(<span class="string">'keydown'</span>, <span class="variable language_">this</span>.<span class="property">preventDefaultKeys</span>.<span class="title function_">bind</span>(<span class="variable language_">this</span>));</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>
<p>键盘事件处理是输入系统的核心,需要正确实现状态反转逻辑:</p>
<figure class="highlight javascript"><figcaption><span>input.JS</span></figcaption><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 处理按键按下事件</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="title function_">handleKeyDown</span>(<span class="params">event</span>) {</span><br><span class="line">  <span class="keyword">const</span> keyInfo = <span class="variable language_">this</span>.<span class="property">keyCodeMap</span>[event.<span class="property">keyCode</span>];</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">if</span> (!keyInfo || <span class="variable language_">this</span>.<span class="property">pressedKeys</span>.<span class="title function_">has</span>(event.<span class="property">keyCode</span>)) {</span><br><span class="line">    <span class="keyword">return</span>; <span class="comment">// 不是我们关心的按键或重复按键</span></span><br><span class="line">  }</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 记录按键状态</span></span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">pressedKeys</span>.<span class="title function_">add</span>(event.<span class="property">keyCode</span>);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 更新矩阵状态:按下时对应位设为 0</span></span><br><span class="line">  <span class="keyword">const</span> rowMask = ~(<span class="number">1</span> &lt;&lt; keyInfo.<span class="property">row</span>); <span class="comment">// 创建掩码,对应位为0</span></span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">keyMatrix</span>[keyInfo.<span class="property">column</span>] &amp;= rowMask;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (<span class="variable language_">window</span>.<span class="property">DEBUG_MODE</span>) {</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`🎯 按键按下: <span class="subst">${keyInfo.name}</span> - 列<span class="subst">${keyInfo.column}</span><span class="subst">${keyInfo.row}</span>`</span>);</span><br><span class="line">  }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 处理按键释放事件</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="title function_">handleKeyUp</span>(<span class="params">event</span>) {</span><br><span class="line">  <span class="keyword">const</span> keyInfo = <span class="variable language_">this</span>.<span class="property">keyCodeMap</span>[event.<span class="property">keyCode</span>];</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">if</span> (!keyInfo || !<span class="variable language_">this</span>.<span class="property">pressedKeys</span>.<span class="title function_">has</span>(event.<span class="property">keyCode</span>)) {</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">  }</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 移除按键状态</span></span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">pressedKeys</span>.<span class="title function_">delete</span>(event.<span class="property">keyCode</span>);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 更新矩阵状态:释放时对应位设为 1</span></span><br><span class="line">  <span class="keyword">const</span> rowMask = <span class="number">1</span> &lt;&lt; keyInfo.<span class="property">row</span>;</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">keyMatrix</span>[keyInfo.<span class="property">column</span>] |= rowMask;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 防止按键默认行为</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="title function_">preventDefaultKeys</span>(<span class="params">event</span>) {</span><br><span class="line">  <span class="keyword">const</span> keyInfo = <span class="variable language_">this</span>.<span class="property">keyCodeMap</span>[event.<span class="property">keyCode</span>];</span><br><span class="line">  <span class="keyword">if</span> (keyInfo) {</span><br><span class="line">    event.<span class="title function_">preventDefault</span>(); <span class="comment">// 阻止箭头键滚动页面等</span></span><br><span class="line">  }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 统计和调试支持</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="title function_">initializeDebugInfo</span>(<span class="params"></span>) {</span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">stats</span> = {</span><br><span class="line">    <span class="attr">keyPressCount</span>: <span class="number">0</span>,</span><br><span class="line">    <span class="attr">keyReleaseCount</span>: <span class="number">0</span>,</span><br><span class="line">    <span class="attr">lastKeyPressed</span>: <span class="literal">null</span>,</span><br><span class="line">    <span class="attr">registerReads</span>: <span class="number">0</span>,</span><br><span class="line">    <span class="attr">registerWrites</span>: <span class="number">0</span></span><br><span class="line">  };</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>
<h4 id="621-位操作详解状态反转的数值示例"><a class="markdownIt-Anchor" href="#621-位操作详解状态反转的数值示例"></a> 6.2.1. 位操作详解:状态反转的数值示例</h4>
<p>GameBoy 的状态反转逻辑是整个输入系统的核心。让我们通过具体的数值示例来理解这个过程:</p>
<blockquote>
<p><strong>初始状态(所有按键未按下):</strong></p>
<figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">列1(功能键): keyMatrix[0] = 0x0F = 0b00001111</span><br><span class="line">列2(方向键): keyMatrix[1] = 0x0F = 0b00001111</span><br><span class="line"></span><br><span class="line">位含义:[bit3=Start, bit2=Select, bit1=B, bit0=A]</span><br><span class="line">      [bit3=Down,  bit2=Up,     bit1=Left, bit0=Right]</span><br></pre></td></tr></tbody></table></figure>
<p><strong>示例 1:按下 A 键(列 0 行 0)</strong></p>
<figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">1. 按键信息:A_BUTTON = { column: 0, row: 0, ... }</span><br><span class="line">2. 创建行掩码:rowMask = ~(1 &lt;&lt; 0) = ~0b00000001 = 0b11111110 = 0xFE</span><br><span class="line">3. 应用掩码:keyMatrix[0] &amp;= 0xFE</span><br><span class="line">   原值:0x0F = 0b00001111</span><br><span class="line">   掩码:0xFE = 0b11111110</span><br><span class="line">   结果:0x0E = 0b00001110  ← A 键现在为 0(按下状态)</span><br></pre></td></tr></tbody></table></figure>
<p><strong>示例 2:按下方向键 Up(列 1 行 2)</strong></p>
<figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">1. 按键信息:UP = { column: 1, row: 2, ... }</span><br><span class="line">2. 创建行掩码:rowMask = ~(1 &lt;&lt; 2) = ~0b00000100 = 0b11111011 = 0xFB</span><br><span class="line">3. 应用掩码:keyMatrix[1] &amp;= 0xFB</span><br><span class="line">   原值:0x0F = 0b00001111</span><br><span class="line">   掩码:0xFB = 0b11111011</span><br><span class="line">   结果:0x0B = 0b00001011  ← Up 键现在为 0(按下状态)</span><br></pre></td></tr></tbody></table></figure>
<p><strong>示例 3:同时按下 A 键和 B 键</strong></p>
<figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">初始:keyMatrix[0] = 0x0F = 0b00001111</span><br><span class="line"></span><br><span class="line">按下A键:</span><br><span class="line">rowMask = ~(1 &lt;&lt; 0) = 0xFE = 0b11111110</span><br><span class="line">keyMatrix[0] = 0x0F &amp; 0xFE = 0x0E = 0b00001110</span><br><span class="line"></span><br><span class="line">按下B键:</span><br><span class="line">rowMask = ~(1 &lt;&lt; 1) = 0xFD = 0b11111101  </span><br><span class="line">keyMatrix[0] = 0x0E &amp; 0xFD = 0x0C = 0b00001100</span><br><span class="line"></span><br><span class="line">最终状态:0b00001100 ← A 和 B 键都为 0(都被按下)</span><br></pre></td></tr></tbody></table></figure>
<p><strong>释放按键的过程</strong>(以释放 A 键为例):</p>
<figure class="highlight plaintext"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">1. 当前状态:keyMatrix[0] = 0x0C = 0b00001100 (A和B都按下)</span><br><span class="line">2. 创建恢复掩码:rowMask = 1 &lt;&lt; 0 = 0b00000001 = 0x01</span><br><span class="line">3. 应用或操作:keyMatrix[0] |= 0x01</span><br><span class="line">   当前:0x0C = 0b00001100</span><br><span class="line">   掩码:0x01 = 0b00000001</span><br><span class="line">   结果:0x0D = 0b00001101  ← A 键恢复为 1(未按下),B 键保持 0(仍按下)</span><br></pre></td></tr></tbody></table></figure>
</blockquote>
<p><strong>关键理解点:</strong></p>
<ol>
<li>
<p><strong>按下操作</strong>:使用 <code>&amp;=</code> 和反转掩码将指定位设为 0</p>
<ul>
<li><code>~(1 &lt;&lt; row)</code> 创建只有目标位为 0 的掩码</li>
<li>与运算确保只有目标位被清零</li>
</ul>
</li>
<li>
<p><strong>释放操作</strong>:使用 <code>|=</code> 和正常掩码将指定位设为 1</p>
<ul>
<li><code>(1 &lt;&lt; row)</code> 创建只有目标位为 1 的掩码</li>
<li>或运算确保只有目标位被置位</li>
</ul>
</li>
<li>
<p><strong>组合按键</strong>:多个按键可以同时处于按下状态,每个按键独立管理其对应的位</p>
</li>
</ol>
<p>这种设计让 GameBoy 能够准确检测任意按键组合,为复杂的游戏操作提供了基础。</p>
<blockquote>
<p><strong>为什么是反转的?</strong></p>
<p>GameBoy 使用 "低电平有效" 的硬件设计:</p>
<ul>
<li>按键未按下时,该位为高电平(1)</li>
<li>按键按下时,该位被拉低(0)</li>
</ul>
</blockquote>
<h2 id="63-寄存器接口实现"><a class="markdownIt-Anchor" href="#63-寄存器接口实现"></a> 6.3. 寄存器接口实现</h2>
<p>输入控制器的最终目的是为 CPU 提供符合 GameBoy 硬件规范的寄存器接口。CPU 通过读写 <code>0xFF00</code> 地址来获取按键状态和控制扫描模式。</p>
<p>输入控制器必须提供符合 GameBoy 硬件规范的寄存器接口:</p>
<figure class="highlight javascript"><figcaption><span>input.JS</span></figcaption><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 读取游戏手柄寄存器</span></span><br><span class="line"><span class="comment"> * 根据当前选择的列返回对应的按键状态</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="title function_">readJoypadRegister</span>(<span class="params"></span>) {</span><br><span class="line">  <span class="keyword">let</span> result = <span class="number">0xC0</span>; <span class="comment">// 高2位始终为1(未使用)</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// 添加列选择位</span></span><br><span class="line">  result |= <span class="variable language_">this</span>.<span class="property">selectedColumn</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 根据选择的列返回对应的行状态</span></span><br><span class="line">  <span class="keyword">switch</span> (<span class="variable language_">this</span>.<span class="property">selectedColumn</span>) {</span><br><span class="line">    <span class="keyword">case</span> <span class="variable constant_">INPUT_CONSTANTS</span>.<span class="property">COLUMN_1_SELECT</span>:</span><br><span class="line">      <span class="comment">// 功能键列</span></span><br><span class="line">      result |= <span class="variable language_">this</span>.<span class="property">keyMatrix</span>[<span class="number">0</span>] &amp; <span class="variable constant_">INPUT_CONSTANTS</span>.<span class="property">ROW_MASK</span>;</span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">case</span> <span class="variable constant_">INPUT_CONSTANTS</span>.<span class="property">COLUMN_2_SELECT</span>:</span><br><span class="line">      <span class="comment">// 方向键列  </span></span><br><span class="line">      result |= <span class="variable language_">this</span>.<span class="property">keyMatrix</span>[<span class="number">1</span>] &amp; <span class="variable constant_">INPUT_CONSTANTS</span>.<span class="property">ROW_MASK</span>;</span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line"></span><br><span class="line">    <span class="attr">default</span>:</span><br><span class="line">      <span class="comment">// 没有选择列,返回所有行为高电平</span></span><br><span class="line">      result |= <span class="variable constant_">INPUT_CONSTANTS</span>.<span class="property">ROW_MASK</span>;</span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line">  }</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> result;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 写入游戏手柄寄存器</span></span><br><span class="line"><span class="comment"> * 主要用于选择要读取的列</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="title function_">writeJoypadRegister</span>(<span class="params">value</span>) {</span><br><span class="line">  <span class="comment">// 提取列选择位</span></span><br><span class="line">  <span class="keyword">const</span> columnSelect = value &amp; <span class="variable constant_">INPUT_CONSTANTS</span>.<span class="property">COLUMN_SELECT_MASK</span>;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">if</span> (columnSelect !== <span class="variable language_">this</span>.<span class="property">selectedColumn</span>) {</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">selectedColumn</span> = columnSelect;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (<span class="variable language_">window</span>.<span class="property">DEBUG_MODE</span>) {</span><br><span class="line">      <span class="keyword">const</span> columnName = columnSelect === <span class="variable constant_">INPUT_CONSTANTS</span>.<span class="property">COLUMN_1_SELECT</span> ? <span class="string">'功能键'</span> : </span><br><span class="line">                        columnSelect === <span class="variable constant_">INPUT_CONSTANTS</span>.<span class="property">COLUMN_2_SELECT</span> ? <span class="string">'方向键'</span> : <span class="string">'无'</span>;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`✏️ 选择<span class="subst">${columnName}</span>列`</span>);</span><br><span class="line">    }</span><br><span class="line">  }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>
<p>这些函数是整个输入系统的核心,实现了完整的矩阵扫描逻辑:</p>
<ol>
<li><strong>高位设置</strong><code>0xC0</code> 设置第 6、7 位为 1(硬件规范要求)</li>
<li><strong>列选择回显</strong>:将当前选择的列值加入结果</li>
<li><strong>行状态返回</strong>:根据选择的列返回对应的 4 位行状态</li>
</ol>
<p>矩阵扫描的工作流程:</p>
<svg aria-roledescription="flowchart-v2" role="graphics-document document" viewBox="-8 -8 277.875 369.125" style="max-width: 277.875px;" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" width="100%" id="mermaid-1771490033718"><style>#mermaid-1771490033718{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-1771490033718 .error-icon{fill:#552222;}#mermaid-1771490033718 .error-text{fill:#552222;stroke:#552222;}#mermaid-1771490033718 .edge-thickness-normal{stroke-width:2px;}#mermaid-1771490033718 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-1771490033718 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-1771490033718 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-1771490033718 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-1771490033718 .marker{fill:#333333;stroke:#333333;}#mermaid-1771490033718 .marker.cross{stroke:#333333;}#mermaid-1771490033718 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-1771490033718 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-1771490033718 .cluster-label text{fill:#333;}#mermaid-1771490033718 .cluster-label span,#mermaid-1771490033718 p{color:#333;}#mermaid-1771490033718 .label text,#mermaid-1771490033718 span,#mermaid-1771490033718 p{fill:#333;color:#333;}#mermaid-1771490033718 .node rect,#mermaid-1771490033718 .node circle,#mermaid-1771490033718 .node ellipse,#mermaid-1771490033718 .node polygon,#mermaid-1771490033718 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-1771490033718 .flowchart-label text{text-anchor:middle;}#mermaid-1771490033718 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-1771490033718 .node .label{text-align:center;}#mermaid-1771490033718 .node.clickable{cursor:pointer;}#mermaid-1771490033718 .arrowheadPath{fill:#333333;}#mermaid-1771490033718 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-1771490033718 .flowchart-link{stroke:#333333;fill:none;}#mermaid-1771490033718 .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-1771490033718 .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-1771490033718 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-1771490033718 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-1771490033718 .cluster text{fill:#333;}#mermaid-1771490033718 .cluster span,#mermaid-1771490033718 p{color:#333;}#mermaid-1771490033718 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-1771490033718 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-1771490033718 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}</style><g><marker orient="auto" markerHeight="12" markerWidth="12" markerUnits="userSpaceOnUse" refY="5" refX="6" viewBox="0 0 10 10" class="marker flowchart" id="mermaid-1771490033718_flowchart-pointEnd"><path style="stroke-width: 1; stroke-dasharray: 1, 0;" class="arrowMarkerPath" d="M 0 0 L 10 5 L 0 10 z"></path></marker><marker orient="auto" markerHeight="12" markerWidth="12" markerUnits="userSpaceOnUse" refY="5" refX="4.5" viewBox="0 0 10 10" class="marker flowchart" id="mermaid-1771490033718_flowchart-pointStart"><path style="stroke-width: 1; stroke-dasharray: 1, 0;" class="arrowMarkerPath" d="M 0 5 L 10 10 L 10 0 z"></path></marker><marker orient="auto" markerHeight="11" markerWidth="11" markerUnits="userSpaceOnUse" refY="5" refX="11" viewBox="0 0 10 10" class="marker flowchart" id="mermaid-1771490033718_flowchart-circleEnd"><circle style="stroke-width: 1; stroke-dasharray: 1, 0;" class="arrowMarkerPath" r="5" cy="5" cx="5"></circle></marker><marker orient="auto" markerHeight="11" markerWidth="11" markerUnits="userSpaceOnUse" refY="5" refX="-1" viewBox="0 0 10 10" class="marker flowchart" id="mermaid-1771490033718_flowchart-circleStart"><circle style="stroke-width: 1; stroke-dasharray: 1, 0;" class="arrowMarkerPath" r="5" cy="5" cx="5"></circle></marker><marker orient="auto" markerHeight="11" markerWidth="11" markerUnits="userSpaceOnUse" refY="5.2" refX="12" viewBox="0 0 11 11" class="marker cross flowchart" id="mermaid-1771490033718_flowchart-crossEnd"><path style="stroke-width: 2; stroke-dasharray: 1, 0;" class="arrowMarkerPath" d="M 1,1 l 9,9 M 10,1 l -9,9"></path></marker><marker orient="auto" markerHeight="11" markerWidth="11" markerUnits="userSpaceOnUse" refY="5.2" refX="-1" viewBox="0 0 11 11" class="marker cross flowchart" id="mermaid-1771490033718_flowchart-crossStart"><path style="stroke-width: 2; stroke-dasharray: 1, 0;" class="arrowMarkerPath" d="M 1,1 l 9,9 M 10,1 l -9,9"></path></marker><g class="root"><g class="clusters"></g><g class="edgePaths"><path marker-end="url(#mermaid-1771490033718_flowchart-pointEnd)" style="fill:none;" class="edge-thickness-normal edge-pattern-solid flowchart-link LS-A LE-B" id="L-A-B-0" d="M133.859,33L133.859,37.167C133.859,41.333,133.859,49.667,133.925,57.2C133.991,64.734,134.123,71.467,134.189,74.834L134.255,78.201"></path><path marker-end="url(#mermaid-1771490033718_flowchart-pointEnd)" style="fill:none;" class="edge-thickness-normal edge-pattern-solid flowchart-link LS-B LE-C" id="L-B-C-0" d="M109.434,144.699L95.854,154.437C82.274,164.175,55.113,183.65,41.533,198.171C27.953,212.692,27.953,222.258,27.953,227.042L27.953,231.825"></path><path marker-end="url(#mermaid-1771490033718_flowchart-pointEnd)" style="fill:none;" class="edge-thickness-normal edge-pattern-solid flowchart-link LS-B LE-D" id="L-B-D-0" d="M134.359,169.625L134.276,175.208C134.193,180.792,134.026,191.958,133.943,202.325C133.859,212.692,133.859,222.258,133.859,227.042L133.859,231.825"></path><path marker-end="url(#mermaid-1771490033718_flowchart-pointEnd)" style="fill:none;" class="edge-thickness-normal edge-pattern-solid flowchart-link LS-B LE-E" id="L-B-E-0" d="M158.991,144.994L171.966,154.682C184.942,164.371,210.893,183.748,223.868,198.22C236.844,212.692,236.844,222.258,236.844,227.042L236.844,231.825"></path><path marker-end="url(#mermaid-1771490033718_flowchart-pointEnd)" style="fill:none;" class="edge-thickness-normal edge-pattern-solid flowchart-link LS-C LE-F" id="L-C-F-0" d="M27.953,270.125L27.953,274.292C27.953,278.458,27.953,286.792,37.795,294.815C47.636,302.838,67.32,310.551,77.161,314.407L87.003,318.264"></path><path marker-end="url(#mermaid-1771490033718_flowchart-pointEnd)" style="fill:none;" class="edge-thickness-normal edge-pattern-solid flowchart-link LS-D LE-F" id="L-D-F-0" d="M133.859,270.125L133.859,274.292C133.859,278.458,133.859,286.792,133.859,294.242C133.859,301.692,133.859,308.258,133.859,311.542L133.859,314.825"></path><path marker-end="url(#mermaid-1771490033718_flowchart-pointEnd)" style="fill:none;" class="edge-thickness-normal edge-pattern-solid flowchart-link LS-E LE-F" id="L-E-F-0" d="M236.844,270.125L236.844,274.292C236.844,278.458,236.844,286.792,227.323,294.795C217.803,302.798,198.762,310.471,189.241,314.308L179.721,318.144"></path></g><g class="edgeLabels"><g class="edgeLabel"><g transform="translate(0, 0)" class="label"><foreignObject height="0" width="0"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="edgeLabel"></span></div></foreignObject></g></g><g transform="translate(27.953125, 203.125)" class="edgeLabel"><g transform="translate(-17.3515625, -9)" class="label"><foreignObject height="18" width="34.703125"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="edgeLabel">0x10</span></div></foreignObject></g></g><g transform="translate(133.859375, 203.125)" class="edgeLabel"><g transform="translate(-17.3515625, -9)" class="label"><foreignObject height="18" width="34.703125"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="edgeLabel">0x20</span></div></foreignObject></g></g><g transform="translate(236.84375, 203.125)" class="edgeLabel"><g transform="translate(-5.84375, -9)" class="label"><foreignObject height="18" width="11.6875"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="edgeLabel"> 其他</span></div></foreignObject></g></g><g class="edgeLabel"><g transform="translate(0, 0)" class="label"><foreignObject height="0" width="0"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="edgeLabel"></span></div></foreignObject></g></g><g class="edgeLabel"><g transform="translate(0, 0)" class="label"><foreignObject height="0" width="0"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="edgeLabel"></span></div></foreignObject></g></g><g class="edgeLabel"><g transform="translate(0, 0)" class="label"><foreignObject height="0" width="0"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="edgeLabel"></span></div></foreignObject></g></g></g><g class="nodes"><g transform="translate(133.859375, 16.5)" data-id="A" data-node="true" id="flowchart-A-0" class="node default default flowchart-label"><rect height="33" width="78" y="-16.5" x="-39" ry="0" rx="0" style="" class="basic label-container"></rect><g transform="translate(-31.5, -9)" style="" class="label"><rect></rect><foreignObject height="18" width="63"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel"> CPU 写入列选择</span></div></foreignObject></g></g><g transform="translate(133.859375, 126.0625)" data-id="B" data-node="true" id="flowchart-B-1" class="node default default flowchart-label"><polygon style="" transform="translate(-43.0625,43.0625)" class="label-container" points="43.0625,0 86.125,-43.0625 43.0625,-86.125 0,-43.0625"></polygon><g transform="translate(-19.0625, -9)" style="" class="label"><rect></rect><foreignObject height="18" width="38.125"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">选择哪一列?</span></div></foreignObject></g></g><g transform="translate(27.953125, 253.625)" data-id="C" data-node="true" id="flowchart-C-3" class="node default default flowchart-label"><rect height="33" width="55.90625" y="-16.5" x="-27.953125" ry="0" rx="0" style="" class="basic label-container"></rect><g transform="translate(-20.453125, -9)" style="" class="label"><rect></rect><foreignObject height="18" width="40.90625"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">返回功能键状态</span></div></foreignObject></g></g><g transform="translate(133.859375, 253.625)" data-id="D" data-node="true" id="flowchart-D-5" class="node default default flowchart-label"><rect height="33" width="55.90625" y="-16.5" x="-27.953125" ry="0" rx="0" style="" class="basic label-container"></rect><g transform="translate(-20.453125, -9)" style="" class="label"><rect></rect><foreignObject height="18" width="40.90625"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">返回方向键状态</span></div></foreignObject></g></g><g transform="translate(236.84375, 253.625)" data-id="E" data-node="true" id="flowchart-E-7" class="node default default flowchart-label"><rect height="33" width="50.0625" y="-16.5" x="-25.03125" ry="0" rx="0" style="" class="basic label-container"></rect><g transform="translate(-17.53125, -9)" style="" class="label"><rect></rect><foreignObject height="18" width="35.0625"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel">返回全高电平</span></div></foreignObject></g></g><g transform="translate(133.859375, 336.625)" data-id="F" data-node="true" id="flowchart-F-9" class="node default default flowchart-label"><rect height="33" width="83.84375" y="-16.5" x="-41.921875" ry="0" rx="0" style="" class="basic label-container"></rect><g transform="translate(-34.421875, -9)" style="" class="label"><rect></rect><foreignObject height="18" width="68.84375"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml"><span class="nodeLabel"> CPU 读取按键状态</span></div></foreignObject></g></g></g></g></g></svg>
<h2 id="64-mmu-集成"><a class="markdownIt-Anchor" href="#64-mmu-集成"></a> 6.4. MMU 集成</h2>
<p>在 MMU 中完善输入寄存器的处理逻辑:</p>
<figure class="highlight javascript"><figcaption><span>input.JS</span></figcaption><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 读取I/O寄存器 - 支持硬件组件路由</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="title function_">readIORegister</span>(<span class="params">address</span>) {</span><br><span class="line">  <span class="comment">// 🎮 按键输入寄存器 (0xFF00) - 第6章实现 ✅ 已完成</span></span><br><span class="line">  <span class="keyword">if</span> (address === <span class="variable constant_">IO_REGISTERS</span>.<span class="property">JOYPAD</span>) {</span><br><span class="line">    <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">inputController</span> &amp;&amp; <span class="keyword">typeof</span> <span class="variable language_">this</span>.<span class="property">inputController</span>.<span class="property">readJoypadRegister</span> === <span class="string">'function'</span>) {</span><br><span class="line">      <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">inputController</span>.<span class="title function_">readJoypadRegister</span>();</span><br><span class="line">    } <span class="keyword">else</span> {</span><br><span class="line">      <span class="comment">// 如果输入控制器未连接,返回默认值(所有按键未按下)</span></span><br><span class="line">      <span class="keyword">return</span> <span class="number">0xFF</span>; <span class="comment">// 默认:所有按键未按下</span></span><br><span class="line">    }</span><br><span class="line">  }</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// ... 其他寄存器处理 ...</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 写入I/O寄存器 - 支持硬件组件路由</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="title function_">writeIORegister</span>(<span class="params">address, value</span>) {</span><br><span class="line">  <span class="comment">// 🎮 按键输入寄存器 (0xFF00) - 第6章实现 ✅ 已完成</span></span><br><span class="line">  <span class="keyword">if</span> (address === <span class="variable constant_">IO_REGISTERS</span>.<span class="property">JOYPAD</span>) {</span><br><span class="line">    <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">inputController</span> &amp;&amp; <span class="keyword">typeof</span> <span class="variable language_">this</span>.<span class="property">inputController</span>.<span class="property">writeJoypadRegister</span> === <span class="string">'function'</span>) {</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">inputController</span>.<span class="title function_">writeJoypadRegister</span>(value);</span><br><span class="line">    }</span><br><span class="line">    <span class="comment">// 保存到本地数组</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">ioRegisters</span>[address - <span class="variable constant_">MEMORY_REGIONS</span>.<span class="property">IO_START</span>] = value;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">  }</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// ... 其他寄存器处理 ...</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 获取I/O寄存器状态 (调试用)</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@returns</span> {<span class="type">Object</span>} I/O寄存器状态</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="title function_">getIORegisterStatus</span>(<span class="params"></span>) {</span><br><span class="line">  <span class="keyword">const</span> status = {</span><br><span class="line">    <span class="attr">gpu</span>: {</span><br><span class="line">      <span class="attr">lcdc</span>: <span class="variable language_">this</span>.<span class="title function_">readByte</span>(<span class="variable constant_">IO_REGISTERS</span>.<span class="property">LCDC</span>).<span class="title function_">toString</span>(<span class="number">16</span>).<span class="title function_">padStart</span>(<span class="number">2</span>, <span class="string">'0'</span>),</span><br><span class="line">      <span class="attr">stat</span>: <span class="variable language_">this</span>.<span class="title function_">readByte</span>(<span class="variable constant_">IO_REGISTERS</span>.<span class="property">STAT</span>).<span class="title function_">toString</span>(<span class="number">16</span>).<span class="title function_">padStart</span>(<span class="number">2</span>, <span class="string">'0'</span>),</span><br><span class="line">      <span class="attr">scy</span>: <span class="variable language_">this</span>.<span class="title function_">readByte</span>(<span class="variable constant_">IO_REGISTERS</span>.<span class="property">SCY</span>),</span><br><span class="line">      <span class="attr">scx</span>: <span class="variable language_">this</span>.<span class="title function_">readByte</span>(<span class="variable constant_">IO_REGISTERS</span>.<span class="property">SCX</span>),</span><br><span class="line">      <span class="attr">ly</span>: <span class="variable language_">this</span>.<span class="title function_">readByte</span>(<span class="variable constant_">IO_REGISTERS</span>.<span class="property">LY</span>),</span><br><span class="line">      <span class="attr">bgp</span>: <span class="variable language_">this</span>.<span class="title function_">readByte</span>(<span class="variable constant_">IO_REGISTERS</span>.<span class="property">BGP</span>).<span class="title function_">toString</span>(<span class="number">16</span>).<span class="title function_">padStart</span>(<span class="number">2</span>, <span class="string">'0'</span>)</span><br><span class="line">    },</span><br><span class="line">    <span class="attr">input</span>: {</span><br><span class="line">      <span class="attr">joypad</span>: <span class="variable language_">this</span>.<span class="title function_">readByte</span>(<span class="variable constant_">IO_REGISTERS</span>.<span class="property">JOYPAD</span>).<span class="title function_">toString</span>(<span class="number">16</span>).<span class="title function_">padStart</span>(<span class="number">2</span>, <span class="string">'0'</span>)</span><br><span class="line">    },</span><br><span class="line">    <span class="attr">interrupts</span>: {</span><br><span class="line">      <span class="attr">ie</span>: <span class="variable language_">this</span>.<span class="title function_">readByte</span>(<span class="variable constant_">IO_REGISTERS</span>.<span class="property">IE</span>).<span class="title function_">toString</span>(<span class="number">16</span>).<span class="title function_">padStart</span>(<span class="number">2</span>, <span class="string">'0'</span>),</span><br><span class="line">      <span class="attr">if</span>: <span class="variable language_">this</span>.<span class="title function_">readByte</span>(<span class="variable constant_">IO_REGISTERS</span>.<span class="property">IF</span>).<span class="title function_">toString</span>(<span class="number">16</span>).<span class="title function_">padStart</span>(<span class="number">2</span>, <span class="string">'0'</span>)</span><br><span class="line">    }</span><br><span class="line">  };</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 如果输入控制器可用,添加详细的输入状态</span></span><br><span class="line">  <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">inputController</span>) {</span><br><span class="line">    status.<span class="property">input</span>.<span class="property">detailed</span> = <span class="variable language_">this</span>.<span class="property">inputController</span>.<span class="title function_">getInputStatus</span>();</span><br><span class="line">  }</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> status;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>
<h2 id="65-系统集成"><a class="markdownIt-Anchor" href="#65-系统集成"></a> 6.5. 系统集成</h2>
<p>在主系统中创建和连接输入控制器:</p>
<figure class="highlight javascript"><figcaption><span>gameboy.JS</span></figcaption><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 创建硬件组件实例</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">async</span> <span class="title function_">createHardwareComponents</span>(<span class="params"></span>) {</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// 创建输入控制器</span></span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">inputController</span> = <span class="keyword">new</span> <span class="title class_">GameBoyInputController</span>();</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'✅ 输入控制器已创建'</span>);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 设置全局引用(兼容性)</span></span><br><span class="line">  <span class="variable language_">window</span>.<span class="property">InputController</span> = <span class="variable language_">this</span>.<span class="property">inputController</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 连接硬件组件</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="title function_">connectComponents</span>(<span class="params"></span>) {</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// 连接输入控制器到 MMU</span></span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">mmu</span>.<span class="title function_">connectHardware</span>(<span class="string">'input'</span>, <span class="variable language_">this</span>.<span class="property">inputController</span>);</span><br><span class="line"></span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'🔗 硬件组件连接完成'</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 检查必要的类是否已加载</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="title function_">checkRequiredClasses</span>(<span class="params"></span>) {</span><br><span class="line">  <span class="keyword">const</span> requiredClasses = [</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">    { <span class="attr">name</span>: <span class="string">'GameBoyInputController'</span>, <span class="attr">class</span>: <span class="variable language_">window</span>.<span class="property">GameBoyInputController</span> }</span><br><span class="line">  ];</span><br><span class="line"></span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 系统重置</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="title function_">reset</span>(<span class="params"></span>) {</span><br><span class="line">  <span class="keyword">try</span> {</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">inputController</span>) <span class="variable language_">this</span>.<span class="property">inputController</span>.<span class="title function_">reset</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">  } <span class="keyword">catch</span> (error) {</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">  }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 获取系统状态</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@returns</span> {<span class="type">Object</span>} 系统状态对象</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="title function_">getStatus</span>(<span class="params"></span>) {</span><br><span class="line">  <span class="keyword">const</span> status = {</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">    <span class="attr">components</span>: {</span><br><span class="line">      <span class="comment">// ...</span></span><br><span class="line">      <span class="attr">input</span>: <span class="variable language_">this</span>.<span class="property">inputController</span> ? <span class="variable language_">this</span>.<span class="property">inputController</span>.<span class="title function_">getInputStatus</span>() : <span class="string">'未初始化'</span></span><br><span class="line">    }</span><br><span class="line">  };</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 获取调试信息</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@returns</span> {<span class="type">string</span>} 格式化的调试信息</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="title function_">getDebugInfo</span>(<span class="params"></span>) {</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// 输入控制器状态</span></span><br><span class="line">  <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">inputController</span>) {</span><br><span class="line">    debugInfo += <span class="string">`\n\n🎮 输入控制器状态:\n<span class="subst">${<span class="variable language_">this</span>.inputController.getDebugInfo()}</span>`</span>;</span><br><span class="line">  }</span><br><span class="line"></span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 销毁系统(清理资源)</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="title function_">destroy</span>(<span class="params"></span>) {</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">  <span class="variable language_">this</span>.<span class="property">inputController</span> = <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">  <span class="keyword">if</span> (<span class="variable language_">window</span>.<span class="property">inputController</span>) <span class="keyword">delete</span> <span class="variable language_">window</span>.<span class="property">InputController</span>;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>
<h2 id="66-调试和测试工具"><a class="markdownIt-Anchor" href="#66-调试和测试工具"></a> 6.6. 调试和测试工具</h2>
<p>输入系统包含了完整的调试功能:</p>
<figure class="highlight javascript"><figcaption><span>input.JS</span></figcaption><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 获取完整的输入状态(调试专用)</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="title function_">getInputStatus</span>(<span class="params"></span>) {</span><br><span class="line">  <span class="keyword">const</span> pressedKeyNames = <span class="title class_">Array</span>.<span class="title function_">from</span>(<span class="variable language_">this</span>.<span class="property">pressedKeys</span>).<span class="title function_">map</span>(<span class="function"><span class="params">keyCode</span> =&gt;</span> {</span><br><span class="line">    <span class="keyword">const</span> keyInfo = <span class="variable language_">this</span>.<span class="property">keyCodeMap</span>[keyCode];</span><br><span class="line">    <span class="keyword">return</span> keyInfo ? keyInfo.<span class="property">name</span> : <span class="string">`Unknown(<span class="subst">${keyCode}</span>)`</span>;</span><br><span class="line">  });</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> {</span><br><span class="line">    <span class="attr">selectedColumn</span>: <span class="variable language_">this</span>.<span class="property">selectedColumn</span>,</span><br><span class="line">    <span class="attr">keyMatrix</span>: [...<span class="variable language_">this</span>.<span class="property">keyMatrix</span>],           <span class="comment">// 复制数组避免外部修改</span></span><br><span class="line">    <span class="attr">pressedKeys</span>: pressedKeyNames,</span><br><span class="line">    <span class="attr">stats</span>: { ...<span class="variable language_">this</span>.<span class="property">stats</span> }                 <span class="comment">// 复制对象</span></span><br><span class="line">  };</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 获取按键映射信息(用于UI显示)</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="title function_">getKeyMappings</span>(<span class="params"></span>) {</span><br><span class="line">  <span class="keyword">return</span> <span class="title class_">Object</span>.<span class="title function_">values</span>(<span class="variable constant_">KEY_MAPPING</span>).<span class="title function_">map</span>(<span class="function"><span class="params">keyInfo</span> =&gt;</span> ({</span><br><span class="line">    <span class="attr">gameboyKey</span>: keyInfo.<span class="property">name</span>,</span><br><span class="line">    <span class="attr">keyboardKey</span>: keyInfo.<span class="property">key</span>,</span><br><span class="line">    <span class="attr">keyCode</span>: keyInfo.<span class="property">keyCode</span>,</span><br><span class="line">    <span class="attr">isPressed</span>: <span class="variable language_">this</span>.<span class="property">pressedKeys</span>.<span class="title function_">has</span>(keyInfo.<span class="property">keyCode</span>)</span><br><span class="line">  }));</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 模拟按键操作(测试专用)</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="title function_">simulateKeyPress</span>(<span class="params">keyName</span>) {</span><br><span class="line">  <span class="keyword">const</span> keyInfo = <span class="title class_">Object</span>.<span class="title function_">values</span>(<span class="variable constant_">KEY_MAPPING</span>).<span class="title function_">find</span>(<span class="function"><span class="params">info</span> =&gt;</span> info.<span class="property">name</span> === keyName);</span><br><span class="line">  <span class="keyword">if</span> (keyInfo) {</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">handleKeyDown</span>({ <span class="attr">keyCode</span>: keyInfo.<span class="property">keyCode</span>, <span class="attr">preventDefault</span>: <span class="function">() =&gt;</span> {} });</span><br><span class="line">  }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="title function_">simulateKeyRelease</span>(<span class="params">keyName</span>) {</span><br><span class="line">  <span class="keyword">const</span> keyInfo = <span class="title class_">Object</span>.<span class="title function_">values</span>(<span class="variable constant_">KEY_MAPPING</span>).<span class="title function_">find</span>(<span class="function"><span class="params">info</span> =&gt;</span> info.<span class="property">name</span> === keyName);</span><br><span class="line">  <span class="keyword">if</span> (keyInfo) {</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">handleKeyUp</span>({ <span class="attr">keyCode</span>: keyInfo.<span class="property">keyCode</span> });</span><br><span class="line">  }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 获取调试信息字符串</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="title function_">getDebugInfo</span>(<span class="params"></span>) {</span><br><span class="line">  <span class="keyword">const</span> status = <span class="variable language_">this</span>.<span class="title function_">getInputStatus</span>();</span><br><span class="line">  <span class="keyword">const</span> mappings = <span class="variable language_">this</span>.<span class="title function_">getKeyMappings</span>();</span><br><span class="line"></span><br><span class="line">  <span class="keyword">let</span> debugInfo = <span class="string">`🎮 输入控制器状态:</span></span><br><span class="line"><span class="string">选择列: 0x<span class="subst">${status.selectedColumn.toString(<span class="number">16</span>).padStart(<span class="number">2</span>, <span class="string">'0'</span>)}</span></span></span><br><span class="line"><span class="string">矩阵状态: [0x<span class="subst">${status.keyMatrix[<span class="number">0</span>].toString(<span class="number">16</span>)}</span>, 0x<span class="subst">${status.keyMatrix[<span class="number">1</span>].toString(<span class="number">16</span>)}</span>]</span></span><br><span class="line"><span class="string">当前按下: <span class="subst">${status.pressedKeys.join(<span class="string">', '</span>) || <span class="string">'无'</span>}</span></span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">📊 统计信息:</span></span><br><span class="line"><span class="string">按键次数: <span class="subst">${status.stats.keyPressCount}</span></span></span><br><span class="line"><span class="string">释放次数: <span class="subst">${status.stats.keyReleaseCount}</span></span></span><br><span class="line"><span class="string">寄存器读取: <span class="subst">${status.stats.registerReads}</span></span></span><br><span class="line"><span class="string">寄存器写入: <span class="subst">${status.stats.registerWrites}</span></span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">🗝️ 按键映射:`</span>;</span><br><span class="line"></span><br><span class="line">  mappings.<span class="title function_">forEach</span>(<span class="function"><span class="params">mapping</span> =&gt;</span> {</span><br><span class="line">    <span class="keyword">const</span> statusIcon = mapping.<span class="property">isPressed</span> ? <span class="string">'🔴'</span> : <span class="string">'⚪'</span>;</span><br><span class="line">    debugInfo += <span class="string">`\n<span class="subst">${statusIcon}</span> <span class="subst">${mapping.gameboyKey}</span>: <span class="subst">${mapping.keyboardKey}</span>`</span>;</span><br><span class="line">  });</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> debugInfo;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>
<p>自动化测试功能可以验证所有按键:</p>
<figure class="highlight javascript"><figcaption><span>gameboy.JS</span></figcaption><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 测试输入系统(调试用)</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="title function_">testInputSystem</span>(<span class="params"></span>) {</span><br><span class="line">  <span class="keyword">if</span> (!<span class="variable language_">this</span>.<span class="property">inputController</span>) {</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">warn</span>(<span class="string">'⚠️ 输入控制器未初始化'</span>);</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">  }</span><br><span class="line"></span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'🧪 开始输入系统测试...'</span>);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 测试各个按键</span></span><br><span class="line">  <span class="keyword">const</span> testKeys = [<span class="string">'A'</span>, <span class="string">'B'</span>, <span class="string">'Start'</span>, <span class="string">'Select'</span>, <span class="string">'Up'</span>, <span class="string">'Down'</span>, <span class="string">'Left'</span>, <span class="string">'Right'</span>];</span><br><span class="line">  </span><br><span class="line">  testKeys.<span class="title function_">forEach</span>(<span class="function">(<span class="params">keyName, index</span>) =&gt;</span> {</span><br><span class="line">    <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> {</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">inputController</span>.<span class="title function_">simulateKeyPress</span>(keyName);</span><br><span class="line">      </span><br><span class="line">      <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> {</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">inputController</span>.<span class="title function_">simulateKeyRelease</span>(keyName);</span><br><span class="line">      }, <span class="number">200</span>);</span><br><span class="line">    }, index * <span class="number">500</span>);</span><br><span class="line">  });</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>
<h2 id="67-用户界面完善"><a class="markdownIt-Anchor" href="#67-用户界面完善"></a> 6.7. 用户界面完善</h2>
<p>为了提高用户体验,需要实现一个可视化的虚拟手柄。虚拟手柄的核心在于将触摸 / 鼠标事件转换为 GameBoy 按键事件::</p>
<figure class="highlight html"><figcaption><span>index.HTML</span></figcaption><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- 输入控制 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"controls"</span>&gt;</span></span><br><span class="line">  <span class="comment">&lt;!-- 虚拟按键显示 --&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"control-section"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"control-title"</span>&gt;</span>🕹️ 虚拟手柄<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"virtual-gamepad"</span> <span class="attr">id</span>=<span class="string">"virtual-gamepad"</span>&gt;</span></span><br><span class="line">      <span class="comment">&lt;!-- 方向键 --&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"dpad-container"</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"dpad"</span>&gt;</span></span><br><span class="line">          <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"dpad-button dpad-up"</span> <span class="attr">data-key</span>=<span class="string">"Up"</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">          <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"dpad-middle"</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"dpad-button dpad-left"</span> <span class="attr">data-key</span>=<span class="string">"Left"</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"dpad-center"</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"dpad-button dpad-right"</span> <span class="attr">data-key</span>=<span class="string">"Right"</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">          <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">          <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"dpad-button dpad-down"</span> <span class="attr">data-key</span>=<span class="string">"Down"</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"dpad-label"</span>&gt;</span>方向键<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">      </span><br><span class="line">      <span class="comment">&lt;!-- 功能键 --&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"action-buttons"</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"button-row"</span>&gt;</span></span><br><span class="line">          <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"action-button"</span> <span class="attr">data-key</span>=<span class="string">"Select"</span>&gt;</span>SELECT<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">          <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"action-button"</span> <span class="attr">data-key</span>=<span class="string">"Start"</span>&gt;</span>START<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"button-row"</span>&gt;</span></span><br><span class="line">          <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"action-button action-b"</span> <span class="attr">data-key</span>=<span class="string">"B"</span>&gt;</span>B<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">          <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"action-button action-a"</span> <span class="attr">data-key</span>=<span class="string">"A"</span>&gt;</span>A<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">&lt;!-- 调试和图形控制 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"controls"</span>&gt;</span> ... <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></tbody></table></figure>
<p>样式:</p>
<figure class="highlight html"><figcaption><span>index.HTML</span></figcaption><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br><span class="line">252</span><br><span class="line">253</span><br><span class="line">254</span><br><span class="line">255</span><br><span class="line">256</span><br><span class="line">257</span><br><span class="line">258</span><br><span class="line">259</span><br><span class="line">260</span><br><span class="line">261</span><br><span class="line">262</span><br><span class="line">263</span><br><span class="line">264</span><br><span class="line">265</span><br><span class="line">266</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">style</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css">  <span class="comment">/* 虚拟手柄样式 */</span></span></span><br><span class="line"><span class="language-css">  <span class="selector-class">.virtual-gamepad</span> {</span></span><br><span class="line"><span class="language-css">    <span class="attribute">display</span>: flex;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">justify-content</span>: space-between;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">align-items</span>: center;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">padding</span>: <span class="number">20px</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">background</span>: <span class="built_in">linear-gradient</span>(<span class="number">145deg</span>, <span class="number">#5a6b4d</span>, <span class="number">#6a7b5d</span>);</span></span><br><span class="line"><span class="language-css">    <span class="attribute">border-radius</span>: <span class="number">15px</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">margin</span>: <span class="number">10px</span> <span class="number">0</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">min-height</span>: <span class="number">120px</span>;</span></span><br><span class="line"><span class="language-css">  }</span></span><br><span class="line"><span class="language-css"></span></span><br><span class="line"><span class="language-css">  <span class="selector-class">.dpad-container</span> {</span></span><br><span class="line"><span class="language-css">    <span class="attribute">display</span>: flex;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">flex-direction</span>: column;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">align-items</span>: center;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">gap</span>: <span class="number">10px</span>;</span></span><br><span class="line"><span class="language-css">  }</span></span><br><span class="line"><span class="language-css"></span></span><br><span class="line"><span class="language-css">  <span class="selector-class">.dpad</span> {</span></span><br><span class="line"><span class="language-css">    <span class="attribute">display</span>: grid;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">grid-template-areas</span>: </span></span><br><span class="line"><span class="language-css">      <span class="string">". up ."</span></span></span><br><span class="line"><span class="language-css">      <span class="string">"left center right"</span></span></span><br><span class="line"><span class="language-css">      <span class="string">". down ."</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">grid-template-columns</span>: <span class="number">30px</span> <span class="number">30px</span> <span class="number">30px</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">grid-template-rows</span>: <span class="number">30px</span> <span class="number">30px</span> <span class="number">30px</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">gap</span>: <span class="number">2px</span>;</span></span><br><span class="line"><span class="language-css">  }</span></span><br><span class="line"><span class="language-css"></span></span><br><span class="line"><span class="language-css">  <span class="selector-class">.dpad-button</span> {</span></span><br><span class="line"><span class="language-css">    <span class="attribute">display</span>: flex;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">align-items</span>: center;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">justify-content</span>: center;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">background</span>: <span class="built_in">linear-gradient</span>(<span class="number">145deg</span>, <span class="number">#7a8a6d</span>, <span class="number">#6a7a5d</span>);</span></span><br><span class="line"><span class="language-css">    <span class="attribute">border-radius</span>: <span class="number">5px</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">font-size</span>: <span class="number">16px</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">font-weight</span>: bold;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">color</span>: <span class="number">#2c5530</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">cursor</span>: pointer;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">transition</span>: all <span class="number">0.1s</span> ease;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">box-shadow</span>: <span class="number">0</span> <span class="number">2px</span> <span class="number">4px</span> <span class="built_in">rgba</span>(<span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0.2</span>);</span></span><br><span class="line"><span class="language-css">    <span class="attribute">user-select</span>: none;</span></span><br><span class="line"><span class="language-css">  }</span></span><br><span class="line"><span class="language-css"></span></span><br><span class="line"><span class="language-css">  <span class="selector-class">.dpad-button</span><span class="selector-pseudo">:hover</span> {</span></span><br><span class="line"><span class="language-css">    <span class="attribute">background</span>: <span class="built_in">linear-gradient</span>(<span class="number">145deg</span>, <span class="number">#8a9a7d</span>, <span class="number">#7a8a6d</span>);</span></span><br><span class="line"><span class="language-css">  }</span></span><br><span class="line"><span class="language-css"></span></span><br><span class="line"><span class="language-css">  <span class="selector-class">.dpad-button</span><span class="selector-class">.pressed</span> {</span></span><br><span class="line"><span class="language-css">    <span class="attribute">background</span>: <span class="built_in">linear-gradient</span>(<span class="number">145deg</span>, <span class="number">#5a7c4a</span>, <span class="number">#4a6c3a</span>);</span></span><br><span class="line"><span class="language-css">    <span class="attribute">color</span>: <span class="number">#fff</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">transform</span>: <span class="built_in">translateY</span>(<span class="number">1px</span>);</span></span><br><span class="line"><span class="language-css">    <span class="attribute">box-shadow</span>: <span class="number">0</span> <span class="number">1px</span> <span class="number">2px</span> <span class="built_in">rgba</span>(<span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0.3</span>);</span></span><br><span class="line"><span class="language-css">  }</span></span><br><span class="line"><span class="language-css"></span></span><br><span class="line"><span class="language-css">  <span class="selector-class">.dpad-up</span> { <span class="attribute">grid-area</span>: up; }</span></span><br><span class="line"><span class="language-css">  <span class="selector-class">.dpad-down</span> { <span class="attribute">grid-area</span>: down; }</span></span><br><span class="line"><span class="language-css">  <span class="selector-class">.dpad-left</span> { <span class="attribute">grid-area</span>: left; }</span></span><br><span class="line"><span class="language-css">  <span class="selector-class">.dpad-right</span> { <span class="attribute">grid-area</span>: right; }</span></span><br><span class="line"><span class="language-css"></span></span><br><span class="line"><span class="language-css">  <span class="selector-class">.dpad-middle</span> {</span></span><br><span class="line"><span class="language-css">    <span class="attribute">grid-area</span>: center;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">display</span>: flex;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">align-items</span>: center;</span></span><br><span class="line"><span class="language-css">  }</span></span><br><span class="line"><span class="language-css"></span></span><br><span class="line"><span class="language-css">  <span class="selector-class">.dpad-center</span> {</span></span><br><span class="line"><span class="language-css">    <span class="attribute">width</span>: <span class="number">30px</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">height</span>: <span class="number">30px</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">background</span>: <span class="built_in">linear-gradient</span>(<span class="number">145deg</span>, <span class="number">#4a5c3a</span>, <span class="number">#3a4c2a</span>);</span></span><br><span class="line"><span class="language-css">    <span class="attribute">border-radius</span>: <span class="number">50%</span>;</span></span><br><span class="line"><span class="language-css">  }</span></span><br><span class="line"><span class="language-css"></span></span><br><span class="line"><span class="language-css">  <span class="selector-class">.dpad-label</span> {</span></span><br><span class="line"><span class="language-css">    <span class="attribute">font-size</span>: <span class="number">10px</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">color</span>: <span class="number">#6b7a5e</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">text-align</span>: center;</span></span><br><span class="line"><span class="language-css">  }</span></span><br><span class="line"><span class="language-css"></span></span><br><span class="line"><span class="language-css">  <span class="selector-class">.action-buttons</span> {</span></span><br><span class="line"><span class="language-css">    <span class="attribute">display</span>: flex;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">flex-direction</span>: column;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">gap</span>: <span class="number">15px</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">align-items</span>: center;</span></span><br><span class="line"><span class="language-css">  }</span></span><br><span class="line"><span class="language-css"></span></span><br><span class="line"><span class="language-css">  <span class="selector-class">.button-row</span> {</span></span><br><span class="line"><span class="language-css">    <span class="attribute">display</span>: flex;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">gap</span>: <span class="number">15px</span>;</span></span><br><span class="line"><span class="language-css">  }</span></span><br><span class="line"><span class="language-css"></span></span><br><span class="line"><span class="language-css">  <span class="selector-class">.action-button</span> {</span></span><br><span class="line"><span class="language-css">    <span class="attribute">width</span>: <span class="number">50px</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">height</span>: <span class="number">50px</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">border-radius</span>: <span class="number">50%</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">display</span>: flex;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">align-items</span>: center;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">justify-content</span>: center;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">font-size</span>: <span class="number">12px</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">font-weight</span>: bold;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">color</span>: <span class="number">#2c5530</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">cursor</span>: pointer;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">transition</span>: all <span class="number">0.1s</span> ease;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">box-shadow</span>: <span class="number">0</span> <span class="number">3px</span> <span class="number">6px</span> <span class="built_in">rgba</span>(<span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0.2</span>);</span></span><br><span class="line"><span class="language-css">    <span class="attribute">user-select</span>: none;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">background</span>: <span class="built_in">linear-gradient</span>(<span class="number">145deg</span>, <span class="number">#7a8a6d</span>, <span class="number">#6a7a5d</span>);</span></span><br><span class="line"><span class="language-css">  }</span></span><br><span class="line"><span class="language-css"></span></span><br><span class="line"><span class="language-css">  <span class="selector-class">.action-button</span><span class="selector-pseudo">:hover</span> {</span></span><br><span class="line"><span class="language-css">    <span class="attribute">background</span>: <span class="built_in">linear-gradient</span>(<span class="number">145deg</span>, <span class="number">#8a9a7d</span>, <span class="number">#7a8a6d</span>);</span></span><br><span class="line"><span class="language-css">  }</span></span><br><span class="line"><span class="language-css"></span></span><br><span class="line"><span class="language-css">  <span class="selector-class">.action-button</span><span class="selector-class">.pressed</span> {</span></span><br><span class="line"><span class="language-css">    <span class="attribute">background</span>: <span class="built_in">linear-gradient</span>(<span class="number">145deg</span>, <span class="number">#5a7c4a</span>, <span class="number">#4a6c3a</span>);</span></span><br><span class="line"><span class="language-css">    <span class="attribute">color</span>: <span class="number">#fff</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">transform</span>: <span class="built_in">translateY</span>(<span class="number">2px</span>);</span></span><br><span class="line"><span class="language-css">    <span class="attribute">box-shadow</span>: <span class="number">0</span> <span class="number">1px</span> <span class="number">3px</span> <span class="built_in">rgba</span>(<span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0.3</span>);</span></span><br><span class="line"><span class="language-css">  }</span></span><br><span class="line"><span class="language-css"></span></span><br><span class="line"><span class="language-css">  <span class="selector-class">.action-a</span> {</span></span><br><span class="line"><span class="language-css">    <span class="attribute">background</span>: <span class="built_in">linear-gradient</span>(<span class="number">145deg</span>, <span class="number">#a56565</span>, <span class="number">#955555</span>);</span></span><br><span class="line"><span class="language-css">    <span class="attribute">color</span>: <span class="number">#fff</span>;</span></span><br><span class="line"><span class="language-css">  }</span></span><br><span class="line"><span class="language-css"></span></span><br><span class="line"><span class="language-css">  <span class="selector-class">.action-a</span><span class="selector-pseudo">:hover</span> {</span></span><br><span class="line"><span class="language-css">    <span class="attribute">background</span>: <span class="built_in">linear-gradient</span>(<span class="number">145deg</span>, <span class="number">#b57575</span>, <span class="number">#a56565</span>);</span></span><br><span class="line"><span class="language-css">  }</span></span><br><span class="line"><span class="language-css"></span></span><br><span class="line"><span class="language-css">  <span class="selector-class">.action-a</span><span class="selector-class">.pressed</span> {</span></span><br><span class="line"><span class="language-css">    <span class="attribute">background</span>: <span class="built_in">linear-gradient</span>(<span class="number">145deg</span>, <span class="number">#854545</span>, <span class="number">#753535</span>);</span></span><br><span class="line"><span class="language-css">  }</span></span><br><span class="line"><span class="language-css"></span></span><br><span class="line"><span class="language-css">  <span class="selector-class">.action-b</span> {</span></span><br><span class="line"><span class="language-css">    <span class="attribute">background</span>: <span class="built_in">linear-gradient</span>(<span class="number">145deg</span>, <span class="number">#5a8a5a</span>, <span class="number">#4a7a4a</span>);</span></span><br><span class="line"><span class="language-css">    <span class="attribute">color</span>: <span class="number">#fff</span>;</span></span><br><span class="line"><span class="language-css">  }</span></span><br><span class="line"><span class="language-css"></span></span><br><span class="line"><span class="language-css">  <span class="selector-class">.action-b</span><span class="selector-pseudo">:hover</span> {</span></span><br><span class="line"><span class="language-css">    <span class="attribute">background</span>: <span class="built_in">linear-gradient</span>(<span class="number">145deg</span>, <span class="number">#6a9a6a</span>, <span class="number">#5a8a5a</span>);</span></span><br><span class="line"><span class="language-css">  }</span></span><br><span class="line"><span class="language-css"></span></span><br><span class="line"><span class="language-css">  <span class="selector-class">.action-b</span><span class="selector-class">.pressed</span> {</span></span><br><span class="line"><span class="language-css">    <span class="attribute">background</span>: <span class="built_in">linear-gradient</span>(<span class="number">145deg</span>, <span class="number">#3a6a3a</span>, <span class="number">#2a5a2a</span>);</span></span><br><span class="line"><span class="language-css">  }</span></span><br><span class="line"><span class="language-css"></span></span><br><span class="line"><span class="language-css">  <span class="comment">/* 按键映射显示 */</span></span></span><br><span class="line"><span class="language-css">  <span class="selector-class">.key-mapping-overlay</span> {</span></span><br><span class="line"><span class="language-css">    <span class="attribute">position</span>: fixed;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">top</span>: <span class="number">0</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">left</span>: <span class="number">0</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">width</span>: <span class="number">100%</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">height</span>: <span class="number">100%</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">background</span>: <span class="built_in">rgba</span>(<span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0.8</span>);</span></span><br><span class="line"><span class="language-css">    <span class="attribute">display</span>: none;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">justify-content</span>: center;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">align-items</span>: center;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">z-index</span>: <span class="number">1000</span>;</span></span><br><span class="line"><span class="language-css">  }</span></span><br><span class="line"><span class="language-css"></span></span><br><span class="line"><span class="language-css">  <span class="selector-class">.key-mapping-content</span> {</span></span><br><span class="line"><span class="language-css">    <span class="attribute">background</span>: <span class="built_in">linear-gradient</span>(<span class="number">145deg</span>, <span class="number">#8b956d</span>, <span class="number">#9bb583</span>);</span></span><br><span class="line"><span class="language-css">    <span class="attribute">border-radius</span>: <span class="number">20px</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">padding</span>: <span class="number">30px</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">max-width</span>: <span class="number">500px</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">width</span>: <span class="number">90%</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">max-height</span>: <span class="number">80%</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">overflow-y</span>: auto;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">position</span>: relative;</span></span><br><span class="line"><span class="language-css">  }</span></span><br><span class="line"><span class="language-css"></span></span><br><span class="line"><span class="language-css">  <span class="selector-class">.key-mapping-title</span> {</span></span><br><span class="line"><span class="language-css">    <span class="attribute">font-size</span>: <span class="number">20px</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">font-weight</span>: bold;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">color</span>: <span class="number">#2c5530</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">text-align</span>: center;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">margin-bottom</span>: <span class="number">20px</span>;</span></span><br><span class="line"><span class="language-css">  }</span></span><br><span class="line"><span class="language-css"></span></span><br><span class="line"><span class="language-css">  <span class="selector-class">.key-mapping-table</span> {</span></span><br><span class="line"><span class="language-css">    <span class="attribute">width</span>: <span class="number">100%</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">border-collapse</span>: collapse;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">margin-bottom</span>: <span class="number">20px</span>;</span></span><br><span class="line"><span class="language-css">  }</span></span><br><span class="line"><span class="language-css"></span></span><br><span class="line"><span class="language-css">  <span class="selector-class">.key-mapping-table</span> <span class="selector-tag">th</span>,</span></span><br><span class="line"><span class="language-css">  <span class="selector-class">.key-mapping-table</span> <span class="selector-tag">td</span> {</span></span><br><span class="line"><span class="language-css">    <span class="attribute">padding</span>: <span class="number">10px</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">text-align</span>: left;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">border-bottom</span>: <span class="number">1px</span> solid <span class="number">#5a6b4d</span>;</span></span><br><span class="line"><span class="language-css">  }</span></span><br><span class="line"><span class="language-css"></span></span><br><span class="line"><span class="language-css">  <span class="selector-class">.key-mapping-table</span> <span class="selector-tag">th</span> {</span></span><br><span class="line"><span class="language-css">    <span class="attribute">background</span>: <span class="built_in">linear-gradient</span>(<span class="number">145deg</span>, <span class="number">#7a8a6d</span>, <span class="number">#6a7a5d</span>);</span></span><br><span class="line"><span class="language-css">    <span class="attribute">color</span>: <span class="number">#2c5530</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">font-weight</span>: bold;</span></span><br><span class="line"><span class="language-css">  }</span></span><br><span class="line"><span class="language-css"></span></span><br><span class="line"><span class="language-css">  <span class="selector-class">.key-mapping-table</span> <span class="selector-tag">td</span> {</span></span><br><span class="line"><span class="language-css">    <span class="attribute">background</span>: <span class="built_in">linear-gradient</span>(<span class="number">145deg</span>, <span class="number">#9bb583</span>, <span class="number">#8b956d</span>);</span></span><br><span class="line"><span class="language-css">    <span class="attribute">color</span>: <span class="number">#2c5530</span>;</span></span><br><span class="line"><span class="language-css">  }</span></span><br><span class="line"><span class="language-css"></span></span><br><span class="line"><span class="language-css">  <span class="selector-class">.key-status</span> {</span></span><br><span class="line"><span class="language-css">    <span class="attribute">display</span>: inline-block;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">width</span>: <span class="number">12px</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">height</span>: <span class="number">12px</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">border-radius</span>: <span class="number">50%</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">margin-right</span>: <span class="number">8px</span>;</span></span><br><span class="line"><span class="language-css">  }</span></span><br><span class="line"><span class="language-css"></span></span><br><span class="line"><span class="language-css">  <span class="selector-class">.key-status</span><span class="selector-class">.pressed</span> {</span></span><br><span class="line"><span class="language-css">    <span class="attribute">background</span>: <span class="number">#ff4444</span>;</span></span><br><span class="line"><span class="language-css">  }</span></span><br><span class="line"><span class="language-css"></span></span><br><span class="line"><span class="language-css">  <span class="selector-class">.key-status</span><span class="selector-class">.released</span> {</span></span><br><span class="line"><span class="language-css">    <span class="attribute">background</span>: <span class="number">#44ff44</span>;</span></span><br><span class="line"><span class="language-css">  }</span></span><br><span class="line"><span class="language-css"></span></span><br><span class="line"><span class="language-css">  <span class="selector-class">.close-btn</span> {</span></span><br><span class="line"><span class="language-css">    <span class="attribute">position</span>: absolute;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">top</span>: <span class="number">10px</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">right</span>: <span class="number">15px</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">background</span>: none;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">border</span>: none;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">font-size</span>: <span class="number">24px</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">color</span>: <span class="number">#2c5530</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">cursor</span>: pointer;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">padding</span>: <span class="number">0</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">width</span>: <span class="number">30px</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">height</span>: <span class="number">30px</span>;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">display</span>: flex;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">align-items</span>: center;</span></span><br><span class="line"><span class="language-css">    <span class="attribute">justify-content</span>: center;</span></span><br><span class="line"><span class="language-css">  }</span></span><br><span class="line"><span class="language-css"></span></span><br><span class="line"><span class="language-css">  <span class="selector-class">.close-btn</span><span class="selector-pseudo">:hover</span> {</span></span><br><span class="line"><span class="language-css">    <span class="attribute">background</span>: <span class="built_in">rgba</span>(<span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0.1</span>);</span></span><br><span class="line"><span class="language-css">    <span class="attribute">border-radius</span>: <span class="number">50%</span>;</span></span><br><span class="line"><span class="language-css">  }</span></span><br><span class="line"><span class="language-css"></span></span><br><span class="line"><span class="language-css">  <span class="comment">/* 隐藏虚拟手柄 */</span></span></span><br><span class="line"><span class="language-css">  <span class="selector-class">.virtual-gamepad</span><span class="selector-class">.hidden</span> {</span></span><br><span class="line"><span class="language-css">    <span class="attribute">display</span>: none;</span></span><br><span class="line"><span class="language-css">  }</span></span><br><span class="line"><span class="language-css"></span></span><br><span class="line"><span class="language-css">  <span class="comment">/* 响应式调整 */</span></span></span><br><span class="line"><span class="language-css">  <span class="keyword">@media</span> (<span class="attribute">max-width</span>: <span class="number">768px</span>) {</span></span><br><span class="line"><span class="language-css">    <span class="selector-class">.virtual-gamepad</span> {</span></span><br><span class="line"><span class="language-css">      <span class="attribute">flex-direction</span>: column;</span></span><br><span class="line"><span class="language-css">      <span class="attribute">gap</span>: <span class="number">20px</span>;</span></span><br><span class="line"><span class="language-css">      <span class="attribute">padding</span>: <span class="number">15px</span>;</span></span><br><span class="line"><span class="language-css">    }</span></span><br><span class="line"><span class="language-css">    </span></span><br><span class="line"><span class="language-css">    <span class="selector-class">.action-button</span> {</span></span><br><span class="line"><span class="language-css">      <span class="attribute">width</span>: <span class="number">40px</span>;</span></span><br><span class="line"><span class="language-css">      <span class="attribute">height</span>: <span class="number">40px</span>;</span></span><br><span class="line"><span class="language-css">      <span class="attribute">font-size</span>: <span class="number">10px</span>;</span></span><br><span class="line"><span class="language-css">    }</span></span><br><span class="line"><span class="language-css">    </span></span><br><span class="line"><span class="language-css">    <span class="selector-class">.dpad-button</span> {</span></span><br><span class="line"><span class="language-css">      <span class="attribute">font-size</span>: <span class="number">14px</span>;</span></span><br><span class="line"><span class="language-css">    }</span></span><br><span class="line"><span class="language-css">  }</span></span><br><span class="line"><span class="language-css"></span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br></pre></td></tr></tbody></table></figure>
<p>虚拟手柄支持鼠标和触摸操作:</p>
<figure class="highlight html"><figcaption><span>index.HTML</span></figcaption><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">let</span> virtualGamepadState = {</span></span><br><span class="line"><span class="language-javascript">    pressedButtons = <span class="keyword">new</span> <span class="title class_">Set</span>(),</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">mouseDown</span>: <span class="literal">false</span>,</span></span><br><span class="line"><span class="language-javascript">  };</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">function</span> <span class="title function_">handleVirtualButtonDown</span>(<span class="params">event</span>) {</span></span><br><span class="line"><span class="language-javascript">    event.<span class="title function_">preventDefault</span>();</span></span><br><span class="line"><span class="language-javascript">    </span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">const</span> button = event.<span class="property">currentTarget</span>;</span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">const</span> keyName = button.<span class="property">dataset</span>.<span class="property">key</span>;</span></span><br><span class="line"><span class="language-javascript">    </span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">if</span> (!keyName || virtualGamepadState.<span class="property">pressedButtons</span>.<span class="title function_">has</span>(keyName)) {</span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">return</span>;</span></span><br><span class="line"><span class="language-javascript">    }</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">    <span class="comment">// 添加视觉反馈</span></span></span><br><span class="line"><span class="language-javascript">    button.<span class="property">classList</span>.<span class="title function_">add</span>(<span class="string">'pressed'</span>);</span></span><br><span class="line"><span class="language-javascript">    virtualGamepadState.<span class="property">pressedButtons</span>.<span class="title function_">add</span>(keyName);</span></span><br><span class="line"><span class="language-javascript">    </span></span><br><span class="line"><span class="language-javascript">    <span class="comment">// 模拟按键按下</span></span></span><br><span class="line"><span class="language-javascript">    <span class="title function_">simulateKeyPress</span>(keyName);</span></span><br><span class="line"><span class="language-javascript">  }</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">function</span> <span class="title function_">simulateKeyPress</span>(<span class="params">keyName</span>) {</span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">if</span> (!systemInstance) <span class="keyword">return</span>;</span></span><br><span class="line"><span class="language-javascript">    </span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">try</span> {</span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">const</span> system = <span class="variable language_">window</span>.<span class="property">GameBoySystemController</span>.<span class="title function_">getInstance</span>();</span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">if</span> (system &amp;&amp; system.<span class="property">inputController</span>) {</span></span><br><span class="line"><span class="language-javascript">        system.<span class="property">inputController</span>.<span class="title function_">simulateKeyPress</span>(keyName);</span></span><br><span class="line"><span class="language-javascript">      }</span></span><br><span class="line"><span class="language-javascript">    } <span class="keyword">catch</span> (error) {</span></span><br><span class="line"><span class="language-javascript">      <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">`模拟按键失败: <span class="subst">${error.message}</span>`</span>);</span></span><br><span class="line"><span class="language-javascript">    }</span></span><br><span class="line"><span class="language-javascript">  }</span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></tbody></table></figure>
<p><code>index.html</code> 中添加用于测试和调试的板块:</p>
<figure class="highlight html"><figcaption><span>index.HTML</span></figcaption><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- 输入控制 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"controls"</span>&gt;</span></span><br><span class="line">  <span class="comment">&lt;!-- 输入测试 --&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"control-section"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"control-title"</span>&gt;</span>🎮 输入控制<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">button</span> <span class="attr">class</span>=<span class="string">"btn"</span> <span class="attr">id</span>=<span class="string">"test-input-btn"</span> <span class="attr">disabled</span>&gt;</span>测试输入系统<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">button</span> <span class="attr">class</span>=<span class="string">"btn"</span> <span class="attr">id</span>=<span class="string">"show-input-btn"</span> <span class="attr">disabled</span>&gt;</span>显示输入状态<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">button</span> <span class="attr">class</span>=<span class="string">"btn"</span> <span class="attr">id</span>=<span class="string">"show-keymapping-btn"</span>&gt;</span>显示按键映射<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">button</span> <span class="attr">class</span>=<span class="string">"btn"</span> <span class="attr">id</span>=<span class="string">"toggle-virtual-keys-btn"</span>&gt;</span>切换虚拟按键<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"keyboard-hint"</span>&gt;</span></span><br><span class="line">      快捷键:<span class="tag">&lt;<span class="name">span</span> <span class="attr">class</span>=<span class="string">"key"</span>&gt;</span>I<span class="tag">&lt;/<span class="name">span</span>&gt;</span> 输入状态 <span class="tag">&lt;<span class="name">span</span> <span class="attr">class</span>=<span class="string">"key"</span>&gt;</span>K<span class="tag">&lt;/<span class="name">span</span>&gt;</span> 按键映射</span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">  </span><br><span class="line">  <span class="comment">&lt;!-- 虚拟按键显示 --&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"control-section"</span>&gt;</span> ... <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></tbody></table></figure>
<p>页面最底部添加按键映射弹窗:</p>
<figure class="highlight html"><figcaption><span>index.HTML</span></figcaption><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- 按键映射弹窗 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"key-mapping-overlay"</span> <span class="attr">id</span>=<span class="string">"key-mapping-overlay"</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"key-mapping-content"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">button</span> <span class="attr">class</span>=<span class="string">"close-btn"</span> <span class="attr">id</span>=<span class="string">"close-key-mapping"</span>&gt;</span><span class="symbol">&amp;times;</span><span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"key-mapping-title"</span>&gt;</span>🎮 GameBoy 按键映射<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">table</span> <span class="attr">class</span>=<span class="string">"key-mapping-table"</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">thead</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">tr</span>&gt;</span></span><br><span class="line">          <span class="tag">&lt;<span class="name">th</span>&gt;</span>状态<span class="tag">&lt;/<span class="name">th</span>&gt;</span></span><br><span class="line">          <span class="tag">&lt;<span class="name">th</span>&gt;</span>GameBoy 按键<span class="tag">&lt;/<span class="name">th</span>&gt;</span></span><br><span class="line">          <span class="tag">&lt;<span class="name">th</span>&gt;</span>键盘按键<span class="tag">&lt;/<span class="name">th</span>&gt;</span></span><br><span class="line">          <span class="tag">&lt;<span class="name">th</span>&gt;</span>功能<span class="tag">&lt;/<span class="name">th</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">tr</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;/<span class="name">thead</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">tbody</span> <span class="attr">id</span>=<span class="string">"key-mapping-tbody"</span>&gt;</span></span><br><span class="line">        <span class="comment">&lt;!-- 动态生成内容 --&gt;</span></span><br><span class="line">      <span class="tag">&lt;/<span class="name">tbody</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">table</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">"info-box"</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">h4</span>&gt;</span>使用说明:<span class="tag">&lt;/<span class="name">h4</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">p</span>&gt;</span>• 使用键盘按键控制 GameBoy 游戏<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">p</span>&gt;</span>• 绿点表示按键未按下,红点表示按键已按下<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">p</span>&gt;</span>• 也可以点击虚拟手柄上的按钮进行控制<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">p</span>&gt;</span>• 按 ESC 键可以关闭此窗口<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></tbody></table></figure>
<figure class="highlight html"><figcaption><span>index.HTML</span></figcaption><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br><span class="line">252</span><br><span class="line">253</span><br><span class="line">254</span><br><span class="line">255</span><br><span class="line">256</span><br><span class="line">257</span><br><span class="line">258</span><br><span class="line">259</span><br><span class="line">260</span><br><span class="line">261</span><br><span class="line">262</span><br><span class="line">263</span><br><span class="line">264</span><br><span class="line">265</span><br><span class="line">266</span><br><span class="line">267</span><br><span class="line">268</span><br><span class="line">269</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="comment">// UI 元素引用</span></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">const</span> elements = {</span></span><br><span class="line"><span class="language-javascript">    <span class="comment">// ...</span></span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">    <span class="comment">// 输入相关元素</span></span></span><br><span class="line"><span class="language-javascript">    <span class="attr">testInputBtn</span>: <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">'test-input-btn'</span>),</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">showInputBtn</span>: <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">'show-input-btn'</span>),</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">showKeymappingBtn</span>: <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">'show-keymapping-btn'</span>),</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">toggleVirtualKeysBtn</span>: <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">'toggle-virtual-keys-btn'</span>),</span></span><br><span class="line"><span class="language-javascript">    </span></span><br><span class="line"><span class="language-javascript">    <span class="comment">// 虚拟手柄</span></span></span><br><span class="line"><span class="language-javascript">    <span class="attr">virtualGamepad</span>: <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">'virtual-gamepad'</span>),</span></span><br><span class="line"><span class="language-javascript">    </span></span><br><span class="line"><span class="language-javascript">    <span class="comment">// 按键映射弹窗</span></span></span><br><span class="line"><span class="language-javascript">    <span class="attr">keyMappingOverlay</span>: <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">'key-mapping-overlay'</span>),</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">keyMappingTbody</span>: <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">'key-mapping-tbody'</span>),</span></span><br><span class="line"><span class="language-javascript">    <span class="attr">closeKeyMappingBtn</span>: <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">'close-key-mapping'</span>)</span></span><br><span class="line"><span class="language-javascript">  }</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="comment">// 更新按钮状态</span></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">function</span> <span class="title function_">updateButtonStates</span>(<span class="params">systemState</span>) {</span></span><br><span class="line"><span class="language-javascript">    <span class="comment">// ...</span></span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">    elements.<span class="property">testInputBtn</span>.<span class="property">disabled</span> = !isInitialized;</span></span><br><span class="line"><span class="language-javascript">    elements.<span class="property">showInputBtn</span>.<span class="property">disabled</span> = !isInitialized;</span></span><br><span class="line"><span class="language-javascript">  }</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">function</span> <span class="title function_">initializeVirtualGamePad</span>(<span class="params"></span>) {</span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">const</span> gamepad = elements.<span class="property">virtualGamepad</span>;</span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">if</span> (!gamepad) <span class="keyword">return</span>;</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">    <span class="comment">// 获取所有虚拟按钮</span></span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">const</span> buttons = gamepad.<span class="title function_">querySelectorAll</span>(<span class="string">'[data-key]'</span>);</span></span><br><span class="line"><span class="language-javascript">    </span></span><br><span class="line"><span class="language-javascript">    buttons.<span class="title function_">forEach</span>(<span class="function"><span class="params">button</span> =&gt;</span> {</span></span><br><span class="line"><span class="language-javascript">      <span class="comment">// 鼠标事件</span></span></span><br><span class="line"><span class="language-javascript">      button.<span class="title function_">addEventListener</span>(<span class="string">'mousedown'</span>, handleVirtualButtonDown);</span></span><br><span class="line"><span class="language-javascript">      button.<span class="title function_">addEventListener</span>(<span class="string">'mouseup'</span>, handleVirtualButtonUp);</span></span><br><span class="line"><span class="language-javascript">      button.<span class="title function_">addEventListener</span>(<span class="string">'mouseleave'</span>, handleVirtualButtonUp);</span></span><br><span class="line"><span class="language-javascript">      </span></span><br><span class="line"><span class="language-javascript">      <span class="comment">// 触摸事件(移动设备支持)</span></span></span><br><span class="line"><span class="language-javascript">      button.<span class="title function_">addEventListener</span>(<span class="string">'touchstart'</span>, handleVirtualButtonDown);</span></span><br><span class="line"><span class="language-javascript">      button.<span class="title function_">addEventListener</span>(<span class="string">'touchend'</span>, handleVirtualButtonUp);</span></span><br><span class="line"><span class="language-javascript">      button.<span class="title function_">addEventListener</span>(<span class="string">'touchcancel'</span>, handleVirtualButtonUp);</span></span><br><span class="line"><span class="language-javascript">      </span></span><br><span class="line"><span class="language-javascript">      <span class="comment">// 防止默认行为</span></span></span><br><span class="line"><span class="language-javascript">      button.<span class="title function_">addEventListener</span>(<span class="string">'contextmenu'</span>, <span class="function"><span class="params">e</span> =&gt;</span> e.<span class="title function_">preventDefault</span>());</span></span><br><span class="line"><span class="language-javascript">      button.<span class="title function_">addEventListener</span>(<span class="string">'selectstart'</span>, <span class="function"><span class="params">e</span> =&gt;</span> e.<span class="title function_">preventDefault</span>());</span></span><br><span class="line"><span class="language-javascript">    });</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'🕹️ 虚拟手柄已初始化'</span>);</span></span><br><span class="line"><span class="language-javascript">  }</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">function</span> <span class="title function_">handleVirtualButtonUp</span>(<span class="params">event</span>) {</span></span><br><span class="line"><span class="language-javascript">    event.<span class="title function_">preventDefault</span>();</span></span><br><span class="line"><span class="language-javascript">    </span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">const</span> button = event.<span class="property">currentTarget</span>;</span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">const</span> keyName = button.<span class="property">dataset</span>.<span class="property">key</span>;</span></span><br><span class="line"><span class="language-javascript">    </span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">if</span> (!keyName || !virtualGamepadState.<span class="property">pressedButtons</span>.<span class="title function_">has</span>(keyName)) {</span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">return</span>;</span></span><br><span class="line"><span class="language-javascript">    }</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">    <span class="comment">// 移除视觉反馈</span></span></span><br><span class="line"><span class="language-javascript">    button.<span class="property">classList</span>.<span class="title function_">remove</span>(<span class="string">'pressed'</span>);</span></span><br><span class="line"><span class="language-javascript">    virtualGamepadState.<span class="property">pressedButtons</span>.<span class="title function_">delete</span>(keyName);</span></span><br><span class="line"><span class="language-javascript">    </span></span><br><span class="line"><span class="language-javascript">    <span class="comment">// 模拟按键释放</span></span></span><br><span class="line"><span class="language-javascript">    <span class="title function_">simulateKeyRelease</span>(keyName);</span></span><br><span class="line"><span class="language-javascript">    </span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">if</span> (<span class="variable language_">window</span>.<span class="property">DEBUG_MODE</span>) {</span></span><br><span class="line"><span class="language-javascript">      <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`🔓 虚拟按钮释放: <span class="subst">${keyName}</span>`</span>);</span></span><br><span class="line"><span class="language-javascript">    }</span></span><br><span class="line"><span class="language-javascript">  }</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">function</span> <span class="title function_">simulateKeyRelease</span>(<span class="params">keyName</span>) {</span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">if</span> (!systemInstance) <span class="keyword">return</span>;</span></span><br><span class="line"><span class="language-javascript">    </span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">try</span> {</span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">const</span> system = <span class="variable language_">window</span>.<span class="property">GameBoySystemController</span>.<span class="title function_">getInstance</span>();</span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">if</span> (system &amp;&amp; system.<span class="property">inputController</span>) {</span></span><br><span class="line"><span class="language-javascript">        system.<span class="property">inputController</span>.<span class="title function_">simulateKeyRelease</span>(keyName);</span></span><br><span class="line"><span class="language-javascript">      }</span></span><br><span class="line"><span class="language-javascript">    } <span class="keyword">catch</span> (error) {</span></span><br><span class="line"><span class="language-javascript">      <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">`模拟按键释放失败: <span class="subst">${error.message}</span>`</span>);</span></span><br><span class="line"><span class="language-javascript">    }</span></span><br><span class="line"><span class="language-javascript">  }</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="comment">// 输入控制函数</span></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">function</span> <span class="title function_">testInputSystem</span>(<span class="params"></span>) {</span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">if</span> (!systemInstance) {</span></span><br><span class="line"><span class="language-javascript">      <span class="title function_">addLog</span>(<span class="string">'❌ 系统未初始化'</span>, <span class="string">'error'</span>);</span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">return</span>;</span></span><br><span class="line"><span class="language-javascript">    }</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">try</span> {</span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">const</span> system = <span class="variable language_">window</span>.<span class="property">GameBoySystemController</span>.<span class="title function_">getInstance</span>();</span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">if</span> (system &amp;&amp; system.<span class="property">inputController</span>) {</span></span><br><span class="line"><span class="language-javascript">        <span class="title function_">addLog</span>(<span class="string">'🧪 开始输入系统测试...'</span>, <span class="string">'debug'</span>);</span></span><br><span class="line"><span class="language-javascript">        system.<span class="title function_">testInputSystem</span>();</span></span><br><span class="line"><span class="language-javascript">        <span class="title function_">addLog</span>(<span class="string">'✅ 输入系统测试已启动,请查看控制台输出'</span>, <span class="string">'success'</span>);</span></span><br><span class="line"><span class="language-javascript">      } <span class="keyword">else</span> {</span></span><br><span class="line"><span class="language-javascript">        <span class="title function_">addLog</span>(<span class="string">'❌ 输入控制器未初始化'</span>, <span class="string">'error'</span>);</span></span><br><span class="line"><span class="language-javascript">      }</span></span><br><span class="line"><span class="language-javascript">    } <span class="keyword">catch</span> (error) {</span></span><br><span class="line"><span class="language-javascript">      <span class="title function_">addLog</span>(<span class="string">`❌ 输入系统测试失败: <span class="subst">${error.message}</span>`</span>, <span class="string">'error'</span>);</span></span><br><span class="line"><span class="language-javascript">    }</span></span><br><span class="line"><span class="language-javascript">  }</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">function</span> <span class="title function_">showInputStatus</span>(<span class="params"></span>) {</span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">if</span> (!systemInstance) {</span></span><br><span class="line"><span class="language-javascript">      <span class="title function_">addLog</span>(<span class="string">'❌ 系统未初始化'</span>, <span class="string">'error'</span>);</span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">return</span>;</span></span><br><span class="line"><span class="language-javascript">    }</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">try</span> {</span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">const</span> system = <span class="variable language_">window</span>.<span class="property">GameBoySystemController</span>.<span class="title function_">getInstance</span>();</span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">if</span> (system &amp;&amp; system.<span class="property">inputController</span>) {</span></span><br><span class="line"><span class="language-javascript">        <span class="title function_">addLog</span>(<span class="string">'🎮 === 输入系统状态 ==='</span>, <span class="string">'debug'</span>);</span></span><br><span class="line"><span class="language-javascript">        <span class="title function_">addLog</span>(system.<span class="property">inputController</span>.<span class="title function_">getDebugInfo</span>(), <span class="string">'debug'</span>);</span></span><br><span class="line"><span class="language-javascript">        <span class="title function_">addLog</span>(<span class="string">'==================='</span>, <span class="string">'debug'</span>);</span></span><br><span class="line"><span class="language-javascript">      } <span class="keyword">else</span> {</span></span><br><span class="line"><span class="language-javascript">        <span class="title function_">addLog</span>(<span class="string">'❌ 输入控制器未初始化'</span>, <span class="string">'error'</span>);</span></span><br><span class="line"><span class="language-javascript">      }</span></span><br><span class="line"><span class="language-javascript">    } <span class="keyword">catch</span> (error) {</span></span><br><span class="line"><span class="language-javascript">      <span class="title function_">addLog</span>(<span class="string">`❌ 获取输入状态失败: <span class="subst">${error.message}</span>`</span>, <span class="string">'error'</span>);</span></span><br><span class="line"><span class="language-javascript">    }</span></span><br><span class="line"><span class="language-javascript">  }</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">function</span> <span class="title function_">showKeyMapping</span>(<span class="params"></span>) {</span></span><br><span class="line"><span class="language-javascript">    <span class="title function_">updateKeyMappingDisplay</span>();</span></span><br><span class="line"><span class="language-javascript">    elements.<span class="property">keyMappingOverlay</span>.<span class="property">style</span>.<span class="property">display</span> = <span class="string">'flex'</span>;</span></span><br><span class="line"><span class="language-javascript">  }</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">function</span> <span class="title function_">hideKeyMapping</span>(<span class="params"></span>) {</span></span><br><span class="line"><span class="language-javascript">    elements.<span class="property">keyMappingOverlay</span>.<span class="property">style</span>.<span class="property">display</span> = <span class="string">'none'</span>;</span></span><br><span class="line"><span class="language-javascript">  }</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">function</span> <span class="title function_">toggleVirtualKeys</span>(<span class="params"></span>) {</span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">const</span> gamepad = elements.<span class="property">virtualGamepad</span>;</span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">const</span> isHidden = gamepad.<span class="property">classList</span>.<span class="title function_">contains</span>(<span class="string">'hidden'</span>);</span></span><br><span class="line"><span class="language-javascript">    </span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">if</span> (isHidden) {</span></span><br><span class="line"><span class="language-javascript">      gamepad.<span class="property">classList</span>.<span class="title function_">remove</span>(<span class="string">'hidden'</span>);</span></span><br><span class="line"><span class="language-javascript">      elements.<span class="property">toggleVirtualKeysBtn</span>.<span class="property">textContent</span> = <span class="string">'隐藏虚拟按键'</span>;</span></span><br><span class="line"><span class="language-javascript">      <span class="title function_">addLog</span>(<span class="string">'👀 虚拟按键已显示'</span>, <span class="string">'info'</span>);</span></span><br><span class="line"><span class="language-javascript">    } <span class="keyword">else</span> {</span></span><br><span class="line"><span class="language-javascript">      gamepad.<span class="property">classList</span>.<span class="title function_">add</span>(<span class="string">'hidden'</span>);</span></span><br><span class="line"><span class="language-javascript">      elements.<span class="property">toggleVirtualKeysBtn</span>.<span class="property">textContent</span> = <span class="string">'显示虚拟按键'</span>;</span></span><br><span class="line"><span class="language-javascript">      <span class="title function_">addLog</span>(<span class="string">'🙈 虚拟按键已隐藏'</span>, <span class="string">'info'</span>);</span></span><br><span class="line"><span class="language-javascript">    }</span></span><br><span class="line"><span class="language-javascript">  }</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">function</span> <span class="title function_">updateKeyMappingDisplay</span>(<span class="params"></span>) {</span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">if</span> (!systemInstance) <span class="keyword">return</span>;</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">try</span> {</span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">const</span> system = <span class="variable language_">window</span>.<span class="property">GameBoySystemController</span>.<span class="title function_">getInstance</span>();</span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">if</span> (!system || !system.<span class="property">inputController</span>) <span class="keyword">return</span>;</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">const</span> mappings = system.<span class="property">inputController</span>.<span class="title function_">getKeyMappings</span>();</span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">const</span> tbody = elements.<span class="property">keyMappingTbody</span>;</span></span><br><span class="line"><span class="language-javascript">      </span></span><br><span class="line"><span class="language-javascript">      tbody.<span class="property">innerHTML</span> = <span class="string">''</span>;</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">      mappings.<span class="title function_">forEach</span>(<span class="function"><span class="params">mapping</span> =&gt;</span> {</span></span><br><span class="line"><span class="language-javascript">        <span class="keyword">const</span> row = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">'tr'</span>);</span></span><br><span class="line"><span class="language-javascript">        <span class="keyword">const</span> statusClass = mapping.<span class="property">isPressed</span> ? <span class="string">'pressed'</span> : <span class="string">'released'</span>;</span></span><br><span class="line"><span class="language-javascript">        <span class="keyword">const</span> statusText = mapping.<span class="property">isPressed</span> ? <span class="string">'按下'</span> : <span class="string">'未按下'</span>;</span></span><br><span class="line"><span class="language-javascript">        </span></span><br><span class="line"><span class="language-javascript">        row.<span class="property">innerHTML</span> = <span class="string">`</span></span></span><br><span class="line"><span class="string"><span class="language-javascript">          &lt;td&gt;&lt;span class="key-status <span class="subst">${statusClass}</span>"&gt;&lt;/span&gt;<span class="subst">${statusText}</span>&lt;/td&gt;</span></span></span><br><span class="line"><span class="string"><span class="language-javascript">          &lt;td&gt;&lt;strong&gt;<span class="subst">${mapping.gameboyKey}</span>&lt;/strong&gt;&lt;/td&gt;</span></span></span><br><span class="line"><span class="string"><span class="language-javascript">          &lt;td&gt;&lt;kbd&gt;<span class="subst">${mapping.keyboardKey}</span>&lt;/kbd&gt;&lt;/td&gt;</span></span></span><br><span class="line"><span class="string"><span class="language-javascript">          &lt;td&gt;<span class="subst">${getKeyFunction(mapping.gameboyKey)}</span>&lt;/td&gt;</span></span></span><br><span class="line"><span class="string"><span class="language-javascript">        `</span>;</span></span><br><span class="line"><span class="language-javascript">        </span></span><br><span class="line"><span class="language-javascript">        tbody.<span class="title function_">appendChild</span>(row);</span></span><br><span class="line"><span class="language-javascript">      });</span></span><br><span class="line"><span class="language-javascript">    } <span class="keyword">catch</span> (error) {</span></span><br><span class="line"><span class="language-javascript">      <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">'更新按键映射显示失败:'</span>, error);</span></span><br><span class="line"><span class="language-javascript">    }</span></span><br><span class="line"><span class="language-javascript">  }</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">function</span> <span class="title function_">getKeyFunction</span>(<span class="params">gameboyKey</span>) {</span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">const</span> functions = {</span></span><br><span class="line"><span class="language-javascript">      <span class="string">'A'</span>: <span class="string">'确认/攻击'</span>,</span></span><br><span class="line"><span class="language-javascript">      <span class="string">'B'</span>: <span class="string">'取消/跳跃'</span>, </span></span><br><span class="line"><span class="language-javascript">      <span class="string">'Start'</span>: <span class="string">'开始/暂停'</span>,</span></span><br><span class="line"><span class="language-javascript">      <span class="string">'Select'</span>: <span class="string">'选择/切换'</span>,</span></span><br><span class="line"><span class="language-javascript">      <span class="string">'Up'</span>: <span class="string">'向上移动'</span>,</span></span><br><span class="line"><span class="language-javascript">      <span class="string">'Down'</span>: <span class="string">'向下移动'</span>,</span></span><br><span class="line"><span class="language-javascript">      <span class="string">'Left'</span>: <span class="string">'向左移动'</span>,</span></span><br><span class="line"><span class="language-javascript">      <span class="string">'Right'</span>: <span class="string">'向右移动'</span></span></span><br><span class="line"><span class="language-javascript">    };</span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">return</span> functions[gameboyKey] || <span class="string">'未知功能'</span>;</span></span><br><span class="line"><span class="language-javascript">  }</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="comment">// 性能监控</span></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">function</span> <span class="title function_">startPerformanceMonitoring</span>(<span class="params"></span>) {</span></span><br><span class="line"><span class="language-javascript">    <span class="comment">// ...</span></span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">    <span class="comment">// 启动按键状态监控,每秒更新一次按键映射显示</span></span></span><br><span class="line"><span class="language-javascript">    <span class="built_in">setInterval</span>(updateKeyMappingDisplay, <span class="number">1000</span>);</span></span><br><span class="line"><span class="language-javascript">  }</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="comment">// 绑定事件监听器</span></span></span><br><span class="line"><span class="language-javascript">  <span class="keyword">function</span> <span class="title function_">bindEventListeners</span>(<span class="params"></span>) {</span></span><br><span class="line"><span class="language-javascript">    <span class="comment">// ...</span></span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">    <span class="comment">// 按键映射弹窗的ESC键关闭</span></span></span><br><span class="line"><span class="language-javascript">    elements.<span class="property">keyMappingOverlay</span>.<span class="title function_">addEventListener</span>(<span class="string">'click'</span>, <span class="keyword">function</span>(<span class="params">event</span>) {</span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">if</span> (event.<span class="property">target</span> === elements.<span class="property">keyMappingOverlay</span>) {</span></span><br><span class="line"><span class="language-javascript">        <span class="title function_">hideKeyMapping</span>();</span></span><br><span class="line"><span class="language-javascript">      }</span></span><br><span class="line"><span class="language-javascript">    });</span></span><br><span class="line"><span class="language-javascript">    </span></span><br><span class="line"><span class="language-javascript">    <span class="comment">// 初始化虚拟手柄</span></span></span><br><span class="line"><span class="language-javascript">    <span class="title function_">initializeVirtualGamepad</span>();</span></span><br><span class="line"><span class="language-javascript">  }</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="comment">// 键盘快捷键</span></span></span><br><span class="line"><span class="language-javascript">  <span class="variable language_">document</span>.<span class="title function_">addEventListener</span>(<span class="string">'keydown'</span>, <span class="keyword">function</span>(<span class="params">event</span>) {</span></span><br><span class="line"><span class="language-javascript">    <span class="comment">// 防止在输入框中触发快捷键</span></span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">if</span> (event.<span class="property">target</span>.<span class="property">tagName</span> === <span class="string">'INPUT'</span>) <span class="keyword">return</span>;</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">    <span class="comment">// 如果按键映射弹窗打开,ESC键关闭</span></span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">if</span> (elements.<span class="property">keyMappingOverlay</span>.<span class="property">style</span>.<span class="property">display</span> === <span class="string">'flex'</span> &amp;&amp; event.<span class="property">code</span> === <span class="string">'Escape'</span>) {</span></span><br><span class="line"><span class="language-javascript">      event.<span class="title function_">preventDefault</span>();</span></span><br><span class="line"><span class="language-javascript">      <span class="title function_">hideKeyMapping</span>();</span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">return</span>;</span></span><br><span class="line"><span class="language-javascript">    }</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">    <span class="keyword">switch</span>(event.<span class="property">code</span>) {</span></span><br><span class="line"><span class="language-javascript">      <span class="comment">// ...</span></span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">case</span> <span class="string">'KeyI'</span>:</span></span><br><span class="line"><span class="language-javascript">        event.<span class="title function_">preventDefault</span>();</span></span><br><span class="line"><span class="language-javascript">        <span class="title function_">showInputStatus</span>();</span></span><br><span class="line"><span class="language-javascript">        <span class="keyword">break</span>;</span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">case</span> <span class="string">'KeyK'</span>:</span></span><br><span class="line"><span class="language-javascript">        event.<span class="title function_">preventDefault</span>();</span></span><br><span class="line"><span class="language-javascript">        <span class="title function_">showKeyMapping</span>();</span></span><br><span class="line"><span class="language-javascript">        <span class="keyword">break</span>;</span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">case</span> <span class="string">'Escape'</span>:</span></span><br><span class="line"><span class="language-javascript">        event.<span class="title function_">preventDefault</span>();</span></span><br><span class="line"><span class="language-javascript">        <span class="keyword">if</span> (elements.<span class="property">keyMappingOverlay</span>.<span class="property">style</span>.<span class="property">display</span> === <span class="string">'flex'</span>) {</span></span><br><span class="line"><span class="language-javascript">          <span class="title function_">hideKeyMapping</span>();</span></span><br><span class="line"><span class="language-javascript">        }</span></span><br><span class="line"><span class="language-javascript">        <span class="keyword">break</span>;</span></span><br><span class="line"><span class="language-javascript">    }</span></span><br><span class="line"><span class="language-javascript">  });</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">  <span class="comment">// 页面加载完成后的初始化</span></span></span><br><span class="line"><span class="language-javascript">  <span class="variable language_">window</span>.<span class="title function_">addEventListener</span>(<span class="string">'load'</span>, <span class="keyword">function</span>(<span class="params"></span>) {</span></span><br><span class="line"><span class="language-javascript">    <span class="title function_">bindEventListeners</span>();</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">    <span class="comment">// 检查组件加载状态</span></span></span><br><span class="line"><span class="language-javascript">    <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> {</span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">const</span> components = [</span></span><br><span class="line"><span class="language-javascript">        <span class="comment">// ...</span></span></span><br><span class="line"><span class="language-javascript">        { <span class="attr">name</span>: <span class="string">'输入控制器'</span>, <span class="attr">check</span>: <span class="function">() =&gt;</span> <span class="keyword">typeof</span> <span class="title class_">GameBoyInputController</span> !== <span class="string">'undefined'</span> }</span></span><br><span class="line"><span class="language-javascript">      ];</span></span><br><span class="line"><span class="language-javascript">    });</span></span><br><span class="line"><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">    <span class="comment">// ...</span></span></span><br><span class="line"><span class="language-javascript">  });</span></span><br><span class="line"><span class="language-javascript"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></tbody></table></figure>
<!-- flag of hidden posts --></body></html></div></article></div></main><footer><div class="paginator"></div><!-- Webmention 显示区域--><div class="webmention-section webmention-empty" data-page-url="posts/c4d6.html" data-full-url="https://cytrogen.icu/posts/c4d6.html" data-mode="static">
              <h3 class="webmention-title">Webmentions (<span class="webmention-count">0</span>)</h3>
              <div class="webmention-list"></div>
              <span>暂无 Webmentions</span>
            </div><div class="copyright"><p class="footer-links"><a href="../friends/index.html">友链</a><span class="footer-separator"> ·</span><a href="../links/index.html">邻邦</a><span class="footer-separator"> ·</span><a href="../contact/index.html">联络</a><span class="footer-separator"> ·</span><a href="../colophon/index.html">营造记</a><span class="footer-separator"> ·</span><a href="../atom.xml">RSS订阅</a></p><p>© 2025 - 2026 <a href="https://cytrogen.icu">Cytrogen</a>, powered by <a href="https://hexo.io/" target="_blank">Hexo</a> and <a href="https://github.com/cytrogen/hexo-theme-ares" target="_blank">hexo-theme-ares</a>.</p><p><a href="https://blogscn.fun" target="_blank" rel="noopener">BLOGS·CN</a></p></div></footer></div></div><a class="back-to-top" href="#top" aria-label="返回顶部"><svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path d="M3.293 9.707a1 1 0 010-1.414L9.586 2a2 2 0 012.828 0l6.293 6.293a1 1 0 01-1.414 1.414L11 3.414V17a1 1 0 11-2 0V3.414L2.707 9.707a1 1 0 01-1.414 0z"></path></svg></a><script>document.addEventListener('DOMContentLoaded', function() {
  const codeBlocks = document.querySelectorAll('figure.highlight');
  
  codeBlocks.forEach(block => {
    let caption = block.querySelector('figcaption');
    if (!caption) {
      caption = document.createElement('figcaption');
      block.insertBefore(caption, block.firstChild);
    }

    const info = document.createElement('div');
    info.className = 'info';
    
    const filename = caption.querySelector('span');
    if (filename) {
      filename.className = 'filename';
      info.appendChild(filename);
    }
    
    const lang = block.className.split(' ')[1];
    if (lang) {
      const langSpan = document.createElement('span');
      langSpan.className = 'lang-name';
      langSpan.textContent = lang;
      info.appendChild(langSpan);
    }

    const sourceLink = caption.querySelector('a');
    if (sourceLink) {
      sourceLink.className = 'source-link';
      info.appendChild(sourceLink);
    }

    const actions = document.createElement('div');
    actions.className = 'actions';

    const codeHeight = block.scrollHeight;
    const threshold = 300;

    if (codeHeight > threshold) {
      block.classList.add('folded');
      
      const toggleBtn = document.createElement('button');
      toggleBtn.textContent = '展开';
      toggleBtn.addEventListener('click', () => {
        block.classList.toggle('folded');
        toggleBtn.textContent = block.classList.contains('folded') ? '展开' : '折叠';
      });
      actions.appendChild(toggleBtn);
    }

    const copyBtn = document.createElement('button');
    copyBtn.textContent = '复制';
    copyBtn.addEventListener('click', async () => {
      const codeLines = block.querySelectorAll('.code .line');
      const code = Array.from(codeLines)
        .map(line => line.textContent)
        .join('\n')
        .replace(/\n\n/g, '\n');
      
      try {
        await navigator.clipboard.writeText(code);
        copyBtn.textContent = '已复制';
        copyBtn.classList.add('copied');
        
        setTimeout(() => {
          copyBtn.textContent = '复制';
          copyBtn.classList.remove('copied');
        }, 3000);
      } catch (err) {
        console.error('复制失败:', err);
        copyBtn.textContent = '复制失败';
        
        setTimeout(() => {
          copyBtn.textContent = '复制';
        }, 3000);
      }
    });
    actions.appendChild(copyBtn);

    caption.innerHTML = '';
    caption.appendChild(info);
    caption.appendChild(actions);

    const markedLines = block.getAttribute('data-marked-lines');
    if (markedLines) {
      const lines = markedLines.split(',');
      lines.forEach(range => {
        if (range.includes('-')) {
          const [start, end] = range.split('-').map(Number);
          for (let i = start; i <= end; i++) {
            const line = block.querySelector(`.line-${i}`);
            if (line) line.classList.add('marked');
          }
        } else {
          const line = block.querySelector(`.line-${range}`);
          if (line) line.classList.add('marked');
        }
      });
    }
  });
});</script><script async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js" id="MathJax-script"></script><script>(function() {
  document.addEventListener('DOMContentLoaded', function() {
    const themeToggle = document.querySelector('.theme-toggle');
    
    if (!themeToggle) return;
    
    const getCurrentTheme = () => {
      return document.documentElement.getAttribute('data-theme') || 'light';
    };
    
    const updateUI = (theme) => {
      const isDark = theme === 'dark';
      themeToggle.setAttribute('aria-pressed', isDark.toString());
    };
    
    const setTheme = (theme) => {
      document.documentElement.setAttribute('data-theme', theme);
      document.documentElement.style.colorScheme = theme;
      
      const pageWrapper = document.getElementById('page-wrapper');
      if (pageWrapper) {
        pageWrapper.setAttribute('data-theme', theme);
      }
      
      // Find and remove the temporary anti-flicker style tag if it exists.
      // This ensures the main stylesheet takes full control after the initial load.
      const antiFlickerStyle = document.getElementById('anti-flicker-style');
      if (antiFlickerStyle) {
        antiFlickerStyle.remove();
      }
      
      localStorage.setItem('theme', theme);
      updateUI(theme);
    };
    
    const toggleTheme = () => {
      const current = getCurrentTheme();
      const newTheme = current === 'light' ? 'dark' : 'light';
      setTheme(newTheme);
    };
    
    updateUI(getCurrentTheme());
    
    themeToggle.addEventListener('click', toggleTheme);
    
    if (window.matchMedia) {
      const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
      mediaQuery.addEventListener('change', function(e) {
        if (!localStorage.getItem('theme')) {
          const theme = e.matches ? 'dark' : 'light';
          setTheme(theme);
        }
      });
    }
  });
})();
</script><script src="../js/details-toggle.js" defer></script><script>(function() {
  document.addEventListener('DOMContentLoaded', function() {
    const backToTopBtn = document.querySelector('.back-to-top');
    
    if (!backToTopBtn) return;
    
    const toggleButtonVisibility = () => {
      const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
      const shouldShow = scrollTop > 200;
      
      if (shouldShow) {
        backToTopBtn.classList.add('is-visible');
      } else {
        backToTopBtn.classList.remove('is-visible');
      }
    };
    
    let ticking = false;
    const handleScroll = () => {
      if (!ticking) {
        requestAnimationFrame(() => {
          toggleButtonVisibility();
          ticking = false;
        });
        ticking = true;
      }
    };
    
    const scrollToTop = (event) => {
      event.preventDefault();
      window.scrollTo({
        top: 0,
        behavior: 'smooth'
      });
    };
    
    window.addEventListener('scroll', handleScroll);
    backToTopBtn.addEventListener('click', scrollToTop);
    
    toggleButtonVisibility();
  });
})();</script></body></html>