跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
HTML / CSSNode.js大前端

基于 HTML5+CSS3+JavaScript 的高木同学圣诞树 GalGame 开发

基于 HTML5、CSS3 和原生 JavaScript 构建的视觉小说游戏项目,涵盖场景管理、对话分支、角色表情系统及存档功能。文章详细解析了动态事件绑定、本地文件加载限制及性能优化等实战问题,提供响应式布局方案与部署指南,展示了纯前端技术在游戏开发中的应用潜力。

DockerOne发布于 2026/3/27更新于 2026/10/2592 浏览
基于 HTML5+CSS3+JavaScript 的高木同学圣诞树 GalGame 开发

项目概述与目标

随着 Web 技术的演进,HTML5、CSS3 和现代 JavaScript 已能承载复杂的交互应用。GalGame(视觉小说)强调剧情叙事与角色互动,非常适合用纯前端技术实现。本项目以《擅长捉弄人的高木同学》为题材,结合圣诞节主题,构建一个功能完整的 Web 端游戏引擎。

核心目标包括:

  • 技术目标:不依赖第三方框架,从零构建场景管理、角色表情系统与对话分支机制。
  • 用户体验:支持键盘与鼠标操作,确保响应式布局适配多端设备。
  • 性能目标:优化资源加载与内存使用,保证流畅运行。
  • 扩展性:采用模块化设计,便于后续剧情或功能的迭代。

技术架构选型

我们坚持纯前端技术栈,避免引入复杂依赖,直接利用原生能力。

技术栈清单

  • HTML5:语义化标签、本地存储 API、媒体支持。
  • CSS3:Flexbox/Grid 布局、动画效果、响应式设计。
  • JavaScript ES6+:模块化编程、异步处理、事件系统。

项目结构

GalGame/
├── index.html      # 主入口文件
├── images/         # 资源目录
│   ├── takagi_normal.png
│   └── ...         # 其他表情图片
├── server.bat      # Windows 启动脚本
└── README.md       # 说明文档

这种结构清晰简单,适合快速上手与维护。

核心功能实现

1. 游戏状态管理

游戏的核心在于状态流转。我们采用集中式的数据对象来管理当前场景、玩家选择历史及资源缓存。

const gameData = {
    currentScene: 0,          // 当前场景 ID
    playerChoices: [],        // 玩家选择历史
    useImages: true,          // 是否启用图片模式
    loadedImages: new Map(),  // 图片缓存
    images: {
        'normal': 'takagi_normal.png',
        'happy': 'takagi_happy.png',
        'shy': 'takagi_shy.png',
        : ,
        : ,
        : ,
        : 
    },
    : {  }
};
'teasing'
'takagi_teasing.png'
'surprised'
'takagi_surprised.png'
'thinking'
'takagi_thinking.png'
'gentle'
'温柔.jpeg'
emojis
/* 备用 Emoji 映射 */

这种设计让调试变得容易,任何状态变化都能触发对应的界面更新。

2. 场景与对话系统

场景数据采用 JSON 格式,每个节点包含角色、文本、表情及可选分支。

const scenarios = {
    0: {
        character: "高木同学",
        text: "西片君,圣诞快乐!你想知道我为你准备了什么特别的礼物吗?",
        expression: "normal",
        choices: [
            { text: "当然想知道!", next: 1 },
            { text: "高木同学,你又想捉弄我了吧?", next: 2 },
            { text: "我也有礼物要送给你...", next: 3 }
        ]
    },
    1: {
        character: "高木同学",
        text: "呵呵,西片君还是这么直接呢~那你就先闭上眼睛,数到 10 哦~",
        expression: "teasing",
        choices: [
            { text: "真的闭上眼睛数数", next: 4 },
            { text: "偷偷看看高木同学在做什么", next: 5 }
        ]
    }
};

3. 动态按钮与事件处理

在生成选项按钮时,事件绑定是关键。虽然可以使用 addEventListener,但在动态 DOM 插入的场景下,内联 onclick 往往更可靠,或者配合事件委托。

function updateChoices(choices) {
    const container = document.getElementById('choicesContainer');
    container.innerHTML = '';
    choices.forEach((choice, index) => {
        const button = document.createElement('button');
        button.className = 'choice-btn';
        button.textContent = `${index + 1}. ${choice.text}`;
        // 内联 onclick 确保动态元素也能响应
        button.setAttribute('onclick', `makeChoice(${index + 1})`);
        container.appendChild(button);
    });
}

