驴友花雕 发表于 昨天 21:33

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

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

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

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







驴友花雕 发表于 昨天 21:37

【花雕动手做】基于 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)

驴友花雕 发表于 昨天 21:42

【花雕动手做】基于 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 的角度

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


驴友花雕 发表于 昨天 21:44

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

图形编程参考实验程序




驴友花雕 发表于 昨天 21:46

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

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



实验场景记录





页: [1]
查看完整版本: 【花雕动手做】基于 Kitronik 可编程开发板之方块 Boss