/** * @author Richard Davey <rich@photonstorm.com> * @author Kestrel Moon Studios <creature@kestrelmoon.com> * @copyright 2016 Photon Storm Ltd and Kestrel Moon Studios * @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} */ /** * Creature is a custom Game Object used in conjunction with the Creature Runtime libraries by Kestrel Moon Studios. * * It allows you to display animated Game Objects that were created with the [Creature Automated Animation Tool](http://www.kestrelmoon.com/creature/). * * Note 1: You can only use Phaser.Creature objects in WebGL enabled games. They do not work in Canvas mode games. * * Note 2: You must use a build of Phaser that includes the CreatureMeshBone.js runtime and gl-matrix.js, or have them * loaded before your Phaser game boots. * * See the Phaser custom build process for more details. * * By default the Creature runtimes are NOT included in any pre-configured version of Phaser. * * So you'll need to do `grunt custom` to create a build that includes them. * * @class Phaser.Creature * @extends PIXI.DisplayObjectContainer * @extends Phaser.Component.Core * @extends Phaser.Component.Angle * @extends Phaser.Component.AutoCull * @extends Phaser.Component.BringToTop * @extends Phaser.Component.Destroy * @extends Phaser.Component.FixedToCamera * @extends Phaser.Component.LifeSpan * @extends Phaser.Component.Reset * @constructor * @param {Phaser.Game} game - A reference to the currently running game. * @param {number} x - The x coordinate of the Game Object. The coordinate is relative to any parent container this Game Object may be in. * @param {number} y - The y coordinate of the Game Object. The coordinate is relative to any parent container this Game Object may be in. * @param {string|PIXI.Texture} key - The texture used by the Creature Object during rendering. It can be a string which is a reference to the Cache entry, or an instance of a PIXI.Texture. * @param {string} mesh - The mesh data for the Creature Object. It should be a string which is a reference to the Cache JSON entry. * @param {string} [animation='default'] - The animation within the mesh data to play. */ Phaser.Creature = function (game, x, y, key, mesh, animation) { if (animation === undefined) { animation = 'default'; } /** * @property {number} type - The const type of this object. * @readonly */ this.type = Phaser.CREATURE; if (!game.cache.checkJSONKey(mesh)) { console.warn('Phaser.Creature: Invalid mesh key given. Not found in Phaser.Cache'); return; } var meshData = game.cache.getJSON(mesh); /** * @property {Creature} _creature - The Creature instance. * @private */ this._creature = new Creature(meshData); /** * @property {CreatureAnimation} animation - The CreatureAnimation instance. */ this.animation = new CreatureAnimation(meshData, animation, this._creature); /** * @property {CreatureManager} manager - The CreatureManager instance for this object. */ this.manager = new CreatureManager(this._creature); /** * @property {number} timeDelta - How quickly the animation advances. * @default */ this.timeDelta = 0.05; if (typeof key === 'string') { var texture = game.cache.getPixiTexture(key); } else { var texture = key; } /** * @property {PIXI.Texture} texture - The texture the animation is using. */ this.texture = texture; PIXI.DisplayObjectContainer.call(this); this.dirty = true; this.blendMode = PIXI.blendModes.NORMAL; /** * @property {Phaser.Point} creatureBoundsMin - The minimum bounds point. * @protected */ this.creatureBoundsMin = new Phaser.Point(); /** * @property {Phaser.Point} creatureBoundsMax - The maximum bounds point. * @protected */ this.creatureBoundsMax = new Phaser.Point(); var target = this.manager.target_creature; /** * @property {PIXI.Float32Array} vertices - The vertices data. * @protected */ this.vertices = new PIXI.Float32Array(target.total_num_pts * 2); /** * @property {PIXI.Float32Array} uvs - The UV data. * @protected */ this.uvs = new PIXI.Float32Array(target.total_num_pts * 2); /** * @property {PIXI.Uint16Array} indices * @protected */ this.indices = new PIXI.Uint16Array(target.global_indices.length); for (var i = 0; i < this.indices.length; i++) { this.indices[i] = target.global_indices[i]; } /** * @property {PIXI.Uint16Array} colors - The vertices colors * @protected */ this.colors = new PIXI.Float32Array([1, 1, 1, 1]); this.updateRenderData(target.global_pts, target.global_uvs); this.manager.AddAnimation(this.animation); this.manager.SetActiveAnimationName(animation, false); Phaser.Component.Core.init.call(this, game, x, y); }; Phaser.Creature.prototype = Object.create(PIXI.DisplayObjectContainer.prototype); Phaser.Creature.prototype.constructor = Phaser.Creature; Phaser.Component.Core.install.call(Phaser.Creature.prototype, [ 'Angle', 'AutoCull', 'BringToTop', 'Destroy', 'FixedToCamera', 'LifeSpan', 'Reset' ]); Phaser.Creature.prototype.preUpdateInWorld = Phaser.Component.InWorld.preUpdate; Phaser.Creature.prototype.preUpdateCore = Phaser.Component.Core.preUpdate; /** * Automatically called by World.preUpdate. * * @method Phaser.Creature#preUpdate * @memberof Phaser.Creature */ Phaser.Creature.prototype.preUpdate = function () { if (!this.preUpdateInWorld()) { return false; } this.manager.Update(this.timeDelta); this.updateData(); return this.preUpdateCore(); }; /** * * * @method Phaser.Creature#_initWebGL * @memberof Phaser.Creature * @private */ Phaser.Creature.prototype._initWebGL = function (renderSession) { // build the strip! var gl = renderSession.gl; this._vertexBuffer = gl.createBuffer(); this._indexBuffer = gl.createBuffer(); this._uvBuffer = gl.createBuffer(); this._colorBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, this._vertexBuffer); gl.bufferData(gl.ARRAY_BUFFER, this.vertices, gl.DYNAMIC_DRAW); gl.bindBuffer(gl.ARRAY_BUFFER, this._uvBuffer); gl.bufferData(gl.ARRAY_BUFFER, this.uvs, gl.DYNAMIC_DRAW); gl.bindBuffer(gl.ARRAY_BUFFER, this._colorBuffer); gl.bufferData(gl.ARRAY_BUFFER, this.colors, gl.STATIC_DRAW); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._indexBuffer); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.indices, gl.STATIC_DRAW); }; /** * @method Phaser.Creature#_renderWebGL * @memberof Phaser.Creature * @private */ Phaser.Creature.prototype._renderWebGL = function (renderSession) { // If the sprite is not visible or the alpha is 0 then no need to render this element if (!this.visible || this.alpha <= 0) { return; } renderSession.spriteBatch.stop(); // init! init! if (!this._vertexBuffer) { this._initWebGL(renderSession); } renderSession.shaderManager.setShader(renderSession.shaderManager.stripShader); this._renderCreature(renderSession); renderSession.spriteBatch.start(); }; /** * @method Phaser.Creature#_renderCreature * @memberof Phaser.Creature * @private */ Phaser.Creature.prototype._renderCreature = function (renderSession) { var gl = renderSession.gl; var projection = renderSession.projection; var offset = renderSession.offset; var shader = renderSession.shaderManager.stripShader; renderSession.blendModeManager.setBlendMode(this.blendMode); // Set uniforms gl.uniformMatrix3fv(shader.translationMatrix, false, this.worldTransform.toArray(true)); gl.uniform2f(shader.projectionVector, projection.x, -projection.y); gl.uniform2f(shader.offsetVector, -offset.x, -offset.y); gl.uniform1f(shader.alpha, this.worldAlpha); if (!this.dirty) { gl.bindBuffer(gl.ARRAY_BUFFER, this._vertexBuffer); gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertices); gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 0, 0); // Update the uvs gl.bindBuffer(gl.ARRAY_BUFFER, this._uvBuffer); gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, 0, 0); gl.activeTexture(gl.TEXTURE0); // Check if a texture is dirty.. if (this.texture.baseTexture._dirty[gl.id]) { renderSession.renderer.updateTexture(this.texture.baseTexture); } else { // Bind the current texture gl.bindTexture(gl.TEXTURE_2D, this.texture.baseTexture._glTextures[gl.id]); } // Don't need to upload! gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._indexBuffer); } else { this.dirty = false; gl.bindBuffer(gl.ARRAY_BUFFER, this._vertexBuffer); gl.bufferData(gl.ARRAY_BUFFER, this.vertices, gl.DYNAMIC_DRAW); gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 0, 0); // Update the uvs gl.bindBuffer(gl.ARRAY_BUFFER, this._uvBuffer); gl.bufferData(gl.ARRAY_BUFFER, this.uvs, gl.DYNAMIC_DRAW); gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, 0, 0); gl.activeTexture(gl.TEXTURE0); // Check if a texture is dirty if (this.texture.baseTexture._dirty[gl.id]) { renderSession.renderer.updateTexture(this.texture.baseTexture); } else { gl.bindTexture(gl.TEXTURE_2D, this.texture.baseTexture._glTextures[gl.id]); } // Don't need to upload! gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._indexBuffer); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.indices, gl.STATIC_DRAW); } gl.drawElements(gl.TRIANGLES, this.indices.length, gl.UNSIGNED_SHORT, 0); }; /** * @method Phaser.Creature#updateCreatureBounds * @memberof Phaser.Creature * @private */ Phaser.Creature.prototype.updateCreatureBounds = function () { // Update bounds based off world transform matrix var target = this.manager.target_creature; target.ComputeBoundaryMinMax(); this.creatureBoundsMin.set(target.boundary_min[0], -target.boundary_min[1]); this.creatureBoundsMax.set(target.boundary_max[0], -target.boundary_max[1]); this.worldTransform.apply(this.creatureBoundsMin, this.creatureBoundsMin); this.worldTransform.apply(this.creatureBoundsMax, this.creatureBoundsMax); }; /** * @method Phaser.Creature#updateData * @memberof Phaser.Creature * @private */ Phaser.Creature.prototype.updateData = function () { var target = this.manager.target_creature; var read_pts = target.render_pts; var read_uvs = target.global_uvs; this.updateRenderData(read_pts, read_uvs); this.updateCreatureBounds(); this.dirty = true; }; /** * @method Phaser.Creature#updateRenderData * @memberof Phaser.Creature * @private */ Phaser.Creature.prototype.updateRenderData = function (verts, uvs) { var target = this.manager.target_creature; var pt_index = 0; var uv_index = 0; var write_pt_index = 0; for (var i = 0; i < target.total_num_pts; i++) { this.vertices[write_pt_index] = verts[pt_index]; this.vertices[write_pt_index + 1] = -verts[pt_index + 1]; this.uvs[uv_index] = uvs[uv_index]; this.uvs[uv_index + 1] = uvs[uv_index + 1]; pt_index += 3; uv_index += 2; write_pt_index += 2; } }; /** * Sets the Animation this Creature object will play, as defined in the mesh data. * * @method Phaser.Creature#setAnimation * @memberof Phaser.Creature * @param {string} key - The key of the animation to set, as defined in the mesh data. */ Phaser.Creature.prototype.setAnimation = function (key) { this.manager.SetActiveAnimationName(key, true); }; /** * Plays the currently set animation. * * @method Phaser.Creature#play * @memberof Phaser.Creature * @param {boolean} [loop=false] - Should the animation loop? */ Phaser.Creature.prototype.play = function (loop) { if (loop === undefined) { loop = false; } this.loop = loop; this.manager.SetIsPlaying(true); this.manager.RunAtTime(0); }; /** * Stops the currently playing animation. * * @method Phaser.Creature#stop * @memberof Phaser.Creature */ Phaser.Creature.prototype.stop = function () { this.manager.SetIsPlaying(false); }; /** * @name Phaser.Creature#isPlaying * @property {boolean} isPlaying - Is the _current_ animation playing? */ Object.defineProperty(Phaser.Creature.prototype, 'isPlaying', { get: function() { return this.manager.GetIsPlaying(); }, set: function(value) { this.manager.SetIsPlaying(value); } }); /** * @name Phaser.Creature#loop * @property {boolean} loop - Should the _current_ animation loop or not? */ Object.defineProperty(Phaser.Creature.prototype, 'loop', { get: function() { return this.manager.should_loop; }, set: function(value) { this.manager.SetShouldLoop(value); } });