diff --git a/CHANGELOG.md b/CHANGELOG.md index 0974b447..1d108edf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Added +- Color now can parse RGB/A string using Color.fromRGBString('rgb(255, 255, 255)') or Color.fromRGBString('rgb(255, 255, 255, 1)') + ### Changed ### Deprecated diff --git a/src/engine/Drawing/Color.ts b/src/engine/Drawing/Color.ts index 6bb3938c..3731325c 100644 --- a/src/engine/Drawing/Color.ts +++ b/src/engine/Drawing/Color.ts @@ -65,6 +65,28 @@ export class Color { return new Color(r, g, b, a); } + /** + * Creates a new instance of Color from a rgb string + * + * @param string CSS color string of the form rgba(255, 255, 255, 1) or rgb(255, 255, 255) + */ + public static fromRGBString(string: string): Color { + const rgbaRegEx: RegExp = /^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)/i; + let match = null; + if ((match = string.match(rgbaRegEx))) { + const r = parseInt(match[1], 10); + const g = parseInt(match[2], 10); + const b = parseInt(match[3], 10); + let a = 1; + if (match[4]) { + a = parseFloat(match[4]); + } + return new Color(r, g, b, a); + } else { + throw new Error('Invalid rgb/a string: ' + string); + } + } + /** * Creates a new instance of Color from a hex string * diff --git a/src/spec/ColorSpec.ts b/src/spec/ColorSpec.ts index 0fb8d4f2..e31cda81 100644 --- a/src/spec/ColorSpec.ts +++ b/src/spec/ColorSpec.ts @@ -52,6 +52,22 @@ describe('A color', () => { expect(color.a).toBe(0); }); + it('can be parsed from rgb string', () => { + color = ex.Color.fromRGBString('rgb(100, 120, 140)'); + expect(color.r).toBe(100); + expect(color.g).toBe(120); + expect(color.b).toBe(140); + expect(color.a).toBe(1); + }); + + it('can be parsed from rgba string', () => { + color = ex.Color.fromRGBString('rgb(100, 120, 140, 0.5)'); + expect(color.r).toBe(100); + expect(color.g).toBe(120); + expect(color.b).toBe(140); + expect(color.a).toBe(0.5); + }); + it('can be parsed from hsl', () => { color = ex.Color.fromHSL(0, 0, 1.0); expect(color.r).toBe(255); diff --git a/src/stories/ParticleEmitter.stories.ts b/src/stories/ParticleEmitter.stories.ts new file mode 100644 index 00000000..99a1f6b1 --- /dev/null +++ b/src/stories/ParticleEmitter.stories.ts @@ -0,0 +1,63 @@ +import { withKnobs, number, select, boolean, color } from '@storybook/addon-knobs'; +import { ParticleEmitter, EmitterType, Color, Vector } from '../engine'; +import { withEngine, enumToKnobSelect } from './utils'; + +export default { + title: 'ParticleEmitter', + decorators: [withKnobs] +}; + +export const main = withEngine(async (game) => { + // Knobs + const width = number('width', 1, { range: true, min: 0, max: 100, step: 1 }); + const height = number('height', 1, { range: true, min: 0, max: 100, step: 1 }); + const emitterType = select('emitterType', enumToKnobSelect(EmitterType), EmitterType.Rectangle); + const radius = number('radius', 5, { range: true, min: 0, max: 1000, step: 1 }); + const minVel = number('minVel', 100, { range: true, min: 0, max: 1000, step: 1 }); + const maxVel = number('maxVel', 200, { range: true, min: 0, max: 1000, step: 1 }); + const minAngle = number('minAngle', 0, { range: true, min: 0, max: 6.2, step: 0.1 }); + const maxAngle = number('maxAngle', 6.2, { range: true, min: 0, max: 6.2, step: 0.1 }); + const isEmitting = boolean('isEmitting', true); + const emitRate = number('emitRate', 300, { range: true, min: 0, max: 1000, step: 1 }); + const opacity = number('opacity', 0.5, { range: true, min: 0, max: 1, step: 0.01 }); + const fadeFlag = boolean('fadeFlag', true); + const particleLife = number('particleLife (ms)', 1000, { range: true, min: 0, max: 5000, step: 10 }); + const minSize = number('minSize', 1, { range: true, min: 0, max: 200, step: 1 }); + const maxSize = number('maxSize', 10, { range: true, min: 0, max: 200, step: 1 }); + const startSize = number('startSize', 5, { range: true, min: 0, max: 200, step: 1 }); + const endSize = number('endSize', 10, { range: true, min: 0, max: 200, step: 1 }); + const accelX = number('Accel X', 0, { range: true, min: -2000, max: 2000, step: 1 }); + const accelY = number('Accel Y', 0, { range: true, min: -2000, max: 2000, step: 1 }); + const beginColor = color('beginColor', Color.Rose.toRGBA()); + const endColor = color('endColor', Color.Yellow.toRGBA()); + + // Default Scenario + game.backgroundColor = Color.Black; + + // Particle Emitter + const emitter = new ParticleEmitter(game.currentScene.camera.x, game.currentScene.camera.y); + emitter.width = width; + emitter.height = height; + emitter.emitterType = emitterType; + emitter.radius = radius; + emitter.minVel = minVel; + emitter.maxVel = maxVel; + emitter.minAngle = minAngle; + emitter.maxAngle = maxAngle; + emitter.isEmitting = isEmitting; + emitter.emitRate = emitRate; + emitter.opacity = opacity; + emitter.fadeFlag = fadeFlag; + emitter.particleLife = particleLife; + emitter.minSize = minSize; + emitter.maxSize = maxSize; + emitter.startSize = startSize; + emitter.endSize = endSize; + emitter.acceleration = new Vector(accelX, accelY); + emitter.beginColor = Color.fromRGBString(beginColor); + emitter.endColor = Color.fromRGBString(endColor); + emitter.focusAccel = 800; + game.add(emitter); + + await game.start(); +}); diff --git a/src/stories/utils.ts b/src/stories/utils.ts index ee26be5d..b227ccb7 100644 --- a/src/stories/utils.ts +++ b/src/stories/utils.ts @@ -4,6 +4,9 @@ interface HTMLCanvasElement { gameRef?: Engine; } +/** + * + */ function isCanvasElement(el: any): el is HTMLCanvasElement { return el && el.nodeName === 'CANVAS'; } @@ -47,7 +50,7 @@ export const withEngine = (storyFn: (game: Engine) => void) => { pointerScope: Input.PointerScope.Canvas }); - Logger.getInstance().info("Press 'd' for debug mode"); + Logger.getInstance().info('Press \'d\' for debug mode'); game.input.keyboard.on('down', (keyDown?: Input.KeyEvent) => { if (keyDown.key === Input.Keys.D) { @@ -63,3 +66,16 @@ export const withEngine = (storyFn: (game: Engine) => void) => { return canvas; }; }; + +/** + * Helper to generate Storybook Knob Select Options from a Enum Type + * @param e Enum + */ +export const enumToKnobSelect = (e: any): Record => { + return Object.keys(e) + .filter((k) => typeof e[k as any] === 'number') + .reduce((o: Record, el: string) => { + o[el] = e[el]; + return o; + }, {}); +};