这节我们来加一个设置面板,可以设置背景音乐、音效、天气等。
首先改下 index.html,加一下 html 部分:

<!-- 设置面板 -->
<div id="settingsPanel" style="display: none;">
<div class="settings-content">
<div class="settings-header">
<h2>设置</h2>
<button id="closeSettingsBtn" class="close-btn">关闭 (ESC)</button>
</div>
<div class="settings-body">
<div class="settings-section">
<h3>音频设置</h3>
<div class="settings-item">
<label>
<input type="checkbox" id="bgMusicToggle" checked>
<span>背景音乐</span>
</label>
</div>
<div class="settings-item">
<label>
<input type="checkbox" id="soundEffectToggle" checked>
<span>音效</span>
</label>
</div>
</div>
<div class="settings-section">
<h3>界面设置</h3>
<div class="settings-item">
<label>
<input type="checkbox" id="miniMapToggle" checked>
<span>小地图</span>
</label>
</div>
</div>
<div class="settings-section">
<h3>天气设置</h3>
<div class="weather-buttons">
<button class="weather-btn" data-weather="clear">晴天</button>
<button class="weather-btn" data-weather="rain">雨天</button>
<button class="weather-btn" data-weather="snow">雪天</button>
<button class="weather-btn" data-weather="fog">雾天</button>
</div>
</div>
</div>
</div>
</div>
<!-- 设置按钮 -->
<button id="settingsBtn" class="settings-btn" title="设置 (P)">⚙️</button>
然后加下对应的样式,改下 style.css

/* 设置面板样式 */
#settingsPanel {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
z-index: 3000;
display: flex;
justify-content: center;
align-items: center;
pointer-events: auto;
}
.settings-content {
background: rgba(30, 30, 30, 0.95);
border-radius: 12px;
width: 90%;
max-width: 500px;
max-height: 80vh;
overflow-y: auto;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
border: 2px solid #555;
}
.settings-header {
padding: 20px;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 2px solid #555;
border-radius: 12px 12px 0 0;
}
.settings-header h2 {
color: white;
margin: 0;
font-size: 24px;
}
.settings-body {
padding: 20px;
}
.settings-section {
margin-bottom: 30px;
}
.settings-section h3 {
color: #fff;
margin: 0 0 15px 0;
font-size: 18px;
border-bottom: 1px solid #555;
padding-bottom: 10px;
}
.settings-item {
margin-bottom: 15px;
}
.settings-item label {
display: flex;
align-items: center;
color: white;
font-size: 16px;
cursor: pointer;
gap: 10px;
}
.settings-item input[type="checkbox"] {
width: 20px;
height: 20px;
cursor: pointer;
}
.weather-buttons {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
}
.weather-btn {
padding: 12px 20px;
background: #4a5568;
color: white;
border: 2px solid #666;
border-radius: 6px;
cursor: pointer;
font-size: 16px;
transition: all 0.3s;
}
.weather-btn:hover {
background: #5a6578;
border-color: #888;
transform: translateY(-2px);
}
.weather-btn.active {
background: #3182ce;
border-color: #4299e1;
}
/* 设置按钮 */
.settings-btn {
position: fixed;
top: 20px;
right: 20px;
width: 50px;
height: 50px;
background: rgba(0, 0, 0, 0.6);
border: 2px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
color: white;
font-size: 24px;
cursor: pointer;
z-index: 1001;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s;
}
.settings-btn:hover {
background: rgba(0, 0, 0, 0.8);
border-color: rgba(255, 255, 255, 0.5);
transform: scale(1.1);
}
之后加一下逻辑部分,改下 main.js

