You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Web-Dev-For-Beginners/translations/zh-CN/6-space-game/6-end-condition/README.md

24 KiB

构建太空游戏第6部分结束与重启

journey
    title 你的游戏完成旅程
    section 结束条件
      定义胜负状态: 3: Student
      实现条件检测: 4: Student
      处理状态转换: 4: Student
    section 玩家体验
      设计反馈系统: 4: Student
      创建重启机制: 5: Student
      优化用户界面: 5: Student
    section 系统集成
      管理游戏生命周期: 5: Student
      处理内存清理: 5: Student
      创建完整体验: 5: Student

每个伟大的游戏都需要清晰的结束条件和流畅的重启机制。你已经打造了一个具有移动、战斗和计分功能的令人印象深刻的太空游戏——现在是时候添加最终的部分,让它感觉完整了。

你的游戏当前无限运行就像美国宇航局1977年发射的旅行者探测器——几十年后仍在太空中旅行。虽然这对于太空探索很好但游戏需要定义好的终点来创造令人满意的体验。

今天,我们将实现合适的胜负条件和重启系统。课程结束时,你将拥有一个抛光完善的游戏,玩家可以完成并重玩,就像定义了媒介的经典街机游戏一样。

mindmap
  root((游戏完成))
    End Conditions
      Victory States[胜利状态]
      Defeat Conditions[失败条件]
      Progress Tracking[进度跟踪]
      State Validation[状态验证]
    Player Feedback
      Visual Messages[视觉信息]
      Color Psychology[色彩心理学]
      Clear Communication[清晰传达]
      Emotional Response[情感反应]
    State Management
      Game Loop Control[游戏循环控制]
      Memory Cleanup[内存清理]
      Object Lifecycle[对象生命周期]
      Event Handling[事件处理]
    Restart Systems
      Input Handling[输入处理]
      State Reset[状态重置]
      Fresh Initialization[全新初始化]
      User Experience[用户体验]
    Polish Elements
      Message Display[消息显示]
      Smooth Transitions[平滑过渡]
      Error Prevention[错误预防]
      Accessibility[无障碍]

课前测验

课前测验

理解游戏结束条件

你的游戏应该何时结束?这个基础问题自早期街机时代以来影响了游戏设计。吃豆人被鬼抓住或吃掉所有豆子即结束,太空入侵者则是在外星人到达底部或全部被击毁时结束。

作为游戏创作者,你定义胜利和失败条件。对于我们的太空游戏,以下是创造引人入胜玩法的常见方式:

flowchart TD
    A["🎮 游戏开始"] --> B{"检查条件"}
    
    B --> C["敌人数"]
    B --> D["英雄生命"]
    B --> E["得分阈值"]
    B --> F["关卡进度"]
    
    C --> C1{"敌人 = 0?"}
    D --> D1{"生命 = 0?"}
    E --> E1{"得分 ≥ 目标?"}
    F --> F1{"目标完成?"}
    
    C1 -->|是| G["🏆 胜利"]
    D1 -->|是| H["💀 失败"]
    E1 -->|是| G
    F1 -->|是| G
    
    C1 -->|否| B
    D1 -->|否| B
    E1 -->|否| B
    F1 -->|否| B
    
    G --> I["🔄 重新开始选项"]
    H --> I
    
    style G fill:#e8f5e8
    style H fill:#ffebee
    style I fill:#e3f2fd
  • 已击毁 N 艘敌舰:如果你将游戏分为不同关卡,通常需要击毁 N 艘敌舰以完成关卡
  • 你的飞船被击毁:有些游戏中,如果你的船被毁你就会输。一种常见方式是生命值机制。每当你的船被毁损失一条命,命数耗尽时游戏失败。
  • 你获得了 N:另一种常见的结束条件是你收集到足够的分数。分数来源由你决定,通常分数赋予消灭敌舰或收集敌舰被击毁后掉落的物品。
  • 完成一个关卡:这可能涉及多个条件,如击毁 X 艘敌舰、收集 Y 分数,或收集特定道具。

