shaku
Version:
A simple and effective JavaScript game development framework that knows its place!
235 lines (215 loc) • 8.35 kB
JavaScript
/**
* Define a sprite object we can draw using sprite batches.
*
* |-- copyright and license --|
* @module Shaku
* @file shaku\src\gfx\sprite.js
* @author Ronen Ness (ronenness@gmail.com | http://ronenness.com)
* @copyright (c) 2021 Ronen Ness
* @license MIT
* |-- end copyright and license --|
*
*/
'use strict';
const TextureAsset = require("../assets/texture_asset");
const TextureAssetBase = require("../assets/texture_asset_base");
const Color = require("../utils/color");
const Rectangle = require("../utils/rectangle");
const Vector2 = require("../utils/vector2");
const Vector3 = require("../utils/vector3");
/**
* Sprite class.
*/
class Sprite
{
/**
* Create the sprite object.
* @param {TextureAssetBase} texture Sprite texture.
* @param {Rectangle=} sourceRectangle Optional source rectangle.
*/
constructor(texture, sourceRectangle)
{
/**
* Sprite's texture.
* @name Sprite#texture
* @type {TextureAssetBase}
*/
this.texture = texture;
/**
* Sprite position.
* If Vector3 is provided, the z value will be passed to vertices position in shader code.
* @name Sprite#position
* @type {Vector2|Vector3}
*/
this.position = Vector2.zero();
/**
* Sprite size.
* If Vector3 is provided, the z value will be passed to the bottom vertices position in shader code, as position.z + size.z.
* @name Sprite#size
* @type {Vector2|Vector3}
*/
this.size = new Vector2(100, 100);
/**
* Sprite source rectangle in texture.
* Null will take entire texture.
* @name Sprite#sourceRectangle
* @type {Rectangle}
*/
this.sourceRectangle = sourceRectangle || null;
/**
* Sprite rotation in radians.
* @name Sprite#rotation
* @type {Number}
*/
this.rotation = 0;
/**
* Sprite origin point.
* @name Sprite#origin
* @type {Vector2}
*/
this.origin = new Vector2(0.5, 0.5);
/**
* Skew the sprite corners on X and Y axis, around the origin point.
* @name Sprite#skew
* @type {Vector2}
*/
this.skew = null;
/**
* Sprite color.
* If array is set, will assign each color to different vertex, starting from top-left.
* @name Sprite#color
* @type {Color|Array<Color>}
*/
this.color = Color.white;
}
/**
* Set size to source rectangle size.
* @returns {Sprite} this.
*/
setToSourceRectangleSize()
{
this.size.copy(this.sourceRectangle.getSize());
return this;
}
/**
* Set size to texture size.
* @returns {Sprite} this.
*/
setToTextureSize()
{
this.size.copy(this.texture.getSize());
return this;
}
/**
* Build a sprite from params.
* @param {TextureAssetBase} texture Sprite texture.
* @param {Vector2|Vector3} position Drawing position (at origin). If vector3 is provided, will pass z value to the shader code position attribute.
* @param {Vector2|Vector3|Number} size Drawing size. If vector3 is provided, will pass z value to the shader code position attribute for the bottom vertices, as position.z + size.z.
* @param {Rectangle} sourceRectangle Source rectangle, or undefined to use the entire texture.
* @param {Color|Array<Color>|undefined} color Tint color, or undefined to not change color. If array is set, will assign each color to different vertex, starting from top-left.
* @param {Number=} rotation Rotate sprite.
* @param {Vector2=} origin Drawing origin. This will be the point at 'position' and rotation origin.
* @param {Vector2=} skew Skew the drawing corners on X and Y axis, around the origin point.
* @returns {Sprite} New sprite instance.
*/
static build(texture, position, size, sourceRectangle, color, rotation, origin, skew)
{
let sprite = new Sprite(texture, sourceRectangle);
sprite.position = position;
sprite.size = (typeof size === 'number') ? new Vector2(size, size) : size;
if (color) { sprite.color = color; }
if (rotation) { sprite.rotation = rotation; }
if (origin) { sprite.origin = origin; }
if (skew) { sprite.skew = skew; }
return sprite;
}
/**
* Set the source Rectangle automatically from spritesheet.
* This method get sprite index in sheet and how many sprites there are in total, and calculate the desired
* offset and size in source Rectangle based on it + source image size.
* @param {TextureAssetBase} texture Texture to set source rectangle from.
* @param {Vector2} index Sprite index in spritesheet.
* @param {Vector2} spritesCount How many sprites there are in spritesheet in total.
* @param {Number=} margin How many pixels to trim from the tile (default is 0).
* @param {Boolean=} setSize If true will also set width and height based on source rectangle (default is true).
*/
setSourceFromSpritesheet(texture, index, spritesCount, margin, setSize)
{
if (texture.width === 0 || texture.height === 0) {
throw new Error("Texture has illegal size or is not fully loaded yet!");
}
margin = margin || 0;
let w = texture.width / spritesCount.x;
let h = texture.height / spritesCount.y;
let x = w * index.x + margin;
let y = h * index.y + margin;
w -= 2*margin;
h -= 2*margin;
if (setSize || setSize === undefined) {
this.size.set(w, h)
}
if (this.sourceRectangle) {
this.sourceRectangle.set(x, y, w, h);
} else {
this.sourceRectangle = new Rectangle(x, y, w, h);
}
}
/**
* Clone this sprite.
* @returns {Sprite} cloned sprite.
*/
clone()
{
let sourceRect = this.sourceRectangle ? this.sourceRectangle.clone() : undefined;
let ret = new Sprite(this.texture, sourceRect);
ret.position = this.position.clone();
ret.size = this.size.clone();
ret.rotation = this.rotation || 0;
ret.origin = this.origin ? this.origin.clone() : null;
ret.color = this.color ? this.color.clone() : null;
ret.skew = this.skew ? this.skew.clone() : null;
return ret;
}
/**
* Check if this sprite is flipped around X axis.
* This is just a sugarcoat that returns if size.x < 0.
* @returns {Boolean} If sprite is flipped on X axis.
*/
get flipX()
{
return this.size.x < 0;
}
/**
* Flip sprite around X axis.
* This is just a sugarcoat that set size.x to negative or positive value, without changing its scale.
* @param {Boolean} flip Should we flip the sprite around X axis. If undefined, will take the negative of flipX current value, ie will toggle flipping.
*/
set flipX(flip)
{
if (flip === undefined) flip = !this.flipX;
this.size.x = Math.abs(this.size.x) * (flip ? -1 : 1);
return flip;
}
/**
* Check if this sprite is flipped around y axis.
* This is just a sugarcoat that returns if size.y < 0.
* @returns {Boolean} If sprite is flipped on Y axis.
*/
get flipY()
{
return this.size.y < 0;
}
/**
* Flip sprite around Y axis.
* This is just a sugarcoat that set size.y to negative or positive value, without changing its scale.
* @param {Boolean} flip Should we flip the sprite around Y axis. If undefined, will take the negative of flipY current value, ie will toggle flipping.
*/
set flipY(flip)
{
if (flip === undefined) flip = !this.flipY;
this.size.y = Math.abs(this.size.y) * (flip ? -1 : 1);
return flip;
}
}
// export the sprite class.
module.exports = Sprite;