UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

594 lines (593 loc) 17.1 kB
import { math } from "../../../core/math/math.js"; import { Color } from "../../../core/math/color.js"; import { Vec2 } from "../../../core/math/vec2.js"; import { Vec4 } from "../../../core/math/vec4.js"; import { LAYERID_WORLD, SPRITE_RENDERMODE_SLICED, SPRITE_RENDERMODE_TILED } from "../../../scene/constants.js"; import { BatchGroup } from "../../../scene/batching/batch-group.js"; import { GraphNode } from "../../../scene/graph-node.js"; import { MeshInstance } from "../../../scene/mesh-instance.js"; import { Component } from "../component.js"; import { SPRITETYPE_SIMPLE, SPRITETYPE_ANIMATED } from "./constants.js"; import { SpriteAnimationClip } from "./sprite-animation-clip.js"; const PARAM_EMISSIVE_MAP = "texture_emissiveMap"; const PARAM_OPACITY_MAP = "texture_opacityMap"; const PARAM_EMISSIVE = "material_emissive"; const PARAM_OPACITY = "material_opacity"; const PARAM_INNER_OFFSET = "innerOffset"; const PARAM_OUTER_SCALE = "outerScale"; const PARAM_ATLAS_RECT = "atlasRect"; class SpriteComponent extends Component { static EVENT_PLAY = "play"; static EVENT_PAUSE = "pause"; static EVENT_RESUME = "resume"; static EVENT_STOP = "stop"; static EVENT_END = "end"; static EVENT_LOOP = "loop"; _evtLayersChanged = null; _evtLayerAdded = null; _evtLayerRemoved = null; _type = SPRITETYPE_SIMPLE; _material = null; _color = new Color(1, 1, 1, 1); _colorUniform = new Float32Array(3); _speed = 1; _flipX = false; _flipY = false; _width = 1; _height = 1; _drawOrder = 0; _layers = [LAYERID_WORLD]; // assign to the default world layer // 9-slicing _outerScale = new Vec2(1, 1); _outerScaleUniform = new Float32Array(2); _innerOffset = new Vec4(); _innerOffsetUniform = new Float32Array(4); _atlasRect = new Vec4(); _atlasRectUniform = new Float32Array(4); // batch groups _batchGroupId = -1; // node / mesh instance _node = new GraphNode(); _meshInstance = null; _updateAabbFunc = null; _inLayers = false; // animated sprites _autoPlayClip = null; _clips = {}; _defaultClip = null; _currentClip = null; constructor(system, entity) { super(system, entity); this._material = system.defaultMaterial; entity.addChild(this._node); this._updateAabbFunc = this._updateAabb.bind(this); this._defaultClip = new SpriteAnimationClip(this, { name: this.entity.name, fps: 0, loop: false, spriteAsset: null }); this._currentClip = this._defaultClip; } set type(value) { if (this._type === value) { return; } this._type = value; if (this._type === SPRITETYPE_SIMPLE) { this.stop(); this._currentClip = this._defaultClip; if (this.enabled && this.entity.enabled) { this._currentClip.frame = this.frame; if (this._currentClip.sprite) { this.addToLayers(); } else { this.removeFromLayers(); } } } else if (this._type === SPRITETYPE_ANIMATED) { this.stop(); if (this._autoPlayClip) { this._tryAutoPlay(); } if (this._currentClip && this._currentClip.isPlaying && this.enabled && this.entity.enabled) { this.addToLayers(); } else { this.removeFromLayers(); } } } get type() { return this._type; } set frame(value) { this._currentClip.frame = value; } get frame() { return this._currentClip.frame; } set spriteAsset(value) { this._defaultClip.spriteAsset = value; } get spriteAsset() { return this._defaultClip._spriteAsset; } set sprite(value) { this._currentClip.sprite = value; } get sprite() { return this._currentClip.sprite; } // (private) {pc.Material} material The material used to render a sprite. set material(value) { this._material = value; if (this._meshInstance) { this._meshInstance.material = value; } } get material() { return this._material; } set color(value) { this._color.r = value.r; this._color.g = value.g; this._color.b = value.b; if (this._meshInstance) { this._colorUniform[0] = this._color.r; this._colorUniform[1] = this._color.g; this._colorUniform[2] = this._color.b; this._meshInstance.setParameter(PARAM_EMISSIVE, this._colorUniform); } } get color() { return this._color; } set opacity(value) { this._color.a = value; if (this._meshInstance) { this._meshInstance.setParameter(PARAM_OPACITY, value); } } get opacity() { return this._color.a; } set clips(value) { if (!value) { for (const name in this._clips) { this.removeClip(name); } return; } for (const name in this._clips) { let found = false; for (const key in value) { if (value[key].name === name) { found = true; this._clips[name].fps = value[key].fps; this._clips[name].loop = value[key].loop; if (value[key].hasOwnProperty("sprite")) { this._clips[name].sprite = value[key].sprite; } else if (value[key].hasOwnProperty("spriteAsset")) { this._clips[name].spriteAsset = value[key].spriteAsset; } break; } } if (!found) { this.removeClip(name); } } for (const key in value) { if (this._clips[value[key].name]) continue; this.addClip(value[key]); } if (this._autoPlayClip) { this._tryAutoPlay(); } if (!this._currentClip || !this._currentClip.sprite) { this.removeFromLayers(); } } get clips() { return this._clips; } get currentClip() { return this._currentClip; } set speed(value) { this._speed = value; } get speed() { return this._speed; } set flipX(value) { if (this._flipX === value) return; this._flipX = value; this._updateTransform(); } get flipX() { return this._flipX; } set flipY(value) { if (this._flipY === value) return; this._flipY = value; this._updateTransform(); } get flipY() { return this._flipY; } set width(value) { if (value === this._width) return; this._width = value; this._outerScale.x = this._width; if (this.sprite && (this.sprite.renderMode === SPRITE_RENDERMODE_TILED || this.sprite.renderMode === SPRITE_RENDERMODE_SLICED)) { this._updateTransform(); } } get width() { return this._width; } set height(value) { if (value === this._height) return; this._height = value; this._outerScale.y = this.height; if (this.sprite && (this.sprite.renderMode === SPRITE_RENDERMODE_TILED || this.sprite.renderMode === SPRITE_RENDERMODE_SLICED)) { this._updateTransform(); } } get height() { return this._height; } set batchGroupId(value) { if (this._batchGroupId === value) { return; } const prev = this._batchGroupId; this._batchGroupId = value; if (this.entity.enabled && prev >= 0) { this.system.app.batcher?.remove(BatchGroup.SPRITE, prev, this.entity); } if (this.entity.enabled && value >= 0) { this.system.app.batcher?.insert(BatchGroup.SPRITE, value, this.entity); } else { if (prev >= 0) { if (this._currentClip && this._currentClip.sprite && this.enabled && this.entity.enabled) { this.addToLayers(); } } } } get batchGroupId() { return this._batchGroupId; } set autoPlayClip(value) { this._autoPlayClip = value instanceof SpriteAnimationClip ? value.name : value; this._tryAutoPlay(); } get autoPlayClip() { return this._autoPlayClip; } set drawOrder(value) { this._drawOrder = value; if (this._meshInstance) { this._meshInstance.drawOrder = value; } } get drawOrder() { return this._drawOrder; } set layers(value) { if (this._inLayers) { this.removeFromLayers(); } this._layers = value; if (!this._meshInstance) { return; } if (this.enabled && this.entity.enabled) { this.addToLayers(); } } get layers() { return this._layers; } get aabb() { if (this._meshInstance) { return this._meshInstance.aabb; } return null; } onEnable() { const app = this.system.app; const scene = app.scene; const layers = scene.layers; this._evtLayersChanged = scene.on("set:layers", this._onLayersChanged, this); if (layers) { this._evtLayerAdded = layers.on("add", this._onLayerAdded, this); this._evtLayerRemoved = layers.on("remove", this._onLayerRemoved, this); } this.addToLayers(); if (this._autoPlayClip) { this._tryAutoPlay(); } if (this._batchGroupId >= 0) { app.batcher?.insert(BatchGroup.SPRITE, this._batchGroupId, this.entity); } } onDisable() { const app = this.system.app; const scene = app.scene; const layers = scene.layers; this._evtLayersChanged?.off(); this._evtLayersChanged = null; if (layers) { this._evtLayerAdded?.off(); this._evtLayerAdded = null; this._evtLayerRemoved?.off(); this._evtLayerRemoved = null; } this.stop(); this.removeFromLayers(); if (this._batchGroupId >= 0) { app.batcher?.remove(BatchGroup.SPRITE, this._batchGroupId, this.entity); } } onDestroy() { this._currentClip = null; if (this._defaultClip) { this._defaultClip._destroy(); this._defaultClip = null; } for (const key in this._clips) { this._clips[key]._destroy(); } this._clips = null; this.removeFromLayers(); this._node?.remove(); this._node = null; if (this._meshInstance) { this._meshInstance.material = null; this._meshInstance.mesh = null; this._meshInstance = null; } } addToLayers() { if (this._inLayers) return; if (!this._meshInstance) return; const meshInstances = [this._meshInstance]; for (let i = 0, len = this._layers.length; i < len; i++) { const layer = this.system.app.scene.layers.getLayerById(this._layers[i]); if (layer) { layer.addMeshInstances(meshInstances); } } this._inLayers = true; } removeFromLayers() { if (!this._inLayers || !this._meshInstance) return; const meshInstances = [this._meshInstance]; for (let i = 0, len = this._layers.length; i < len; i++) { const layer = this.system.app.scene.layers.getLayerById(this._layers[i]); if (layer) { layer.removeMeshInstances(meshInstances); } } this._inLayers = false; } // Set the desired mesh on the mesh instance _showFrame(frame) { if (!this.sprite) return; const mesh = this.sprite.meshes[frame]; if (!mesh) { if (this._meshInstance) { this._meshInstance.mesh = null; this._meshInstance.visible = false; } return; } let material; if (this.sprite.renderMode === SPRITE_RENDERMODE_SLICED) { material = this.system.default9SlicedMaterialSlicedMode; } else if (this.sprite.renderMode === SPRITE_RENDERMODE_TILED) { material = this.system.default9SlicedMaterialTiledMode; } else { material = this.system.defaultMaterial; } if (!this._meshInstance) { this._meshInstance = new MeshInstance(mesh, this._material, this._node); this._meshInstance.castShadow = false; this._meshInstance.receiveShadow = false; this._meshInstance.drawOrder = this._drawOrder; this._colorUniform[0] = this._color.r; this._colorUniform[1] = this._color.g; this._colorUniform[2] = this._color.b; this._meshInstance.setParameter(PARAM_EMISSIVE, this._colorUniform); this._meshInstance.setParameter(PARAM_OPACITY, this._color.a); if (this.enabled && this.entity.enabled) { this.addToLayers(); } } if (this._meshInstance.material !== material) { this._meshInstance.material = material; } if (this._meshInstance.mesh !== mesh) { this._meshInstance.mesh = mesh; this._meshInstance.visible = true; this._meshInstance._aabbVer = -1; } if (this.sprite.atlas && this.sprite.atlas.texture) { this._meshInstance.setParameter(PARAM_EMISSIVE_MAP, this.sprite.atlas.texture); this._meshInstance.setParameter(PARAM_OPACITY_MAP, this.sprite.atlas.texture); } else { this._meshInstance.deleteParameter(PARAM_EMISSIVE_MAP); this._meshInstance.deleteParameter(PARAM_OPACITY_MAP); } if (this.sprite.atlas && (this.sprite.renderMode === SPRITE_RENDERMODE_SLICED || this.sprite.renderMode === SPRITE_RENDERMODE_TILED)) { this._meshInstance._updateAabbFunc = this._updateAabbFunc; const frameData = this.sprite.atlas.frames[this.sprite.frameKeys[frame]]; if (frameData) { const borderWidthScale = 2 / frameData.rect.z; const borderHeightScale = 2 / frameData.rect.w; this._innerOffset.set( frameData.border.x * borderWidthScale, frameData.border.y * borderHeightScale, frameData.border.z * borderWidthScale, frameData.border.w * borderHeightScale ); const tex = this.sprite.atlas.texture; this._atlasRect.set( frameData.rect.x / tex.width, frameData.rect.y / tex.height, frameData.rect.z / tex.width, frameData.rect.w / tex.height ); } else { this._innerOffset.set(0, 0, 0, 0); } this._innerOffsetUniform[0] = this._innerOffset.x; this._innerOffsetUniform[1] = this._innerOffset.y; this._innerOffsetUniform[2] = this._innerOffset.z; this._innerOffsetUniform[3] = this._innerOffset.w; this._meshInstance.setParameter(PARAM_INNER_OFFSET, this._innerOffsetUniform); this._atlasRectUniform[0] = this._atlasRect.x; this._atlasRectUniform[1] = this._atlasRect.y; this._atlasRectUniform[2] = this._atlasRect.z; this._atlasRectUniform[3] = this._atlasRect.w; this._meshInstance.setParameter(PARAM_ATLAS_RECT, this._atlasRectUniform); } else { this._meshInstance._updateAabbFunc = null; } this._updateTransform(); } _updateTransform() { let scaleX = this.flipX ? -1 : 1; let scaleY = this.flipY ? -1 : 1; let posX = 0; let posY = 0; if (this.sprite && (this.sprite.renderMode === SPRITE_RENDERMODE_SLICED || this.sprite.renderMode === SPRITE_RENDERMODE_TILED)) { let w = 1; let h = 1; if (this.sprite.atlas) { const frameData = this.sprite.atlas.frames[this.sprite.frameKeys[this.frame]]; if (frameData) { w = frameData.rect.z; h = frameData.rect.w; posX = (0.5 - frameData.pivot.x) * this._width; posY = (0.5 - frameData.pivot.y) * this._height; } } const scaleMulX = w / this.sprite.pixelsPerUnit; const scaleMulY = h / this.sprite.pixelsPerUnit; this._outerScale.set(Math.max(this._width, this._innerOffset.x * scaleMulX), Math.max(this._height, this._innerOffset.y * scaleMulY)); scaleX *= scaleMulX; scaleY *= scaleMulY; this._outerScale.x /= scaleMulX; this._outerScale.y /= scaleMulY; scaleX *= math.clamp(this._width / (this._innerOffset.x * scaleMulX), 1e-4, 1); scaleY *= math.clamp(this._height / (this._innerOffset.y * scaleMulY), 1e-4, 1); if (this._meshInstance) { this._outerScaleUniform[0] = this._outerScale.x; this._outerScaleUniform[1] = this._outerScale.y; this._meshInstance.setParameter(PARAM_OUTER_SCALE, this._outerScaleUniform); } } this._node.setLocalScale(scaleX, scaleY, 1); this._node.setLocalPosition(posX, posY, 0); } // updates AABB while 9-slicing _updateAabb(aabb) { aabb.center.set(0, 0, 0); aabb.halfExtents.set(this._outerScale.x * 0.5, this._outerScale.y * 0.5, 1e-3); aabb.setFromTransformedAabb(aabb, this._node.getWorldTransform()); return aabb; } _tryAutoPlay() { if (!this._autoPlayClip) return; if (this.type !== SPRITETYPE_ANIMATED) return; const clip = this._clips[this._autoPlayClip]; if (clip && !clip.isPlaying && (!this._currentClip || !this._currentClip.isPlaying)) { if (this.enabled && this.entity.enabled) { this.play(clip.name); } } } _onLayersChanged(oldComp, newComp) { oldComp.off("add", this._onLayerAdded, this); oldComp.off("remove", this._onLayerRemoved, this); newComp.on("add", this._onLayerAdded, this); newComp.on("remove", this._onLayerRemoved, this); if (this.enabled && this.entity.enabled) { this.addToLayers(); } } _onLayerAdded(layer) { const index = this.layers.indexOf(layer.id); if (index < 0) return; if (this._inLayers && this.enabled && this.entity.enabled && this._meshInstance) { layer.addMeshInstances([this._meshInstance]); } } _onLayerRemoved(layer) { if (!this._meshInstance) return; const index = this.layers.indexOf(layer.id); if (index < 0) return; layer.removeMeshInstances([this._meshInstance]); } addClip(data) { const clip = new SpriteAnimationClip(this, { name: data.name, fps: data.fps, loop: data.loop, spriteAsset: data.spriteAsset }); this._clips[data.name] = clip; if (clip.name && clip.name === this._autoPlayClip) { this._tryAutoPlay(); } return clip; } removeClip(name) { delete this._clips[name]; } clip(name) { return this._clips[name]; } play(name) { const clip = this._clips[name]; const current = this._currentClip; if (current && current !== clip) { current._playing = false; } this._currentClip = clip; if (this._currentClip) { this._currentClip = clip; this._currentClip.play(); } else { } return clip; } pause() { if (this._currentClip === this._defaultClip) return; if (this._currentClip.isPlaying) { this._currentClip.pause(); } } resume() { if (this._currentClip === this._defaultClip) return; if (this._currentClip.isPaused) { this._currentClip.resume(); } } stop() { if (this._currentClip === this._defaultClip) return; this._currentClip.stop(); } } export { SpriteComponent };