小赖子的英国生活和资讯

Microbit 游戏编程: 通过精灵对象来编写吃苹果游戏

阅读 桌面完整版

上周, 我们讲了Microbit最重要的输出装置也就是LED显示屏, 只有25个像素点, 也就是Microbit的显示器. 我们还介绍了让一个像素点从第一行最左边的位置一直往右跑, 跑完第一行就换下一行, 当跑完25个像素点的时候又回到了第一个位置.

我们还可以让这个像素点绕着LED显示屏跑一周. 我们需要2组变量, 分别是像素点的当前位置 (x, y) 和 方向偏移量 (xoffset, yoffset). 比如当方向为右的时候, X偏移量为1, 而Y偏移量为0.

1
2
3
let x = 0, y = 0;
// 初始方向是右
let xoffset = 1, yoffset = 1;
let x = 0, y = 0;
// 初始方向是右
let xoffset = 1, yoffset = 1;

然后, 类似地使像素朝一个方向奔跑, 我们可以绘制和取消绘制像素, 再等待一些时间间隔.

1
2
3
4
5
6
7
basic.forever(function() {
   led.plot(x, y);
   basic.pause(100);
   led.unplot(x, y);
   x += xoffset;
   y += yoffset;
});
basic.forever(function() {
   led.plot(x, y);
   basic.pause(100);
   led.unplot(x, y);
   x += xoffset;
   y += yoffset;
});

上面的Javascript代码将使像素点从(0, 0)-左上角向右移动, 当其位置超出了LED屏幕的最右边框, 它很快就会消失. 我们可以加个判断, 当它到达了右上角, 我们就改变方向偏移量-让它接下来往下跑.

1
2
3
4
5
6
7
8
9
10
11
basic.forever(function() {
   led.plot(x, y);
   basic.pause(100);
   led.unplot(x, y);
   x += xoffset;
   y += yoffset;
   if (x == 4 && y == 0) { // 如果到达了右上角
      xoffset = 0;
      yoffset = 1;   // 往下跑
   }
});
basic.forever(function() {
   led.plot(x, y);
   basic.pause(100);
   led.unplot(x, y);
   x += xoffset;
   y += yoffset;
   if (x == 4 && y == 0) { // 如果到达了右上角
      xoffset = 0;
      yoffset = 1;   // 往下跑
   }
});

同样, 我们必须处理其它三个角落情况, 以使像素保持沿边缘奔跑而不会出现问题.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
basic.forever(function() {
   led.plot(x, y);
   basic.pause(100);
   led.unplot(x, y);
   x += xoffset;
   y += yoffset;
   if (x == 4 && y == 0) { // 右上角
      xoffset = 0;
      yoffset = 1;   // 方向向下
   } else if (x == 4 && y == 4) { // 右下角
      xoffset = -1;
      yoffset = 0;   // 方向向左
   } else if (x == 0 && y == 4) { // 左下角
      xoffset = 0;
      yoffset = -1;  // 上
   } else if (x == 0 && y == 0) { // 左上角
      xoffset = 1;
      yoffset = 0;   // 右
   }
});
basic.forever(function() {
   led.plot(x, y);
   basic.pause(100);
   led.unplot(x, y);
   x += xoffset;
   y += yoffset;
   if (x == 4 && y == 0) { // 右上角
      xoffset = 0;
      yoffset = 1;   // 方向向下
   } else if (x == 4 && y == 4) { // 右下角
      xoffset = -1;
      yoffset = 0;   // 方向向左
   } else if (x == 0 && y == 4) { // 左下角
      xoffset = 0;
      yoffset = -1;  // 上
   } else if (x == 0 && y == 0) { // 左上角
      xoffset = 1;
      yoffset = 0;   // 右
   }
});

代码和 Microbit 模拟器: https://makecode.microbit.org/_L4X75r3b4FUU

使用精灵对象

上面的代码可以正常工作, 但是有点冗长和复杂, 因为我们必须通过修改坐标和方向偏移来处理更改方向和移动像素. 我们可以使用精灵对象-这是一个面向对象的编程(OOP). 精灵可以被认为是一个像素点, 我们可以针对每个精灵对象调用几种方法, 例如 移动, 向左转, 向右转等. 还有一个 ifOnEdgeBounce() 方法可以让精灵碰到边后就向后转.

我们可以使用game.createSprite()方法来创建一个精灵. 参数 (x, y) 是精灵的初始位置.

1
2
3
4
5
6
7
8
9
let pixel = game.createSprite(0, 0);
 
basic.forever(function() {
   pixel.move(1);  // 让像素点往它的方向前进一个点. 
   if (pixel.isTouchingEdge()) { // 如果碰到了边
      pixel.turnRight(90);       // 就向右转90度. 
   }
   basic.pause(100);
});
let pixel = game.createSprite(0, 0);

basic.forever(function() {
   pixel.move(1);  // 让像素点往它的方向前进一个点. 
   if (pixel.isTouchingEdge()) { // 如果碰到了边
      pixel.turnRight(90);       // 就向右转90度. 
   }
   basic.pause(100);
});