实现游戏重启功能

好的游戏通过流畅的重启机制提高重玩价值。当玩家完成游戏(或失败)时,通常希望立即再试一次——无论是为了超越分数还是提升表现。

stateDiagram-v2
    [*] --> Playing: 游戏开始
    Playing --> Victory: 消灭所有敌人
    Playing --> Defeat: 生命值 = 0
    
    Victory --> MessageDisplay: 显示胜利信息
    Defeat --> MessageDisplay: 显示失败信息
    
    MessageDisplay --> WaitingRestart: 按回车提示
    WaitingRestart --> Resetting: 按下回车键
    
    Resetting --> CleanupMemory: 清除定时器
    CleanupMemory --> ClearEvents: 移除监听器
    ClearEvents --> InitializeGame: 全新开始
    InitializeGame --> Playing: 新游戏开始
    
    note right of MessageDisplay
        颜色编码反馈:
        绿色 = 胜利
        红色 = 失败
    end note
    
    note right of Resetting
        完成状态重置
        防止内存泄漏
    end note

俄罗斯方块是这方面的典范:当方块堆满顶部时,你可以立即开始新游戏,无需复杂菜单操作。我们将构建类似的重启系统,清晰地重置游戏状态,让玩家迅速回归游戏。

思考:想想你玩过的游戏。它们在什么情况下结束?如何提示你重启?什么样的重启体验让人感觉顺畅而非烦躁?

你将构建的内容

你将实现最终的功能,把项目打造成完整的游戏体验。这些元素让精致游戏区别于基础原型。

今天我们添加的内容:

  1. 胜利条件:消灭全部敌人并庆祝胜利(你应得的!)
  2. 失败条件:耗尽所有生命,面对失败画面
  3. 重启机制:按回车键即可重新开始——因为一场游戏永远不够
  4. 状态管理:每次都是崭新开始——无遗留敌人或奇怪故障

准备工作

让我们准备开发环境。你应该已经有上节课的所有太空游戏文件。

你的项目文件结构应大致如下:

-| assets
  -| enemyShip.png
  -| player.png
  -| laserRed.png
  -| life.png
-| index.html
-| app.js
-| package.json

启动你的开发服务器:

cd your-work
npm start

该命令执行:

  • http://localhost:5000 运行本地服务器
  • 正确提供你的文件服务
  • 当你修改时自动刷新浏览器

在浏览器打开 http://localhost:5000 并验证游戏是否能运行。你应该可以移动,发射并与敌人互动。确认后,我们即可开始实现功能。

💡 专业建议为避免Visual Studio Code中警告请在文件顶部声明 let gameLoopId;,而不是在 window.onload 函数内声明。这符合现代JavaScript变量声明最佳实践。

flowchart TD
    A["1. 条件追踪"] --> B["2. 事件处理器"]
    B --> C["3. 消息常量"]
    C --> D["4. 重启控制"]
    D --> E["5. 消息显示"]
    E --> F["6. 重置系统"]
    
    G["isHeroDead()\nisEnemiesDead()"] --> A
    H["碰撞事件\n结束游戏事件"] --> B
    I["游戏结束_胜利\n游戏结束_失败"] --> C
    J["回车键\n重启触发"] --> D
    K["胜利/失败\n颜色编码文本"] --> E
    L["状态清理\n全新初始化"] --> F
    
    F --> M["🎮 完整游戏"]
    
    style A fill:#e3f2fd
    style B fill:#e8f5e8
    style C fill:#fff3e0
    style D fill:#f3e5f5
    style E fill:#e0f2f1
    style F fill:#fce4ec
    style M fill:#e1f5fe

实现步骤

步骤1创建结束条件追踪函数

我们需要函数来监测游戏何时结束。就像国际空间站上的传感器不断监控关键系统一样,这些函数将持续检查游戏状态。

