本帖最后由 驴友花雕 于 2025-9-10 18:53 编辑
作为学习、练习与尝试,这里创建一个漫天烟花的小游戏。
打开网页版:https://arcade.makecode.com/,设置项目名称:漫天烟花
JavaScript 实验参考代码
- const fireworkEffects: effects.ParticleEffect[] = [
- /** small spinner effect **/
- createEffect(
- 1000,
- 300,
- () => {
- /**
- * this extends the radial factory used in the warm radial, cool radial,
- * and halo effects to shorten the lifespan of the particles, so they will
- * form a smaller radius
- */
- class ShortRadial extends particles.RadialFactory {
- createParticle(anchor: particles.ParticleAnchor) {
- const p = super.createParticle(anchor);
- p.lifespan = randint(200, 450);
- return p;
- }
- }
-
- return new ShortRadial(
- 2,
- 50,
- 5,
- randomPalette(randint(2, 5))
- );
- }
- ),
- /** Brocade: forms an 'umbrella like' pattern. I started building this off of the 'fountain' particle **/
- new effects.ParticleEffect(600, 500, function (anchor: particles.ParticleAnchor, particlesPerSecond: number) {
- class BrocadeFactory extends particles.SprayFactory {
- galois: Math.FastRandom;
- palette: number[];
-
- constructor() {
- super(110, 180, 359);
- this.galois = new Math.FastRandom();
- this.palette = randomPalette(2);
- }
-
- createParticle(anchor: particles.ParticleAnchor) {
- const p = super.createParticle(anchor);
-
- if (this.galois.percentChance(25)) {
- p.color = this.palette[0];
- p.lifespan = randint(50, 150);
- } else {
- p.color = this.palette[1];
- p.lifespan = randint(50, 350);
- }
- return p;
- }
-
- drawParticle(p: particles.Particle, x: Fx8, y: Fx8) {
- // always just fill a pixel if color is first color, otherwise single pixel 3/4 the time
- if (p.color == this.palette[0] || this.galois.percentChance(85)) {
- screen.setPixel(Fx.toInt(x), Fx.toInt(y), p.color);
- } else {
- const toPrint = this.galois.randomBool()
- ? img`
- . 1 .
- 1 1 1
- . 1 .
- `
- : img`
- 1 . 1
- . 1 .
- 1 . 1
- `;
- toPrint.replace(0x1, p.color);
- screen.drawTransparentImage(
- toPrint,
- Fx.toInt(x),
- Fx.toInt(y)
- );
- }
- }
- }
-
- const factory = new BrocadeFactory();
- const source = new particles.ParticleSource(anchor, particlesPerSecond, factory);
- source.setAcceleration(0, 600);
- return source;
- }),
- /** Sparkler like effect**/
- createEffect(
- 600,
- 600,
- () => {
- class SparklerFactory extends particles.SprayFactory {
- galois: Math.FastRandom;
- palette: number[];
-
- constructor() {
- super(50, 180, 359);
- this.galois = new Math.FastRandom();
- this.palette = randomPalette(2);
- }
-
- createParticle(anchor: particles.ParticleAnchor) {
- const p = super.createParticle(anchor);
- p.data = randint(0, 10);
-
- if (this.galois.percentChance(25)) {
- p.color = this.palette[0];
- p.lifespan = randint(250, 450);
- } else {
- p.color = this.palette[2];
- p.lifespan = randint(500, 750);
- }
-
- return p;
- }
-
- drawParticle(p: particles.Particle, x: Fx8, y: Fx8) {
- ++p.data;
- // this condition will make the particles flicker;
- // p.data >> 1 is equivalent to dividing by 2,
- // and % 2 evaluates to 1 or 0 (effectively, odd or even)
- // this condition then executes if it evaluates to 1,
- // which javascript considers to be 'truthy'
- if ((p.data >> 1) % 2) {
- // mostly print single dots, but potentially also print small shapes
- const toPrint = this.galois.percentChance(90)
- ? img`1`
- : this.galois.randomBool()
- ? img`
- . 1 .
- 1 . 1
- . 1 .
- `
- : img`
- 1 . 1
- . 1 .
- `;
- toPrint.replace(1, p.color);
- screen.drawTransparentImage(
- toPrint,
- Fx.toInt(x),
- Fx.toInt(y)
- );
- }
- }
- }
-
- return new SparklerFactory();
- }
- ),
- /** Crossette: straight lines that fly straight out, with small 'branches' **/
- createEffect(
- 100,
- 600,
- () => {
- class CrossetteFactory extends particles.SprayFactory {
- galois: Math.FastRandom;
- anchor: particles.ParticleAnchor;
- particlesRemaining: number
- palette: number[];
-
- constructor() {
- super(40, 180, 359);
- this.galois = new Math.FastRandom();
- this.particlesRemaining = 8;
- this.palette = randomPalette(2);
- }
-
- createParticle(anchor: particles.ParticleAnchor) {
- if (--this.particlesRemaining < 0) {
- return undefined;
- }
- if (!this.anchor)
- this.anchor = anchor;
- const p = super.createParticle(anchor);
- const particleRateMultiple = Fx8(randint(60, 100) / 100);
- p.vx = Fx.mul(p.vx, particleRateMultiple);
- p.vy = Fx.mul(p.vy, particleRateMultiple);
- p.color = this.palette[this.galois.randomRange(0, 1)];;
-
- p.lifespan = randint(600, 800);
- return p;
- }
-
- drawParticle(p: particles.Particle, x: Fx8, y: Fx8) {
- // double line with offset x to make the current position of the particle
- // slightly 'thicker'
- for (let i = 0; i < 2; i++) {
- screen.drawLine(
- Fx.toInt(x) + i,
- Fx.toInt(y),
- this.anchor.x,
- this.anchor.y,
- p.color
- );
- }
- if (this.galois.randomBool()) {
- screen.drawTransparentImage(
- this.galois.randomBool()
- ? img`
- 4 . 4
- . 4 .
- 4 . 4
- `
- : img`
- . 4 .
- 4 . 4
- . 4 .
- `,
- Fx.toInt(x) - 1,
- Fx.toInt(y) - 1
- );
- }
- }
- }
-
- return new CrossetteFactory();
- }
- ),
- ]
-
- /**
- * This is copied from my original definition for it in
- * pxt-common-packages/libs/game/particleeffects.ts, as that isn't currently exported.
- *
- * It is used to wrap simple particle factories that are created with a standard source
- * into effects that can be easily used
- */
- function createEffect(
- defaultParticlesPerSecond: number,
- defaultLifespan: number,
- factoryFactory: (anchor?: particles.ParticleAnchor) => particles.ParticleFactory
- ): effects.ParticleEffect {
- return new effects.ParticleEffect(defaultParticlesPerSecond, defaultLifespan,
- (anchor: particles.ParticleAnchor, pps: number) =>
- new particles.ParticleSource(anchor, pps, factoryFactory()));
- }
-
- // stars that don't twinkle - focus should be on fireworks, not the random
- // changes in the background
- new effects.ScreenEffect(
- 2,
- 5,
- 5000,
- function (anchor: particles.ParticleAnchor, particlesPerSecond: number) {
- class NoTwinkleStarFactory extends particles.StarFactory {
- constructor() {
- super();
- this.possibleColors = [0xE, 0xB, 0xC, 0xD];
- }
-
- drawParticle(p: particles.Particle, x: Fx8, y: Fx8) {
- const rest = 0x7FFF;
- const selected = this.images[rest & p.data].clone();
- selected.replace(0x1, p.color);
- screen.drawTransparentImage(
- selected,
- Fx.toInt(x),
- Fx.toInt(y)
- );
- }
- }
- const factory = new NoTwinkleStarFactory();
- return new particles.ParticleSource(
- anchor,
- particlesPerSecond,
- new NoTwinkleStarFactory()
- );
- }
- ).startScreenEffect();
-
- const fireworkTrail = createEffect(
- 25,
- 50,
- a => {
- class FireworkTrail extends particles.ParticleFactory {
- constructor() {
- super();
- }
-
- createParticle(anchor: particles.ParticleAnchor) {
- const p = super.createParticle(anchor);
- p.vx = Fx.neg(Fx8(anchor.vx + randint(-10, 10)));
- p.vy = Fx.neg(Fx8(anchor.vy >> 1));
- p.lifespan = randint(50, 500);
- p.color = Math.percentChance(90) ? 0xE : randint(0x1, 0xD);
- return p;
- }
-
- drawParticle(p: particles.Particle, x: Fx8, y: Fx8) {
- screen.setPixel(
- Fx.toInt(x),
- Fx.toInt(y),
- p.color
- );
- }
- }
- return new FireworkTrail;
- }
- );
-
- // disable the menu button - menus shouldn't get in the way of the demonstration!
- controller.menu.onEvent(ControllerButtonEvent.Pressed, undefined);
- controller.anyButton.onEvent(
- ControllerButtonEvent.Pressed,
- tryToFire
- );
-
- const TIMEOUT = 200;
- let lastFired = game.runtime();
- function tryToFire() {
- const time = game.runtime();
- if (lastFired + TIMEOUT < time) {
- const vx = randint(-35, 35);
- const firework = sprites.createProjectileFromSide(
- img`
- e
- e
- `,
- vx,
- randint(-150, -125)
- );
-
- if (!firework.vx || Math.percentChance(70)) {
- firework.x = randint(25, screen.width - 25);
- } else {
- firework.y -= 20;
- firework.vy *= .8;
- if (Math.abs(firework.vx) < 10) {
- firework.vx = randint(30, 40) * (firework.vx < 0 ? -1 : 1);
- } else {
- firework.vx *= 2;
- }
- }
-
- firework.startEffect(fireworkTrail);
- firework.ay = 100;
- firework.lifespan = randint(800, 1200);
- lastFired = time;
- }
- }
-
- game.onUpdate(function () {
- if (lastFired + (3 * TIMEOUT) < game.runtime()) {
- // auto fire if there hasn't been any for a while
- tryToFire();
- }
- });
-
-
- sprites.onDestroyed(SpriteKind.Projectile, s => {
- Math.pickRandom(fireworkEffects).start(s, 500);
- });
-
- /**
- * Color stuff
- *
- * this uses the pxt-color extension to change the color palette at runtime.
- * To make all the fireworks unique, this generates a random palette of pastel-ish colors,
- * with the exception of 0xE (set to white) and 0xF (left as black).
- * It also continuously changes the colors from 0x1 to 0xA, fading between different palettes
- * for those colors at random.
- */
- const p = color.currentPalette();
- p.setColor(0xB, generatePastel().hexValue());
- p.setColor(0xC, generatePastel().hexValue());
- p.setColor(0xD, generatePastel().hexValue());
- p.setColor(0xE, 0xFFFFFF);
- color.setPalette(p);
-
- forever(() => {
- new color.Fade()
- .mapEndHSL(
- generatePastel,
- 0x1,
- 0xA
- )
- .startUntilDone(500);
- })
-
- function generatePastel() {
- // generate a random pastel-adjacent color:
- // pastels have 100% saturation and high luminosity ('brightness')
- return new color.HSL(
- randint(0, 359),
- 1,
- randint(75, 95) / 100
- );
- }
-
- /**
- * Generates a value to be used to specify the colors for each firework,
- * so that the colors aren't always the same between fireworks that run
- * at the same time (value between 1 and 8, so there will always be )
- */
- function randomPalette(len: number) {
- if (len > 8) {
- len = 8;
- }
- const palette: number[] = [];
- for (let i = 0; i < len; i++) {
- while (palette.length == i) {
- const selected = randint(1, 0xA);
- if (palette.indexOf(selected) < 0) {
- palette.push(selected);
- }
- }
- }
-
- return palette;
- }
复制代码
|