浏览代码

Merge pull request #6690 from BabylonJSGuide/master

Packed Sprites
David Catuhe 6 年之前
父节点
当前提交
ed35f4ff28

+ 44 - 0
Playground/textures/pack1.json

@@ -0,0 +1,44 @@
+{"frames": {
+
+"eye.png":
+{
+	"frame": {"x":0,"y":148,"w":400,"h":400},
+	"rotated": false,
+	"trimmed": false,
+	"spriteSourceSize": {"x":0,"y":0,"w":400,"h":400},
+	"sourceSize": {"w":400,"h":400}
+},
+"redman.png":
+{
+	"frame": {"x":0,"y":0,"w":55,"h":97},
+	"rotated": false,
+	"trimmed": false,
+	"spriteSourceSize": {"x":0,"y":0,"w":55,"h":97},
+	"sourceSize": {"w":55,"h":97}
+},
+"spot.png":
+{
+	"frame": {"x":199,"y":0,"w":148,"h":148},
+	"rotated": false,
+	"trimmed": false,
+	"spriteSourceSize": {"x":0,"y":0,"w":148,"h":148},
+	"sourceSize": {"w":148,"h":148}
+},
+"triangle.png":
+{
+	"frame": {"x":55,"y":0,"w":144,"h":72},
+	"rotated": false,
+	"trimmed": false,
+	"spriteSourceSize": {"x":0,"y":0,"w":144,"h":72},
+	"sourceSize": {"w":144,"h":72}
+}},
+"meta": {
+	"app": "https://www.codeandweb.com/texturepacker",
+	"version": "1.0",
+	"image": "pack1.png",
+	"format": "RGBA8888",
+	"size": {"w":400,"h":548},
+	"scale": "1",
+	"smartupdate": "$TexturePacker:SmartUpdate:c5944b8d86d99a167f95924d4a62d5c3:3ed0ae95f00621580b477fcf2f6edb75:5d0ff2351eb79b7bb8a91bc3358bcff4$"
+}
+}

二进制
Playground/textures/pack1.png


+ 1 - 0
dist/preview release/what's new.md