function isHeroDead() {
  return hero.life <= 0;
}

function isEnemiesDead() {
  const enemies = gameObjects.filter((go) => go.type === "Enemy" && !go.dead);
  return enemies.length === 0;
}

幕后发生的事情:

  • 检查 主角是否已无生命(糟糕!)
  • 计算 当前还活着并在战斗的敌人数量
  • 返回 当战场清空敌人时返回 true
  • 使用 简单的真假逻辑保持清晰明了
  • 过滤 所有游戏对象,找出存活者

步骤2更新事件处理器以检测结束条件

现在我们将把这些条件检测连接到游戏事件系统中。每当发生碰撞时,游戏会评估是否触发结束条件。这为关键游戏事件创建即时反馈。

sequenceDiagram
    participant Collision
    participant GameLogic
    participant Conditions
    participant EventSystem
    participant Display
    
    Collision->>GameLogic: 激光击中敌人
    GameLogic->>GameLogic: 摧毁对象
    GameLogic->>Conditions: 检查 isEnemiesDead()
    
    alt 全部敌人被击败
        Conditions->>EventSystem: 发送 GAME_END_WIN
        EventSystem->>Display: 显示胜利消息
    else 敌人仍然存在
        Conditions->>GameLogic: 继续游戏
    end
    
    Collision->>GameLogic: 敌人攻击英雄
    GameLogic->>GameLogic: 减少生命值
    GameLogic->>Conditions: 检查 isHeroDead()
    
    alt 生命值 = 0
        Conditions->>EventSystem: 发送 GAME_END_LOSS
        EventSystem->>Display: 显示失败消息
    else 生命值仍然存在
        GameLogic->>Conditions: 检查 isEnemiesDead()
        alt 全部敌人被击败
            Conditions->>EventSystem: 发送 GAME_END_WIN
        end
    end
eventEmitter.on(Messages.COLLISION_ENEMY_LASER, (_, { first, second }) => {
    first.dead = true;
    second.dead = true;
    hero.incrementPoints();

    if (isEnemiesDead()) {
      eventEmitter.emit(Messages.GAME_END_WIN);
    }
});

eventEmitter.on(Messages.COLLISION_ENEMY_HERO, (_, { enemy }) => {
    enemy.dead = true;
    hero.decrementLife();
    if (isHeroDead())  {
      eventEmitter.emit(Messages.GAME_END_LOSS);
      return; // 胜利之前的代价
    }
    if (isEnemiesDead()) {
      eventEmitter.emit(Messages.GAME_END_WIN);
    }
});

eventEmitter.on(Messages.GAME_END_WIN, () => {
    endGame(true);
});
  
eventEmitter.on(Messages.GAME_END_LOSS, () => {
  endGame(false);
});

这里发生的事情:

  • 激光击中敌人:双方消失,你得分,我们检查你是否获胜
  • 敌人撞击你:你失去一条命,我们检查你是否还活着
  • 合理顺序:先检查失败(没人想同时赢又输!)
  • 即时反应:重要事件发生时游戏立刻感知

步骤3添加新消息常量

你需要为 Messages 常量对象添加新的消息类型。这些常量帮助保持一致性,避免事件系统中的拼写错误。

GAME_END_LOSS: "GAME_END_LOSS",
GAME_END_WIN: "GAME_END_WIN",

上面内容:

  • 添加 代表游戏结束事件的常量以保持一致性
  • 使用 描述性名称清晰标识事件目的
  • 遵循 现有消息类型命名规范

步骤4实现重启控制

接下来添加键盘控制,让玩家能重启游戏。回车键是自然选择,因其常用作确认与开始新游戏的按键。

在现有keydown事件监听器中添加对回车键的检测

else if(evt.key === "Enter") {
   eventEmitter.emit(Messages.KEY_EVENT_ENTER);
}

添加新的消息常量:

KEY_EVENT_ENTER: "KEY_EVENT_ENTER",

