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

[项目] 【花雕动手做】基于Kitronik可编程开发板之贪吃毛毛虫

[复制链接]
Kitronik ARCADE 是一款由英国教育科技公司 Kitronik 精心打造的可编程游戏机开发板,专为编程教学与创客实践而设计。该设备原生支持微软的 MakeCode Arcade 平台,用户可通过图形化或 JavaScript 编程方式,轻松创建、下载并运行复古风格的街机游戏。

它集成了彩色 LCD 显示屏、方向控制键、功能按键、蜂鸣器和震动马达等交互组件,提供完整的游戏输入输出体验。无论是初学者进行编程启蒙,还是创客群体开发交互式作品,Kitronik ARCADE 都能作为理想的硬件载体,助力创意实现。

凭借其开源友好、易于上手、兼容性强等特点,该开发板广泛应用于中小学编程课程、创客工作坊、游戏开发教学以及个人项目原型设计,深受教育者与技术爱好者的喜爱。

【花雕动手做】基于Kitronik可编程开发板之贪吃毛毛虫图2

【花雕动手做】基于Kitronik可编程开发板之贪吃毛毛虫图1

驴友花雕  中级技神
 楼主|

发表于 2 小时前

【花雕动手做】基于Kitronik可编程开发板之贪吃毛毛虫

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

