UNPKG

pixi-sprite-utilities

Version:

A bunch of useful functions for creating Pixi.js sprites and making them easier to work with.

1,240 lines (1,061 loc) 35.3 kB
export default class SpriteUtilities { constructor (renderingEngine = PIXI) { if (renderingEngine === undefined) throw new Error('Please supply a reference to PIXI in the SpriteUtilities constructor before using spriteUtilities.js') // Find out which rendering engine is being used (the default is Pixi) this.renderer = '' // If the `renderingEngine` is Pixi, set up Pixi object aliases if (renderingEngine.particles.ParticleContainer && renderingEngine.Sprite) { this.renderer = 'pixi' this.Container = renderingEngine.Container this.ParticleContainer = renderingEngine.particles.ParticleContainer this.TextureCache = renderingEngine.utils.TextureCache this.Texture = renderingEngine.Texture this.Rectangle = renderingEngine.Rectangle this.AnimatedSprite = renderingEngine.extras.AnimatedSprite this.BitmapText = renderingEngine.extras.BitmapText this.Sprite = renderingEngine.Sprite this.TilingSprite = renderingEngine.extras.TilingSprite this.Graphics = renderingEngine.Graphics this.Text = renderingEngine.Text // An array to store all the shaking sprites this.shakingSprites = [] } } update () { if (this.shakingSprites.length > 0) { for (let i = this.shakingSprites.length - 1; i >= 0; i--) { const shakingSprite = this.shakingSprites[i] if (shakingSprite.updateShake) shakingSprite.updateShake() } } } sprite (source, x = 0, y = 0, tiling = false, width, height) { let o, texture // Create a sprite if the `source` is a string if (typeof source === 'string') { // Access the texture in the cache if it's there if (this.TextureCache[source]) { texture = this.TextureCache[source] } // If it's not is the cache, load it from the source file else { texture = this.Texture.fromImage(source) } // If the texture was created, make the o if (texture) { // If `tiling` is `false`, make a regular `Sprite` if (!tiling) { o = new this.Sprite(texture) } // If `tiling` is `true` make a `TilingSprite` else { o = new this.TilingSprite(texture, width, height) } } // But if the source still can't be found, alert the user else { throw new Error(`${source} cannot be found`) } } // Create a o if the `source` is a texture else if (source instanceof this.Texture) { if (!tiling) { o = new this.Sprite(source) } else { o = new this.TilingSprite(source, width, height) } } // Create a `AnimatedSprite` o if the `source` is an array else if (source instanceof Array) { // Is it an array of frame ids or textures? if (typeof source[0] === 'string') { // They're strings, but are they pre-existing texture or // paths to image files? // Check to see if the first element matches a texture in the // cache if (this.TextureCache[source[0]]) { // It does, so it's an array of frame ids o = this.AnimatedSprite.fromFrames(source) } else { // It's not already in the cache, so let's load it o = this.AnimatedSprite.fromImages(source) } } // If the `source` isn't an array of strings, check whether // it's an array of textures else if (source[0] instanceof this.Texture) { // Yes, it's an array of textures. // Use them to make a AnimatedSprite o o = new this.AnimatedSprite(source) } } // If the sprite was successfully created, intialize it if (o) { // Position the sprite o.x = x o.y = y // Set optional width and height if (width) o.width = width if (height) o.height = height // If the sprite is a AnimatedSprite, add a state player so that // it's easier to control if (o instanceof this.AnimatedSprite) this.addStatePlayer(o) // Assign the sprite return o } } addStatePlayer (sprite) { let frameCounter = 0 let numberOfFrames = 0 let startFrame = 0 let endFrame = 0 let timerInterval // The `show` function (to display static states) function show (frameNumber) { // Reset any possible previous animations reset() // Find the new state on the sprite sprite.gotoAndStop(frameNumber) } // The `stop` function stops the animation at the current frame function stopAnimation () { reset() sprite.gotoAndStop(sprite.currentFrame) } // The `playSequence` function, to play a sequence of frames function playAnimation (sequenceArray) { // Reset any possible previous animations reset() // Figure out how many frames there are in the range if (!sequenceArray) { startFrame = 0 endFrame = sprite.totalFrames - 1 } else { startFrame = sequenceArray[0] endFrame = sequenceArray[1] } // Calculate the number of frames numberOfFrames = endFrame - startFrame // Compensate for two edge cases: // 1. If the `startFrame` happens to be `0` /* if (startFrame === 0) { numberOfFrames += 1; frameCounter += 1; } */ // 2. If only a two-frame sequence was provided /* if(numberOfFrames === 1) { numberOfFrames = 2; frameCounter += 1; } */ // Calculate the frame rate. Set the default fps to 12 if (!sprite.fps) sprite.fps = 12 const frameRate = 1000 / sprite.fps // Set the sprite to the starting frame sprite.gotoAndStop(startFrame) // Set the `frameCounter` to the first frame frameCounter = 1 // If the state isn't already `playing`, start it if (!sprite.animating) { timerInterval = setInterval(advanceFrame.bind(this), frameRate) sprite.animating = true } } // `advanceFrame` is called by `setInterval` to display the next frame // in the sequence based on the `frameRate`. When the frame sequence // reaches the end, it will either stop or loop function advanceFrame () { // Advance the frame if `frameCounter` is less than // the state's total frames if (frameCounter < numberOfFrames + 1) { // Advance the frame sprite.gotoAndStop(sprite.currentFrame + 1) // Update the frame counter frameCounter += 1 // If we've reached the last frame and `loop` // is `true`, then start from the first frame again } else if (sprite.loop) { sprite.gotoAndStop(startFrame) frameCounter = 1 } } function reset () { // Reset `sprite.playing` to `false`, set the `frameCounter` to 0, //and clear the `timerInterval` if (timerInterval !== undefined && sprite.animating === true) { sprite.animating = false frameCounter = 0 startFrame = 0 endFrame = 0 numberOfFrames = 0 clearInterval(timerInterval) } } // Add the `show`, `play`, `stop`, and `playSequence` methods to the sprite sprite.show = show sprite.stopAnimation = stopAnimation sprite.playAnimation = playAnimation } // `tilingSpirte` lets you quickly create Pixi tiling sprites tilingSprite (source, width, height, x, y) { if (width === undefined) { throw new Error('Please define a width as your second argument for the tiling sprite') } if (height === undefined) { throw new Error('Please define a height as your third argument for the tiling sprite') } const o = this.sprite(source, x, y, true, width, height) // Add `tileX`, `tileY`, `tileScaleX` and `tileScaleY` properties Object.defineProperties(o, { tileX: { get () { return o.tilePosition.x }, set (value) { o.tilePosition.x = value }, enumerable: true, configurable: true }, tileY: { get () { return o.tilePosition.y }, set (value) { o.tilePosition.y = value }, enumerable: true, configurable: true }, tileScaleX: { get () { return o.tileScale.x }, set (value) { o.tileScale.x = value }, enumerable: true, configurable: true }, tileScaleY: { get () { return o.tileScale.y }, set (value) { o.tileScale.y = value }, enumerable: true, configurable: true } }) return o } filmstrip ( texture, frameWidth, frameHeight, spacing = 0 ) { // An array to store the x/y positions of the frames const positions = [] // Find the width and height of the texture let textureWidth = this.TextureCache[texture].width let textureHeight = this.TextureCache[texture].height // Find out how many columns and rows there are let columns = textureWidth / frameWidth let rows = textureHeight / frameHeight // Find the total number of frames const numberOfFrames = columns * rows for (let i = 0; i < numberOfFrames; i++) { // Find the correct row and column for each frame // and figure out its x and y position let x = (i % columns) * frameWidth let y = Math.floor(i / columns) * frameHeight // Compensate for any optional spacing (padding) around the tiles if // there is any. This bit of code accumlates the spacing offsets from the // left side of the tileset and adds them to the current tile's position if (spacing > 0) { x += spacing + (spacing * i % columns) y += spacing + (spacing * Math.floor(i / columns)) } // Add the x and y value of each frame to the `positions` array positions.push([x, y]) } // Return the frames return this.frames(texture, positions, frameWidth, frameHeight) } // Make a texture from a frame in another texture or image frame (source, x, y, width, height) { let texture, imageFrame // If the source is a string, it's either a texture in the // cache or an image file if (typeof source === 'string') { if (this.TextureCache[source]) { texture = new this.Texture(this.TextureCache[source]) } } // If the `source` is a texture, use it else if (source instanceof this.Texture) { texture = new this.Texture(source) } if (!texture) { throw new Error(`Please load the ${source} texture into the cache.`) } else { // Make a rectangle the size of the sub-image imageFrame = new this.Rectangle(x, y, width, height) texture.frame = imageFrame return texture } } // Make an array of textures from a 2D array of frame x and y coordinates in // texture frames (source, coordinates, frameWidth, frameHeight) { let baseTexture, textures // If the source is a string, it's either a texture in the // cache or an image file if (typeof source === 'string') { if (this.TextureCache[source]) { baseTexture = new this.Texture(this.TextureCache[source]) } } // If the `source` is a texture, use it else if (source instanceof this.Texture) { baseTexture = new this.Texture(source) } if (!baseTexture) { throw new Error(`Please load the ${source} texture into the cache.`) } else { const textures = coordinates.map((position) => { let x = position[0] let y = position[1] const imageFrame = new this.Rectangle(x, y, frameWidth, frameHeight) const frameTexture = new this.Texture(baseTexture) frameTexture.frame = imageFrame return frameTexture }) return textures } } frameSeries (startNumber = 0, endNumber = 1, baseName = '', extension = '') { // Create an array to store the frame names const frames = [] for (let i = startNumber; i < endNumber + 1; i++) { const frame = this.TextureCache[`${baseName + i + extension}`] frames.push(frame) } return frames } /* Text creation */ // The`text` method is a quick way to create a Pixi Text sprite text (content = 'message', font = '16px sans', fillStyle = 'red', x = 0, y = 0) { // Create a Pixi Sprite object const message = new this.Text(content, { font, fill: fillStyle }) message.x = x message.y = y // Add a `_text` property with a getter/setter message._content = content Object.defineProperty(message, 'content', { get () { return this._content }, set (value) { this._content = value this.text = value }, enumerable: true, configurable: true }) // Return the text object return message } // The`bitmapText` method lets you create bitmap text bitmapText (content = 'message', font, align, tint, x = 0, y = 0) { // Create a Pixi Sprite object const message = new this.BitmapText(content, { font, align, tint }) message.x = x message.y = y // Add a `_text` property with a getter/setter message._content = content Object.defineProperty(message, 'content', { get () { return this._content }, set (value) { this._content = value this.text = value }, enumerable: true, configurable: true }) // Return the text object return message } /* Shapes and lines */ // Rectangle rectangle ( width = 32, height = 32, fillStyle = 0xFF3300, strokeStyle = 0x0033CC, lineWidth = 0, x = 0, y = 0 ) { const o = new this.Graphics() o._sprite = undefined o._width = width o._height = height o._fillStyle = this.color(fillStyle) o._strokeStyle = this.color(strokeStyle) o._lineWidth = lineWidth // Draw the rectangle const draw = (width, height, fillStyle, strokeStyle, lineWidth) => { o.clear() o.beginFill(fillStyle) if (lineWidth > 0) { o.lineStyle(lineWidth, strokeStyle, 1) } o.drawRect(0, 0, width, height) o.endFill() } // Draw the line and capture the sprite that the `draw` function // returns draw(o._width, o._height, o._fillStyle, o._strokeStyle, o._lineWidth) // Generate a texture from the rectangle const texture = o.generateTexture() // Use the texture to create a sprite const sprite = new this.Sprite(texture) // Position the sprite sprite.x = x sprite.y = y // Add getters and setters to the sprite const self = this Object.defineProperties(sprite, { fillStyle: { get () { return o._fillStyle }, set (value) { o._fillStyle = self.color(value) // Draw the new rectangle draw(o._width, o._height, o._fillStyle, o._strokeStyle, o._lineWidth, o._x, o._y) // Generate a new texture and set it as the sprite's texture const texture = o.generateTexture() o._sprite.texture = texture }, enumerable: true, configurable: true }, strokeStyle: { get () { return o._strokeStyle }, set (value) { o._strokeStyle = self.color(value) // Draw the new rectangle draw(o._width, o._height, o._fillStyle, o._strokeStyle, o._lineWidth, o._x, o._y) // Generate a new texture and set it as the sprite's texture const texture = o.generateTexture() o._sprite.texture = texture }, enumerable: true, configurable: true }, lineWidth: { get () { return o._lineWidth }, set (value) { o._lineWidth = value // Draw the new rectangle draw(o._width, o._height, o._fillStyle, o._strokeStyle, o._lineWidth, o._x, o._y) // Generate a new texture and set it as the sprite's texture const texture = o.generateTexture() o._sprite.texture = texture }, enumerable: true, configurable: true } }) // Get a local reference to the sprite so that we can // change the rectangle properties later using the getters/setters o._sprite = sprite // Return the sprite return sprite } // Circle circle ( diameter = 32, fillStyle = 0xFF3300, strokeStyle = 0x0033CC, lineWidth = 0, x = 0, y = 0 ) { const o = new this.Graphics() o._diameter = diameter o._fillStyle = this.color(fillStyle) o._strokeStyle = this.color(strokeStyle) o._lineWidth = lineWidth // Draw the circle const draw = (diameter, fillStyle, strokeStyle, lineWidth) => { o.clear() o.beginFill(fillStyle) if (lineWidth > 0) { o.lineStyle(lineWidth, strokeStyle, 1) } o.drawCircle(0, 0, diameter / 2) o.endFill() } // Draw the cirlce draw(o._diameter, o._fillStyle, o._strokeStyle, o._lineWidth) // Generate a texture from the rectangle const texture = o.generateTexture() // Use the texture to create a sprite const sprite = new this.Sprite(texture) // Position the sprite sprite.x = x sprite.y = y // Add getters and setters to the sprite const self = this Object.defineProperties(sprite, { fillStyle: { get () { return o._fillStyle }, set (value) { o._fillStyle = self.color(value) // Draw the cirlce draw(o._diameter, o._fillStyle, o._strokeStyle, o._lineWidth) // Generate a new texture and set it as the sprite's texture const texture = o.generateTexture() o._sprite.texture = texture }, enumerable: true, configurable: true }, strokeStyle: { get () { return o._strokeStyle }, set (value) { o._strokeStyle = self.color(value) // Draw the cirlce draw(o._diameter, o._fillStyle, o._strokeStyle, o._lineWidth) // Generate a new texture and set it as the sprite's texture const texture = o.generateTexture() o._sprite.texture = texture }, enumerable: true, configurable: true }, diameter: { get () { return o._diameter }, set (value) { o._lineWidth = 10 // Draw the cirlce draw(o._diameter, o._fillStyle, o._strokeStyle, o._lineWidth) // Generate a new texture and set it as the sprite's texture const texture = o.generateTexture() o._sprite.texture = texture }, enumerable: true, configurable: true }, radius: { get () { return o._diameter / 2 }, set (value) { // Draw the cirlce draw(value * 2, o._fillStyle, o._strokeStyle, o._lineWidth) // Generate a new texture and set it as the sprite's texture const texture = o.generateTexture() o._sprite.texture = texture }, enumerable: true, configurable: true } }) // Get a local reference to the sprite so that we can // change the circle properties later using the getters/setters o._sprite = sprite // Return the sprite return sprite } // Line line ( strokeStyle = 0x000000, lineWidth = 1, ax = 0, ay = 0, bx = 32, by = 32 ) { // Create the line object const o = new this.Graphics() // Private properties o._strokeStyle = this.color(strokeStyle) o._width = lineWidth o._ax = ax o._ay = ay o._bx = bx o._by = by // A helper function that draws the line const draw = (strokeStyle, lineWidth, ax, ay, bx, by) => { o.clear() o.lineStyle(lineWidth, strokeStyle, 1) o.moveTo(ax, ay) o.lineTo(bx, by) } // Draw the line draw(o._strokeStyle, o._width, o._ax, o._ay, o._bx, o._by) // Define getters and setters that redefine the line's start and // end points and re-draws it if they change const self = this Object.defineProperties(o, { ax: { get () { return o._ax }, set (value) { o._ax = value draw(o._strokeStyle, o._width, o._ax, o._ay, o._bx, o._by) }, enumerable: true, configurable: true }, ay: { get () { return o._ay }, set (value) { o._ay = value draw(o._strokeStyle, o._width, o._ax, o._ay, o._bx, o._by) }, enumerable: true, configurable: true }, bx: { get () { return o._bx }, set (value) { o._bx = value draw(o._strokeStyle, o._width, o._ax, o._ay, o._bx, o._by) }, enumerable: true, configurable: true }, by: { get () { return o._by }, set (value) { o._by = value draw(o._strokeStyle, o._width, o._ax, o._ay, o._bx, o._by) }, enumerable: true, configurable: true }, strokeStyle: { get () { return o._strokeStyle }, set (value) { o._strokeStyle = self.color(value) // Draw the line draw(o._strokeStyle, o._width, o._ax, o._ay, o._bx, o._by) }, enumerable: true, configurable: true }, width: { get () { return o._width }, set (value) { o._width = value // Draw the line draw(o._strokeStyle, o._width, o._ax, o._ay, o._bx, o._by) }, enumerable: true, configurable: true } }) // Return the line return o } /* Compound sprites */ // Use `grid` to create a grid of sprites grid ( columns = 0, rows = 0, cellWidth = 32, cellHeight = 32, centerCell = false, xOffset = 0, yOffset = 0, makeSprite = undefined, extra = undefined ) { // Create an empty group called `container`. This `container` // group is what the function returns back to the main program. // All the sprites in the grid cells will be added // as children to this container const container = new this.Container() // The `create` method plots the grid const createGrid = () => { // Figure out the number of cells in the grid const length = columns * rows // Create a sprite for each cell for (let i = 0; i < length; i++) { // Figure out the sprite's x/y placement in the grid let x = (i % columns) * cellWidth let y = Math.floor(i / columns) * cellHeight // Use the `makeSprite` function supplied in the constructor // to make a sprite for the grid cell const sprite = makeSprite() // Add the sprite to the `container` container.addChild(sprite) // Should the sprite be centered in the cell? // No, it shouldn't be centered if (!centerCell) { sprite.x = x + xOffset sprite.y = y + yOffset } // Yes, it should be centered else { sprite.x = x + (cellWidth / 2) - (sprite.width / 2) + xOffset sprite.y = y + (cellHeight / 2) - (sprite.width / 2) + yOffset } // Run any optional extra code. This calls the // `extra` function supplied by the constructor if (extra) extra(sprite) } } // Run the `createGrid` method createGrid() // Return the `container` group back to the main program return container } // Use `shoot` to create bullet sprites shoot ( shooter, angle, x, y, container, bulletSpeed, bulletArray, bulletSprite ) { // Make a new sprite using the user-supplied `bulletSprite` function const bullet = bulletSprite() // Set the bullet's anchor point to its center bullet.anchor.set(0.5, 0.5) // Temporarily add the bullet to the shooter // so that we can position it relative to the // shooter's position shooter.addChild(bullet) bullet.x = x bullet.y = y // Find the bullet's global coordinates so that we can use // them to position the bullet on the new parent container let tempGx = bullet.getGlobalPosition().x let tempGy = bullet.getGlobalPosition().y // Add the bullet to the new parent container using // the new global coordinates container.addChild(bullet) bullet.x = tempGx bullet.y = tempGy // Set the bullet's velocity bullet.vx = Math.cos(angle) * bulletSpeed bullet.vy = Math.sin(angle) * bulletSpeed // Push the bullet into the `bulletArray` bulletArray.push(bullet) } /* grid ---- Helps you to automatically create a grid of sprites. `grid` returns a `group` sprite object that contains a sprite for every cell in the grid. You can define the rows and columns in the grid, whether or not the sprites should be centered inside each cell, or what their offset from the top left corner of each cell should be. Supply a function that returns the sprite that you want to make for each cell. You can supply an optional final function that runs any extra code after each sprite has been created. Here's the format for creating a grid: gridGroup = grid( //Set the grid's properties columns, rows, cellWidth, cellHeight, areSpirtesCentered?, xOffset, yOffset, //A function that returns a sprite () => g.circle(16, "blue"), //An optional final function that runs some extra code () => console.log("extra!") ); */ grid ( // eslint-disable-line columns = 0, rows = 0, cellWidth = 32, cellHeight = 32, centerCell = false, xOffset = 0, yOffset = 0, makeSprite = undefined, extra = undefined ) { // Create an empty group called `container`. This `container` // group is what the function returns back to the main program. // All the sprites in the grid cells will be added // as children to this container const container = this.group() // The `create` method plots the grid const createGrid = () => { // Figure out the number of cells in the grid const length = columns * rows // Create a sprite for each cell for (let i = 0; i < length; i++) { // Figure out the sprite's x/y placement in the grid let x = (i % columns) * cellWidth let y = Math.floor(i / columns) * cellHeight // Use the `makeSprite` function supplied in the constructor // to make a sprite for the grid cell const sprite = makeSprite() // Add the sprite to the `container` container.addChild(sprite) // Should the sprite be centered in the cell? // No, it shouldn't be centered if (!centerCell) { sprite.x = x + xOffset sprite.y = y + yOffset } // Yes, it should be centered else { sprite.x = x + (cellWidth / 2) - sprite.halfWidth + xOffset sprite.y = y + (cellHeight / 2) - sprite.halfHeight + yOffset } // Run any optional extra code. This calls the // `extra` function supplied by the constructor if (extra) extra(sprite) } } // Run the `createGrid` method createGrid() // Return the `container` group back to the main program return container } /* shake ----- Used to create a shaking effect, like a screen shake */ shake (sprite, magnitude = 16, angular = false) { // Get a reference to this current object so that // it's easy to maintain scope in the nested sub-functions const self = this // A counter to count the number of shakes let counter = 1 // The total number of shakes (there will be 1 shake per frame) const numberOfShakes = 10 // Capture the sprite's position and angle so you can // restore them after the shaking has finished let startX = sprite.x let startY = sprite.y let startAngle = sprite.rotation // Divide the magnitude into 10 units so that you can // reduce the amount of shake by 10 percent each frame const magnitudeUnit = magnitude / numberOfShakes // The `randomInt` helper function const randomInt = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min // Add the sprite to the `shakingSprites` array if it // isn't already there if (self.shakingSprites.indexOf(sprite) === -1) { self.shakingSprites.push(sprite) // Add an `updateShake` method to the sprite. // The `updateShake` method will be called each frame // in the game loop. The shake effect type can be either // up and down (x/y shaking) or angular (rotational shaking). sprite.updateShake = () => { if (angular) { angularShake() } else { upAndDownShake() } } } // The `upAndDownShake` function function upAndDownShake () { // Shake the sprite while the `counter` is less than // the `numberOfShakes` if (counter < numberOfShakes) { // Reset the sprite's position at the start of each shake sprite.x = startX sprite.y = startY // Reduce the magnitude magnitude -= magnitudeUnit // Randomly change the sprite's position sprite.x += randomInt(-magnitude, magnitude) sprite.y += randomInt(-magnitude, magnitude) // Add 1 to the counter counter += 1 } // When the shaking is finished, restore the sprite to its original // position and remove it from the `shakingSprites` array if (counter >= numberOfShakes) { sprite.x = startX sprite.y = startY self.shakingSprites.splice(self.shakingSprites.indexOf(sprite), 1) } } // The `angularShake` function // First set the initial tilt angle to the right (+1) let tiltAngle = 1 function angularShake () { if (counter < numberOfShakes) { // Reset the sprite's rotation sprite.rotation = startAngle // Reduce the magnitude magnitude -= magnitudeUnit // Rotate the sprite left or right, depending on the direction, // by an amount in radians that matches the magnitude sprite.rotation = magnitude * tiltAngle counter += 1 // Reverse the tilt angle so that the sprite is tilted // in the opposite direction for the next shake tiltAngle *= -1 } // When the shaking is finished, reset the sprite's angle and // remove it from the `shakingSprites` array if (counter >= numberOfShakes) { sprite.rotation = startAngle self.shakingSprites.splice(self.shakingSprites.indexOf(sprite), 1) } } } /* _getCenter ---------- A utility that finds the center point of the sprite. If it's anchor point is the sprite's top left corner, then the center is calculated from that point. If the anchor point has been shifted, then the anchor x/y point is used as the sprite's center */ _getCenter (o, dimension, axis) { if (o.anchor !== undefined) { if (o.anchor[axis] !== 0) { return 0 } return dimension / 2 } return dimension } /* Groups */ // Group sprites into a container group (...sprites) { const container = new this.Container() sprites.forEach((sprite) => { container.addChild(sprite) }) return container } // Use the `batch` method to create a ParticleContainer batch (size = 15000, options = { rotation: true, alpha: true, scale: true, uvs: true }) { const o = new this.ParticleContainer(size, options) return o } // `remove` is a global convenience method that will // remove any sprite, or an argument list of sprites, from its parent. remove (...sprites) { // Remove sprites that's aren't in an array if (!(sprites[0] instanceof Array)) { if (sprites.length > 1) { sprites.forEach((sprite) => { sprite.parent.removeChild(sprite) }) } else { sprites[0].parent.removeChild(sprites[0]) } } // Remove sprites in an array of sprites else { const spritesArray = sprites[0] if (spritesArray.length > 0) { for (let i = spritesArray.length - 1; i >= 0; i--) { const sprite = spritesArray[i] sprite.parent.removeChild(sprite) spritesArray.splice(spritesArray.indexOf(sprite), 1) } } } } /* Color conversion */ // From: http://stackoverflow.com/questions/1573053/javascript-function-to-convert-color-names-to-hex-codes // Utilities to convert HTML color string names to hexadecimal codes colorToRGBA (color) { // Returns the color as an array of [r, g, b, a] -- all range from 0 - 255 // color must be a valid canvas fillStyle. This will cover most anything // you'd want to use. // Examples: // colorToRGBA('red') # [255, 0, 0, 255] // colorToRGBA('#f00') # [255, 0, 0, 255] let cvs, ctx cvs = document.createElement('canvas') cvs.height = 1 cvs.width = 1 ctx = cvs.getContext('2d') ctx.fillStyle = color ctx.fillRect(0, 0, 1, 1) const data = ctx.getImageData(0, 0, 1, 1).data return data } byteToHex (num) { // Turns a number (0-255) into a 2-character hex number (00-ff) return (`0${num.toString(16)}`).slice(-2) } colorToHex (color) { // Convert any CSS color to a hex representation // Examples: // colorToHex('red') # '#ff0000' // colorToHex('rgb(255, 0, 0)') # '#ff0000' let rgba, hex rgba = this.colorToRGBA(color) hex = [0, 1, 2].map( idx => this.byteToHex(rgba[idx]) ).join('') return `0x${hex}` } // A function to find out if the user entered a number (a hex color // code) or a string (an HTML color string) color (value) { // Check if it's a number if (!isNaN(value)) { // Yes, it is a number, so just return it return value } // No it's not a number, so it must be a string return parseInt(this.colorToHex(value)) /* //Find out what kind of color string it is. //Let's first grab the first character of the string let firstCharacter = value.charAt(0); //If the first character is a "#" or a number, then //we know it must be a RGBA color if (firstCharacter === "#") { console.log("first character: " + value.charAt(0)) } */ /* //Find out if the first character in the string is a number if (!isNaN(parseInt(string.charAt(0)))) { //It's not, so convert it to a hex code return colorToHex(string); //The use input a number, so it must be a hex code. Just return it } else { return string; } */ } }