UNPKG

pxt-common-packages

Version:
370 lines (319 loc) 14.8 kB
namespace effects { //% fixedInstances export interface BackgroundEffect { startScreenEffect(): void; } //% fixedInstances export class ParticleEffect { protected sourceFactory: (anchor: particles.ParticleAnchor, pps: number) => particles.ParticleSource; protected defaultRate: number; protected defaultLifespan: number; constructor(defaultParticlesPerSecond: number, defaultLifespan: number, sourceFactory: (anchor: particles.ParticleAnchor, particlesPerSecond: number) => particles.ParticleSource) { this.sourceFactory = sourceFactory; this.defaultRate = defaultParticlesPerSecond; this.defaultLifespan = defaultLifespan; } /** * Attaches a new particle animation to the sprite or anchor for a short period of time * @param anchor * @param duration * @param particlesPerSecond */ start(anchor: particles.ParticleAnchor, duration?: number, particlesPerSecond?: number, relativeToCamera?: boolean): void { if (!this.sourceFactory) return; const src = this.sourceFactory(anchor, particlesPerSecond ? particlesPerSecond : this.defaultRate); src.setRelativeToCamera(!!relativeToCamera); if (duration) src.lifespan = duration > 0 ? duration : this.defaultLifespan; } /** * Destroy the provided sprite with an effect * @param sprite * @param duration how long the sprite will remain on the screen. If set to 0 or undefined, * uses the default rate for this effect. * @param particlesPerSecond */ destroy(anchor: Sprite, duration?: number, particlesPerSecond?: number) { anchor.setFlag(SpriteFlag.Ghost, true); this.start(anchor, particlesPerSecond, null, !!(anchor.flags & sprites.Flag.RelativeToCamera)); anchor.lifespan = duration ? duration : this.defaultLifespan >> 2; effects.dissolve.applyTo(anchor); } } /** * Anchor used for effects that occur across the screen. */ class SceneAnchor implements particles.ParticleAnchor { private camera: scene.Camera; constructor() { this.camera = game.currentScene().camera; } get x() { return this.camera.offsetX + (screen.width >> 1); } get y() { return this.camera.offsetY + (screen.height >> 1); } get width() { return screen.width; } get height() { return screen.height; } } //% fixedInstances export class ScreenEffect extends ParticleEffect implements BackgroundEffect { protected source: particles.ParticleSource; protected sceneDefaultRate: number; constructor(anchorDefault: number, sceneDefault: number, defaultLifespan: number, sourceFactory: (anchor: particles.ParticleAnchor, particlesPerSecond: number) => particles.ParticleSource) { super(anchorDefault, defaultLifespan, sourceFactory); this.sceneDefaultRate = sceneDefault; } /** * Creates a new effect that occurs over the entire screen * @param particlesPerSecond * @param duration */ //% blockId=particlesStartScreenAnimation block="start screen %effect effect || for %duration ms" //% duration.shadow=timePicker //% blockNamespace=scene //% group="Effects" blockGap=8 //% weight=90 help=effects/start-screen-effect startScreenEffect(duration?: number, particlesPerSecond?: number): void { if (!this.sourceFactory) return; if (this.source && this.source.enabled) { if (duration) this.source.lifespan = duration; return; } this.endScreenEffect(); this.source = this.sourceFactory(new SceneAnchor(), particlesPerSecond ? particlesPerSecond : this.sceneDefaultRate); this.source.priority = 10; if (duration) this.source.lifespan = duration; } /** * If this effect is currently occurring as a full screen effect, stop producing particles and end the effect * @param particlesPerSecond */ //% blockId=particlesEndScreenAnimation block="end screen %effect effect" //% blockNamespace=scene //% group="Effects" blockGap=8 //% weight=80 help=effects/end-screen-effect endScreenEffect(): void { if (this.source) { this.source.destroy(); this.source = undefined; } } } /** * Removes all effects attached to the given anchor * @param anchor the anchor to remove effects from */ //% blockId=particlesclearparticles block="clear effects on %anchor=variables_get(mySprite)" //% blockNamespace=sprites //% group="Effects" weight=89 //% help=effects/clear-particles export function clearParticles(anchor: Sprite | particles.ParticleAnchor) { const sources = game.currentScene().particleSources; if (!sources) return; sources .filter(ps => ps.anchor === anchor) .forEach(ps => ps.destroy()); } function createEffect(defaultParticlesPerSecond: number, defaultLifespan: number, factoryFactory: (anchor?: particles.ParticleAnchor) => particles.ParticleFactory): ParticleEffect { return new ParticleEffect(defaultParticlesPerSecond, defaultLifespan, (anchor: particles.ParticleAnchor, pps: number) => new particles.ParticleSource(anchor, pps, factoryFactory())); } //% fixedInstance whenUsed block="spray" export const spray = createEffect(20, 2000, function () { return new particles.SprayFactory(100, 0, 120) }); //% fixedInstance whenUsed block="trail" export const trail = new ParticleEffect(20, 4000, function (anchor: particles.ParticleAnchor, particlesPerSecond: number) { const factory = new particles.TrailFactory(anchor, 250, 1000); return new particles.ParticleSource(anchor, particlesPerSecond, factory); }); //% fixedInstance whenUsed block="fountain" export const fountain = new ParticleEffect(20, 3000, function (anchor: particles.ParticleAnchor, particlesPerSecond: number) { class FountainFactory extends particles.SprayFactory { galois: Math.FastRandom; constructor() { super(40, 180, 90); this.galois = new Math.FastRandom(1234); } createParticle(anchor: particles.ParticleAnchor) { const p = super.createParticle(anchor); p.color = this.galois.randomBool() ? 8 : 9; p.lifespan = 1500; return p; } drawParticle(p: particles.Particle, x: Fx8, y: Fx8) { screen.setPixel(Fx.toInt(x), Fx.toInt(y), p.color); } } const factory = new FountainFactory(); const source = new particles.ParticleSource(anchor, particlesPerSecond, factory); source.setAcceleration(0, 40); return source; }); //% fixedInstance whenUsed block="confetti" export const confetti = new ScreenEffect(10, 40, 4000, function (anchor: particles.ParticleAnchor, particlesPerSecond: number) { const factory = new particles.ConfettiFactory(anchor.width ? anchor.width : 16, 16); factory.setSpeed(30); return new particles.ParticleSource(anchor, particlesPerSecond, factory); }); //% fixedInstance whenUsed block="hearts" export const hearts = new ScreenEffect(5, 20, 2000, function (anchor: particles.ParticleAnchor, particlesPerSecond: number) { const factory = new particles.ShapeFactory(anchor.width ? anchor.width : 16, 16, img` . F . F . F . F . F F . . . F . F . F . . . F . . `); // if large anchor, increase lifespan if (factory.xRange > 50) { factory.minLifespan = 1000; factory.maxLifespan = 2000; } factory.setSpeed(90); return new particles.ParticleSource(anchor, particlesPerSecond, factory); }); //% fixedInstance whenUsed block="smiles" export const smiles = new ScreenEffect(5, 25, 1500, function (anchor: particles.ParticleAnchor, particlesPerSecond: number) { const factory = new particles.ShapeFactory(anchor.width ? anchor.width : 16, 16, img` . f . f . . f . f . . . . . . f . . . f . f f f . `); // if large anchor, increase lifespan if (factory.xRange > 50) { factory.minLifespan = 1250; factory.maxLifespan = 2500; } factory.setSpeed(50); return new particles.ParticleSource(anchor, particlesPerSecond, factory); }); //% fixedInstance whenUsed block="rings" export const rings = createEffect(5, 1000, function () { return new particles.ShapeFactory(16, 16, img` . F F F . F . . . F F . . . F f . . . f . f f f . `); }); //% fixedInstance whenUsed block="fire" export const fire = new ParticleEffect(50, 5000, function (anchor: particles.ParticleAnchor, particlesPerSecond: number) { const factory = new particles.FireFactory(5); const src = new particles.FireSource(anchor, particlesPerSecond, factory); src.setAcceleration(0, -20); return src; }); //% fixedInstance whenUsed block="warm radial" export const warmRadial = createEffect(30, 2500, function () { return new particles.RadialFactory(0, 30, 10) }); //% fixedInstance whenUsed block="cool radial" export const coolRadial = createEffect(30, 2000, function () { return new particles.RadialFactory(0, 30, 10, [0x6, 0x7, 0x8, 0x9, 0xA]) }); //% fixedInstance whenUsed block="halo" export const halo = createEffect(70, 3000, function () { class RingFactory extends particles.RadialFactory { createParticle(anchor: particles.ParticleAnchor) { const p = super.createParticle(anchor); p.lifespan = this.galois.randomRange(200, 350); return p; } } return new RingFactory(30, 40, 10, [0x4, 0x4, 0x5]); }); //% fixedInstance whenUsed block="ashes" export const ashes = new ParticleEffect(60, 2000, function (anchor: particles.ParticleAnchor, particlesPerSecond: number) { const factory = new particles.AshFactory(anchor); const src = new particles.ParticleSource(anchor, particlesPerSecond, factory); src.setAcceleration(0, 500); return src; }); //% fixedInstance whenUsed block="disintegrate" export const disintegrate = new ParticleEffect(60, 1250, function (anchor: particles.ParticleAnchor, particlesPerSecond: number) { const factory = new particles.AshFactory(anchor, true, 30); factory.minLifespan = 200; factory.maxLifespan = 500; const src = new particles.ParticleSource(anchor, particlesPerSecond, factory); src.setAcceleration(0, 750); return src; }); //% fixedInstance whenUsed block="blizzard" export const blizzard = new ScreenEffect(15, 50, 3000, function (anchor: particles.ParticleAnchor, particlesPerSecond: number) { class SnowFactory extends particles.ShapeFactory { constructor(xRange: number, yRange: number) { super(xRange, yRange, img`F`); this.addShape(img` F F` ); this.minLifespan = 200; this.maxLifespan = this.xRange > 50 ? 1200: 700; } createParticle(anchor: particles.ParticleAnchor) { const p = super.createParticle(anchor); p.color = this.galois.percentChance(80) ? 0x1 : 0x9; return p; } } const factory = new SnowFactory(anchor.width ? anchor.width : 16, anchor.height ? anchor.height : 16); const src = new particles.ParticleSource(anchor, particlesPerSecond, factory); src.setAcceleration(-300, -100); return src; }); //% fixedInstance whenUsed block="bubbles" export const bubbles = new ScreenEffect(15, 40, 5000, function (anchor: particles.ParticleAnchor, particlesPerSecond: number) { const min = anchor.width > 50 ? 2000 : 500; const factory = new particles.BubbleFactory(anchor, min, min * 2.5); return new particles.BubbleSource(anchor, particlesPerSecond, factory.stateCount - 1, factory); }); //% fixedInstance whenUsed block="star field" export const starField = new ScreenEffect(2, 5, 5000, function (anchor: particles.ParticleAnchor, particlesPerSecond: number) { const factory = new particles.StarFactory([0x1, 0x3, 0x5, 0x9, 0xC]); return new particles.ParticleSource(anchor, particlesPerSecond, factory); }); //% fixedInstance whenUsed block="clouds" export const clouds = new ScreenEffect(.5, 1.5, 5000, function (anchor: particles.ParticleAnchor, particlesPerSecond: number) { const factory = new particles.CloudFactory(); const source = new particles.ParticleSource(anchor, particlesPerSecond, factory); // render behind tile map source.z = -2; return source; }); //% fixedInstance whenUsed block="none" export const none = new ScreenEffect(0, 0, 0, function (anchor: particles.ParticleAnchor, particlesPerSecond: number) { class NullParticleSource extends particles.ParticleSource { constructor() { super(null, 0); this._prune(); } __draw(camera: scene.Camera) {} _update(dt: number) {} // remove self at next opportunity _prune() { const scene = game.currentScene(); if (!scene) return; scene.allSprites.removeElement(this); const sources = scene.particleSources; if (sources && sources.length) sources.removeElement(this); } destroy() { this._prune(); } clear() { this.head = undefined; } } const source = new NullParticleSource(); return source; }); }