JavaScript 实验代码

  1. const NEXT_SECTION_KEY = "__child_node";
  2. namespace SpriteKind {
  3.     export const Tail = SpriteKind.create();
  4. }
  5. enum Direction {
  6.     Up,
  7.     Down,
  8.     Left,
  9.     Right
  10. }
  11. const size = 8;
  12. const caterpillarHead = sprites.create(img`
  13.     . . 3 3 3 3 . .
  14.     . 3 2 2 2 2 3 .
  15.     3 2 f 2 2 f 2 3
  16.     3 2 f 2 2 f 2 3
  17.     3 2 2 2 2 2 2 3
  18.     3 2 2 2 2 2 2 3
  19.     . 3 2 2 2 2 3 .
  20.     . . 3 3 3 3 . .
  21. `, SpriteKind.Player);
  22. caterpillarHead.left = 4 * size;
  23. caterpillarHead.top = 12 * size;
  24. caterpillarHead.data = {};
  25. let currentLeaf: Sprite;
  26. tiles.setTilemap(tilemap`level`);
  27. const leafImage = img`
  28.     . . . . . f f 7
  29.     . . . f f f 6 f
  30.     . . f 6 7 f f f
  31.     . f 6 7 f 7 7 f
  32.     f f 7 f 7 7 7 f
  33.     f 6 f 7 7 7 f .
  34.     f 6 7 7 f f . .
  35.     f f f f f . . .
  36. `;
  37. const shinyLeafImage = img`
  38.     . . 1 . . f f 7
  39.     . 1 . f f f 6 f
  40.     1 . f 6 7 f f f
  41.     . f 6 7 f 7 7 f
  42.     f f 7 f 7 7 7 f
  43.     f 6 f 7 7 7 f .
  44.     f 6 7 7 f f . 1
  45.     f f f f f . 1 .
  46. `;
  47. placeFruit();
  48. info.setScore(0);
  49. let direction = Direction.Up;
  50. let addSection = true;
  51. let enqueued = false;
  52. let lastIteration = 0;
  53. let timeout = 500;
  54. forever(function () {
  55.     if (caterpillarHead.left < 0 || caterpillarHead.right > screen.width
  56.         || caterpillarHead.top < 0 || caterpillarHead.bottom > screen.height) {
  57.         game.over(false);
  58.     }
  59.     if (!enqueued && game.runtime() - lastIteration < timeout) {
  60.         return;
  61.     }
  62.     if (addSection) {
  63.         addToBody();
  64.     } else {
  65.         move(caterpillarHead);
  66.     }
  67.     switch (direction) {
  68.         case Direction.Up:
  69.             caterpillarHead.y -= size;
  70.             break;
  71.         case Direction.Down:
  72.             caterpillarHead.y += size;
  73.             break;
  74.         case Direction.Left:
  75.             caterpillarHead.x -= size;
  76.             break;
  77.         case Direction.Right:
  78.             caterpillarHead.x += size;
  79.             break;
  80.     }
  81.     enqueued = false;
  82.     lastIteration = game.runtime();
  83.     function addToBody() {
  84.         const newSection = sprites.create(img`
  85.             . . f f f f . .
  86.             . f 1 1 1 1 f .
  87.             f 1 1 1 1 1 1 f
  88.             f 1 1 1 1 1 1 f
  89.             f 1 1 1 1 1 1 f
  90.             f 1 1 1 1 1 1 f
  91.             . f 1 1 1 1 f .
  92.             . . f f f f . .
  93.         `, SpriteKind.Tail);
  94.         newSection.data = {};
  95.         let newColor: number;
  96.         do {
  97.             newColor = randint(0x1, 0xE);
  98.         } while (newColor === 0x6);
  99.         newSection.image.replace(0x1, newColor);
  100.         do {
  101.             newColor = randint(0x1, 0xE);
  102.         } while (newColor === 0x6);
  103.         newSection.image.replace(0xF, newColor);
  104.         newSection.x = caterpillarHead.x;
  105.         newSection.y = caterpillarHead.y;
  106.         newSection.data[NEXT_SECTION_KEY] = caterpillarHead.data[NEXT_SECTION_KEY];
  107.         caterpillarHead.data[NEXT_SECTION_KEY] = newSection;
  108.         addSection = false;
  109.     }
  110.     function move(piece: Sprite) {
  111.         const next = piece.data[NEXT_SECTION_KEY];
  112.         if (next) {
  113.             move(next);
  114.             next.x = piece.x;
  115.             next.y = piece.y;
  116.         }
  117.     }
  118. });
  119. sprites.onOverlap(SpriteKind.Player, SpriteKind.Food, function (sprite: Sprite, otherSprite: Sprite) {
  120.     info.changeScoreBy(1);
  121.     otherSprite.destroy(effects.disintegrate);
  122.     music.baDing.play();
  123.     timeout = Math.max(150, timeout - 50);
  124.     addSection = true;
  125.     placeFruit();
  126. });
  127. sprites.onOverlap(SpriteKind.Player, SpriteKind.Tail, function (sprite: Sprite, otherSprite: Sprite) {
  128.     game.over(false);
  129. });
  130. controller.up.onEvent(ControllerButtonEvent.Pressed, function () {
  131.     setDirection(Direction.Up, Direction.Down, img`
  132.         . . 3 3 3 3 . .
  133.         . 3 2 2 2 2 3 .
  134.         3 2 f 2 2 f 2 3
  135.         3 2 f 2 2 f 2 3
  136.         3 2 2 2 2 2 2 3
  137.         3 2 2 2 2 2 2 3
  138.         . 3 2 2 2 2 3 .
  139.         . . 3 3 3 3 . .
  140.     `);
  141. });
  142. controller.down.onEvent(ControllerButtonEvent.Pressed, function () {
  143.     setDirection(Direction.Down, Direction.Up, img`
  144.         . . 3 3 3 3 . .
  145.         . 3 2 2 2 2 3 .
  146.         3 2 2 2 2 2 2 3
  147.         3 2 2 2 2 2 2 3
  148.         3 2 f 2 2 f 2 3
  149.         3 2 f 2 2 f 2 3
  150.         . 3 2 2 2 2 3 .
  151.         . . 3 3 3 3 . .
  152.     `);
  153. });
  154. controller.left.onEvent(ControllerButtonEvent.Pressed, function () {
  155.     setDirection(Direction.Left, Direction.Right, img`
  156.         . . 3 3 3 3 . .
  157.         . 3 2 2 2 2 3 .
  158.         3 2 f f 2 2 2 3
  159.         3 2 2 2 2 2 2 3
  160.         3 2 2 2 2 2 2 3
  161.         3 2 f f 2 2 2 3
  162.         . 3 2 2 2 2 3 .
  163.         . . 3 3 3 3 . .
  164.     `);
  165. });
  166. controller.right.onEvent(ControllerButtonEvent.Pressed, function () {
  167.     setDirection(Direction.Right, Direction.Left, img`
  168.         . . 3 3 3 3 . .
  169.         . 3 2 2 2 2 3 .
  170.         3 2 2 2 f f 2 3
  171.         3 2 2 2 2 2 2 3
  172.         3 2 2 2 2 2 2 3
  173.         3 2 2 2 f f 2 3
  174.         . 3 2 2 2 2 3 .
  175.         . . 3 3 3 3 . .
  176.     `);
  177. });
  178. function setDirection(targetDir: Direction, oppositeDir: Direction, im: Image) {
  179.     if (!enqueued && direction !== targetDir && direction !== oppositeDir) {
  180.         caterpillarHead.setImage(im);
  181.         direction = targetDir;
  182.         enqueued = true;
  183.     }
  184. }
  185. function placeFruit() {
  186.     currentLeaf = sprites.create(leafImage, SpriteKind.Food);
  187.     do {
  188.         currentLeaf.left = randint(0, 19) * size;
  189.         currentLeaf.top = randint(0, 14) * size;
  190.     } while (
  191.         (currentLeaf.top === 0 && currentLeaf.right === screen.width)
  192.         || sprites
  193.             .allOfKind(SpriteKind.Tail)
  194.             .some(s => s.overlapsWith(currentLeaf))
  195.     );
  196. }
  197. game.onUpdateInterval(500, function () {
  198.     if (currentLeaf.image === leafImage) {
  199.         currentLeaf.setImage(shinyLeafImage);
  200.     } else {
  201.         currentLeaf.setImage(leafImage);
  202.     }
  203. });
