UNPKG

flashlib

Version:

Build items from Animate CC (.fla) library using PIXI.js

481 lines (413 loc) 16.5 kB
import {Container, Ticker, UPDATE_PRIORITY} from 'pixi.js' import FlashLib from './FlashLib' import DisplayProperties from './DisplayProperties' export default class MovieClip extends Container { constructor($data, $displayItemData) { super(); this.libData = $data; //this.displayData = this.libData.displayData; this.displayData = $displayItemData; this.libName = this.libData.libraryName; this.timelineData = this.libData.timeline; this.layersData = this.timelineData.layers/*.concat().reverse()*/; this.currentFrameIndex = -1; this.currentFrameName = ''; this.animateParams = null; this.layers = []; this.layerMask = null; this.elements = {}; this.layersData.forEach((layerData) => { let layer = { name: layerData.name, type: layerData.layerType, parent: layerData.parentLayer, elements: [] }; this.layers.push(layer); }); this.startFrameTime = null; this.isPlaying = false; this.goToFrame(1); DisplayProperties.setDisplayItemProperties(this, this.displayData); DisplayProperties.setBlendMode(this); } /** * Вызывается при смене кадра перед очисткой кадра и созданием новых элементов */ exitFrame() { } getElement(name) { return this.elements[name]; } /** * Получить детей по имени в библиотеке * Паботает только с элементами этого расширения * @param {string} $name имя элемента из библиотеки * @returns {array} массив объектов имя в бибилиотеке совпадает с заданым */ getChildrenByLibName($name) { return this.children.filter(function (child) { return (child.hasOwnProperty('libData') && child.libData.hasOwnProperty('name') && child.libData.name === $name); }) } /** * Переход к следующему кадру клипа * @param {boolean} $loop если дошли до последнего кадра переходить ли на первыц */ goToNextFrame($loop) { let nextIndex = this._getNextFrameIndex($loop, false, 1); this.goToFrame(nextIndex); } /** * Расчет индекса следующего кадра * @param $loop * @param $reverse * @param $delta * @returns {*} * @private */ _getNextFrameIndex($loop, $reverse, $delta) { let nextIndex = 0; let offset = 0; if ($reverse) { nextIndex = this.currentFrameIndex - $delta % this.timelineData.frameCount; offset = nextIndex; if (offset < 1) { nextIndex = $loop ? this.timelineData.frameCount + offset : 1; } } else { nextIndex = this.currentFrameIndex + $delta % this.timelineData.frameCount; offset = nextIndex - this.timelineData.frameCount; if (offset > 0) { nextIndex = $loop ? offset : this.currentFrameIndex; } } return nextIndex; } /** * Перейти к предыдущему кадру * @param {boolean} $loop если дошли до первого кадра переходить ли на последний */ goToPreviousFrame($loop) { let nextIndex = this._getNextFrameIndex($loop, true, 1); this.goToFrame(nextIndex); } /** * Перейти на конкретный кадр * @param {number | string} $frameId номер или имя кадра на который нужно перейти * @param {boolean} $force перерисовать кадр даже если текущий кадр такой же */ goToFrame($frameId, $force = false) { if (typeof $frameId === 'string') { $frameId = this.findFrameIndexByName($frameId); } if ($frameId === this.currentFrameIndex && this.isPlaying && !this.animateParams.loop) { this.stop(); this.emit('animationComplete'); } if ($frameId === this.currentFrameIndex && !$force) { //console.log(`MovieClip ${this.name} (${this.timelineData.name}) now on frame ${$frameId}`); return; } if ($frameId > this.timelineData.frameCount || $frameId < 1) { console.log(`MovieClip ${this.name} (${this.timelineData.name}) does not have a frame ${$frameId}`); return; } this.exitFrame(); this.constructFrame($frameId); } /** * Получить номер кадра по имени * @param {string} $name имя кадра * @returns {number} номер кадра или -1 если кадр не найден */ findFrameIndexByName($name) { let index = -1; /*for (let i = 0; i < this.timelineData.frameCount; i++) { let frameData = this.timelineData.frames[i]; index = frameData.findIndex((frameData1) => { return frameData1.name === $name; }); if (index !== -1) { return i + 1; } }*/ return index; } //TODO: Написать правильноые расчеты смены кадров /** * * @param $delta */ animate($delta) { let frameDuration = 1000 / this.animateParams.fps; let currentTime = Date.now(); let deltaTime = currentTime - this.startFrameTime; if (deltaTime < frameDuration) { return; } let skipFramesCount = Math.ceil(deltaTime / frameDuration); let nextFrameIndex = this._getNextFrameIndex(this.animateParams.loop, this.animateParams.revers, skipFramesCount); this.goToFrame(nextFrameIndex); this.startFrameTime = currentTime - (deltaTime % frameDuration); } /** * * @param {boolean} $loop зациклена ли анимация * @param {boolean} $revers проигрывание в обратную сторону * @param {number} $fps какой частотой обновления кадров нужно проиграть */ play($loop, $revers, $fps) { if (this.isPlaying) { return; } this.animateParams = {loop: $loop, revers: $revers, fps: $fps || 24}; this.startFrameTime = Date.now(); Ticker.shared.add(this.animate, this, UPDATE_PRIORITY.HIGH); this.isPlaying = true; } /** * Перейти на кадр и запустить проигрывание мувиклипа * @param {number | string} $frameId с какого кадра начать проигрывание * @param {boolean} $loop зациклена ли анимация * @param {boolean} $revers проигрывание в обратную сторону * @param {number} $fps с какой частотой обновления кадров нужно проиграть */ goToAndPlay($frameId, $loop, $revers, $fps) { this.stop(); this.goToFrame($frameId); this.play($loop, $revers, $fps); } /** * Остановить проишрывание мувиклипа */ stop() { if (!this.isPlaying) { return; } this.animateParams = null; Ticker.shared.remove(this.animate, this); this.isPlaying = false; } /** * Создать элемениы кадра * @param {number} $frameId номер кадра который нужно создать */ constructFrame($frameId) { let startAddPosition = 0; this.layersData.forEach((currentLayerData, layerIndex) => { /*this._buildLayer(currentLayerData, layerIndex, $frameId, startAddPosition);*/ if (!currentLayerData.frames[$frameId - 1]) { this._removeElements(layerIndex); return; } let currentFrameData = currentLayerData.frames[$frameId - 1]; currentFrameData = currentLayerData.frames[currentFrameData.startFrame]; let prevFrameData = currentLayerData.frames[this.currentFrameIndex - 1]; startAddPosition += currentFrameData.elements.length; if (prevFrameData && $frameId >= prevFrameData.startFrame + 1 && $frameId <= prevFrameData.startFrame + prevFrameData.duration) { return; } this._removeElements(layerIndex); let newAdded = this._addNewChild(currentFrameData, startAddPosition, $frameId); this.layers[layerIndex].elements = newAdded; }); this._setMaskLayer(); this.currentFrameIndex = $frameId; } _buildLayer($layerData, $layerIndex, $frameId, $startAddPosition) { if (!$layerData.frames[$frameId - 1]) { this._removeElements($layerIndex); return; } let currentFrameData = $layerData.frames[$frameId - 1]; currentFrameData = $layerData.frames[currentFrameData.startFrame]; let prevFrameData = $layerData.frames[this.currentFrameIndex - 1]; $startAddPosition += $layerData.elements.length; if (prevFrameData && $frameId >= prevFrameData.startFrame + 1 && $frameId <= prevFrameData.startFrame + prevFrameData.duration) { return; } let newAdded = this._addNewChild(currentFrameData, $startAddPosition, $frameId); this._removeElements($layerIndex); this.layers[$layerIndex].elements = newAdded; } /** * Добавление слоя с маской к слоям * @private */ _setMaskLayer() { this.layers.forEach((layer) => { if (layer.parent) { let parentLayer = this.layers.find((sLayer) => { return sLayer.name === layer.parent; }); if (parentLayer && parentLayer.type === 'mask') { layer.elements.forEach((lElement) => { lElement.layerMask = parentLayer; }) } } }); } /** * Добавление чайлов для нового кадра * @param {object} $currentFrameData данные чайлов для текущего кадра * @param {number} $startAddPosition стартовая позиция добавления чайлдов * @param {number} $frameId номер кадра * @returns {[]} * @private */ _addNewChild($currentFrameData, $startAddPosition, $frameId) { let newAdded = []; $currentFrameData.elements.forEach((elementData, index) => { let displayItem = FlashLib.createDisplayItemFromData(elementData, this.libName); if (this.blendMode !== undefined) { displayItem.blendMode = this.blendMode; } this.addChildAt(displayItem, $startAddPosition - $currentFrameData.elements.length + index); newAdded.push(displayItem); if (displayItem.name && displayItem.name !== '') { this.elements[displayItem.name] = displayItem; } this.currentFrameName = $currentFrameData.name; this.evalScript($currentFrameData.actionScript, $frameId); }); return newAdded; } /** * Удаление элементов кадра с слоя * @param $layerIndex индекс слоя * @private */ _removeElements($layerIndex) { this.layers[$layerIndex].elements.forEach((elem) => { if (elem.name && elem.name !== '' && this.elements.hasOwnProperty(elem.name)) { delete this.elements[elem.name]; } if (!elem._destroyed) { elem.destroy({children: true}); } }); //this.layers[$layerIndex].elements = []; } /** * Выполнить скрипт с текущим контекстом * @param {string} $script текст скрипта который нужно выполнить * @param {number | string} $frameId номер или имя кадра */ evalScript($script, $frameId) { /*if ($script && $script !== '') { try { window.eval($script); } catch (error) { console.log(`Can't eval script on "${this.libData.name}" in ${$frameId} frame`); } }*/ } resetBlendMode() { DisplayProperties.setBlendMode(this); } destroy(options) { this.stop(); super.destroy(options); } render(renderer) { if (!this.visible || this.worldAlpha <= 0 || !this.renderable) { return; } if (this._mask || (this.filters && this.filters.length) || this.layerMask) { this.renderAdvanced(renderer); } else { this._render(renderer); for (let i = 0, j = this.children.length; i < j; ++i) { this.children[i].render(renderer); } } } renderAdvanced(renderer) { renderer.batch.flush(); const filters = this.filters; const mask = this._mask; const layerMask = this.layerMask ? this.layerMask.elements.length > 0 ? this.layerMask.elements : null : null; if (filters) { if (!this._enabledFilters) { this._enabledFilters = []; } this._enabledFilters.length = 0; for (let i = 0; i < filters.length; i++) { if (filters[i].enabled) { this._enabledFilters.push(filters[i]); } } if (this._enabledFilters.length) { renderer.filter.push(this, this._enabledFilters); } } if (mask) { renderer.mask.push(this, this._mask); } if (layerMask) { /*if(!this.tempCont) { this.tempCont = {}; this.tempCont.render = (renderer) => { for (let i = 0, j = this.layerMask.elements.length; i < j; ++i){ this.layerMask.elements[i].renderable = true; this.layerMask.elements[i].render(renderer); this.layerMask.elements[i].renderable = false; } } }*/ //renderer.mask.push(this, this.tempCont); layerMask[0].renderable = false; renderer.mask.push(this, layerMask[0]); } this._render(renderer); for (let i = 0, j = this.children.length; i < j; i++) { this.children[i].render(renderer); } renderer.batch.flush(); if (layerMask) { //renderer.mask.pop(this, this.tempCont); renderer.mask.pop(this, layerMask[0]); } if (mask) { renderer.mask.pop(this, this._mask); } if (filters && this._enabledFilters && this._enabledFilters.length) { renderer.filter.pop(); } } get blendMode() { return this._blendMode; } set blendMode(value) { if (value === undefined) { return; } this._blendMode = value; this.children.forEach((child)=>{ child.blendMode = this.blendMode; if (child.resetBlendMode) { child.resetBlendMode(); } }) } get useTransformPoint() { return this._useTransformPoint || false; } set useTransformPoint(value) { if (this._useTransformPoint === value) { return; } this._useTransformPoint = value; if (this._useTransformPoint) { this.pivot.set(this.tpX, this.tpY); this.x += this.tpX; this.y += this.tpY; } else { this.pivot.set(0, 0); this.x -= this.tpX; this.y -= this.tpY; } } }