phaser/src/textures/Texture.js

525 lines
15 KiB
JavaScript

/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2013-2023 Photon Storm Ltd.
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
var Class = require('../utils/Class');
var Frame = require('./Frame');
var TextureSource = require('./TextureSource');
var TEXTURE_MISSING_ERROR = 'Texture "%s" has no frame "%s"';
/**
* @classdesc
* A Texture consists of a source, usually an Image from the Cache, and a collection of Frames.
* The Frames represent the different areas of the Texture. For example a texture atlas
* may have many Frames, one for each element within the atlas. Where-as a single image would have
* just one frame, that encompasses the whole image.
*
* Every Texture, no matter where it comes from, always has at least 1 frame called the `__BASE` frame.
* This frame represents the entirety of the source image.
*
* Textures are managed by the global TextureManager. This is a singleton class that is
* responsible for creating and delivering Textures and their corresponding Frames to Game Objects.
*
* Sprites and other Game Objects get the texture data they need from the TextureManager.
*
* @class Texture
* @memberof Phaser.Textures
* @constructor
* @since 3.0.0
*
* @param {Phaser.Textures.TextureManager} manager - A reference to the Texture Manager this Texture belongs to.
* @param {string} key - The unique string-based key of this Texture.
* @param {(HTMLImageElement|HTMLCanvasElement|HTMLImageElement[]|HTMLCanvasElement[]|Phaser.Renderer.WebGL.Wrappers.WebGLTextureWrapper)} source - An array of sources that are used to create the texture. Usually Images, but can also be a Canvas.
* @param {number} [width] - The width of the Texture. This is optional and automatically derived from the source images.
* @param {number} [height] - The height of the Texture. This is optional and automatically derived from the source images.
*/
var Texture = new Class({
initialize:
function Texture (manager, key, source, width, height)
{
if (!Array.isArray(source))
{
source = [ source ];
}
/**
* A reference to the Texture Manager this Texture belongs to.
*
* @name Phaser.Textures.Texture#manager
* @type {Phaser.Textures.TextureManager}
* @since 3.0.0
*/
this.manager = manager;
/**
* The unique string-based key of this Texture.
*
* @name Phaser.Textures.Texture#key
* @type {string}
* @since 3.0.0
*/
this.key = key;
/**
* An array of TextureSource instances.
* These are unique to this Texture and contain the actual Image (or Canvas) data.
*
* @name Phaser.Textures.Texture#source
* @type {Phaser.Textures.TextureSource[]}
* @since 3.0.0
*/
this.source = [];
/**
* An array of TextureSource data instances.
* Used to store additional data images, such as normal maps or specular maps.
*
* @name Phaser.Textures.Texture#dataSource
* @type {array}
* @since 3.0.0
*/
this.dataSource = [];
/**
* A key-value object pair associating the unique Frame keys with the Frames objects.
*
* @name Phaser.Textures.Texture#frames
* @type {object}
* @since 3.0.0
*/
this.frames = {};
/**
* Any additional data that was set in the source JSON (if any),
* or any extra data you'd like to store relating to this texture
*
* @name Phaser.Textures.Texture#customData
* @type {object}
* @since 3.0.0
*/
this.customData = {};
/**
* The name of the first frame of the Texture.
*
* @name Phaser.Textures.Texture#firstFrame
* @type {string}
* @since 3.0.0
*/
this.firstFrame = '__BASE';
/**
* The total number of Frames in this Texture, including the `__BASE` frame.
*
* A Texture will always contain at least 1 frame because every Texture contains a `__BASE` frame by default,
* in addition to any extra frames that have been added to it, such as when parsing a Sprite Sheet or Texture Atlas.
*
* @name Phaser.Textures.Texture#frameTotal
* @type {number}
* @default 0
* @since 3.0.0
*/
this.frameTotal = 0;
// Load the Sources
for (var i = 0; i < source.length; i++)
{
this.source.push(new TextureSource(this, source[i], width, height));
}
},
/**
* Adds a new Frame to this Texture.
*
* A Frame is a rectangular region of a TextureSource with a unique index or string-based key.
*
* The name given must be unique within this Texture. If it already exists, this method will return `null`.
*
* @method Phaser.Textures.Texture#add
* @since 3.0.0
*
* @param {(number|string)} name - The name of this Frame. The name is unique within the Texture.
* @param {number} sourceIndex - The index of the TextureSource that this Frame is a part of.
* @param {number} x - The x coordinate of the top-left of this Frame.
* @param {number} y - The y coordinate of the top-left of this Frame.
* @param {number} width - The width of this Frame.
* @param {number} height - The height of this Frame.
*
* @return {?Phaser.Textures.Frame} The Frame that was added to this Texture, or `null` if the given name already exists.
*/
add: function (name, sourceIndex, x, y, width, height)
{
if (this.has(name))
{
return null;
}
var frame = new Frame(this, name, sourceIndex, x, y, width, height);
this.frames[name] = frame;
// Set the first frame of the Texture (other than __BASE)
// This is used to ensure we don't spam the display with entire
// atlases of sprite sheets, but instead just the first frame of them
// should the dev incorrectly specify the frame index
if (this.firstFrame === '__BASE')
{
this.firstFrame = name;
}
this.frameTotal++;
return frame;
},
/**
* Removes the given Frame from this Texture. The Frame is destroyed immediately.
*
* Any Game Objects using this Frame should stop using it _before_ you remove it,
* as it does not happen automatically.
*
* @method Phaser.Textures.Texture#remove
* @since 3.19.0
*
* @param {string} name - The key of the Frame to remove.
*
* @return {boolean} True if a Frame with the matching key was removed from this Texture.
*/
remove: function (name)
{
if (this.has(name))
{
var frame = this.get(name);
frame.destroy();
delete this.frames[name];
return true;
}
return false;
},
/**
* Checks to see if a Frame matching the given key exists within this Texture.
*
* @method Phaser.Textures.Texture#has
* @since 3.0.0
*
* @param {string} name - The key of the Frame to check for.
*
* @return {boolean} True if a Frame with the matching key exists in this Texture.
*/
has: function (name)
{
return this.frames.hasOwnProperty(name);
},
/**
* Gets a Frame from this Texture based on either the key or the index of the Frame.
*
* In a Texture Atlas Frames are typically referenced by a key.
* In a Sprite Sheet Frames are referenced by an index.
* Passing no value for the name returns the base texture.
*
* @method Phaser.Textures.Texture#get
* @since 3.0.0
*
* @param {(string|number)} [name] - The string-based name, or integer based index, of the Frame to get from this Texture.
*
* @return {Phaser.Textures.Frame} The Texture Frame.
*/
get: function (name)
{
// null, undefined, empty string, zero
if (!name)
{
name = this.firstFrame;
}
var frame = this.frames[name];
if (!frame)
{
console.warn(TEXTURE_MISSING_ERROR, this.key, name);
frame = this.frames[this.firstFrame];
}
return frame;
},
/**
* Takes the given TextureSource and returns the index of it within this Texture.
* If it's not in this Texture, it returns -1.
* Unless this Texture has multiple TextureSources, such as with a multi-atlas, this
* method will always return zero or -1.
*
* @method Phaser.Textures.Texture#getTextureSourceIndex
* @since 3.0.0
*
* @param {Phaser.Textures.TextureSource} source - The TextureSource to check.
*
* @return {number} The index of the TextureSource within this Texture, or -1 if not in this Texture.
*/
getTextureSourceIndex: function (source)
{
for (var i = 0; i < this.source.length; i++)
{
if (this.source[i] === source)
{
return i;
}
}
return -1;
},
/**
* Returns an array of all the Frames in the given TextureSource.
*
* @method Phaser.Textures.Texture#getFramesFromTextureSource
* @since 3.0.0
*
* @param {number} sourceIndex - The index of the TextureSource to get the Frames from.
* @param {boolean} [includeBase=false] - Include the `__BASE` Frame in the output array?
*
* @return {Phaser.Textures.Frame[]} An array of Texture Frames.
*/
getFramesFromTextureSource: function (sourceIndex, includeBase)
{
if (includeBase === undefined) { includeBase = false; }
var out = [];
for (var frameName in this.frames)
{
if (frameName === '__BASE' && !includeBase)
{
continue;
}
var frame = this.frames[frameName];
if (frame.sourceIndex === sourceIndex)
{
out.push(frame);
}
}
return out;
},
/**
* Returns an array with all of the names of the Frames in this Texture.
*
* Useful if you want to randomly assign a Frame to a Game Object, as you can
* pick a random element from the returned array.
*
* @method Phaser.Textures.Texture#getFrameNames
* @since 3.0.0
*
* @param {boolean} [includeBase=false] - Include the `__BASE` Frame in the output array?
*
* @return {string[]} An array of all Frame names in this Texture.
*/
getFrameNames: function (includeBase)
{
if (includeBase === undefined) { includeBase = false; }
var out = Object.keys(this.frames);
if (!includeBase)
{
var idx = out.indexOf('__BASE');
if (idx !== -1)
{
out.splice(idx, 1);
}
}
return out;
},
/**
* Given a Frame name, return the source image it uses to render with.
*
* This will return the actual DOM Image or Canvas element.
*
* @method Phaser.Textures.Texture#getSourceImage
* @since 3.0.0
*
* @param {(string|number)} [name] - The string-based name, or integer based index, of the Frame to get from this Texture.
*
* @return {(HTMLImageElement|HTMLCanvasElement|Phaser.GameObjects.RenderTexture)} The DOM Image, Canvas Element or Render Texture.
*/
getSourceImage: function (name)
{
if (name === undefined || name === null || this.frameTotal === 1)
{
name = '__BASE';
}
var frame = this.frames[name];
if (frame)
{
return frame.source.image;
}
else
{
console.warn(TEXTURE_MISSING_ERROR, this.key, name);
return this.frames['__BASE'].source.image;
}
},
/**
* Given a Frame name, return the data source image it uses to render with.
* You can use this to get the normal map for an image for example.
*
* This will return the actual DOM Image.
*
* @method Phaser.Textures.Texture#getDataSourceImage
* @since 3.7.0
*
* @param {(string|number)} [name] - The string-based name, or integer based index, of the Frame to get from this Texture.
*
* @return {(HTMLImageElement|HTMLCanvasElement)} The DOM Image or Canvas Element.
*/
getDataSourceImage: function (name)
{
if (name === undefined || name === null || this.frameTotal === 1)
{
name = '__BASE';
}
var frame = this.frames[name];
var idx;
if (!frame)
{
console.warn(TEXTURE_MISSING_ERROR, this.key, name);
idx = this.frames['__BASE'].sourceIndex;
}
else
{
idx = frame.sourceIndex;
}
return this.dataSource[idx].image;
},
/**
* Adds a data source image to this Texture.
*
* An example of a data source image would be a normal map, where all of the Frames for this Texture
* equally apply to the normal map.
*
* @method Phaser.Textures.Texture#setDataSource
* @since 3.0.0
*
* @param {(HTMLImageElement|HTMLCanvasElement|HTMLImageElement[]|HTMLCanvasElement[])} data - The source image.
*/
setDataSource: function (data)
{
if (!Array.isArray(data))
{
data = [ data ];
}
for (var i = 0; i < data.length; i++)
{
var source = this.source[i];
this.dataSource.push(new TextureSource(this, data[i], source.width, source.height));
}
},
/**
* Sets the Filter Mode for this Texture.
*
* The mode can be either Linear, the default, or Nearest.
*
* For pixel-art you should use Nearest.
*
* The mode applies to the entire Texture, not just a specific Frame of it.
*
* @method Phaser.Textures.Texture#setFilter
* @since 3.0.0
*
* @param {Phaser.Textures.FilterMode} filterMode - The Filter Mode.
*/
setFilter: function (filterMode)
{
var i;
for (i = 0; i < this.source.length; i++)
{
this.source[i].setFilter(filterMode);
}
for (i = 0; i < this.dataSource.length; i++)
{
this.dataSource[i].setFilter(filterMode);
}
},
/**
* Destroys this Texture and releases references to its sources and frames.
*
* @method Phaser.Textures.Texture#destroy
* @since 3.0.0
*/
destroy: function ()
{
var i;
var source = this.source;
var dataSource = this.dataSource;
for (i = 0; i < source.length; i++)
{
if (source[i])
{
source[i].destroy();
}
}
for (i = 0; i < dataSource.length; i++)
{
if (dataSource[i])
{
dataSource[i].destroy();
}
}
for (var frameName in this.frames)
{
var frame = this.frames[frameName];
if (frame)
{
frame.destroy();
}
}
this.source = [];
this.dataSource = [];
this.frames = {};
this.manager.removeKey(this.key);
this.manager = null;
}
});
module.exports = Texture;