复制代码


回复

使用道具 举报

驴友花雕  中级技神
 楼主|

发表于 2 小时前

【花雕动手做】基于Kitronik可编程开发板之贪吃毛毛虫

本帖最后由 驴友花雕 于 2025-9-18 13:42 编辑

ARCADE MakeCode 之贪吃毛毛虫游戏代码解读
这是一个经典的贪吃蛇游戏变体,玩家控制一条毛毛虫吃叶子并增长身体。

代码结构分析

1. 常量和枚举定义
javascript
  1. const NEXT_SECTION_KEY = "__child_node"; // 用于存储下一节身体的键名
  2. namespace SpriteKind {
  3.     export const Tail = SpriteKind.create(); // 创建尾部精灵类型
  4. }
  5. enum Direction {
  6.     Up,    // 上
  7.     Down,  // 下
  8.     Left,  // 左
  9.     Right  // 右
  10. }
  11. const size = 8; // 网格大小(像素)
复制代码


2. 毛毛虫头部初始化
javascript
  1. const caterpillarHead = sprites.create(img`
  2.     . . 3 3 3 3 . .
  3.     . 3 2 2 2 2 3 .
  4.     3 2 f 2 2 f 2 3
  5.     3 2 f 2 2 f 2 3
  6.     3 2 2 2 2 2 2 3
  7.     3 2 2 2 2 2 2 3
  8.     . 3 2 2 2 2 3 .
  9.     . . 3 3 3 3 . .
  10. `, SpriteKind.Player);
  11. caterpillarHead.left = 4 * size;  // 初始位置X
  12. caterpillarHead.top = 12 * size;  // 初始位置Y
  13. caterpillarHead.data = {};        // 存储自定义数据
复制代码


3. 游戏地图和叶子设置
javascript
  1. tiles.setTilemap(tilemap`level`); // 设置游戏地图
  2. // 普通叶子图像
  3. const leafImage = img`
  4.     . . . . . f f 7
  5.     . . . f f f 6 f
  6.     . . f 6 7 f f f
  7.     . f 6 7 f 7 7 f
  8.     f f 7 f 7 7 7 f
  9.     f 6 f 7 7 7 f .
  10.     f 6 7 7 f f . .
  11.     f f f f f . . .
  12. `;
  13. // 闪光叶子图像(用于动画效果)
  14. const shinyLeafImage = img`
  15.     . . 1 . . f f 7
  16.     . 1 . f f f 6 f
  17.     1 . f 6 7 f f f
  18.     . f 6 7 f 7 7 f
  19.     f f 7 f 7 7 7 f
  20.     f 6 f 7 7 7 f .
  21.     f 6 7 7 f f . 1
  22.     f f f f f . 1 .
  23. `;
  24. placeFruit(); // 放置第一个叶子
  25. info.setScore(0); // 初始化分数
复制代码


4. 游戏状态变量
javascript
  1. let direction = Direction.Up; // 当前移动方向
  2. let addSection = true;        // 是否需要添加身体节
  3. let enqueued = false;         // 方向改变是否已排队
  4. let lastIteration = 0;        // 上次迭代时间
  5. let timeout = 500;            // 移动间隔时间(毫秒)
复制代码


