回到首页 返回首页
回到顶部 回到顶部
返回上一页 返回上一页

【花雕动手做】Kitronik 可编程开发板基于 ARCADE MakeCode 之方块 Boss 简单

头像 驴友花雕 2025.09.12 8 0

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

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

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

 

00 (2).jpg
00 (3).jpg
00 (4).jpg

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

MicroPython实验参考代码

 

代码
@namespace
class SpriteKind:
    PlayerShot = SpriteKind.create()
    LifeBar = SpriteKind.create()
def moveSpriteInTime(sprite: Sprite, x: number, y: number, t: number):
    global globalX, globalY, dx, dy
    globalX = x
    globalY = y
    dx = x - sprite.x
    dy = y - sprite.y
    sprite.set_velocity(dx / t, dy / t)

def on_on_overlap(sprite2, otherSprite):
    global bossLife
    if started:
        info.change_score_by(20)
        bossLife += -1
        music.play_tone(208, music.beat(BeatFraction.EIGHTH))
        lifeBarPic.fill_rect(bossLife * 2, 0, 96 - bossLife * 2, 5, 15)
        lifeBar.set_image(lifeBarPic)
        if bossLife <= 0:
            game.over(True)
        elif bossLife % 12 == 0:
            preSetBossPosition(80, 30)
    otherSprite.destroy()
sprites.on_overlap(SpriteKind.enemy, SpriteKind.PlayerShot, on_on_overlap)

def spell1():
    enemyShootAimingPlayer(boss, 90, 5)

def on_b_released():
    controller.move_sprite(mySprite)
controller.B.on_event(ControllerButtonEvent.RELEASED, on_b_released)

def moveSpriteRandom(sprite3: Sprite, yLowerBound: number, outerBound: number, v: number):
    moveSprite(sprite3,
        randint(outerBound, scene.screen_width() - outerBound),
        randint(outerBound, yLowerBound),
        v)

def on_a_pressed():
    shootBulletFromSprite(mySprite, 200, -90)
controller.A.on_event(ControllerButtonEvent.PRESSED, on_a_pressed)

def nonSpell1():
    global offset
    index2 = 0
    while index2 <= MAX - 1:
        shootBulletFromSprite(boss, 60, 360 / MAX * index2 + offset)
        index2 += 1
    offset += 13
def spell2():
    global offset
    for index in range(5):
        shootBulletFromSprite(boss, 60, offset + index * 30)
    offset += 23

def on_on_overlap2(sprite4, otherSprite2):
    info.change_life_by(-1)
    scene.camera_shake(3, 200)
    music.play_tone(139, music.beat(BeatFraction.EIGHTH))
    otherSprite2.destroy()
sprites.on_overlap(SpriteKind.player, SpriteKind.projectile, on_on_overlap2)

def on_b_pressed():
    controller.move_sprite(mySprite, 50, 50)
controller.B.on_event(ControllerButtonEvent.PRESSED, on_b_pressed)

def preSetBossPosition(x2: number, y2: number):
    global started, ready, offset
    started = False
    ready = False
    offset = 0
    moveSpriteInTime(boss, x2, y2, 1)
def moveSpriteRandomFixedTime(sprite5: Sprite, yLowerBound2: number, outerBound2: number, u: number):
    moveSpriteInTime(sprite5,
        randint(outerBound2, scene.screen_width() - outerBound2),
        randint(outerBound2, yLowerBound2),
        u)
def nonSpell2():
    index3 = 0
    while index3 <= MAX - 1:
        shootBulletFromSprite(boss, 60, 360 / MAX * index3 + offset)
        shootBulletFromSprite(boss, 100, 360 / MAX * (index3 + 0.5) + offset)
        index3 += 1
