playcanvas
Version:
PlayCanvas WebGL game engine
259 lines (256 loc) • 6.94 kB
JavaScript
import { EventHandler } from '../core/event-handler.js';
import { Vec2 } from '../core/math/vec2.js';
import { SPRITE_RENDERMODE_SIMPLE, SPRITE_RENDERMODE_SLICED, SPRITE_RENDERMODE_TILED } from './constants.js';
import { Mesh } from './mesh.js';
import { Geometry } from './geometry/geometry.js';
const spriteNormals = [
0,
0,
1,
0,
0,
1,
0,
0,
1,
0,
0,
1
];
const spriteIndices = [
0,
1,
3,
2,
3,
1
];
class Sprite extends EventHandler {
constructor(device, options){
super();
this._device = device;
this._pixelsPerUnit = options && options.pixelsPerUnit !== undefined ? options.pixelsPerUnit : 1;
this._renderMode = options && options.renderMode !== undefined ? options.renderMode : SPRITE_RENDERMODE_SIMPLE;
this._atlas = options && options.atlas !== undefined ? options.atlas : null;
this._frameKeys = options && options.frameKeys !== undefined ? options.frameKeys : null;
this._meshes = [];
this._updatingProperties = false;
this._meshesDirty = false;
if (this._atlas && this._frameKeys) {
this._createMeshes();
}
}
set frameKeys(value) {
this._frameKeys = value;
if (this._atlas && this._frameKeys) {
if (this._updatingProperties) {
this._meshesDirty = true;
} else {
this._createMeshes();
}
}
this.fire('set:frameKeys', value);
}
get frameKeys() {
return this._frameKeys;
}
set atlas(value) {
if (value === this._atlas) return;
if (this._atlas) {
this._atlas.off('set:frames', this._onSetFrames, this);
this._atlas.off('set:frame', this._onFrameChanged, this);
this._atlas.off('remove:frame', this._onFrameRemoved, this);
}
this._atlas = value;
if (this._atlas && this._frameKeys) {
this._atlas.on('set:frames', this._onSetFrames, this);
this._atlas.on('set:frame', this._onFrameChanged, this);
this._atlas.on('remove:frame', this._onFrameRemoved, this);
if (this._updatingProperties) {
this._meshesDirty = true;
} else {
this._createMeshes();
}
}
this.fire('set:atlas', value);
}
get atlas() {
return this._atlas;
}
set pixelsPerUnit(value) {
if (this._pixelsPerUnit === value) return;
this._pixelsPerUnit = value;
this.fire('set:pixelsPerUnit', value);
if (this._atlas && this._frameKeys && this.renderMode === SPRITE_RENDERMODE_SIMPLE) {
if (this._updatingProperties) {
this._meshesDirty = true;
} else {
this._createMeshes();
}
}
}
get pixelsPerUnit() {
return this._pixelsPerUnit;
}
set renderMode(value) {
if (this._renderMode === value) {
return;
}
const prev = this._renderMode;
this._renderMode = value;
this.fire('set:renderMode', value);
if (prev === SPRITE_RENDERMODE_SIMPLE || value === SPRITE_RENDERMODE_SIMPLE) {
if (this._atlas && this._frameKeys) {
if (this._updatingProperties) {
this._meshesDirty = true;
} else {
this._createMeshes();
}
}
}
}
get renderMode() {
return this._renderMode;
}
get meshes() {
return this._meshes;
}
_createMeshes() {
const len = this._meshes.length;
for(let i = 0; i < len; i++){
const mesh = this._meshes[i];
if (mesh) {
mesh.destroy();
}
}
const count = this._frameKeys.length;
this._meshes = new Array(count);
const createMeshFunc = this.renderMode === SPRITE_RENDERMODE_SLICED || this._renderMode === SPRITE_RENDERMODE_TILED ? this._create9SliceMesh : this._createSimpleMesh;
for(let i = 0; i < count; i++){
const frame = this._atlas.frames[this._frameKeys[i]];
this._meshes[i] = frame ? createMeshFunc.call(this, frame) : null;
}
this.fire('set:meshes');
}
_createSimpleMesh(frame) {
const rect = frame.rect;
const texWidth = this._atlas.texture.width;
const texHeight = this._atlas.texture.height;
const w = rect.z / this._pixelsPerUnit;
const h = rect.w / this._pixelsPerUnit;
const hp = frame.pivot.x;
const vp = frame.pivot.y;
const positions = [
-hp * w,
-vp * h,
0,
(1 - hp) * w,
-vp * h,
0,
(1 - hp) * w,
(1 - vp) * h,
0,
-hp * w,
(1 - vp) * h,
0
];
const lu = rect.x / texWidth;
const bv = 1.0 - rect.y / texHeight;
const ru = (rect.x + rect.z) / texWidth;
const tv = 1.0 - (rect.y + rect.w) / texHeight;
const uvs = [
lu,
bv,
ru,
bv,
ru,
tv,
lu,
tv
];
const geom = new Geometry();
geom.positions = positions;
geom.normals = spriteNormals;
geom.uvs = uvs;
geom.indices = spriteIndices;
return Mesh.fromGeometry(this._device, geom);
}
_create9SliceMesh() {
const he = Vec2.ONE;
const ws = 3;
const ls = 3;
const positions = [];
const normals = [];
const uvs = [];
const indices = [];
let vcounter = 0;
for(let i = 0; i <= ws; i++){
const u = i === 0 || i === ws ? 0 : 1;
for(let j = 0; j <= ls; j++){
const x = -he.x + 2.0 * he.x * (i <= 1 ? 0 : 3) / ws;
const y = 0.0;
const z = -(-he.y + 2.0 * he.y * (j <= 1 ? 0 : 3) / ls);
const v = j === 0 || j === ls ? 0 : 1;
positions.push(-x, y, z);
normals.push(0.0, 1.0, 0.0);
uvs.push(u, v);
if (i < ws && j < ls) {
indices.push(vcounter + ls + 1, vcounter + 1, vcounter);
indices.push(vcounter + ls + 1, vcounter + ls + 2, vcounter + 1);
}
vcounter++;
}
}
const geom = new Geometry();
geom.positions = positions;
geom.normals = normals;
geom.uvs = uvs;
geom.indices = indices;
return Mesh.fromGeometry(this._device, geom);
}
_onSetFrames(frames) {
if (this._updatingProperties) {
this._meshesDirty = true;
} else {
this._createMeshes();
}
}
_onFrameChanged(frameKey, frame) {
const idx = this._frameKeys.indexOf(frameKey);
if (idx < 0) return;
if (frame) {
if (this.renderMode === SPRITE_RENDERMODE_SIMPLE) {
this._meshes[idx] = this._createSimpleMesh(frame);
}
} else {
this._meshes[idx] = null;
}
this.fire('set:meshes');
}
_onFrameRemoved(frameKey) {
const idx = this._frameKeys.indexOf(frameKey);
if (idx < 0) return;
this._meshes[idx] = null;
this.fire('set:meshes');
}
startUpdate() {
this._updatingProperties = true;
this._meshesDirty = false;
}
endUpdate() {
this._updatingProperties = false;
if (this._meshesDirty && this._atlas && this._frameKeys) {
this._createMeshes();
}
this._meshesDirty = false;
}
destroy() {
for (const mesh of this._meshes){
if (mesh) {
mesh.destroy();
}
}
this._meshes.length = 0;
}
}
export { Sprite };