// 设置面板控制
export let isSettingsOpen = false;
let soundEffectEnabled = true;
let backgroundMusicEnabled = true;
let miniMapEnabled = true;
function toggleSettings() {
const settingsPanel = document.getElementById('settingsPanel');
if (!settingsPanel) return;
isSettingsOpen = !isSettingsOpen;
settingsPanel.style.display = isSettingsOpen ? 'flex' : 'none';
// 当打开设置面板时,退出指针锁定模式
if (isSettingsOpen && document.pointerLockElement) {
document.exitPointerLock();
}
// 更新当前天气按钮状态
if (isSettingsOpen) {
updateWeatherButtonStates();
}
}
function updateWeatherButtonStates() {
const weatherButtons = document.querySelectorAll('.weather-btn');
const weatherSystem = getWeatherSystem();
const currentWeather = weatherSystem ? weatherSystem.getCurrentWeather() : WeatherType.CLEAR;
weatherButtons.forEach(btn => {
const weather = btn.getAttribute('data-weather');
let weatherType;
switch(weather) {
case 'clear':
weatherType = WeatherType.CLEAR;
break;
case 'rain':
weatherType = WeatherType.RAIN;
break;
case 'snow':
weatherType = WeatherType.SNOW;
break;
case 'fog':
weatherType = WeatherType.FOG;
break;
default:
weatherType = WeatherType.CLEAR;
}
btn.classList.toggle('active', weatherType === currentWeather);
});
}
// 初始化设置面板
document.addEventListener('DOMContentLoaded', () => {
const settingsBtn = document.getElementById('settingsBtn');
const closeSettingsBtn = document.getElementById('closeSettingsBtn');
const bgMusicToggle = document.getElementById('bgMusicToggle');
const soundEffectToggle = document.getElementById('soundEffectToggle');
const miniMapToggle = document.getElementById('miniMapToggle');
const weatherButtons = document.querySelectorAll('.weather-btn');
const settingsPanel = document.getElementById('settingsPanel');
const miniMap = document.getElementById('miniMap');
// 阻止设置面板内的点击事件冒泡,防止触发指针锁定
if (settingsPanel) {
settingsPanel.addEventListener('mousedown', (e) => {
e.stopPropagation();
});
settingsPanel.addEventListener('click', (e) => {
e.stopPropagation();
});
}
if (settingsBtn) {
settingsBtn.addEventListener('click', (e) => {
e.stopPropagation();
toggleSettings();
});
}
if (closeSettingsBtn) {
closeSettingsBtn.addEventListener('click', (e) => {
e.stopPropagation();
toggleSettings();
});
}
// 背景音乐开关
if (bgMusicToggle) {
bgMusicToggle.addEventListener('change', (e) => {
backgroundMusicEnabled = e.target.checked;
if (backgroundMusicEnabled && musicStarted) {
backgroundMusic.play().catch(err => {
console.log('播放背景音乐失败:', err);
});
} else {
backgroundMusic.pause();
}
});
}
// 音效开关
if (soundEffectToggle) {
soundEffectToggle.addEventListener('change', (e) => {
soundEffectEnabled = e.target.checked;
setSoundEffectEnabled(soundEffectEnabled);
});
}
// 小地图开关
if (miniMapToggle && miniMap) {
// 初始化小地图显示状态
miniMap.style.display = miniMapEnabled ? 'flex' : 'none';
miniMapToggle.addEventListener('change', (e) => {
miniMapEnabled = e.target.checked;
miniMap.style.display = miniMapEnabled ? 'flex' : 'none';
});
}
// 天气切换按钮
weatherButtons.forEach(btn => {
btn.addEventListener('click', () => {
const weather = btn.getAttribute('data-weather');
let weatherType;
switch(weather) {
case 'clear':
weatherType = WeatherType.CLEAR;
break;
case 'rain':
weatherType = WeatherType.RAIN;
break;
case 'snow':
weatherType = WeatherType.SNOW;
break;
case 'fog':
weatherType = WeatherType.FOG;
break;
default:
weatherType = WeatherType.CLEAR;
}
setWeather(weatherType);
// 更新按钮状态
updateWeatherButtonStates();
});
});
});
首先是点击设置按钮,切换设置面板的显示隐藏。

然后加一下各种按钮的功能,各种切换逻辑:

虽然代码比较多,但是整体还是比较简单的,很清晰。
看下效果:

这样,设置面板就完成了。
案例代码上传了小册仓库
总结
这节我们加上了设置面板。
比较简单但是很有必要的一个功能。
就是 html、css 部分,然后绑定 js 事件,点击按钮做各种切换处理就可以了。