function makeChoice(choiceIndex) {
    console.log('玩家选择了选项:', choiceIndex);
    const scene = scenarios[gameData.currentScene];
    const choice = scene.choices[choiceIndex - 1];
    
    if (choice) {
        gameData.playerChoices.push({
            scene: gameData.currentScene,
            choice: choiceIndex,
            text: choice.text,
            timestamp: Date.now()
        });

        if (choice.next && scenarios[choice.next]) {
            setTimeout(() => showScene(choice.next), 300); // 添加过渡延迟
        } else {
            showEnding();
        }
    }
}

4. 角色表情显示

为了兼容不同环境,我们实现了图片优先、Emoji 兜底的策略。

function updateCharacterDisplay(expression) {
    const displayElement = document.getElementById('characterDisplay');
    const imagePath = gameData.images[expression];

    if (gameData.useImages && imagePath) {
        const img = document.createElement('img');
        img.className = 'character-image';
        img.src = `images/${imagePath}`;
        img.alt = '高木同学';
        
        img.onload = () => {
            displayElement.innerHTML = '';
            displayElement.appendChild(img);
        };
        img.onerror = () => {
            // 图片加载失败自动降级为 Emoji
            displayElement.innerHTML = `<div>${gameData.emojis[expression]}</div>`;
        };
    } else {
        displayElement.innerHTML = `<div>${gameData.emojis[expression]}</div>`;
    }
}

界面设计与响应式布局

紧凑布局

采用左右分屏设计,确保信息在一屏内完整展示。

.game-main {
    flex: 1;
    display: flex;
    padding: 20px;
    gap: 20px;
    max-height: calc(100vh - 120px);
}

.character-section {
    flex: 0 0 40%;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(255, 255, 255, 0.05);
    border-radius: 15px;
    border: 1px solid rgba(255, 255, 255, 0.1);
}

.dialogue-section {
    flex: 1;
    display: flex;
    flex-direction: column;
    background: rgba(0, 0, 0, 0.3);
    backdrop-filter: blur(10px);
    border-radius: 15px;
}

移动端适配

针对小屏幕设备,切换为上下布局并调整字体大小。

@media (max-width: 768px) {
    .game-main {
        flex-direction: column;
        padding: 10px;
    }
    .character-section {
        flex: 0 0 200px;
        width: 100%;
    }
    .character-emoji {
        font-size: 80px;
    }
}

视觉动效

增加按钮悬停光波效果和角色浮动动画,提升沉浸感。

.choice-btn:before {
    content: '';
    position: absolute;
    top: 0;
    left: -100%;
    width: 100%;
    height: 100%;
    background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
    transition: left 0.5s;
}

.choice-btn:hover:before {
    left: 100%;
}

@keyframes float {
    0%, 100% { transform: translateY(0); }
    50% { transform: translateY(-15px); }
}

开发难点与解决方案

1. 动态元素事件绑定

动态生成的按钮有时无法正确响应点击。除了内联 onclick,还可以使用事件委托作为备选方案。

// 事件委托方案
document.addEventListener('click', function(e) {
    if (e.target.classList.contains('choice-btn')) {
        const choice = parseInt(e.target.dataset.choice);
        if (choice) makeChoice(choice);
    }
});

2. 本地文件加载限制

