UNPKG

@pixi-spine/base

Version:

Base of pixi-spine integration, common files for spine runtimes of different versions

635 lines (631 loc) 22.1 kB
'use strict'; var AttachmentType = require('./core/AttachmentType.js'); var TextureRegion = require('./core/TextureRegion.js'); var Utils = require('./core/Utils.js'); var core = require('@pixi/core'); var display = require('@pixi/display'); var sprite = require('@pixi/sprite'); var meshExtras = require('@pixi/mesh-extras'); var graphics = require('@pixi/graphics'); var settings = require('./settings.js'); const tempRgb = [0, 0, 0]; class SpineSprite extends sprite.Sprite { constructor() { super(...arguments); this.region = null; this.attachment = null; } } class SpineMesh extends meshExtras.SimpleMesh { constructor(texture, vertices, uvs, indices, drawMode) { super(texture, vertices, uvs, indices, drawMode); this.region = null; this.attachment = null; } } const _SpineBase = class extends display.Container { constructor(spineData) { super(); if (!spineData) { throw new Error("The spineData param is required."); } if (typeof spineData === "string") { throw new Error('spineData param cant be string. Please use spine.Spine.fromAtlas("YOUR_RESOURCE_NAME") from now on.'); } this.spineData = spineData; this.createSkeleton(spineData); this.slotContainers = []; this.tempClipContainers = []; for (let i = 0, n = this.skeleton.slots.length; i < n; i++) { const slot = this.skeleton.slots[i]; const attachment = slot.getAttachment(); const slotContainer = this.newContainer(); this.slotContainers.push(slotContainer); this.addChild(slotContainer); this.tempClipContainers.push(null); if (!attachment) { continue; } if (attachment.type === AttachmentType.AttachmentType.Region) { const spriteName = attachment.name; const sprite = this.createSprite(slot, attachment, spriteName); slot.currentSprite = sprite; slot.currentSpriteName = spriteName; slotContainer.addChild(sprite); } else if (attachment.type === AttachmentType.AttachmentType.Mesh) { const mesh = this.createMesh(slot, attachment); slot.currentMesh = mesh; slot.currentMeshId = attachment.id; slot.currentMeshName = attachment.name; slotContainer.addChild(mesh); } else if (attachment.type === AttachmentType.AttachmentType.Clipping) { this.createGraphics(slot, attachment); slotContainer.addChild(slot.clippingContainer); slotContainer.addChild(slot.currentGraphics); } } this.tintRgb = new Float32Array([1, 1, 1]); this.autoUpdate = true; this.visible = true; } get debug() { return this._debug; } set debug(value) { if (value == this._debug) { return; } this._debug?.unregisterSpine(this); value?.registerSpine(this); this._debug = value; } /** * If this flag is set to true, the spine animation will be automatically updated every * time the object id drawn. The down side of this approach is that the delta time is * automatically calculated and you could miss out on cool effects like slow motion, * pause, skip ahead and the sorts. Most of these effects can be achieved even with * autoUpdate enabled but are harder to achieve. * * @member {boolean} * @memberof spine.Spine# * @default true */ get autoUpdate() { return this._autoUpdate; } set autoUpdate(value) { if (value !== this._autoUpdate) { this._autoUpdate = value; this.updateTransform = value ? _SpineBase.prototype.autoUpdateTransform : display.Container.prototype.updateTransform; } } /** * The tint applied to the spine object. This is a hex value. A value of 0xFFFFFF will remove any tint effect. * * @member {number} * @memberof spine.Spine# * @default 0xFFFFFF */ get tint() { return core.utils.rgb2hex(this.tintRgb); } set tint(value) { this.tintRgb = core.utils.hex2rgb(value, this.tintRgb); } /** * Limit value for the update dt with Spine.globalDelayLimit * that can be overridden with localDelayLimit * @return {number} - Maximum processed dt value for the update */ get delayLimit() { const limit = typeof this.localDelayLimit !== "undefined" ? this.localDelayLimit : settings.settings.GLOBAL_DELAY_LIMIT; return limit || Number.MAX_VALUE; } /** * Update the spine skeleton and its animations by delta time (dt) * * @param dt {number} Delta time. Time by which the animation should be updated */ update(dt) { const delayLimit = this.delayLimit; if (dt > delayLimit) dt = delayLimit; this.state.update(dt); this.state.apply(this.skeleton); if (!this.skeleton) { return; } this.skeleton.updateWorldTransform(); const slots = this.skeleton.slots; const globalClr = this.color; let light = null; let dark = null; if (globalClr) { light = globalClr.light; dark = globalClr.dark; } else { light = this.tintRgb; } for (let i = 0, n = slots.length; i < n; i++) { const slot = slots[i]; const attachment = slot.getAttachment(); const slotContainer = this.slotContainers[i]; if (!attachment) { slotContainer.visible = false; continue; } let spriteColor = null; if (attachment.sequence) { attachment.sequence.apply(slot, attachment); } let region = attachment.region; const attColor = attachment.color; switch (attachment != null && attachment.type) { case AttachmentType.AttachmentType.Region: const transform = slotContainer.transform; transform.setFromMatrix(slot.bone.matrix); region = attachment.region; if (slot.currentMesh) { slot.currentMesh.visible = false; slot.currentMesh = null; slot.currentMeshId = void 0; slot.currentMeshName = void 0; } if (!region) { if (slot.currentSprite) { slot.currentSprite.renderable = false; } break; } if (!slot.currentSpriteName || slot.currentSpriteName !== attachment.name) { const spriteName = attachment.name; if (slot.currentSprite) { slot.currentSprite.visible = false; } slot.sprites = slot.sprites || {}; if (slot.sprites[spriteName] !== void 0) { slot.sprites[spriteName].visible = true; } else { const sprite = this.createSprite(slot, attachment, spriteName); slotContainer.addChild(sprite); } slot.currentSprite = slot.sprites[spriteName]; slot.currentSpriteName = spriteName; } slot.currentSprite.renderable = true; if (!slot.hackRegion) { this.setSpriteRegion(attachment, slot.currentSprite, region); } if (slot.currentSprite.color) { spriteColor = slot.currentSprite.color; } else { tempRgb[0] = light[0] * slot.color.r * attColor.r; tempRgb[1] = light[1] * slot.color.g * attColor.g; tempRgb[2] = light[2] * slot.color.b * attColor.b; slot.currentSprite.tint = core.utils.rgb2hex(tempRgb); } slot.currentSprite.blendMode = slot.blendMode; break; case AttachmentType.AttachmentType.Mesh: if (slot.currentSprite) { slot.currentSprite.visible = false; slot.currentSprite = null; slot.currentSpriteName = void 0; const transform2 = new core.Transform(); transform2._parentID = -1; transform2._worldID = slotContainer.transform._worldID; slotContainer.transform = transform2; } if (!region) { if (slot.currentMesh) { slot.currentMesh.renderable = false; } break; } const id = attachment.id; if (slot.currentMeshId === void 0 || slot.currentMeshId !== id) { const meshId = id; if (slot.currentMesh) { slot.currentMesh.visible = false; } slot.meshes = slot.meshes || {}; if (slot.meshes[meshId] !== void 0) { slot.meshes[meshId].visible = true; } else { const mesh = this.createMesh(slot, attachment); slotContainer.addChild(mesh); } slot.currentMesh = slot.meshes[meshId]; slot.currentMeshName = attachment.name; slot.currentMeshId = meshId; } slot.currentMesh.renderable = true; attachment.computeWorldVerticesOld(slot, slot.currentMesh.vertices); if (slot.currentMesh.color) { spriteColor = slot.currentMesh.color; } else { tempRgb[0] = light[0] * slot.color.r * attColor.r; tempRgb[1] = light[1] * slot.color.g * attColor.g; tempRgb[2] = light[2] * slot.color.b * attColor.b; slot.currentMesh.tint = core.utils.rgb2hex(tempRgb); } slot.currentMesh.blendMode = slot.blendMode; if (!slot.hackRegion) { this.setMeshRegion(attachment, slot.currentMesh, region); } break; case AttachmentType.AttachmentType.Clipping: if (!slot.currentGraphics) { this.createGraphics(slot, attachment); slotContainer.addChild(slot.clippingContainer); slotContainer.addChild(slot.currentGraphics); } this.updateGraphics(slot, attachment); slotContainer.alpha = 1; slotContainer.visible = true; continue; default: slotContainer.visible = false; continue; } slotContainer.visible = true; if (spriteColor) { let r0 = slot.color.r * attColor.r; let g0 = slot.color.g * attColor.g; let b0 = slot.color.b * attColor.b; spriteColor.setLight(light[0] * r0 + dark[0] * (1 - r0), light[1] * g0 + dark[1] * (1 - g0), light[2] * b0 + dark[2] * (1 - b0)); if (slot.darkColor) { r0 = slot.darkColor.r; g0 = slot.darkColor.g; b0 = slot.darkColor.b; } else { r0 = 0; g0 = 0; b0 = 0; } spriteColor.setDark(light[0] * r0 + dark[0] * (1 - r0), light[1] * g0 + dark[1] * (1 - g0), light[2] * b0 + dark[2] * (1 - b0)); } slotContainer.alpha = slot.color.a; } const drawOrder = this.skeleton.drawOrder; let clippingAttachment = null; let clippingContainer = null; for (let i = 0, n = drawOrder.length; i < n; i++) { const slot = slots[drawOrder[i].data.index]; const slotContainer = this.slotContainers[drawOrder[i].data.index]; if (!clippingContainer) { if (slotContainer.parent !== null && slotContainer.parent !== this) { slotContainer.parent.removeChild(slotContainer); slotContainer.parent = this; } } if (slot.currentGraphics && slot.getAttachment()) { clippingContainer = slot.clippingContainer; clippingAttachment = slot.getAttachment(); clippingContainer.children.length = 0; this.children[i] = slotContainer; if (clippingAttachment.endSlot === slot.data) { clippingAttachment.endSlot = null; } } else if (clippingContainer) { let c = this.tempClipContainers[i]; if (!c) { c = this.tempClipContainers[i] = this.newContainer(); c.visible = false; } this.children[i] = c; slotContainer.parent = null; clippingContainer.addChild(slotContainer); if (clippingAttachment.endSlot == slot.data) { clippingContainer.renderable = true; clippingContainer = null; clippingAttachment = null; } } else { this.children[i] = slotContainer; } } this._debug?.renderDebug(this); } setSpriteRegion(attachment, sprite, region) { if (sprite.attachment === attachment && sprite.region === region) { return; } sprite.region = region; sprite.attachment = attachment; sprite.texture = region.texture; sprite.rotation = attachment.rotation * Utils.MathUtils.degRad; sprite.position.x = attachment.x; sprite.position.y = attachment.y; sprite.alpha = attachment.color.a; if (!region.size) { sprite.scale.x = attachment.scaleX * attachment.width / region.originalWidth; sprite.scale.y = -attachment.scaleY * attachment.height / region.originalHeight; } else { sprite.scale.x = region.size.width / region.originalWidth; sprite.scale.y = -region.size.height / region.originalHeight; } } setMeshRegion(attachment, mesh, region) { if (mesh.attachment === attachment && mesh.region === region) { return; } mesh.region = region; mesh.attachment = attachment; mesh.texture = region.texture; region.texture.updateUvs(); mesh.uvBuffer.update(attachment.regionUVs); } /** * When autoupdate is set to yes this function is used as pixi's updateTransform function * * @private */ autoUpdateTransform() { if (settings.settings.GLOBAL_AUTO_UPDATE) { this.lastTime = this.lastTime || Date.now(); const timeDelta = (Date.now() - this.lastTime) * 1e-3; this.lastTime = Date.now(); this.update(timeDelta); } else { this.lastTime = 0; } display.Container.prototype.updateTransform.call(this); } /** * Create a new sprite to be used with core.RegionAttachment * * @param slot {spine.Slot} The slot to which the attachment is parented * @param attachment {spine.RegionAttachment} The attachment that the sprite will represent * @private */ createSprite(slot, attachment, defName) { let region = attachment.region; if (slot.hackAttachment === attachment) { region = slot.hackRegion; } const texture = region ? region.texture : null; const sprite = this.newSprite(texture); sprite.anchor.set(0.5); if (region) { this.setSpriteRegion(attachment, sprite, attachment.region); } slot.sprites = slot.sprites || {}; slot.sprites[defName] = sprite; return sprite; } /** * Creates a Strip from the spine data * @param slot {spine.Slot} The slot to which the attachment is parented * @param attachment {spine.RegionAttachment} The attachment that the sprite will represent * @private */ createMesh(slot, attachment) { if (!attachment.region && attachment.sequence) { attachment.sequence.apply(slot, attachment); } let region = attachment.region; if (slot.hackAttachment === attachment) { region = slot.hackRegion; slot.hackAttachment = null; slot.hackRegion = null; } const strip = this.newMesh( region ? region.texture : null, new Float32Array(attachment.regionUVs.length), attachment.regionUVs, new Uint16Array(attachment.triangles), core.DRAW_MODES.TRIANGLES ); if (typeof strip._canvasPadding !== "undefined") { strip._canvasPadding = 1.5; } strip.alpha = attachment.color.a; strip.region = attachment.region; if (region) { this.setMeshRegion(attachment, strip, region); } slot.meshes = slot.meshes || {}; slot.meshes[attachment.id] = strip; return strip; } // @ts-ignore createGraphics(slot, clip) { const graphics = this.newGraphics(); const poly = new core.Polygon([]); graphics.clear(); graphics.beginFill(16777215, 1); graphics.drawPolygon(poly); graphics.renderable = false; slot.currentGraphics = graphics; slot.clippingContainer = this.newContainer(); slot.clippingContainer.mask = slot.currentGraphics; return graphics; } updateGraphics(slot, clip) { const geom = slot.currentGraphics.geometry; const vertices = geom.graphicsData[0].shape.points; const n = clip.worldVerticesLength; vertices.length = n; clip.computeWorldVertices(slot, 0, n, vertices, 0, 2); geom.invalidate(); } /** * Changes texture in attachment in specific slot. * * PIXI runtime feature, it was made to satisfy our users. * * @param slotIndex {number} * @param [texture = null] {PIXI.Texture} If null, take default (original) texture * @param [size = null] {PIXI.Point} sometimes we need new size for region attachment, you can pass 'texture.orig' there * @returns {boolean} Success flag */ hackTextureBySlotIndex(slotIndex, texture = null, size = null) { const slot = this.skeleton.slots[slotIndex]; if (!slot) { return false; } const attachment = slot.getAttachment(); let region = attachment.region; if (texture) { region = new TextureRegion.TextureRegion(); region.texture = texture; region.size = size; slot.hackRegion = region; slot.hackAttachment = attachment; } else { slot.hackRegion = null; slot.hackAttachment = null; } if (slot.currentSprite) { this.setSpriteRegion(attachment, slot.currentSprite, region); } else if (slot.currentMesh) { this.setMeshRegion(attachment, slot.currentMesh, region); } return true; } /** * Changes texture in attachment in specific slot. * * PIXI runtime feature, it was made to satisfy our users. * * @param slotName {string} * @param [texture = null] {PIXI.Texture} If null, take default (original) texture * @param [size = null] {PIXI.Point} sometimes we need new size for region attachment, you can pass 'texture.orig' there * @returns {boolean} Success flag */ hackTextureBySlotName(slotName, texture = null, size = null) { const index = this.skeleton.findSlotIndex(slotName); if (index == -1) { return false; } return this.hackTextureBySlotIndex(index, texture, size); } /** * Changes texture of an attachment * * PIXI runtime feature, it was made to satisfy our users. * * @param slotName {string} * @param attachmentName {string} * @param [texture = null] {PIXI.Texture} If null, take default (original) texture * @param [size = null] {PIXI.Point} sometimes we need new size for region attachment, you can pass 'texture.orig' there * @returns {boolean} Success flag */ hackTextureAttachment(slotName, attachmentName, texture, size = null) { const slotIndex = this.skeleton.findSlotIndex(slotName); const attachment = this.skeleton.getAttachmentByName(slotName, attachmentName); attachment.region.texture = texture; const slot = this.skeleton.slots[slotIndex]; if (!slot) { return false; } const currentAttachment = slot.getAttachment(); if (attachmentName === currentAttachment.name) { let region = attachment.region; if (texture) { region = new TextureRegion.TextureRegion(); region.texture = texture; region.size = size; slot.hackRegion = region; slot.hackAttachment = currentAttachment; } else { slot.hackRegion = null; slot.hackAttachment = null; } if (slot.currentSprite && slot.currentSprite.region != region) { this.setSpriteRegion(currentAttachment, slot.currentSprite, region); slot.currentSprite.region = region; } else if (slot.currentMesh && slot.currentMesh.region != region) { this.setMeshRegion(currentAttachment, slot.currentMesh, region); } return true; } return false; } // those methods can be overriden to spawn different classes newContainer() { return new display.Container(); } newSprite(tex) { return new SpineSprite(tex); } newGraphics() { return new graphics.Graphics(); } newMesh(texture, vertices, uvs, indices, drawMode) { return new SpineMesh(texture, vertices, uvs, indices, drawMode); } transformHack() { return 1; } /** * Hack for pixi-display and pixi-lights. Every attachment name ending with a suffix will be added to different layer * @param nameSuffix * @param group * @param outGroup */ hackAttachmentGroups(nameSuffix, group, outGroup) { if (!nameSuffix) { return void 0; } const list_d = []; const list_n = []; for (let i = 0, len = this.skeleton.slots.length; i < len; i++) { const slot = this.skeleton.slots[i]; const name = slot.currentSpriteName || slot.currentMeshName || ""; const target = slot.currentSprite || slot.currentMesh; if (name.endsWith(nameSuffix)) { target.parentGroup = group; list_n.push(target); } else if (outGroup && target) { target.parentGroup = outGroup; list_d.push(target); } } return [list_d, list_n]; } destroy(options) { this.debug = null; for (let i = 0, n = this.skeleton.slots.length; i < n; i++) { const slot = this.skeleton.slots[i]; for (const name in slot.meshes) { slot.meshes[name].destroy(options); } slot.meshes = null; for (const name in slot.sprites) { slot.sprites[name].destroy(options); } slot.sprites = null; } for (let i = 0, n = this.slotContainers.length; i < n; i++) { this.slotContainers[i].destroy(options); } this.spineData = null; this.skeleton = null; this.slotContainers = null; this.stateData = null; this.state = null; this.tempClipContainers = null; super.destroy(options); } }; let SpineBase = _SpineBase; SpineBase.clippingPolygon = []; Object.defineProperty(SpineBase.prototype, "visible", { get() { return this._visible; }, set(value) { if (value !== this._visible) { this._visible = value; if (value) { this.lastTime = 0; } } } }); exports.SpineBase = SpineBase; exports.SpineMesh = SpineMesh; exports.SpineSprite = SpineSprite; //# sourceMappingURL=SpineBase.js.map