@@ -13,6 +13,7 @@
   - WebXR webVR parity helpers (Vive, WMR, Oculus Rift) ([TrevorDev](https://github.com/TrevorDev))
 
 ## Updates
+- SpritePackedManager extends SpriteManager so that a sprite sheet with different size sprites can be used ([JohnK](https://github.com/BabylonJSGuide))
 
 ### General
 - Added support for dual shock gamepads ([Deltakosh](https://github.com/deltakosh/))

+ 6 - 4
src/Shaders/sprites.vertex.fx

@@ -1,11 +1,11 @@
 // Attributes
 attribute vec4 position;
 attribute vec4 options;
+attribute vec2 inverts;
 attribute vec4 cellInfo;
 attribute vec4 color;
 
 // Uniforms
-uniform vec2 textureInfos;
 uniform mat4 view;
 uniform mat4 projection;
 
@@ -22,7 +22,6 @@ void main(void) {
 	float angle = position.w;
 	vec2 size = vec2(options.x, options.y);
 	vec2 offset = options.zw;
-	vec2 uvScale = textureInfos.xy;
 
 	cornerPos = vec2(offset.x - 0.5, offset.y  - 0.5) * size;
 
@@ -40,9 +39,12 @@ void main(void) {
 	vColor = color;
 	
 	// Texture
-	vec2 uvOffset = vec2(abs(offset.x - cellInfo.x), 1.0 - abs(offset.y - cellInfo.y));
+	vec2 uvOffset = vec2(abs(offset.x - inverts.x), abs(1.0 - offset.y - inverts.y));
+	vec2 uvPlace = cellInfo.xy;
+	vec2 uvSize = cellInfo.zw;
 
-	vUV = (uvOffset + cellInfo.zw) * uvScale;
+	vUV.x = uvPlace.x + uvSize.x * uvOffset.x;
+	vUV.y = uvPlace.y + uvSize.y * uvOffset.y;
 
 	// Fog
 #ifdef FOG

+ 1 - 0
src/Sprites/index.ts

@@ -1,3 +1,4 @@
 export * from "./sprite";
 export * from "./spriteManager";
+export * from "./spritePackedManager";
 export * from "./spriteSceneComponent";

+ 3 - 1
src/Sprites/sprite.ts

@@ -20,7 +20,9 @@ export class Sprite {
     /** Gets or sets rotation angle */
     public angle = 0;
     /** Gets or sets the cell index in the sprite sheet */
-    public cellIndex = 0;
+    public cellIndex: number;
+    /** Gets or sets the cell reference in the sprite sheet, uses sprite's filename when added to sprite sheet */
+    public cellRef: string;
     /** Gets or sets a boolean indicating if UV coordinates should be inverted in U axis */
     public invertU = 0;
     /** Gets or sets a boolean indicating if UV coordinates should be inverted in B axis */

+ 116 - 24
src/Sprites/spriteManager.ts

@@ -13,6 +13,7 @@ import { Effect } from "../Materials/effect";
 import { Material } from "../Materials/material";
 import { SceneComponentConstants } from "../sceneComponent";
 import { Constants } from "../Engines/constants";
+import { Logger } from "../Misc/logger";
 
 import "../Shaders/sprites.fragment";
 import "../Shaders/sprites.vertex";
@@ -81,6 +82,13 @@ export class SpriteManager implements ISpriteManager {
     /** Defines the default height of a cell in the spritesheet */
     public cellHeight: number;
 
+    /** Associative array from JSON sprite data file */
+    private _cellData: any;
+    /** Array of sprite names from JSON sprite data file */
+    private _spriteMap: Array<string>;
+    /** True when packed cell data from JSON file is ready*/
+    private _packedAndReady: boolean = false;
+
     /**
     * An event triggered when the manager is disposed.
     */
@@ -99,6 +107,7 @@ export class SpriteManager implements ISpriteManager {
     }
 
     private _capacity: number;
+    private _fromPacked: boolean;
     private _spriteTexture: Texture;
     private _epsilon: number;
 
@@ -131,15 +140,18 @@ export class SpriteManager implements ISpriteManager {
      * @param scene defines the hosting scene
      * @param epsilon defines the epsilon value to align texture (0.01 by default)
      * @param samplingMode defines the smapling mode to use with spritesheet
+     * @param fromPacked set to false; do not alter
+     * @param spriteJSON null otherwise a JSON object defining sprite sheet data; do not alter
      */
     constructor(
         /** defines the manager's name */
         public name: string,
-        imgUrl: string, capacity: number, cellSize: any, scene: Scene, epsilon: number = 0.01, samplingMode: number = Texture.TRILINEAR_SAMPLINGMODE) {
+        imgUrl: string, capacity: number, cellSize: any, scene: Scene, epsilon: number = 0.01, samplingMode: number = Texture.TRILINEAR_SAMPLINGMODE, fromPacked: boolean = false, spriteJSON: string | null = null) {
         if (!scene._getComponent(SceneComponentConstants.NAME_SPRITE)) {
             scene._addComponent(new SpriteSceneComponent(scene));
         }
         this._capacity = capacity;
+        this._fromPacked = fromPacked;
         this._spriteTexture = new Texture(imgUrl, scene, true, false, samplingMode);
         this._spriteTexture.wrapU = Texture.CLAMP_ADDRESSMODE;
         this._spriteTexture.wrapV = Texture.CLAMP_ADDRESSMODE;
@@ -173,34 +185,88 @@ export class SpriteManager implements ISpriteManager {
         this._indexBuffer = scene.getEngine().createIndexBuffer(indices);
 
         // VBO
-        // 16 floats per sprite (x, y, z, angle, sizeX, sizeY, offsetX, offsetY, invertU, invertV, cellIndexX, cellIndexY, color r, color g, color b, color a)
-        this._vertexData = new Float32Array(capacity * 16 * 4);
-        this._buffer = new Buffer(scene.getEngine(), this._vertexData, true, 16);
+        // 18 floats per sprite (x, y, z, angle, sizeX, sizeY, offsetX, offsetY, invertU, invertV, cellLeft, cellTop, cellWidth, cellHeight, color r, color g, color b, color a)
+        this._vertexData = new Float32Array(capacity * 18 * 4);
+        this._buffer = new Buffer(scene.getEngine(), this._vertexData, true, 18);
 
         var positions = this._buffer.createVertexBuffer(VertexBuffer.PositionKind, 0, 4);
         var options = this._buffer.createVertexBuffer("options", 4, 4);
-        var cellInfo = this._buffer.createVertexBuffer("cellInfo", 8, 4);
-        var colors = this._buffer.createVertexBuffer(VertexBuffer.ColorKind, 12, 4);
+        var inverts = this._buffer.createVertexBuffer("inverts", 8, 2);
+        var cellInfo = this._buffer.createVertexBuffer("cellInfo", 10, 4);
+        var colors = this._buffer.createVertexBuffer(VertexBuffer.ColorKind, 14, 4);
 
         this._vertexBuffers[VertexBuffer.PositionKind] = positions;
         this._vertexBuffers["options"] = options;
+        this._vertexBuffers["inverts"] = inverts;
         this._vertexBuffers["cellInfo"] = cellInfo;
         this._vertexBuffers[VertexBuffer.ColorKind] = colors;
 
         // Effects
         this._effectBase = this._scene.getEngine().createEffect("sprites",
-            [VertexBuffer.PositionKind, "options", "cellInfo", VertexBuffer.ColorKind],
+            [VertexBuffer.PositionKind, "options", "inverts", "cellInfo", VertexBuffer.ColorKind],
             ["view", "projection", "textureInfos", "alphaTest"],
             ["diffuseSampler"], "");
 
         this._effectFog = this._scene.getEngine().createEffect("sprites",
-            [VertexBuffer.PositionKind, "options", "cellInfo", VertexBuffer.ColorKind],
+            [VertexBuffer.PositionKind, "options", "inverts", "cellInfo", VertexBuffer.ColorKind],
             ["view", "projection", "textureInfos", "alphaTest", "vFogInfos", "vFogColor"],
             ["diffuseSampler"], "#define FOG");
+
+        if (this._fromPacked) {
+            this._makePacked(imgUrl, spriteJSON);
+        }
+    }
+
+    private _makePacked(imgUrl: string, spriteJSON: string | null) {
+        if (spriteJSON !== null) {
+            try {
+                let celldata = JSON.parse(spriteJSON);
+                let spritemap = (<string[]>(<any>Reflect).ownKeys(celldata.frames));
+                this._spriteMap = spritemap;
+                this._packedAndReady = true;
+                this._cellData = celldata.frames;
+            }
+            catch (e) {
+                this._fromPacked = false;
+                this._packedAndReady = false;
+                throw new Error("Invalid JSON from string. Spritesheet managed with constant cell size.");
+            }
+        }
+        else {
+            let re = /\./g;
+            let li: number;
+            do {
+                li = re.lastIndex;
+                re.test(imgUrl);
+            } while (re.lastIndex > 0);
+            let jsonUrl = imgUrl.substring(0, li - 1) + ".json";
+            let xmlhttp = new XMLHttpRequest();
+            xmlhttp.open("GET", jsonUrl, true);
+            xmlhttp.onerror = () => {
+                Logger.Error("Unable to Load Sprite JSON Data. Spritesheet managed with constant cell size.");
+                this._fromPacked = false;
+                this._packedAndReady = false;
+            };
+            xmlhttp.onload = () => {
+                try {
+                    let celldata  = JSON.parse(xmlhttp.response);
+                    let spritemap = (<string[]>(<any>Reflect).ownKeys(celldata.frames));
+                    this._spriteMap = spritemap;
+                    this._packedAndReady = true;
+                    this._cellData = celldata.frames;
+                }
+                catch (e) {
+                    this._fromPacked = false;
+                    this._packedAndReady = false;
+                    throw new Error("Invalid JSON from file. Spritesheet managed with constant cell size.");
+                }
+            };
+            xmlhttp.send();
+        }
     }
 
-    private _appendSpriteVertex(index: number, sprite: Sprite, offsetX: number, offsetY: number, rowSize: number): void {
-        var arrayOffset = index * 16;
+    private _appendSpriteVertex(index: number, sprite: Sprite, offsetX: number, offsetY: number, baseSize: any): void {
+        var arrayOffset = index * 18;
 
         if (offsetX === 0) {
             offsetX = this._epsilon;
@@ -216,24 +282,49 @@ export class SpriteManager implements ISpriteManager {
             offsetY = 1 - this._epsilon;
         }
 
+        // Positions
         this._vertexData[arrayOffset] = sprite.position.x;
         this._vertexData[arrayOffset + 1] = sprite.position.y;
         this._vertexData[arrayOffset + 2] = sprite.position.z;
         this._vertexData[arrayOffset + 3] = sprite.angle;
+        // Options
         this._vertexData[arrayOffset + 4] = sprite.width;
         this._vertexData[arrayOffset + 5] = sprite.height;
         this._vertexData[arrayOffset + 6] = offsetX;
         this._vertexData[arrayOffset + 7] = offsetY;
+        // Inverts
         this._vertexData[arrayOffset + 8] = sprite.invertU ? 1 : 0;
         this._vertexData[arrayOffset + 9] = sprite.invertV ? 1 : 0;
-        var offset = (sprite.cellIndex / rowSize) >> 0;
-        this._vertexData[arrayOffset + 10] = sprite.cellIndex - offset * rowSize;
-        this._vertexData[arrayOffset + 11] = offset;
+        // CellIfo
+        if (this._packedAndReady) {
+            if (!sprite.cellRef) {
+                sprite.cellIndex = 0;
+            }
+            let num = sprite.cellIndex;
+            if (typeof (num) === "number" && isFinite(num) && Math.floor(num) === num) {
+                sprite.cellRef = this._spriteMap[sprite.cellIndex];
+            }
+            this._vertexData[arrayOffset + 10] = this._cellData[sprite.cellRef].frame.x / baseSize.width;
+            this._vertexData[arrayOffset + 11] = this._cellData[sprite.cellRef].frame.y / baseSize.height;
+            this._vertexData[arrayOffset + 12] = this._cellData[sprite.cellRef].frame.w / baseSize.width;
+            this._vertexData[arrayOffset + 13] = this._cellData[sprite.cellRef].frame.h / baseSize.height;
+        }
+        else {
+            if (!sprite.cellIndex) {
+                sprite.cellIndex = 0;
+            }
+            var rowSize = baseSize.width / this.cellWidth;
+            var offset = (sprite.cellIndex / rowSize) >> 0;
+            this._vertexData[arrayOffset + 10] = (sprite.cellIndex - offset * rowSize) * this.cellWidth / baseSize.width;
+            this._vertexData[arrayOffset + 11] = offset * this.cellHeight / baseSize.height;
+            this._vertexData[arrayOffset + 12] = this.cellWidth / baseSize.width;
+            this._vertexData[arrayOffset + 13] = this.cellHeight / baseSize.height;
+        }
         // Color
-        this._vertexData[arrayOffset + 12] = sprite.color.r;
-        this._vertexData[arrayOffset + 13] = sprite.color.g;
-        this._vertexData[arrayOffset + 14] = sprite.color.b;
-        this._vertexData[arrayOffset + 15] = sprite.color.a;
+        this._vertexData[arrayOffset + 14] = sprite.color.r;
+        this._vertexData[arrayOffset + 15] = sprite.color.g;
+        this._vertexData[arrayOffset + 16] = sprite.color.b;
+        this._vertexData[arrayOffset + 17] = sprite.color.a;
     }
 
     /**
@@ -320,13 +411,16 @@ export class SpriteManager implements ISpriteManager {
             return;
         }
 
+        if (this._fromPacked  && (!this._packedAndReady || !this._spriteMap || !this._cellData)) {
+            return;
+        }
+
         var engine = this._scene.getEngine();
         var baseSize = this._spriteTexture.getBaseSize();
 
         // Sprites
         var deltaTime = engine.getDeltaTime();
         var max = Math.min(this._capacity, this.sprites.length);
-        var rowSize = baseSize.width / this.cellWidth;
 
         var offset = 0;
         let noSprite = true;
@@ -339,10 +433,10 @@ export class SpriteManager implements ISpriteManager {
             noSprite = false;
             sprite._animate(deltaTime);
 
-            this._appendSpriteVertex(offset++, sprite, 0, 0, rowSize);
-            this._appendSpriteVertex(offset++, sprite, 1, 0, rowSize);
-            this._appendSpriteVertex(offset++, sprite, 1, 1, rowSize);
-            this._appendSpriteVertex(offset++, sprite, 0, 1, rowSize);
+            this._appendSpriteVertex(offset++, sprite, 0, 0, baseSize);
+            this._appendSpriteVertex(offset++, sprite, 1, 0, baseSize);
+            this._appendSpriteVertex(offset++, sprite, 1, 1, baseSize);
+            this._appendSpriteVertex(offset++, sprite, 0, 1, baseSize);
         }
 
         if (noSprite) {
@@ -365,8 +459,6 @@ export class SpriteManager implements ISpriteManager {
         effect.setMatrix("view", viewMatrix);
         effect.setMatrix("projection", this._scene.getProjectionMatrix());
 
-        effect.setFloat2("textureInfos", this.cellWidth / baseSize.width, this.cellHeight / baseSize.height);
-
         // Fog
         if (this._scene.fogEnabled && this._scene.fogMode !== Scene.FOGMODE_NONE && this.fogEnabled) {
             effect.setFloat4("vFogInfos", this._scene.fogMode, this._scene.fogStart, this._scene.fogEnd, this._scene.fogDensity);

+ 33 - 0
src/Sprites/spritePackedManager.ts

@@ -0,0 +1,33 @@
+import { SpriteManager } from "./spriteManager";
+import { Scene } from "../scene";
+import { Texture } from "../Materials/Textures/texture";
+
+/**
+ * Class used to manage multiple sprites of different sizes on the same spritesheet
+ * @see http://doc.babylonjs.com/babylon101/sprites
+ */
+
+export class SpritePackedManager extends SpriteManager{
+
+    /**
+     * Creates a new sprite manager from a packed sprite sheet
+     * @param name defines the manager's name
+     * @param imgUrl defines the sprite sheet url
+     * @param capacity defines the maximum allowed number of sprites
+     * @param scene defines the hosting scene
+     * @param spriteJSON null otherwise a JSON object defining sprite sheet data
+     * @param epsilon defines the epsilon value to align texture (0.01 by default)
+     * @param samplingMode defines the smapling mode to use with spritesheet
+     * @param fromPacked set to true; do not alter
+     */
+
+    constructor(
+        /** defines the packed manager's name */
+        public name: string,
+        imgUrl: string, capacity: number, scene: Scene, spriteJSON: string | null = null, epsilon: number = 0.01, samplingMode: number = Texture.TRILINEAR_SAMPLINGMODE) {
+
+        //the cellSize parameter is not used when built from JSON which provides individual cell data, defaults to 64 if JSON load fails
+        super(name, imgUrl, capacity, 64, scene, epsilon, samplingMode, true, spriteJSON);
+
+    }
+}