def shootBulletFromSprite(sourceSprite: Sprite, speed: number, angle: number):
    global projectile
    projectile = sprites.create_projectile_from_sprite(img("""
            . . . . . . . . . . . . . . . .
            . . . . . . . . . . . . . . . .
            . . . . . . . . . . . . . . . .
            . . . . . . . . . . . . . . . .
            . . . . . . . . . . . . . . . .
            . . . . . . . . . . . . . . . .
            . . . . . . . . . . . . . . . .
            . . . . . . . . . . . . . . . .
            . . . . . . . . . . . . . . . .
            . . . . . . . . . . . . . . . .
            . . . . . . . . . . . . . . . .
            . . . . . . . . . . . . . . . .
            . . . . . . . . . . . . . . . .
            . . . . . . . . . . . . . . . .
            . . . . . . . . . . . . . . . .
            . . . . . . . . . . . . . . . .
            """),
        sourceSprite,
        speed * Math.cos(angle / 57.3),
        speed * Math.sin(angle / 57.3))
    projectile.set_flag(SpriteFlag.AUTO_DESTROY, True)
    if sourceSprite.kind() == SpriteKind.player:
        projectile.set_kind(SpriteKind.PlayerShot)
        projectile.set_image(img("""
            . . . . . . . . . . . . . . . .
            . . . . . . . . . . . . . . . .
            . . . . . . . . . . . . . . . .
            . . . . . . . . . . . . . . . .
            . . . . . . . . . . . . . . . .
            . . . . . . . 5 5 . . . . . . .
            . . . . . . 5 4 4 5 . . . . . .
            . . . . . 5 4 2 2 4 5 . . . . .
            . . . . . 5 4 2 2 4 5 . . . . .
            . . . . . . 5 4 4 5 . . . . . .
            . . . . . . . 5 5 . . . . . . .
            . . . . . . . . . . . . . . . .
            . . . . . . . . . . . . . . . .
            . . . . . . . . . . . . . . . .
            . . . . . . . . . . . . . . . .
            . . . . . . . . . . . . . . . .
            """))
    else:
        projectile.set_image(img("""
            . . . . . . . . . . . . . . . .
            . . . . . . . . . . . . . . . .
            . . . . . . . . . . . . . . . .
            . . . . . . . . . . . . . . . .
            . . . . . . . . . . . . . . . .
            . . . . . . . 9 9 . . . . . . .
            . . . . . . 9 6 6 9 . . . . . .
            . . . . . 9 6 8 8 6 9 . . . . .
            . . . . . 9 6 8 8 6 9 . . . . .
            . . . . . . 9 6 6 9 . . . . . .
            . . . . . . . 9 9 . . . . . . .
            . . . . . . . . . . . . . . . .
            . . . . . . . . . . . . . . . .
            . . . . . . . . . . . . . . . .
            . . . . . . . . . . . . . . . .
            . . . . . . . . . . . . . . . .
            """))
def moveSprite(sprite6: Sprite, x3: number, y3: number, w: number):
    global globalX, globalY, dx, dy, speed3
    globalX = x3
    globalY = y3
    dx = x3 - sprite6.x
    dy = y3 - sprite6.y
    speed3 = Math.sqrt(dx * dx + dy * dy)
    if speed3 != 0:
        sprite6.set_velocity(dx / speed3 * w, dy / speed3 * w)
def enemyShootAimingPlayer(sprite7: Sprite, speed2: number, spread: number):
    shootBulletFromSprite(sprite7,
        speed2,
        Math.atan2(mySprite.y - sprite7.y, mySprite.x - sprite7.x) * 57.3 + randint(0 - spread, spread))
lifeBarProgress = 0
bossProgress = 0
speed3 = 0
projectile: Sprite = None
ready = False
started = False
dy = 0
dx = 0
globalY = 0
globalX = 0
MAX = 0
offset = 0
lifeBar: Sprite = None
lifeBarPic: Image = None
boss: Sprite = None
mySprite: Sprite = None
bossLife = 0
bossLife = 48
info.set_life(20)
info.set_score(0)
music.set_volume(20)
mySprite = sprites.create(img("""
        . . . . . . f f f f . . . . . .
        . . . . f f e e e e f f . . . .
        . . . f e e e f f e e e f . . .
        . . f f f f f 2 2 f f f f f . .
        . . f f e 2 e 2 2 e 2 e f f . .
        . . f e 2 f 2 f f 2 f 2 e f . .
        . . f f f 2 2 e e 2 2 f f f . .
        . f f e f 2 f e e f 2 f e f f .
        . f e e f f e e e e f e e e f .
        . . f e e e e e e e e e e f . .
        . . . f e e e e e e e e f . . .
        . . e 4 f f f f f f f f 4 e . .
        . . 4 d f 2 2 2 2 2 2 f d 4 . .
        . . 4 4 f 4 4 4 4 4 4 f 4 4 . .
        . . . . . f f f f f f . . . . .
        . . . . . f f . . f f . . . . .
        """),
    SpriteKind.player)
mySprite.set_position(80, 105)
mySprite.set_flag(SpriteFlag.STAY_IN_SCREEN, True)
controller.move_sprite(mySprite)
boss = sprites.create(img("""
        ........................
        ........................
        ........................
        ........................
        ..........ffff..........
        ........ff1111ff........
        .......fb111111bf.......
        .......f11111111f.......
        ......fd11111111df......
        ......fd11111111df......
        ......fddd1111dddf......
        ......fbdbfddfbdbf......
        ......fcdcf11fcdcf......
        .......fb111111bf.......
        ......fffcdb1bdffff.....
        ....fc111cbfbfc111cf....
        ....f1b1b1ffff1b1b1f....
        ....fbfbffffffbfbfbf....
        .........ffffff.........
        ...........fff..........
        ........................
        ........................
        ........................
        ........................
        """),
    SpriteKind.enemy)
