驴友花雕 发表于 昨天 19:16

【花雕动手做】基于 Kitronik 可编程开发板之粒子效果

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

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

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







驴友花雕 发表于 昨天 19:18

【花雕动手做】基于 Kitronik 可编程开发板之粒子效果

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

JavaScript 实验代码

interface ParticleDemonstration {
    start(): particles.ParticleSource[];
}

// show controls
let ctlMessage = image.create(scene.screenWidth(), 10);
ctlMessage.printCenter("Effects: 'A' (+), 'B' (-)", 0, 0);
let msgSprite = sprites.create(ctlMessage);
let msgInterval = 0;

const myDemonstrations: ParticleDemonstration[] = [];
let count = 1;

controller.A.onEvent(ControllerButtonEvent.Pressed, function () {
    count = Math.min(count + 1, 6);
});

controller.B.onEvent(ControllerButtonEvent.Pressed, function () {
    count = Math.max(count - 1, 1);
});

namespace demonstrations {
    export class Fire implements ParticleDemonstration {
      start() {
            const sources: particles.ParticleSource[] = [];
            const factory = new particles.FireFactory(8);
            const src = new particles.FireSource(makeSimpleAnchor(), 100, factory);
            src.setAcceleration(0, -40);

            sources.push(src);
            return sources;
      }
    }

    export class Spinner implements ParticleDemonstration {
      start() {
            class RingFactory extends particles.RadialFactory {
                createParticle(anchor: particles.ParticleAnchor) {
                  const p = super.createParticle(anchor);
                  p.lifespan = this.galois.randomRange(200, 350);
                  return p;
                }
            }

            const sources: particles.ParticleSource[] = [];
            const colors = ;
            const factory = new RingFactory(20, 30, 10, colors);
            const src = new particles.ParticleSource(makeSimpleAnchor(), 400, factory);

            sources.push(src);
            return sources;
      }
    }

    export class BubbleConfetti implements ParticleDemonstration {
      start() {
            const sources: particles.ParticleSource[] = [];
            const min = 1000;
            const anchor = makeSimpleAnchor();
            anchor.width = screen.width;

            const bubbleFactory = new particles.BubbleFactory(anchor, min, min * 2.5);
            sources.push(new particles.BubbleSource(anchor, 30, bubbleFactory.stateCount - 1, bubbleFactory));

            const confettiFactory = new particles.ConfettiFactory(anchor.width, 16);
            confettiFactory.setSpeed(50);

            sources.push(new particles.ParticleSource(anchor, 50, confettiFactory));
            return sources;
      }
    }

    export class RadialGroup implements ParticleDemonstration {
      start() {
            const sources: particles.ParticleSource[] = [];
            const anchor = makeSimpleAnchor();

            const radius = Math.percentChance(50) ? 0 : 20;
            const increaseRate = Math.percentChance(50);

            control.runInParallel(() => {
                for (let i = 0; i < 3; ++i) {
                  const colors = Math.percentChance(10) ?
                        
                        :
                        Math.percentChance(50) ?
                           
                            :
                            undefined;
                  let factory: particles.ParticleFactory = new particles.RadialFactory(radius, 90, 5, colors);
                  const src = new particles.ParticleSource(anchor, increaseRate ? 50 + (i * 50) : 100, factory);
                  sources.push(src);
                  pause(350);
                }
            });

            return sources;
      }
    }