直接在浏览器打开 HTML 文件(File://协议)可能因同源策略导致图片无法加载。建议使用本地 HTTP 服务器运行项目。

Windows 启动脚本示例:

@echo off
chcp 65001 >nul
python -m http.server 8000
start http://localhost:8000
pause

3. 存档功能实现

利用 localStorage 保存游戏进度,支持导出导入。

function saveGame() {
    const saveData = {
        currentScene: gameData.currentScene,
        playerChoices: gameData.playerChoices,
        timestamp: new Date().toISOString()
    };
    localStorage.setItem('takagiChristmasSave', JSON.stringify(saveData));
}

function loadGame() {
    const data = localStorage.getItem('takagiChristmasSave');
    if (data) {
        const parsed = JSON.parse(data);
        gameData.currentScene = parsed.currentScene;
        showScene(gameData.currentScene);
    }
}

性能优化与用户体验

代码优化

  • 防抖处理:防止重复点击导致逻辑错误。
  • 图片预加载:初始化时异步加载所有资源,减少运行时卡顿。
function preloadImages() {
    const promises = Object.values(gameData.images).map(path => {
        return new Promise((resolve) => {
            const img = new Image();
            img.onload = resolve;
            img.onerror = resolve; // 失败也不阻塞
            img.src = `images/${path}`;
        });
    });
    Promise.all(promises).then(() => console.log('资源加载完成'));
}

键盘快捷键

支持数字键选择及 Ctrl+S/L 进行存档/读档。

document.addEventListener('keydown', function(e) {
    if (e.key >= '1' && e.key <= '3') makeChoice(parseInt(e.key));
    if (e.ctrlKey && e.key === 's') { e.preventDefault(); saveGame(); }
    if (e.ctrlKey && e.key === 'l') { e.preventDefault(); loadGame(); }
});

部署与运行指南

本地开发

推荐使用 Python 或 Node.js 启动本地服务器,避免 File://协议限制。

# Python
python -m http.server 8000
# Node.js
npx http-server -p 8000

生产环境

可部署至 GitHub Pages 或使用 Nginx 托管。Nginx 配置中建议开启 Gzip 压缩并设置静态资源缓存策略。

server {
    listen 80;
    root /path/to/game;
    gzip on;
    location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
        expires 1y;
    }
}

总结与展望

通过这个项目,我们实践了从状态管理到 UI 渲染的完整流程。纯前端技术栈足以支撑中等规模的交互式叙事游戏。

未来可以进一步探索 WebAssembly 优化计算密集型逻辑,或集成 PWA 实现离线安装体验。对于开发者而言,掌握原生 JS 与 CSS 的底层能力,比单纯依赖框架更能写出高性能的代码。

希望这个案例能为你的 Web 游戏开发提供有价值的参考。

目录

  1. 项目概述与目标
  2. 技术架构选型
  3. 技术栈清单
  4. 项目结构
  5. 核心功能实现
  6. 1. 游戏状态管理
  7. 2. 场景与对话系统
  8. 3. 动态按钮与事件处理
  9. 4. 角色表情显示
  10. 界面设计与响应式布局
  11. 紧凑布局
  12. 移动端适配
  13. 视觉动效
  14. 开发难点与解决方案
  15. 1. 动态元素事件绑定
  16. 2. 本地文件加载限制
  17. 3. 存档功能实现
  18. 性能优化与用户体验
  19. 代码优化
  20. 键盘快捷键
  21. 部署与运行指南
  22. 本地开发
  23. Python
  24. Node.js
  25. 生产环境
  26. 总结与展望

更多推荐文章

查看全部
  • ComfyUI Photoshop 插件安装及工作流配置指南
  • Spring Security 接入 SpringBoot 与前后端分离实战
  • 大模型技术名词与概念学习笔记:架构、Prompt 与推理
  • GPT-5 发布会深度解析:架构革新与实战应用
  • Web 版 IM 聊天信息加密的三种实现方案
  • WebStorm 安装与首次启动配置指南
  • 快速排序核心思想与多种实现方式详解
  • Rust 实战:抓取 GitHub Trending 热门仓库
  • OpenClaw 汉化版部署常见问题排查指南
  • QGroundControl 跨平台安装指南:Windows、macOS、Linux 与 Android 部署
  • 基于 nomic-embed-text-v2-moe 与 Gradio 构建多语言文本相似度 WebUI
  • 无人机 Remote ID Beacon 帧结构与字段深度解析
  • Spring Web MVC 入门指南:从概念到实践
  • 2026 年 3 月全球大模型全景:国产登顶、百万上下文与智能体爆发
  • Docker pull 报错 registry-1.docker.io 超时解决方案
  • Linux 线程与进程的资源划分与内核实现全景解析
  • C++ 实现红黑树与 STL map 底层原理
  • Stable Diffusion 与 Z-Image-Turbo 模型部署及性能对比实战
  • 基于 WebRTC+AI 的智能远程控制解决方案
  • OpenClaw 开源 AI Agent 框架技术解析与架构设计

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online