boss.set_position(-16, -16)
lifeBarPic = image.create(96, 5)
lifeBar = sprites.create(lifeBarPic, SpriteKind.LifeBar)
lifeBar.set_position(80, 5)
lifeBar.set_flag(SpriteFlag.GHOST, True)
offset = 0
MAX = 10
bossCanMove = True
preSetBossPosition(80, 30)

def on_on_update():
    global bossProgress, bossCanMove, MAX, ready
    if abs(boss.x - globalX) + abs(boss.y - globalY) <= 2:
        boss.set_velocity(0, 0)
        if not (ready):
            bossProgress += 1
            if bossProgress == 2:
                bossCanMove = False
            else:
                if bossProgress == 2:
                    MAX = 8
                bossCanMove = True
        ready = True
game.on_update(on_on_update)

def on_update_interval():
    if started:
        if bossProgress == 3:
            nonSpell2()
game.on_update_interval(750, on_update_interval)

def on_update_interval2():
    if started and bossCanMove:
        moveSpriteRandom(boss, 40, 8, 60)
game.on_update_interval(2500, on_update_interval2)

def on_update_interval3():
    if started:
        if bossProgress == 2:
            spell1()
        else:
            if bossProgress == 4:
                spell2()
game.on_update_interval(150, on_update_interval3)

def on_update_interval4():
    if started:
        if bossProgress == 1:
            nonSpell1()
game.on_update_interval(500, on_update_interval4)

def on_update_interval5():
    global lifeBarProgress, started
    if ready and not (started):
        if lifeBarProgress < 4:
            lifeBarPic.fill_rect(24 * lifeBarProgress, 0, 24, 5, 14 - lifeBarProgress % 2 * 6)
            lifeBarPic.fill_rect(24 * lifeBarProgress, 1, 24, 3, lifeBarProgress % 2 * 5 + 4)
            lifeBar.set_image(lifeBarPic)
            lifeBarProgress += 1
        else:
            started = True
game.on_update_interval(100, on_update_interval5)

这段代码是一个用 MakeCode Arcade 编写的完整 Boss 战游戏框架,名为 “方块 Boss”。它融合了玩家控制、Boss AI、弹幕系统、生命条动画、阶段切换等机制,构建出一个具有挑战性的战斗场景。下面是从专业角度对其结构与逻辑的详细解读:

一、游戏核心机制概览
 

110-.jpg

二、关键逻辑模块解析
1、玩家与 Boss 初始化
python
mySprite = sprites.create(..., SpriteKind.player)
boss = sprites.create(..., SpriteKind.enemy)
玩家角色使用 controller.move_sprite() 控制移动

Boss 初始位置为屏幕外,随后通过 preSetBossPosition() 移入战场

2、Boss 生命条加载动画
python
on_update_interval5()
每 100ms 更新一次生命条图像,逐步填充

加载完成后 started = True,正式进入战斗状态

3、玩家攻击 Boss
python
controller.A.on_event(...): shootBulletFromSprite(mySprite, 200, -90)
sprites.on_overlap(SpriteKind.enemy, SpriteKind.PlayerShot, on_on_overlap)
玩家按 A 键发射子弹

子弹击中 Boss 后:

得分 +20

Boss 生命值减少

更新生命条图像

每损失 12 点生命触发一次位置重置

4、Boss 攻击模式切换
python
bossProgress = 1 → nonSpell1()
bossProgress = 2 → spell1()
bossProgress = 3 → nonSpell2()
bossProgress = 4 → spell2()
每种攻击模式通过定时器触发不同弹幕函数

nonSpell1():环形弹幕

spell1():追踪弹

nonSpell2():双层环形弹幕

spell2():扇形弹幕

5、Boss 移动逻辑
python
moveSpriteRandom(boss, 40, 8, 60)
每 2.5 秒随机移动一次

使用 moveSprite() 或 moveSpriteInTime() 实现平滑移动

到达目标位置后触发阶段推进逻辑

6、玩家受伤逻辑
python
sprites.on_overlap(SpriteKind.player, SpriteKind.projectile, on_on_overlap2)
玩家被 Boss 弹幕击中:

生命值 -1

屏幕震动

播放音效

三、弹幕生成机制
shootBulletFromSprite(source, speed, angle)
使用三角函数计算速度向量

根据发射者类型设置不同图像与种类

自动销毁,避免资源堆积

enemyShootAimingPlayer(sprite, speed, spread)
计算玩家与 Boss 的角度

添加随机偏移实现“散射追踪弹”。

图形编程参考实验程序

 

 

111.jpg

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

 

00208---0.gif


实验场景记录

 

112 (1).jpg
112 (2).jpg

评论

user-avatar
icon 他的勋章
    展开更多