    export class Stars implements ParticleDemonstration {
      start() {
            class StarFactory extends particles.ParticleFactory {
                protected galois: Math.FastRandom;
                protected possibleColors: number[];
                images: Image[];

                constructor(possibleColors?: number[]) {
                  super();
                  this.galois = new Math.FastRandom();
                  this.images = [
                        img`
                            1
                        `, img`
                            1 . 1
                            . 1 .
                            1 . 1
                        `, img`
                            . 1 .
                            1 1 1
                            . 1 .
                        `
                  ];

                  if (possibleColors && possibleColors.length)
                        this.possibleColors = possibleColors;
                  else
                        this.possibleColors = ;
                }

                createParticle(anchor: particles.ParticleAnchor) {
                  const p = super.createParticle(anchor);

                  p._x = Fx8(this.galois.randomRange(0, screen.width));
                  p._y = Fx8(0);
                  p.vy = Fx8(this.galois.randomRange(40, 60));

                  // set lifespan based off velocity and screen height (plus a little to make sure it doesn't disappear early)
                  p.lifespan = Fx.toInt(Fx.mul(Fx.div(Fx8(screen.height + 20), p.vy), Fx8(1000)));

                  const length = this.possibleColors.length - 1;
                  p.color = this.possibleColors;
                  for (let i = 0; i < length; ++i) {
                        if (this.galois.percentChance(50)) {
                            p.color = this.possibleColors;
                            break;
                        }
                  }

                  // images besides the first one are only used on occasion
                  p.data = this.galois.percentChance(15) ? this.galois.randomRange(1, this.images.length - 1) : 0;

                  return p;
                }

                drawParticle(p: particles.Particle, x: Fx8, y: Fx8) {
                  // on occasion, twinkle from white to yellow
                  const twinkleFlag = 0x8000;
                  const rest = 0x7FFF;
                  if (twinkleFlag && p.data) {
                        if (this.galois.percentChance(10)) {
                            p.color = 1;
                            p.data &= rest;
                        }
                  } else if (p.color === 1 && this.galois.percentChance(1)) {
                        p.color = 5;
                        p.data |= twinkleFlag;
                  }

                  const selected = this.images.clone();
                  selected.replace(0x1, p.color);
                  screen.drawImage(selected, Fx.toInt(x), Fx.toInt(y));
                }
            }

            const sources: particles.ParticleSource[] = [];
            const colors = ;
            for (let i = 0; i < 4; i++)
                colors.push(randint(2, 0xE));

            const factory = new StarFactory(colors);
            const src = new particles.ParticleSource(makeSimpleAnchor(), 25, factory)
            sources.push(src);
            return sources;
      }
    }
}

function makeSimpleAnchor(): particles.ParticleAnchor {
    return {
      x: screen.width >> 1,
      y: screen.height >> 1
    };
}

// Radial group as a lot of different possible configurations, so make it twice as likely as others
for (let i = 0; i < 2; i++) {
    myDemonstrations.push(new demonstrations.RadialGroup());
}
myDemonstrations.push(new demonstrations.Stars());
myDemonstrations.push(new demonstrations.BubbleConfetti());
myDemonstrations.push(new demonstrations.Spinner());
myDemonstrations.push(new demonstrations.Fire());

forever(() => {
    particles.disableAll()

    for (let i = 0; i < count; ++i) {
      Math.pickRandom(myDemonstrations)
            .start();
    }

    msgSprite.top = scene.screenHeight();
    msgSprite.top += msgInterval % 5 == 0 ? -10 : 0;
    msgInterval += 1;
    pause(3000);
});

驴友花雕 发表于 昨天 19:27

【花雕动手做】基于 Kitronik 可编程开发板之粒子效果

这段 JavaScript 代码是基于 MakeCode Arcade 的粒子系统演示框架,展示了如何使用不同的粒子工厂(如火焰、星星、气泡、旋转环等)来创建丰富的视觉效果。它不仅体现了粒子系统的多样性,还通过控制器交互实现了动态切换和组合展示。逐段解读如下:

一、核心结构概览



二、控制器逻辑
ts
controller.A.onEvent(...): count++
controller.B.onEvent(...): count--

按下 A 键:增加展示数量(最多 6)

按下 B 键:减少展示数量(最少 1)

三、粒子演示类详解
每个类都实现了 ParticleDemonstration 接口,返回一个或多个 ParticleSource。

1、Fire
使用 FireFactory 创建火焰效果

设置向上的加速度模拟火苗飘动

2、Spinner
使用 RadialFactory 创建旋转粒子环

自定义 lifespan,粒子存活时间随机

3、BubbleConfetti
同时生成气泡和五彩纸屑

使用两个不同的工厂:BubbleFactory 和 ConfettiFactory

4、RadialGroup
并行生成多个 RadialFactory 粒子源

每个源颜色、半径、速率都可能不同

使用 control.runInParallel() 异步创建,增强动态感

5、 Stars
自定义 StarFactory,生成闪烁星星

粒子图像有三种形态,颜色随机

实现 drawParticle() 方法,控制星星闪烁逻辑

四、辅助函数与配置
1、makeSimpleAnchor()
ts
return { x: screen.width >> 1, y: screen.height >> 1 }

返回一个屏幕中心点作为粒子源锚点

2、初始化演示列表
ts
myDemonstrations.push(...)
RadialGroup 被添加两次,增加其出现概率


其余演示类各添加一次

五、主循环逻辑
ts
forever(() => {
    particles.disableAll()
    for (let i = 0; i < count; ++i) {
      Math.pickRandom(myDemonstrations).start();
    }
    ...
    pause(3000);
});

每 3 秒:

清除所有粒子

随机选择 count 个演示类并启动

控制提示信息上下浮动,形成动态 UI 效果。



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

【花雕动手做】基于 Kitronik 可编程开发板之粒子效果

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





实验场景记录





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