5. 主游戏循环
javascript
  1. forever(function () {
  2.     // 边界检查 - 如果碰到边界游戏结束
  3.     if (caterpillarHead.left < 0 || caterpillarHead.right > screen.width
  4.         || caterpillarHead.top < 0 || caterpillarHead.bottom > screen.height) {
  5.         game.over(false);
  6.     }
  7.     // 检查是否到了移动时间
  8.     if (!enqueued && game.runtime() - lastIteration < timeout) {
  9.         return;
  10.     }
  11.     // 添加身体节或移动身体
  12.     if (addSection) {
  13.         addToBody();
  14.     } else {
  15.         move(caterpillarHead);
  16.     }
  17.     // 根据方向移动头部
  18.     switch (direction) {
  19.         case Direction.Up:
  20.             caterpillarHead.y -= size;
  21.             break;
  22.         case Direction.Down:
  23.             caterpillarHead.y += size;
  24.             break;
  25.         case Direction.Left:
  26.             caterpillarHead.x -= size;
  27.             break;
  28.         case Direction.Right:
  29.             caterpillarHead.x += size;
  30.             break;
  31.     }
  32.     // 重置状态变量
  33.     enqueued = false;
  34.     lastIteration = game.runtime();
  35. });
复制代码


6. 添加身体节函数
javascript
  1. function addToBody() {
  2.     const newSection = sprites.create(img`
  3.         . . f f f f . .
  4.         . f 1 1 1 1 f .
  5.         f 1 1 1 1 1 1 f
  6.         f 1 1 1 1 1 1 f
  7.         f 1 1 1 1 1 1 f
  8.         f 1 1 1 1 1 1 f
  9.         . f 1 1 1 1 f .
  10.         . . f f f f . .
  11.     `, SpriteKind.Tail);
  12.    
  13.     newSection.data = {};
  14.     // 随机生成身体节颜色(避免使用特定颜色)
  15.     let newColor: number;
  16.     do {
  17.         newColor = randint(0x1, 0xE);
  18.     } while (newColor === 0x6);
  19.     newSection.image.replace(0x1, newColor);
  20.     do {
  21.         newColor = randint(0x1, 0xE);
  22.     } while (newColor === 0x6);
  23.     newSection.image.replace(0xF, newColor);
  24.     // 设置身体节位置
  25.     newSection.x = caterpillarHead.x;
  26.     newSection.y = caterpillarHead.y;
  27.     // 链接身体节(链表结构)
  28.     newSection.data[NEXT_SECTION_KEY] = caterpillarHead.data[NEXT_SECTION_KEY];
  29.     caterpillarHead.data[NEXT_SECTION_KEY] = newSection;
  30.    
  31.     addSection = false; // 重置添加标志
  32. }
复制代码


7. 移动身体函数
javascript
  1. function move(piece: Sprite) {
  2.     const next = piece.data[NEXT_SECTION_KEY];
  3.     if (next) {
  4.         move(next); // 递归移动下一节
  5.         next.x = piece.x; // 设置位置为前一节的位置
  6.         next.y = piece.y;
  7.     }
  8. }
复制代码

8. 碰撞检测事件
javascript
  1. // 吃到叶子事件
  2. sprites.onOverlap(SpriteKind.Player, SpriteKind.Food, function (sprite: Sprite, otherSprite: Sprite) {
  3.     info.changeScoreBy(1); // 增加分数
  4.     otherSprite.destroy(effects.disintegrate); // 销毁叶子
  5.     music.baDing.play(); // 播放音效
  6.     timeout = Math.max(150, timeout - 50); // 加快游戏速度
  7.     addSection = true; // 标记需要添加身体节
  8.     placeFruit(); // 放置新叶子
  9. });
  10. // 碰到自己身体事件
  11. sprites.onOverlap(SpriteKind.Player, SpriteKind.Tail, function (sprite: Sprite, otherSprite: Sprite) {
  12.     game.over(false); // 游戏结束
  13. });
复制代码


