10浏览
查看: 10|回复: 3

[项目] 【花雕动手做】基于 Kitronik 游戏机开发板之乒乓球游戏

[复制链接]
【花雕动手做】基于 Kitronik 游戏机开发板之乒乓球游戏图2

Kitronik ARCADE 使用 Microsoft MakeCode 平台,具有以下优势:
图形化编程界面:适合初学者,支持拖拽式编程。
即时模拟器:可以实时测试游戏效果。
硬件兼容性:可部署到 Kitronik ARCADE 设备,实现实体游戏体验。
支持 Python/JavaScript:便于进阶学习。


【花雕动手做】基于 Kitronik 游戏机开发板之乒乓球游戏图1

驴友花雕  中级技神
 楼主|

发表于 3 小时前

【花雕动手做】基于 Kitronik 游戏机开发板之乒乓球游戏

作为学习、练习与尝试,这里创建一个乒乓球的小游戏。
打开网页版:https://arcade.makecode.com/,设置项目名称:乒乓球

JavaScript 实验代码

  1. const BALL_IMAGE = img`
  2.     . . e e 1 e e e . .
  3.     . e 1 1 d d d d e .
  4.     e 1 d d d d d d d e
  5.     e d d d d d d d d e
  6.     e d d d d d d d d e
  7.     e d d d d d d d d e
  8.     e d d d d d d d d e
  9.     . e d d d d d d e .
  10.     . . e e e e e e . .
  11. `;
  12. const PADDLE_SPEED = 150;
  13. const PADDING_FROM_WALL = 3;
  14. let pingMessage = false;
  15. // if player doesn't interact for 'TIMEOUT' time, revert to ai
  16. const TIMEOUT = 5000;
  17. let playerOneLastMove = -TIMEOUT;
  18. let playerTwoLastMove = -TIMEOUT;
  19. controller.setRepeatDefault(0, 1000);
  20. controller.up.onEvent(ControllerButtonEvent.Repeated, () => playerOneLastMove = game.runtime());
  21. controller.down.onEvent(ControllerButtonEvent.Repeated, () => playerOneLastMove = game.runtime());
  22. controller.player2.up.onEvent(ControllerButtonEvent.Repeated, () => playerTwoLastMove = game.runtime());
  23. controller.player2.down.onEvent(ControllerButtonEvent.Repeated, () => playerTwoLastMove = game.runtime());
  24. const playerOne = createPlayer(info.player1);
  25. playerOne.left = PADDING_FROM_WALL;
  26. controller.moveSprite(playerOne, 0, PADDLE_SPEED);
  27. const playerTwo = createPlayer(info.player2);
  28. playerTwo.right = screen.width - PADDING_FROM_WALL;
  29. controller.player2.moveSprite(playerTwo, 0, PADDLE_SPEED);
  30. createBall();
  31. function createPlayer(player: info.PlayerInfo) {
  32.     const output = sprites.create(image.create(3, 18), SpriteKind.Player);
  33.     output.image.fill(player.bg);
  34.     output.setStayInScreen(true);
  35.     player.setScore(0);
  36.     player.showPlayer = false;
  37.     return output;
  38. }
  39. function createBall() {
  40.     let ball = sprites.create(BALL_IMAGE.clone(), SpriteKind.Enemy);
  41.     ball.vy = randint(-20, 20);
  42.     ball.vx = 60 * (Math.percentChance(50) ? 1 : -1);
  43. }
  44. game.onUpdate(function () {
  45.     sprites
  46.         .allOfKind(SpriteKind.Enemy)
  47.         .forEach(b => {
  48.             const scoreRight = b.x < 0;
  49.             const scoreLeft = b.x >= screen.width;
  50.             if (scoreRight) {
  51.                 info.player2.changeScoreBy(1)
  52.             } else if (scoreLeft) {
  53.                 info.player1.changeScoreBy(1)
  54.             }
  55.             if (b.top < 0) {
  56.                 b.vy = Math.abs(b.vy);
  57.             } else if (b.bottom > screen.height) {
  58.                 b.vy = -Math.abs(b.vy);
  59.             }
  60.             if (scoreLeft || scoreRight) {
  61.                 b.destroy(effects.disintegrate, 500);
  62.                 control.runInParallel(function () {
  63.                     pause(250);
  64.                     createBall();
  65.                 });
  66.             }
  67.         }
  68.         );
  69. });
  70. game.onShade(function () {
  71.     if (pingMessage) {
  72.         screen.printCenter("ping", 5);
  73.     } else {
  74.         screen.printCenter("pong", 5);
  75.     }
  76. })
  77. sprites.onOverlap(SpriteKind.Player, SpriteKind.Enemy,
  78.     (sprite: Sprite, otherSprite: Sprite) => {
  79.         const fromCenter = otherSprite.y - sprite.y;
  80.         otherSprite.vx = otherSprite.vx * -1.05;
  81.         otherSprite.vy += (sprite.vy >> 1) + (fromCenter * 3);
  82.         otherSprite.startEffect(effects.ashes, 150);
  83.         sprite.startEffect(effects.ashes, 100);
  84.         otherSprite.image.setPixel(
  85.             randint(1, otherSprite.image.width - 2),
  86.             randint(1, otherSprite.image.height - 2),
  87.             sprite.image.getPixel(0, 0)
  88.         );
  89.         pingMessage = !pingMessage;
  90.         // time out this event so it doesn't retrigger on the same collision
  91.         pause(500);
  92.     }
  93. );
  94. controller.A.onEvent(ControllerButtonEvent.Pressed, () => addBall(info.player1));
  95. controller.B.onEvent(ControllerButtonEvent.Pressed, () => removeBall(info.player1));
  96. controller.player2.A.onEvent(ControllerButtonEvent.Pressed, () => addBall(info.player2));
  97. controller.player2.B.onEvent(ControllerButtonEvent.Pressed, () => removeBall(info.player2));
  98. function addBall(player: info.PlayerInfo) {
  99.     player.changeScoreBy(-2);
  100.     createBall();
  101. }
  102. function removeBall(player: info.PlayerInfo) {
  103.     const balls = sprites.allOfKind(SpriteKind.Enemy);
  104.     if (balls.length > 1) {
  105.         Math.pickRandom(balls).destroy();
  106.         player.changeScoreBy(-2);
  107.     }
  108. }
  109. game.onUpdate(function () {
  110.     const currTime = game.runtime();
  111.     if (playerOneLastMove + TIMEOUT < currTime) {
  112.         trackBall(playerOne);
  113.     }
  114.     if (playerTwoLastMove + TIMEOUT < currTime) {
  115.         trackBall(playerTwo);
  116.     }
  117.     function trackBall(player: Sprite) {
  118.         const next = nextBall(player);
  119.         if (!next)
  120.             return;
  121.         if (ballFacingPlayer(player, next)) {
  122.             // move to where ball is expected to intersect
  123.             intersectBall(player, next);
  124.         } else {
  125.             // relax, ball is going other way
  126.             player.vy = 0;
  127.         }
  128.     }
  129.     function nextBall(player: Sprite) {
  130.         return sprites
  131.             .allOfKind(SpriteKind.Enemy)
  132.             .sort((a, b) => {
  133.                 const aFacingPlayer = ballFacingPlayer(player, a);
  134.                 const bFacingPlayer = ballFacingPlayer(player, b);
  135.                 // else prefer ball facing player
  136.                 if (aFacingPlayer && !bFacingPlayer) return -1;
  137.                 else if (!aFacingPlayer && bFacingPlayer) return 1;
  138.                 // else prefer ball that will next reach player
  139.                 const aDiff = Math.abs((a.x - player.x) / a.vx);
  140.                 const bDiff = Math.abs((b.x - player.x) / b.vx);
  141.                 return aDiff - bDiff;
  142.             })[0];
  143.     }
  144.     function ballFacingPlayer(player: Sprite, ball: Sprite) {
  145.         return (ball.vx < 0 && player.x < 80) || (ball.vx > 0 && player.x > 80);
  146.     }
  147.     function intersectBall(player: Sprite, target: Sprite) {
  148.         const projectedDY = (target.x - player.x) * target.vy / target.vx;
  149.         let intersectionPoint = target.y - projectedDY;
  150.         // quick 'estimation' for vertical bounces
  151.         if (intersectionPoint < 0) {
  152.             intersectionPoint = Math.abs(intersectionPoint % screen.height)
  153.         } else if (intersectionPoint > screen.height) {
  154.             intersectionPoint -= intersectionPoint % screen.height;
  155.         }
  156.         // move toward estimated intersection point if not in range
  157.         if (intersectionPoint > player.y + (player.height >> 2)) {
  158.             player.vy = PADDLE_SPEED;
  159.         } else if (intersectionPoint < player.y - (player.height >> 2)) {
  160.             player.vy = -PADDLE_SPEED;
  161.         } else {
  162.             player.vy = 0;
  163.         }
  164.     }
  165. });
