Skip to content

229. 综合实战:开放世界(三十)

Published:

这节我们来加一个设置面板,可以设置背景音乐、音效、天气等。

首先改下 index.html,加一下 html 部分:

image.png

    <!-- 设置面板 -->
    <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

image.png

/* 设置面板样式 */
#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

image.png

// 设置面板控制
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();
    });
  });
});

首先是点击设置按钮,切换设置面板的显示隐藏。

image.png

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

image.png

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

看下效果:

2026-02-22 20.30.36.gif

这样,设置面板就完成了。

案例代码上传了小册仓库

总结

这节我们加上了设置面板。

比较简单但是很有必要的一个功能。

就是 html、css 部分,然后绑定 js 事件,点击按钮做各种切换处理就可以了。

评论