UNPKG

canvas2djs

Version:

HTML5 canvas based game engine

921 lines (773 loc) 24 kB
import { EventHelper, UIEvent } from '../UIEvent'; import { Color, convertColor, uid } from '../Util'; import { Action } from '../action/Action'; import { Texture } from '../Texture'; import { EventEmitter } from '../EventEmitter'; import { ReleasePool } from '../ReleasePool'; import { Stage } from '../Stage'; import { SpriteProps } from '../createSprite'; // import { CanvasFrameBuffer } from '../framebuffer/CanvasFrameBuffer'; export const RAD_PER_DEG: number = Math.PI / 180; export enum AlignType { TOP, RIGHT, BOTTOM, LEFT, CENTER } export enum BlendMode { SOURCE_IN, SOURCE_OVER, SOURCE_ATOP, SOURCE_OUT, DESTINATION_OVER, DESTINATION_IN, DESTINATION_OUT, DESTINATION_ATOP, LIGHTER, COPY, XOR, } export const BlendModeStrings = { [BlendMode.SOURCE_IN]: "source-in", [BlendMode.SOURCE_OVER]: "source-over", [BlendMode.SOURCE_ATOP]: "source-atop", [BlendMode.SOURCE_OUT]: "source-out", [BlendMode.DESTINATION_OVER]: "destination-over", [BlendMode.DESTINATION_IN]: "destination-in", [BlendMode.DESTINATION_OUT]: "destination-out", [BlendMode.DESTINATION_ATOP]: "destination-atop", [BlendMode.LIGHTER]: "lighter", [BlendMode.COPY]: "copy", [BlendMode.XOR]: "xor", } export class Sprite<T extends ISprite> extends EventEmitter { protected _props: T & SpriteProps; // Define for tsx protected _width: number = 0; protected _height: number = 0; protected _originX: number = 0.5; protected _originY: number = 0.5; protected _rotation: number = 0; protected _rotationInRadians: number = 0; protected _texture: Texture; protected _alignX: AlignType; protected _alignY: AlignType; protected _parent: Sprite<{}>; protected _stage: Stage; protected _top: number; protected _right: number; protected _bottom: number; protected _left: number; protected _percentWidth: number; protected _percentHeight: number; protected _originPixelX: number = 0; protected _originPixelY: number = 0; protected _grid: number[]; // protected _canvasFrameBuffer: CanvasFrameBuffer; id: number; x: number = 0; y: number = 0; scaleX: number = 1; scaleY: number = 1; radius: number = 0; opacity: number = 1; sourceX: number = 0; sourceY: number = 0; sourceWidth: number; sourceHeight: number; autoResize: boolean = true; flippedX: boolean = false; flippedY: boolean = false; visible: boolean = true; clipOverflow: boolean = false; bgColor: Color; borderColor: Color; borderWidth: number; children: Sprite<{}>[]; blendMode: BlendMode; touchEnabled: boolean = true; mouseEnabled: boolean = true; onClick: ISprite["onClick"]; onMouseBegin: ISprite["onMouseBegin"]; onMouseMoved: ISprite["onMouseMoved"]; onMouseEnded: ISprite["onMouseEnded"]; onTouchBegin: ISprite["onTouchBegin"]; onTouchMoved: ISprite["onTouchMoved"]; onTouchEnded: ISprite["onTouchEnded"]; constructor(props?: T & SpriteProps) { super(); this.id = uid(this); // this._canvasFrameBuffer = CanvasFrameBuffer.create(this.id); this._init(props); } protected _init(props?: T & SpriteProps) { if (props) { this.setProps(props); } } setProps(props: T & SpriteProps) { for (let key in props) { this[key as any] = props[key]; } } // get canvasFrameBuffer() { // return this._canvasFrameBuffer; // } get rotationInRadians() { return this._rotationInRadians; } get originPixelX() { return this._originPixelX; } get originPixelY() { return this._originPixelY; } set width(value: number) { if (this._width === value) { return; } this._width = value; this._originPixelX = this._width * this._originX; if (this.left != null || this.right != null) { this._reCalcX(); } else { this._adjustAlignX(); } this._reLayoutChildrenOnWidthChanged(); this._onChildResize(); } get width(): number { return this._width; } set height(value: number) { if (this._height === value) { return; } this._height = value; this._originPixelY = this._height * this._originY; if (this.top != null || this.bottom != null) { this._reCalcY(); } else { this._adjustAlignY(); } this._reLayoutChildrenOnHeightChanged(); this._onChildResize(); } get height(): number { return this._height; } set originX(value: number) { if (this._originX === value) { return; } this._originX = value; this._originPixelX = this._originX * this._width; if (this.left != null || this.right != null) { this._reCalcX(); } else { this._adjustAlignX(); } } get originX(): number { return this._originX; } set originY(value: number) { if (this._originY === value) { return; } this._originY = value; this._originPixelY = this._originY * this._height; if (this.top != null || this.bottom != null) { this._reCalcY(); } else { this._adjustAlignY(); } } get originY(): number { return this._originY; } get top() { return this._top; } set top(top: number) { this.autoResize = false; this._top = top; this._resizeHeight(); } get right() { return this._right; } set right(right: number) { this.autoResize = false; this._right = right; this._resizeWidth(); } get bottom() { return this._bottom; } set bottom(bottom: number) { this.autoResize = false; this._bottom = bottom; this._resizeHeight(); } get left() { return this._left; } set left(left: number) { this.autoResize = false; this._left = left; this._resizeWidth(); } get percentWidth() { return this._percentWidth; } set percentWidth(percentWidth: number) { this.autoResize = false; this._percentWidth = percentWidth; this._resizeWidth(); } get percentHeight() { return this._percentHeight; } set percentHeight(percentHeight: number) { this.autoResize = false; this._percentHeight = percentHeight; this._resizeHeight(); } get grid() { return this._grid; } set grid(grid: number[]) { this._grid = grid; this.autoResize = false; } set rotation(value: number) { if (this._rotation === value) { return; } this._rotation = value; this._rotationInRadians = this._rotation * RAD_PER_DEG; } get rotation(): number { return this._rotation; } set texture(value: Texture | string) { let texture: Texture; if (typeof value === 'string') { texture = Texture.create(value); } else { texture = value; } if (texture === this._texture) { return; } this._texture = texture; if (!this.autoResize) { return; } if (texture) { if (texture.ready) { this.width = texture.width; this.height = texture.height; } else { texture.onReady((size) => { if (this.autoResize || (this.width === 0 && this.height === 0)) { this.width = size.width; this.height = size.height; } }); } } else { this.width = 0; this.height = 0; } } get texture() { return this._texture; } set parent(sprite: Sprite<{}>) { if (sprite === this._parent) { return; } this._parent = sprite; } get parent() { return this._parent; } get stage() { return this._stage; } set stage(stage: Stage) { if (stage == null) { this.emit(UIEvent.REMOVED_FROM_STAGE); this._stage = stage; } else { this._stage = stage; this.emit(UIEvent.ADD_TO_STAGE); } if (this.children) { for (let i = 0, child: Sprite<{}>; child = this.children[i]; i++) { child.stage = stage; } } } set alignX(value: AlignType) { if (this._alignX === value || value === AlignType.BOTTOM || value === AlignType.TOP) { return; } this._alignX = value; this._adjustAlignX(); } get alignX() { return this._alignX; } set alignY(value: AlignType) { if (this._alignY === value || value === AlignType.LEFT || value === AlignType.RIGHT) { return; } this._alignY = value; this._adjustAlignY(); } get alignY() { return this._alignY; } protected _onChildResize() { if (this.parent) { this.parent._onChildResize(); } } protected _update(deltaTime: number): void { this.emit(UIEvent.FRAME, deltaTime); this.update(deltaTime); if (this.children && this.children.length) { let list = this.children.slice(); for (let i = 0, child: Sprite<{}>; child = list[i]; i++) { child._update(deltaTime); } } } protected _reLayoutChildrenOnWidthChanged() { if (!this.children || !this.children.length) { return; } for (let i = 0, child: Sprite<{}>; child = this.children[i]; i++) { child._resizeWidth(); child._adjustAlignX(); } } protected _reLayoutChildrenOnHeightChanged() { if (!this.children || !this.children.length) { return; } for (let i = 0, child: Sprite<{}>; child = this.children[i]; i++) { child._resizeHeight(); child._adjustAlignY(); } } protected _resizeWidth() { if (this.parent == null) { return; } const { parent, percentWidth, right, left } = this; if (left != null && right != null) { this.width = parent.width - left - right; } else if (percentWidth != null) { this.width = parent.width * percentWidth; } this._reCalcX(); } protected _reCalcX() { const { left, right, parent, width, _originPixelX } = this; if (left != null) { this.x = left + _originPixelX; } else if (right != null && parent != null) { this.x = parent.width - (width + right - _originPixelX); } } protected _resizeHeight() { if (this.parent == null) { return; } const { parent, percentHeight, top, bottom } = this; if (top != null && bottom != null) { this.height = parent.height - top - bottom; this.y = top + this._originPixelY; return; } if (percentHeight != null) { this.height = parent.height * percentHeight; } this._reCalcY(); } protected _reCalcY() { const { top, bottom, parent, height, _originPixelY } = this; if (top != null) { this.y = top + _originPixelY; } else if (bottom != null && parent != null) { this.y = parent.height - (height + bottom - _originPixelY); } } protected _adjustAlignX() { if (!this.parent || this._alignX == null || this.left != null || this.right != null) { return false; } let x: number; let ox = this._originPixelX; switch (this._alignX) { case AlignType.LEFT: x = ox; break; case AlignType.RIGHT: x = this.parent.width - (this.width - ox); break; case AlignType.CENTER: x = this.parent.width * 0.5 + ox - this.width * 0.5; break; } if (x != null) { this.x = x; } return true; } protected _adjustAlignY() { if (!this.parent || this._alignY == null || this.top != null || this.bottom != null) { return false; } let y: number; let oy = this._originPixelY; switch (this._alignY) { case AlignType.TOP: y = oy; break; case AlignType.BOTTOM: y = this.parent.height - (this.height - oy); break; case AlignType.CENTER: y = this.parent.height * 0.5 + oy - this.height * 0.5; break; } if (y != null) { this.y = y; } return true; } protected _visit(context: CanvasRenderingContext2D): void { if (!this.visible || this.opacity === 0) { return; } var sx: number = this.scaleX; var sy: number = this.scaleY; context.save(); if (this.blendMode != null) { context.globalCompositeOperation = BlendModeStrings[this.blendMode]; } if (this.x !== 0 || this.y !== 0) { context.translate(this.x, this.y); } if (this.opacity !== 1) { context.globalAlpha = this.opacity; } if (this.flippedX) { sx = -sx; } if (this.flippedY) { sy = -sy; } if (sx !== 1 || sy !== 1) { context.scale(sx, sy); } if (this.rotation % 360 !== 0) { context.rotate(this._rotationInRadians); } if ((this._width !== 0 && this._height !== 0) || this.radius > 0) { this.draw(context); } this._visitChildren(context); context.restore(); } protected _visitChildren(context: CanvasRenderingContext2D): void { if (!this.children || !this.children.length) { return; } if (this._originPixelX !== 0 || this._originPixelY !== 0) { context.translate(-this._originPixelX, -this._originPixelY); } for (let i = 0, child: Sprite<{}>; child = this.children[i]; i++) { child._visit(context); } } protected _clip(context: CanvasRenderingContext2D) { if (!this.clipOverflow) { return; } context.beginPath(); if (this.radius > 0) { context.arc(0, 0, this.radius, 0, Math.PI * 2, true); } else { context.rect(-this._originPixelX, -this._originPixelY, this._width, this._height); } context.closePath(); context.clip(); } protected _drawBgColor(context: CanvasRenderingContext2D): void { if (this.bgColor == null) { return; } context.fillStyle = convertColor(this.bgColor); context.beginPath(); if (this.radius > 0) { context.arc(0, 0, this.radius, 0, Math.PI * 2, true); } else { context.rect(-this._originPixelX, -this._originPixelY, this._width, this._height); } context.closePath(); context.fill(); } protected _drawBorder(context: CanvasRenderingContext2D): void { if (this.borderColor != null) { context.lineWidth = this.borderWidth || 1; context.strokeStyle = convertColor(this.borderColor || 0x000); context.beginPath(); if (this.radius > 0) { context.arc(0, 0, this.radius, 0, Math.PI * 2, true); } else { context.rect(-this._originPixelX, -this._originPixelY, this._width, this._height); } context.closePath(); context.stroke(); } } protected draw(context: CanvasRenderingContext2D): void { this._clip(context); this._drawBgColor(context); this._drawBorder(context); let texture = this._texture; if (!texture || !texture.ready || texture.width === 0 || texture.height === 0) { return; } let sx: number = this.sourceX; let sy: number = this.sourceY; let sw: number = this.sourceWidth == null ? texture.width : this.sourceWidth; let sh: number = this.sourceHeight == null ? texture.height : this.sourceHeight; let w = this.width; let h = this.height; let ox = this._originPixelX; let oy = this._originPixelY; let grid = this.grid; if (!Array.isArray(grid)) { context.drawImage(texture.source, sx, sy, sw, sh, -ox, -oy, w, h); } else { let gridSource = (<Texture>this.texture).createGridSource(w, h, sx, sy, sw, sh, grid); context.drawImage(gridSource, -ox, -oy, w, h); } } addChild(target: Sprite<{}>, position?: number): void { if (target.parent) { throw new Error("canvas2d.Sprite.addChild(): Child has been added."); } if (!this.children) { this.children = []; } var children: Sprite<{}>[] = this.children; if (children.indexOf(target) < 0) { if (position > -1 && position < children.length) { children.splice(position, 0, target); } else { children.push(target); } target.parent = this; if (this.stage) { target.stage = this.stage; } target._resizeWidth(); target._resizeHeight(); target._adjustAlignX(); target._adjustAlignY(); } } addChildren(...children: Sprite<{}>[]) { for (let i = 0, child: Sprite<{}>; child = children[i]; i++) { this.addChild(child); } } removeChild(target: Sprite<{}>): void { if (!this.children || !this.children.length) { return; } var index = this.children.indexOf(target); if (index > -1) { this.children.splice(index, 1); target.parent = null; target.stage = null; } } removeChildren(...children: Sprite<{}>[]) { for (let i = 0, child: Sprite<{}>; child = children[i]; i++) { this.removeChild(child); } } removeAllChildren(recusive?: boolean): void { if (!this.children || !this.children.length) { return; } while (this.children.length) { var sprite: Sprite<{}> = this.children[0]; if (recusive) { sprite.removeAllChildren(true); Action.stop(sprite); } this.removeChild(sprite); } this.children = null; } replaceChild(oldChild: Sprite<{}>, ...newChildren: Sprite<{}>[]) { if (!this.children || !this.children.length) { return; } let index = this.children.indexOf(oldChild); if (index < 0) { return; } this.removeChild(oldChild); // this.addChild(newChild, index); for (let i = 0, child: Sprite<{}>; child = newChildren[i]; i++) { this.addChild(child, index++); } } contains(target: Sprite<{}>) { let children = this.children; if (!children || !children.length) { return false; } if (children.indexOf(target) > -1) { return true; } for (let i = 0, child: Sprite<{}>; child = children[i]; i++) { if (child.contains(target)) { return true; } } return false; } release(recusive?: boolean) { Action.stop(this); if (recusive && this.children) { while (this.children.length) { this.children[0].release(recusive); } } else { this.removeAllChildren(); } if (this.parent) { this.parent.removeChild(this); } // CanvasFrameBuffer.remove(this.id); ReleasePool.instance.add(this); this.removeAllListeners(); } update(deltaTime: number): any { } } export interface ISprite { x?: number; y?: number; width?: number; height?: number; scaleX?: number; scaleY?: number; originX?: number; originY?: number; bgColor?: Color; radius?: number; borderWidth?: number; borderColor?: Color; texture?: Texture | string; rotation?: number; opacity?: number; visible?: boolean; alignX?: AlignType, alignY?: AlignType, flippedX?: boolean; flippedY?: boolean; clipOverflow?: boolean; top?: number; right?: number; bottom?: number; left?: number; percentWidth?: number; percentHeight?: number; grid?: number[]; /** * Position X of the clipping rect on texture */ sourceX?: number; /** * Position Y of the clipping rect on texture */ sourceY?: number; /** * Width of the clipping rect on texture */ sourceWidth?: number; /** * Height of the clipping rect on texture */ sourceHeight?: number; blendMode?: BlendMode; /** * Auto resize by the texture */ autoResize?: boolean; touchEnabled?: boolean; mouseEnabled?: boolean; /** * Sprite would call this method each frame * @param deltaTime Duration between now and last frame */ update?(deltaTime: number): any; /** * Click event handler */ onClick?(e: EventHelper, event: MouseEvent): any; /** * Mouse begin event handler */ onMouseBegin?(e: EventHelper, event: MouseEvent): any; /** * Mouse moved event handler */ onMouseMoved?(e: EventHelper, event: MouseEvent): any; /** * Mouse ended event handler */ onMouseEnded?(e: EventHelper, event: MouseEvent): any; /** * Touch begin event handler */ onTouchBegin?(touches: EventHelper[], event: TouchEvent): any; /** * Touch moved event handler */ onTouchMoved?(touches: EventHelper[], event: TouchEvent): any; /** * Touch ended event hadndler */ onTouchEnded?(touches: EventHelper[], event: TouchEvent): any; }