【花雕动手做】基于 Kitronik 可编程开发板之方块 Boss
Kitronik ARCADE 是一款由英国教育科技公司 Kitronik 精心打造的可编程游戏机开发板,专为编程教学与创客实践而设计。该设备原生支持微软的 MakeCode Arcade 平台,用户可通过图形化或 JavaScript 编程方式,轻松创建、下载并运行复古风格的街机游戏。它集成了彩色 LCD 显示屏、方向控制键、功能按键、蜂鸣器和震动马达等交互组件,提供完整的游戏输入输出体验。无论是初学者进行编程启蒙,还是创客群体开发交互式作品,Kitronik ARCADE 都能作为理想的硬件载体,助力创意实现。
凭借其开源友好、易于上手、兼容性强等特点,该开发板广泛应用于中小学编程课程、创客工作坊、游戏开发教学以及个人项目原型设计,深受教育者与技术爱好者的喜爱。
【花雕动手做】基于 Kitronik 可编程开发板之方块 Boss
作为学习、练习与尝试,这里创建一个方块 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)
【花雕动手做】基于 Kitronik 可编程开发板之方块 Boss
这段代码是一个用 MakeCode Arcade 编写的完整 Boss 战游戏框架,名为 “方块 Boss”。它融合了玩家控制、Boss AI、弹幕系统、生命条动画、阶段切换等机制,构建出一个具有挑战性的战斗场景。下面是从专业角度对其结构与逻辑的详细解读:一、游戏核心机制概览
二、关键逻辑模块解析
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 的角度
添加随机偏移实现“散射追踪弹”。
【花雕动手做】基于 Kitronik 可编程开发板之方块 Boss
图形编程参考实验程序【花雕动手做】基于 Kitronik 可编程开发板之方块 Boss
通过模拟器,调试与模拟运行实验场景记录
页:
[1]