你需要知道的:

  • 扩展 你现有的键盘事件处理系统
  • 使用 回车键作为重启触发器,提升用户体验直观性
  • 发布 自定义事件,供游戏的其他部分监听
  • 保持 与其他键盘控制相同模式

步骤5创建消息显示系统

游戏需要清楚地向玩家传达结果。我们将创建一个消息系统,使用颜色编码的文本显示胜利和失败状态,类似早期计算机终端界面绿色表示成功,红色表示错误。

创建 displayMessage() 函数:

function displayMessage(message, color = "red") {
  ctx.font = "30px Arial";
  ctx.fillStyle = color;
  ctx.textAlign = "center";
  ctx.fillText(message, canvas.width / 2, canvas.height / 2);
}

一步步,发生了什么:

  • 设置 字号和字体,确保文字清晰易读
  • 应用 颜色参数,默认为“红色”表示警告
  • 文字在画布水平和垂直居中
  • 利用 现代JavaScript默认参数支持灵活颜色选择
  • 借助 canvas 2D上下文直接渲染文本

创建 endGame() 函数:

function endGame(win) {
  clearInterval(gameLoopId);

  // 设置延迟以确保所有待处理的渲染完成
  setTimeout(() => {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.fillStyle = "black";
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    if (win) {
      displayMessage(
        "Victory!!! Pew Pew... - Press [Enter] to start a new game Captain Pew Pew",
        "green"
      );
    } else {
      displayMessage(
        "You died !!! Press [Enter] to start a new game Captain Pew Pew"
      );
    }
  }, 200)  
}

此函数做了什么:

  • 冻结 所有移动——不再有舰船或激光动作
  • 暂停 200毫秒让最后一帧完成绘制
  • 清屏 并将背景涂成黑色,效果更戏剧化
  • 显示 不同消息给胜利者和失败者
  • 用色彩区分消息——绿代表好消息,红代表……不太好
  • 告诉 玩家如何重新开始游戏

🔄 教学反馈点

游戏状态管理:在实现重置功能前确保理解:

  • 结束条件如何创造清晰的游戏目标
  • 为什么视觉反馈对玩家理解至关重要
  • 合适清理对防止内存泄漏的重要性
  • 事件驱动架构如何实现干净的状态切换

自测速答:如果重置时不清理事件监听器会怎样? 答案:导致内存泄漏和重复事件处理器,引发不可预测的行为

游戏设计原则:你正在实现的是:

  • 清晰目标:玩家明确什么是胜利、什么是失败
  • 即时反馈:游戏状态变化立即被传达
  • 用户控制:玩家准备好了就能重启
  • 系统可靠性适当清理避免bug和性能损耗

步骤6实现游戏重置功能

重置系统需彻底清理当前游戏状态,初始化清新的游戏会话。保证玩家得到干净的开局,无前一局遗留数据。

创建 resetGame() 函数:

function resetGame() {
  if (gameLoopId) {
    clearInterval(gameLoopId);
    eventEmitter.clear();
    initGame();
    gameLoopId = setInterval(() => {
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      ctx.fillStyle = "black";
      ctx.fillRect(0, 0, canvas.width, canvas.height);
      drawPoints();
      drawLife();
      updateGameObjects();
      drawGameObjects(ctx);
    }, 100);
  }
}

逐部分理解:

  • 检查 当前是否有游戏循环正在运行
  • 清除 现有游戏循环以停止所有当前游戏活动
  • 移除 所有事件监听器防止内存泄漏
  • 重新初始化 游戏状态,生成全新对象和变量
  • 启动 新的游戏循环,包含必需的游戏逻辑
  • 保持 100毫秒间隔确保游戏性能一致

将回车键事件处理器添加到 initGame() 函数:

eventEmitter.on(Messages.KEY_EVENT_ENTER, () => {
  resetGame();
});

为你的 EventEmitter 类添加 clear() 方法:

clear() {
  this.listeners = {};
}

