HTML5+CSS3+JavaScript实现高木同学圣诞树GalGame完整开发指南

4 阅读5分钟

高木同学圣诞树 GalGame

完整前端实现(HTML5+CSS3+JS),可直接复制运行,带剧情、选项、分支、立绘、雪花、圣诞树、音效逻辑

我给你做一套可直接运行、结构完整、可扩展的 GalGame 模板,主题: 圣诞夜 × 西片 × 高木同学 × 告白剧情


一、最终效果

  • 圣诞主题背景(圣诞树+雪景)
  • 高木同学动态立绘
  • 对话文字逐字打印效果
  • 选项分支(不同选择不同剧情)
  • 底部对话框、姓名板
  • 雪花飘落动画(CSS3)
  • 按钮 hover 动效、转场渐变
  • 简单存档/读档(localStorage)
  • 纯前端,无需后端,打开即玩

二、完整代码(直接复制到 .html 运行)

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>高木同学的圣诞树下</title>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
      font-family: "微软雅黑", sans-serif;
    }

    body {
      background: #0b132b;
      overflow: hidden;
      height: 100vh;
    }

    /* 背景 + 圣诞树 */
    .game-bg {
      position: absolute;
      width: 100%;
      height: 100%;
      background: linear-gradient(to bottom, #1a237e, #311b92);
      overflow: hidden;
    }

    .tree {
      position: absolute;
      bottom: 0;
      left: 50%;
      transform: translateX(-50%);
      width: 260px;
      height: 360px;
      z-index: 1;
    }

    /* 雪花动画 */
    .snow {
      position: absolute;
      top: -10px;
      color: white;
      font-size: 12px;
      pointer-events: none;
      animation: snowfall linear infinite;
    }

    @keyframes snowfall {
      0% { transform: translateY(-10px) rotate(0deg); opacity: 1; }
      100% { transform: translateY(100vh) rotate(360deg); opacity: 0.6; }
    }

    /* 立绘 */
    .character {
      position: absolute;
      bottom: 120px;
      left: 50%;
      transform: translateX(-50%);
      width: 280px;
      z-index: 2;
      transition: all 0.5s ease;
    }

    /* 对话框 */
    .dialog-box {
      position: absolute;
      bottom: 0;
      width: 100%;
      height: 180px;
      background: rgba(0,0,0,0.75);
      border-top: 3px solid #e91e63;
      z-index: 10;
      padding: 20px 30px;
      color: #fff;
    }

    .name {
      display: inline-block;
      background: #e91e63;
      padding: 4px 12px;
      border-radius: 6px;
      font-size: 16px;
      margin-bottom: 12px;
    }

    .text {
      font-size: 18px;
      line-height: 1.7;
      min-height: 80px;
      white-space: pre-wrap;
    }

    /* 选项按钮 */
    .choice-box {
      margin-top: 12px;
      display: flex;
      gap: 16px;
    }

    .choice-btn {
      background: rgba(255,255,255,0.15);
      border: 2px solid #fff;
      color: #fff;
      padding: 10px 20px;
      border-radius: 10px;
      cursor: pointer;
      transition: 0.3s;
      font-size: 16px;
    }

    .choice-btn:hover {
      background: #e91e63;
      border-color: #e91e63;
      transform: scale(1.05);
    }

    /* 菜单 */
    .menu {
      position: absolute;
      top: 10px;
      right: 20px;
      z-index: 99;
    }

    .menu button {
      background: rgba(0,0,0,0.4);
      color: #fff;
      border: 1px solid #fff;
      padding: 6px 12px;
      border-radius: 6px;
      margin-left: 8px;
      cursor: pointer;
    }
  </style>
</head>
<body>

<div class="game-bg">
  <div class="menu">
    <button id="save">存档</button>
    <button id="load">读档</button>
  </div>

  <img class="tree" src="https://i.328888.xyz/2025/04/09/76f18f45a787e4.png" alt="圣诞树">
  <img class="character" id="char" src="https://i.328888.xyz/2025/04/09/87e1d88c7a355.png" alt="高木">

  <div class="dialog-box">
    <div class="name" id="name">高木</div>
    <div class="text" id="text"></div>
    <div class="choice-box" id="choice"></div>
  </div>
</div>

<script>
  // ==============================================
  // 1. 雪花生成
  // ==============================================
  function createSnow() {
    const snow = document.createElement('div');
    snow.className = 'snow';
    snow.textContent = '❄';
    snow.style.left = Math.random() * 100 + 'vw';
    snow.style.animationDuration = (Math.random() * 3 + 2) + 's';
    document.body.appendChild(snow);
    setTimeout(() => snow.remove(), 5000);
  }
  setInterval(createSnow, 120);

  // ==============================================
  // 2. 剧情数据
  // ==============================================
  const storyData = [
    {
      name: "高木",
      text: "西片同学……圣诞快乐呀。",
      next: 1
    },
    {
      name: "高木",
      text: "你看,这棵圣诞树漂亮吗?我可是特意等西片过来才一起看的哦。",
      next: 2
    },
    {
      name: "西片",
      text: "呜……又、又捉弄我……不过圣诞树确实很好看。",
      next: 3
    },
    {
      name: "高木",
      text: "西片同学,今天……有什么想对我说的吗?",
      type: "choice",
      choices: [
        { text: "我喜欢你!", next: 4 },
        { text: "今、今天天气不错……", next: 6 }
      ]
    },
    // 好结局
    {
      name: "高木",
      text: "哎呀,西片终于主动说了呢……",
      next: 5
    },
    {
      name: "高木",
      text: "其实我也是哦,最喜欢西片了。圣诞快乐,我的西片同学。",
      end: true
    },
    // 坏结局
    {
      name: "高木",
      text: "呵呵……西片还是这么不坦率呢。",
      next: 7
    },
    {
      name: "高木",
      text: "那下次,可要好好说出真心话哦。",
      end: true
    }
  ];

  // ==============================================
  // 3. 游戏核心逻辑
  // ==============================================
  const textEl = document.getElementById('text');
  const nameEl = document.getElementById('name');
  const choiceEl = document.getElementById('choice');
  let currentIndex = 0;

  // 逐字打印
  async function typeText(str) {
    textEl.textContent = '';
    for (let c of str) {
      textEl.textContent += c;
      await new Promise(r => setTimeout(r, 35));
    }
  }

  // 加载剧情
  async function loadStory(index) {
    currentIndex = index;
    const data = storyData[index];
    choiceEl.innerHTML = '';
    nameEl.textContent = data.name;
    await typeText(data.text);

    // 选项
    if (data.type === 'choice') {
      data.choices.forEach(ch => {
        const btn = document.createElement('button');
        btn.className = 'choice-btn';
        btn.textContent = ch.text;
        btn.onclick = () => loadStory(ch.next);
        choiceEl.appendChild(btn);
      });
    } else if (data.end) {
      const btn = document.createElement('button');
      btn.className = 'choice-btn';
      btn.textContent = '重新开始';
      btn.onclick = () => loadStory(0);
      choiceEl.appendChild(btn);
    } else {
      document.onclick = () => {
        document.onclick = null;
        if (!data.end && data.next != null) loadStory(data.next);
      };
    }
  }

  // ==============================================
  // 4. 存档 / 读档(localStorage)
  // ==============================================
  document.getElementById('save').onclick = () => {
    localStorage.setItem('takagi_save', currentIndex);
    alert('已保存');
  };

  document.getElementById('load').onclick = () => {
    const idx = localStorage.getItem('takagi_save');
    if (idx != null) loadStory(Number(idx));
    else alert('无存档');
  };

  // 开始游戏
  loadStory(0);
</script>
</body>
</html>

三、图片说明(失效可替换)

我用的是占位图床链接,如果打不开,你可以自己替换:

  • ​.tree​​ → 圣诞树图片
  • ​.character​​ → 高木同学立绘

你只要换成自己的 URL 或本地路径即可。


四、核心功能讲解(开发指南重点)

1. 画面结构(GalGame 标准布局)

  • 背景层 ​​game-bg​
  • 特效层(雪花)
  • 角色立绘层
  • 对话框 UI 层
  • 菜单按钮层

完全符合 GalGame 视觉习惯。

2. CSS3 动画

  • 雪花飘落:​​@keyframes snowfall​
  • 按钮放大、渐变
  • 立绘平滑过渡
  • 对话框半透明毛玻璃风格

3. JS 核心玩法逻辑

  • 逐字打印对话(typeText)
  • 剧情节点跳转
  • 选项分支系统
  • 点击屏幕继续
  • 存档/读档(localStorage)

4. 剧情扩展方式

你只需要在 ​​storyData​​ 里加对象:

{
  name: "高木",
  text: "新的对话内容",
  next: 下一个序号
}

加选项:

{
  name: "高木",
  text: "选一个吧",
  type: "choice",
  choices: [
    { text: "选项A", next: 8 },
    { text: "选项B", next: 9 }
  ]
}

五、你可以继续扩展的高级功能(我可以帮你写)

  • 背景音乐 / 音效
  • 立绘表情切换(害羞/笑/脸红)
  • 文本速度调节
  • 快速跳过
  • 多角色多立绘
  • 夜间模式 / UI 主题切换
  • CG 鉴赏模式
  • 更多分支结局

六、我可以帮你定制

你想要:

  1. 更甜的圣诞剧情(牵手、送礼物、告白、亲吻)
  2. 更好看的 UI(粉色/梦幻/日系风格)
  3. 完整可发布版本

告诉我,我可以直接给你升级成完整版可上线的高木同学 GalGame