Web-Dev-For-Beginners 空间游戏课程作业:用继承、组合与 Pub/Sub 事件系统打造游戏原型
Web-Dev-For-Beginners 空间游戏课程作业用继承、组合与 Pub/Sub 事件系统打造游戏原型【免费下载链接】Web-Dev-For-Beginners24 Lessons, 12 Weeks, Get Started as a Web Developer项目地址: https://gitcode.com/GitHub_Trending/we/Web-Dev-For-Beginners本篇文章围绕 Web-Dev-For-Beginners 开源课程6-space-game/1-introductionBuild a Space Game Part 1: Introduction的结课作业展开使用 JavaScript 设计模式搭建一个可运行的简单游戏原型。你将学会在继承与组合两种对象组织方式之间做出选择实现一个完整的EventEmitter发布-订阅pub/sub通信系统并把键盘/鼠标输入、游戏对象与事件流串联起来。读完本文你可以独立完成一套功能可用、架构清晰、易于扩展的事件驱动游戏原型并对照仓库中的评分标准自查交付质量。作业目标把设计模式真正用起来该作业对应课程第一课 课程讲义空间游戏引言 的结尾任务其核心目标是将本课学到的两类知识落成代码——即架构层面的继承 / 组合对象组织方式与通信层面的pub/sub 事件系统。课程讲义用一个非常形象的比喻解释了本作业的价值游戏对象英雄、敌人、道具、爆炸特效虽然外观迥异但共享四类基本行为——存在拥有x、y坐标、可移动许多对象会改变位置、有生命周期部分对象短暂存活、会响应交互碰撞、拾取、状态更新。本作业要求你把这些抽象落到一个真实可玩的小型游戏里从而理解这些模式如何支撑从简单脚本到大型应用的演进。现实参照pub/sub 的发布-订阅通信思路与 React/Vue 前端事件系统、后端微服务通信、Unity 事件系统等业界实践同源。本作业正是为这些场景打地基。二选一继承还是组合作业要求你从两种架构模式中明确选择一种来实现方案 A基于类的继承。典型层级为GameObject→Movable→Hero用is-a英雄是一个可移动对象关系组织代码。方案 B基于组合。使用工厂函数 行为混合behavior mixing用has-a英雄拥有移动能力关系按需拼装对象。课程讲义给出了两套完整示例这里复述并拆解其要点作为作业实现的参照// —— 方案 A类继承 —— // Step 1: 基础 GameObject 类保存位置与类型 class GameObject { constructor(x, y, type) { this.x x; this.y y; this.type type; } } // Step 2: 通过继承增加移动能力 class Movable extends GameObject { constructor(x, y, type) { super(x, y, type); // 调用父类构造器初始化继承的属性 } moveTo(x, y) { this.x x; this.y y; } } // Step 3: 创建具体的游戏对象类型 class Hero extends Movable { constructor(x, y) { super(x, y, Hero); // 自动设定类型 } } class Tree extends GameObject { constructor(x, y) { super(x, y, Tree); // 树不需要移动能力 } } // Step 4: 使用 const hero new Hero(0, 0); hero.moveTo(5, 5); // 英雄可以移动 const tree new Tree(10, 15); // tree.moveTo() 会报错 —— 树不能移动层级关系阻止了错误行为// —— 方案 B组合 / 工厂函数 —— // Step 1: 基础行为对象 const gameObject { x: 0, y: 0, type: }; const movable { moveTo(x, y) { this.x x; this.y y; } }; // Step 2: 用展开语法合并行为 const movableObject { ...gameObject, ...movable }; // Step 3: 工厂函数按需定制对象 function createHero(x, y) { return { ...movableObject, x, y, type: Hero }; } function createStatic(x, y, type) { return { ...gameObject, x, y, type }; } // Step 4: 使用 const hero createHero(10, 10); hero.moveTo(5, 5); // 正常 const tree createStatic(0, 0, Tree); // tree.moveTo 是 undefined —— 没有组合进移动行为选型建议来自课程讲义存在清晰的 is-a 关系时选继承Herois-aMovable需要 has-a 关系与最大灵活性时选组合Herohasmovement同一应用中可以混合使用两种方式还需考虑团队习惯与项目需求。仓库中的实际实现可以印证方案 A 的可行性。例如 移动元素一课的解决方案 app.js 中Hero extends GameObject、Enemy extends GameObject基类GameObject保存x、y、dead、type、width、height、img并统一提供draw(ctx)方法子类通过super(x, y)复用基类构造器并追加自己的属性如Hero.speed、Enemy的自动下移逻辑——这正是作业要求有清晰类/对象层级的评分标准所指的实现形态。进阶练习讲义自测题如果要创建一个既能移动又能飞行的 Flying Enemy继承方案是class FlyingEnemy extends Movable组合方案则是{ ...movable, ...flyable, ...gameObject }。构建 EventEmitterpub/sub 通信的核心作业要求包含一个EventEmitter类用它实现发布-订阅消息传递。课程讲义给出的是最小但完整的实现class EventEmitter { constructor() { this.listeners {}; // 按消息类型存放所有监听器 } // 注册某个消息类型的监听器订阅 on(message, listener) { if (!this.listeners[message]) { this.listeners[message] []; } this.listeners[message].push(listener); } // 向所有已注册监听器广播消息发布可携带可选数据 emit(message, payload null) { if (this.listeners[message]) { this.listeners[message].forEach(listener { listener(message, payload); }); } } }这一实现的四个关键点集中管理所有监听器存在一个以消息名为键的对象中形成唯一的事件中枢on()订阅为指定消息追加监听函数重复调用可注册多个监听者emit()发布把消息广播给该类型下的全部监听者支持可选的payload参数传递相关信息解耦发布者不需要知道谁在监听订阅者也不需要知道消息从何而来。仓库中的正式解决方案与讲义实现完全一致见 移动元素解决方案on负责收集监听器emit以(message, payload)的形式逐个调用监听函数。后续课程进一步展示了该模式的伸缩性——在 终局判定一课的解决方案 app.js 中同一个EventEmitter类被原样复用通过注册更多消息类型支撑起完整的对局流程这正是作业可扩展架构加分项的最佳示范。设计消息类型至少 2–3 种从常量开始作业要求设定至少 2–3 种不同的消息类型例如PLAYER_MOVE、ENEMY_SPAWN、SCORE_UPDATE并建议用常量对象集中定义以杜绝拼写错误并提升可维护性。讲义中的示范const Messages { HERO_MOVE_LEFT: HERO_MOVE_LEFT, HERO_MOVE_RIGHT: HERO_MOVE_RIGHT, ENEMY_SPOTTED: ENEMY_SPOTTED };仓库中的实际项目展示了消息类型如何随游戏复杂度自然生长。早期阶段移动元素解决方案只有方向键事件const Messages { KEY_EVENT_UP: KEY_EVENT_UP, KEY_EVENT_DOWN: KEY_EVENT_DOWN, KEY_EVENT_LEFT: KEY_EVENT_LEFT, KEY_EVENT_RIGHT: KEY_EVENT_RIGHT, };到终局判定阶段终局判定解决方案消息类型扩展为输入、碰撞、胜负三类共 10 种const Messages { KEY_EVENT_ENTER: KEY_EVENT_ENTER, KEY_EVENT_UP: KEY_EVENT_UP, KEY_EVENT_DOWN: KEY_EVENT_DOWN, KEY_EVENT_LEFT: KEY_EVENT_LEFT, KEY_EVENT_RIGHT: KEY_EVENT_RIGHT, KEY_EVENT_SPACE: KEY_EVENT_SPACE, COLLISION_ENEMY_LASER: COLLISION_ENEMY_LASER, COLLISION_ENEMY_HERO: COLLISION_ENEMY_HERO, GAME_END_LOSS: GAME_END_LOSS, GAME_END_WIN: GAME_END_WIN, };注意它的演进轨迹完全符合讲义中的最佳实践警告不要一开始就定义过多过细的消息先使用宽泛类别随需求明朗再细化。COLLISION_ENEMY_LASER等消息还携带payload如{ enemy }、{ first, second }来传递碰撞双方的信息这是消息设计的第二个要点payload 保持简单聚焦只带必要数据。在事件驱动架构下碰撞检测系统只负责发布COLLISION_ENEMY_LASER而得分更新、爆炸特效、音效播放等系统各自订阅并响应同一事件——多个订阅者互不知晓、互不干扰这就是解耦的直接收益见 终局判定解决方案 中多监听器订阅同一碰撞消息的写法。打通输入键盘/鼠标事件如何驱动游戏事件作业要求把用户输入键盘/鼠标连接到游戏事件。实现路径分两步监听原生输入事件作为发布者用emit广播游戏对象作为订阅者用on注册响应。课程讲义中的最小示例// 发布端原生键盘事件 → 游戏消息 window.addEventListener(keydown, (event) { switch(event.key) { case ArrowLeft: eventEmitter.emit(Messages.HERO_MOVE_LEFT); break; case ArrowRight: eventEmitter.emit(Messages.HERO_MOVE_RIGHT); break; } });仓库解决方案中这条链路被完整落地移动元素解决方案 app.js// 发布端keydown 中根据按键 emit 对应消息 window.addEventListener(keydown, (event) { if (event.key ArrowUp) eventEmitter.emit(Messages.KEY_EVENT_UP); if (event.key ArrowDown) eventEmitter.emit(Messages.KEY_EVENT_DOWN); if (event.key ArrowLeft) eventEmitter.emit(Messages.KEY_EVENT_LEFT); if (event.key ArrowRight) eventEmitter.emit(Messages.KEY_EVENT_RIGHT); }); // 订阅端英雄移动逻辑 eventEmitter.on(Messages.KEY_EVENT_UP, () { hero.y - 5; });这种间接通信带来三个实际好处讲义原话要点改键位零成本想换键位只需改keydown的映射游戏逻辑毫发无损一个输入、多个响应同一个KEY_EVENT_UP可以被移动、音效、镜头跟随等多个系统同时订阅可测试性提升每个订阅者独立工作出现问题可以准确定位到具体监听器。加分项提示作业明确把多种输入方法键盘和鼠标事件列为加分项。如果你采用讲义中Pong变体的建议鼠标移动控制球拍就是天然的鼠标输入用例。游戏元素与交互建议作业对游戏内容的最低要求是至少一个玩家控制的角色至少一个其他游戏对象敌人、可收集道具或环境元素对象间的基本交互碰撞、收集或通信。讲义给出的四个可选原型方向均适合作为作业载体建议游戏核心机制可用消息示例贪吃蛇Snake蛇身跟随蛇头食物随机出现PLAYER_MOVE、FOOD_EATENPong 变体球拍响应输入球体弹墙PADDLE_MOVE、BALL_BOUNCE、SCORE_UPDATE收集游戏玩家移动拾取物品、躲避障碍PLAYER_MOVE、ITEM_COLLECTED、OBSTACLE_HIT塔防基础Tower Defense防御塔探测并射击移动敌人ENEMY_SPAWN、TOWER_FIRE、ENEMY_DIED仓库成品 空间游戏完整解决方案 可以视作满分作业的参考它以player、bullets、enemies、particles四类对象构成世界spawnEnemy()在屏幕四边随机生成敌人见 app.jsfireBullet()在按住空格时按冷却时间发射子弹update()中通过距离平方比较实现子弹-敌人、敌人-玩家的碰撞判定gameOver()用localStorage持久化最高分。这个实现证明了即使不引入重型框架仅凭原生 Canvas 与清晰的对象组织就能构建完整可玩、可扩展的游戏循环。代码结构指南作业给定骨架作业给出了推荐的起始结构请在你的实现中沿用// Example starting structure const Messages { // Define your game messages here }; class EventEmitter { // Your event system implementation } // Choose either class-based OR composition approach // Class-based example: class GameObject { /* base properties */ } class Player extends GameObject { /* player-specific behavior */ } // OR Composition example: const gameObject { /* base properties */ }; const movable { /* movement behavior */ }; function createPlayer() { /* combine behaviors */ }一个符合要求的原型结构可以组织为Messages常量 →EventEmitter类 → 游戏对象定义继承或组合二选一→eventEmitter实例 → 键盘/鼠标监听发布端→ 各对象on()订阅订阅端→ 简单requestAnimationFrame或setInterval驱动渲染。仓库中的完整流程可对照 移动元素解决方案 app.js 全文件研读。验证你的实现四条自测标准作业明确要求对实现进行测试验证清单如下对象随事件移动或改变发布移动消息后对象位置确实更新多对象响应同一事件同一个消息被两个以上监听器订阅且全部正常触发无需改动旧代码即可添加新行为新增一个on()订阅就能接入新系统这是 pub/sub 的核心卖点键盘/鼠标输入正确触发游戏事件模拟按键确认emit链路的每一步都生效。讲义给出的测试技巧是让每个订阅者独立可测例如移动逻辑只依赖hero.moveTo()那么单独调用监听器即可验证坐标变化无需依赖浏览器事件。提交清单与评分标准作业的交付物要求如下JavaScript 文件包含游戏实现可拆分为多个文件HTML 文件用于运行和测试游戏可以非常简陋注释说明你选择了哪种模式及理由简要文档列出你的消息类型及其功能。评分标准原文完整保留标准优秀3 分合格2 分待改进1 分架构模式正确实现继承或组合类/对象层级清晰使用了所选模式但存在小问题或不一致尝试使用模式但实现存在明显问题Pub/Sub 实现EventEmitter 正常工作支持多种消息类型且事件流正确基础 pub/sub 可用具备部分事件处理存在事件系统但运行不可靠游戏功能三个及以上通过事件通信的交互元素两个交互元素具备基本事件通信仅一个元素响应事件或基本交互代码质量代码整洁、注释完善、组织逻辑清晰且使用现代 JavaScript整体组织良好注释充分代码可运行但缺乏组织或清晰注释加分项展示模式巧妙用法的创意游戏机制多种输入方式键盘与鼠标事件并用易于扩展新功能的可扩展架构。小结本作业是空间游戏课程系列的架构热身它不要求华丽画面只要求你用继承或组合把对象组织清楚用EventEmitter把通信解耦。完成它之后你将拥有可直接迁移到前端框架、后端服务与任何事件驱动系统中的三项能力对象结构设计、发布-订阅实现、输入到事件的链路搭建。仓库中从 第一课讲义 到 移动元素、终局判定 乃至 完整成品 的逐级演进就是这套作业方法论最直观的参考答案。【免费下载链接】Web-Dev-For-Beginners24 Lessons, 12 Weeks, Get Started as a Web Developer项目地址: https://gitcode.com/GitHub_Trending/we/Web-Dev-For-Beginners创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联
返回资讯列表 →