代码和 Microbit 模拟器: https://makecode.microbit.org/_b6q2d8Cym63P
但是, 它不能按预期工作, 虽然有点酷.

该代码背后的原因是: 当像素位于边缘时, 它将向右转. 拐角是边缘, 但是边缘本质上不是拐角. 我们可以通过添加一个函数来检查像素是否在拐角处来修复代码.

1
2
3
4
5
6
7
8
function isAtCorner(pixel: game.ledSprite): boolean {
  const x = pixel.x();
  const y = pixel.y();
  return ((x == 0) && (y == 0)) ||
         ((x == 0) && (y == 4)) ||
         ((x == 4) && (y == 0)) ||
         ((x == 4) && (y == 4));
}
function isAtCorner(pixel: game.ledSprite): boolean {
  const x = pixel.x();
  const y = pixel.y();
  return ((x == 0) && (y == 0)) ||
         ((x == 0) && (y == 4)) ||
         ((x == 4) && (y == 0)) ||
         ((x == 4) && (y == 4));
}

整个代码和模拟器: https://makecode.microbit.org/_VD17z4exrWED

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let pixel = game.createSprite(0, 0);
 
function isAtCorner(pixel: game.LedSprite): boolean {
    const x = pixel.x();
    const y = pixel.y();
    return ((x == 0) && (y == 0)) ||
        ((x == 0) && (y == 4)) ||
        ((x == 4) && (y == 0)) ||
        ((x == 4) && (y == 4));
}
 
basic.forever(function () {
    pixel.move(1);
    if (pixel.isTouchingEdge() && isAtCorner(pixel)) {
        pixel.turnRight(90);
    }
    basic.pause(100);
});
let pixel = game.createSprite(0, 0);

function isAtCorner(pixel: game.LedSprite): boolean {
    const x = pixel.x();
    const y = pixel.y();
    return ((x == 0) && (y == 0)) ||
        ((x == 0) && (y == 4)) ||
        ((x == 4) && (y == 0)) ||
        ((x == 4) && (y == 4));
}

basic.forever(function () {
    pixel.move(1);
    if (pixel.isTouchingEdge() && isAtCorner(pixel)) {
        pixel.turnRight(90);
    }
    basic.pause(100);
});

我们使用 game.LedSprite 来指定 isAtCorner 函数的输入参数类型. 该函数的返回类型为布尔值-可以为TRUE或FALSE. 然后, 我们只能在拐角处和边处进行转弯. 请注意, 尽管我们将两个布尔表达式都放在此处以显示逻辑AND运算符(即&&)的用法, 但其实只要检查是否是角就足够了.

游戏的随机性

游戏需要随机性才好玩. 我们使用 Math.randomRange(from, to) 函数生成一个在范围为[from, to]的随机整数. 例如, 以下操作将使像素在LED屏幕中随机跳跃.

1
2
3
4
5
6
7
8
let pixel = game.createSprite(0, 0);
 
basic.forever(function () {
    const x = Math.randomRange(0, 4);
    const y = Math.randomRange(0, 4);
    pixel.goTo(x, y);
    basic.pause(100);
});
let pixel = game.createSprite(0, 0);

basic.forever(function () {
    const x = Math.randomRange(0, 4);
    const y = Math.randomRange(0, 4);
    pixel.goTo(x, y);
    basic.pause(100);
});

代码和 Microbit 模拟器: https://makecode.microbit.org/_W64WXLEDX6Xq

使用精灵对象来设计一个吃苹果游戏

我们可以通过到目前为止所学的技能来制作一款好玩的游戏. 我们可以生成一个随机掉落的苹果, 然后我们需要捕捉/吃掉它. (很有意思, 在上课的时候突发奇想就写了这么一个小游戏)

1
2
// 在第一行随机生成一个苹果对象
let apple = game.createSprite(Math.randomRange(0, 4), 0);
// 在第一行随机生成一个苹果对象
let apple = game.createSprite(Math.randomRange(0, 4), 0);

然后, 我们可以将盘子最初放在中间的最后一行中:

1
let pixel = game.createSprite(2, 4);
let pixel = game.createSprite(2, 4);

然后, 我们可以使用两个按钮(按钮A和按钮B)向左或向右移动(一次移动一个像素)来控制盘子(接苹果的). 代码应如下所示很简单清楚:

1
2
3
4
5
6
7
input.onButtonPressed(Button.A, function() {
    pixel.changeXBy(-1);
});
 
input.onButtonPressed(Button.B, function() {
    pixel.changeXBy(1);
});
input.onButtonPressed(Button.A, function() {
    pixel.changeXBy(-1);
});

input.onButtonPressed(Button.B, function() {
    pixel.changeXBy(1);
});

请注意, sprite.changeXBy(偏移量)方法用一个整数表示要更改的偏移量, 即位置将移动此偏移量. 同样, 我们有changeYBy()方法. 这两种方法将限制精灵在LED屏幕中的位置, 这意味着当像素的坐标X = 0时, 调用changeByX(-1)不会更改X坐标, 因为它已经在最左侧(第一列)并且限制在LED上 屏幕.