9. 方向控制函数
javascript
  1. // 上方向键
  2. controller.up.onEvent(ControllerButtonEvent.Pressed, function () {
  3.     setDirection(Direction.Up, Direction.Down, img`
  4.         . . 3 3 3 3 . .
  5.         . 3 2 2 2 2 3 .
  6.         3 2 f 2 2 f 2 3
  7.         3 2 f 2 2 f 2 3
  8.         3 2 2 2 2 2 2 3
  9.         3 2 2 2 2 2 2 3
  10.         . 3 2 2 2 2 3 .
  11.         . . 3 3 3 3 . .
  12.     `);
  13. });
  14. // 下方向键
  15. controller.down.onEvent(ControllerButtonEvent.Pressed, function () {
  16.     setDirection(Direction.Down, Direction.Up, img`
  17.         . . 3 3 3 3 . .
  18.         . 3 2 2 2 2 3 .
  19.         3 2 2 2 2 2 2 3
  20.         3 2 2 2 2 2 2 3
  21.         3 2 f 2 2 f 2 3
  22.         3 2 f 2 2 f 2 3
  23.         . 3 2 2 2 2 3 .
  24.         . . 3 3 3 3 . .
  25.     `);
  26. });
  27. // 左方向键
  28. controller.left.onEvent(ControllerButtonEvent.Pressed, function () {
  29.     setDirection(Direction.Left, Direction.Right, img`
  30.         . . 3 3 3 3 . .
  31.         . 3 2 2 2 2 3 .
  32.         3 2 f f 2 2 2 3
  33.         3 2 2 2 2 2 2 3
  34.         3 2 2 2 2 2 2 3
  35.         3 2 f f 2 2 2 3
  36.         . 3 2 2 2 2 3 .
  37.         . . 3 3 3 3 . .
  38.     `);
  39. });
  40. // 右方向键
  41. controller.right.onEvent(ControllerButtonEvent.Pressed, function () {
  42.     setDirection(Direction.Right, Direction.Left, img`
  43.         . . 3 3 3 3 . .
  44.         . 3 2 2 2 2 3 .
  45.         3 2 2 2 f f 2 3
  46.         3 2 2 2 2 2 2 3
  47.         3 2 2 2 2 2 2 3
  48.         3 2 2 2 f f 2 3
  49.         . 3 2 2 2 2 3 .
  50.         . . 3 3 3 3 . .
  51.     `);
  52. });
  53. // 设置方向函数
  54. function setDirection(targetDir: Direction, oppositeDir: Direction, im: Image) {
  55.     if (!enqueued && direction !== targetDir && direction !== oppositeDir) {
  56.         caterpillarHead.setImage(im); // 更新头部图像
  57.         direction = targetDir; // 设置新方向
  58.         enqueued = true; // 标记方向已改变
  59.     }
  60. }
复制代码


10. 放置叶子函数

javascript
  1. function placeFruit() {
  2.     currentLeaf = sprites.create(leafImage, SpriteKind.Food);
  3.     do {
  4.         // 随机位置(网格对齐)
  5.         currentLeaf.left = randint(0, 19) * size;
  6.         currentLeaf.top = randint(0, 14) * size;
  7.     } while (
  8.         // 确保不放在角落或身体上
  9.         (currentLeaf.top === 0 && currentLeaf.right === screen.width)
  10.         || sprites
  11.             .allOfKind(SpriteKind.Tail)
  12.             .some(s => s.overlapsWith(currentLeaf))
  13.     );
  14. }
复制代码


11. 叶子闪烁动画
javascript
  1. game.onUpdateInterval(500, function () {
  2.     // 切换叶子图像创建闪烁效果
  3.     if (currentLeaf.image === leafImage) {
  4.         currentLeaf.setImage(shinyLeafImage);
  5.     } else {
  6.         currentLeaf.setImage(leafImage);
  7.     }
  8. });
复制代码


游戏机制解析
移动机制:毛毛虫以固定时间间隔移动,方向键控制移动方向
身体增长:每吃一个叶子,身体增加一节

碰撞检测:
碰到边界游戏结束
碰到自己身体游戏结束
碰到叶子得分并增长
难度递增:随着分数增加,移动速度加快
视觉效果:叶子有闪烁动画,身体节有随机颜色

这个游戏使用了链表数据结构来管理毛毛虫的身体节,每个身体节都存储指向下一节的引用,实现了高效的移动和碰撞检测。



回复

使用道具 举报

驴友花雕  中级技神
 楼主|

发表于 2 小时前

【花雕动手做】基于Kitronik可编程开发板之贪吃毛毛虫

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

【花雕动手做】基于Kitronik可编程开发板之贪吃毛毛虫图1

实验场景记录

【花雕动手做】基于Kitronik可编程开发板之贪吃毛毛虫图3

【花雕动手做】基于Kitronik可编程开发板之贪吃毛毛虫图2

【花雕动手做】基于Kitronik可编程开发板之贪吃毛毛虫图4

回复

使用道具 举报

驴友花雕  中级技神
 楼主|

发表于 2 小时前

【花雕动手做】基于Kitronik可编程开发板之贪吃毛毛虫

【花雕动手做】基于Kitronik可编程开发板之贪吃毛毛虫图1
回复

使用道具 举报

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

本版积分规则

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

硬件清单

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

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

mail