复制代码


回复

使用道具 举报

驴友花雕  中级技神
 楼主|

发表于 3 小时前

【花雕动手做】基于 Kitronik 游戏机开发板之乒乓球游戏

这是一个功能丰富的双人乒乓球游戏,支持玩家对战和AI自动对战,包含多种高级特性如多球模式、智能AI追踪等。
代码结构分析

1. 常量定义和初始化
javascript
  1. const BALL_IMAGE = img`...`;  // 球的像素图像
  2. const PADDLE_SPEED = 150;     // 球拍移动速度
  3. const PADDING_FROM_WALL = 3;  // 球拍离墙的距离
  4. const TIMEOUT = 5000;         // AI接管超时时间(5秒)
复制代码


2. 玩家交互检测系统
javascript
  1. let playerOneLastMove = -TIMEOUT;
  2. let playerTwoLastMove = -TIMEOUT;
  3. // 设置按键重复延迟
  4. controller.setRepeatDefault(0, 1000);
  5. // 监听玩家操作时间戳
  6. controller.up.onEvent(ControllerButtonEvent.Repeated, () => playerOneLastMove = game.runtime());
  7. controller.down.onEvent(ControllerButtonEvent.Repeated, () => playerOneLastMove = game.runtime());
复制代码

智能特性:如果玩家5秒内没有操作,AI会自动接管控制。

