playcanvas
Version:
PlayCanvas WebGL game engine
602 lines (599 loc) • 18.6 kB
JavaScript
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_TILED, SPRITE_RENDERMODE_SLICED } 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 { Model } from '../../../scene/model.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{
this.EVENT_PLAY = 'play';
}
static{
this.EVENT_PAUSE = 'pause';
}
static{
this.EVENT_RESUME = 'resume';
}
static{
this.EVENT_STOP = 'stop';
}
static{
this.EVENT_END = 'end';
}
static{
this.EVENT_LOOP = 'loop';
}
constructor(system, entity){
super(system, entity), this._evtLayersChanged = null, this._evtLayerAdded = null, this._evtLayerRemoved = null;
this._type = SPRITETYPE_SIMPLE;
this._material = system.defaultMaterial;
this._color = new Color(1, 1, 1, 1);
this._colorUniform = new Float32Array(3);
this._speed = 1;
this._flipX = false;
this._flipY = false;
this._width = 1;
this._height = 1;
this._drawOrder = 0;
this._layers = [
LAYERID_WORLD
];
this._outerScale = new Vec2(1, 1);
this._outerScaleUniform = new Float32Array(2);
this._innerOffset = new Vec4();
this._innerOffsetUniform = new Float32Array(4);
this._atlasRect = new Vec4();
this._atlasRectUniform = new Float32Array(4);
this._batchGroupId = -1;
this._batchGroup = null;
this._node = new GraphNode();
this._model = new Model();
this._model.graph = this._node;
this._meshInstance = null;
entity.addChild(this._model.graph);
this._model._entity = entity;
this._updateAabbFunc = this._updateAabb.bind(this);
this._addedModel = false;
this._autoPlayClip = null;
this._clips = {};
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._showModel();
} else {
this._hideModel();
}
}
} 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._showModel();
} else {
this._hideModel();
}
}
}
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;
}
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._hideModel();
}
}
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._showModel();
}
}
}
}
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._addedModel) {
this._hideModel();
}
this._layers = value;
if (!this._meshInstance) {
return;
}
if (this.enabled && this.entity.enabled) {
this._showModel();
}
}
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._showModel();
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._hideModel();
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._hideModel();
this._model = null;
this._node?.remove();
this._node = null;
if (this._meshInstance) {
this._meshInstance.material = null;
this._meshInstance.mesh = null;
this._meshInstance = null;
}
}
_showModel() {
if (this._addedModel) 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._addedModel = true;
}
_hideModel() {
if (!this._addedModel || !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._addedModel = false;
}
_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._model.meshInstances.push(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);
this._meshInstance.setParameter(PARAM_OPACITY, this._color.a);
if (this.enabled && this.entity.enabled) {
this._showModel();
}
}
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), 0.0001, 1);
scaleY *= math.clamp(this._height / (this._innerOffset.y * scaleMulY), 0.0001, 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);
}
_updateAabb(aabb) {
aabb.center.set(0, 0, 0);
aabb.halfExtents.set(this._outerScale.x * 0.5, this._outerScale.y * 0.5, 0.001);
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._showModel();
}
}
_onLayerAdded(layer) {
const index = this.layers.indexOf(layer.id);
if (index < 0) return;
if (this._addedModel && 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
]);
}
removeModelFromLayers() {
for(let i = 0; i < this.layers.length; i++){
const layer = this.system.app.scene.layers.getLayerById(this.layers[i]);
if (!layer) continue;
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();
}
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 };