关键点总结:

  • 回车键按下连接到重置游戏功能
  • 游戏初始化时注册此事件监听器
  • 提供 一种清理所有事件监听器的干净方式,用于重置
  • 通过 清空事件处理器避免内存泄漏
  • 监听器对象重置为空以便重新初始化

恭喜!🎉

👽 💥 🚀 你成功从零构建了完整的游戏。像1970年代首批电子游戏程序员一样你把代码变成了带有合适游戏机制和用户反馈的互动体验。🚀 💥 👽

你完成了:

  • 实现 完整的胜负条件并向用户反馈
  • 创建 无缝重启系统,支持连续游戏
  • 设计 明确的游戏状态视觉传达
  • 管理 复杂的游戏状态切换和清理
  • 整合 所有组件成一个连贯、可玩的游戏

🔄 教学反馈点

完整游戏开发系统:庆祝你掌握了完整开发流程:

  • 结束条件如何创造满足感玩家体验?
  • 为什么正确的状态管理对游戏稳定性关键?
  • 视觉反馈如何增强玩家理解?
  • 重启系统在玩家留存中扮演什么角色?

系统掌控力:你的完整游戏展现了:

  • 全栈游戏开发:涵盖图形、输入和状态管理
  • 专业架构:事件驱动系统加上适当清理
  • 用户体验设计:清晰反馈与直观控制
  • 性能优化:高效渲染和内存管理
  • 打磨完善:所有细节让游戏感觉完美

行业级技能:你实现了:

  • 游戏循环架构:实时系统性能稳定
  • 事件驱动编程:解耦系统便于扩展
  • 状态管理:复杂数据处理与生命周期管控
  • 用户界面设计:清晰沟通与响应式操作
  • 测试与调试:迭代开发和问题解决

下5分钟你可以做什么

  • 玩你的完整游戏,测试所有胜负条件
  • 尝试修改不同结束条件参数
  • 加入 console.log 语句追踪游戏状态变化
  • 与朋友分享游戏并收集反馈

🎯 这小时你可以完成什么

  • 完成课后测验,反思你的开发历程
  • 为胜负状态添加音效效果
  • 实现额外的结束条件,如限时或奖励目标
  • 制作不同难度,调整敌人数量
  • 美化视觉效果,使用更好字体和颜色

📅 你的周度游戏开发精通计划

  • 完成增强版太空游戏,多关卡和进度系统
  • 增加高级功能,如增强道具、多样敌人类型和特殊武器
  • 创建保存的高分榜系统
  • 设计菜单、设置和选项的用户界面
  • 优化性能,适配不同设备和浏览器
  • 在线部署你的游戏,与社区分享

🌟 您的一个月游戏开发职业规划

  • 制作多个完整游戏,探索不同的类型和机制
  • 学习高级游戏开发框架,如 Phaser 或 Three.js
  • 参与开源游戏开发项目的贡献
  • 研究游戏设计原则和玩家心理
  • 创建展示您游戏开发技能的作品集
  • 与游戏开发社区连接,持续学习

🎯 您的完整游戏开发精通时间表

timeline
    title 完整游戏开发学习进度
    
    section 基础课程1-2
        游戏架构:项目结构
                         :资源管理
                         :画布基础
                         :事件系统
        
    section 交互系统课程3-4
        玩家控制:输入处理
                      :移动机制
                      :碰撞检测
                      :物理模拟
        
    section 游戏机制课程5
        反馈系统:得分机制
                        :生命管理
                        :视觉传达
                        :玩家激励
        
    section 游戏完成课程6
        精细化与流程:结束条件
                     :状态管理
                     :重启系统
                     :用户体验
        
    section 高级功能(一周)
        增强技能:音频整合
                          :视觉特效
                          :关卡进度
                          :性能优化
        
    section 职业发展(一月)
        行业准备:框架精通
                          :团队协作
                          :作品集开发
                          :社区参与
        
    section 职业晋升(三月)
        专业化:高级游戏引擎
                      :平台部署
                      :盈利策略
                      :行业网络