3. 游戏对象创建
创建玩家球拍
javascript
  1. function createPlayer(player: info.PlayerInfo) {
  2.     const output = sprites.create(image.create(3, 18), SpriteKind.Player);
  3.     output.image.fill(player.bg);  // 使用玩家主题色
  4.     output.setStayInScreen(true);  // 限制在屏幕内
  5.     return output;
  6. }
复制代码

创建乒乓球
javascript
  1. function createBall() {
  2.     let ball = sprites.create(BALL_IMAGE.clone(), SpriteKind.Enemy);
  3.     ball.vy = randint(-20, 20);    // 随机垂直速度
  4.     ball.vx = 60 * (Math.percentChance(50) ? 1 : -1);  // 随机水平方向
  5. }
复制代码


4. 核心游戏逻辑
球的状态更新
javascript
  1. game.onUpdate(function () {
  2.     sprites.allOfKind(SpriteKind.Enemy).forEach(b => {
  3.         // 检测得分
  4.         const scoreRight = b.x < 0;
  5.         const scoreLeft = b.x >= screen.width;
  6.         
  7.         if (scoreRight) info.player2.changeScoreBy(1);
  8.         else if (scoreLeft) info.player1.changeScoreBy(1);
  9.         
  10.         // 上下边界反弹
  11.         if (b.top < 0) b.vy = Math.abs(b.vy);
  12.         else if (b.bottom > screen.height) b.vy = -Math.abs(b.vy);
  13.         
  14.         // 得分后重新生成球
  15.         if (scoreLeft || scoreRight) {
  16.             b.destroy(effects.disintegrate, 500);
  17.             control.runInParallel(() => {
  18.                 pause(250);
  19.                 createBall();
  20.             });
  21.         }
  22.     });
  23. });
复制代码

碰撞检测与物理响应
javascript
  1. sprites.onOverlap(SpriteKind.Player, SpriteKind.Enemy, (sprite, otherSprite) => {
  2.     const fromCenter = otherSprite.y - sprite.y;  // 计算击中点偏移
  3.    
  4.     // 物理反弹效果
  5.     otherSprite.vx = otherSprite.vx * -1.05;      // 反向并加速5%
  6.     otherSprite.vy += (sprite.vy >> 1) + (fromCenter * 3);  // 加入旋转效果
  7.    
  8.     // 视觉效果
  9.     otherSprite.startEffect(effects.ashes, 150);
  10.     sprite.startEffect(effects.ashes, 100);
  11.    
  12.     // 球的颜色变化(击中时染色)
  13.     otherSprite.image.setPixel(
  14.         randint(1, otherSprite.image.width - 2),
  15.         randint(1, otherSprite.image.height - 2),
  16.         sprite.image.getPixel(0, 0)  // 使用球拍颜色
  17.     );
  18.    
  19.     pingMessage = !pingMessage;  // 切换"ping"/"pong"显示
  20.     pause(500);  // 防重复触发
  21. });