为了使游戏更加人性化, 我们可以通过允许平板像素倒退到另一侧(例如, 当像素位于最左边时, 您可以按A按钮转到最右边, 反之亦然. 这有时会缩短抓苹果的时间, 并且为用户提供了更多选择.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let px = 2;
let py = 4;
 
input.onButtonPressed(Button.A, function () {
    px--;
    if (px < 0) px = 4;
    pixel.setX(px);
})
 
input.onButtonPressed(Button.B, function () {
    px++;
    if (px > 4) px = 0;
    pixel.setX(px);
})
let px = 2;
let py = 4;

input.onButtonPressed(Button.A, function () {
    px--;
    if (px < 0) px = 4;
    pixel.setX(px);
})

input.onButtonPressed(Button.B, function () {
    px++;
    if (px > 4) px = 0;
    pixel.setX(px);
})

我们还可以定义按钮A + B(同时按下)以重置游戏.

1
2
3
4
5
6
7
input.onButtonPressed(Button.AB, function () {
    if (apple.y() == 4) { // 避免误操作
        game.setScore(0);
        score = 0;
        apple.goTo(Math.randomRange(0, 4), -1);
    }
})
input.onButtonPressed(Button.AB, function () {
    if (apple.y() == 4) { // 避免误操作
        game.setScore(0);
        score = 0;
        apple.goTo(Math.randomRange(0, 4), -1);
    }
})

在这里, 我们只在苹果落到地面上时重置游戏, 因此您在玩游戏时不会意外重置游戏. 然后我们将分数重置为零. 苹果还需要移动到另一个初始随机位置.

game.setScore(分数)设置内部游戏得分, 当您调用game.gameOver() 时会显示游戏最后得分. 该方法类似于以下内容:

1
2
3
function gameOver() {
  basic.showString("Game Over, Score: " + score);
}
function gameOver() {
  basic.showString("Game Over, Score: " + score);
}

我们定义了一个全局游戏得分变量-这不是强制性的, 因为我们可以使用game.addScore(score)game.setScore(score)来更改/更改得分.

为了使游戏更具挑战性, 每当分数增加时(当您抓到一个苹果时), 我们就缩短延迟时间, 让苹果掉得更快一些.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
basic.forever(function () {
    apple.changeYBy(1);       // 苹果下落一格
    if (pixel.isTouching(apple)) {  // 接到了
        score++;
        apple.goTo(Math.randomRange(0, 4), -1); // 生成另一个随机苹果位置
    } else if (4 <= apple.y()) {   // 当苹果掉到地上了
        basic.showNumber(score);
        game.setScore(score);
        game.gameOver();
    }
    let delay = 500 - 10 * score;   // 下落得越来越快
    delay = Math.max(50, delay);    // 最快的速度有限制
    basic.pause(delay);
})
basic.forever(function () {
    apple.changeYBy(1);       // 苹果下落一格
    if (pixel.isTouching(apple)) {  // 接到了
        score++;
        apple.goTo(Math.randomRange(0, 4), -1); // 生成另一个随机苹果位置
    } else if (4 <= apple.y()) {   // 当苹果掉到地上了
        basic.showNumber(score);
        game.setScore(score);
        game.gameOver();
    }
    let delay = 500 - 10 * score;   // 下落得越来越快
    delay = Math.max(50, delay);    // 最快的速度有限制
    basic.pause(delay);
})

这是主要的游戏逻辑, 在主循环中, 苹果将首先下降1个像素. 然后我们使用sprite.isTouching(anotherSprite)方法(来判断两个精灵是否发生了碰撞检测)借此来检查是否捕获了它. 然后, 我们还检查应用的Y坐标是否大于或等于4-落在了地面上. 然后我们可以延迟一些时间间隔(延迟越来越短, 苹果掉落的速度越来越快).

通过使用Math.max()函数将最大下降速度限制为50, 该函数与以下相同:

1
2
3
if (delay < 50) {
  delay = 50;
}
if (delay < 50) {
  delay = 50;
}

这个吃苹果游戏代码和Microbit 模拟器: https://makecode.microbit.org/_DV93uT7i0WuK

游戏的可视化显示了该游戏的几大部件:

microbit-eat-apple-game

剑桥 Chesterton Community College – Coding Club

来几张我上课的照片和视频:

视频:

经过一些修改和参数调整, Microbit吃苹果游戏的可玩性大大提高了

孩子们在玩这个游戏很有兴趣, 老大说比Youshi 好玩

下一周, 我们将让 Microbit 拥有最简单的人工智能来玩这个游戏: Microbit 编程: 简易人工智能让电脑玩游戏

英文: Microbit Programming: How to Make a Catching-Apple Game by Using Sprites Objects?

强烈推荐

微信公众号: 小赖子的英国生活和资讯 JustYYUK

阅读 桌面完整版
Exit mobile version