🛠️ 您的完整游戏开发工具包总结

完成整个太空游戏系列后,您现已掌握:

  • 游戏架构:事件驱动系统、游戏循环和状态管理
  • 图形编程Canvas API精灵渲染和视觉效果
  • 输入系统:键盘处理、碰撞检测和响应式控制
  • 游戏设计:玩家反馈、进度系统和参与机制
  • 性能优化:高效渲染、内存管理和帧率控制
  • 用户体验:清晰沟通、直观控制和细节打磨
  • 专业模式:整洁代码、调试技术和项目组织

现实应用:您的游戏开发技能直接适用于:

  • 交互式网络应用:动态界面和实时系统
  • 数据可视化:动画图表和交互图形
  • 教育技术:游戏化和吸引人的学习体验
  • 移动开发:基于触摸的交互和性能优化
  • 模拟软件:物理引擎和实时建模
  • 创意产业:互动艺术、娱乐和数字体验

获得的专业技能:您现在可以:

  • 架构复杂的交互系统从零开始
  • 调试使用系统化方法的实时应用
  • 优化性能以流畅用户体验
  • 设计吸引人的用户界面和交互模式
  • 高效协作处理技术项目并合理组织代码

掌握的游戏开发概念

  • 实时系统:游戏循环、帧率管理和性能
  • 事件驱动架构:解耦系统和消息传递
  • 状态管理:复杂数据处理和生命周期管理
  • 用户界面编程Canvas 图形和响应式设计
  • 游戏设计理论:玩家心理和参与机制

下一阶段您已准备好探索高级游戏框架、3D 图形、多玩家系统,或转向专业游戏开发职位!

🌟 成就解锁:您已完成完整的游戏开发旅程,从零打造出专业品质的互动体验!

欢迎加入游戏开发社区! 🎮

GitHub Copilot Agent 挑战 🚀

使用 Agent 模式完成以下挑战:

描述: 通过实现一个关卡进度系统,增加难度和奖励功能,增强太空游戏。

提示: 创建一个多关卡太空游戏系统,每个关卡中敌舰数量增加,速度和生命值也提高。增加随关卡增长的得分倍增器,并实现当敌人被摧毁时随机出现的强化道具(如速射或护盾)。包括关卡完成奖励,并在屏幕上显示当前关卡与现有的得分和生命值。

了解更多关于agent mode的信息。

🚀 可选增强挑战

给您的游戏添加音效:通过实现音效来增强游戏体验!考虑添加以下音效:

  • 激光射击,玩家开火时
  • 敌舰摧毁,敌舰被击中时
  • 英雄受伤,玩家受击时
  • 胜利音乐,游戏胜利时
  • 失败音效,游戏失败时

音频实现示例:

// 创建音频对象
const laserSound = new Audio('assets/laser.wav');
const explosionSound = new Audio('assets/explosion.wav');

// 在游戏事件中播放声音
function playLaserSound() {
  laserSound.currentTime = 0; // 重置到开始处
  laserSound.play();
}

您需要了解:

  • 创建用于不同音效的 Audio 对象
  • 重置currentTime以支持快速连续音效播放
  • 处理浏览器自动播放策略,通过用户交互触发声音
  • 管理音量和时序以提升游戏体验

💡 学习资源:探索这个audio sandbox,了解更多 JavaScript 游戏中音频实现。

课后测验

课后测验

复习与自学

您的作业是创建一个新的示例游戏,您可以探索一些有趣的游戏,看看想制作哪种类型的游戏。

任务

构建示例游戏


免责声明
本文件由 AI 翻译服务 Co-op Translator 翻译而成。尽管我们努力确保准确性,但请注意自动翻译可能包含错误或不准确之处。原始文本的原文版本应视为权威来源。对于重要信息,建议采用专业人工翻译。对于因使用本翻译而产生的任何误解或曲解,我们概不负责。