复制代码


5. 高级AI追踪系统
这是游戏最复杂和智能的部分:

javascript
  1. function trackBall(player: Sprite) {
  2.     const next = nextBall(player);
  3.     if (!next) return;
  4.    
  5.     if (ballFacingPlayer(player, next)) {
  6.         intersectBall(player, next);  // 追踪球的预计落点
  7.     } else {
  8.         player.vy = 0;  // 球朝反方向,放松等待
  9.     }
  10. }
复制代码

智能球选择算法
javascript
  1. function nextBall(player: Sprite) {
  2.     return sprites.allOfKind(SpriteKind.Enemy).sort((a, b) => {
  3.         const aFacingPlayer = ballFacingPlayer(player, a);
  4.         const bFacingPlayer = ballFacingPlayer(player, b);
  5.         
  6.         // 优先选择面向玩家的球
  7.         if (aFacingPlayer && !bFacingPlayer) return -1;
  8.         else if (!aFacingPlayer && bFacingPlayer) return 1;
  9.         
  10.         // 其次选择最先到达的球
  11.         const aDiff = Math.abs((a.x - player.x) / a.vx);
  12.         const bDiff = Math.abs((b.x - player.x) / b.vx);
  13.         return aDiff - bDiff;
  14.     })[0];
  15. }
复制代码

物理轨迹预测算法
javascript
  1. function intersectBall(player: Sprite, target: Sprite) {
  2.     // 计算球的预计落点:使用相似三角形原理
  3.     const projectedDY = (target.x - player.x) * target.vy / target.vx;
  4.     let intersectionPoint = target.y - projectedDY;
  5.    
  6.     // 处理边界反弹的估算
  7.     if (intersectionPoint < 0) {
  8.         intersectionPoint = Math.abs(intersectionPoint % screen.height)
  9.     } else if (intersectionPoint > screen.height) {
  10.         intersectionPoint -= intersectionPoint % screen.height;
  11.     }
  12.    
  13.     // 移动到预计落点
  14.     if (intersectionPoint > player.y + (player.height >> 2)) {
  15.         player.vy = PADDLE_SPEED;  // 向下移动
  16.     } else if (intersectionPoint < player.y - (player.height >> 2)) {
  17.         player.vy = -PADDLE_SPEED; // 向上移动
  18.     } else {
  19.         player.vy = 0;  // 已在正确位置
  20.     }
  21. }
复制代码


6. 特殊功能系统
多球模式控制
javascript
  1. // 添加球(消耗2分)
  2. controller.A.onEvent(ControllerButtonEvent.Pressed, () => addBall(info.player1));
  3. function addBall(player: info.PlayerInfo) {
  4.     player.changeScoreBy(-2);
  5.     createBall();
  6. }
  7. // 移除球(消耗2分,至少保留1个)
  8. controller.B.onEvent(ControllerButtonEvent.Pressed, () => removeBall(info.player1));
  9. function removeBall(player: info.PlayerInfo) {
  10.     const balls = sprites.allOfKind(SpriteKind.Enemy);
  11.     if (balls.length > 1) {
  12.         Math.pickRandom(balls).destroy();
  13.         player.changeScoreBy(-2);
  14.     }
  15. }
  16. 视觉反馈系统
  17. javascript
  18. game.onShade(function () {
  19.     if (pingMessage) {
  20.         screen.printCenter("ping", 5);
  21.     } else {
  22.         screen.printCenter("pong", 5);
  23.     }
  24. })
复制代码


回复

使用道具 举报

驴友花雕  中级技神
 楼主|

发表于 3 小时前

【花雕动手做】基于 Kitronik 游戏机开发板之乒乓球游戏

通过模拟器,调试与模拟运行

【花雕动手做】基于 Kitronik 游戏机开发板之乒乓球游戏图1

实验场景记录

【花雕动手做】基于 Kitronik 游戏机开发板之乒乓球游戏图2

【花雕动手做】基于 Kitronik 游戏机开发板之乒乓球游戏图3

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4 备案 沪公网安备31011502402448

© 2013-2025 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail