<!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> =></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> << 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>] &= 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> << 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 << 0) = ~0b00000001 = 0b11111110 = 0xFE</span><br><span class="line">3. 应用掩码:keyMatrix[0] &= 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 << 2) = ~0b00000100 = 0b11111011 = 0xFB</span><br><span class="line">3. 应用掩码:keyMatrix[1] &= 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 << 0) = 0xFE = 0b11111110</span><br><span class="line">keyMatrix[0] = 0x0F & 0xFE = 0x0E = 0b00001110</span><br><span class="line"></span><br><span class="line">按下B键:</span><br><span class="line">rowMask = ~(1 << 1) = 0xFD = 0b11111101 </span><br><span class="line">keyMatrix[0] = 0x0E & 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 << 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>&=</code> 和反转掩码将指定位设为 0</p>
<ul>
<li><code>~(1 << row)</code> 创建只有目标位为 0 的掩码</li>
<li>与运算确保只有目标位被清零</li>
</ul>
</li>
<li>
<p><strong>释放操作</strong>:使用 <code>|=</code> 和正常掩码将指定位设为 1</p>
<ul>
<li><code>(1 << 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>] & <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>] & <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 & <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> && <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> && <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> =></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> =></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> =></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">() =></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> =></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> =></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>) =></span> {</span><br><span class="line"> <span class="built_in">setTimeout</span>(<span class="function">() =></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">() =></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"><!-- 输入控制 --></span></span><br><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"controls"</span>></span></span><br><span class="line"> <span class="comment"><!-- 虚拟按键显示 --></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"control-section"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"control-title"</span>></span>🕹️ 虚拟手柄<span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<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>></span></span><br><span class="line"> <span class="comment"><!-- 方向键 --></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"dpad-container"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"dpad"</span>></span></span><br><span class="line"> <span class="tag"><<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>></span>↑<span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"dpad-middle"</span>></span></span><br><span class="line"> <span class="tag"><<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>></span>←<span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"dpad-center"</span>></span><span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<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>></span>→<span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<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>></span>↓<span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"dpad-label"</span>></span>方向键<span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> </span><br><span class="line"> <span class="comment"><!-- 功能键 --></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"action-buttons"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"button-row"</span>></span></span><br><span class="line"> <span class="tag"><<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>></span>SELECT<span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<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>></span>START<span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"button-row"</span>></span></span><br><span class="line"> <span class="tag"><<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>></span>B<span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<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>></span>A<span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br><span class="line"></span><br><span class="line"><span class="comment"><!-- 调试和图形控制 --></span></span><br><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"controls"</span>></span> ... <span class="tag"></<span class="name">div</span>></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"><<span class="name">style</span>></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"></<span class="name">style</span>></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"><<span class="name">script</span>></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 && 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"></<span class="name">script</span>></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"><!-- 输入控制 --></span></span><br><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"controls"</span>></span></span><br><span class="line"> <span class="comment"><!-- 输入测试 --></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"control-section"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"control-title"</span>></span>🎮 输入控制<span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<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>></span>测试输入系统<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"> <span class="tag"><<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>></span>显示输入状态<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"> <span class="tag"><<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>></span>显示按键映射<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"> <span class="tag"><<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>></span>切换虚拟按键<span class="tag"></<span class="name">button</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"keyboard-hint"</span>></span></span><br><span class="line"> 快捷键:<span class="tag"><<span class="name">span</span> <span class="attr">class</span>=<span class="string">"key"</span>></span>I<span class="tag"></<span class="name">span</span>></span> 输入状态 <span class="tag"><<span class="name">span</span> <span class="attr">class</span>=<span class="string">"key"</span>></span>K<span class="tag"></<span class="name">span</span>></span> 按键映射</span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> </span><br><span class="line"> <span class="comment"><!-- 虚拟按键显示 --></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"control-section"</span>></span> ... <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"><span class="tag"></<span class="name">div</span>></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"><!-- 按键映射弹窗 --></span></span><br><span class="line"><span class="tag"><<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>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"key-mapping-content"</span>></span></span><br><span class="line"> <span class="tag"><<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>></span><span class="symbol">&times;</span><span class="tag"></<span class="name">button</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"key-mapping-title"</span>></span>🎮 GameBoy 按键映射<span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">table</span> <span class="attr">class</span>=<span class="string">"key-mapping-table"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">thead</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">tr</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">th</span>></span>状态<span class="tag"></<span class="name">th</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">th</span>></span>GameBoy 按键<span class="tag"></<span class="name">th</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">th</span>></span>键盘按键<span class="tag"></<span class="name">th</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">th</span>></span>功能<span class="tag"></<span class="name">th</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">tr</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">thead</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">tbody</span> <span class="attr">id</span>=<span class="string">"key-mapping-tbody"</span>></span></span><br><span class="line"> <span class="comment"><!-- 动态生成内容 --></span></span><br><span class="line"> <span class="tag"></<span class="name">tbody</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">table</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"info-box"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">h4</span>></span>使用说明:<span class="tag"></<span class="name">h4</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">p</span>></span>• 使用键盘按键控制 GameBoy 游戏<span class="tag"></<span class="name">p</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">p</span>></span>• 绿点表示按键未按下,红点表示按键已按下<span class="tag"></<span class="name">p</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">p</span>></span>• 也可以点击虚拟手柄上的按钮进行控制<span class="tag"></<span class="name">p</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">p</span>></span>• 按 ESC 键可以关闭此窗口<span class="tag"></<span class="name">p</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"><span class="tag"></<span class="name">div</span>></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"><<span class="name">script</span>></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> =></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> =></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> =></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 && 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 && 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 && 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> =></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"> <td><span class="key-status <span class="subst">${statusClass}</span>"></span><span class="subst">${statusText}</span></td></span></span></span><br><span class="line"><span class="string"><span class="language-javascript"> <td><strong><span class="subst">${mapping.gameboyKey}</span></strong></td></span></span></span><br><span class="line"><span class="string"><span class="language-javascript"> <td><kbd><span class="subst">${mapping.keyboardKey}</span></kbd></td></span></span></span><br><span class="line"><span class="string"><span class="language-javascript"> <td><span class="subst">${getKeyFunction(mapping.gameboyKey)}</span></td></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> && 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">() =></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">() =></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"></<span class="name">script</span>></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>